ADDED .dockerignore Index: .dockerignore ================================================================== --- /dev/null +++ .dockerignore @@ -0,0 +1,27 @@ +_FOSSIL_ +.fslckout +ajax +art +autosetup +bld +compat +debian +fossil +fossil.exe +msvcbld +setup +src +test +tools +win +wbld +win +www +*.a +*.lib +*.log +*.manifest +*.o +*.obj +*.pdb +*.res ADDED .editorconfig Index: .editorconfig ================================================================== --- /dev/null +++ .editorconfig @@ -0,0 +1,14 @@ +# EditorConfig (https://editorconfig.com) Configuration for Fossil +# +# Following https://fossil-scm.org/fossil/doc/trunk/www/style.wiki +# + +# Defaults for all files +[*] +end_of_line = lf +insert_final_newline = true +indent_style = space +indent_size = 2 + +[{Makefile,Makefile.*,*.mk}] +indent_style = tab ADDED .fossil-settings/binary-glob Index: .fossil-settings/binary-glob ================================================================== --- /dev/null +++ .fossil-settings/binary-glob @@ -0,0 +1,12 @@ +*.gif +*.ico +*.jpg +*.odp +*.dia +*.pdf +*.png +*.wav +compat/zlib/contrib/blast/test.pk +compat/zlib/contrib/dotzlib/DotZLib.chm +compat/zlib/contrib/puff/zeros.raw +compat/zlib/zlib.3.pdf ADDED .fossil-settings/clean-glob Index: .fossil-settings/clean-glob ================================================================== --- /dev/null +++ .fossil-settings/clean-glob @@ -0,0 +1,20 @@ +*.a +*.lib +*.manifest +*.o +*.obj +*.pdb +*.res +Makefile +autosetup/jimsh0 +autosetup/jimsh0.exe +bld/* +msvcbld/* +wbld/* +win/*.c +win/*.h +win/*.exe +win/headers +win/linkopts +autoconfig.h +config.log ADDED .fossil-settings/crlf-glob Index: .fossil-settings/crlf-glob ================================================================== --- /dev/null +++ .fossil-settings/crlf-glob @@ -0,0 +1,5 @@ +compat/zlib/* +setup/fossil.iss +test/th1-docs-input.txt +test/th1-hooks-input.txt +win/buildmsvc.bat ADDED .fossil-settings/encoding-glob Index: .fossil-settings/encoding-glob ================================================================== --- /dev/null +++ .fossil-settings/encoding-glob @@ -0,0 +1,4 @@ +compat/zlib/contrib/dotzlib/DotZLib/*.cs +test/utf16be.txt +test/utf16le.txt +win/fossil.rc ADDED .fossil-settings/ignore-glob Index: .fossil-settings/ignore-glob ================================================================== --- /dev/null +++ .fossil-settings/ignore-glob @@ -0,0 +1,7 @@ +compat/openssl* +compat/tcl* +fossil +fossil.exe +win/fossil.exe +*shell-see.* +*sqlite3-see.* ADDED .project Index: .project ================================================================== --- /dev/null +++ .project @@ -0,0 +1,11 @@ + + + fossil + + + + + + + + ADDED .settings/org.eclipse.core.resources.prefs Index: .settings/org.eclipse.core.resources.prefs ================================================================== --- /dev/null +++ .settings/org.eclipse.core.resources.prefs @@ -0,0 +1,2 @@ +eclipse.preferences.version=1 +encoding/=UTF-8 ADDED .settings/org.eclipse.core.runtime.prefs Index: .settings/org.eclipse.core.runtime.prefs ================================================================== --- /dev/null +++ .settings/org.eclipse.core.runtime.prefs @@ -0,0 +1,2 @@ +eclipse.preferences.version=1 +line.separator=\n Index: BUILD.txt ================================================================== --- BUILD.txt +++ BUILD.txt @@ -1,56 +1,78 @@ -All of the source code for fossil is contained in the src/ subdirectory. -But there is a lot of generated code, so you will probably want to -use the Makefile. To do a complete build, just type: - - make - -That should work out-of-the-box on Macs and Linux systems. If you are -building on a Windows box, install MinGW as well as MinGW's make (or -MSYS). You can then type: - - make -f Makefile.w32 +To do a complete build, just type: + + ./configure; make + +The ./configure script builds Makefile from Makefile.in based on +your system and any options you select (run "./configure --help" +for a listing of the available options.) + +If you wish to use the original Makefile with no configuration, you can +instead use: + + make -f Makefile.classic + +On a windows box, use one of the Makefiles in the win/ subdirectory, +according to your compiler and environment. If you have MinGW or +MinGW-w64 installed on your system (Msys or Cygwin, or as +cross-compile environment on Linux or Darwin), then consider: + + make -f win/Makefile.mingw + +If you have VC++ installed on your system, then consider: + + cd win; nmake /f Makefile.msc If you have trouble, or you want to do something fancy, just look at -top level makefile. There are 6 configuration options that are all well -commented. Instead of editing the Makefile, consider copying the Makefile -to an alternative name such as "GNUMakefile", "BSDMakefile", or "makefile" -and editing the copy. +Makefile.classic. There are 6 configuration options that are all well +commented. Instead of editing the Makefile.classic, consider copying +Makefile.classic to an alternative name such as "GNUMakefile", +"BSDMakefile", or "makefile" and editing the copy. + -Out of source builds? --------------------------------------------------------------------------- +BUILDING OUTSIDE THE SOURCE TREE An out of source build is pretty easy: - 1. Make a new directory to do the builds in. - 2. Copy "Makefile" from the source into the build directory and - modify the SRCDIR macro along the lines of: - - SRCDIR=../src - - 3. type: "make" - -This will now keep all generates files seperate from the maintained + 1. Make and change to a new directory to do the builds in. + 2. Run the "configure" script from this directory. + 3. Type: "make" + +For example: + + mkdir build + cd build + ../configure + make + +This will now keep all generates files separate from the maintained source code. -------------------------------------------------------------------------- Here are some notes on what is happening behind the scenes: + +* The configure script (if used) examines the options given + and runs various tests with the C compiler to create Makefile + from the Makefile.in template as well as autoconfig.h * The Makefile just sets up a few macros and then invokes the real makefile in src/main.mk. The src/main.mk makefile is automatically generated by a TCL script found at src/makemake.tcl. Do not edit src/main.mk directly. Update src/makemake.tcl and then rerun it. * The *.h header files are automatically generated using a program called "makeheaders". Source code to the makeheaders program is - found in src/makeheaders.c. Documentation is found in + found in src/makeheaders.c. Documentation is found in src/makeheaders.html. * Most *.c source files are preprocessed using a program called "translate". The sources to translate are found in src/translate.c. A header comment in src/translate.c explains in detail what it does. * The src/mkindex.c program generates some C code that implements static lookup tables. See the header comment in the source code for details on what it does. + +Additional information on the build process is available from +http://fossil-scm.org/home/doc/trunk/www/makefile.wiki Index: COPYRIGHT-BSD2.txt ================================================================== --- COPYRIGHT-BSD2.txt +++ COPYRIGHT-BSD2.txt @@ -1,31 +1,31 @@ Copyright 2007 D. Richard Hipp. All rights reserved. -Redistribution and use in source and binary forms, with or -without modification, are permitted provided that the +Redistribution and use in source and binary forms, with or +without modification, are permitted provided that the following conditions are met: 1. Redistributions of source code must retain the above - copyright notice, this list of conditions and the + copyright notice, this list of conditions and the following disclaimer. 2. 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 AUTHORS ``AS IS'' AND ANY EXPRESS -OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED +THIS SOFTWARE IS PROVIDED BY THE AUTHORS ``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 AUTHORS OR CONTRIBUTORS BE +ARE DISCLAIMED. IN NO EVENT SHALL THE AUTHORS 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 +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, +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. -The views and conclusions contained in the software and documentation -are those of the authors and contributors and should not be interpreted +The views and conclusions contained in the software and documentation +are those of the authors and contributors and should not be interpreted as representing official policies, either expressed or implied, of anybody else. ADDED Dockerfile Index: Dockerfile ================================================================== --- /dev/null +++ Dockerfile @@ -0,0 +1,26 @@ +### +# Dockerfile for Fossil +### +FROM fedora:29 + +### Now install some additional parts we will need for the build +RUN dnf update -y && dnf install -y gcc make tcl tcl-devel zlib-devel openssl-devel tar && dnf clean all && groupadd -r fossil -g 433 && useradd -u 431 -r -g fossil -d /opt/fossil -s /sbin/nologin -c "Fossil user" fossil + +### If you want to build "trunk", change the next line accordingly. +ENV FOSSIL_INSTALL_VERSION release + +RUN curl "https://fossil-scm.org/home/tarball/fossil-src.tar.gz?name=fossil-src&uuid=${FOSSIL_INSTALL_VERSION}" | tar zx +RUN cd fossil-src && ./configure --disable-fusefs --json --with-th1-docs --with-th1-hooks --with-tcl=1 --with-tcl-stubs --with-tcl-private-stubs +RUN cd fossil-src/src && mv main.c main.c.orig && sed s/\"now\"/0/ main.c +RUN cd fossil-src && make && strip fossil && cp fossil /usr/bin && cd .. && rm -rf fossil-src && chmod a+rx /usr/bin/fossil && mkdir -p /opt/fossil && chown fossil:fossil /opt/fossil + +### Build is done, remove modules no longer needed +RUN dnf remove -y gcc make zlib-devel tcl-devel openssl-devel tar && dnf clean all + +USER fossil + +ENV HOME /opt/fossil + +EXPOSE 8080 + +CMD ["/usr/bin/fossil", "server", "--create", "--user", "admin", "/opt/fossil/repository.fossil"] DELETED Makefile Index: Makefile ================================================================== --- Makefile +++ /dev/null @@ -1,56 +0,0 @@ -#!/usr/bin/make -# -#### The toplevel directory of the source tree. Fossil can be built -# in a directory that is separate from the source tree. Just change -# the following to point from the build directory to the src/ folder. -# -SRCDIR = ./src - -#### The directory into which object code files should be written. -# -# -OBJDIR = ./obj - -#### C Compiler and options for use in building executables that -# will run on the platform that is doing the build. This is used -# to compile code-generator programs as part of the build process. -# See TCC below for the C compiler for building the finished binary. -# -BCC = gcc -g -O2 - -#### The suffix to add to executable files. ".exe" for windows. -# Nothing for unix. -# -E = - -#### C Compile and options for use in building executables that -# will run on the target platform. This is usually the same -# as BCC, unless you are cross-compiling. This C compiler builds -# the finished binary for fossil. The BCC compiler above is used -# for building intermediate code-generator tools. -# -#TCC = gcc -O6 -#TCC = gcc -g -O0 -Wall -fprofile-arcs -ftest-coverage -TCC = gcc -g -Os -Wall - -#### Extra arguments for linking the finished binary. Fossil needs -# to link against the Z-Lib compression library. There are no -# other dependencies. We sometimes add the -static option here -# so that we can build a static executable that will run in a -# chroot jail. -# -LIB = -lz $(LDFLAGS) -# If you're on OpenSolaris: -# LIB += lsocket -# Solaris 10 needs: -# LIB += -lsocket -lnsl -# My assumption is that the Sol10 flags will work for Sol8/9 and possibly 11. -# - -#### Tcl shell for use in running the fossil testsuite. -# -TCLSH = tclsh - -# You should not need to change anything below this line -############################################################################### -include $(SRCDIR)/main.mk ADDED Makefile.classic Index: Makefile.classic ================================================================== --- /dev/null +++ Makefile.classic @@ -0,0 +1,104 @@ +#!/usr/bin/make +# +# This is the top-level makefile for Fossil when the build is occurring +# on a unix platform. This works out-of-the-box on most unix platforms. +# But you are free to vary some of the definitions if desired. +# +#### The toplevel directory of the source tree. Fossil can be built +# in a directory that is separate from the source tree. Just change +# the following to point from the build directory to the src/ folder. +# +SRCDIR = ./src + +#### The directory into which object code files should be written. +# +# +OBJDIR = ./bld + +#### C Compiler and options for use in building executables that +# will run on the platform that is doing the build. This is used +# to compile code-generator programs as part of the build process. +# See TCC below for the C compiler for building the finished binary. +# +BCC = gcc +BCCFLAGS = $(CFLAGS) + +#### The suffix to add to final executable file. When cross-compiling +# to windows, make this ".exe". Otherwise leave it blank. +# +E = + +#### C Compile and options for use in building executables that +# will run on the target platform. This is usually the same +# as BCC, unless you are cross-compiling. This C compiler builds +# the finished binary for fossil. The BCC compiler above is used +# for building intermediate code-generator tools. +# +#TCC = gcc -O6 +#TCC = gcc -g -O0 -Wall -fprofile-arcs -ftest-coverage +TCC = gcc -g -Os -Wall + +# To use the included miniz library +# FOSSIL_ENABLE_MINIZ = 1 +# TCC += -DFOSSIL_ENABLE_MINIZ + +# To add support for HTTPS +TCC += -DFOSSIL_ENABLE_SSL + +#### We sometimes add the -static option here so that we can build a +# static executable that will run in a chroot jail. +#LIB = -static +TCC += -DFOSSIL_DYNAMIC_BUILD=1 + +TCCFLAGS = $(CFLAGS) + +# We don't attempt to use libedit or libreadline in this simplified +# build system (contrast auto.def and Makefile.in) so use the included +# copy of linenoise. MinGW can't make use of this, but linenoise is +# ifdef'd out elsewhere for that platform. Note that this is a make +# flag handled in src/main.mk, not a C preprocessor flag. +USE_LINENOISE := 1 + +#### Extra arguments for linking the finished binary. Fossil needs +# to link against the Z-Lib compression library unless the miniz +# library in the source tree is being used. There are no other +# required dependencies. +ZLIB_LIB.0 = -lz +ZLIB_LIB.1 = +ZLIB_LIB. = $(ZLIB_LIB.0) + +# If using zlib: +LIB += $(ZLIB_LIB.$(FOSSIL_ENABLE_MINIZ)) $(LDFLAGS) + +# If using HTTPS: +LIB += -lcrypto -lssl + +# Many platforms put cos() needed by src/piechart.c in libm, rather than +# in libc. We cannot enable this by default because libm doesn't exist +# everywhere. +#LIB += -lm + +#### Tcl shell for use in running the fossil testsuite. If you do not +# care about testing the end result, this can be blank. +# +TCLSH = tclsh + +# You should not need to change anything below this line +############################################################################### +# +# Automatic platform-specific options. +HOST_OS_CMD = uname -s +HOST_OS = $(HOST_OS_CMD:sh) + +LIB.SunOS= -lsocket -lnsl +LIB += $(LIB.$(HOST_OS)) + +TCC.DragonFly += -DUSE_PREAD +TCC.FreeBSD += -DUSE_PREAD +TCC.NetBSD += -DUSE_PREAD +TCC.OpenBSD += -DUSE_PREAD +TCC += $(TCC.$(HOST_OS)) + +APPNAME = fossil$(E) + +include $(SRCDIR)/main.mk ADDED Makefile.in Index: Makefile.in ================================================================== --- /dev/null +++ Makefile.in @@ -0,0 +1,84 @@ +#!/usr/bin/make +# +# This is the top-level makefile for Fossil when the build is occurring +# on a unix platform. This works out-of-the-box on most unix platforms. +# But you are free to vary some of the definitions if desired. +# +#### The toplevel directory of the source tree. Fossil can be built +# in a directory that is separate from the source tree. Just change +# the following to point from the build directory to the src/ folder. +# +SRCDIR = @srcdir@/src + +#### The directory into which object code files should be written. +# Having a "./" prefix in the value of this variable breaks our use of the +# "makeheaders" tool when running make on the MinGW platform, apparently +# due to some command line argument manipulation performed automatically +# by the shell. +# +# +OBJDIR = bld + +#### C Compiler and options for use in building executables that +# will run on the platform that is doing the build. This is used +# to compile code-generator programs as part of the build process. +# See TCC below for the C compiler for building the finished binary. +# +BCC = @CC_FOR_BUILD@ + +#### The suffix to add to final executable file. When cross-compiling +# to windows, make this ".exe". Otherwise leave it blank. +# +E = @EXEEXT@ + +TCC = @CC@ + +#### Tcl shell for use in running the fossil testsuite. If you do not +# care about testing the end result, this can be blank. +# +TCLSH = @TCLSH@ + +CFLAGS = @CFLAGS@ +LIB = @LDFLAGS@ @EXTRA_LDFLAGS@ @LIBS@ +BCCFLAGS = @CPPFLAGS@ $(CFLAGS) +TCCFLAGS = @EXTRA_CFLAGS@ @CPPFLAGS@ $(CFLAGS) -DHAVE_AUTOCONFIG_H -D_HAVE_SQLITE_CONFIG_H +INSTALLDIR = $(DESTDIR)@prefix@/bin +USE_SYSTEM_SQLITE = @USE_SYSTEM_SQLITE@ +USE_LINENOISE = @USE_LINENOISE@ +USE_MMAN_H = @USE_MMAN_H@ +USE_SEE = @USE_SEE@ +FOSSIL_ENABLE_MINIZ = @FOSSIL_ENABLE_MINIZ@ +APPNAME = fossil + +.PHONY: all tags + +include $(SRCDIR)/main.mk + +distclean: clean + -rm -f autoconfig.h config.log Makefile + -rm -f cscope.out tags + +reconfig: + @AUTOREMAKE@ + +tags: + ctags -R @srcdir@/src + @COLLECT_CSCOPE_DATA@ + +# Automatically reconfigure whenever an autosetup file or one of the +# make source files change. +# +# The "touch" is necessary to avoid a make loop due to a new upstream +# feature in autosetup (GH 0a71e3c3b7) which rewrites *.in outputs only +# if doing so will write different contents; otherwise, it leaves them +# alone so the mtime doesn't change. This means that if you change one +# our depdendencies besides Makefile.in, we'll reconfigure but Makefile +# won't change, so this rule will remain out of date, so we'll reconfig +# but Makefile won't change, so we'll reconfig but... endlessly. +# +# This is also why we repeat the reconfig target's command here instead +# of delegating to it with "$(MAKE) reconfig": having children running +# around interfering makes this failure mode even worse. +Makefile: @srcdir@/Makefile.in $(SRCDIR)/main.mk @AUTODEPS@ + @AUTOREMAKE@ + touch @builddir@/Makefile ADDED Makefile.osx-jaguar Index: Makefile.osx-jaguar ================================================================== --- /dev/null +++ Makefile.osx-jaguar @@ -0,0 +1,72 @@ +#!/usr/bin/make +# +# This is a specially modified version of the Makefile that will build +# Fossil on Mac OSX Jaguar (10.2) circa 2002. This Makefile is used for +# testing on an old PPC iBook. The use of this old platform helps to verify +# Fossil and SQLite running on big-endian hardware. +# +# To build with this Makefile, run: +# +# make -f Makefile.osx-jaguar +# +# +# This is the top-level makefile for Fossil when the build is occurring +# on a unix platform. This works out-of-the-box on most unix platforms. +# But you are free to vary some of the definitions if desired. +# +#### The toplevel directory of the source tree. Fossil can be built +# in a directory that is separate from the source tree. Just change +# the following to point from the build directory to the src/ folder. +# +SRCDIR = ./src + +#### The directory into which object code files should be written. +# Having a "./" prefix in the value of this variable breaks our use of the +# "makeheaders" tool when running make on the MinGW platform, apparently +# due to some command line argument manipulation performed automatically +# by the shell. +# +# +OBJDIR = bld + +#### C Compiler and options for use in building executables that +# will run on the platform that is doing the build. This is used +# to compile code-generator programs as part of the build process. +# See TCC below for the C compiler for building the finished binary. +# +BCC = cc +BCCFLAGS = $(CFLAGS) + +#### The suffix to add to final executable file. When cross-compiling +# to windows, make this ".exe". Otherwise leave it blank. +# +E = + +TCC = cc +TCCFLAGS = $(CFLAGS) + +#### Tcl shell for use in running the fossil testsuite. If you do not +# care about testing the end result, this can be blank. +# +TCLSH = tclsh + +# LIB = -lz +LIB = compat/zlib/libz.a +TCC += -g -O0 -DHAVE_AUTOCONFIG_H +TCC += -Icompat/zlib +TCC += -DSQLITE_WITHOUT_ZONEMALLOC +TCC += -D_BSD_SOURCE=1 +TCC += -DWITHOUT_ICONV +TCC += -Dsocklen_t=int +TCC += -DSQLITE_MAX_MMAP_SIZE=0 +INSTALLDIR = $(DESTDIR)/usr/local/bin +USE_SYSTEM_SQLITE = +USE_LINENOISE = 1 +# FOSSIL_ENABLE_TCL = @FOSSIL_ENABLE_TCL@ +FOSSIL_ENABLE_TCL = 0 +FOSSIL_ENABLE_MINIZ = 0 + +include $(SRCDIR)/main.mk + +distclean: clean + rm -f autoconfig.h config.log Makefile DELETED Makefile.w32 Index: Makefile.w32 ================================================================== --- Makefile.w32 +++ /dev/null @@ -1,67 +0,0 @@ -#!/usr/bin/make -# -#### The toplevel directory of the source tree. Fossil can be built -# in a directory that is separate from the source tree. Just change -# the following to point from the build directory to the src/ folder. -# -SRCDIR = ./src -OBJDIR = ./wobj - -#### C Compiler and options for use in building executables that -# will run on the platform that is doing the build. This is used -# to compile code-generator programs as part of the build process. -# See TCC below for the C compiler for building the finished binary. -# -BCC = gcc -g -O2 - -#### The suffix to add to executable files. ".exe" for windows. -# Nothing for unix. -# -E = .exe - -#### Enable HTTPS support via OpenSSL (links to libssl and libcrypto) -# -# FOSSIL_ENABLE_SSL=1 - -#### C Compile and options for use in building executables that -# will run on the target platform. This is usually the same -# as BCC, unless you are cross-compiling. This C compiler builds -# the finished binary for fossil. The BCC compiler above is used -# for building intermediate code-generator tools. -# -#TCC = gcc -O6 -#TCC = gcc -g -O0 -Wall -fprofile-arcs -ftest-coverage -#TCC = gcc -g -Os -Wall -#TCC = gcc -g -Os -Wall -DFOSSIL_I18N=0 -L/usr/local/lib -I/usr/local/include -TCC = gcc -Os -Wall -DFOSSIL_I18N=0 -L/mingw/lib -I/mingw/include - -# With HTTPS support -ifdef FOSSIL_ENABLE_SSL -TCC += -DFOSSIL_ENABLE_SSL=1 -endif - -#### Extra arguments for linking the finished binary. Fossil needs -# to link against the Z-Lib compression library. There are no -# other dependencies. We sometimes add the -static option here -# so that we can build a static executable that will run in a -# chroot jail. -# -#LIB = -lz -#LIB = -lz -lws2_32 -LIB = -lmingwex -lz -lws2_32 -# OpenSSL: -ifdef FOSSIL_ENABLE_SSL -LIB += -lcrypto -lssl -endif - -#### Tcl shell for use in running the fossil testsuite. -# -TCLSH = tclsh - -#### Include a configuration file that can override any one of these settings. -# --include config.w32 - -# You should not need to change anything below this line -############################################################################### -include $(SRCDIR)/main.mk ADDED README.md Index: README.md ================================================================== --- /dev/null +++ README.md @@ -0,0 +1,15 @@ +# About Fossil + +Fossil is a distributed version control system that has been widely +used since 2007. Fossil was originally designed to support the +[SQLite](https://sqlite.org) project but has been adopted by many other +projects as well. + +Fossil is self-hosting at . + +If you are reading this on GitHub, then you are looking at a Git mirror +of the self-hosting Fossil repository. The purpose of that mirror is to +test and exercise Fossil's ability to export a Git mirror. Nobody much +uses the GitHub mirror, except to verify that the mirror logic works. If +you want to know more about Fossil, visit the official self-hosting site +linked above. ADDED VERSION Index: VERSION ================================================================== --- /dev/null +++ VERSION @@ -0,0 +1,1 @@ +2.16 ADDED ajax/README Index: ajax/README ================================================================== --- /dev/null +++ ajax/README @@ -0,0 +1,38 @@ +This is the README for how to set up the Fossil/JSON test web page +under Apache on Unix systems. This is only intended only for +Fossil/JSON developers/tinkerers: + +First, copy cgi-bin/fossil-json.cgi.example to +cgi-bin/fossil-json.cgi. Edit it and correct the paths to the fossil +binary and the repo you want to serve. Make it executable. + +MAKE SURE that the fossil repo you use is world-writable OR that your +Web/CGI server is set up to run as the user ID of the owner of the +fossil file. ALSO: the DIRECTORY CONTAINING the repo file must be +writable by the CGI process. + +Next, set up an apache vhost entry. Mine looks like: + + + ServerAlias fjson + ScriptAlias /cgi-bin/ /home/stephan/cvs/fossil/fossil-json/ajax/cgi-bin/ + DocumentRoot /home/stephan/cvs/fossil/fossil-json/ajax + + +Now add your preferred vhost name (fjson in the above example) to /etc/hosts: + + 127.0.0.1 ...other aliases... fjson + +Restart your Apache. + +Now visit: http://fjson/ + +that will show the test/demo page. If it doesn't, edit index.html and +make sure that: + + WhAjaj.Connector.options.ajax.url = ...; + +points to your CGI script. In theory you can also do this over fossil +standalone server mode, but i haven't yet tested that particular test +page in that mode. + ADDED ajax/cgi-bin/fossil-json.cgi.example Index: ajax/cgi-bin/fossil-json.cgi.example ================================================================== --- /dev/null +++ ajax/cgi-bin/fossil-json.cgi.example @@ -0,0 +1,2 @@ +#!/path/to/fossil/binary +repository: /path/to/repo.fsl ADDED ajax/i-test/rhino-shell.js Index: ajax/i-test/rhino-shell.js ================================================================== --- /dev/null +++ ajax/i-test/rhino-shell.js @@ -0,0 +1,208 @@ +var FShell = { + serverUrl: + 'http://localhost:8080' + //'http://fjson/cgi-bin/fossil-json.cgi' + //'http://192.168.1.62:8080' + //'http://fossil.wanderinghorse.net/repos/fossil-json-java/index.cgi' + , + verbose:false, + prompt:"fossil shell > ", + wiki:{}, + consol:java.lang.System.console(), + v:function(msg){ + if(this.verbose){ + print("VERBOSE: "+msg); + } + } +}; +(function bootstrap() { + var srcdir = '../js/'; + var includes = [srcdir+'json2.js', + srcdir+'whajaj.js', + srcdir+'fossil-ajaj.js' + ]; + for( var i in includes ) { + load(includes[i]); + } + WhAjaj.Connector.prototype.sendImpl = WhAjaj.Connector.sendImpls.rhino; + FShell.fossil = new FossilAjaj({ + asynchronous:false, /* rhino-based impl doesn't support async. */ + timeout:10000, + url:FShell.serverUrl + }); + print("Server: "+FShell.serverUrl); + var cb = FShell.fossil.ajaj.callbacks; + cb.beforeSend = function(req,opt){ + if(!FShell.verbose) return; + print("SENDING REQUEST: AJAJ options="+JSON.stringify(opt)); + if(req) print("Request envelope="+WhAjaj.stringify(req)); + }; + cb.afterSend = function(req,opt){ + //if(!FShell.verbose) return; + //print("REQUEST RETURNED: opt="+JSON.stringify(opt)); + //if(req) print("Request="+WhAjaj.stringify(req)); + }; + cb.onError = function(req,opt){ + //if(!FShell.verbose) return; + print("ERROR: "+WhAjaj.stringify(opt)); + }; + cb.onResponse = function(resp,req){ + if(!FShell.verbose) return; + if(resp && resp.resultCode){ + print("Response contains error info: "+resp.resultCode+": "+resp.resultText); + } + print("GOT RESPONSE: "+(('string'===typeof resp) ? resp : WhAjaj.stringify(resp))); + }; + FShell.fossil.HAI({ + onResponse:function(resp,opt){ + assertResponseOK(resp); + } + }); +})(); + +/** + Throws an exception of cond is a falsy value. +*/ +function assert(cond, descr){ + descr = descr || "Undescribed condition."; + if(!cond){ + throw new Error("Assertion failed: "+descr); + }else{ + //print("Assertion OK: "+descr); + } +} + +/** + Convenience form of FShell.fossil.sendCommand(command,payload,ajajOpt). +*/ +function send(command,payload, ajajOpt){ + FShell.fossil.sendCommand(command,payload,ajajOpt); +} + +/** + Asserts that resp is-a Object, resp.fossil is-a string, and + !resp.resultCode. +*/ +function assertResponseOK(resp){ + assert('object' === typeof resp,'Response is-a object.'); + assert( 'string' === typeof resp.fossil, 'Response contains fossil property.'); + assert( !resp.resultCode, 'resp.resultCode='+resp.resultCode); +} +/** + Asserts that resp is-a Object, resp.fossil is-a string, and + resp.resultCode is a truthy value. If expectCode is set then + it also asserts that (resp.resultCode=='FOSSIL-'+expectCode). +*/ +function assertResponseError(resp,expectCode){ + assert('object' === typeof resp,'Response is-a object.'); + assert( 'string' === typeof resp.fossil, 'Response contains fossil property.'); + assert( resp.resultCode, 'resp.resultCode='+resp.resultCode); + if(expectCode){ + assert( 'FOSSIL-'+expectCode == resp.resultCode, 'Expecting result code '+expectCode ); + } +} + +FShell.readline = (typeof readline === 'function') ? (readline) : (function() { + importPackage(java.io); + importPackage(java.lang); + var stdin = new BufferedReader(new InputStreamReader(System['in'])); + var self = this; + return function(prompt) { + if(prompt) print(prompt); + var x = stdin.readLine(); + return null===x ? x : String(x) /*convert to JS string!*/; + }; +}()); + +FShell.dispatchLine = function(line){ + var av = line.split(' '); // FIXME: to shell-like tokenization. Too tired! + var cmd = av[0]; + var key, h; + if('/' == cmd[0]) key = '/'; + else key = this.commandAliases[cmd]; + if(!key) key = cmd; + h = this.commandHandlers[key]; + if(!h){ + print("Command not known: "+cmd +" ("+key+")"); + }else if(!WhAjaj.isFunction(h)){ + print("Not a function: "+key); + } + else{ + print("Sending ["+key+"] command... "); + try{h(av);} + catch(e){ print("EXCEPTION: "+e); } + } +}; + +FShell.onResponseDefault = function(callback){ + return function(resp,req){ + assertResponseOK(resp); + print("Payload: "+(resp.payload ? WhAjaj.stringify(resp.payload) : "none")); + if(WhAjaj.isFunction(callback)){ + callback(resp,req); + } + }; +}; +FShell.commandHandlers = { + "?":function(args){ + var k; + print("Available commands...\n"); + var o = FShell.commandHandlers; + for(k in o){ + if(! o.hasOwnProperty(k)) continue; + print("\t"+k); + } + }, + "/":function(args){ + FShell.fossil.sendCommand('/json'+args[0],undefined,{ + beforeSend:function(req,opt){ + print("Sending to: "+opt.url); + }, + onResponse:FShell.onResponseDefault() + }); + }, + "eval":function(args){ + eval(args.join(' ')); + }, + "login":function(args){ + FShell.fossil.login(args[1], args[2], { + onResponse:FShell.onResponseDefault() + }); + }, + "whoami":function(args){ + FShell.fossil.whoami({ + onResponse:FShell.onResponseDefault() + }); + }, + "HAI":function(args){ + FShell.fossil.HAI({ + onResponse:FShell.onResponseDefault() + }); + } + +}; +FShell.commandAliases = { + "li":"login", + "lo":"logout", + "who":"whoami", + "hi":"HAI", + "tci":"/timeline/ci?limit=3" +}; +FShell.mainLoop = function(){ + var line; + var check = /\S/; + //var isJavaNull = /java\.lang\.null/; + //print(typeof java.lang['null']); + while( null != (line=this.readline(this.prompt)) ){ + if(null===line) break /*EOF*/; + else if( "" === line ) continue; + //print("Got line: "+line); + else if(!check.test(line)) continue; + print('typeof line = '+typeof line); + this.dispatchLine(line); + print(""); + } + print("Bye!"); +}; + +FShell.mainLoop(); ADDED ajax/i-test/rhino-test.js Index: ajax/i-test/rhino-test.js ================================================================== --- /dev/null +++ ajax/i-test/rhino-test.js @@ -0,0 +1,279 @@ +var TestApp = { + serverUrl: + 'http://localhost:8080' + //'http://fjson/cgi-bin/fossil-json.cgi' + //'http://192.168.1.62:8080' + //'http://fossil.wanderinghorse.net/repos/fossil-json-java/index.cgi' + , + verbose:true, + fossilBinary:'fossil', + wiki:{} +}; +(function bootstrap() { + var srcdir = '../js/'; + var includes = [srcdir+'json2.js', + srcdir+'whajaj.js', + srcdir+'fossil-ajaj.js' + ]; + for( var i in includes ) { + load(includes[i]); + } + WhAjaj.Connector.prototype.sendImpl = WhAjaj.Connector.sendImpls.rhino; + TestApp.fossil = new FossilAjaj({ + asynchronous:false, /* rhino-based impl doesn't support async or timeout. */ + timeout:0, + url:TestApp.serverUrl, + fossilBinary:TestApp.fossilBinary + }); + var cb = TestApp.fossil.ajaj.callbacks; + cb.beforeSend = function(req,opt){ + if(!TestApp.verbose) return; + print("SENDING REQUEST: AJAJ options="+JSON.stringify(opt)); + if(req) print("Request envelope="+WhAjaj.stringify(req)); + }; + cb.afterSend = function(req,opt){ + //if(!TestApp.verbose) return; + //print("REQUEST RETURNED: opt="+JSON.stringify(opt)); + //if(req) print("Request="+WhAjaj.stringify(req)); + }; + cb.onError = function(req,opt){ + if(!TestApp.verbose) return; + print("ERROR: "+WhAjaj.stringify(opt)); + }; + cb.onResponse = function(resp,req){ + if(!TestApp.verbose) return; + print("GOT RESPONSE: "+(('string'===typeof resp) ? resp : WhAjaj.stringify(resp))); + }; + +})(); + +/** + Throws an exception of cond is a falsy value. +*/ +function assert(cond, descr){ + descr = descr || "Undescribed condition."; + if(!cond){ + print("Assertion FAILED: "+descr); + throw new Error("Assertion failed: "+descr); + // aarrgghh. Exceptions are of course swallowed by + // the AJAX layer, to keep from killing a browser's + // script environment. + }else{ + if(TestApp.verbose) print("Assertion OK: "+descr); + } +} + +/** + Calls func() in a try/catch block and throws an exception if + func() does NOT throw. +*/ +function assertThrows(func, descr){ + descr = descr || "Undescribed condition failed."; + var ex; + try{ + func(); + }catch(e){ + ex = e; + } + if(!ex){ + throw new Error("Function did not throw (as expected): "+descr); + }else{ + if(TestApp.verbose) print("Function threw (as expected): "+descr+": "+ex); + } +} + +/** + Convenience form of TestApp.fossil.sendCommand(command,payload,ajajOpt). +*/ +function send(command,payload, ajajOpt){ + TestApp.fossil.sendCommand(command,payload,ajajOpt); +} + +/** + Asserts that resp is-a Object, resp.fossil is-a string, and + !resp.resultCode. +*/ +function assertResponseOK(resp){ + assert('object' === typeof resp,'Response is-a object.'); + assert( 'string' === typeof resp.fossil, 'Response contains fossil property.'); + assert( undefined === resp.resultCode, 'resp.resultCode is not set'); +} +/** + Asserts that resp is-a Object, resp.fossil is-a string, and + resp.resultCode is a truthy value. If expectCode is set then + it also asserts that (resp.resultCode=='FOSSIL-'+expectCode). +*/ +function assertResponseError(resp,expectCode){ + assert('object' === typeof resp,'Response is-a object.'); + assert( 'string' === typeof resp.fossil, 'Response contains fossil property.'); + assert( !!resp.resultCode, 'resp.resultCode='+resp.resultCode); + if(expectCode){ + assert( 'FOSSIL-'+expectCode == resp.resultCode, 'Expecting result code '+expectCode ); + } +} + +function testHAI(){ + var rs; + TestApp.fossil.HAI({ + onResponse:function(resp,req){ + rs = resp; + } + }); + assertResponseOK(rs); + TestApp.serverVersion = rs.fossil; + assert( 'string' === typeof TestApp.serverVersion, 'server version = '+TestApp.serverVersion); +} +testHAI.description = 'Get server version info.'; + +function testIAmNobody(){ + TestApp.fossil.whoami('/json/whoami'); + assert('nobody' === TestApp.fossil.auth.name, 'User == nobody.' ); + assert(!TestApp.fossil.auth.authToken, 'authToken is not set.' ); + +} +testIAmNobody.description = 'Ensure that current user is "nobody".'; + + +function testAnonymousLogin(){ + TestApp.fossil.login(); + assert('string' === typeof TestApp.fossil.auth.authToken, 'authToken = '+TestApp.fossil.auth.authToken); + assert( 'string' === typeof TestApp.fossil.auth.name, 'User name = '+TestApp.fossil.auth.name); + TestApp.fossil.userName = null; + TestApp.fossil.whoami('/json/whoami'); + assert( 'string' === typeof TestApp.fossil.auth.name, 'User name = '+TestApp.fossil.auth.name); +} +testAnonymousLogin.description = 'Perform anonymous login.'; + +function testAnonWiki(){ + var rs; + TestApp.fossil.sendCommand('/json/wiki/list',undefined,{ + beforeSend:function(req,opt){ + assert( req && (req.authToken==TestApp.fossil.auth.authToken), 'Request envelope contains expected authToken.' ); + }, + onResponse:function(resp,req){ + rs = resp; + } + }); + assertResponseOK(rs); + assert( (typeof [] === typeof rs.payload) && rs.payload.length, + "Wiki list seems to be okay."); + TestApp.wiki.list = rs.payload; + + TestApp.fossil.sendCommand('/json/wiki/get',{ + name:TestApp.wiki.list[0] + },{ + onResponse:function(resp,req){ + rs = resp; + } + }); + assertResponseOK(rs); + assert(rs.payload.name == TestApp.wiki.list[0], "Fetched page name matches expectations."); + print("Got first wiki page: "+WhAjaj.stringify(rs.payload)); + +} +testAnonWiki.description = 'Fetch wiki list as anonymous user.'; + +function testFetchCheckinArtifact(){ + var art = '18dd383e5e7684ece'; + var rs; + TestApp.fossil.sendCommand('/json/artifact',{ + 'name': art + }, + { + onResponse:function(resp,req){ + rs = resp; + } + }); + assertResponseOK(rs); + assert(3 == rs.payload.parents.length, 'Got 3 parent artifacts.'); +} +testFetchCheckinArtifact.description = '/json/artifact/CHECKIN'; + +function testAnonLogout(){ + var rs; + TestApp.fossil.logout({ + onResponse:function(resp,req){ + rs = resp; + } + }); + assertResponseOK(rs); + print("Ensure that second logout attempt fails..."); + TestApp.fossil.logout({ + onResponse:function(resp,req){ + rs = resp; + } + }); + assertResponseError(rs); +} +testAnonLogout.description = 'Log out anonymous user.'; + +function testExternalProcess(){ + + var req = { command:"HAI", requestId:'testExternalProcess()' }; + var args = [TestApp.fossilBinary, 'json', '--json-input', '-']; + var p = java.lang.Runtime.getRuntime().exec(args); + var outs = p.getOutputStream(); + var osr = new java.io.OutputStreamWriter(outs); + var osb = new java.io.BufferedWriter(osr); + var json = JSON.stringify(req); + osb.write(json,0, json.length); + osb.close(); + req = json = outs = osr = osb = undefined; + var ins = p.getInputStream(); + var isr = new java.io.InputStreamReader(ins); + var br = new java.io.BufferedReader(isr); + var line; + + while( null !== (line=br.readLine())){ + print(line); + } + br.close(); + isr.close(); + ins.close(); + p.waitFor(); +} +testExternalProcess.description = 'Run fossil as external process.'; + +function testExternalProcessHandler(){ + var aj = TestApp.fossil.ajaj; + var oldImpl = aj.sendImpl; + aj.sendImpl = FossilAjaj.rhinoLocalBinarySendImpl; + var rs; + TestApp.fossil.sendCommand('/json/HAI',undefined,{ + onResponse:function(resp,opt){ + rs = resp; + } + }); + aj.sendImpl = oldImpl; + assertResponseOK(rs); + print("Using local fossil binary via AJAX interface, we fetched: "+ + WhAjaj.stringify(rs)); +} +testExternalProcessHandler.description = 'Try local fossil binary via AJAX interface.'; + +(function runAllTests(){ + var testList = [ + testHAI, + testIAmNobody, + testAnonymousLogin, + testAnonWiki, + testFetchCheckinArtifact, + testAnonLogout, + testExternalProcess, + testExternalProcessHandler + ]; + var i, f; + for( i = 0; i < testList.length; ++i ){ + f = testList[i]; + try{ + print("Running test #"+(i+1)+": "+(f.description || "no description.")); + f(); + }catch(e){ + print("Test #"+(i+1)+" failed: "+e); + throw e; + } + } + +})(); + +print("Done! If you don't see an exception message in the last few lines, you win!"); ADDED ajax/index.html Index: ajax/index.html ================================================================== --- /dev/null +++ ajax/index.html @@ -0,0 +1,332 @@ + + + + + Fossil/JSON raw request sending + + + + + + + + + + + + + +
+

You know, for sending raw JSON requests to Fossil...

+ +If you're actually using this page, then you know what you're doing and don't +need help text, hoverhelp, and a snazzy interface. + +

+ + +JSON API docs: https://docs.google.com/document/d/1fXViveNhDbiXgCuE7QDXQOKeFzf2qNUkBEgiUvoqFN4/edit + +
+See also: prototype wiki editor. + +

Request...

+ +Path: +
+If the POST textarea is not empty then it will be posted with the request. +
+Quick-posts:
+ + + + + + + + + +
+ + + + + + + +
+ + + + + + + +
+ + + + + + + + + +
+ + + + + + + + + + + + + + + + +
+Login: +
+ + +
+name: +pw: + + +
+ + +
+ +
+ + + + + + + + + + + + + + + + +
POST dataRequest AJAJ options
+ + + +
Response
+ +
+
+
+
+ + ADDED ajax/js/fossil-ajaj.js Index: ajax/js/fossil-ajaj.js ================================================================== --- /dev/null +++ ajax/js/fossil-ajaj.js @@ -0,0 +1,274 @@ +/** + This file contains a WhAjaj extension for use with Fossil/JSON. + + Author: Stephan Beal (sgbeal@googlemail.com) + + License: Public Domain +*/ + +/** + Constructor for a new Fossil AJAJ client. ajajOpt may be an optional + object suitable for passing to the WhAjaj.Connector() constructor. + + On returning, this.ajaj is-a WhAjaj.Connector instance which can + be used to send requests to the back-end (though the convenience + functions of this class are the preferred way to do it). Clients + are encouraged to use FossilAjaj.sendCommand() (and friends) instead + of the underlying WhAjaj.Connector API, since this class' API + contains Fossil-specific request-calling handling (e.g. of authentication + info) whereas WhAjaj is more generic. +*/ +function FossilAjaj(ajajOpt) +{ + this.ajaj = new WhAjaj.Connector(ajajOpt); + return this; +} + +FossilAjaj.prototype.generateRequestId = function() { + return this.ajaj.generateRequestId(); +}; + +/** + Proxy for this.ajaj.sendRequest(). +*/ +FossilAjaj.prototype.sendRequest = function(req,opt) { + return this.ajaj.sendRequest(req,opt); +}; + +/** + Sends a command to the fossil back-end. Command should be the + path part of the URL, e.g. /json/stat, payload is a request-specific + value type (may often be null/undefined). ajajOpt is an optional object + holding WhAjaj.sendRequest()-compatible options. + + This function constructs a Fossil/JSON request envelope based + on the given arguments and adds this.auth.authToken and a requestId + to it. +*/ +FossilAjaj.prototype.sendCommand = function(command, payload, ajajOpt) { + var req; + ajajOpt = ajajOpt || {}; + if(payload || (this.auth && this.auth.authToken) || ajajOpt.jsonp) { + req = { + payload:payload, + requestId:('function' === typeof this.generateRequestId) ? this.generateRequestId() : undefined, + authToken:(this.auth ? this.auth.authToken : undefined), + jsonp:('string' === typeof ajajOpt.jsonp) ? ajajOpt.jsonp : undefined + }; + } + ajajOpt.method = req ? 'POST' : 'GET'; + // just for debuggering: ajajOpt.method = 'POST'; if(!req) req={}; + if(command) ajajOpt.url = this.ajaj.derivedOption('url',ajajOpt) + command; + this.ajaj.sendRequest(req,ajajOpt); +}; + +/** + Sends a login request to the back-end. + + ajajOpt is an optional configuration object suitable for passing + to sendCommand(). + + After the response returns, this.auth will be + set to the response payload. + + If name === 'anonymous' (the default if none is passed in) then this + function ignores the pw argument and must make two requests - the first + one gets the captcha code and the second one submits it. + ajajOpt.onResponse() (if set) is only called for the actual login + response (the 2nd one), as opposed to being called for both requests. + However, this.ajaj.callbacks.onResponse() _is_ called for both (because + it happens at a lower level). + + If this object has an onLogin() function it is called (with + no arguments) before the onResponse() handler of the login is called + (that is the 2nd request for anonymous logins) and any exceptions + it throws are ignored. + +*/ +FossilAjaj.prototype.login = function(name,pw,ajajOpt) { + name = name || 'anonymous'; + var self = this; + var loginReq = { + name:name, + password:pw + }; + ajajOpt = this.ajaj.normalizeAjaxParameters( ajajOpt || {} ); + var oldOnResponse = ajajOpt.onResponse; + ajajOpt.onResponse = function(resp,req) { + var thisOpt = this; + //alert('login response:\n'+WhAjaj.stringify(resp)); + if( resp && resp.payload ) { + //self.userName = resp.payload.name; + //self.capabilities = resp.payload.capabilities; + self.auth = resp.payload; + } + if( WhAjaj.isFunction( self.onLogin ) ){ + try{ self.onLogin(); } + catch(e){} + } + if( WhAjaj.isFunction(oldOnResponse) ) { + oldOnResponse.apply(thisOpt,[resp,req]); + } + }; + function doLogin(){ + //alert("Sending login request..."+WhAjaj.stringify(loginReq)); + self.sendCommand('/json/login', loginReq, ajajOpt); + } + if( 'anonymous' === name ){ + this.sendCommand('/json/anonymousPassword',undefined,{ + onResponse:function(resp,req){ +/* + if( WhAjaj.isFunction(oldOnResponse) ){ + oldOnResponse.apply(this, [resp,req]); + }; +*/ + if(resp && !resp.resultCode){ + //alert("Got PW. Trying to log in..."+WhAjaj.stringify(resp)); + loginReq.anonymousSeed = resp.payload.seed; + loginReq.password = resp.payload.password; + doLogin(); + } + } + }); + } + else doLogin(); +}; + +/** + Logs out of fossil, invaliding this login token. + + ajajOpt is an optional configuration object suitable for passing + to sendCommand(). + + If this object has an onLogout() function it is called (with + no arguments) before the onResponse() handler is called. + IFF the response succeeds then this.auth is unset. +*/ +FossilAjaj.prototype.logout = function(ajajOpt) { + var self = this; + ajajOpt = this.ajaj.normalizeAjaxParameters( ajajOpt || {} ); + var oldOnResponse = ajajOpt.onResponse; + ajajOpt.onResponse = function(resp,req) { + var thisOpt = this; + self.auth = undefined; + if( WhAjaj.isFunction( self.onLogout ) ){ + try{ self.onLogout(); } + catch(e){} + } + if( WhAjaj.isFunction(oldOnResponse) ) { + oldOnResponse.apply(thisOpt,[resp,req]); + } + }; + this.sendCommand('/json/logout', undefined, ajajOpt ); +}; + +/** + Sends a HAI request to the server. /json/HAI is an alias /json/version. + + ajajOpt is an optional configuration object suitable for passing + to sendCommand(). +*/ +FossilAjaj.prototype.HAI = function(ajajOpt) { + this.sendCommand('/json/HAI', undefined, ajajOpt); +}; + + +/** + Sends a /json/whoami request. Updates this.auth to contain + the login info, removing them if the response does not contain + that data. +*/ +FossilAjaj.prototype.whoami = function(ajajOpt) { + var self = this; + ajajOpt = this.ajaj.normalizeAjaxParameters( ajajOpt || {} ); + var oldOnResponse = ajajOpt.onResponse; + ajajOpt.onResponse = function(resp,req) { + var thisOpt = this; + if( resp && resp.payload ){ + if(!self.auth || (self.auth.authToken!==resp.payload.authToken)){ + self.auth = resp.payload; + if( WhAjaj.isFunction(self.onLogin) ){ + self.onLogin(); + } + } + } + else { delete self.auth; } + if( WhAjaj.isFunction(oldOnResponse) ) { + oldOnResponse.apply(thisOpt,[resp,req]); + } + }; + self.sendCommand('/json/whoami', undefined, ajajOpt); +}; + +/** + EXPERIMENTAL concrete WhAjaj.Connector.sendImpl() implementation which + uses Rhino to connect to a local fossil binary for input and output. Its + signature and semantics are as described for + WhAjaj.Connector.prototype.sendImpl(), with a few exceptions and + additions: + + - It does not support timeouts or asynchronous mode. + + - The args.fossilBinary property must point to the local fossil binary + (it need not be a complete path if fossil is in the $PATH). This + function throws (without calling any request callbacks) if + args.fossilBinary is not set. fossilBinary may be set on + WhAjaj.Connector.options.ajax, in the FossilAjaj constructor call, as + the ajax options parameter to any of the FossilAjaj.sendCommand() family + of functions, or by setting + aFossilAjajInstance.ajaj.options.fossilBinary on a specific + FossilAjaj instance. + + - It uses the args.url field to create the "command" property of the + request, constructs a request envelope, spawns a fossil process in JSON + mode, feeds it the request envelope, and returns the response envelope + via the same mechanisms defined for the HTTP-based implementations. + + The interface is otherwise compatible with the "normal" + FossilAjaj.sendCommand() front-end (it is, however, fossil-specific, and + not back-end agnostic like the WhAjaj.sendImpl() interface intends). + + +*/ +FossilAjaj.rhinoLocalBinarySendImpl = function(request,args){ + var self = this; + request = request || {}; + if(!args.fossilBinary){ + throw new Error("fossilBinary is not set on AJAX options!"); + } + var url = args.url.split('?')[0].split(/\/+/); + if(url.length>1){ + // 3x shift(): protocol, host, 'json' part of path + request.command = (url.shift(),url.shift(),url.shift(), url.join('/')); + } + delete args.url; + //print("rhinoLocalBinarySendImpl SENDING: "+WhAjaj.stringify(request)); + var json; + try{ + var pargs = [args.fossilBinary, 'json', '--json-input', '-']; + var p = java.lang.Runtime.getRuntime().exec(pargs); + var outs = p.getOutputStream(); + var osr = new java.io.OutputStreamWriter(outs); + var osb = new java.io.BufferedWriter(osr); + + json = JSON.stringify(request); + osb.write(json,0, json.length); + osb.close(); + var ins = p.getInputStream(); + var isr = new java.io.InputStreamReader(ins); + var br = new java.io.BufferedReader(isr); + var line; + json = []; + while( null !== (line=br.readLine())){ + json.push(line); + } + ins.close(); + }catch(e){ + args.errorMessage = e.toString(); + WhAjaj.Connector.sendHelper.onSendError.apply( self, [request, args] ); + return undefined; + } + json = json.join(''); + //print("READ IN JSON: "+json); + WhAjaj.Connector.sendHelper.onSendSuccess.apply( self, [request, json, args] ); +}/*rhinoLocalBinary*/ ADDED ajax/js/json2.js Index: ajax/js/json2.js ================================================================== --- /dev/null +++ ajax/js/json2.js @@ -0,0 +1,476 @@ +/* + http://www.JSON.org/json2.js + 2009-06-29 + + Public Domain. + + NO WARRANTY EXPRESSED OR IMPLIED. USE AT YOUR OWN RISK. + + See http://www.JSON.org/js.html + + This file creates a global JSON object containing two methods: stringify + and parse. + + JSON.stringify(value, replacer, space) + value any JavaScript value, usually an object or array. + + replacer an optional parameter that determines how object + values are stringified for objects. It can be a + function or an array of strings. + + space an optional parameter that specifies the indentation + of nested structures. If it is omitted, the text will + be packed without extra whitespace. If it is a number, + it will specify the number of spaces to indent at each + level. If it is a string (such as '\t' or ' '), + it contains the characters used to indent at each level. + + This method produces a JSON text from a JavaScript value. + + When an object value is found, if the object contains a toJSON + method, its toJSON method will be called and the result will be + stringified. A toJSON method does not serialize: it returns the + value represented by the name/value pair that should be serialized, + or undefined if nothing should be serialized. The toJSON method + will be passed the key associated with the value, and this will be + bound to the object holding the key. + + For example, this would serialize Dates as ISO strings. + + Date.prototype.toJSON = function (key) { + function f(n) { + // Format integers to have at least two digits. + return n < 10 ? '0' + n : n; + } + + return this.getUTCFullYear() + '-' + + f(this.getUTCMonth() + 1) + '-' + + f(this.getUTCDate()) + 'T' + + f(this.getUTCHours()) + ':' + + f(this.getUTCMinutes()) + ':' + + f(this.getUTCSeconds()) + 'Z'; + }; + + You can provide an optional replacer method. It will be passed the + key and value of each member, with this bound to the containing + object. The value that is returned from your method will be + serialized. If your method returns undefined, then the member will + be excluded from the serialization. + + If the replacer parameter is an array of strings, then it will be + used to select the members to be serialized. It filters the results + such that only members with keys listed in the replacer array are + stringified. + + Values that do not have JSON representations, such as undefined or + functions, will not be serialized. Such values in objects will be + dropped; in arrays they will be replaced with null. You can use + a replacer function to replace those with JSON values. + JSON.stringify(undefined) returns undefined. + + The optional space parameter produces a stringification of the + value that is filled with line breaks and indentation to make it + easier to read. + + If the space parameter is a non-empty string, then that string will + be used for indentation. If the space parameter is a number, then + the indentation will be that many spaces. + + Example: + + text = JSON.stringify(['e', {pluribus: 'unum'}]); + // text is '["e",{"pluribus":"unum"}]' + + + text = JSON.stringify(['e', {pluribus: 'unum'}], null, '\t'); + // text is '[\n\t"e",\n\t{\n\t\t"pluribus": "unum"\n\t}\n]' + + text = JSON.stringify([new Date()], function (key, value) { + return this[key] instanceof Date ? + 'Date(' + this[key] + ')' : value; + }); + // text is '["Date(---current time---)"]' + + + JSON.parse(text, reviver) + This method parses a JSON text to produce an object or array. + It can throw a SyntaxError exception. + + The optional reviver parameter is a function that can filter and + transform the results. It receives each of the keys and values, + and its return value is used instead of the original value. + If it returns what it received, then the structure is not modified. + If it returns undefined then the member is deleted. + + Example: + + // Parse the text. Values that look like ISO date strings will + // be converted to Date objects. + + myData = JSON.parse(text, function (key, value) { + var a; + if (typeof value === 'string') { + a = +/^(\d{4})-(\d{2})-(\d{2})T(\d{2}):(\d{2}):(\d{2}(?:\.\d*)?)Z$/.exec(value); + if (a) { + return new Date(Date.UTC(+a[1], +a[2] - 1, +a[3], +a[4], + +a[5], +a[6])); + } + } + return value; + }); + + myData = JSON.parse('["Date(09/09/2001)"]', function (key, value) { + var d; + if (typeof value === 'string' && + value.slice(0, 5) === 'Date(' && + value.slice(-1) === ')') { + d = new Date(value.slice(5, -1)); + if (d) { + return d; + } + } + return value; + }); + + + This is a reference implementation. You are free to copy, modify, or + redistribute. + + This code should be minified before deployment. + See http://javascript.crockford.com/jsmin.html + + USE YOUR OWN COPY. IT IS EXTREMELY UNWISE TO LOAD CODE FROM SERVERS YOU DO + NOT CONTROL. +*/ + +/*jslint evil: true */ + +/*members "", "\b", "\t", "\n", "\f", "\r", "\"", JSON, "\\", apply, + call, charCodeAt, getUTCDate, getUTCFullYear, getUTCHours, + getUTCMinutes, getUTCMonth, getUTCSeconds, hasOwnProperty, join, + lastIndex, length, parse, prototype, push, replace, slice, stringify, + test, toJSON, toString, valueOf +*/ + +// Create a JSON object only if one does not already exist. We create the +// methods in a closure to avoid creating global variables. + +var JSON = JSON || {}; + +(function () { + + function f(n) { + // Format integers to have at least two digits. + return n < 10 ? '0' + n : n; + } + + if (typeof Date.prototype.toJSON !== 'function') { + + Date.prototype.toJSON = function (key) { + + return isFinite(this.valueOf()) ? + this.getUTCFullYear() + '-' + + f(this.getUTCMonth() + 1) + '-' + + f(this.getUTCDate()) + 'T' + + f(this.getUTCHours()) + ':' + + f(this.getUTCMinutes()) + ':' + + f(this.getUTCSeconds()) + 'Z' : null; + }; + + String.prototype.toJSON = + Number.prototype.toJSON = + Boolean.prototype.toJSON = function (key) { + return this.valueOf(); + }; + } + + var cx = /[\u0000\u00ad\u0600-\u0604\u070f\u17b4\u17b5\u200c-\u200f\u2028-\u202f\u2060-\u206f\ufeff\ufff0-\uffff]/g, + escapable = /[\\\"\x00-\x1f\x7f-\x9f\u00ad\u0600-\u0604\u070f\u17b4\u17b5\u200c-\u200f\u2028-\u202f\u2060-\u206f\ufeff\ufff0-\uffff]/g, + gap, + indent, + meta = { // table of character substitutions + '\b': '\\b', + '\t': '\\t', + '\n': '\\n', + '\f': '\\f', + '\r': '\\r', + '"' : '\\"', + '\\': '\\\\' + }, + rep; + + + function quote(string) { + +// If the string contains no control characters, no quote characters, and no +// backslash characters, then we can safely slap some quotes around it. +// Otherwise we must also replace the offending characters with safe escape +// sequences. + + escapable.lastIndex = 0; + return escapable.test(string) ? + '"' + string.replace(escapable, function (a) { + var c = meta[a]; + return typeof c === 'string' ? c : + '\\u' + ('0000' + a.charCodeAt(0).toString(16)).slice(-4); + }) + '"' : + '"' + string + '"'; + } + + + function str(key, holder) { + +// Produce a string from holder[key]. + + var i, // The loop counter. + k, // The member key. + v, // The member value. + length, + mind = gap, + partial, + value = holder[key]; + +// If the value has a toJSON method, call it to obtain a replacement value. + + if (value && typeof value === 'object' && + typeof value.toJSON === 'function') { + value = value.toJSON(key); + } + +// If we were called with a replacer function, then call the replacer to +// obtain a replacement value. + + if (typeof rep === 'function') { + value = rep.call(holder, key, value); + } + +// What happens next depends on the value's type. + + switch (typeof value) { + case 'string': + return quote(value); + + case 'number': + +// JSON numbers must be finite. Encode non-finite numbers as null. + + return isFinite(value) ? String(value) : 'null'; + + case 'boolean': + case 'null': + +// If the value is a boolean or null, convert it to a string. Note: +// typeof null does not produce 'null'. The case is included here in +// the remote chance that this gets fixed someday. + + return String(value); + +// If the type is 'object', we might be dealing with an object or an array or +// null. + + case 'object': + +// Due to a specification blunder in ECMAScript, typeof null is 'object', +// so watch out for that case. + + if (!value) { + return 'null'; + } + +// Make an array to hold the partial results of stringifying this object value. + + gap += indent; + partial = []; + +// Is the value an array? + + if (Object.prototype.toString.apply(value) === '[object Array]') { + +// The value is an array. Stringify every element. Use null as a placeholder +// for non-JSON values. + + length = value.length; + for (i = 0; i < length; i += 1) { + partial[i] = str(i, value) || 'null'; + } + +// Join all of the elements together, separated with commas, and wrap them in +// brackets. + + v = partial.length === 0 ? '[]' : + gap ? '[\n' + gap + + partial.join(',\n' + gap) + '\n' + + mind + ']' : + '[' + partial.join(',') + ']'; + gap = mind; + return v; + } + +// If the replacer is an array, use it to select the members to be stringified. + + if (rep && typeof rep === 'object') { + length = rep.length; + for (i = 0; i < length; i += 1) { + k = rep[i]; + if (typeof k === 'string') { + v = str(k, value); + if (v) { + partial.push(quote(k) + (gap ? ': ' : ':') + v); + } + } + } + } else { + +// Otherwise, iterate through all of the keys in the object. + + for (k in value) { + if (Object.hasOwnProperty.call(value, k)) { + v = str(k, value); + if (v) { + partial.push(quote(k) + (gap ? ': ' : ':') + v); + } + } + } + } + +// Join all of the member texts together, separated with commas, +// and wrap them in braces. + + v = partial.length === 0 ? '{}' : + gap ? '{\n' + gap + partial.join(',\n' + gap) + '\n' + + mind + '}' : '{' + partial.join(',') + '}'; + gap = mind; + return v; + } + } + +// If the JSON object does not yet have a stringify method, give it one. + + if (typeof JSON.stringify !== 'function') { + JSON.stringify = function (value, replacer, space) { + +// The stringify method takes a value and an optional replacer, and an optional +// space parameter, and returns a JSON text. The replacer can be a function +// that can replace values, or an array of strings that will select the keys. +// A default replacer method can be provided. Use of the space parameter can +// produce text that is more easily readable. + + var i; + gap = ''; + indent = ''; + +// If the space parameter is a number, make an indent string containing that +// many spaces. + + if (typeof space === 'number') { + for (i = 0; i < space; i += 1) { + indent += ' '; + } + +// If the space parameter is a string, it will be used as the indent string. + + } else if (typeof space === 'string') { + indent = space; + } + +// If there is a replacer, it must be a function or an array. +// Otherwise, throw an error. + + rep = replacer; + if (replacer && typeof replacer !== 'function' && + (typeof replacer !== 'object' || + typeof replacer.length !== 'number')) { + throw new Error('JSON.stringify'); + } + +// Make a fake root object containing our value under the key of ''. +// Return the result of stringifying the value. + + return str('', {'': value}); + }; + } + + +// If the JSON object does not yet have a parse method, give it one. + + if (typeof JSON.parse !== 'function') { + JSON.parse = function (text, reviver) { + +// The parse method takes a text and an optional reviver function, and returns +// a JavaScript value if the text is a valid JSON text. + + var j; + + function walk(holder, key) { + +// The walk method is used to recursively walk the resulting structure so +// that modifications can be made. + + var k, v, value = holder[key]; + if (value && typeof value === 'object') { + for (k in value) { + if (Object.hasOwnProperty.call(value, k)) { + v = walk(value, k); + if (v !== undefined) { + value[k] = v; + } else { + delete value[k]; + } + } + } + } + return reviver.call(holder, key, value); + } + + +// Parsing happens in four stages. In the first stage, we replace certain +// Unicode characters with escape sequences. JavaScript handles many characters +// incorrectly, either silently deleting them, or treating them as line endings. + + cx.lastIndex = 0; + if (cx.test(text)) { + text = text.replace(cx, function (a) { + return '\\u' + + ('0000' + a.charCodeAt(0).toString(16)).slice(-4); + }); + } + +// In the second stage, we run the text against regular expressions that look +// for non-JSON patterns. We are especially concerned with '()' and 'new' +// because they can cause invocation, and '=' because it can cause mutation. +// But just to be safe, we want to reject all unexpected forms. + +// We split the second stage into 4 regexp operations in order to work around +// crippling inefficiencies in IE's and Safari's regexp engines. First we +// replace the JSON backslash pairs with '@' (a non-JSON character). Second, we +// replace all simple value tokens with ']' characters. Third, we delete all +// open brackets that follow a colon or comma or that begin the text. Finally, +// we look to see that the remaining characters are only whitespace or ']' or +// ',' or ':' or '{' or '}'. If that is so, then the text is safe for eval. + + if (/^[\],:{}\s]*$/. +test(text.replace(/\\(?:["\\\/bfnrt]|u[0-9a-fA-F]{4})/g, '@'). +replace(/"[^"\\\n\r]*"|true|false|null|-?\d+(?:\.\d*)?(?:[eE][+\-]?\d+)?/g, ']'). +replace(/(?:^|:|,)(?:\s*\[)+/g, ''))) { + +// In the third stage we use the eval function to compile the text into a +// JavaScript structure. The '{' operator is subject to a syntactic ambiguity +// in JavaScript: it can begin a block or an object literal. We wrap the text +// in parens to eliminate the ambiguity. + + j = eval('(' + text + ')'); + +// In the optional fourth stage, we recursively walk the new structure, passing +// each name/value pair to a reviver function for possible transformation. + + return typeof reviver === 'function' ? + walk({'': j}, '') : j; + } + +// If the text is not JSON parseable, then a SyntaxError is thrown. + + throw new SyntaxError('JSON.parse'); + }; + } +}()); ADDED ajax/js/whajaj.js Index: ajax/js/whajaj.js ================================================================== --- /dev/null +++ ajax/js/whajaj.js @@ -0,0 +1,1221 @@ +/** + This file provides a JS interface into the core functionality of + JSON-centric back-ends. It sends GET or JSON POST requests to + a back-end and expects JSON responses. The exact semantics of + the underlying back-end and overlying front-end are not its concern, + and it leaves the interpretation of the data up to the client/server + insofar as possible. + + All functionality is part of a class named WhAjaj, and that class + acts as namespace for this framework. + + Author: Stephan Beal (http://wanderinghorse.net/home/stephan/) + + License: Public Domain + + This framework is directly derived from code originally found in + http://code.google.com/p/jsonmessage, and later in + http://whiki.wanderinghorse.net, where it contained quite a bit + of application-specific logic. It was eventually (the 3rd time i + needed it) split off into its own library to simplify inclusion + into my many mini-projects. +*/ + + +/** + The WhAjaj function is primarily a namespace, and not intended + to called or instantiated via the 'new' operator. +*/ +function WhAjaj() +{ +} + +/** Returns a millisecond Unix Epoch timestamp. */ +WhAjaj.msTimestamp = function() +{ + return (new Date()).getTime(); +}; + +/** Returns a Unix Epoch timestamp (in seconds) in integer format. + + Reminder to self: (1.1 %1.2) evaluates to a floating-point value + in JS, and thus this implementation is less than optimal. +*/ +WhAjaj.unixTimestamp = function() +{ + var ts = (new Date()).getTime(); + return parseInt( ""+((ts / 1000) % ts) ); +}; + +/** + Returns true if v is-a Array instance. +*/ +WhAjaj.isArray = function( v ) +{ + return (v && + (v instanceof Array) || + (Object.prototype.toString.call(v) === "[object Array]") + ); + /* Reminders to self: + typeof [] == "object" + toString.call([]) == "[object Array]" + ([]).toString() == empty + */ +}; + +/** + Returns true if v is-a Object instance. +*/ +WhAjaj.isObject = function( v ) +{ + return v && + (v instanceof Object) && + ('[object Object]' === Object.prototype.toString.apply(v) ); +}; + +/** + Returns true if v is-a Function instance. +*/ +WhAjaj.isFunction = function(obj) +{ + return obj + && ( + (obj instanceof Function) + || ('function' === typeof obj) + || ("[object Function]" === Object.prototype.toString.call(obj)) + ) + ; +}; + +/** + Parses window.location.search-style string into an object + containing key/value pairs of URL arguments (already urldecoded). + + If the str argument is not passed (arguments.length==0) then + window.location.search.substring(1) is used by default. If + neither str is passed in nor window exists then false is returned. + + On success it returns an Object containing the key/value pairs + parsed from the string. Keys which have no value are treated + has having the boolean true value. + + FIXME: for keys in the form "name[]", build an array of results, + like PHP does. + +*/ +WhAjaj.processUrlArgs = function(str) { + if( 0 === arguments.length ) { + if( ('undefined' === typeof window) || + !window.location || + !window.location.search ) return false; + else str = (''+window.location.search).substring(1); + } + if( ! str ) return false; + str = (''+str).split(/#/,2)[0]; // remove #... to avoid it being added as part of the last value. + var args = {}; + var sp = str.split(/&+/); + var rx = /^([^=]+)(=(.+))?/; + var i, m; + for( i in sp ) { + m = rx.exec( sp[i] ); + if( ! m ) continue; + args[decodeURIComponent(m[1])] = (m[3] ? decodeURIComponent(m[3]) : true); + } + return args; +}; + +/** + A simple wrapper around JSON.stringify(), using my own personal + preferred values for the 2nd and 3rd parameters. To globally + set its indentation level, assign WhAjaj.stringify.indent to + an integer value (0 for no intendation). + + This function is intended only for human-readable output, not + generic over-the-wire JSON output (where JSON.stringify(val) will + produce smaller results). +*/ +WhAjaj.stringify = function(val) { + if( ! arguments.callee.indent ) arguments.callee.indent = 4; + return JSON.stringify(val,0,arguments.callee.indent); +}; + +/** + Each instance of this class holds state information for making + AJAJ requests to a back-end system. While clients may use one + "requester" object per connection attempt, for connections to the + same back-end, using an instance configured for that back-end + can simplify usage. This class is designed so that the actual + connection-related details (i.e. _how_ it connects to the + back-end) may be re-implemented to use a client's preferred + connection mechanism (e.g. jQuery). + + The optional opt parameter may be an object with any (or all) of + the properties documented for WhAjaj.Connector.options.ajax. + Properties set here (or later via modification of the "options" + property of this object) will be used in calls to + WhAjaj.Connector.sendRequest(), and these override (normally) any + options set in WhAjaj.Connector.options.ajax. Note that + WhAjaj.Connector.sendRequest() _also_ takes an options object, + and ones passed there will override, for purposes of that one + request, any options passed in here or defined in + WhAjaj.Connector.options.ajax. See WhAjaj.Connector.options.ajax + and WhAjaj.Connector.prototype.sendRequest() for more details + about the precedence of options. + + Sample usage: + + @code + // Set up common connection-level options: + var cgi = new WhAjaj.Connector({ + url: '/cgi-bin/my.cgi', + timeout:10000, + onResponse(resp,req) { alert(JSON.stringify(resp,0.4)); }, + onError(req,opt) { + alert(opt.errorMessage); + } + }); + // Any of those options may optionally be set globally in + // WhAjaj.Connector.options.ajax (onError(), beforeSend(), and afterSend() + // are often easiest/most useful to set globally). + + // Get list of pages... + cgi.sendRequest( null, { + onResponse(resp,req){ alert(WhAjaj.stringify(resp)); } + }); + @endcode + + For common request types, clients can add functions to this + object which act as wrappers for backend-specific functionality. As + a simple example: + + @code + cgi.login = function(name,pw,ajajOpt) { + this.sendRequest( + {command:"json/login", + name:name, + password:pw + }, ajajOpt ); + }; + @endcode + + TODOs: + + - Caching of page-load requests, with a configurable lifetime. + + - Use-cases like the above login() function are a tiny bit + problematic to implement when each request has a different URL + path (i know this from the whiki and fossil implementations). + This is partly a side-effect of design descisions made back in + the very first days of this code's life. i need to go through + and see where i can bend those conventions a bit (where it won't + break my other apps unduly). +*/ +WhAjaj.Connector = function(opt) +{ + if(WhAjaj.isObject(opt)) this.options = opt; + //TODO?: this.$cache = {}; +}; + +/** + The core options used by WhAjaj.Connector instances for performing + network operations. These options can (and some _should_) + be changed by a client application. They can also be changed + on specific instances of WhAjaj.Connector, but for most applications + it is simpler to set them here and not have to bother with configuring + each WhAjaj.Connector instance. Apps which use multiple back-ends at one time, + however, will need to customize each instance for a given back-end. +*/ +WhAjaj.Connector.options = { + /** + A (meaningless) prefix to apply to WhAjaj.Connector-generated + request IDs. + */ + requestIdPrefix:'WhAjaj.Connector-', + /** + Default options for WhAjaj.Connector.sendRequest() connection + parameters. This object holds only connection-related + options and callbacks (all optional), and not options + related to the required JSON structure of any given request. + i.e. the page name used in a get-page request are not set + here but are specified as part of the request object. + + These connection options are a "normalized form" of options + often found in various AJAX libraries like jQuery, + Prototype, dojo, etc. This approach allows us to swap out + the real connection-related parts by writing a simple proxy + which transforms our "normalized" form to the + backend-specific form. For examples, see the various + implementations stored in WhAjaj.Connector.sendImpls. + + The following callback options are, in practice, almost + always set globally to some app-wide defaults: + + - onError() to report errors using a common mechanism. + - beforeSend() to start a visual activity notification + - afterSend() to disable the visual activity notification + + However, be aware that if any given WhAjaj.Connector instance is + given its own before/afterSend callback then those will + override these. Mixing shared/global and per-instance + callbacks can potentially lead to confusing results if, e.g., + the beforeSend() and afterSend() functions have side-effects + but are not used with their proper before/after partner. + + TODO: rename this to 'ajaj' (the name is historical). The + problem with renaming it is is that the word 'ajax' is + pretty prevelant in the source tree, so i can't globally + swap it out. + */ + ajax: { + /** + URL of the back-end server/CGI. + */ + url: '/some/path', + + /** + Connection method. Some connection-related functions might + override any client-defined setting. + + Must be one of 'GET' or 'POST'. For custom connection + implementation, it may optionally be some + implementation-specified value. + + Normally the API can derive this value automatically - if the + request uses JSON data it is POSTed, else it is GETted. + */ + method:'GET', + + /** + A hint whether to run the operation asynchronously or + not. Not all concrete WhAjaj.Connector.sendImpl() + implementations can support this. Interestingly, at + least one popular AJAX toolkit does not document + supporting _synchronous_ AJAX operations. All common + browser-side implementations support async operation, but + non-browser implementations might not. + */ + asynchronous:true, + + /** + A HTTP authentication login name for the AJAX + connection. Not all concrete WhAjaj.Connector.sendImpl() + implementations can support this. + */ + loginName:undefined, + + /** + An HTTP authentication login password for the AJAJ + connection. Not all concrete WhAjaj.Connector.sendImpl() + implementations can support this. + */ + loginPassword:undefined, + + /** + A connection timeout, in milliseconds, for establishing + an AJAJ connection. Not all concrete + WhAjaj.Connector.sendImpl() implementations can support this. + */ + timeout:10000, + + /** + If an AJAJ request receives JSON data from the back-end, + that data is passed as a plain Object as the response + parameter (exception: in jsonp mode it is passed a + string (why???)). The initiating request object is + passed as the second parameter, but clients can normally + ignore it (only those which need a way to map specific + requests to responses will need it). The 3rd parameter + is the same as the 'this' object for the context of the + callback, but is provided because the instance-level + callbacks (set in (WhAjaj.Connector instance).callbacks, + require it in some cases (because their 'this' is + different!). + + Note that the response might contain error information + which comes from the back-end. The difference between + this error info and the info passed to the onError() + callback is that this data indicates an + application-level error, whereas onError() is used to + report connection-level problems or when the backend + produces non-JSON data (which, when not in jsonp mode, + is unexpected and is as fatal to the request as a + connection error). + */ + onResponse: function(response, request, opt){}, + + /** + If an AJAX request fails to establish a connection or it + receives non-JSON data from the back-end, this function + is called (e.g. timeout error or host name not + resolvable). It is passed the originating request and the + "normalized" connection parameters used for that + request. The connectOpt object "should" (or "might") + have an "errorMessage" property which describes the + nature of the problem. + + Clients will almost always want to replace the default + implementation with something which integrates into + their application. + */ + onError: function(request, connectOpt) + { + alert('AJAJ request failed:\n' + +'Connection information:\n' + +JSON.stringify(connectOpt,0,4) + ); + }, + + /** + Called before each connection attempt is made. Clients + can use this to, e.g., enable a visual "network activity + notification" for the user. It is passed the original + request object and the normalized connection parameters + for the request. If this function changes opt, those + changes _are_ applied to the subsequent request. If this + function throws, neither the onError() nor afterSend() + callbacks are triggered and WhAjaj.Connector.sendImpl() + propagates the exception back to the caller. + */ + beforeSend: function(request,opt){}, + + /** + Called after an AJAJ connection attempt completes, + regardless of success or failure. Passed the same + parameters as beforeSend() (see that function for + details). + + Here's an example of setting up a visual notification on + ajax operations using jQuery (but it's also easy to do + without jQuery as well): + + @code + function startAjaxNotif(req,opt) { + var me = arguments.callee; + var c = ++me.ajaxCount; + me.element.text( c + " pending AJAX operation(s)..." ); + if( 1 == c ) me.element.stop().fadeIn(); + } + startAjaxNotif.ajaxCount = 0. + startAjaxNotif.element = jQuery('#whikiAjaxNotification'); + + function endAjaxNotif() { + var c = --startAjaxNotif.ajaxCount; + startAjaxNotif.element.text( c+" pending AJAX operation(s)..." ); + if( 0 == c ) startAjaxNotif.element.stop().fadeOut(); + } + @endcode + + Set the beforeSend/afterSend properties to those + functions to enable the notifications by default. + */ + afterSend: function(request,opt){}, + + /** + If jsonp is a string then the WhAjaj-internal response + handling code ASSUMES that the response contains a JSONP-style + construct and eval()s it after afterSend() but before onResponse(). + In this case, onResponse() will get a string value for the response + instead of a response object parsed from JSON. + */ + jsonp:undefined, + /** + Don't use yet. Planned future option. + */ + propagateExceptions:false + } +}; + + +/** + WhAjaj.Connector.prototype.callbacks defines callbacks analog + to the onXXX callbacks defined in WhAjaj.Connector.options.ajax, + with two notable differences: + + 1) these callbacks, if set, are called in addition to any + request-specific callback. The intention is to allow a framework to set + "framework-level" callbacks which should be called independently of the + request-specific callbacks (without interfering with them, e.g. + requiring special re-forwarding features). + + 2) The 'this' object in these callbacks is the Connector instance + associated with the callback, whereas the "other" onXXX form has its + "ajax options" object as its this. + + When this API says that an onXXX callback will be called for a request, + both the request's onXXX (if set) and this one (if set) will be called. +*/ +WhAjaj.Connector.prototype.callbacks = {}; +/** + Instance-specific values for AJAJ-level properties (as opposed to + application-level request properties). Options set here "override" those + specified in WhAjaj.Connector.options.ajax and are "overridden" by + options passed to sendRequest(). +*/ +WhAjaj.Connector.prototype.options = {}; + + +/** + Tries to find the given key in any of the following, returning + the first match found: opt, this.options, WhAjaj.Connector.options.ajax. + + Returns undefined if key is not found. +*/ +WhAjaj.Connector.prototype.derivedOption = function(key,opt) { + var v = opt ? opt[key] : undefined; + if( undefined !== v ) return v; + else v = this.options[key]; + if( undefined !== v ) return v; + else v = WhAjaj.Connector.options.ajax[key]; + return v; +}; + +/** + Returns a unique string on each call containing a generic + reandom request identifier string. This is not used by the core + API but can be used by client code to generate unique IDs for + each request (if needed). + + The exact format is unspecified and may change in the future. + + Request IDs can be used by clients to "match up" responses to + specific requests if needed. In practice, however, they are + seldom, if ever, needed. When passing several concurrent + requests through the same response callback, it might be useful + for some clients to be able to distinguish, possibly re-routing + them through other handlers based on the originating request type. + + If this.options.requestIdPrefix or + WhAjaj.Connector.options.requestIdPrefix is set then that text + is prefixed to the returned string. +*/ +WhAjaj.Connector.prototype.generateRequestId = function() +{ + if( undefined === arguments.callee.sequence ) + { + arguments.callee.sequence = 0; + } + var pref = this.options.requestIdPrefix || WhAjaj.Connector.options.requestIdPrefix || ''; + return pref + + WhAjaj.msTimestamp() + + '/'+(Math.round( Math.random() * 100000000) )+ + ':'+(++arguments.callee.sequence); +}; + +/** + Copies (SHALLOWLY) all properties in opt to this.options. +*/ +WhAjaj.Connector.prototype.addOptions = function(opt) { + var k, v; + for( k in opt ) { + if( ! opt.hasOwnProperty(k) ) continue /* proactive Prototype kludge! */; + this.options[k] = opt[k]; + } + return this.options; +}; + +/** + An internal helper object which holds several functions intended + to simplify the creation of concrete communication channel + implementations for WhAjaj.Connector.sendImpl(). These operations + take care of some of the more error-prone parts of ensuring that + onResponse(), onError(), etc. callbacks are called consistently + using the same rules. +*/ +WhAjaj.Connector.sendHelper = { + /** + opt is assumed to be a normalized set of + WhAjaj.Connector.sendRequest() options. This function + creates a url by concatenating opt.url and some form of + opt.urlParam. + + If opt.urlParam is an object or string then it is appended + to the url. An object is assumed to be a one-dimensional set + of simple (urlencodable) key/value pairs, and not larger + data structures. A string value is assumed to be a + well-formed, urlencoded set of key/value pairs separated by + '&' characters. + + The new/normalized URL is returned (opt is not modified). If + opt.urlParam is not set then opt.url is returned (or an + empty string if opt.url is itself a false value). + + TODO: if opt is-a Object and any key points to an array, + build up a list of keys in the form "keyname[]". We could + arguably encode sub-objects like "keyname[subkey]=...", but + i don't know if that's conventions-compatible with other + frameworks. + */ + normalizeURL: function(opt) { + var u = opt.url || ''; + if( opt.urlParam ) { + var addQ = (u.indexOf('?') >= 0) ? false : true; + var addA = addQ ? false : ((u.indexOf('&')>=0) ? true : false); + var tail = ''; + if( WhAjaj.isObject(opt.urlParam) ) { + var li = [], k; + for( k in opt.urlParam) { + li.push( k+'='+encodeURIComponent( opt.urlParam[k] ) ); + } + tail = li.join('&'); + } + else if( 'string' === typeof opt.urlParam ) { + tail = opt.urlParam; + } + u = u + (addQ ? '?' : '') + (addA ? '&' : '') + tail; + } + return u; + }, + /** + Should be called by WhAjaj.Connector.sendImpl() + implementations after a response has come back. This + function takes care of most of ensuring that framework-level + conventions involving WhAjaj.Connector.options.ajax + properties are followed. + + The request argument must be the original request passed to + the sendImpl() function. It may legally be null for GET requests. + + The opt object should be the normalized AJAX options used + for the connection. + + The resp argument may be either a plain Object or a string + (in which case it is assumed to be JSON). + + The 'this' object for this call MUST be a WhAjaj.Connector + instance in order for callback processing to work properly. + + This function takes care of the following: + + - Calling opt.afterSend() + + - If resp is a string, de-JSON-izing it to an object. + + - Calling opt.onResponse() + + - Calling opt.onError() in several common (potential) error + cases. + + - If resp is-a String and opt.jsonp then resp is assumed to be + a JSONP-form construct and is eval()d BEFORE opt.onResponse() + is called. It is arguable to eval() it first, but the logic + integrates better with the non-jsonp handler. + + The sendImpl() should return immediately after calling this. + + The sendImpl() must call only one of onSendSuccess() or + onSendError(). It must call one of them or it must implement + its own response/error handling, which is not recommended + because getting the documented semantics of the + onError/onResponse/afterSend handling correct can be tedious. + */ + onSendSuccess:function(request,resp,opt) { + var cb = this.callbacks || {}; + if( WhAjaj.isFunction(cb.afterSend) ) { + try {cb.afterSend( request, opt );} + catch(e){} + } + if( WhAjaj.isFunction(opt.afterSend) ) { + try {opt.afterSend( request, opt );} + catch(e){} + } + function doErr(){ + if( WhAjaj.isFunction(cb.onError) ) { + try {cb.onError( request, opt );} + catch(e){} + } + if( WhAjaj.isFunction(opt.onError) ) { + try {opt.onError( request, opt );} + catch(e){} + } + } + if( ! resp ) { + opt.errorMessage = "Sending of request succeeded but returned no data!"; + doErr(); + return false; + } + + if( 'string' === typeof resp ) { + try { + resp = opt.jsonp ? eval(resp) : JSON.parse(resp); + } catch(e) { + opt.errorMessage = e.toString(); + doErr(); + return; + } + } + try { + if( WhAjaj.isFunction( cb.onResponse ) ) { + cb.onResponse( resp, request, opt ); + } + if( WhAjaj.isFunction( opt.onResponse ) ) { + opt.onResponse( resp, request, opt ); + } + return true; + } + catch(e) { + opt.errorMessage = "Exception while handling inbound JSON response:\n" + + e + +"\nOriginal response data:\n"+JSON.stringify(resp,0,2) + ; + ; + doErr(); + return false; + } + }, + /** + Should be called by sendImpl() implementations after a response + has failed to connect (e.g. could not resolve host or timeout + reached). This function takes care of most of ensuring that + framework-level conventions involving WhAjaj.Connector.options.ajax + properties are followed. + + The request argument must be the original request passed to + the sendImpl() function. It may legally be null for GET + requests. + + The 'this' object for this call MUST be a WhAjaj.Connector + instance in order for callback processing to work properly. + + The opt object should be the normalized AJAX options used + for the connection. By convention, the caller of this + function "should" set opt.errorMessage to contain a + human-readable description of the error. + + The sendImpl() should return immediately after calling this. The + return value from this function is unspecified. + */ + onSendError: function(request,opt) { + var cb = this.callbacks || {}; + if( WhAjaj.isFunction(cb.afterSend) ) { + try {cb.afterSend( request, opt );} + catch(e){} + } + if( WhAjaj.isFunction(opt.afterSend) ) { + try {opt.afterSend( request, opt );} + catch(e){} + } + if( WhAjaj.isFunction( cb.onError ) ) { + try {cb.onError( request, opt );} + catch(e) {/*ignore*/} + } + if( WhAjaj.isFunction( opt.onError ) ) { + try {opt.onError( request, opt );} + catch(e) {/*ignore*/} + } + } +}; + +/** + WhAjaj.Connector.sendImpls holds several concrete + implementations of WhAjaj.Connector.prototype.sendImpl(). To use + a specific implementation by default assign + WhAjaj.Connector.prototype.sendImpl to one of these functions. + + The functions defined here require that the 'this' object be-a + WhAjaj.Connector instance. + + Historical notes: + + a) We once had an implementation based on Prototype, but that + library just pisses me off (they change base-most types' + prototypes, introducing side-effects in client code which + doesn't even use Prototype). The Prototype version at the time + had a serious toJSON() bug which caused empty arrays to + serialize as the string "[]", which broke a bunch of my code. + (That has been fixed in the mean time, but i don't use + Prototype.) + + b) We once had an implementation for the dojo library, + + If/when the time comes to add Prototype/dojo support, we simply + need to port: + + http://code.google.com/p/jsonmessage/source/browse/trunk/lib/JSONMessage/JSONMessage.inc.js + + (search that file for "dojo" and "Prototype") to this tree. That + code is this code's generic grandfather and they are still very + similar, so a port is trivial. + +*/ +WhAjaj.Connector.sendImpls = { + /** + This is a concrete implementation of + WhAjaj.Connector.prototype.sendImpl() which uses the + environment's native XMLHttpRequest class to send whiki + requests and fetch the responses. + + The only argument must be a connection properties object, as + constructed by WhAjaj.Connector.normalizeAjaxParameters(). + + If window.firebug is set then window.firebug.watchXHR() is + called to enable monitoring of the XMLHttpRequest object. + + This implementation honors the loginName and loginPassword + connection parameters. + + Returns the XMLHttpRequest object. + + This implementation requires that the 'this' object be-a + WhAjaj.Connector. + + This implementation uses setTimeout() to implement the + timeout support, and thus the JS engine must provide that + functionality. + */ + XMLHttpRequest: function(request, args) + { + var json = WhAjaj.isObject(request) ? JSON.stringify(request) : request; + var xhr = new XMLHttpRequest(); + var startTime = (new Date()).getTime(); + var timeout = args.timeout || 10000/*arbitrary!*/; + var hitTimeout = false; + var done = false; + var tmid /* setTimeout() ID */; + var whself = this; + function handleTimeout() + { + hitTimeout = true; + if( ! done ) + { + var now = (new Date()).getTime(); + try { xhr.abort(); } catch(e) {/*ignore*/} + // see: http://www.w3.org/TR/XMLHttpRequest/#the-abort-method + args.errorMessage = "Timeout of "+timeout+"ms reached after "+(now-startTime)+"ms during AJAX request."; + WhAjaj.Connector.sendHelper.onSendError.apply( whself, [request, args] ); + } + return; + } + function onStateChange() + { // reminder to self: apparently 'this' is-not-a XHR :/ + if( hitTimeout ) + { /* we're too late - the error was already triggered. */ + return; + } + + if( 4 == xhr.readyState ) + { + done = true; + if( tmid ) + { + clearTimeout( tmid ); + tmid = null; + } + if( (xhr.status >= 200) && (xhr.status < 300) ) + { + WhAjaj.Connector.sendHelper.onSendSuccess.apply( whself, [request, xhr.responseText, args] ); + return; + } + else + { + if( undefined === args.errorMessage ) + { + args.errorMessage = "Error sending a '"+args.method+"' AJAX request to " + +"["+args.url+"]: " + +"Status text=["+xhr.statusText+"]" + ; + WhAjaj.Connector.sendHelper.onSendError.apply( whself, [request, args] ); + } + else { /*maybe it was was set by the timeout handler. */ } + return; + } + } + }; + + xhr.onreadystatechange = onStateChange; + if( ('undefined'!==(typeof window)) && ('firebug' in window) && ('watchXHR' in window.firebug) ) + { /* plug in to firebug lite's XHR monitor... */ + window.firebug.watchXHR( xhr ); + } + try + { + //alert( JSON.stringify( args )); + function xhrOpen() + { + if( ('loginName' in args) && args.loginName ) + { + xhr.open( args.method, args.url, args.asynchronous, args.loginName, args.loginPassword ); + } + else + { + xhr.open( args.method, args.url, args.asynchronous ); + } + } + if( json && ('POST' === args.method.toUpperCase()) ) + { + xhrOpen(); + xhr.setRequestHeader("Content-Type", "application/json; charset=utf-8"); + // Google Chrome warns that it refuses to set these + // "unsafe" headers (his words, not mine): + // xhr.setRequestHeader("Content-length", json.length); + // xhr.setRequestHeader("Connection", "close"); + xhr.send( json ); + } + else /* assume GET */ + { + xhrOpen(); + xhr.send(null); + } + tmid = setTimeout( handleTimeout, timeout ); + return xhr; + } + catch(e) + { + args.errorMessage = e.toString(); + WhAjaj.Connector.sendHelper.onSendError.apply( whself, [request, args] ); + return undefined; + } + }/*XMLHttpRequest()*/, + /** + This is a concrete implementation of + WhAjaj.Connector.prototype.sendImpl() which uses the jQuery + AJAX API to send requests and fetch the responses. + + The first argument may be either null/false, an Object + containing toJSON-able data to post to the back-end, or such an + object in JSON string form. + + The second argument must be a connection properties object, as + constructed by WhAjaj.Connector.normalizeAjaxParameters(). + + If window.firebug is set then window.firebug.watchXHR() is + called to enable monitoring of the XMLHttpRequest object. + + This implementation honors the loginName and loginPassword + connection parameters. + + Returns the XMLHttpRequest object. + + This implementation requires that the 'this' object be-a + WhAjaj.Connector. + */ + jQuery:function(request,args) + { + var data = request || undefined; + var whself = this; + if( data ) { + if('string'!==typeof data) { + try { + data = JSON.stringify(data); + } + catch(e) { + WhAjaj.Connector.sendHelper.onSendError.apply( whself, [request, args] ); + return; + } + } + } + var ajopt = { + url: args.url, + data: data, + type: args.method, + async: args.asynchronous, + password: (undefined !== args.loginPassword) ? args.loginPassword : undefined, + username: (undefined !== args.loginName) ? args.loginName : undefined, + contentType: 'application/json; charset=utf-8', + error: function(xhr, textStatus, errorThrown) + { + //this === the options for this ajax request + args.errorMessage = "Error sending a '"+ajopt.type+"' request to ["+ajopt.url+"]: " + +"Status text=["+textStatus+"]" + +(errorThrown ? ("Error=["+errorThrown+"]") : "") + ; + WhAjaj.Connector.sendHelper.onSendError.apply( whself, [request, args] ); + }, + success: function(data) + { + WhAjaj.Connector.sendHelper.onSendSuccess.apply( whself, [request, data, args] ); + }, + /* Set dataType=text instead of json to keep jQuery from doing our carefully + written response handling for us. + */ + dataType: 'text' + }; + if( undefined !== args.timeout ) + { + ajopt.timeout = args.timeout; + } + try + { + return jQuery.ajax(ajopt); + } + catch(e) + { + args.errorMessage = e.toString(); + WhAjaj.Connector.sendHelper.onSendError.apply( whself, [request, args] ); + return undefined; + } + }/*jQuery()*/, + /** + This is a concrete implementation of + WhAjaj.Connector.prototype.sendImpl() which uses the rhino + Java API to send requests and fetch the responses. + + Limitations vis-a-vis the interface: + + - timeouts are not supported. + + - asynchronous mode is not supported because implementing it + requires the ability to kill a running thread (which is deprecated + in the Java API). + + TODOs: + + - add socket timeouts. + + - support HTTP proxy. + + The Java APIs support this, it just hasn't been added here yet. + */ + rhino:function(request,args) + { + var self = this; + var data = request || undefined; + if( data ) { + if('string'!==typeof data) { + try { + data = JSON.stringify(data); + } + catch(e) { + WhAjaj.Connector.sendHelper.onSendError.apply( self, [request, args] ); + return; + } + } + } + var url; + var con; + var IO = new JavaImporter(java.io); + var wr; + var rd, ln, json = []; + function setIncomingCookies(list){ + if(!list || !list.length) return; + if( !self.cookies ) self.cookies = {}; + var k, v, i; + for( i = 0; i < list.length; ++i ){ + v = list[i].split('=',2); + k = decodeURIComponent(v[0]) + v = v[0] ? decodeURIComponent(v[0].split(';',2)[0]) : null; + //print("RECEIVED COOKIE: "+k+"="+v); + if(!v) { + delete self.cookies[k]; + continue; + }else{ + self.cookies[k] = v; + } + } + }; + function setOutboundCookies(conn){ + if(!self.cookies) return; + var k, v; + for( k in self.cookies ){ + if(!self.cookies.hasOwnProperty(k)) continue /*kludge for broken JS libs*/; + v = self.cookies[k]; + conn.addRequestProperty("Cookie", encodeURIComponent(k)+'='+encodeURIComponent(v)); + //print("SENDING COOKIE: "+k+"="+v); + } + }; + try{ + url = new java.net.URL( args.url ) + con = url.openConnection(/*FIXME: add proxy support!*/); + con.setRequestProperty("Accept-Charset","utf-8"); + setOutboundCookies(con); + if(data){ + con.setRequestProperty("Content-Type","application/json; charset=utf-8"); + con.setDoOutput( true ); + wr = new IO.OutputStreamWriter(con.getOutputStream()) + wr.write(data); + wr.flush(); + wr.close(); + wr = null; + //print("POSTED: "+data); + } + rd = new IO.BufferedReader(new IO.InputStreamReader(con.getInputStream())); + //var skippedHeaders = false; + while ((line = rd.readLine()) !== null) { + //print("LINE: "+line); + //if(!line.length && !skippedHeaders){ + // skippedHeaders = true; + // json = []; + // continue; + //} + json.push(line); + } + setIncomingCookies(con.getHeaderFields().get("Set-Cookie")); + }catch(e){ + args.errorMessage = e.toString(); + WhAjaj.Connector.sendHelper.onSendError.apply( self, [request, args] ); + return undefined; + } + try { if(wr) wr.close(); } catch(e) { /*ignore*/} + try { if(rd) rd.close(); } catch(e) { /*ignore*/} + json = json.join(''); + //print("READ IN JSON: "+json); + WhAjaj.Connector.sendHelper.onSendSuccess.apply( self, [request, json, args] ); + }/*rhino*/ +}; + +/** + An internal function which takes an object containing properties + for a WhAjaj.Connector network request. This function creates a new + object containing a superset of the properties from: + + a) opt + b) this.options + c) WhAjaj.Connector.options.ajax + + in that order, using the first one it finds. + + All non-function properties are _deeply_ copied via JSON cloning + in order to prevent accidental "cross-request pollenation" (been + there, done that). Functions cannot be cloned and are simply + copied by reference. + + This function throws if JSON-copying one of the options fails + (e.g. due to cyclic data structures). + + Reminder to self: this function does not "normalize" opt.urlParam + by encoding it into opt.url, mainly for historical reasons, but + also because that behaviour was specifically undesirable in this + code's genetic father. +*/ +WhAjaj.Connector.prototype.normalizeAjaxParameters = function (opt) +{ + var rc = {}; + function merge(k,v) + { + if( rc.hasOwnProperty(k) ) return; + else if( WhAjaj.isFunction(v) ) {} + else if( WhAjaj.isObject(v) ) v = JSON.parse( JSON.stringify(v) ); + rc[k]=v; + } + function cp(obj) { + if( ! WhAjaj.isObject(obj) ) return; + var k; + for( k in obj ) { + if( ! obj.hasOwnProperty(k) ) continue /* i will always hate the Prototype designers for this. */; + merge(k, obj[k]); + } + } + cp( opt ); + cp( this.options ); + cp( WhAjaj.Connector.options.ajax ); + // no, not here: rc.url = WhAjaj.Connector.sendHelper.normalizeURL(rc); + return rc; +}; + +/** + This is the generic interface for making calls to a back-end + JSON-producing request handler. It is a simple wrapper around + WhAjaj.Connector.prototype.sendImpl(), which just normalizes the + connection options for sendImpl() and makes sure that + opt.beforeSend() is (possibly) called. + + The request parameter must either be false/null/empty or a + fully-populated JSON-able request object (which will be sent as + unencoded application/json text), depending on the type of + request being made. It is never semantically legal (in this API) + for request to be a string/number/true/array value. As a rule, + only POST requests use the request data. GET requests should + encode their data in opt.url or opt.urlParam (see below). + + opt must contain the network-related parameters for the request. + Paramters _not_ set in opt are pulled from this.options or + WhAjaj.Connector.options.ajax (in that order, using the first + value it finds). Thus the set of connection-level options used + for the request are a superset of those various sources. + + The "normalized" (or "superimposed") opt object's URL may be + modified before the request is sent, as follows: + + if opt.urlParam is a string then it is assumed to be properly + URL-encoded parameters and is appended to the opt.url. If it is + an Object then it is assumed to be a one-dimensional set of + key/value pairs with simple values (numbers, strings, booleans, + null, and NOT objects/arrays). The keys/values are URL-encoded + and appended to the URL. + + The beforeSend() callback (see below) can modify the options + object before the request attempt is made. + + The callbacks in the normalized opt object will be triggered as + follows (if they are set to Function values): + + - beforeSend(request,opt) will be called before any network + processing starts. If beforeSend() throws then no other + callbacks are triggered and this function propagates the + exception. This function is passed normalized connection options + as its second parameter, and changes this function makes to that + object _will_ be used for the pending connection attempt. + + - onError(request,opt) will be called if a connection to the + back-end cannot be established. It will be passed the original + request object (which might be null, depending on the request + type) and the normalized options object. In the error case, the + opt object passed to onError() "should" have a property called + "errorMessage" which contains a description of the problem. + + - onError(request,opt) will also be called if connection + succeeds but the response is not JSON data. + + - onResponse(response,request) will be called if the response + returns JSON data. That data might hold an error response code - + clients need to check for that. It is passed the response object + (a plain object) and the original request object. + + - afterSend(request,opt) will be called directly after the + AJAX request is finished, before onError() or onResonse() are + called. Possible TODO: we explicitly do NOT pass the response to + this function in order to keep the line between the responsibilities + of the various callback clear (otherwise this could be used the same + as onResponse()). In practice it would sometimes be useful have the + response passed to this function, mainly for logging/debugging + purposes. + + The return value from this function is meaningless because + AJAX operations tend to take place asynchronously. + +*/ +WhAjaj.Connector.prototype.sendRequest = function(request,opt) +{ + if( !WhAjaj.isFunction(this.sendImpl) ) + { + throw new Error("This object has no sendImpl() member function! I don't know how to send the request!"); + } + var ex = false; + var av = Array.prototype.slice.apply( arguments, [0] ); + + /** + FIXME: how to handle the error, vis-a-vis- the callbacks, if + normalizeAjaxParameters() throws? It can throw if + (de)JSON-izing fails. + */ + var norm = this.normalizeAjaxParameters( WhAjaj.isObject(opt) ? opt : {} ); + norm.url = WhAjaj.Connector.sendHelper.normalizeURL(norm); + if( ! request ) norm.method = 'GET'; + var cb = this.callbacks || {}; + if( this.callbacks && WhAjaj.isFunction(this.callbacks.beforeSend) ) { + this.callbacks.beforeSend( request, norm ); + } + if( WhAjaj.isFunction(norm.beforeSend) ){ + norm.beforeSend( request, norm ); + } + //alert( WhAjaj.stringify(request)+'\n'+WhAjaj.stringify(norm)); + try { this.sendImpl( request, norm ); } + catch(e) { ex = e; } + if(ex) throw ex; +}; + +/** + sendImpl() holds a concrete back-end connection implementation. It + can be replaced with a custom implementation if one follows the rules + described throughout this API. See WhAjaj.Connector.sendImpls for + the concrete implementations included with this API. +*/ +//WhAjaj.Connector.prototype.sendImpl = WhAjaj.Connector.sendImpls.XMLHttpRequest; +//WhAjaj.Connector.prototype.sendImpl = WhAjaj.Connector.sendImpls.rhino; +//WhAjaj.Connector.prototype.sendImpl = WhAjaj.Connector.sendImpls.jQuery; + +if( 'undefined' !== typeof jQuery ){ + WhAjaj.Connector.prototype.sendImpl = WhAjaj.Connector.sendImpls.jQuery; +} +else { + WhAjaj.Connector.prototype.sendImpl = WhAjaj.Connector.sendImpls.XMLHttpRequest; +} ADDED ajax/wiki-editor.html Index: ajax/wiki-editor.html ================================================================== --- /dev/null +++ ajax/wiki-editor.html @@ -0,0 +1,381 @@ + + + + + Fossil/JSON Wiki Editor Prototype + + + + + + + + + + + + + +

PROTOTYPE JSON-based Fossil Wiki Editor

+ +See also: main test page. + +
+Login: +
+ +or: +name: +pw: + + + +
+ + +
+Quick-posts:
+ + + + + + + +
+ + + + + + + + + + + + + + + + +
Page ListContent
+
+
+
+
+ +
Response
+ +
+
+
+
+ + Index: art/CollRev3.dia ================================================================== --- art/CollRev3.dia +++ art/CollRev3.dia @@ -784,11 +784,11 @@ - # + # belongs to# Index: art/CollRev4.dia ================================================================== --- art/CollRev4.dia +++ art/CollRev4.dia @@ -474,11 +474,11 @@ - # + # child# @@ -508,11 +508,11 @@ - # + # child# @@ -609,11 +609,11 @@ - # + # parent# ADDED auto.def Index: auto.def ================================================================== --- /dev/null +++ auto.def @@ -0,0 +1,669 @@ +# System autoconfiguration. Try: ./configure --help + +# This must be above "options" below because it implicitly brings in the +# default Autosetup options, things like --prefix. +use cc cc-lib + +options { + with-openssl:path|auto|tree|none + => {Look for OpenSSL in the given path, automatically, in the source tree, or none} + with-miniz=0 => {Use miniz from the source tree} + with-zlib:path|auto|tree + => {Look for zlib in the given path, automatically, or in the source tree} + with-exec-rel-paths=0 + => {Enable relative paths for external diff/gdiff} + with-sanitizer: => {Build with C compiler's -fsanitize=LIST; e.g. address,enum,null,undefined} + with-th1-docs=0 => {Enable TH1 for embedded documentation pages} + with-th1-hooks=0 => {Enable TH1 hooks for commands and web pages} + with-tcl:path => {Enable Tcl integration, with Tcl in the specified path} + with-tcl-stubs=0 => {Enable Tcl integration via stubs library mechanism} + with-tcl-private-stubs=0 + => {Enable Tcl integration via private stubs mechanism} + with-mman=0 => {Enable use of POSIX memory APIs from "sys/mman.h"} + with-see=0 => {Enable the SQLite Encryption Extension (SEE)} + print-minimum-sqlite-version=0 + => {print the minimum SQLite version number required, and exit} + internal-sqlite=1 => {Don't use the internal SQLite, use the system one} + static=0 => {Link a static executable} + fusefs=1 => {Disable the Fuse Filesystem} + fossil-debug=0 => {Build with fossil debugging enabled} + no-opt=0 => {Build without optimization} + json=0 => {Build with fossil JSON API enabled} +} + +# Update the minimum required SQLite version number here, and also +# in src/main.c near the sqlite3_libversion_number() call. Take care +# that both places agree! +define MINIMUM_SQLITE_VERSION "3.35.0" + +# This is useful for people wanting Fossil to use an external SQLite library +# to compare the one they have against the minimum required +if {[opt-bool print-minimum-sqlite-version]} { + puts [get-define MINIMUM_SQLITE_VERSION] + exit 0 +} + +# sqlite wants these types if possible +cc-with {-includes {stdint.h inttypes.h}} { + cc-check-types uint32_t uint16_t int16_t uint8_t +} + +# Use pread/pwrite system calls in place of seek + read/write if possible +define USE_PREAD [cc-check-functions pread] + +# If we have cscope here, we'll use it in the "tags" target +if {[cc-check-progs cscope]} { + define COLLECT_CSCOPE_DATA "cscope -bR -s$::autosetup(srcdir)/src" +} else { + define COLLECT_CSCOPE_DATA "" +} + +# Find tclsh for the test suite. +# +# We can't use jimsh for this: the test suite uses features of Tcl that +# Jim doesn't support, either statically or due to the way it's built by +# autosetup. For example, Jim supports `file normalize`, but only if +# you build it with HAVE_REALPATH, which won't ever be defined in this +# context because autosetup doesn't try to discover platform-specific +# details like that before it decides to build jimsh0. Besides which, +# autosetup won't build jimsh0 at all if it can find tclsh itself. +# Ironically, this means we may right now be running under either jimsh0 +# or a version of tclsh that we find unsuitable below! +cc-check-progs tclsh +set hbtd /usr/local/Cellar/tcl-tk +if {[string equal false [get-define TCLSH]]} { + msg-result "WARNING: 'make test' will not run here." +} else { + set v [exec /bin/sh -c "echo 'puts \$tcl_version' | tclsh"] + if {[expr {$v >= 8.6}]} { + msg-result "Found Tclsh version $v in the PATH." + define TCLSH tclsh + } elseif {[file isdirectory $hbtd]} { + # This is a macOS system with the Homebrew version of Tcl/Tk + # installed. Select the newest version. It won't normally be + # in the PATH to avoid shadowing /usr/bin/tclsh, and even if it + # were in the PATH, it's bad practice to put /usr/local/bin (the + # Homebrew default) ahead of /usr/bin, especially given that + # it's user-writeable by default with Homebrew. Thus, we can be + # pretty sure the only way to call it is with an absolute path. + set v [exec ls -tr $hbtd | tail -1] + set path "$hbtd/$v/bin/tclsh" + define TCLSH $path + msg-result "Using Homebrew Tcl/Tk version $path." + } else { + msg-result "WARNING: tclsh $v found; need >= 8.6 for 'make test'." + define TCLSH false ;# force "make test" failure via /usr/bin/false + } +} + +define EXTRA_CFLAGS "-Wall" +define EXTRA_LDFLAGS "" +define USE_SYSTEM_SQLITE 0 +define USE_LINENOISE 0 +define FOSSIL_ENABLE_MINIZ 0 +define USE_MMAN_H 0 +define USE_SEE 0 + + +# Maintain the C89/C90-style order of variable declarations before statements. +# Check if the compiler supports the respective warning flag. +if {[cctest -cflags -Wdeclaration-after-statement]} { + define-append EXTRA_CFLAGS -Wdeclaration-after-statement +} + + +# This procedure is a customized version of "cc-check-function-in-lib", +# that does not modify the LIBS variable. Its use prevents prematurely +# pulling in libraries that will be added later anyhow (e.g. "-ldl"). +proc check-function-in-lib {function libs {otherlibs {}}} { + if {[string length $otherlibs]} { + msg-checking "Checking for $function in $libs with $otherlibs..." + } else { + msg-checking "Checking for $function in $libs..." + } + set found 0 + cc-with [list -libs $otherlibs] { + if {[cctest_function $function]} { + msg-result "none needed" + define lib_$function "" + incr found + } else { + foreach lib $libs { + cc-with [list -libs -l$lib] { + if {[cctest_function $function]} { + msg-result -l$lib + define lib_$function -l$lib + incr found + break + } + } + } + } + } + if {$found} { + define [feature-define-name $function] + } else { + msg-result "no" + } + return $found +} + +if {![opt-bool internal-sqlite]} { + proc find_system_sqlite {} { + + # On some systems (slackware), libsqlite3 requires -ldl to link. So + # search for the system SQLite once with -ldl, and once without. If + # the library can only be found with $extralibs set to -ldl, then + # the code below will append -ldl to LIBS. + # + foreach extralibs {{} {-ldl}} { + + # Locate the system SQLite by searching for sqlite3_open(). Then check + # if sqlite3_stmt_isexplain can be found as well. If we can find open() but + # not stmt_isexplain(), then the system SQLite is too old to link against + # fossil. + # + if {[check-function-in-lib sqlite3_open sqlite3 $extralibs]} { + # Success. Update symbols and return. + # + define USE_SYSTEM_SQLITE 1 + define-append LIBS -lsqlite3 + define-append LIBS $extralibs + return + } + } + user-error "system sqlite3 not found" + } + + find_system_sqlite + + proc test_system_sqlite {} { + # Check compatibility of the system SQLite library by running the sqlcompttest.c + # program in the source tree + # passes MINIMUM_SQLITE_VERSION set at the top of this file to sqlcompttest.c + # + set cmdline {} + lappend cmdline {*}[get-define CCACHE] + lappend cmdline {*}[get-define CC] {*}[get-define CFLAGS] + lappend cmdline $::autosetup(dir)/../src/sqlcompattest.c -o conftest__ + lappend cmdline {*}[get-define LDFLAGS] + lappend cmdline {*}[get-define LIBS] + set sqlite-version [string cat "-D MINIMUM_SQLITE_VERSION=" [get-define MINIMUM_SQLITE_VERSION]] + lappend cmdline {*}[set sqlite-version] + set ok 1 + set err [catch {exec-with-stderr {*}$cmdline} result errinfo] + if {$err} { + configlog "Failed: [join $cmdline]" + if {[string length $result]>0} {configlog $result} + configlog "============" + set ok 0 + } elseif {$::autosetup(debug)} { + configlog "Compiled OK: [join $cmdline]" + configlog "============" + } + if {!$ok} { + user-error "unable to compile SQLite compatibility test program" + } + set err [catch {exec-with-stderr ./conftest__} result errinfo] + if {[get-define build] eq [get-define host]} { + set err [catch {exec-with-stderr ./conftest__} result errinfo] + if {$err} { + user-error $result + } + } + file delete ./conftest__ + } + test_system_sqlite + +} + +proc is_mingw {} { + return [string match *mingw* [get-define host]] +} + +if {[is_mingw]} { + define-append EXTRA_CFLAGS -DBROKEN_MINGW_CMDLINE + define-append LIBS -lkernel32 -lws2_32 +} else { + # + # NOTE: All platforms except MinGW should use the linenoise + # package. It is currently unsupported on Win32. + # + define USE_LINENOISE 1 +} + +if {[string match *-solaris* [get-define host]]} { + define-append EXTRA_CFLAGS {-D_XOPEN_SOURCE=500 -D__EXTENSIONS__} +} + +if {[opt-bool fossil-debug]} { + define CFLAGS {-g -O0 -Wall} + define-append CFLAGS -DFOSSIL_DEBUG + msg-result "Debugging support enabled" +} + +if {[opt-bool no-opt]} { + define CFLAGS {-g -O0 -Wall} + msg-result "Builting without compiler optimization" + if {[opt-bool fossil-debug]} { + define-append CFLAGS -DFOSSIL_DEBUG + } +} + +if {[opt-bool with-mman]} { + define-append EXTRA_CFLAGS -DUSE_MMAN_H + define USE_MMAN_H 1 + msg-result "Enabling \"sys/mman.h\" support" +} + +if {[opt-bool with-see]} { + define-append EXTRA_CFLAGS -DUSE_SEE + define USE_SEE 1 + msg-result "Enabling encryption support" +} + +if {[opt-bool json]} { + # Reminder/FIXME (stephan): FOSSIL_ENABLE_JSON + # is required in the CFLAGS because json*.c + # have #ifdef guards around the whole file without + # reading config.h first. + define-append EXTRA_CFLAGS -DFOSSIL_ENABLE_JSON + define FOSSIL_ENABLE_JSON + msg-result "JSON support enabled" +} + +if {[opt-bool with-exec-rel-paths]} { + define-append EXTRA_CFLAGS -DFOSSIL_ENABLE_EXEC_REL_PATHS + define FOSSIL_ENABLE_EXEC_REL_PATHS + msg-result "Relative paths in external diff/gdiff enabled" +} + +if {[opt-bool with-th1-docs]} { + define-append EXTRA_CFLAGS -DFOSSIL_ENABLE_TH1_DOCS + define FOSSIL_ENABLE_TH1_DOCS + msg-result "TH1 embedded documentation support enabled" +} + +if {[opt-bool with-th1-hooks]} { + define-append EXTRA_CFLAGS -DFOSSIL_ENABLE_TH1_HOOKS + define FOSSIL_ENABLE_TH1_HOOKS + msg-result "TH1 hooks support enabled" +} + +#if {[opt-bool markdown]} { +# # no-op. Markdown is now enabled by default. +# msg-result "Markdown support enabled" +#} + +if {[opt-bool static]} { + # XXX: This will not work on all systems. + define-append EXTRA_LDFLAGS -static + msg-result "Trying to link statically" +} else { + define-append EXTRA_CFLAGS -DFOSSIL_DYNAMIC_BUILD=1 + define FOSSIL_DYNAMIC_BUILD +} + +# Check for libraries that need to be sorted out early +cc-check-function-in-lib iconv iconv + +# Helper for OpenSSL checking +proc check-for-openssl {msg {cflags {}} {libs {-lssl -lcrypto -lpthread}}} { + msg-checking "Checking for $msg..." + set rc 0 + if {[is_mingw]} { + lappend libs -lgdi32 -lwsock32 -lcrypt32 + } + if {[info exists ::zlib_lib]} { + lappend libs $::zlib_lib + } + msg-quiet cc-with [list -cflags $cflags -libs $libs] { + if {[cc-check-includes openssl/ssl.h] && \ + [cc-check-functions SSL_new]} { + incr rc + } + } + if {!$rc && ![is_mingw]} { + # On some systems, OpenSSL appears to require -ldl to link. + lappend libs -ldl + msg-quiet cc-with [list -cflags $cflags -libs $libs] { + if {[cc-check-includes openssl/ssl.h] && \ + [cc-check-functions SSL_new]} { + incr rc + } + } + } + if {$rc} { + msg-result "ok" + return 1 + } else { + msg-result "no" + return 0 + } +} + +if {[opt-bool with-miniz]} { + define FOSSIL_ENABLE_MINIZ 1 + msg-result "Using miniz for compression" +} else { + # Check for zlib, using the given location if specified + set zlibpath [opt-val with-zlib] + if {$zlibpath eq "tree"} { + set zlibdir [file dirname $autosetup(dir)]/compat/zlib + if {![file isdirectory $zlibdir]} { + user-error "The zlib in source tree directory does not exist" + } + cc-with [list -cflags "-I$zlibdir -L$zlibdir"] + define-append EXTRA_CFLAGS -I$zlibdir + define-append LIBS $zlibdir/libz.a + set ::zlib_lib $zlibdir/libz.a + msg-result "Using zlib in source tree" + } else { + if {$zlibpath ni {auto ""}} { + cc-with [list -cflags "-I$zlibpath -L$zlibpath"] + define-append EXTRA_CFLAGS -I$zlibpath + define-append EXTRA_LDFLAGS -L$zlibpath + msg-result "Using zlib from $zlibpath" + } + if {![cc-check-includes zlib.h] || ![check-function-in-lib inflateEnd z]} { + user-error "zlib not found please install it or specify the location with --with-zlib" + } + set ::zlib_lib -lz + } +} + +set ssldirs [opt-val with-openssl] +if {$ssldirs ne "none"} { + if {[opt-bool with-miniz]} { + user-error "The --with-miniz option is incompatible with OpenSSL" + } + set found 0 + if {$ssldirs eq "tree"} { + set ssldir [file dirname $autosetup(dir)]/compat/openssl + if {![file isdirectory $ssldir]} { + user-error "The OpenSSL in source tree directory does not exist" + } + set msg "ssl in $ssldir" + set cflags "-I$ssldir/include" + set ldflags "-L$ssldir" + set ssllibs "$ssldir/libssl.a $ssldir/libcrypto.a -lpthread" + set found [check-for-openssl "ssl in source tree" "$cflags $ldflags" $ssllibs] + } else { + if {$ssldirs in {auto ""}} { + catch { + set cflags [exec pkg-config openssl --cflags-only-I] + set ldflags [exec pkg-config openssl --libs-only-L] + set found [check-for-openssl "ssl via pkg-config" "$cflags $ldflags"] + } msg + if {!$found} { + set ssldirs "{} /usr/sfw /usr/local/ssl /usr/lib/ssl /usr/ssl \ + /usr/pkg /usr/local /usr /usr/local/opt/openssl \ + /opt/homebrew/opt/openssl" + } + } + if {!$found} { + foreach dir $ssldirs { + if {$dir eq ""} { + set msg "system ssl" + set cflags "" + set ldflags "" + } else { + set msg "ssl in $dir" + set cflags "-I$dir/include" + set ldflags "-L$dir/lib" + } + if {[check-for-openssl $msg "$cflags $ldflags"]} { + incr found + break + } + } + } + } + if {$found} { + define FOSSIL_ENABLE_SSL + define-append EXTRA_CFLAGS $cflags + define-append EXTRA_LDFLAGS $ldflags + if {[info exists ssllibs]} { + define-append LIBS $ssllibs + } else { + define-append LIBS -lssl -lcrypto + } + if {[info exists ::zlib_lib]} { + define-append LIBS $::zlib_lib + } + if {[is_mingw]} { + define-append LIBS -lgdi32 -lwsock32 -lcrypt32 + } + msg-result "HTTPS support enabled" + + # Silence OpenSSL deprecation warnings on Mac OS X 10.7. + if {[string match *-darwin* [get-define host]]} { + if {[cctest -cflags {-Wdeprecated-declarations}]} { + define-append EXTRA_CFLAGS -Wdeprecated-declarations + } + } + } else { + user-error "OpenSSL not found. Consider --with-openssl=none to disable HTTPS support" + } +} else { + if {[info exists ::zlib_lib]} { + define-append LIBS $::zlib_lib + } +} + +set tclpath [opt-val with-tcl] +if {$tclpath ne ""} { + set tclprivatestubs [opt-bool with-tcl-private-stubs] + # Note parse-tclconfig-sh is in autosetup/local.tcl + if {$tclpath eq "1"} { + set tcldir [file dirname $autosetup(dir)]/compat/tcl-8.6 + if {$tclprivatestubs} { + set tclconfig(TCL_INCLUDE_SPEC) -I$tcldir/generic + set tclconfig(TCL_VERSION) {Private Stubs} + set tclconfig(TCL_PATCH_LEVEL) {} + set tclconfig(TCL_PREFIX) $tcldir + set tclconfig(TCL_LD_FLAGS) { } + } else { + # Use the system Tcl. Look in some likely places. + array set tclconfig [parse-tclconfig-sh \ + $tcldir/unix $tcldir/win \ + /usr /usr/local /usr/share /opt/local] + set msg "on your system" + } + } else { + array set tclconfig [parse-tclconfig-sh $tclpath] + set msg "at $tclpath" + } + if {[opt-bool static]} { + set tclconfig(TCL_LD_FLAGS) { } + } + if {![info exists tclconfig(TCL_INCLUDE_SPEC)]} { + user-error "Cannot find Tcl $msg" + } + set tclstubs [opt-bool with-tcl-stubs] + if {$tclprivatestubs} { + define FOSSIL_ENABLE_TCL_PRIVATE_STUBS + define USE_TCL_STUBS + } elseif {$tclstubs && $tclconfig(TCL_SUPPORTS_STUBS)} { + set libs "$tclconfig(TCL_STUB_LIB_SPEC)" + define FOSSIL_ENABLE_TCL_STUBS + define USE_TCL_STUBS + } else { + set libs "$tclconfig(TCL_LIB_SPEC) $tclconfig(TCL_LIBS)" + } + set cflags $tclconfig(TCL_INCLUDE_SPEC) + if {!$tclprivatestubs} { + set foundtcl 0; # Did we find a working Tcl library? + cc-with [list -cflags $cflags -libs $libs] { + if {$tclstubs} { + if {[cc-check-functions Tcl_InitStubs]} { + set foundtcl 1 + } + } else { + if {[cc-check-functions Tcl_CreateInterp]} { + set foundtcl 1 + } + } + } + if {!$foundtcl && [string match *-lieee* $libs]} { + # On some systems, using "-lieee" from TCL_LIB_SPEC appears + # to cause issues. + msg-result "Removing \"-lieee\" and retrying for Tcl..." + set libs [string map [list -lieee ""] $libs] + cc-with [list -cflags $cflags -libs $libs] { + if {$tclstubs} { + if {[cc-check-functions Tcl_InitStubs]} { + set foundtcl 1 + } + } else { + if {[cc-check-functions Tcl_CreateInterp]} { + set foundtcl 1 + } + } + } + } + if {!$foundtcl && ![string match *-lpthread* $libs]} { + # On some systems, TCL_LIB_SPEC appears to be missing + # "-lpthread". Try adding it. + msg-result "Adding \"-lpthread\" and retrying for Tcl..." + set libs "$libs -lpthread" + cc-with [list -cflags $cflags -libs $libs] { + if {$tclstubs} { + if {[cc-check-functions Tcl_InitStubs]} { + set foundtcl 1 + } + } else { + if {[cc-check-functions Tcl_CreateInterp]} { + set foundtcl 1 + } + } + } + } + if {!$foundtcl} { + if {$tclstubs} { + user-error "Cannot find a usable Tcl stubs library $msg" + } else { + user-error "Cannot find a usable Tcl library $msg" + } + } + } + set version $tclconfig(TCL_VERSION)$tclconfig(TCL_PATCH_LEVEL) + msg-result "Found Tcl $version at $tclconfig(TCL_PREFIX)" + if {!$tclprivatestubs} { + define-append LIBS $libs + } + define-append EXTRA_CFLAGS $cflags + if {[info exists zlibpath] && $zlibpath eq "tree"} { + # + # NOTE: When using zlib in the source tree, prevent Tcl from + # pulling in the system one. + # + set tclconfig(TCL_LD_FLAGS) [string map [list -lz ""] \ + $tclconfig(TCL_LD_FLAGS)] + } + # + # NOTE: Remove "-ldl" from the TCL_LD_FLAGS because it will be + # be checked for near the bottom of this file. + # + set tclconfig(TCL_LD_FLAGS) [string map [list -ldl ""] \ + $tclconfig(TCL_LD_FLAGS)] + define-append EXTRA_LDFLAGS $tclconfig(TCL_LD_FLAGS) + define FOSSIL_ENABLE_TCL +} + +# Network functions require libraries on some systems +cc-check-function-in-lib gethostbyname nsl +if {![cc-check-function-in-lib socket {socket network}]} { + # Last resort, may be Windows + if {[is_mingw]} { + define-append LIBS -lwsock32 + } +} + +# The SMTP module requires special libraries and headers for MX DNS +# record lookups and such. +cc-check-includes arpa/nameser.h +cc-include-needs bind/resolv.h netinet/in.h +cc-check-includes bind/resolv.h +cc-check-includes resolv.h +if { !(([cc-check-function-in-lib dn_expand resolv] || + [cc-check-function-in-lib ns_name_uncompress {bind resolv}] || + [cc-check-function-in-lib __ns_name_uncompress {bind resolv}]) && + ([cc-check-function-in-lib ns_parserr {bind resolv}] || + [cc-check-function-in-lib __ns_parserr {bind resolv}]) && + ([cc-check-function-in-lib res_query {bind resolv}] || + [cc-check-function-in-lib __res_query {bind resolv}]))} { + msg-result "WARNING: SMTP feature will not be able to look up local MX." +} +cc-check-function-in-lib res_9_ns_initparse resolv + +# Other nonstandard function checks +cc-check-functions utime +cc-check-functions usleep +cc-check-functions strchrnul +cc-check-functions pledge +cc-check-functions backtrace + +# Termux on Android adds "getpass(char *)" to unistd.h, so check this so we +# guard against including it again; use cctest as cc-check-functions and +# cctest_function check for "getpass()" with no args and fail +if {[cctest -link 1 -includes {unistd.h} -code "getpass(0);"]} { + define FOSSIL_HAVE_GETPASS 1 + msg-result "Found getpass() with unistd.h" +} + +# Check for getloadavg(), and if it doesn't exist, define FOSSIL_OMIT_LOAD_AVERAGE +if {![cc-check-functions getloadavg]} { + define FOSSIL_OMIT_LOAD_AVERAGE 1 + msg-result "Load average support unavailable" +} + +# Check for getpassphrase() for Solaris 10 where getpass() truncates to 10 chars +if {![cc-check-functions getpassphrase]} { + # Haiku needs this + cc-check-function-in-lib getpass bsd +} +cc-check-function-in-lib sin m + +# Check for the FuseFS library +if {[opt-bool fusefs]} { + if {[opt-bool static]} { + msg-result "FuseFS support disabled due to -static" + } elseif {[cc-check-function-in-lib fuse_mount fuse]} { + define-append EXTRA_CFLAGS -DFOSSIL_HAVE_FUSEFS + define FOSSIL_HAVE_FUSEFS 1 + define-append LIBS -lfuse + msg-result "FuseFS support enabled" + } +} + +# Add -fsanitize compile and link options late: we don't want the C +# checks above to run with those sanitizers enabled. It can not only +# be pointless, it can actually break correct tests. +set fsan [opt-val with-sanitizer] +if {[string length $fsan]} { + define-append EXTRA_CFLAGS -fsanitize=$fsan + define-append EXTRA_LDFLAGS -fsanitize=$fsan + if {[string first "undefined" $fsan] != -1} { + # We need to link with libubsan if we're compiling under + # GCC with -fsanitize=undefined. + cc-check-function-in-lib __ubsan_handle_add_overflow ubsan + } +} + +# Finally, append libraries that must be last. This matters more on some +# OSes than others, but is most broadly required for static linking. +if {[check-function-in-lib dlopen dl]} { + # Some platforms (*BSD) have the dl functions already in libc and no libdl. + # In such case we can link directly without -ldl. + define-append LIBS [get-define lib_dlopen] +} +if {[opt-bool static]} { + # Linux can only infer the dependency on pthread from OpenSSL when + # doing dynamic linkage. + define-append LIBS -lpthread +} + + +make-template Makefile.in +make-config-header autoconfig.h -auto {USE_* FOSSIL_*} ADDED autosetup/LICENSE Index: autosetup/LICENSE ================================================================== --- /dev/null +++ autosetup/LICENSE @@ -0,0 +1,35 @@ +Unless explicitly stated, all files which form part of autosetup +are released under the following license: + +--------------------------------------------------------------------- +autosetup - A build environment "autoconfigurator" + +Copyright (c) 2010-2011, WorkWare Systems + +Redistribution and use in source and binary forms, with or without +modification, are permitted provided that the following conditions +are met: + +1. Redistributions of source code must retain the above copyright + notice, this list of conditions and the following disclaimer. +2. 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 WORKWARE SYSTEMS ``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 WORKWARE +SYSTEMS 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. + +The views and conclusions contained in the software and documentation +are those of the authors and should not be interpreted as representing +official policies, either expressed or implied, of WorkWare Systems. ADDED autosetup/README.autosetup Index: autosetup/README.autosetup ================================================================== --- /dev/null +++ autosetup/README.autosetup @@ -0,0 +1,11 @@ +README.autosetup created by autosetup v0.6.9 + +This is the autosetup directory for a local install of autosetup. +It contains autosetup, support files and loadable modules. + +*.tcl files in this directory are optional modules which +can be loaded with the 'use' directive. + +*.auto files in this directory are auto-loaded. + +For more information, see http://msteveb.github.com/autosetup/ ADDED autosetup/autosetup Index: autosetup/autosetup ================================================================== --- /dev/null +++ autosetup/autosetup @@ -0,0 +1,2459 @@ +#!/bin/sh +# Copyright (c) 2006-2011 WorkWare Systems http://www.workware.net.au/ +# All rights reserved +# vim:se syntax=tcl: +# \ +dir=`dirname "$0"`; exec "`$dir/autosetup-find-tclsh`" "$0" "$@" + +# Note that the version has a trailing + on unreleased versions +set autosetup(version) 0.6.9 + +# Can be set to 1 to debug early-init problems +set autosetup(debug) [expr {"--debug" in $argv}] + +################################################################## +# +# Main flow of control, option handling +# +proc main {argv} { + global autosetup define + + # There are 3 potential directories involved: + # 1. The directory containing autosetup (this script) + # 2. The directory containing auto.def + # 3. The current directory + + # From this we need to determine: + # a. The path to this script (and related support files) + # b. The path to auto.def + # c. The build directory, where output files are created + + # This is also complicated by the fact that autosetup may + # have been run via the configure wrapper ([getenv WRAPPER] is set) + + # Here are the rules. + # a. This script is $::argv0 + # => dir, prog, exe, libdir + # b. auto.def is in the directory containing the configure wrapper, + # otherwise it is in the current directory. + # => srcdir, autodef + # c. The build directory is the current directory + # => builddir, [pwd] + + # 'misc' is needed before we can do anything, so set a temporary libdir + # in case this is the development version + set autosetup(libdir) [file dirname $::argv0]/lib + use misc + + # (a) + set autosetup(dir) [realdir [file dirname [realpath $::argv0]]] + set autosetup(prog) [file join $autosetup(dir) [file tail $::argv0]] + set autosetup(exe) [getenv WRAPPER $autosetup(prog)] + if {$autosetup(installed)} { + set autosetup(libdir) $autosetup(dir) + } else { + set autosetup(libdir) [file join $autosetup(dir) lib] + } + autosetup_add_dep $autosetup(prog) + + # (b) + if {[getenv WRAPPER ""] eq ""} { + # Invoked directly + set autosetup(srcdir) [pwd] + } else { + # Invoked via the configure wrapper + set autosetup(srcdir) [file-normalize [file dirname $autosetup(exe)]] + } + set autosetup(autodef) [relative-path $autosetup(srcdir)/auto.def] + + # (c) + set autosetup(builddir) [pwd] + + set autosetup(argv) $argv + set autosetup(cmdline) {} + # options is a list of known options + set autosetup(options) {} + # optset is a dictionary of option values set by the user based on getopt + set autosetup(optset) {} + # optdefault is a dictionary of default values + set autosetup(optdefault) {} + # options-defaults is a dictionary of overrides for default values for options + set autosetup(options-defaults) {} + set autosetup(optionhelp) {} + set autosetup(showhelp) 0 + + use util + + # Parse options + use getopt + + # At the is point we don't know what is a valid option + # We simply parse anything that looks like an option + set autosetup(getopt) [getopt argv] + + #"=Core Options:" + options-add { + help:=local => "display help and options. Optionally specify a module name, such as --help=system" + licence license => "display the autosetup license" + version => "display the version of autosetup" + ref:=text manual:=text + reference:=text => "display the autosetup command reference. 'text', 'wiki', 'asciidoc' or 'markdown'" + debug => "display debugging output as autosetup runs" + install:=. => "install autosetup to the current or given directory" + } + if {$autosetup(installed)} { + # hidden options so we can produce a nice error + options-add { + sysinstall:path + } + } else { + options-add { + sysinstall:path => "install standalone autosetup to the given directory (e.g.: /usr/local)" + } + } + options-add { + force init:=help => "create initial auto.def, etc. Use --init=help for known types" + # Undocumented options + option-checking=1 + nopager + quiet + timing + conf: + } + + if {[opt-bool version]} { + puts $autosetup(version) + exit 0 + } + + # autosetup --conf=alternate-auto.def + if {[opt-str conf o]} { + set autosetup(autodef) $o + } + + # Debugging output (set this early) + incr autosetup(debug) [opt-bool debug] + incr autosetup(force) [opt-bool force] + incr autosetup(msg-quiet) [opt-bool quiet] + incr autosetup(msg-timing) [opt-bool timing] + + # If the local module exists, source it now to allow for + # project-local customisations + if {[file exists $autosetup(libdir)/local.tcl]} { + use local + } + + # Now any auto-load modules + autosetup_load_auto_modules + + if {[opt-str help o]} { + incr autosetup(showhelp) + use help + autosetup_help $o + } + + if {[opt-bool licence license]} { + use help + autosetup_show_license + exit 0 + } + + if {[opt-str {manual ref reference} o]} { + use help + autosetup_reference $o + } + + # Allow combining --install and --init + set earlyexit 0 + if {[opt-str install o]} { + use install + autosetup_install $o + incr earlyexit + } + + if {[opt-str init o]} { + use init + autosetup_init $o + incr earlyexit + } + + if {$earlyexit} { + exit 0 + } + if {[opt-str sysinstall o]} { + use install + autosetup_install $o 1 + exit 0 + } + + if {![file exists $autosetup(autodef)]} { + # Check for invalid option first + options {} + user-error "No auto.def found in \"$autosetup(srcdir)\" (use [file tail $::autosetup(exe)] --init to create one)" + } + + # Parse extra arguments into autosetup(cmdline) + foreach arg $argv { + if {[regexp {([^=]*)=(.*)} $arg -> n v]} { + dict set autosetup(cmdline) $n $v + define $n $v + } else { + user-error "Unexpected parameter: $arg" + } + } + + autosetup_add_dep $autosetup(autodef) + + define CONFIGURE_OPTS "" + foreach arg $autosetup(argv) { + define-append CONFIGURE_OPTS [quote-if-needed $arg] + } + define AUTOREMAKE [file-normalize $autosetup(exe)] + define-append AUTOREMAKE [get-define CONFIGURE_OPTS] + + + # Log how we were invoked + configlog "Invoked as: [getenv WRAPPER $::argv0] [quote-argv $autosetup(argv)]" + configlog "Tclsh: [info nameofexecutable]" + + # Note that auto.def is *not* loaded in the global scope + source $autosetup(autodef) + + # Could warn here if options {} was not specified + + show-notices + + if {$autosetup(debug)} { + msg-result "Writing all defines to config.log" + configlog "================ defines ======================" + foreach n [lsort [array names define]] { + configlog "define $n $define($n)" + } + } + + exit 0 +} + +# @opt-bool ?-nodefault? option ... +# +# Check each of the named, boolean options and if any have been explicitly enabled +# or disabled by the user, return 1 or 0 accordingly. +# +# If the option was specified more than once, the last value wins. +# e.g. With '--enable-foo --disable-foo', '[opt-bool foo]' will return 0 +# +# If no value was specified by the user, returns the default value for the +# first option. If '-nodefault' is given, this behaviour changes and +# -1 is returned instead. +# +proc opt-bool {args} { + set nodefault 0 + if {[lindex $args 0] eq "-nodefault"} { + set nodefault 1 + set args [lrange $args 1 end] + } + option-check-names {*}$args + + foreach opt $args { + if {[dict exists $::autosetup(optset) $opt]} { + return [dict get $::autosetup(optset) $opt] + } + } + + if {$nodefault} { + return -1 + } + # Default value is the default for the first option + return [dict get $::autosetup(optdefault) [lindex $args 0]] +} + +# @opt-val optionlist ?default=""? +# +# Returns a list containing all the values given for the non-boolean options in '$optionlist'. +# There will be one entry in the list for each option given by the user, including if the +# same option was used multiple times. +# +# If no options were set, '$default' is returned (exactly, not as a list). +# +# Note: For most use cases, 'opt-str' should be preferred. +# +proc opt-val {names {default ""}} { + option-check-names {*}$names + + foreach opt $names { + if {[dict exists $::autosetup(optset) $opt]} { + lappend result {*}[dict get $::autosetup(optset) $opt] + } + } + if {[info exists result]} { + return $result + } + return $default +} + +# @opt-str optionlist varname ?default? +# +# Sets '$varname' in the callers scope to the value for one of the given options. +# +# For the list of options given in '$optionlist', if any value is set for any option, +# the option value is taken to be the *last* value of the last option (in the order given). +# +# If no option was given, and a default was specified with 'options-defaults', +# that value is used. +# +# If no 'options-defaults' value was given and '$default' was given, it is used. +# +# If none of the above provided a value, no value is set. +# +# The return value depends on whether '$default' was specified. +# If it was, the option value is returned. +# If it was not, 1 is returns if a value was set, or 0 if not. +# +# Typical usage is as follows: +# +## if {[opt-str {myopt altname} o]} { +## do something with $o +## } +# +# Or: +## define myname [opt-str {myopt altname} o "/usr/local"] +# +proc opt-str {names varname args} { + global autosetup + + option-check-names {*}$names + upvar $varname value + + if {[llength $args]} { + # A default was given, so always return the string value of the option + set default [lindex $args 0] + set retopt 1 + } else { + # No default, so return 0 or 1 to indicate if a value was found + set retopt 0 + } + + foreach opt $names { + if {[dict exists $::autosetup(optset) $opt]} { + set result [lindex [dict get $::autosetup(optset) $opt] end] + } + } + + if {![info exists result]} { + # No user-specified value. Has options-defaults been set? + foreach opt $names { + if {[dict exists $::autosetup(options-defaults) $opt]} { + set result [dict get $autosetup(options-defaults) $opt] + } + } + } + + if {[info exists result]} { + set value $result + if {$retopt} { + return $value + } + return 1 + } + + if {$retopt} { + set value $default + return $value + } + + return 0 +} + +proc option-check-names {args} { + foreach o $args { + if {$o ni $::autosetup(options)} { + autosetup-error "Request for undeclared option --$o" + } + } +} + +# Parse the option definition in $opts and update +# ::autosetup(setoptions) and ::autosetup(optionhelp) appropriately +# +proc options-add {opts {header ""}} { + global autosetup + + # First weed out comment lines + set realopts {} + foreach line [split $opts \n] { + if {![string match "#*" [string trimleft $line]]} { + append realopts $line \n + } + } + set opts $realopts + + for {set i 0} {$i < [llength $opts]} {incr i} { + set opt [lindex $opts $i] + if {[string match =* $opt]} { + # This is a special heading + lappend autosetup(optionhelp) $opt "" + set header {} + continue + } + unset -nocomplain defaultvalue equal value + + #puts "i=$i, opt=$opt" + regexp {^([^:=]*)(:)?(=)?(.*)$} $opt -> name colon equal value + if {$name in $autosetup(options)} { + autosetup-error "Option $name already specified" + } + + #puts "$opt => $name $colon $equal $value" + + # Find the corresponding value in the user options + # and set the default if necessary + if {[string match "-*" $opt]} { + # This is a documentation-only option, like "-C " + set opthelp $opt + } elseif {$colon eq ""} { + # Boolean option + lappend autosetup(options) $name + + # Check for override + if {[dict exists $autosetup(options-defaults) $name]} { + # A default was specified with options-defaults, so use it + set value [dict get $autosetup(options-defaults) $name] + } + + if {$value eq "1"} { + set opthelp "--disable-$name" + } else { + set opthelp "--$name" + } + + # Set the default + if {$value eq ""} { + set value 0 + } + set defaultvalue $value + dict set autosetup(optdefault) $name $defaultvalue + + if {[dict exists $autosetup(getopt) $name]} { + # The option was specified by the user. Look at the last value. + lassign [lindex [dict get $autosetup(getopt) $name] end] type setvalue + if {$type eq "str"} { + # Can we convert the value to a boolean? + if {$setvalue in {1 enabled yes}} { + set setvalue 1 + } elseif {$setvalue in {0 disabled no}} { + set setvalue 0 + } else { + user-error "Boolean option $name given as --$name=$setvalue" + } + } + dict set autosetup(optset) $name $setvalue + #puts "Found boolean option --$name=$setvalue" + } + } else { + # String option. + lappend autosetup(options) $name + + if {$colon eq ":"} { + # Was ":name=default" given? + # If so, set $value to the display name and $defaultvalue to the default + # (This is the preferred way to set a default value for a string option) + if {[regexp {^([^=]+)=(.*)$} $value -> value defaultvalue]} { + dict set autosetup(optdefault) $name $defaultvalue + } + } + + # Maybe override the default value + if {[dict exists $autosetup(options-defaults) $name]} { + # A default was specified with options-defaults, so use it + set defaultvalue [dict get $autosetup(options-defaults) $name] + dict set autosetup(optdefault) $name $defaultvalue + } elseif {![info exists defaultvalue]} { + # For backward compatibility, if ":name" was given, use name as both + # the display text and the default value, but only if the user + # specified the option without the value + set defaultvalue $value + } + + if {$equal eq "="} { + # String option with optional value + set opthelp "--$name?=$value?" + } else { + # String option with required value + set opthelp "--$name=$value" + } + + # Get the values specified by the user + if {[dict exists $autosetup(getopt) $name]} { + set listvalue {} + + foreach pair [dict get $autosetup(getopt) $name] { + lassign $pair type setvalue + if {$type eq "bool" && $setvalue} { + if {$equal ne "="} { + user-error "Option --$name requires a value" + } + # If given as a boolean, use the default value + set setvalue $defaultvalue + } + lappend listvalue $setvalue + } + + #puts "Found string option --$name=$listvalue" + dict set autosetup(optset) $name $listvalue + } + } + + # Now create the help for this option if appropriate + if {[lindex $opts $i+1] eq "=>"} { + set desc [lindex $opts $i+2] + if {[info exists defaultvalue]} { + set desc [string map [list @default@ $defaultvalue] $desc] + } + #string match \n* $desc + if {$header ne ""} { + lappend autosetup(optionhelp) $header "" + set header "" + } + # A multi-line description + lappend autosetup(optionhelp) $opthelp $desc + incr i 2 + } + } +} + +# @module-options optionlist +# +# Like 'options', but used within a module. +proc module-options {opts} { + set header "" + if {$::autosetup(showhelp) > 1 && [llength $opts]} { + set header "Module Options:" + } + options-add $opts $header + + if {$::autosetup(showhelp)} { + # Ensure that the module isn't executed on --help + # We are running under eval or source, so use break + # to prevent further execution + #return -code break -level 2 + return -code break + } +} + +proc max {a b} { + expr {$a > $b ? $a : $b} +} + +proc options-wrap-desc {text length firstprefix nextprefix initial} { + set len $initial + set space $firstprefix + foreach word [split $text] { + set word [string trim $word] + if {$word == ""} { + continue + } + if {$len && [string length $space$word] + $len >= $length} { + puts "" + set len 0 + set space $nextprefix + } + incr len [string length $space$word] + puts -nonewline $space$word + set space " " + } + if {$len} { + puts "" + } +} + +proc options-show {} { + # Determine the max option width + set max 0 + foreach {opt desc} $::autosetup(optionhelp) { + if {[string match =* $opt] || [string match \n* $desc]} { + continue + } + set max [max $max [string length $opt]] + } + set indent [string repeat " " [expr $max+4]] + set cols [getenv COLUMNS 80] + catch { + lassign [exec stty size] rows cols + } + incr cols -1 + # Now output + foreach {opt desc} $::autosetup(optionhelp) { + if {[string match =* $opt]} { + puts [string range $opt 1 end] + continue + } + puts -nonewline " [format %-${max}s $opt]" + if {[string match \n* $desc]} { + puts $desc + } else { + options-wrap-desc [string trim $desc] $cols " " $indent [expr $max + 2] + } + } +} + +# @options optionspec +# +# Specifies configuration-time options which may be selected by the user +# and checked with 'opt-str' and 'opt-bool'. '$optionspec' contains a series +# of options specifications separated by newlines, as follows: +# +# A boolean option is of the form: +# +## name[=0|1] => "Description of this boolean option" +# +# The default is 'name=0', meaning that the option is disabled by default. +# If 'name=1' is used to make the option enabled by default, the description should reflect +# that with text like "Disable support for ...". +# +# An argument option (one which takes a parameter) is of the form: +# +## name:[=]value => "Description of this option" +# +# If the 'name:value' form is used, the value must be provided with the option (as '--name=myvalue'). +# If the 'name:=value' form is used, the value is optional and the given value is used as the default +# if it is not provided. +# +# The description may contain '@default@', in which case it will be replaced with the default +# value for the option (taking into account defaults specified with 'options-defaults'. +# +# Undocumented options are also supported by omitting the '=> description'. +# These options are not displayed with '--help' and can be useful for internal options or as aliases. +# +# For example, '--disable-lfs' is an alias for '--disable=largefile': +# +## lfs=1 largefile=1 => "Disable large file support" +# +proc options {optlist} { + # Allow options as a list or args + options-add $optlist "Local Options:" + + if {$::autosetup(showhelp)} { + options-show + exit 0 + } + + # Check for invalid options + if {[opt-bool option-checking]} { + foreach o [dict keys $::autosetup(getopt)] { + if {$o ni $::autosetup(options)} { + user-error "Unknown option --$o" + } + } + } +} + +# @options-defaults dictionary +# +# Specifies a dictionary of options and a new default value for each of those options. +# Use before any 'use' statements in 'auto.def' to change the defaults for +# subsequently included modules. +proc options-defaults {dict} { + foreach {n v} $dict { + dict set ::autosetup(options-defaults) $n $v + } +} + +proc config_guess {} { + if {[file-isexec $::autosetup(dir)/autosetup-config.guess]} { + if {[catch {exec-with-stderr sh $::autosetup(dir)/autosetup-config.guess} alias]} { + user-error $alias + } + return $alias + } else { + configlog "No autosetup-config.guess, so using uname" + string tolower [exec uname -p]-unknown-[exec uname -s][exec uname -r] + } +} + +proc config_sub {alias} { + if {[file-isexec $::autosetup(dir)/autosetup-config.sub]} { + if {[catch {exec-with-stderr sh $::autosetup(dir)/autosetup-config.sub $alias} alias]} { + user-error $alias + } + } + return $alias +} + +# @define name ?value=1? +# +# Defines the named variable to the given value. +# These (name, value) pairs represent the results of the configuration check +# and are available to be subsequently checked, modified and substituted. +# +proc define {name {value 1}} { + set ::define($name) $value + #dputs "$name <= $value" +} + +# @undefine name +# +# Undefine the named variable. +# +proc undefine {name} { + unset -nocomplain ::define($name) + #dputs "$name <= " +} + +# @define-append name value ... +# +# Appends the given value(s) to the given "defined" variable. +# If the variable is not defined or empty, it is set to '$value'. +# Otherwise the value is appended, separated by a space. +# Any extra values are similarly appended. +# If any value is already contained in the variable (as a substring) it is omitted. +# +proc define-append {name args} { + if {[get-define $name ""] ne ""} { + # Avoid duplicates + foreach arg $args { + if {$arg eq ""} { + continue + } + set found 0 + foreach str [split $::define($name) " "] { + if {$str eq $arg} { + incr found + } + } + if {!$found} { + append ::define($name) " " $arg + } + } + } else { + set ::define($name) [join $args] + } + #dputs "$name += [join $args] => $::define($name)" +} + +# @get-define name ?default=0? +# +# Returns the current value of the "defined" variable, or '$default' +# if not set. +# +proc get-define {name {default 0}} { + if {[info exists ::define($name)]} { + #dputs "$name => $::define($name)" + return $::define($name) + } + #dputs "$name => $default" + return $default +} + +# @is-defined name +# +# Returns 1 if the given variable is defined. +# +proc is-defined {name} { + info exists ::define($name) +} + +# @is-define-set name +# +# Returns 1 if the given variable is defined and is set +# to a value other than "" or 0 +# +proc is-define-set {name} { + if {[get-define $name] in {0 ""}} { + return 0 + } + return 1 +} + +# @all-defines +# +# Returns a dictionary (name, value list) of all defined variables. +# +# This is suitable for use with 'dict', 'array set' or 'foreach' +# and allows for arbitrary processing of the defined variables. +# +proc all-defines {} { + array get ::define +} + + +# @get-env name default +# +# If '$name' was specified on the command line, return it. +# Otherwise if '$name' was set in the environment, return it. +# Otherwise return '$default'. +# +proc get-env {name default} { + if {[dict exists $::autosetup(cmdline) $name]} { + return [dict get $::autosetup(cmdline) $name] + } + getenv $name $default +} + +# @env-is-set name +# +# Returns 1 if '$name' was specified on the command line or in the environment. +# Note that an empty environment variable is not considered to be set. +# +proc env-is-set {name} { + if {[dict exists $::autosetup(cmdline) $name]} { + return 1 + } + if {[getenv $name ""] ne ""} { + return 1 + } + return 0 +} + +# @readfile filename ?default=""? +# +# Return the contents of the file, without the trailing newline. +# If the file doesn't exist or can't be read, returns '$default'. +# +proc readfile {filename {default_value ""}} { + set result $default_value + catch { + set f [open $filename] + set result [read -nonewline $f] + close $f + } + return $result +} + +# @writefile filename value +# +# Creates the given file containing '$value'. +# Does not add an extra newline. +# +proc writefile {filename value} { + set f [open $filename w] + puts -nonewline $f $value + close $f +} + +proc quote-if-needed {str} { + if {[string match {*[\" ]*} $str]} { + return \"[string map [list \" \\" \\ \\\\] $str]\" + } + return $str +} + +proc quote-argv {argv} { + set args {} + foreach arg $argv { + lappend args [quote-if-needed $arg] + } + join $args +} + +# @list-non-empty list +# +# Returns a copy of the given list with empty elements removed +proc list-non-empty {list} { + set result {} + foreach p $list { + if {$p ne ""} { + lappend result $p + } + } + return $result +} + +# @find-executable-path name +# +# Searches the path for an executable with the given name. +# Note that the name may include some parameters, e.g. 'cc -mbig-endian', +# in which case the parameters are ignored. +# The full path to the executable if found, or "" if not found. +# Returns 1 if found, or 0 if not. +# +proc find-executable-path {name} { + # Ignore any parameters + set name [lindex $name 0] + # The empty string is never a valid executable + if {$name ne ""} { + foreach p [split-path] { + dputs "Looking for $name in $p" + set exec [file join $p $name] + if {[file-isexec $exec]} { + dputs "Found $name -> $exec" + return $exec + } + } + } + return {} +} + +# @find-executable name +# +# Searches the path for an executable with the given name. +# Note that the name may include some parameters, e.g. 'cc -mbig-endian', +# in which case the parameters are ignored. +# Returns 1 if found, or 0 if not. +# +proc find-executable {name} { + if {[find-executable-path $name] eq {}} { + return 0 + } + return 1 +} + +# @find-an-executable ?-required? name ... +# +# Given a list of possible executable names, +# searches for one of these on the path. +# +# Returns the name found, or "" if none found. +# If the first parameter is '-required', an error is generated +# if no executable is found. +# +proc find-an-executable {args} { + set required 0 + if {[lindex $args 0] eq "-required"} { + set args [lrange $args 1 end] + incr required + } + foreach name $args { + if {[find-executable $name]} { + return $name + } + } + if {$required} { + if {[llength $args] == 1} { + user-error "failed to find: [join $args]" + } else { + user-error "failed to find one of: [join $args]" + } + } + return "" +} + +# @configlog msg +# +# Writes the given message to the configuration log, 'config.log'. +# +proc configlog {msg} { + if {![info exists ::autosetup(logfh)]} { + set ::autosetup(logfh) [open config.log w] + } + puts $::autosetup(logfh) $msg +} + +# @msg-checking msg +# +# Writes the message with no newline to stdout. +# +proc msg-checking {msg} { + if {$::autosetup(msg-quiet) == 0} { + maybe-show-timestamp + puts -nonewline $msg + set ::autosetup(msg-checking) 1 + } +} + +# @msg-result msg +# +# Writes the message to stdout. +# +proc msg-result {msg} { + if {$::autosetup(msg-quiet) == 0} { + maybe-show-timestamp + puts $msg + set ::autosetup(msg-checking) 0 + show-notices + } +} + +# @msg-quiet command ... +# +# 'msg-quiet' evaluates it's arguments as a command with output +# from 'msg-checking' and 'msg-result' suppressed. +# +# This is useful if a check needs to run a subcheck which isn't +# of interest to the user. +proc msg-quiet {args} { + incr ::autosetup(msg-quiet) + set rc [uplevel 1 $args] + incr ::autosetup(msg-quiet) -1 + return $rc +} + +# Will be overridden by 'use misc' +proc error-stacktrace {msg} { + return $msg +} + +proc error-location {msg} { + return $msg +} + +################################################################## +# +# Debugging output +# +proc dputs {msg} { + if {$::autosetup(debug)} { + puts $msg + } +} + +################################################################## +# +# User and system warnings and errors +# +# Usage errors such as wrong command line options + +# @user-error msg +# +# Indicate incorrect usage to the user, including if required components +# or features are not found. +# 'autosetup' exits with a non-zero return code. +# +proc user-error {msg} { + show-notices + puts stderr "Error: $msg" + puts stderr "Try: '[file tail $::autosetup(exe)] --help' for options" + exit 1 +} + +# @user-notice msg +# +# Output the given message to stderr. +# +proc user-notice {msg} { + lappend ::autosetup(notices) $msg +} + +# Incorrect usage in the auto.def file. Identify the location. +proc autosetup-error {msg} { + autosetup-full-error [error-location $msg] +} + +# Like autosetup-error, except $msg is the full error message. +proc autosetup-full-error {msg} { + show-notices + puts stderr $msg + exit 1 +} + +proc show-notices {} { + if {$::autosetup(msg-checking)} { + puts "" + set ::autosetup(msg-checking) 0 + } + flush stdout + if {[info exists ::autosetup(notices)]} { + puts stderr [join $::autosetup(notices) \n] + unset ::autosetup(notices) + } +} + +proc maybe-show-timestamp {} { + if {$::autosetup(msg-timing) && $::autosetup(msg-checking) == 0} { + puts -nonewline [format {[%6.2f] } [expr {([clock millis] - $::autosetup(start)) % 10000 / 1000.0}]] + } +} + +# @autosetup-require-version required +# +# Checks the current version of 'autosetup' against '$required'. +# A fatal error is generated if the current version is less than that required. +# +proc autosetup-require-version {required} { + if {[compare-versions $::autosetup(version) $required] < 0} { + user-error "autosetup version $required is required, but this is $::autosetup(version)" + } +} + +proc autosetup_version {} { + return "autosetup v$::autosetup(version)" +} + +################################################################## +# +# Directory/path handling +# + +proc realdir {dir} { + set oldpwd [pwd] + cd $dir + set pwd [pwd] + cd $oldpwd + return $pwd +} + +# Follow symlinks until we get to something which is not a symlink +proc realpath {path} { + while {1} { + if {[catch { + set path [file readlink $path] + }]} { + # Not a link + break + } + } + return $path +} + +# Convert absolute path, $path into a path relative +# to the given directory (or the current dir, if not given). +# +proc relative-path {path {pwd {}}} { + set diff 0 + set same 0 + set newf {} + set prefix {} + set path [file-normalize $path] + if {$pwd eq ""} { + set pwd [pwd] + } else { + set pwd [file-normalize $pwd] + } + + if {$path eq $pwd} { + return . + } + + # Try to make the filename relative to the current dir + foreach p [split $pwd /] f [split $path /] { + if {$p ne $f} { + incr diff + } elseif {!$diff} { + incr same + } + if {$diff} { + if {$p ne ""} { + # Add .. for sibling or parent dir + lappend prefix .. + } + if {$f ne ""} { + lappend newf $f + } + } + } + if {$same == 1 || [llength $prefix] > 3} { + return $path + } + + file join [join $prefix /] [join $newf /] +} + +# Add filename as a dependency to rerun autosetup +# The name will be normalised (converted to a full path) +# +proc autosetup_add_dep {filename} { + lappend ::autosetup(deps) [file-normalize $filename] +} + +################################################################## +# +# Library module support +# + +# @use module ... +# +# Load the given library modules. +# e.g. 'use cc cc-shared' +# +# Note that module 'X' is implemented in either 'autosetup/X.tcl' +# or 'autosetup/X/init.tcl' +# +# The latter form is useful for a complex module which requires additional +# support file. In this form, '$::usedir' is set to the module directory +# when it is loaded. +# +proc use {args} { + global autosetup libmodule modsource + + set dirs [list $autosetup(libdir)] + if {[info exists autosetup(srcdir)]} { + lappend dirs $autosetup(srcdir)/autosetup + } + foreach m $args { + if {[info exists libmodule($m)]} { + continue + } + set libmodule($m) 1 + if {[info exists modsource(${m}.tcl)]} { + automf_load eval $modsource(${m}.tcl) + } else { + set locs [list ${m}.tcl ${m}/init.tcl] + set found 0 + foreach dir $dirs { + foreach loc $locs { + set source $dir/$loc + if {[file exists $source]} { + incr found + break + } + } + if {$found} { + break + } + } + if {$found} { + # For the convenience of the "use" source, point to the directory + # it is being loaded from + set ::usedir [file dirname $source] + automf_load source $source + autosetup_add_dep $source + } else { + autosetup-error "use: No such module: $m" + } + } + } +} + +proc autosetup_load_auto_modules {} { + global autosetup modsource + # First load any embedded auto modules + foreach mod [array names modsource *.auto] { + automf_load eval $modsource($mod) + } + # Now any external auto modules + foreach file [glob -nocomplain $autosetup(libdir)/*.auto $autosetup(libdir)/*/*.auto] { + automf_load source $file + } +} + +# Load module source in the global scope by executing the given command +proc automf_load {args} { + if {[catch [list uplevel #0 $args] msg opts] ni {0 2 3}} { + autosetup-full-error [error-dump $msg $opts $::autosetup(debug)] + } +} + +# Initial settings +set autosetup(exe) $::argv0 +set autosetup(istcl) 1 +set autosetup(start) [clock millis] +set autosetup(installed) 0 +set autosetup(sysinstall) 0 +set autosetup(msg-checking) 0 +set autosetup(msg-quiet) 0 +set autosetup(inittypes) {} + +# Embedded modules are inserted below here +set autosetup(installed) 1 +set autosetup(sysinstall) 0 +# ----- @module asciidoc-formatting.tcl ----- + +set modsource(asciidoc-formatting.tcl) { +# Copyright (c) 2010 WorkWare Systems http://www.workware.net.au/ +# All rights reserved + +# Module which provides text formatting +# asciidoc format + +use formatting + +proc para {text} { + regsub -all "\[ \t\n\]+" [string trim $text] " " +} +proc title {text} { + underline [para $text] = + nl +} +proc p {text} { + puts [para $text] + nl +} +proc code {text} { + foreach line [parse_code_block $text] { + puts " $line" + } + nl +} +proc codelines {lines} { + foreach line $lines { + puts " $line" + } + nl +} +proc nl {} { + puts "" +} +proc underline {text char} { + regexp "^(\[ \t\]*)(.*)" $text -> indent words + puts $text + puts $indent[string repeat $char [string length $words]] +} +proc section {text} { + underline "[para $text]" - + nl +} +proc subsection {text} { + underline "$text" ~ + nl +} +proc bullet {text} { + puts "* [para $text]" +} +proc indent {text} { + puts " :: " + puts [para $text] +} +proc defn {first args} { + set sep "" + if {$first ne ""} { + puts "${first}::" + } else { + puts " :: " + } + set defn [string trim [join $args \n]] + regsub -all "\n\n" $defn "\n ::\n" defn + puts $defn +} +} + +# ----- @module formatting.tcl ----- + +set modsource(formatting.tcl) { +# Copyright (c) 2010 WorkWare Systems http://www.workware.net.au/ +# All rights reserved + +# Module which provides common text formatting + +# This is designed for documentation which looks like: +# code {...} +# or +# code { +# ... +# ... +# } +# In the second case, we need to work out the indenting +# and strip it from all lines but preserve the remaining indenting. +# Note that all lines need to be indented with the same initial +# spaces/tabs. +# +# Returns a list of lines with the indenting removed. +# +proc parse_code_block {text} { + # If the text begins with newline, take the following text, + # otherwise just return the original + if {![regexp "^\n(.*)" $text -> text]} { + return [list [string trim $text]] + } + + # And trip spaces off the end + set text [string trimright $text] + + set min 100 + # Examine each line to determine the minimum indent + foreach line [split $text \n] { + if {$line eq ""} { + # Ignore empty lines for the indent calculation + continue + } + regexp "^(\[ \t\]*)" $line -> indent + set len [string length $indent] + if {$len < $min} { + set min $len + } + } + + # Now make a list of lines with this indent removed + set lines {} + foreach line [split $text \n] { + lappend lines [string range $line $min end] + } + + # Return the result + return $lines +} +} + +# ----- @module getopt.tcl ----- + +set modsource(getopt.tcl) { +# Copyright (c) 2006 WorkWare Systems http://www.workware.net.au/ +# All rights reserved + +# Simple getopt module + +# Parse everything out of the argv list which looks like an option +# Everything which doesn't look like an option, or is after --, is left unchanged +# Understands --enable-xxx as a synonym for --xxx to enable the boolean option xxx. +# Understands --disable-xxx to disable the boolean option xxx. +# +# The returned value is a dictionary keyed by option name +# Each value is a list of {type value} ... where type is "bool" or "str". +# The value for a boolean option is 0 or 1. The value of a string option is the value given. +proc getopt {argvname} { + upvar $argvname argv + set nargv {} + + set opts {} + + for {set i 0} {$i < [llength $argv]} {incr i} { + set arg [lindex $argv $i] + + #dputs arg=$arg + + if {$arg eq "--"} { + # End of options + incr i + lappend nargv {*}[lrange $argv $i end] + break + } + + if {[regexp {^--([^=][^=]+)=(.*)$} $arg -> name value]} { + # --name=value + dict lappend opts $name [list str $value] + } elseif {[regexp {^--(enable-|disable-)?([^=]*)$} $arg -> prefix name]} { + if {$prefix in {enable- ""}} { + set value 1 + } else { + set value 0 + } + dict lappend opts $name [list bool $value] + } else { + lappend nargv $arg + } + } + + #puts "getopt: argv=[join $argv] => [join $nargv]" + #array set getopt $opts + #parray getopt + + set argv $nargv + + return $opts +} +} + +# ----- @module help.tcl ----- + +set modsource(help.tcl) { +# Copyright (c) 2010 WorkWare Systems http://workware.net.au/ +# All rights reserved + +# Module which provides usage, help and the command reference + +proc autosetup_help {what} { + use_pager + + puts "Usage: [file tail $::autosetup(exe)] \[options\] \[settings\]\n" + puts "This is [autosetup_version], a build environment \"autoconfigurator\"" + puts "See the documentation online at http://msteveb.github.com/autosetup/\n" + + if {$what eq "local"} { + if {[file exists $::autosetup(autodef)]} { + # This relies on auto.def having a call to 'options' + # which will display options and quit + source $::autosetup(autodef) + } else { + options-show + } + } else { + incr ::autosetup(showhelp) + if {[catch {use $what}]} { + user-error "Unknown module: $what" + } else { + options-show + } + } + exit 0 +} + +proc autosetup_show_license {} { + global modsource autosetup + use_pager + + if {[info exists modsource(LICENSE)]} { + puts $modsource(LICENSE) + return + } + foreach dir [list $autosetup(libdir) $autosetup(srcdir)] { + set path [file join $dir LICENSE] + if {[file exists $path]} { + puts [readfile $path] + return + } + } + puts "LICENSE not found" +} + +# If not already paged and stdout is a tty, pipe the output through the pager +# This is done by reinvoking autosetup with --nopager added +proc use_pager {} { + if {![opt-bool nopager] && [getenv PAGER ""] ne "" && [isatty? stdin] && [isatty? stdout]} { + if {[catch { + exec [info nameofexecutable] $::argv0 --nopager {*}$::argv |& {*}[getenv PAGER] >@stdout <@stdin 2>@stderr + } msg opts] == 1} { + if {[dict get $opts -errorcode] eq "NONE"} { + # an internal/exec error + puts stderr $msg + exit 1 + } + } + exit 0 + } +} + +# Outputs the autosetup references in one of several formats +proc autosetup_reference {{type text}} { + + use_pager + + switch -glob -- $type { + wiki {use wiki-formatting} + ascii* {use asciidoc-formatting} + md - markdown {use markdown-formatting} + default {use text-formatting} + } + + title "[autosetup_version] -- Command Reference" + + section {Introduction} + + p { + See http://msteveb.github.com/autosetup/ for the online documentation for 'autosetup' + } + + p { + 'autosetup' provides a number of built-in commands which + are documented below. These may be used from 'auto.def' to test + for features, define variables, create files from templates and + other similar actions. + } + + automf_command_reference + + exit 0 +} + +proc autosetup_output_block {type lines} { + if {[llength $lines]} { + switch $type { + section { + section $lines + } + subsection { + subsection $lines + } + code { + codelines $lines + } + p { + p [join $lines] + } + list { + foreach line $lines { + bullet $line + } + nl + } + } + } +} + +# Generate a command reference from inline documentation +proc automf_command_reference {} { + lappend files $::autosetup(prog) + lappend files {*}[lsort [glob -nocomplain $::autosetup(libdir)/*.tcl]] + + # We want to process all non-module files before module files + # and then modules in alphabetical order. + # So examine all files and extract docs into doc($modulename) and doc(_core_) + # + # Each entry is a list of {type data} where $type is one of: section, subsection, code, list, p + # and $data is a string for section, subsection or a list of text lines for other types. + + # XXX: Should commands be in alphabetical order too? Currently they are in file order. + + set doc(_core_) {} + lappend doc(_core_) [list section "Core Commands"] + + foreach file $files { + set modulename [file rootname [file tail $file]] + set current _core_ + set f [open $file] + while {![eof $f]} { + set line [gets $f] + + # Find embedded module names + if {[regexp {^#.*@module ([^ ]*)} $line -> modulename]} { + continue + } + + # Find lines starting with "# @*" and continuing through the remaining comment lines + if {![regexp {^# @(.*)} $line -> cmd]} { + continue + } + + # Synopsis or command? + if {$cmd eq "synopsis:"} { + set current $modulename + lappend doc($current) [list section "Module: $modulename"] + } else { + lappend doc($current) [list subsection $cmd] + } + + set lines {} + set type p + + # Now the description + while {![eof $f]} { + set line [gets $f] + + if {![regexp {^#(#)? ?(.*)} $line -> hash cmd]} { + break + } + if {$hash eq "#"} { + set t code + } elseif {[regexp {^- (.*)} $cmd -> cmd]} { + set t list + } else { + set t p + } + + #puts "hash=$hash, oldhash=$oldhash, lines=[llength $lines], cmd=$cmd" + + if {$t ne $type || $cmd eq ""} { + # Finish the current block + lappend doc($current) [list $type $lines] + set lines {} + set type $t + } + if {$cmd ne ""} { + lappend lines $cmd + } + } + + lappend doc($current) [list $type $lines] + } + close $f + } + + # Now format and output the results + + # _core_ will sort first + foreach module [lsort [array names doc]] { + foreach item $doc($module) { + autosetup_output_block {*}$item + } + } +} +} + +# ----- @module init.tcl ----- + +set modsource(init.tcl) { +# Copyright (c) 2010 WorkWare Systems http://www.workware.net.au/ +# All rights reserved + +# Module to help create auto.def and configure + +proc autosetup_init {type} { + set help 0 + if {$type in {? help}} { + incr help + } elseif {![dict exists $::autosetup(inittypes) $type]} { + puts "Unknown type, --init=$type" + incr help + } + if {$help} { + puts "Use one of the following types (e.g. --init=make)\n" + foreach type [lsort [dict keys $::autosetup(inittypes)]] { + lassign [dict get $::autosetup(inittypes) $type] desc + # XXX: Use the options-show code to wrap the description + puts [format "%-10s %s" $type $desc] + } + return + } + lassign [dict get $::autosetup(inittypes) $type] desc script + + puts "Initialising $type: $desc\n" + + # All initialisations happens in the top level srcdir + cd $::autosetup(srcdir) + + uplevel #0 $script +} + +proc autosetup_add_init_type {type desc script} { + dict set ::autosetup(inittypes) $type [list $desc $script] +} + +# This is for in creating build-system init scripts +# +# If the file doesn't exist, create it containing $contents +# If the file does exist, only overwrite if --force is specified. +# +proc autosetup_check_create {filename contents} { + if {[file exists $filename]} { + if {!$::autosetup(force)} { + puts "I see $filename already exists." + return + } else { + puts "I will overwrite the existing $filename because you used --force." + } + } else { + puts "I don't see $filename, so I will create it." + } + writefile $filename $contents +} +} + +# ----- @module install.tcl ----- + +set modsource(install.tcl) { +# Copyright (c) 2006-2010 WorkWare Systems http://www.workware.net.au/ +# All rights reserved + +# Module which can install autosetup + +# autosetup(installed)=1 means that autosetup is not running from source +# autosetup(sysinstall)=1 means that autosetup is running from a sysinstall version +# shared=1 means that we are trying to do a sysinstall. This is only possible from the development source. + +proc autosetup_install {dir {shared 0}} { + global autosetup + if {$shared} { + if {$autosetup(installed) || $autosetup(sysinstall)} { + user-error "Can only --sysinstall from development sources" + } + } elseif {$autosetup(installed) && !$autosetup(sysinstall)} { + user-error "Can't --install from project install" + } + + if {$autosetup(sysinstall)} { + # This is the sysinstall version, so install just uses references + cd $dir + + puts "[autosetup_version] creating configure to use system-installed autosetup" + autosetup_create_configure 1 + puts "Creating autosetup/README.autosetup" + file mkdir autosetup + autosetup_install_readme autosetup/README.autosetup 1 + return + } + + if {[catch { + if {$shared} { + set target $dir/bin/autosetup + set installedas $target + } else { + if {$dir eq "."} { + set installedas autosetup + } else { + set installedas $dir/autosetup + } + cd $dir + file mkdir autosetup + set target autosetup/autosetup + } + set targetdir [file dirname $target] + file mkdir $targetdir + + set f [open $target w] + + set publicmodules {} + + # First the main script, but only up until "CUT HERE" + set in [open $autosetup(dir)/autosetup] + while {[gets $in buf] >= 0} { + if {$buf ne "##-- CUT HERE --##"} { + puts $f $buf + continue + } + + # Insert the static modules here + # i.e. those which don't contain @synopsis: + # All modules are inserted if $shared is set + puts $f "set autosetup(installed) 1" + puts $f "set autosetup(sysinstall) $shared" + foreach file [lsort [glob $autosetup(libdir)/*.{tcl,auto}]] { + set modname [file tail $file] + set ext [file ext $modname] + set buf [readfile $file] + if {!$shared} { + if {$ext eq ".auto" || [string match "*\n# @synopsis:*" $buf]} { + lappend publicmodules $file + continue + } + } + dputs "install: importing lib/[file tail $file]" + puts $f "# ----- @module $modname -----" + puts $f "\nset modsource($modname) \{" + puts $f $buf + puts $f "\}\n" + } + if {$shared} { + foreach {srcname destname} [list $autosetup(libdir)/README.autosetup-lib README.autosetup \ + $autosetup(srcdir)/LICENSE LICENSE] { + dputs "install: importing $srcname as $destname" + puts $f "\nset modsource($destname) \\\n[list [readfile $srcname]\n]\n" + } + } + } + close $in + close $f + catch {exec chmod 755 $target} + + set installfiles {autosetup-config.guess autosetup-config.sub autosetup-test-tclsh} + set removefiles {} + + if {!$shared} { + autosetup_install_readme $targetdir/README.autosetup 0 + + # Install public modules + foreach file $publicmodules { + set tail [file tail $file] + autosetup_install_file $file $targetdir/$tail + } + lappend installfiles jimsh0.c autosetup-find-tclsh LICENSE + lappend removefiles config.guess config.sub test-tclsh find-tclsh + } else { + lappend installfiles {sys-find-tclsh autosetup-find-tclsh} + } + + # Install support files + foreach fileinfo $installfiles { + if {[llength $fileinfo] == 2} { + lassign $fileinfo source dest + } else { + lassign $fileinfo source + set dest $source + } + autosetup_install_file $autosetup(dir)/$source $targetdir/$dest + } + + # Remove obsolete files + foreach file $removefiles { + if {[file exists $targetdir/$file]} { + file delete $targetdir/$file + } + } + } error]} { + user-error "Failed to install autosetup: $error" + } + if {$shared} { + set type "system" + } else { + set type "local" + } + puts "Installed $type [autosetup_version] to $installedas" + + if {!$shared} { + # Now create 'configure' if necessary + autosetup_create_configure 0 + } +} + +proc autosetup_create_configure {shared} { + if {[file exists configure]} { + if {!$::autosetup(force)} { + # Could this be an autosetup configure? + if {![string match "*\nWRAPPER=*" [readfile configure]]} { + puts "I see configure, but not created by autosetup, so I won't overwrite it." + puts "Remove it or use --force to overwrite." + return + } + } else { + puts "I will overwrite the existing configure because you used --force." + } + } else { + puts "I don't see configure, so I will create it." + } + if {$shared} { + writefile configure \ +{#!/bin/sh +WRAPPER="$0"; export WRAPPER; "autosetup" "$@" +} + } else { + writefile configure \ +{#!/bin/sh +dir="`dirname "$0"`/autosetup" +WRAPPER="$0"; export WRAPPER; exec "`"$dir/autosetup-find-tclsh"`" "$dir/autosetup" "$@" +} + } + catch {exec chmod 755 configure} +} + +# Append the contents of $file to filehandle $f +proc autosetup_install_append {f file} { + dputs "install: include $file" + set in [open $file] + puts $f [read $in] + close $in +} + +proc autosetup_install_file {source target} { + dputs "install: $source => $target" + if {![file exists $source]} { + error "Missing installation file '$source'" + } + writefile $target [readfile $source]\n + # If possible, copy the file mode + file stat $source stat + set mode [format %o [expr {$stat(mode) & 0x1ff}]] + catch {exec chmod $mode $target} +} + +proc autosetup_install_readme {target sysinstall} { + set readme "README.autosetup created by [autosetup_version]\n\n" + if {$sysinstall} { + append readme \ +{This is the autosetup directory for a system install of autosetup. +Loadable modules can be added here. +} + } else { + append readme \ +{This is the autosetup directory for a local install of autosetup. +It contains autosetup, support files and loadable modules. +} +} + + append readme { +*.tcl files in this directory are optional modules which +can be loaded with the 'use' directive. + +*.auto files in this directory are auto-loaded. + +For more information, see http://msteveb.github.com/autosetup/ +} + dputs "install: autosetup/README.autosetup" + writefile $target $readme +} +} + +# ----- @module markdown-formatting.tcl ----- + +set modsource(markdown-formatting.tcl) { +# Copyright (c) 2010 WorkWare Systems http://www.workware.net.au/ +# All rights reserved + +# Module which provides text formatting +# markdown format (kramdown syntax) + +use formatting + +proc para {text} { + regsub -all "\[ \t\n\]+" [string trim $text] " " text + regsub -all {([^a-zA-Z])'([^']*)'} $text {\1**`\2`**} text + regsub -all {^'([^']*)'} $text {**`\1`**} text + regsub -all {(http[^ \t\n]*)} $text {[\1](\1)} text + return $text +} +proc title {text} { + underline [para $text] = + nl +} +proc p {text} { + puts [para $text] + nl +} +proc codelines {lines} { + puts "~~~~~~~~~~~~" + foreach line $lines { + puts $line + } + puts "~~~~~~~~~~~~" + nl +} +proc code {text} { + puts "~~~~~~~~~~~~" + foreach line [parse_code_block $text] { + puts $line + } + puts "~~~~~~~~~~~~" + nl +} +proc nl {} { + puts "" +} +proc underline {text char} { + regexp "^(\[ \t\]*)(.*)" $text -> indent words + puts $text + puts $indent[string repeat $char [string length $words]] +} +proc section {text} { + underline "[para $text]" - + nl +} +proc subsection {text} { + puts "### `$text`" + nl +} +proc bullet {text} { + puts "* [para $text]" +} +proc defn {first args} { + puts "^" + set defn [string trim [join $args \n]] + if {$first ne ""} { + puts "**${first}**" + puts -nonewline ": " + regsub -all "\n\n" $defn "\n: " defn + } + puts "$defn" +} +} + +# ----- @module misc.tcl ----- + +set modsource(misc.tcl) { +# Copyright (c) 2007-2010 WorkWare Systems http://www.workware.net.au/ +# All rights reserved + +# Module containing misc procs useful to modules +# Largely for platform compatibility + +set autosetup(istcl) [info exists ::tcl_library] +set autosetup(iswin) [string equal windows $tcl_platform(platform)] + +if {$autosetup(iswin)} { + # mingw/windows separates $PATH with semicolons + # and doesn't have an executable bit + proc split-path {} { + split [getenv PATH .] {;} + } + proc file-isexec {exec} { + # Basic test for windows. We ignore .bat + if {[file isfile $exec] || [file isfile $exec.exe]} { + return 1 + } + return 0 + } +} else { + # unix separates $PATH with colons and has and executable bit + proc split-path {} { + split [getenv PATH .] : + } + proc file-isexec {exec} { + file executable $exec + } +} + +# Assume that exec can return stdout and stderr +proc exec-with-stderr {args} { + exec {*}$args 2>@1 +} + +if {$autosetup(istcl)} { + # Tcl doesn't have the env command + proc getenv {name args} { + if {[info exists ::env($name)]} { + return $::env($name) + } + if {[llength $args]} { + return [lindex $args 0] + } + return -code error "environment variable \"$name\" does not exist" + } + proc isatty? {channel} { + dict exists [fconfigure $channel] -xchar + } +} else { + if {$autosetup(iswin)} { + # On Windows, backslash convert all environment variables + # (Assume that Tcl does this for us) + proc getenv {name args} { + string map {\\ /} [env $name {*}$args] + } + } else { + # Jim on unix is simple + alias getenv env + } + proc isatty? {channel} { + set tty 0 + catch { + # isatty is a recent addition to Jim Tcl + set tty [$channel isatty] + } + return $tty + } +} + +# In case 'file normalize' doesn't exist +# +proc file-normalize {path} { + if {[catch {file normalize $path} result]} { + if {$path eq ""} { + return "" + } + set oldpwd [pwd] + if {[file isdir $path]} { + cd $path + set result [pwd] + } else { + cd [file dirname $path] + set result [file join [pwd] [file tail $path]] + } + cd $oldpwd + } + return $result +} + +# If everything is working properly, the only errors which occur +# should be generated in user code (e.g. auto.def). +# By default, we only want to show the error location in user code. +# We use [info frame] to achieve this, but it works differently on Tcl and Jim. +# +# This is designed to be called for incorrect usage in auto.def, via autosetup-error +# +proc error-location {msg} { + if {$::autosetup(debug)} { + return -code error $msg + } + # Search back through the stack trace for the first error in a .def file + for {set i 1} {$i < [info level]} {incr i} { + if {$::autosetup(istcl)} { + array set info [info frame -$i] + } else { + lassign [info frame -$i] info(caller) info(file) info(line) + } + if {[string match *.def $info(file)]} { + return "[relative-path $info(file)]:$info(line): Error: $msg" + } + #puts "Skipping $info(file):$info(line)" + } + return $msg +} + +# If everything is working properly, the only errors which occur +# should be generated in user code (e.g. auto.def). +# By default, we only want to show the error location in user code. +# We use [info frame] to achieve this, but it works differently on Tcl and Jim. +# +# This is designed to be called for incorrect usage in auto.def, via autosetup-error +# +proc error-stacktrace {msg} { + if {$::autosetup(debug)} { + return -code error $msg + } + # Search back through the stack trace for the first error in a .def file + for {set i 1} {$i < [info level]} {incr i} { + if {$::autosetup(istcl)} { + array set info [info frame -$i] + } else { + lassign [info frame -$i] info(caller) info(file) info(line) + } + if {[string match *.def $info(file)]} { + return "[relative-path $info(file)]:$info(line): Error: $msg" + } + #puts "Skipping $info(file):$info(line)" + } + return $msg +} + +# Given the return from [catch {...} msg opts], returns an appropriate +# error message. A nice one for Jim and a less-nice one for Tcl. +# If 'fulltrace' is set, a full stack trace is provided. +# Otherwise a simple message is provided. +# +# This is designed for developer errors, e.g. in module code or auto.def code +# +# +proc error-dump {msg opts fulltrace} { + if {$::autosetup(istcl)} { + if {$fulltrace} { + return "Error: [dict get $opts -errorinfo]" + } else { + return "Error: $msg" + } + } else { + lassign $opts(-errorinfo) p f l + if {$f ne ""} { + set result "$f:$l: Error: " + } + append result "$msg\n" + if {$fulltrace} { + append result [stackdump $opts(-errorinfo)] + } + + # Remove the trailing newline + string trim $result + } +} +} + +# ----- @module text-formatting.tcl ----- + +set modsource(text-formatting.tcl) { +# Copyright (c) 2010 WorkWare Systems http://www.workware.net.au/ +# All rights reserved + +# Module which provides text formatting + +use formatting + +proc wordwrap {text length {firstprefix ""} {nextprefix ""}} { + set len 0 + set space $firstprefix + + foreach word [split $text] { + set word [string trim $word] + if {$word eq ""} { + continue + } + if {[info exists partial]} { + append partial " " $word + if {[string first $quote $word] < 0} { + # Haven't found end of quoted word + continue + } + # Finished quoted word + set word $partial + unset partial + unset quote + } else { + set quote [string index $word 0] + if {$quote in {' *}} { + if {[string first $quote $word 1] < 0} { + # Haven't found end of quoted word + # Not a whole word. + set first [string index $word 0] + # Start of quoted word + set partial $word + continue + } + } + } + + if {$len && [string length $space$word] + $len >= $length} { + puts "" + set len 0 + set space $nextprefix + } + incr len [string length $space$word] + + # Use man-page conventions for highlighting 'quoted' and *quoted* + # single words. + # Use x^Hx for *bold* and _^Hx for 'underline'. + # + # less and more will both understand this. + # Pipe through 'col -b' to remove them. + if {[regexp {^'(.*)'(.*)} $word -> quoted after]} { + set quoted [string map {~ " "} $quoted] + regsub -all . $quoted "&\b&" quoted + set word $quoted$after + } elseif {[regexp {^[*](.*)[*](.*)} $word -> quoted after]} { + set quoted [string map {~ " "} $quoted] + regsub -all . $quoted "_\b&" quoted + set word $quoted$after + } + puts -nonewline $space$word + set space " " + } + if {[info exists partial]} { + # Missing end of quote + puts -nonewline $space$partial + } + if {$len} { + puts "" + } +} +proc title {text} { + underline [string trim $text] = + nl +} +proc p {text} { + wordwrap $text 80 + nl +} +proc codelines {lines} { + foreach line $lines { + puts " $line" + } + nl +} +proc nl {} { + puts "" +} +proc underline {text char} { + regexp "^(\[ \t\]*)(.*)" $text -> indent words + puts $text + puts $indent[string repeat $char [string length $words]] +} +proc section {text} { + underline "[string trim $text]" - + nl +} +proc subsection {text} { + underline "$text" ~ + nl +} +proc bullet {text} { + wordwrap $text 76 " * " " " +} +proc indent {text} { + wordwrap $text 76 " " " " +} +proc defn {first args} { + if {$first ne ""} { + underline " $first" ~ + } + foreach p $args { + if {$p ne ""} { + indent $p + } + } +} +} + +# ----- @module util.tcl ----- + +set modsource(util.tcl) { +# Copyright (c) 2012 WorkWare Systems http://www.workware.net.au/ +# All rights reserved + +# Module which contains miscellaneous utility functions + +# @compare-versions version1 version2 +# +# Versions are of the form 'a.b.c' (may be any number of numeric components) +# +# Compares the two versions and returns: +## -1 if v1 < v2 +## 0 if v1 == v2 +## 1 if v1 > v2 +# +# If one version has fewer components than the other, 0 is substituted to the right. e.g. +## 0.2 < 0.3 +## 0.2.5 > 0.2 +## 1.1 == 1.1.0 +# +proc compare-versions {v1 v2} { + foreach c1 [split $v1 .] c2 [split $v2 .] { + if {$c1 eq ""} { + set c1 0 + } + if {$c2 eq ""} { + set c2 0 + } + if {$c1 < $c2} { + return -1 + } + if {$c1 > $c2} { + return 1 + } + } + return 0 +} + +# @suffix suf list +# +# Takes a list and returns a new list with '$suf' appended +# to each element +# +## suffix .c {a b c} => {a.c b.c c.c} +# +proc suffix {suf list} { + set result {} + foreach p $list { + lappend result $p$suf + } + return $result +} + +# @prefix pre list +# +# Takes a list and returns a new list with '$pre' prepended +# to each element +# +## prefix jim- {a.c b.c} => {jim-a.c jim-b.c} +# +proc prefix {pre list} { + set result {} + foreach p $list { + lappend result $pre$p + } + return $result +} + +# @lpop list +# +# Removes the last entry from the given list and returns it. +proc lpop {listname} { + upvar $listname list + set val [lindex $list end] + set list [lrange $list 0 end-1] + return $val +} +} + +# ----- @module wiki-formatting.tcl ----- + +set modsource(wiki-formatting.tcl) { +# Copyright (c) 2010 WorkWare Systems http://www.workware.net.au/ +# All rights reserved + +# Module which provides text formatting +# wiki.tcl.tk format output + +use formatting + +proc joinlines {text} { + set lines {} + foreach l [split [string trim $text] \n] { + lappend lines [string trim $l] + } + join $lines +} +proc p {text} { + puts [joinlines $text] + puts "" +} +proc title {text} { + puts "*** [joinlines $text] ***" + puts "" +} +proc codelines {lines} { + puts "======" + foreach line $lines { + puts " $line" + } + puts "======" +} +proc code {text} { + puts "======" + foreach line [parse_code_block $text] { + puts " $line" + } + puts "======" +} +proc nl {} { +} +proc section {text} { + puts "'''$text'''" + puts "" +} +proc subsection {text} { + puts "''$text''" + puts "" +} +proc bullet {text} { + puts " * [joinlines $text]" +} +proc indent {text} { + puts " : [joinlines $text]" +} +proc defn {first args} { + if {$first ne ""} { + indent '''$first''' + } + + foreach p $args { + p $p + } +} +} + + +################################################################## +# +# Entry/Exit +# +if {$autosetup(debug)} { + main $argv +} +if {[catch {main $argv} msg opts] == 1} { + show-notices + autosetup-full-error [error-dump $msg $opts $autosetup(debug)] + if {!$autosetup(debug)} { + puts stderr "Try: '[file tail $autosetup(exe)] --debug' for a full stack trace" + } + exit 1 +} ADDED autosetup/autosetup-config.guess Index: autosetup/autosetup-config.guess ================================================================== --- /dev/null +++ autosetup/autosetup-config.guess @@ -0,0 +1,1476 @@ +#! /bin/sh +# Attempt to guess a canonical system name. +# Copyright 1992-2018 Free Software Foundation, Inc. + +timestamp='2018-03-08' + +# This file is free software; you can redistribute it and/or modify it +# under the terms of the GNU General Public License as published by +# the Free Software Foundation; either version 3 of the License, or +# (at your option) any later version. +# +# 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. See the GNU +# General Public License for more details. +# +# You should have received a copy of the GNU General Public License +# along with this program; if not, see . +# +# As a special exception to the GNU General Public License, if you +# distribute this file as part of a program that contains a +# configuration script generated by Autoconf, you may include it under +# the same distribution terms that you use for the rest of that +# program. This Exception is an additional permission under section 7 +# of the GNU General Public License, version 3 ("GPLv3"). +# +# Originally written by Per Bothner; maintained since 2000 by Ben Elliston. +# +# You can get the latest version of this script from: +# https://git.savannah.gnu.org/gitweb/?p=config.git;a=blob_plain;f=config.guess +# +# Please send patches to . + + +me=`echo "$0" | sed -e 's,.*/,,'` + +usage="\ +Usage: $0 [OPTION] + +Output the configuration name of the system \`$me' is run on. + +Options: + -h, --help print this help, then exit + -t, --time-stamp print date of last modification, then exit + -v, --version print version number, then exit + +Report bugs and patches to ." + +version="\ +GNU config.guess ($timestamp) + +Originally written by Per Bothner. +Copyright 1992-2018 Free Software Foundation, Inc. + +This is free software; see the source for copying conditions. There is NO +warranty; not even for MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE." + +help=" +Try \`$me --help' for more information." + +# Parse command line +while test $# -gt 0 ; do + case $1 in + --time-stamp | --time* | -t ) + echo "$timestamp" ; exit ;; + --version | -v ) + echo "$version" ; exit ;; + --help | --h* | -h ) + echo "$usage"; exit ;; + -- ) # Stop option processing + shift; break ;; + - ) # Use stdin as input. + break ;; + -* ) + echo "$me: invalid option $1$help" >&2 + exit 1 ;; + * ) + break ;; + esac +done + +if test $# != 0; then + echo "$me: too many arguments$help" >&2 + exit 1 +fi + +trap 'exit 1' 1 2 15 + +# CC_FOR_BUILD -- compiler used by this script. Note that the use of a +# compiler to aid in system detection is discouraged as it requires +# temporary files to be created and, as you can see below, it is a +# headache to deal with in a portable fashion. + +# Historically, `CC_FOR_BUILD' used to be named `HOST_CC'. We still +# use `HOST_CC' if defined, but it is deprecated. + +# Portable tmp directory creation inspired by the Autoconf team. + +set_cc_for_build=' +trap "exitcode=\$?; (rm -f \$tmpfiles 2>/dev/null; rmdir \$tmp 2>/dev/null) && exit \$exitcode" 0 ; +trap "rm -f \$tmpfiles 2>/dev/null; rmdir \$tmp 2>/dev/null; exit 1" 1 2 13 15 ; +: ${TMPDIR=/tmp} ; + { tmp=`(umask 077 && mktemp -d "$TMPDIR/cgXXXXXX") 2>/dev/null` && test -n "$tmp" && test -d "$tmp" ; } || + { test -n "$RANDOM" && tmp=$TMPDIR/cg$$-$RANDOM && (umask 077 && mkdir $tmp) ; } || + { tmp=$TMPDIR/cg-$$ && (umask 077 && mkdir $tmp) && echo "Warning: creating insecure temp directory" >&2 ; } || + { echo "$me: cannot create a temporary directory in $TMPDIR" >&2 ; exit 1 ; } ; +dummy=$tmp/dummy ; +tmpfiles="$dummy.c $dummy.o $dummy.rel $dummy" ; +case $CC_FOR_BUILD,$HOST_CC,$CC in + ,,) echo "int x;" > "$dummy.c" ; + for c in cc gcc c89 c99 ; do + if ($c -c -o "$dummy.o" "$dummy.c") >/dev/null 2>&1 ; then + CC_FOR_BUILD="$c"; break ; + fi ; + done ; + if test x"$CC_FOR_BUILD" = x ; then + CC_FOR_BUILD=no_compiler_found ; + fi + ;; + ,,*) CC_FOR_BUILD=$CC ;; + ,*,*) CC_FOR_BUILD=$HOST_CC ;; +esac ; set_cc_for_build= ;' + +# This is needed to find uname on a Pyramid OSx when run in the BSD universe. +# (ghazi@noc.rutgers.edu 1994-08-24) +if (test -f /.attbin/uname) >/dev/null 2>&1 ; then + PATH=$PATH:/.attbin ; export PATH +fi + +UNAME_MACHINE=`(uname -m) 2>/dev/null` || UNAME_MACHINE=unknown +UNAME_RELEASE=`(uname -r) 2>/dev/null` || UNAME_RELEASE=unknown +UNAME_SYSTEM=`(uname -s) 2>/dev/null` || UNAME_SYSTEM=unknown +UNAME_VERSION=`(uname -v) 2>/dev/null` || UNAME_VERSION=unknown + +case "$UNAME_SYSTEM" in +Linux|GNU|GNU/*) + # If the system lacks a compiler, then just pick glibc. + # We could probably try harder. + LIBC=gnu + + eval "$set_cc_for_build" + cat <<-EOF > "$dummy.c" + #include + #if defined(__UCLIBC__) + LIBC=uclibc + #elif defined(__dietlibc__) + LIBC=dietlibc + #else + LIBC=gnu + #endif + EOF + eval "`$CC_FOR_BUILD -E "$dummy.c" 2>/dev/null | grep '^LIBC' | sed 's, ,,g'`" + + # If ldd exists, use it to detect musl libc. + if command -v ldd >/dev/null && \ + ldd --version 2>&1 | grep -q ^musl + then + LIBC=musl + fi + ;; +esac + +# Note: order is significant - the case branches are not exclusive. + +case "$UNAME_MACHINE:$UNAME_SYSTEM:$UNAME_RELEASE:$UNAME_VERSION" in + *:NetBSD:*:*) + # NetBSD (nbsd) targets should (where applicable) match one or + # more of the tuples: *-*-netbsdelf*, *-*-netbsdaout*, + # *-*-netbsdecoff* and *-*-netbsd*. For targets that recently + # switched to ELF, *-*-netbsd* would select the old + # object file format. This provides both forward + # compatibility and a consistent mechanism for selecting the + # object file format. + # + # Note: NetBSD doesn't particularly care about the vendor + # portion of the name. We always set it to "unknown". + sysctl="sysctl -n hw.machine_arch" + UNAME_MACHINE_ARCH=`(uname -p 2>/dev/null || \ + "/sbin/$sysctl" 2>/dev/null || \ + "/usr/sbin/$sysctl" 2>/dev/null || \ + echo unknown)` + case "$UNAME_MACHINE_ARCH" in + armeb) machine=armeb-unknown ;; + arm*) machine=arm-unknown ;; + sh3el) machine=shl-unknown ;; + sh3eb) machine=sh-unknown ;; + sh5el) machine=sh5le-unknown ;; + earmv*) + arch=`echo "$UNAME_MACHINE_ARCH" | sed -e 's,^e\(armv[0-9]\).*$,\1,'` + endian=`echo "$UNAME_MACHINE_ARCH" | sed -ne 's,^.*\(eb\)$,\1,p'` + machine="${arch}${endian}"-unknown + ;; + *) machine="$UNAME_MACHINE_ARCH"-unknown ;; + esac + # The Operating System including object format, if it has switched + # to ELF recently (or will in the future) and ABI. + case "$UNAME_MACHINE_ARCH" in + earm*) + os=netbsdelf + ;; + arm*|i386|m68k|ns32k|sh3*|sparc|vax) + eval "$set_cc_for_build" + if echo __ELF__ | $CC_FOR_BUILD -E - 2>/dev/null \ + | grep -q __ELF__ + then + # Once all utilities can be ECOFF (netbsdecoff) or a.out (netbsdaout). + # Return netbsd for either. FIX? + os=netbsd + else + os=netbsdelf + fi + ;; + *) + os=netbsd + ;; + esac + # Determine ABI tags. + case "$UNAME_MACHINE_ARCH" in + earm*) + expr='s/^earmv[0-9]/-eabi/;s/eb$//' + abi=`echo "$UNAME_MACHINE_ARCH" | sed -e "$expr"` + ;; + esac + # The OS release + # Debian GNU/NetBSD machines have a different userland, and + # thus, need a distinct triplet. However, they do not need + # kernel version information, so it can be replaced with a + # suitable tag, in the style of linux-gnu. + case "$UNAME_VERSION" in + Debian*) + release='-gnu' + ;; + *) + release=`echo "$UNAME_RELEASE" | sed -e 's/[-_].*//' | cut -d. -f1,2` + ;; + esac + # Since CPU_TYPE-MANUFACTURER-KERNEL-OPERATING_SYSTEM: + # contains redundant information, the shorter form: + # CPU_TYPE-MANUFACTURER-OPERATING_SYSTEM is used. + echo "$machine-${os}${release}${abi}" + exit ;; + *:Bitrig:*:*) + UNAME_MACHINE_ARCH=`arch | sed 's/Bitrig.//'` + echo "$UNAME_MACHINE_ARCH"-unknown-bitrig"$UNAME_RELEASE" + exit ;; + *:OpenBSD:*:*) + UNAME_MACHINE_ARCH=`arch | sed 's/OpenBSD.//'` + echo "$UNAME_MACHINE_ARCH"-unknown-openbsd"$UNAME_RELEASE" + exit ;; + *:LibertyBSD:*:*) + UNAME_MACHINE_ARCH=`arch | sed 's/^.*BSD\.//'` + echo "$UNAME_MACHINE_ARCH"-unknown-libertybsd"$UNAME_RELEASE" + exit ;; + *:MidnightBSD:*:*) + echo "$UNAME_MACHINE"-unknown-midnightbsd"$UNAME_RELEASE" + exit ;; + *:ekkoBSD:*:*) + echo "$UNAME_MACHINE"-unknown-ekkobsd"$UNAME_RELEASE" + exit ;; + *:SolidBSD:*:*) + echo "$UNAME_MACHINE"-unknown-solidbsd"$UNAME_RELEASE" + exit ;; + macppc:MirBSD:*:*) + echo powerpc-unknown-mirbsd"$UNAME_RELEASE" + exit ;; + *:MirBSD:*:*) + echo "$UNAME_MACHINE"-unknown-mirbsd"$UNAME_RELEASE" + exit ;; + *:Sortix:*:*) + echo "$UNAME_MACHINE"-unknown-sortix + exit ;; + *:Redox:*:*) + echo "$UNAME_MACHINE"-unknown-redox + exit ;; + mips:OSF1:*.*) + echo mips-dec-osf1 + exit ;; + alpha:OSF1:*:*) + case $UNAME_RELEASE in + *4.0) + UNAME_RELEASE=`/usr/sbin/sizer -v | awk '{print $3}'` + ;; + *5.*) + UNAME_RELEASE=`/usr/sbin/sizer -v | awk '{print $4}'` + ;; + esac + # According to Compaq, /usr/sbin/psrinfo has been available on + # OSF/1 and Tru64 systems produced since 1995. I hope that + # covers most systems running today. This code pipes the CPU + # types through head -n 1, so we only detect the type of CPU 0. + ALPHA_CPU_TYPE=`/usr/sbin/psrinfo -v | sed -n -e 's/^ The alpha \(.*\) processor.*$/\1/p' | head -n 1` + case "$ALPHA_CPU_TYPE" in + "EV4 (21064)") + UNAME_MACHINE=alpha ;; + "EV4.5 (21064)") + UNAME_MACHINE=alpha ;; + "LCA4 (21066/21068)") + UNAME_MACHINE=alpha ;; + "EV5 (21164)") + UNAME_MACHINE=alphaev5 ;; + "EV5.6 (21164A)") + UNAME_MACHINE=alphaev56 ;; + "EV5.6 (21164PC)") + UNAME_MACHINE=alphapca56 ;; + "EV5.7 (21164PC)") + UNAME_MACHINE=alphapca57 ;; + "EV6 (21264)") + UNAME_MACHINE=alphaev6 ;; + "EV6.7 (21264A)") + UNAME_MACHINE=alphaev67 ;; + "EV6.8CB (21264C)") + UNAME_MACHINE=alphaev68 ;; + "EV6.8AL (21264B)") + UNAME_MACHINE=alphaev68 ;; + "EV6.8CX (21264D)") + UNAME_MACHINE=alphaev68 ;; + "EV6.9A (21264/EV69A)") + UNAME_MACHINE=alphaev69 ;; + "EV7 (21364)") + UNAME_MACHINE=alphaev7 ;; + "EV7.9 (21364A)") + UNAME_MACHINE=alphaev79 ;; + esac + # A Pn.n version is a patched version. + # A Vn.n version is a released version. + # A Tn.n version is a released field test version. + # A Xn.n version is an unreleased experimental baselevel. + # 1.2 uses "1.2" for uname -r. + echo "$UNAME_MACHINE"-dec-osf"`echo "$UNAME_RELEASE" | sed -e 's/^[PVTX]//' | tr ABCDEFGHIJKLMNOPQRSTUVWXYZ abcdefghijklmnopqrstuvwxyz`" + # Reset EXIT trap before exiting to avoid spurious non-zero exit code. + exitcode=$? + trap '' 0 + exit $exitcode ;; + Amiga*:UNIX_System_V:4.0:*) + echo m68k-unknown-sysv4 + exit ;; + *:[Aa]miga[Oo][Ss]:*:*) + echo "$UNAME_MACHINE"-unknown-amigaos + exit ;; + *:[Mm]orph[Oo][Ss]:*:*) + echo "$UNAME_MACHINE"-unknown-morphos + exit ;; + *:OS/390:*:*) + echo i370-ibm-openedition + exit ;; + *:z/VM:*:*) + echo s390-ibm-zvmoe + exit ;; + *:OS400:*:*) + echo powerpc-ibm-os400 + exit ;; + arm:RISC*:1.[012]*:*|arm:riscix:1.[012]*:*) + echo arm-acorn-riscix"$UNAME_RELEASE" + exit ;; + arm*:riscos:*:*|arm*:RISCOS:*:*) + echo arm-unknown-riscos + exit ;; + SR2?01:HI-UX/MPP:*:* | SR8000:HI-UX/MPP:*:*) + echo hppa1.1-hitachi-hiuxmpp + exit ;; + Pyramid*:OSx*:*:* | MIS*:OSx*:*:* | MIS*:SMP_DC-OSx*:*:*) + # akee@wpdis03.wpafb.af.mil (Earle F. Ake) contributed MIS and NILE. + if test "`(/bin/universe) 2>/dev/null`" = att ; then + echo pyramid-pyramid-sysv3 + else + echo pyramid-pyramid-bsd + fi + exit ;; + NILE*:*:*:dcosx) + echo pyramid-pyramid-svr4 + exit ;; + DRS?6000:unix:4.0:6*) + echo sparc-icl-nx6 + exit ;; + DRS?6000:UNIX_SV:4.2*:7* | DRS?6000:isis:4.2*:7*) + case `/usr/bin/uname -p` in + sparc) echo sparc-icl-nx7; exit ;; + esac ;; + s390x:SunOS:*:*) + echo "$UNAME_MACHINE"-ibm-solaris2"`echo "$UNAME_RELEASE" | sed -e 's/[^.]*//'`" + exit ;; + sun4H:SunOS:5.*:*) + echo sparc-hal-solaris2"`echo "$UNAME_RELEASE"|sed -e 's/[^.]*//'`" + exit ;; + sun4*:SunOS:5.*:* | tadpole*:SunOS:5.*:*) + echo sparc-sun-solaris2"`echo "$UNAME_RELEASE" | sed -e 's/[^.]*//'`" + exit ;; + i86pc:AuroraUX:5.*:* | i86xen:AuroraUX:5.*:*) + echo i386-pc-auroraux"$UNAME_RELEASE" + exit ;; + i86pc:SunOS:5.*:* | i86xen:SunOS:5.*:*) + eval "$set_cc_for_build" + SUN_ARCH=i386 + # If there is a compiler, see if it is configured for 64-bit objects. + # Note that the Sun cc does not turn __LP64__ into 1 like gcc does. + # This test works for both compilers. + if [ "$CC_FOR_BUILD" != no_compiler_found ]; then + if (echo '#ifdef __amd64'; echo IS_64BIT_ARCH; echo '#endif') | \ + (CCOPTS="" $CC_FOR_BUILD -E - 2>/dev/null) | \ + grep IS_64BIT_ARCH >/dev/null + then + SUN_ARCH=x86_64 + fi + fi + echo "$SUN_ARCH"-pc-solaris2"`echo "$UNAME_RELEASE"|sed -e 's/[^.]*//'`" + exit ;; + sun4*:SunOS:6*:*) + # According to config.sub, this is the proper way to canonicalize + # SunOS6. Hard to guess exactly what SunOS6 will be like, but + # it's likely to be more like Solaris than SunOS4. + echo sparc-sun-solaris3"`echo "$UNAME_RELEASE"|sed -e 's/[^.]*//'`" + exit ;; + sun4*:SunOS:*:*) + case "`/usr/bin/arch -k`" in + Series*|S4*) + UNAME_RELEASE=`uname -v` + ;; + esac + # Japanese Language versions have a version number like `4.1.3-JL'. + echo sparc-sun-sunos"`echo "$UNAME_RELEASE"|sed -e 's/-/_/'`" + exit ;; + sun3*:SunOS:*:*) + echo m68k-sun-sunos"$UNAME_RELEASE" + exit ;; + sun*:*:4.2BSD:*) + UNAME_RELEASE=`(sed 1q /etc/motd | awk '{print substr($5,1,3)}') 2>/dev/null` + test "x$UNAME_RELEASE" = x && UNAME_RELEASE=3 + case "`/bin/arch`" in + sun3) + echo m68k-sun-sunos"$UNAME_RELEASE" + ;; + sun4) + echo sparc-sun-sunos"$UNAME_RELEASE" + ;; + esac + exit ;; + aushp:SunOS:*:*) + echo sparc-auspex-sunos"$UNAME_RELEASE" + exit ;; + # The situation for MiNT is a little confusing. The machine name + # can be virtually everything (everything which is not + # "atarist" or "atariste" at least should have a processor + # > m68000). The system name ranges from "MiNT" over "FreeMiNT" + # to the lowercase version "mint" (or "freemint"). Finally + # the system name "TOS" denotes a system which is actually not + # MiNT. But MiNT is downward compatible to TOS, so this should + # be no problem. + atarist[e]:*MiNT:*:* | atarist[e]:*mint:*:* | atarist[e]:*TOS:*:*) + echo m68k-atari-mint"$UNAME_RELEASE" + exit ;; + atari*:*MiNT:*:* | atari*:*mint:*:* | atarist[e]:*TOS:*:*) + echo m68k-atari-mint"$UNAME_RELEASE" + exit ;; + *falcon*:*MiNT:*:* | *falcon*:*mint:*:* | *falcon*:*TOS:*:*) + echo m68k-atari-mint"$UNAME_RELEASE" + exit ;; + milan*:*MiNT:*:* | milan*:*mint:*:* | *milan*:*TOS:*:*) + echo m68k-milan-mint"$UNAME_RELEASE" + exit ;; + hades*:*MiNT:*:* | hades*:*mint:*:* | *hades*:*TOS:*:*) + echo m68k-hades-mint"$UNAME_RELEASE" + exit ;; + *:*MiNT:*:* | *:*mint:*:* | *:*TOS:*:*) + echo m68k-unknown-mint"$UNAME_RELEASE" + exit ;; + m68k:machten:*:*) + echo m68k-apple-machten"$UNAME_RELEASE" + exit ;; + powerpc:machten:*:*) + echo powerpc-apple-machten"$UNAME_RELEASE" + exit ;; + RISC*:Mach:*:*) + echo mips-dec-mach_bsd4.3 + exit ;; + RISC*:ULTRIX:*:*) + echo mips-dec-ultrix"$UNAME_RELEASE" + exit ;; + VAX*:ULTRIX*:*:*) + echo vax-dec-ultrix"$UNAME_RELEASE" + exit ;; + 2020:CLIX:*:* | 2430:CLIX:*:*) + echo clipper-intergraph-clix"$UNAME_RELEASE" + exit ;; + mips:*:*:UMIPS | mips:*:*:RISCos) + eval "$set_cc_for_build" + sed 's/^ //' << EOF > "$dummy.c" +#ifdef __cplusplus +#include /* for printf() prototype */ + int main (int argc, char *argv[]) { +#else + int main (argc, argv) int argc; char *argv[]; { +#endif + #if defined (host_mips) && defined (MIPSEB) + #if defined (SYSTYPE_SYSV) + printf ("mips-mips-riscos%ssysv\\n", argv[1]); exit (0); + #endif + #if defined (SYSTYPE_SVR4) + printf ("mips-mips-riscos%ssvr4\\n", argv[1]); exit (0); + #endif + #if defined (SYSTYPE_BSD43) || defined(SYSTYPE_BSD) + printf ("mips-mips-riscos%sbsd\\n", argv[1]); exit (0); + #endif + #endif + exit (-1); + } +EOF + $CC_FOR_BUILD -o "$dummy" "$dummy.c" && + dummyarg=`echo "$UNAME_RELEASE" | sed -n 's/\([0-9]*\).*/\1/p'` && + SYSTEM_NAME=`"$dummy" "$dummyarg"` && + { echo "$SYSTEM_NAME"; exit; } + echo mips-mips-riscos"$UNAME_RELEASE" + exit ;; + Motorola:PowerMAX_OS:*:*) + echo powerpc-motorola-powermax + exit ;; + Motorola:*:4.3:PL8-*) + echo powerpc-harris-powermax + exit ;; + Night_Hawk:*:*:PowerMAX_OS | Synergy:PowerMAX_OS:*:*) + echo powerpc-harris-powermax + exit ;; + Night_Hawk:Power_UNIX:*:*) + echo powerpc-harris-powerunix + exit ;; + m88k:CX/UX:7*:*) + echo m88k-harris-cxux7 + exit ;; + m88k:*:4*:R4*) + echo m88k-motorola-sysv4 + exit ;; + m88k:*:3*:R3*) + echo m88k-motorola-sysv3 + exit ;; + AViiON:dgux:*:*) + # DG/UX returns AViiON for all architectures + UNAME_PROCESSOR=`/usr/bin/uname -p` + if [ "$UNAME_PROCESSOR" = mc88100 ] || [ "$UNAME_PROCESSOR" = mc88110 ] + then + if [ "$TARGET_BINARY_INTERFACE"x = m88kdguxelfx ] || \ + [ "$TARGET_BINARY_INTERFACE"x = x ] + then + echo m88k-dg-dgux"$UNAME_RELEASE" + else + echo m88k-dg-dguxbcs"$UNAME_RELEASE" + fi + else + echo i586-dg-dgux"$UNAME_RELEASE" + fi + exit ;; + M88*:DolphinOS:*:*) # DolphinOS (SVR3) + echo m88k-dolphin-sysv3 + exit ;; + M88*:*:R3*:*) + # Delta 88k system running SVR3 + echo m88k-motorola-sysv3 + exit ;; + XD88*:*:*:*) # Tektronix XD88 system running UTekV (SVR3) + echo m88k-tektronix-sysv3 + exit ;; + Tek43[0-9][0-9]:UTek:*:*) # Tektronix 4300 system running UTek (BSD) + echo m68k-tektronix-bsd + exit ;; + *:IRIX*:*:*) + echo mips-sgi-irix"`echo "$UNAME_RELEASE"|sed -e 's/-/_/g'`" + exit ;; + ????????:AIX?:[12].1:2) # AIX 2.2.1 or AIX 2.1.1 is RT/PC AIX. + echo romp-ibm-aix # uname -m gives an 8 hex-code CPU id + exit ;; # Note that: echo "'`uname -s`'" gives 'AIX ' + i*86:AIX:*:*) + echo i386-ibm-aix + exit ;; + ia64:AIX:*:*) + if [ -x /usr/bin/oslevel ] ; then + IBM_REV=`/usr/bin/oslevel` + else + IBM_REV="$UNAME_VERSION.$UNAME_RELEASE" + fi + echo "$UNAME_MACHINE"-ibm-aix"$IBM_REV" + exit ;; + *:AIX:2:3) + if grep bos325 /usr/include/stdio.h >/dev/null 2>&1; then + eval "$set_cc_for_build" + sed 's/^ //' << EOF > "$dummy.c" + #include + + main() + { + if (!__power_pc()) + exit(1); + puts("powerpc-ibm-aix3.2.5"); + exit(0); + } +EOF + if $CC_FOR_BUILD -o "$dummy" "$dummy.c" && SYSTEM_NAME=`"$dummy"` + then + echo "$SYSTEM_NAME" + else + echo rs6000-ibm-aix3.2.5 + fi + elif grep bos324 /usr/include/stdio.h >/dev/null 2>&1; then + echo rs6000-ibm-aix3.2.4 + else + echo rs6000-ibm-aix3.2 + fi + exit ;; + *:AIX:*:[4567]) + IBM_CPU_ID=`/usr/sbin/lsdev -C -c processor -S available | sed 1q | awk '{ print $1 }'` + if /usr/sbin/lsattr -El "$IBM_CPU_ID" | grep ' POWER' >/dev/null 2>&1; then + IBM_ARCH=rs6000 + else + IBM_ARCH=powerpc + fi + if [ -x /usr/bin/lslpp ] ; then + IBM_REV=`/usr/bin/lslpp -Lqc bos.rte.libc | + awk -F: '{ print $3 }' | sed s/[0-9]*$/0/` + else + IBM_REV="$UNAME_VERSION.$UNAME_RELEASE" + fi + echo "$IBM_ARCH"-ibm-aix"$IBM_REV" + exit ;; + *:AIX:*:*) + echo rs6000-ibm-aix + exit ;; + ibmrt:4.4BSD:*|romp-ibm:4.4BSD:*) + echo romp-ibm-bsd4.4 + exit ;; + ibmrt:*BSD:*|romp-ibm:BSD:*) # covers RT/PC BSD and + echo romp-ibm-bsd"$UNAME_RELEASE" # 4.3 with uname added to + exit ;; # report: romp-ibm BSD 4.3 + *:BOSX:*:*) + echo rs6000-bull-bosx + exit ;; + DPX/2?00:B.O.S.:*:*) + echo m68k-bull-sysv3 + exit ;; + 9000/[34]??:4.3bsd:1.*:*) + echo m68k-hp-bsd + exit ;; + hp300:4.4BSD:*:* | 9000/[34]??:4.3bsd:2.*:*) + echo m68k-hp-bsd4.4 + exit ;; + 9000/[34678]??:HP-UX:*:*) + HPUX_REV=`echo "$UNAME_RELEASE"|sed -e 's/[^.]*.[0B]*//'` + case "$UNAME_MACHINE" in + 9000/31?) HP_ARCH=m68000 ;; + 9000/[34]??) HP_ARCH=m68k ;; + 9000/[678][0-9][0-9]) + if [ -x /usr/bin/getconf ]; then + sc_cpu_version=`/usr/bin/getconf SC_CPU_VERSION 2>/dev/null` + sc_kernel_bits=`/usr/bin/getconf SC_KERNEL_BITS 2>/dev/null` + case "$sc_cpu_version" in + 523) HP_ARCH=hppa1.0 ;; # CPU_PA_RISC1_0 + 528) HP_ARCH=hppa1.1 ;; # CPU_PA_RISC1_1 + 532) # CPU_PA_RISC2_0 + case "$sc_kernel_bits" in + 32) HP_ARCH=hppa2.0n ;; + 64) HP_ARCH=hppa2.0w ;; + '') HP_ARCH=hppa2.0 ;; # HP-UX 10.20 + esac ;; + esac + fi + if [ "$HP_ARCH" = "" ]; then + eval "$set_cc_for_build" + sed 's/^ //' << EOF > "$dummy.c" + + #define _HPUX_SOURCE + #include + #include + + int main () + { + #if defined(_SC_KERNEL_BITS) + long bits = sysconf(_SC_KERNEL_BITS); + #endif + long cpu = sysconf (_SC_CPU_VERSION); + + switch (cpu) + { + case CPU_PA_RISC1_0: puts ("hppa1.0"); break; + case CPU_PA_RISC1_1: puts ("hppa1.1"); break; + case CPU_PA_RISC2_0: + #if defined(_SC_KERNEL_BITS) + switch (bits) + { + case 64: puts ("hppa2.0w"); break; + case 32: puts ("hppa2.0n"); break; + default: puts ("hppa2.0"); break; + } break; + #else /* !defined(_SC_KERNEL_BITS) */ + puts ("hppa2.0"); break; + #endif + default: puts ("hppa1.0"); break; + } + exit (0); + } +EOF + (CCOPTS="" $CC_FOR_BUILD -o "$dummy" "$dummy.c" 2>/dev/null) && HP_ARCH=`"$dummy"` + test -z "$HP_ARCH" && HP_ARCH=hppa + fi ;; + esac + if [ "$HP_ARCH" = hppa2.0w ] + then + eval "$set_cc_for_build" + + # hppa2.0w-hp-hpux* has a 64-bit kernel and a compiler generating + # 32-bit code. hppa64-hp-hpux* has the same kernel and a compiler + # generating 64-bit code. GNU and HP use different nomenclature: + # + # $ CC_FOR_BUILD=cc ./config.guess + # => hppa2.0w-hp-hpux11.23 + # $ CC_FOR_BUILD="cc +DA2.0w" ./config.guess + # => hppa64-hp-hpux11.23 + + if echo __LP64__ | (CCOPTS="" $CC_FOR_BUILD -E - 2>/dev/null) | + grep -q __LP64__ + then + HP_ARCH=hppa2.0w + else + HP_ARCH=hppa64 + fi + fi + echo "$HP_ARCH"-hp-hpux"$HPUX_REV" + exit ;; + ia64:HP-UX:*:*) + HPUX_REV=`echo "$UNAME_RELEASE"|sed -e 's/[^.]*.[0B]*//'` + echo ia64-hp-hpux"$HPUX_REV" + exit ;; + 3050*:HI-UX:*:*) + eval "$set_cc_for_build" + sed 's/^ //' << EOF > "$dummy.c" + #include + int + main () + { + long cpu = sysconf (_SC_CPU_VERSION); + /* The order matters, because CPU_IS_HP_MC68K erroneously returns + true for CPU_PA_RISC1_0. CPU_IS_PA_RISC returns correct + results, however. */ + if (CPU_IS_PA_RISC (cpu)) + { + switch (cpu) + { + case CPU_PA_RISC1_0: puts ("hppa1.0-hitachi-hiuxwe2"); break; + case CPU_PA_RISC1_1: puts ("hppa1.1-hitachi-hiuxwe2"); break; + case CPU_PA_RISC2_0: puts ("hppa2.0-hitachi-hiuxwe2"); break; + default: puts ("hppa-hitachi-hiuxwe2"); break; + } + } + else if (CPU_IS_HP_MC68K (cpu)) + puts ("m68k-hitachi-hiuxwe2"); + else puts ("unknown-hitachi-hiuxwe2"); + exit (0); + } +EOF + $CC_FOR_BUILD -o "$dummy" "$dummy.c" && SYSTEM_NAME=`"$dummy"` && + { echo "$SYSTEM_NAME"; exit; } + echo unknown-hitachi-hiuxwe2 + exit ;; + 9000/7??:4.3bsd:*:* | 9000/8?[79]:4.3bsd:*:*) + echo hppa1.1-hp-bsd + exit ;; + 9000/8??:4.3bsd:*:*) + echo hppa1.0-hp-bsd + exit ;; + *9??*:MPE/iX:*:* | *3000*:MPE/iX:*:*) + echo hppa1.0-hp-mpeix + exit ;; + hp7??:OSF1:*:* | hp8?[79]:OSF1:*:*) + echo hppa1.1-hp-osf + exit ;; + hp8??:OSF1:*:*) + echo hppa1.0-hp-osf + exit ;; + i*86:OSF1:*:*) + if [ -x /usr/sbin/sysversion ] ; then + echo "$UNAME_MACHINE"-unknown-osf1mk + else + echo "$UNAME_MACHINE"-unknown-osf1 + fi + exit ;; + parisc*:Lites*:*:*) + echo hppa1.1-hp-lites + exit ;; + C1*:ConvexOS:*:* | convex:ConvexOS:C1*:*) + echo c1-convex-bsd + exit ;; + C2*:ConvexOS:*:* | convex:ConvexOS:C2*:*) + if getsysinfo -f scalar_acc + then echo c32-convex-bsd + else echo c2-convex-bsd + fi + exit ;; + C34*:ConvexOS:*:* | convex:ConvexOS:C34*:*) + echo c34-convex-bsd + exit ;; + C38*:ConvexOS:*:* | convex:ConvexOS:C38*:*) + echo c38-convex-bsd + exit ;; + C4*:ConvexOS:*:* | convex:ConvexOS:C4*:*) + echo c4-convex-bsd + exit ;; + CRAY*Y-MP:*:*:*) + echo ymp-cray-unicos"$UNAME_RELEASE" | sed -e 's/\.[^.]*$/.X/' + exit ;; + CRAY*[A-Z]90:*:*:*) + echo "$UNAME_MACHINE"-cray-unicos"$UNAME_RELEASE" \ + | sed -e 's/CRAY.*\([A-Z]90\)/\1/' \ + -e y/ABCDEFGHIJKLMNOPQRSTUVWXYZ/abcdefghijklmnopqrstuvwxyz/ \ + -e 's/\.[^.]*$/.X/' + exit ;; + CRAY*TS:*:*:*) + echo t90-cray-unicos"$UNAME_RELEASE" | sed -e 's/\.[^.]*$/.X/' + exit ;; + CRAY*T3E:*:*:*) + echo alphaev5-cray-unicosmk"$UNAME_RELEASE" | sed -e 's/\.[^.]*$/.X/' + exit ;; + CRAY*SV1:*:*:*) + echo sv1-cray-unicos"$UNAME_RELEASE" | sed -e 's/\.[^.]*$/.X/' + exit ;; + *:UNICOS/mp:*:*) + echo craynv-cray-unicosmp"$UNAME_RELEASE" | sed -e 's/\.[^.]*$/.X/' + exit ;; + F30[01]:UNIX_System_V:*:* | F700:UNIX_System_V:*:*) + FUJITSU_PROC=`uname -m | tr ABCDEFGHIJKLMNOPQRSTUVWXYZ abcdefghijklmnopqrstuvwxyz` + FUJITSU_SYS=`uname -p | tr ABCDEFGHIJKLMNOPQRSTUVWXYZ abcdefghijklmnopqrstuvwxyz | sed -e 's/\///'` + FUJITSU_REL=`echo "$UNAME_RELEASE" | sed -e 's/ /_/'` + echo "${FUJITSU_PROC}-fujitsu-${FUJITSU_SYS}${FUJITSU_REL}" + exit ;; + 5000:UNIX_System_V:4.*:*) + FUJITSU_SYS=`uname -p | tr ABCDEFGHIJKLMNOPQRSTUVWXYZ abcdefghijklmnopqrstuvwxyz | sed -e 's/\///'` + FUJITSU_REL=`echo "$UNAME_RELEASE" | tr ABCDEFGHIJKLMNOPQRSTUVWXYZ abcdefghijklmnopqrstuvwxyz | sed -e 's/ /_/'` + echo "sparc-fujitsu-${FUJITSU_SYS}${FUJITSU_REL}" + exit ;; + i*86:BSD/386:*:* | i*86:BSD/OS:*:* | *:Ascend\ Embedded/OS:*:*) + echo "$UNAME_MACHINE"-pc-bsdi"$UNAME_RELEASE" + exit ;; + sparc*:BSD/OS:*:*) + echo sparc-unknown-bsdi"$UNAME_RELEASE" + exit ;; + *:BSD/OS:*:*) + echo "$UNAME_MACHINE"-unknown-bsdi"$UNAME_RELEASE" + exit ;; + *:FreeBSD:*:*) + UNAME_PROCESSOR=`/usr/bin/uname -p` + case "$UNAME_PROCESSOR" in + amd64) + UNAME_PROCESSOR=x86_64 ;; + i386) + UNAME_PROCESSOR=i586 ;; + esac + echo "$UNAME_PROCESSOR"-unknown-freebsd"`echo "$UNAME_RELEASE"|sed -e 's/[-(].*//'`" + exit ;; + i*:CYGWIN*:*) + echo "$UNAME_MACHINE"-pc-cygwin + exit ;; + *:MINGW64*:*) + echo "$UNAME_MACHINE"-pc-mingw64 + exit ;; + *:MINGW*:*) + echo "$UNAME_MACHINE"-pc-mingw32 + exit ;; + *:MSYS*:*) + echo "$UNAME_MACHINE"-pc-msys + exit ;; + i*:PW*:*) + echo "$UNAME_MACHINE"-pc-pw32 + exit ;; + *:Interix*:*) + case "$UNAME_MACHINE" in + x86) + echo i586-pc-interix"$UNAME_RELEASE" + exit ;; + authenticamd | genuineintel | EM64T) + echo x86_64-unknown-interix"$UNAME_RELEASE" + exit ;; + IA64) + echo ia64-unknown-interix"$UNAME_RELEASE" + exit ;; + esac ;; + i*:UWIN*:*) + echo "$UNAME_MACHINE"-pc-uwin + exit ;; + amd64:CYGWIN*:*:* | x86_64:CYGWIN*:*:*) + echo x86_64-unknown-cygwin + exit ;; + prep*:SunOS:5.*:*) + echo powerpcle-unknown-solaris2"`echo "$UNAME_RELEASE"|sed -e 's/[^.]*//'`" + exit ;; + *:GNU:*:*) + # the GNU system + echo "`echo "$UNAME_MACHINE"|sed -e 's,[-/].*$,,'`-unknown-$LIBC`echo "$UNAME_RELEASE"|sed -e 's,/.*$,,'`" + exit ;; + *:GNU/*:*:*) + # other systems with GNU libc and userland + echo "$UNAME_MACHINE-unknown-`echo "$UNAME_SYSTEM" | sed 's,^[^/]*/,,' | tr "[:upper:]" "[:lower:]"``echo "$UNAME_RELEASE"|sed -e 's/[-(].*//'`-$LIBC" + exit ;; + i*86:Minix:*:*) + echo "$UNAME_MACHINE"-pc-minix + exit ;; + aarch64:Linux:*:*) + echo "$UNAME_MACHINE"-unknown-linux-"$LIBC" + exit ;; + aarch64_be:Linux:*:*) + UNAME_MACHINE=aarch64_be + echo "$UNAME_MACHINE"-unknown-linux-"$LIBC" + exit ;; + alpha:Linux:*:*) + case `sed -n '/^cpu model/s/^.*: \(.*\)/\1/p' < /proc/cpuinfo` in + EV5) UNAME_MACHINE=alphaev5 ;; + EV56) UNAME_MACHINE=alphaev56 ;; + PCA56) UNAME_MACHINE=alphapca56 ;; + PCA57) UNAME_MACHINE=alphapca56 ;; + EV6) UNAME_MACHINE=alphaev6 ;; + EV67) UNAME_MACHINE=alphaev67 ;; + EV68*) UNAME_MACHINE=alphaev68 ;; + esac + objdump --private-headers /bin/sh | grep -q ld.so.1 + if test "$?" = 0 ; then LIBC=gnulibc1 ; fi + echo "$UNAME_MACHINE"-unknown-linux-"$LIBC" + exit ;; + arc:Linux:*:* | arceb:Linux:*:*) + echo "$UNAME_MACHINE"-unknown-linux-"$LIBC" + exit ;; + arm*:Linux:*:*) + eval "$set_cc_for_build" + if echo __ARM_EABI__ | $CC_FOR_BUILD -E - 2>/dev/null \ + | grep -q __ARM_EABI__ + then + echo "$UNAME_MACHINE"-unknown-linux-"$LIBC" + else + if echo __ARM_PCS_VFP | $CC_FOR_BUILD -E - 2>/dev/null \ + | grep -q __ARM_PCS_VFP + then + echo "$UNAME_MACHINE"-unknown-linux-"$LIBC"eabi + else + echo "$UNAME_MACHINE"-unknown-linux-"$LIBC"eabihf + fi + fi + exit ;; + avr32*:Linux:*:*) + echo "$UNAME_MACHINE"-unknown-linux-"$LIBC" + exit ;; + cris:Linux:*:*) + echo "$UNAME_MACHINE"-axis-linux-"$LIBC" + exit ;; + crisv32:Linux:*:*) + echo "$UNAME_MACHINE"-axis-linux-"$LIBC" + exit ;; + e2k:Linux:*:*) + echo "$UNAME_MACHINE"-unknown-linux-"$LIBC" + exit ;; + frv:Linux:*:*) + echo "$UNAME_MACHINE"-unknown-linux-"$LIBC" + exit ;; + hexagon:Linux:*:*) + echo "$UNAME_MACHINE"-unknown-linux-"$LIBC" + exit ;; + i*86:Linux:*:*) + echo "$UNAME_MACHINE"-pc-linux-"$LIBC" + exit ;; + ia64:Linux:*:*) + echo "$UNAME_MACHINE"-unknown-linux-"$LIBC" + exit ;; + k1om:Linux:*:*) + echo "$UNAME_MACHINE"-unknown-linux-"$LIBC" + exit ;; + m32r*:Linux:*:*) + echo "$UNAME_MACHINE"-unknown-linux-"$LIBC" + exit ;; + m68*:Linux:*:*) + echo "$UNAME_MACHINE"-unknown-linux-"$LIBC" + exit ;; + mips:Linux:*:* | mips64:Linux:*:*) + eval "$set_cc_for_build" + sed 's/^ //' << EOF > "$dummy.c" + #undef CPU + #undef ${UNAME_MACHINE} + #undef ${UNAME_MACHINE}el + #if defined(__MIPSEL__) || defined(__MIPSEL) || defined(_MIPSEL) || defined(MIPSEL) + CPU=${UNAME_MACHINE}el + #else + #if defined(__MIPSEB__) || defined(__MIPSEB) || defined(_MIPSEB) || defined(MIPSEB) + CPU=${UNAME_MACHINE} + #else + CPU= + #endif + #endif +EOF + eval "`$CC_FOR_BUILD -E "$dummy.c" 2>/dev/null | grep '^CPU'`" + test "x$CPU" != x && { echo "$CPU-unknown-linux-$LIBC"; exit; } + ;; + mips64el:Linux:*:*) + echo "$UNAME_MACHINE"-unknown-linux-"$LIBC" + exit ;; + openrisc*:Linux:*:*) + echo or1k-unknown-linux-"$LIBC" + exit ;; + or32:Linux:*:* | or1k*:Linux:*:*) + echo "$UNAME_MACHINE"-unknown-linux-"$LIBC" + exit ;; + padre:Linux:*:*) + echo sparc-unknown-linux-"$LIBC" + exit ;; + parisc64:Linux:*:* | hppa64:Linux:*:*) + echo hppa64-unknown-linux-"$LIBC" + exit ;; + parisc:Linux:*:* | hppa:Linux:*:*) + # Look for CPU level + case `grep '^cpu[^a-z]*:' /proc/cpuinfo 2>/dev/null | cut -d' ' -f2` in + PA7*) echo hppa1.1-unknown-linux-"$LIBC" ;; + PA8*) echo hppa2.0-unknown-linux-"$LIBC" ;; + *) echo hppa-unknown-linux-"$LIBC" ;; + esac + exit ;; + ppc64:Linux:*:*) + echo powerpc64-unknown-linux-"$LIBC" + exit ;; + ppc:Linux:*:*) + echo powerpc-unknown-linux-"$LIBC" + exit ;; + ppc64le:Linux:*:*) + echo powerpc64le-unknown-linux-"$LIBC" + exit ;; + ppcle:Linux:*:*) + echo powerpcle-unknown-linux-"$LIBC" + exit ;; + riscv32:Linux:*:* | riscv64:Linux:*:*) + echo "$UNAME_MACHINE"-unknown-linux-"$LIBC" + exit ;; + s390:Linux:*:* | s390x:Linux:*:*) + echo "$UNAME_MACHINE"-ibm-linux-"$LIBC" + exit ;; + sh64*:Linux:*:*) + echo "$UNAME_MACHINE"-unknown-linux-"$LIBC" + exit ;; + sh*:Linux:*:*) + echo "$UNAME_MACHINE"-unknown-linux-"$LIBC" + exit ;; + sparc:Linux:*:* | sparc64:Linux:*:*) + echo "$UNAME_MACHINE"-unknown-linux-"$LIBC" + exit ;; + tile*:Linux:*:*) + echo "$UNAME_MACHINE"-unknown-linux-"$LIBC" + exit ;; + vax:Linux:*:*) + echo "$UNAME_MACHINE"-dec-linux-"$LIBC" + exit ;; + x86_64:Linux:*:*) + echo "$UNAME_MACHINE"-pc-linux-"$LIBC" + exit ;; + xtensa*:Linux:*:*) + echo "$UNAME_MACHINE"-unknown-linux-"$LIBC" + exit ;; + i*86:DYNIX/ptx:4*:*) + # ptx 4.0 does uname -s correctly, with DYNIX/ptx in there. + # earlier versions are messed up and put the nodename in both + # sysname and nodename. + echo i386-sequent-sysv4 + exit ;; + i*86:UNIX_SV:4.2MP:2.*) + # Unixware is an offshoot of SVR4, but it has its own version + # number series starting with 2... + # I am not positive that other SVR4 systems won't match this, + # I just have to hope. -- rms. + # Use sysv4.2uw... so that sysv4* matches it. + echo "$UNAME_MACHINE"-pc-sysv4.2uw"$UNAME_VERSION" + exit ;; + i*86:OS/2:*:*) + # If we were able to find `uname', then EMX Unix compatibility + # is probably installed. + echo "$UNAME_MACHINE"-pc-os2-emx + exit ;; + i*86:XTS-300:*:STOP) + echo "$UNAME_MACHINE"-unknown-stop + exit ;; + i*86:atheos:*:*) + echo "$UNAME_MACHINE"-unknown-atheos + exit ;; + i*86:syllable:*:*) + echo "$UNAME_MACHINE"-pc-syllable + exit ;; + i*86:LynxOS:2.*:* | i*86:LynxOS:3.[01]*:* | i*86:LynxOS:4.[02]*:*) + echo i386-unknown-lynxos"$UNAME_RELEASE" + exit ;; + i*86:*DOS:*:*) + echo "$UNAME_MACHINE"-pc-msdosdjgpp + exit ;; + i*86:*:4.*:*) + UNAME_REL=`echo "$UNAME_RELEASE" | sed 's/\/MP$//'` + if grep Novell /usr/include/link.h >/dev/null 2>/dev/null; then + echo "$UNAME_MACHINE"-univel-sysv"$UNAME_REL" + else + echo "$UNAME_MACHINE"-pc-sysv"$UNAME_REL" + fi + exit ;; + i*86:*:5:[678]*) + # UnixWare 7.x, OpenUNIX and OpenServer 6. + case `/bin/uname -X | grep "^Machine"` in + *486*) UNAME_MACHINE=i486 ;; + *Pentium) UNAME_MACHINE=i586 ;; + *Pent*|*Celeron) UNAME_MACHINE=i686 ;; + esac + echo "$UNAME_MACHINE-unknown-sysv${UNAME_RELEASE}${UNAME_SYSTEM}{$UNAME_VERSION}" + exit ;; + i*86:*:3.2:*) + if test -f /usr/options/cb.name; then + UNAME_REL=`sed -n 's/.*Version //p' /dev/null >/dev/null ; then + UNAME_REL=`(/bin/uname -X|grep Release|sed -e 's/.*= //')` + (/bin/uname -X|grep i80486 >/dev/null) && UNAME_MACHINE=i486 + (/bin/uname -X|grep '^Machine.*Pentium' >/dev/null) \ + && UNAME_MACHINE=i586 + (/bin/uname -X|grep '^Machine.*Pent *II' >/dev/null) \ + && UNAME_MACHINE=i686 + (/bin/uname -X|grep '^Machine.*Pentium Pro' >/dev/null) \ + && UNAME_MACHINE=i686 + echo "$UNAME_MACHINE"-pc-sco"$UNAME_REL" + else + echo "$UNAME_MACHINE"-pc-sysv32 + fi + exit ;; + pc:*:*:*) + # Left here for compatibility: + # uname -m prints for DJGPP always 'pc', but it prints nothing about + # the processor, so we play safe by assuming i586. + # Note: whatever this is, it MUST be the same as what config.sub + # prints for the "djgpp" host, or else GDB configure will decide that + # this is a cross-build. + echo i586-pc-msdosdjgpp + exit ;; + Intel:Mach:3*:*) + echo i386-pc-mach3 + exit ;; + paragon:*:*:*) + echo i860-intel-osf1 + exit ;; + i860:*:4.*:*) # i860-SVR4 + if grep Stardent /usr/include/sys/uadmin.h >/dev/null 2>&1 ; then + echo i860-stardent-sysv"$UNAME_RELEASE" # Stardent Vistra i860-SVR4 + else # Add other i860-SVR4 vendors below as they are discovered. + echo i860-unknown-sysv"$UNAME_RELEASE" # Unknown i860-SVR4 + fi + exit ;; + mini*:CTIX:SYS*5:*) + # "miniframe" + echo m68010-convergent-sysv + exit ;; + mc68k:UNIX:SYSTEM5:3.51m) + echo m68k-convergent-sysv + exit ;; + M680?0:D-NIX:5.3:*) + echo m68k-diab-dnix + exit ;; + M68*:*:R3V[5678]*:*) + test -r /sysV68 && { echo 'm68k-motorola-sysv'; exit; } ;; + 3[345]??:*:4.0:3.0 | 3[34]??A:*:4.0:3.0 | 3[34]??,*:*:4.0:3.0 | 3[34]??/*:*:4.0:3.0 | 4400:*:4.0:3.0 | 4850:*:4.0:3.0 | SKA40:*:4.0:3.0 | SDS2:*:4.0:3.0 | SHG2:*:4.0:3.0 | S7501*:*:4.0:3.0) + OS_REL='' + test -r /etc/.relid \ + && OS_REL=.`sed -n 's/[^ ]* [^ ]* \([0-9][0-9]\).*/\1/p' < /etc/.relid` + /bin/uname -p 2>/dev/null | grep 86 >/dev/null \ + && { echo i486-ncr-sysv4.3"$OS_REL"; exit; } + /bin/uname -p 2>/dev/null | /bin/grep entium >/dev/null \ + && { echo i586-ncr-sysv4.3"$OS_REL"; exit; } ;; + 3[34]??:*:4.0:* | 3[34]??,*:*:4.0:*) + /bin/uname -p 2>/dev/null | grep 86 >/dev/null \ + && { echo i486-ncr-sysv4; exit; } ;; + NCR*:*:4.2:* | MPRAS*:*:4.2:*) + OS_REL='.3' + test -r /etc/.relid \ + && OS_REL=.`sed -n 's/[^ ]* [^ ]* \([0-9][0-9]\).*/\1/p' < /etc/.relid` + /bin/uname -p 2>/dev/null | grep 86 >/dev/null \ + && { echo i486-ncr-sysv4.3"$OS_REL"; exit; } + /bin/uname -p 2>/dev/null | /bin/grep entium >/dev/null \ + && { echo i586-ncr-sysv4.3"$OS_REL"; exit; } + /bin/uname -p 2>/dev/null | /bin/grep pteron >/dev/null \ + && { echo i586-ncr-sysv4.3"$OS_REL"; exit; } ;; + m68*:LynxOS:2.*:* | m68*:LynxOS:3.0*:*) + echo m68k-unknown-lynxos"$UNAME_RELEASE" + exit ;; + mc68030:UNIX_System_V:4.*:*) + echo m68k-atari-sysv4 + exit ;; + TSUNAMI:LynxOS:2.*:*) + echo sparc-unknown-lynxos"$UNAME_RELEASE" + exit ;; + rs6000:LynxOS:2.*:*) + echo rs6000-unknown-lynxos"$UNAME_RELEASE" + exit ;; + PowerPC:LynxOS:2.*:* | PowerPC:LynxOS:3.[01]*:* | PowerPC:LynxOS:4.[02]*:*) + echo powerpc-unknown-lynxos"$UNAME_RELEASE" + exit ;; + SM[BE]S:UNIX_SV:*:*) + echo mips-dde-sysv"$UNAME_RELEASE" + exit ;; + RM*:ReliantUNIX-*:*:*) + echo mips-sni-sysv4 + exit ;; + RM*:SINIX-*:*:*) + echo mips-sni-sysv4 + exit ;; + *:SINIX-*:*:*) + if uname -p 2>/dev/null >/dev/null ; then + UNAME_MACHINE=`(uname -p) 2>/dev/null` + echo "$UNAME_MACHINE"-sni-sysv4 + else + echo ns32k-sni-sysv + fi + exit ;; + PENTIUM:*:4.0*:*) # Unisys `ClearPath HMP IX 4000' SVR4/MP effort + # says + echo i586-unisys-sysv4 + exit ;; + *:UNIX_System_V:4*:FTX*) + # From Gerald Hewes . + # How about differentiating between stratus architectures? -djm + echo hppa1.1-stratus-sysv4 + exit ;; + *:*:*:FTX*) + # From seanf@swdc.stratus.com. + echo i860-stratus-sysv4 + exit ;; + i*86:VOS:*:*) + # From Paul.Green@stratus.com. + echo "$UNAME_MACHINE"-stratus-vos + exit ;; + *:VOS:*:*) + # From Paul.Green@stratus.com. + echo hppa1.1-stratus-vos + exit ;; + mc68*:A/UX:*:*) + echo m68k-apple-aux"$UNAME_RELEASE" + exit ;; + news*:NEWS-OS:6*:*) + echo mips-sony-newsos6 + exit ;; + R[34]000:*System_V*:*:* | R4000:UNIX_SYSV:*:* | R*000:UNIX_SV:*:*) + if [ -d /usr/nec ]; then + echo mips-nec-sysv"$UNAME_RELEASE" + else + echo mips-unknown-sysv"$UNAME_RELEASE" + fi + exit ;; + BeBox:BeOS:*:*) # BeOS running on hardware made by Be, PPC only. + echo powerpc-be-beos + exit ;; + BeMac:BeOS:*:*) # BeOS running on Mac or Mac clone, PPC only. + echo powerpc-apple-beos + exit ;; + BePC:BeOS:*:*) # BeOS running on Intel PC compatible. + echo i586-pc-beos + exit ;; + BePC:Haiku:*:*) # Haiku running on Intel PC compatible. + echo i586-pc-haiku + exit ;; + x86_64:Haiku:*:*) + echo x86_64-unknown-haiku + exit ;; + SX-4:SUPER-UX:*:*) + echo sx4-nec-superux"$UNAME_RELEASE" + exit ;; + SX-5:SUPER-UX:*:*) + echo sx5-nec-superux"$UNAME_RELEASE" + exit ;; + SX-6:SUPER-UX:*:*) + echo sx6-nec-superux"$UNAME_RELEASE" + exit ;; + SX-7:SUPER-UX:*:*) + echo sx7-nec-superux"$UNAME_RELEASE" + exit ;; + SX-8:SUPER-UX:*:*) + echo sx8-nec-superux"$UNAME_RELEASE" + exit ;; + SX-8R:SUPER-UX:*:*) + echo sx8r-nec-superux"$UNAME_RELEASE" + exit ;; + SX-ACE:SUPER-UX:*:*) + echo sxace-nec-superux"$UNAME_RELEASE" + exit ;; + Power*:Rhapsody:*:*) + echo powerpc-apple-rhapsody"$UNAME_RELEASE" + exit ;; + *:Rhapsody:*:*) + echo "$UNAME_MACHINE"-apple-rhapsody"$UNAME_RELEASE" + exit ;; + *:Darwin:*:*) + UNAME_PROCESSOR=`uname -p` || UNAME_PROCESSOR=unknown + eval "$set_cc_for_build" + if test "$UNAME_PROCESSOR" = unknown ; then + UNAME_PROCESSOR=powerpc + fi + if test "`echo "$UNAME_RELEASE" | sed -e 's/\..*//'`" -le 10 ; then + if [ "$CC_FOR_BUILD" != no_compiler_found ]; then + if (echo '#ifdef __LP64__'; echo IS_64BIT_ARCH; echo '#endif') | \ + (CCOPTS="" $CC_FOR_BUILD -E - 2>/dev/null) | \ + grep IS_64BIT_ARCH >/dev/null + then + case $UNAME_PROCESSOR in + i386) UNAME_PROCESSOR=x86_64 ;; + powerpc) UNAME_PROCESSOR=powerpc64 ;; + esac + fi + # On 10.4-10.6 one might compile for PowerPC via gcc -arch ppc + if (echo '#ifdef __POWERPC__'; echo IS_PPC; echo '#endif') | \ + (CCOPTS="" $CC_FOR_BUILD -E - 2>/dev/null) | \ + grep IS_PPC >/dev/null + then + UNAME_PROCESSOR=powerpc + fi + fi + elif test "$UNAME_PROCESSOR" = i386 ; then + # Avoid executing cc on OS X 10.9, as it ships with a stub + # that puts up a graphical alert prompting to install + # developer tools. Any system running Mac OS X 10.7 or + # later (Darwin 11 and later) is required to have a 64-bit + # processor. This is not true of the ARM version of Darwin + # that Apple uses in portable devices. + UNAME_PROCESSOR=x86_64 + fi + echo "$UNAME_PROCESSOR"-apple-darwin"$UNAME_RELEASE" + exit ;; + *:procnto*:*:* | *:QNX:[0123456789]*:*) + UNAME_PROCESSOR=`uname -p` + if test "$UNAME_PROCESSOR" = x86; then + UNAME_PROCESSOR=i386 + UNAME_MACHINE=pc + fi + echo "$UNAME_PROCESSOR"-"$UNAME_MACHINE"-nto-qnx"$UNAME_RELEASE" + exit ;; + *:QNX:*:4*) + echo i386-pc-qnx + exit ;; + NEO-*:NONSTOP_KERNEL:*:*) + echo neo-tandem-nsk"$UNAME_RELEASE" + exit ;; + NSE-*:NONSTOP_KERNEL:*:*) + echo nse-tandem-nsk"$UNAME_RELEASE" + exit ;; + NSR-*:NONSTOP_KERNEL:*:*) + echo nsr-tandem-nsk"$UNAME_RELEASE" + exit ;; + NSV-*:NONSTOP_KERNEL:*:*) + echo nsv-tandem-nsk"$UNAME_RELEASE" + exit ;; + NSX-*:NONSTOP_KERNEL:*:*) + echo nsx-tandem-nsk"$UNAME_RELEASE" + exit ;; + *:NonStop-UX:*:*) + echo mips-compaq-nonstopux + exit ;; + BS2000:POSIX*:*:*) + echo bs2000-siemens-sysv + exit ;; + DS/*:UNIX_System_V:*:*) + echo "$UNAME_MACHINE"-"$UNAME_SYSTEM"-"$UNAME_RELEASE" + exit ;; + *:Plan9:*:*) + # "uname -m" is not consistent, so use $cputype instead. 386 + # is converted to i386 for consistency with other x86 + # operating systems. + if test "$cputype" = 386; then + UNAME_MACHINE=i386 + else + UNAME_MACHINE="$cputype" + fi + echo "$UNAME_MACHINE"-unknown-plan9 + exit ;; + *:TOPS-10:*:*) + echo pdp10-unknown-tops10 + exit ;; + *:TENEX:*:*) + echo pdp10-unknown-tenex + exit ;; + KS10:TOPS-20:*:* | KL10:TOPS-20:*:* | TYPE4:TOPS-20:*:*) + echo pdp10-dec-tops20 + exit ;; + XKL-1:TOPS-20:*:* | TYPE5:TOPS-20:*:*) + echo pdp10-xkl-tops20 + exit ;; + *:TOPS-20:*:*) + echo pdp10-unknown-tops20 + exit ;; + *:ITS:*:*) + echo pdp10-unknown-its + exit ;; + SEI:*:*:SEIUX) + echo mips-sei-seiux"$UNAME_RELEASE" + exit ;; + *:DragonFly:*:*) + echo "$UNAME_MACHINE"-unknown-dragonfly"`echo "$UNAME_RELEASE"|sed -e 's/[-(].*//'`" + exit ;; + *:*VMS:*:*) + UNAME_MACHINE=`(uname -p) 2>/dev/null` + case "$UNAME_MACHINE" in + A*) echo alpha-dec-vms ; exit ;; + I*) echo ia64-dec-vms ; exit ;; + V*) echo vax-dec-vms ; exit ;; + esac ;; + *:XENIX:*:SysV) + echo i386-pc-xenix + exit ;; + i*86:skyos:*:*) + echo "$UNAME_MACHINE"-pc-skyos"`echo "$UNAME_RELEASE" | sed -e 's/ .*$//'`" + exit ;; + i*86:rdos:*:*) + echo "$UNAME_MACHINE"-pc-rdos + exit ;; + i*86:AROS:*:*) + echo "$UNAME_MACHINE"-pc-aros + exit ;; + x86_64:VMkernel:*:*) + echo "$UNAME_MACHINE"-unknown-esx + exit ;; + amd64:Isilon\ OneFS:*:*) + echo x86_64-unknown-onefs + exit ;; +esac + +echo "$0: unable to guess system type" >&2 + +case "$UNAME_MACHINE:$UNAME_SYSTEM" in + mips:Linux | mips64:Linux) + # If we got here on MIPS GNU/Linux, output extra information. + cat >&2 <&2 </dev/null || echo unknown` +uname -r = `(uname -r) 2>/dev/null || echo unknown` +uname -s = `(uname -s) 2>/dev/null || echo unknown` +uname -v = `(uname -v) 2>/dev/null || echo unknown` + +/usr/bin/uname -p = `(/usr/bin/uname -p) 2>/dev/null` +/bin/uname -X = `(/bin/uname -X) 2>/dev/null` + +hostinfo = `(hostinfo) 2>/dev/null` +/bin/universe = `(/bin/universe) 2>/dev/null` +/usr/bin/arch -k = `(/usr/bin/arch -k) 2>/dev/null` +/bin/arch = `(/bin/arch) 2>/dev/null` +/usr/bin/oslevel = `(/usr/bin/oslevel) 2>/dev/null` +/usr/convex/getsysinfo = `(/usr/convex/getsysinfo) 2>/dev/null` + +UNAME_MACHINE = "$UNAME_MACHINE" +UNAME_RELEASE = "$UNAME_RELEASE" +UNAME_SYSTEM = "$UNAME_SYSTEM" +UNAME_VERSION = "$UNAME_VERSION" +EOF + +exit 1 + +# Local variables: +# eval: (add-hook 'before-save-hook 'time-stamp) +# time-stamp-start: "timestamp='" +# time-stamp-format: "%:y-%02m-%02d" +# time-stamp-end: "'" +# End: ADDED autosetup/autosetup-config.sub Index: autosetup/autosetup-config.sub ================================================================== --- /dev/null +++ autosetup/autosetup-config.sub @@ -0,0 +1,1801 @@ +#! /bin/sh +# Configuration validation subroutine script. +# Copyright 1992-2018 Free Software Foundation, Inc. + +timestamp='2018-03-08' + +# This file is free software; you can redistribute it and/or modify it +# under the terms of the GNU General Public License as published by +# the Free Software Foundation; either version 3 of the License, or +# (at your option) any later version. +# +# 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. See the GNU +# General Public License for more details. +# +# You should have received a copy of the GNU General Public License +# along with this program; if not, see . +# +# As a special exception to the GNU General Public License, if you +# distribute this file as part of a program that contains a +# configuration script generated by Autoconf, you may include it under +# the same distribution terms that you use for the rest of that +# program. This Exception is an additional permission under section 7 +# of the GNU General Public License, version 3 ("GPLv3"). + + +# Please send patches to . +# +# Configuration subroutine to validate and canonicalize a configuration type. +# Supply the specified configuration type as an argument. +# If it is invalid, we print an error message on stderr and exit with code 1. +# Otherwise, we print the canonical config type on stdout and succeed. + +# You can get the latest version of this script from: +# https://git.savannah.gnu.org/gitweb/?p=config.git;a=blob_plain;f=config.sub + +# This file is supposed to be the same for all GNU packages +# and recognize all the CPU types, system types and aliases +# that are meaningful with *any* GNU software. +# Each package is responsible for reporting which valid configurations +# it does not support. The user should be able to distinguish +# a failure to support a valid configuration from a meaningless +# configuration. + +# The goal of this file is to map all the various variations of a given +# machine specification into a single specification in the form: +# CPU_TYPE-MANUFACTURER-OPERATING_SYSTEM +# or in some cases, the newer four-part form: +# CPU_TYPE-MANUFACTURER-KERNEL-OPERATING_SYSTEM +# It is wrong to echo any other type of specification. + +me=`echo "$0" | sed -e 's,.*/,,'` + +usage="\ +Usage: $0 [OPTION] CPU-MFR-OPSYS or ALIAS + +Canonicalize a configuration name. + +Options: + -h, --help print this help, then exit + -t, --time-stamp print date of last modification, then exit + -v, --version print version number, then exit + +Report bugs and patches to ." + +version="\ +GNU config.sub ($timestamp) + +Copyright 1992-2018 Free Software Foundation, Inc. + +This is free software; see the source for copying conditions. There is NO +warranty; not even for MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE." + +help=" +Try \`$me --help' for more information." + +# Parse command line +while test $# -gt 0 ; do + case $1 in + --time-stamp | --time* | -t ) + echo "$timestamp" ; exit ;; + --version | -v ) + echo "$version" ; exit ;; + --help | --h* | -h ) + echo "$usage"; exit ;; + -- ) # Stop option processing + shift; break ;; + - ) # Use stdin as input. + break ;; + -* ) + echo "$me: invalid option $1$help" + exit 1 ;; + + *local*) + # First pass through any local machine types. + echo "$1" + exit ;; + + * ) + break ;; + esac +done + +case $# in + 0) echo "$me: missing argument$help" >&2 + exit 1;; + 1) ;; + *) echo "$me: too many arguments$help" >&2 + exit 1;; +esac + +# Separate what the user gave into CPU-COMPANY and OS or KERNEL-OS (if any). +# Here we must recognize all the valid KERNEL-OS combinations. +maybe_os=`echo "$1" | sed 's/^\(.*\)-\([^-]*-[^-]*\)$/\2/'` +case $maybe_os in + nto-qnx* | linux-gnu* | linux-android* | linux-dietlibc | linux-newlib* | \ + linux-musl* | linux-uclibc* | uclinux-uclibc* | uclinux-gnu* | kfreebsd*-gnu* | \ + knetbsd*-gnu* | netbsd*-gnu* | netbsd*-eabi* | \ + kopensolaris*-gnu* | cloudabi*-eabi* | \ + storm-chaos* | os2-emx* | rtmk-nova*) + os=-$maybe_os + basic_machine=`echo "$1" | sed 's/^\(.*\)-\([^-]*-[^-]*\)$/\1/'` + ;; + android-linux) + os=-linux-android + basic_machine=`echo "$1" | sed 's/^\(.*\)-\([^-]*-[^-]*\)$/\1/'`-unknown + ;; + *) + basic_machine=`echo "$1" | sed 's/-[^-]*$//'` + if [ "$basic_machine" != "$1" ] + then os=`echo "$1" | sed 's/.*-/-/'` + else os=; fi + ;; +esac + +### Let's recognize common machines as not being operating systems so +### that things like config.sub decstation-3100 work. We also +### recognize some manufacturers as not being operating systems, so we +### can provide default operating systems below. +case $os in + -sun*os*) + # Prevent following clause from handling this invalid input. + ;; + -dec* | -mips* | -sequent* | -encore* | -pc532* | -sgi* | -sony* | \ + -att* | -7300* | -3300* | -delta* | -motorola* | -sun[234]* | \ + -unicom* | -ibm* | -next | -hp | -isi* | -apollo | -altos* | \ + -convergent* | -ncr* | -news | -32* | -3600* | -3100* | -hitachi* |\ + -c[123]* | -convex* | -sun | -crds | -omron* | -dg | -ultra | -tti* | \ + -harris | -dolphin | -highlevel | -gould | -cbm | -ns | -masscomp | \ + -apple | -axis | -knuth | -cray | -microblaze*) + os= + basic_machine=$1 + ;; + -bluegene*) + os=-cnk + ;; + -sim | -cisco | -oki | -wec | -winbond) + os= + basic_machine=$1 + ;; + -scout) + ;; + -wrs) + os=-vxworks + basic_machine=$1 + ;; + -chorusos*) + os=-chorusos + basic_machine=$1 + ;; + -chorusrdb) + os=-chorusrdb + basic_machine=$1 + ;; + -hiux*) + os=-hiuxwe2 + ;; + -sco6) + os=-sco5v6 + basic_machine=`echo "$1" | sed -e 's/86-.*/86-pc/'` + ;; + -sco5) + os=-sco3.2v5 + basic_machine=`echo "$1" | sed -e 's/86-.*/86-pc/'` + ;; + -sco4) + os=-sco3.2v4 + basic_machine=`echo "$1" | sed -e 's/86-.*/86-pc/'` + ;; + -sco3.2.[4-9]*) + os=`echo $os | sed -e 's/sco3.2./sco3.2v/'` + basic_machine=`echo "$1" | sed -e 's/86-.*/86-pc/'` + ;; + -sco3.2v[4-9]*) + # Don't forget version if it is 3.2v4 or newer. + basic_machine=`echo "$1" | sed -e 's/86-.*/86-pc/'` + ;; + -sco5v6*) + # Don't forget version if it is 3.2v4 or newer. + basic_machine=`echo "$1" | sed -e 's/86-.*/86-pc/'` + ;; + -sco*) + os=-sco3.2v2 + basic_machine=`echo "$1" | sed -e 's/86-.*/86-pc/'` + ;; + -udk*) + basic_machine=`echo "$1" | sed -e 's/86-.*/86-pc/'` + ;; + -isc) + os=-isc2.2 + basic_machine=`echo "$1" | sed -e 's/86-.*/86-pc/'` + ;; + -clix*) + basic_machine=clipper-intergraph + ;; + -isc*) + basic_machine=`echo "$1" | sed -e 's/86-.*/86-pc/'` + ;; + -lynx*178) + os=-lynxos178 + ;; + -lynx*5) + os=-lynxos5 + ;; + -lynx*) + os=-lynxos + ;; + -ptx*) + basic_machine=`echo "$1" | sed -e 's/86-.*/86-sequent/'` + ;; + -psos*) + os=-psos + ;; + -mint | -mint[0-9]*) + basic_machine=m68k-atari + os=-mint + ;; +esac + +# Decode aliases for certain CPU-COMPANY combinations. +case $basic_machine in + # Recognize the basic CPU types without company name. + # Some are omitted here because they have special meanings below. + 1750a | 580 \ + | a29k \ + | aarch64 | aarch64_be \ + | alpha | alphaev[4-8] | alphaev56 | alphaev6[78] | alphapca5[67] \ + | alpha64 | alpha64ev[4-8] | alpha64ev56 | alpha64ev6[78] | alpha64pca5[67] \ + | am33_2.0 \ + | arc | arceb \ + | arm | arm[bl]e | arme[lb] | armv[2-8] | armv[3-8][lb] | armv7[arm] \ + | avr | avr32 \ + | ba \ + | be32 | be64 \ + | bfin \ + | c4x | c8051 | clipper \ + | d10v | d30v | dlx | dsp16xx \ + | e2k | epiphany \ + | fido | fr30 | frv | ft32 \ + | h8300 | h8500 | hppa | hppa1.[01] | hppa2.0 | hppa2.0[nw] | hppa64 \ + | hexagon \ + | i370 | i860 | i960 | ia16 | ia64 \ + | ip2k | iq2000 \ + | k1om \ + | le32 | le64 \ + | lm32 \ + | m32c | m32r | m32rle | m68000 | m68k | m88k \ + | maxq | mb | microblaze | microblazeel | mcore | mep | metag \ + | mips | mipsbe | mipseb | mipsel | mipsle \ + | mips16 \ + | mips64 | mips64el \ + | mips64octeon | mips64octeonel \ + | mips64orion | mips64orionel \ + | mips64r5900 | mips64r5900el \ + | mips64vr | mips64vrel \ + | mips64vr4100 | mips64vr4100el \ + | mips64vr4300 | mips64vr4300el \ + | mips64vr5000 | mips64vr5000el \ + | mips64vr5900 | mips64vr5900el \ + | mipsisa32 | mipsisa32el \ + | mipsisa32r2 | mipsisa32r2el \ + | mipsisa32r6 | mipsisa32r6el \ + | mipsisa64 | mipsisa64el \ + | mipsisa64r2 | mipsisa64r2el \ + | mipsisa64r6 | mipsisa64r6el \ + | mipsisa64sb1 | mipsisa64sb1el \ + | mipsisa64sr71k | mipsisa64sr71kel \ + | mipsr5900 | mipsr5900el \ + | mipstx39 | mipstx39el \ + | mn10200 | mn10300 \ + | moxie \ + | mt \ + | msp430 \ + | nds32 | nds32le | nds32be \ + | nios | nios2 | nios2eb | nios2el \ + | ns16k | ns32k \ + | open8 | or1k | or1knd | or32 \ + | pdp10 | pj | pjl \ + | powerpc | powerpc64 | powerpc64le | powerpcle \ + | pru \ + | pyramid \ + | riscv32 | riscv64 \ + | rl78 | rx \ + | score \ + | sh | sh[1234] | sh[24]a | sh[24]aeb | sh[23]e | sh[234]eb | sheb | shbe | shle | sh[1234]le | sh3ele \ + | sh64 | sh64le \ + | sparc | sparc64 | sparc64b | sparc64v | sparc86x | sparclet | sparclite \ + | sparcv8 | sparcv9 | sparcv9b | sparcv9v \ + | spu \ + | tahoe | tic4x | tic54x | tic55x | tic6x | tic80 | tron \ + | ubicom32 \ + | v850 | v850e | v850e1 | v850e2 | v850es | v850e2v3 \ + | visium \ + | wasm32 \ + | x86 | xc16x | xstormy16 | xtensa \ + | z8k | z80) + basic_machine=$basic_machine-unknown + ;; + c54x) + basic_machine=tic54x-unknown + ;; + c55x) + basic_machine=tic55x-unknown + ;; + c6x) + basic_machine=tic6x-unknown + ;; + leon|leon[3-9]) + basic_machine=sparc-$basic_machine + ;; + m6811 | m68hc11 | m6812 | m68hc12 | m68hcs12x | nvptx | picochip) + basic_machine=$basic_machine-unknown + os=-none + ;; + m88110 | m680[12346]0 | m683?2 | m68360 | m5200 | v70 | w65) + ;; + ms1) + basic_machine=mt-unknown + ;; + + strongarm | thumb | xscale) + basic_machine=arm-unknown + ;; + xgate) + basic_machine=$basic_machine-unknown + os=-none + ;; + xscaleeb) + basic_machine=armeb-unknown + ;; + + xscaleel) + basic_machine=armel-unknown + ;; + + # We use `pc' rather than `unknown' + # because (1) that's what they normally are, and + # (2) the word "unknown" tends to confuse beginning users. + i*86 | x86_64) + basic_machine=$basic_machine-pc + ;; + # Object if more than one company name word. + *-*-*) + echo Invalid configuration \`"$1"\': machine \`"$basic_machine"\' not recognized 1>&2 + exit 1 + ;; + # Recognize the basic CPU types with company name. + 580-* \ + | a29k-* \ + | aarch64-* | aarch64_be-* \ + | alpha-* | alphaev[4-8]-* | alphaev56-* | alphaev6[78]-* \ + | alpha64-* | alpha64ev[4-8]-* | alpha64ev56-* | alpha64ev6[78]-* \ + | alphapca5[67]-* | alpha64pca5[67]-* | arc-* | arceb-* \ + | arm-* | armbe-* | armle-* | armeb-* | armv*-* \ + | avr-* | avr32-* \ + | ba-* \ + | be32-* | be64-* \ + | bfin-* | bs2000-* \ + | c[123]* | c30-* | [cjt]90-* | c4x-* \ + | c8051-* | clipper-* | craynv-* | cydra-* \ + | d10v-* | d30v-* | dlx-* \ + | e2k-* | elxsi-* \ + | f30[01]-* | f700-* | fido-* | fr30-* | frv-* | fx80-* \ + | h8300-* | h8500-* \ + | hppa-* | hppa1.[01]-* | hppa2.0-* | hppa2.0[nw]-* | hppa64-* \ + | hexagon-* \ + | i*86-* | i860-* | i960-* | ia16-* | ia64-* \ + | ip2k-* | iq2000-* \ + | k1om-* \ + | le32-* | le64-* \ + | lm32-* \ + | m32c-* | m32r-* | m32rle-* \ + | m68000-* | m680[012346]0-* | m68360-* | m683?2-* | m68k-* \ + | m88110-* | m88k-* | maxq-* | mcore-* | metag-* \ + | microblaze-* | microblazeel-* \ + | mips-* | mipsbe-* | mipseb-* | mipsel-* | mipsle-* \ + | mips16-* \ + | mips64-* | mips64el-* \ + | mips64octeon-* | mips64octeonel-* \ + | mips64orion-* | mips64orionel-* \ + | mips64r5900-* | mips64r5900el-* \ + | mips64vr-* | mips64vrel-* \ + | mips64vr4100-* | mips64vr4100el-* \ + | mips64vr4300-* | mips64vr4300el-* \ + | mips64vr5000-* | mips64vr5000el-* \ + | mips64vr5900-* | mips64vr5900el-* \ + | mipsisa32-* | mipsisa32el-* \ + | mipsisa32r2-* | mipsisa32r2el-* \ + | mipsisa32r6-* | mipsisa32r6el-* \ + | mipsisa64-* | mipsisa64el-* \ + | mipsisa64r2-* | mipsisa64r2el-* \ + | mipsisa64r6-* | mipsisa64r6el-* \ + | mipsisa64sb1-* | mipsisa64sb1el-* \ + | mipsisa64sr71k-* | mipsisa64sr71kel-* \ + | mipsr5900-* | mipsr5900el-* \ + | mipstx39-* | mipstx39el-* \ + | mmix-* \ + | mt-* \ + | msp430-* \ + | nds32-* | nds32le-* | nds32be-* \ + | nios-* | nios2-* | nios2eb-* | nios2el-* \ + | none-* | np1-* | ns16k-* | ns32k-* \ + | open8-* \ + | or1k*-* \ + | orion-* \ + | pdp10-* | pdp11-* | pj-* | pjl-* | pn-* | power-* \ + | powerpc-* | powerpc64-* | powerpc64le-* | powerpcle-* \ + | pru-* \ + | pyramid-* \ + | riscv32-* | riscv64-* \ + | rl78-* | romp-* | rs6000-* | rx-* \ + | sh-* | sh[1234]-* | sh[24]a-* | sh[24]aeb-* | sh[23]e-* | sh[34]eb-* | sheb-* | shbe-* \ + | shle-* | sh[1234]le-* | sh3ele-* | sh64-* | sh64le-* \ + | sparc-* | sparc64-* | sparc64b-* | sparc64v-* | sparc86x-* | sparclet-* \ + | sparclite-* \ + | sparcv8-* | sparcv9-* | sparcv9b-* | sparcv9v-* | sv1-* | sx*-* \ + | tahoe-* \ + | tic30-* | tic4x-* | tic54x-* | tic55x-* | tic6x-* | tic80-* \ + | tile*-* \ + | tron-* \ + | ubicom32-* \ + | v850-* | v850e-* | v850e1-* | v850es-* | v850e2-* | v850e2v3-* \ + | vax-* \ + | visium-* \ + | wasm32-* \ + | we32k-* \ + | x86-* | x86_64-* | xc16x-* | xps100-* \ + | xstormy16-* | xtensa*-* \ + | ymp-* \ + | z8k-* | z80-*) + ;; + # Recognize the basic CPU types without company name, with glob match. + xtensa*) + basic_machine=$basic_machine-unknown + ;; + # Recognize the various machine names and aliases which stand + # for a CPU type and a company and sometimes even an OS. + 386bsd) + basic_machine=i386-pc + os=-bsd + ;; + 3b1 | 7300 | 7300-att | att-7300 | pc7300 | safari | unixpc) + basic_machine=m68000-att + ;; + 3b*) + basic_machine=we32k-att + ;; + a29khif) + basic_machine=a29k-amd + os=-udi + ;; + abacus) + basic_machine=abacus-unknown + ;; + adobe68k) + basic_machine=m68010-adobe + os=-scout + ;; + alliant | fx80) + basic_machine=fx80-alliant + ;; + altos | altos3068) + basic_machine=m68k-altos + ;; + am29k) + basic_machine=a29k-none + os=-bsd + ;; + amd64) + basic_machine=x86_64-pc + ;; + amd64-*) + basic_machine=x86_64-`echo "$basic_machine" | sed 's/^[^-]*-//'` + ;; + amdahl) + basic_machine=580-amdahl + os=-sysv + ;; + amiga | amiga-*) + basic_machine=m68k-unknown + ;; + amigaos | amigados) + basic_machine=m68k-unknown + os=-amigaos + ;; + amigaunix | amix) + basic_machine=m68k-unknown + os=-sysv4 + ;; + apollo68) + basic_machine=m68k-apollo + os=-sysv + ;; + apollo68bsd) + basic_machine=m68k-apollo + os=-bsd + ;; + aros) + basic_machine=i386-pc + os=-aros + ;; + asmjs) + basic_machine=asmjs-unknown + ;; + aux) + basic_machine=m68k-apple + os=-aux + ;; + balance) + basic_machine=ns32k-sequent + os=-dynix + ;; + blackfin) + basic_machine=bfin-unknown + os=-linux + ;; + blackfin-*) + basic_machine=bfin-`echo "$basic_machine" | sed 's/^[^-]*-//'` + os=-linux + ;; + bluegene*) + basic_machine=powerpc-ibm + os=-cnk + ;; + c54x-*) + basic_machine=tic54x-`echo "$basic_machine" | sed 's/^[^-]*-//'` + ;; + c55x-*) + basic_machine=tic55x-`echo "$basic_machine" | sed 's/^[^-]*-//'` + ;; + c6x-*) + basic_machine=tic6x-`echo "$basic_machine" | sed 's/^[^-]*-//'` + ;; + c90) + basic_machine=c90-cray + os=-unicos + ;; + cegcc) + basic_machine=arm-unknown + os=-cegcc + ;; + convex-c1) + basic_machine=c1-convex + os=-bsd + ;; + convex-c2) + basic_machine=c2-convex + os=-bsd + ;; + convex-c32) + basic_machine=c32-convex + os=-bsd + ;; + convex-c34) + basic_machine=c34-convex + os=-bsd + ;; + convex-c38) + basic_machine=c38-convex + os=-bsd + ;; + cray | j90) + basic_machine=j90-cray + os=-unicos + ;; + craynv) + basic_machine=craynv-cray + os=-unicosmp + ;; + cr16 | cr16-*) + basic_machine=cr16-unknown + os=-elf + ;; + crds | unos) + basic_machine=m68k-crds + ;; + crisv32 | crisv32-* | etraxfs*) + basic_machine=crisv32-axis + ;; + cris | cris-* | etrax*) + basic_machine=cris-axis + ;; + crx) + basic_machine=crx-unknown + os=-elf + ;; + da30 | da30-*) + basic_machine=m68k-da30 + ;; + decstation | decstation-3100 | pmax | pmax-* | pmin | dec3100 | decstatn) + basic_machine=mips-dec + ;; + decsystem10* | dec10*) + basic_machine=pdp10-dec + os=-tops10 + ;; + decsystem20* | dec20*) + basic_machine=pdp10-dec + os=-tops20 + ;; + delta | 3300 | motorola-3300 | motorola-delta \ + | 3300-motorola | delta-motorola) + basic_machine=m68k-motorola + ;; + delta88) + basic_machine=m88k-motorola + os=-sysv3 + ;; + dicos) + basic_machine=i686-pc + os=-dicos + ;; + djgpp) + basic_machine=i586-pc + os=-msdosdjgpp + ;; + dpx20 | dpx20-*) + basic_machine=rs6000-bull + os=-bosx + ;; + dpx2*) + basic_machine=m68k-bull + os=-sysv3 + ;; + e500v[12]) + basic_machine=powerpc-unknown + os=$os"spe" + ;; + e500v[12]-*) + basic_machine=powerpc-`echo "$basic_machine" | sed 's/^[^-]*-//'` + os=$os"spe" + ;; + ebmon29k) + basic_machine=a29k-amd + os=-ebmon + ;; + elxsi) + basic_machine=elxsi-elxsi + os=-bsd + ;; + encore | umax | mmax) + basic_machine=ns32k-encore + ;; + es1800 | OSE68k | ose68k | ose | OSE) + basic_machine=m68k-ericsson + os=-ose + ;; + fx2800) + basic_machine=i860-alliant + ;; + genix) + basic_machine=ns32k-ns + ;; + gmicro) + basic_machine=tron-gmicro + os=-sysv + ;; + go32) + basic_machine=i386-pc + os=-go32 + ;; + h3050r* | hiux*) + basic_machine=hppa1.1-hitachi + os=-hiuxwe2 + ;; + h8300hms) + basic_machine=h8300-hitachi + os=-hms + ;; + h8300xray) + basic_machine=h8300-hitachi + os=-xray + ;; + h8500hms) + basic_machine=h8500-hitachi + os=-hms + ;; + harris) + basic_machine=m88k-harris + os=-sysv3 + ;; + hp300-*) + basic_machine=m68k-hp + ;; + hp300bsd) + basic_machine=m68k-hp + os=-bsd + ;; + hp300hpux) + basic_machine=m68k-hp + os=-hpux + ;; + hp3k9[0-9][0-9] | hp9[0-9][0-9]) + basic_machine=hppa1.0-hp + ;; + hp9k2[0-9][0-9] | hp9k31[0-9]) + basic_machine=m68000-hp + ;; + hp9k3[2-9][0-9]) + basic_machine=m68k-hp + ;; + hp9k6[0-9][0-9] | hp6[0-9][0-9]) + basic_machine=hppa1.0-hp + ;; + hp9k7[0-79][0-9] | hp7[0-79][0-9]) + basic_machine=hppa1.1-hp + ;; + hp9k78[0-9] | hp78[0-9]) + # FIXME: really hppa2.0-hp + basic_machine=hppa1.1-hp + ;; + hp9k8[67]1 | hp8[67]1 | hp9k80[24] | hp80[24] | hp9k8[78]9 | hp8[78]9 | hp9k893 | hp893) + # FIXME: really hppa2.0-hp + basic_machine=hppa1.1-hp + ;; + hp9k8[0-9][13679] | hp8[0-9][13679]) + basic_machine=hppa1.1-hp + ;; + hp9k8[0-9][0-9] | hp8[0-9][0-9]) + basic_machine=hppa1.0-hp + ;; + hppaosf) + basic_machine=hppa1.1-hp + os=-osf + ;; + hppro) + basic_machine=hppa1.1-hp + os=-proelf + ;; + i370-ibm* | ibm*) + basic_machine=i370-ibm + ;; + i*86v32) + basic_machine=`echo "$1" | sed -e 's/86.*/86-pc/'` + os=-sysv32 + ;; + i*86v4*) + basic_machine=`echo "$1" | sed -e 's/86.*/86-pc/'` + os=-sysv4 + ;; + i*86v) + basic_machine=`echo "$1" | sed -e 's/86.*/86-pc/'` + os=-sysv + ;; + i*86sol2) + basic_machine=`echo "$1" | sed -e 's/86.*/86-pc/'` + os=-solaris2 + ;; + i386mach) + basic_machine=i386-mach + os=-mach + ;; + vsta) + basic_machine=i386-unknown + os=-vsta + ;; + iris | iris4d) + basic_machine=mips-sgi + case $os in + -irix*) + ;; + *) + os=-irix4 + ;; + esac + ;; + isi68 | isi) + basic_machine=m68k-isi + os=-sysv + ;; + leon-*|leon[3-9]-*) + basic_machine=sparc-`echo "$basic_machine" | sed 's/-.*//'` + ;; + m68knommu) + basic_machine=m68k-unknown + os=-linux + ;; + m68knommu-*) + basic_machine=m68k-`echo "$basic_machine" | sed 's/^[^-]*-//'` + os=-linux + ;; + magnum | m3230) + basic_machine=mips-mips + os=-sysv + ;; + merlin) + basic_machine=ns32k-utek + os=-sysv + ;; + microblaze*) + basic_machine=microblaze-xilinx + ;; + mingw64) + basic_machine=x86_64-pc + os=-mingw64 + ;; + mingw32) + basic_machine=i686-pc + os=-mingw32 + ;; + mingw32ce) + basic_machine=arm-unknown + os=-mingw32ce + ;; + miniframe) + basic_machine=m68000-convergent + ;; + *mint | -mint[0-9]* | *MiNT | *MiNT[0-9]*) + basic_machine=m68k-atari + os=-mint + ;; + mips3*-*) + basic_machine=`echo "$basic_machine" | sed -e 's/mips3/mips64/'` + ;; + mips3*) + basic_machine=`echo "$basic_machine" | sed -e 's/mips3/mips64/'`-unknown + ;; + monitor) + basic_machine=m68k-rom68k + os=-coff + ;; + morphos) + basic_machine=powerpc-unknown + os=-morphos + ;; + moxiebox) + basic_machine=moxie-unknown + os=-moxiebox + ;; + msdos) + basic_machine=i386-pc + os=-msdos + ;; + ms1-*) + basic_machine=`echo "$basic_machine" | sed -e 's/ms1-/mt-/'` + ;; + msys) + basic_machine=i686-pc + os=-msys + ;; + mvs) + basic_machine=i370-ibm + os=-mvs + ;; + nacl) + basic_machine=le32-unknown + os=-nacl + ;; + ncr3000) + basic_machine=i486-ncr + os=-sysv4 + ;; + netbsd386) + basic_machine=i386-unknown + os=-netbsd + ;; + netwinder) + basic_machine=armv4l-rebel + os=-linux + ;; + news | news700 | news800 | news900) + basic_machine=m68k-sony + os=-newsos + ;; + news1000) + basic_machine=m68030-sony + os=-newsos + ;; + news-3600 | risc-news) + basic_machine=mips-sony + os=-newsos + ;; + necv70) + basic_machine=v70-nec + os=-sysv + ;; + next | m*-next) + basic_machine=m68k-next + case $os in + -nextstep* ) + ;; + -ns2*) + os=-nextstep2 + ;; + *) + os=-nextstep3 + ;; + esac + ;; + nh3000) + basic_machine=m68k-harris + os=-cxux + ;; + nh[45]000) + basic_machine=m88k-harris + os=-cxux + ;; + nindy960) + basic_machine=i960-intel + os=-nindy + ;; + mon960) + basic_machine=i960-intel + os=-mon960 + ;; + nonstopux) + basic_machine=mips-compaq + os=-nonstopux + ;; + np1) + basic_machine=np1-gould + ;; + neo-tandem) + basic_machine=neo-tandem + ;; + nse-tandem) + basic_machine=nse-tandem + ;; + nsr-tandem) + basic_machine=nsr-tandem + ;; + nsv-tandem) + basic_machine=nsv-tandem + ;; + nsx-tandem) + basic_machine=nsx-tandem + ;; + op50n-* | op60c-*) + basic_machine=hppa1.1-oki + os=-proelf + ;; + openrisc | openrisc-*) + basic_machine=or32-unknown + ;; + os400) + basic_machine=powerpc-ibm + os=-os400 + ;; + OSE68000 | ose68000) + basic_machine=m68000-ericsson + os=-ose + ;; + os68k) + basic_machine=m68k-none + os=-os68k + ;; + pa-hitachi) + basic_machine=hppa1.1-hitachi + os=-hiuxwe2 + ;; + paragon) + basic_machine=i860-intel + os=-osf + ;; + parisc) + basic_machine=hppa-unknown + os=-linux + ;; + parisc-*) + basic_machine=hppa-`echo "$basic_machine" | sed 's/^[^-]*-//'` + os=-linux + ;; + pbd) + basic_machine=sparc-tti + ;; + pbb) + basic_machine=m68k-tti + ;; + pc532 | pc532-*) + basic_machine=ns32k-pc532 + ;; + pc98) + basic_machine=i386-pc + ;; + pc98-*) + basic_machine=i386-`echo "$basic_machine" | sed 's/^[^-]*-//'` + ;; + pentium | p5 | k5 | k6 | nexgen | viac3) + basic_machine=i586-pc + ;; + pentiumpro | p6 | 6x86 | athlon | athlon_*) + basic_machine=i686-pc + ;; + pentiumii | pentium2 | pentiumiii | pentium3) + basic_machine=i686-pc + ;; + pentium4) + basic_machine=i786-pc + ;; + pentium-* | p5-* | k5-* | k6-* | nexgen-* | viac3-*) + basic_machine=i586-`echo "$basic_machine" | sed 's/^[^-]*-//'` + ;; + pentiumpro-* | p6-* | 6x86-* | athlon-*) + basic_machine=i686-`echo "$basic_machine" | sed 's/^[^-]*-//'` + ;; + pentiumii-* | pentium2-* | pentiumiii-* | pentium3-*) + basic_machine=i686-`echo "$basic_machine" | sed 's/^[^-]*-//'` + ;; + pentium4-*) + basic_machine=i786-`echo "$basic_machine" | sed 's/^[^-]*-//'` + ;; + pn) + basic_machine=pn-gould + ;; + power) basic_machine=power-ibm + ;; + ppc | ppcbe) basic_machine=powerpc-unknown + ;; + ppc-* | ppcbe-*) + basic_machine=powerpc-`echo "$basic_machine" | sed 's/^[^-]*-//'` + ;; + ppcle | powerpclittle) + basic_machine=powerpcle-unknown + ;; + ppcle-* | powerpclittle-*) + basic_machine=powerpcle-`echo "$basic_machine" | sed 's/^[^-]*-//'` + ;; + ppc64) basic_machine=powerpc64-unknown + ;; + ppc64-*) basic_machine=powerpc64-`echo "$basic_machine" | sed 's/^[^-]*-//'` + ;; + ppc64le | powerpc64little) + basic_machine=powerpc64le-unknown + ;; + ppc64le-* | powerpc64little-*) + basic_machine=powerpc64le-`echo "$basic_machine" | sed 's/^[^-]*-//'` + ;; + ps2) + basic_machine=i386-ibm + ;; + pw32) + basic_machine=i586-unknown + os=-pw32 + ;; + rdos | rdos64) + basic_machine=x86_64-pc + os=-rdos + ;; + rdos32) + basic_machine=i386-pc + os=-rdos + ;; + rom68k) + basic_machine=m68k-rom68k + os=-coff + ;; + rm[46]00) + basic_machine=mips-siemens + ;; + rtpc | rtpc-*) + basic_machine=romp-ibm + ;; + s390 | s390-*) + basic_machine=s390-ibm + ;; + s390x | s390x-*) + basic_machine=s390x-ibm + ;; + sa29200) + basic_machine=a29k-amd + os=-udi + ;; + sb1) + basic_machine=mipsisa64sb1-unknown + ;; + sb1el) + basic_machine=mipsisa64sb1el-unknown + ;; + sde) + basic_machine=mipsisa32-sde + os=-elf + ;; + sei) + basic_machine=mips-sei + os=-seiux + ;; + sequent) + basic_machine=i386-sequent + ;; + sh5el) + basic_machine=sh5le-unknown + ;; + simso-wrs) + basic_machine=sparclite-wrs + os=-vxworks + ;; + sps7) + basic_machine=m68k-bull + os=-sysv2 + ;; + spur) + basic_machine=spur-unknown + ;; + st2000) + basic_machine=m68k-tandem + ;; + stratus) + basic_machine=i860-stratus + os=-sysv4 + ;; + strongarm-* | thumb-*) + basic_machine=arm-`echo "$basic_machine" | sed 's/^[^-]*-//'` + ;; + sun2) + basic_machine=m68000-sun + ;; + sun2os3) + basic_machine=m68000-sun + os=-sunos3 + ;; + sun2os4) + basic_machine=m68000-sun + os=-sunos4 + ;; + sun3os3) + basic_machine=m68k-sun + os=-sunos3 + ;; + sun3os4) + basic_machine=m68k-sun + os=-sunos4 + ;; + sun4os3) + basic_machine=sparc-sun + os=-sunos3 + ;; + sun4os4) + basic_machine=sparc-sun + os=-sunos4 + ;; + sun4sol2) + basic_machine=sparc-sun + os=-solaris2 + ;; + sun3 | sun3-*) + basic_machine=m68k-sun + ;; + sun4) + basic_machine=sparc-sun + ;; + sun386 | sun386i | roadrunner) + basic_machine=i386-sun + ;; + sv1) + basic_machine=sv1-cray + os=-unicos + ;; + symmetry) + basic_machine=i386-sequent + os=-dynix + ;; + t3e) + basic_machine=alphaev5-cray + os=-unicos + ;; + t90) + basic_machine=t90-cray + os=-unicos + ;; + tile*) + basic_machine=$basic_machine-unknown + os=-linux-gnu + ;; + tx39) + basic_machine=mipstx39-unknown + ;; + tx39el) + basic_machine=mipstx39el-unknown + ;; + toad1) + basic_machine=pdp10-xkl + os=-tops20 + ;; + tower | tower-32) + basic_machine=m68k-ncr + ;; + tpf) + basic_machine=s390x-ibm + os=-tpf + ;; + udi29k) + basic_machine=a29k-amd + os=-udi + ;; + ultra3) + basic_machine=a29k-nyu + os=-sym1 + ;; + v810 | necv810) + basic_machine=v810-nec + os=-none + ;; + vaxv) + basic_machine=vax-dec + os=-sysv + ;; + vms) + basic_machine=vax-dec + os=-vms + ;; + vpp*|vx|vx-*) + basic_machine=f301-fujitsu + ;; + vxworks960) + basic_machine=i960-wrs + os=-vxworks + ;; + vxworks68) + basic_machine=m68k-wrs + os=-vxworks + ;; + vxworks29k) + basic_machine=a29k-wrs + os=-vxworks + ;; + w65*) + basic_machine=w65-wdc + os=-none + ;; + w89k-*) + basic_machine=hppa1.1-winbond + os=-proelf + ;; + x64) + basic_machine=x86_64-pc + ;; + xbox) + basic_machine=i686-pc + os=-mingw32 + ;; + xps | xps100) + basic_machine=xps100-honeywell + ;; + xscale-* | xscalee[bl]-*) + basic_machine=`echo "$basic_machine" | sed 's/^xscale/arm/'` + ;; + ymp) + basic_machine=ymp-cray + os=-unicos + ;; + none) + basic_machine=none-none + os=-none + ;; + +# Here we handle the default manufacturer of certain CPU types. It is in +# some cases the only manufacturer, in others, it is the most popular. + w89k) + basic_machine=hppa1.1-winbond + ;; + op50n) + basic_machine=hppa1.1-oki + ;; + op60c) + basic_machine=hppa1.1-oki + ;; + romp) + basic_machine=romp-ibm + ;; + mmix) + basic_machine=mmix-knuth + ;; + rs6000) + basic_machine=rs6000-ibm + ;; + vax) + basic_machine=vax-dec + ;; + pdp11) + basic_machine=pdp11-dec + ;; + we32k) + basic_machine=we32k-att + ;; + sh[1234] | sh[24]a | sh[24]aeb | sh[34]eb | sh[1234]le | sh[23]ele) + basic_machine=sh-unknown + ;; + cydra) + basic_machine=cydra-cydrome + ;; + orion) + basic_machine=orion-highlevel + ;; + orion105) + basic_machine=clipper-highlevel + ;; + mac | mpw | mac-mpw) + basic_machine=m68k-apple + ;; + pmac | pmac-mpw) + basic_machine=powerpc-apple + ;; + *-unknown) + # Make sure to match an already-canonicalized machine name. + ;; + *) + echo Invalid configuration \`"$1"\': machine \`"$basic_machine"\' not recognized 1>&2 + exit 1 + ;; +esac + +# Here we canonicalize certain aliases for manufacturers. +case $basic_machine in + *-digital*) + basic_machine=`echo "$basic_machine" | sed 's/digital.*/dec/'` + ;; + *-commodore*) + basic_machine=`echo "$basic_machine" | sed 's/commodore.*/cbm/'` + ;; + *) + ;; +esac + +# Decode manufacturer-specific aliases for certain operating systems. + +if [ x"$os" != x"" ] +then +case $os in + # First match some system type aliases that might get confused + # with valid system types. + # -solaris* is a basic system type, with this one exception. + -auroraux) + os=-auroraux + ;; + -solaris1 | -solaris1.*) + os=`echo $os | sed -e 's|solaris1|sunos4|'` + ;; + -solaris) + os=-solaris2 + ;; + -unixware*) + os=-sysv4.2uw + ;; + -gnu/linux*) + os=`echo $os | sed -e 's|gnu/linux|linux-gnu|'` + ;; + # es1800 is here to avoid being matched by es* (a different OS) + -es1800*) + os=-ose + ;; + # Now accept the basic system types. + # The portable systems comes first. + # Each alternative MUST end in a * to match a version number. + # -sysv* is not here because it comes later, after sysvr4. + -gnu* | -bsd* | -mach* | -minix* | -genix* | -ultrix* | -irix* \ + | -*vms* | -sco* | -esix* | -isc* | -aix* | -cnk* | -sunos | -sunos[34]*\ + | -hpux* | -unos* | -osf* | -luna* | -dgux* | -auroraux* | -solaris* \ + | -sym* | -kopensolaris* | -plan9* \ + | -amigaos* | -amigados* | -msdos* | -newsos* | -unicos* | -aof* \ + | -aos* | -aros* | -cloudabi* | -sortix* \ + | -nindy* | -vxsim* | -vxworks* | -ebmon* | -hms* | -mvs* \ + | -clix* | -riscos* | -uniplus* | -iris* | -rtu* | -xenix* \ + | -hiux* | -knetbsd* | -mirbsd* | -netbsd* \ + | -bitrig* | -openbsd* | -solidbsd* | -libertybsd* \ + | -ekkobsd* | -kfreebsd* | -freebsd* | -riscix* | -lynxos* \ + | -bosx* | -nextstep* | -cxux* | -aout* | -elf* | -oabi* \ + | -ptx* | -coff* | -ecoff* | -winnt* | -domain* | -vsta* \ + | -udi* | -eabi* | -lites* | -ieee* | -go32* | -aux* | -hcos* \ + | -chorusos* | -chorusrdb* | -cegcc* | -glidix* \ + | -cygwin* | -msys* | -pe* | -psos* | -moss* | -proelf* | -rtems* \ + | -midipix* | -mingw32* | -mingw64* | -linux-gnu* | -linux-android* \ + | -linux-newlib* | -linux-musl* | -linux-uclibc* \ + | -uxpv* | -beos* | -mpeix* | -udk* | -moxiebox* \ + | -interix* | -uwin* | -mks* | -rhapsody* | -darwin* \ + | -openstep* | -oskit* | -conix* | -pw32* | -nonstopux* \ + | -storm-chaos* | -tops10* | -tenex* | -tops20* | -its* \ + | -os2* | -vos* | -palmos* | -uclinux* | -nucleus* \ + | -morphos* | -superux* | -rtmk* | -windiss* \ + | -powermax* | -dnix* | -nx6 | -nx7 | -sei* | -dragonfly* \ + | -skyos* | -haiku* | -rdos* | -toppers* | -drops* | -es* \ + | -onefs* | -tirtos* | -phoenix* | -fuchsia* | -redox* | -bme* \ + | -midnightbsd*) + # Remember, each alternative MUST END IN *, to match a version number. + ;; + -qnx*) + case $basic_machine in + x86-* | i*86-*) + ;; + *) + os=-nto$os + ;; + esac + ;; + -nto-qnx*) + ;; + -nto*) + os=`echo $os | sed -e 's|nto|nto-qnx|'` + ;; + -sim | -xray | -os68k* | -v88r* \ + | -windows* | -osx | -abug | -netware* | -os9* \ + | -macos* | -mpw* | -magic* | -mmixware* | -mon960* | -lnews*) + ;; + -mac*) + os=`echo "$os" | sed -e 's|mac|macos|'` + ;; + -linux-dietlibc) + os=-linux-dietlibc + ;; + -linux*) + os=`echo $os | sed -e 's|linux|linux-gnu|'` + ;; + -sunos5*) + os=`echo "$os" | sed -e 's|sunos5|solaris2|'` + ;; + -sunos6*) + os=`echo "$os" | sed -e 's|sunos6|solaris3|'` + ;; + -opened*) + os=-openedition + ;; + -os400*) + os=-os400 + ;; + -wince*) + os=-wince + ;; + -utek*) + os=-bsd + ;; + -dynix*) + os=-bsd + ;; + -acis*) + os=-aos + ;; + -atheos*) + os=-atheos + ;; + -syllable*) + os=-syllable + ;; + -386bsd) + os=-bsd + ;; + -ctix* | -uts*) + os=-sysv + ;; + -nova*) + os=-rtmk-nova + ;; + -ns2) + os=-nextstep2 + ;; + -nsk*) + os=-nsk + ;; + # Preserve the version number of sinix5. + -sinix5.*) + os=`echo $os | sed -e 's|sinix|sysv|'` + ;; + -sinix*) + os=-sysv4 + ;; + -tpf*) + os=-tpf + ;; + -triton*) + os=-sysv3 + ;; + -oss*) + os=-sysv3 + ;; + -svr4*) + os=-sysv4 + ;; + -svr3) + os=-sysv3 + ;; + -sysvr4) + os=-sysv4 + ;; + # This must come after -sysvr4. + -sysv*) + ;; + -ose*) + os=-ose + ;; + -*mint | -mint[0-9]* | -*MiNT | -MiNT[0-9]*) + os=-mint + ;; + -zvmoe) + os=-zvmoe + ;; + -dicos*) + os=-dicos + ;; + -pikeos*) + # Until real need of OS specific support for + # particular features comes up, bare metal + # configurations are quite functional. + case $basic_machine in + arm*) + os=-eabi + ;; + *) + os=-elf + ;; + esac + ;; + -nacl*) + ;; + -ios) + ;; + -none) + ;; + *) + # Get rid of the `-' at the beginning of $os. + os=`echo $os | sed 's/[^-]*-//'` + echo Invalid configuration \`"$1"\': system \`"$os"\' not recognized 1>&2 + exit 1 + ;; +esac +else + +# Here we handle the default operating systems that come with various machines. +# The value should be what the vendor currently ships out the door with their +# machine or put another way, the most popular os provided with the machine. + +# Note that if you're going to try to match "-MANUFACTURER" here (say, +# "-sun"), then you have to tell the case statement up towards the top +# that MANUFACTURER isn't an operating system. Otherwise, code above +# will signal an error saying that MANUFACTURER isn't an operating +# system, and we'll never get to this point. + +case $basic_machine in + score-*) + os=-elf + ;; + spu-*) + os=-elf + ;; + *-acorn) + os=-riscix1.2 + ;; + arm*-rebel) + os=-linux + ;; + arm*-semi) + os=-aout + ;; + c4x-* | tic4x-*) + os=-coff + ;; + c8051-*) + os=-elf + ;; + hexagon-*) + os=-elf + ;; + tic54x-*) + os=-coff + ;; + tic55x-*) + os=-coff + ;; + tic6x-*) + os=-coff + ;; + # This must come before the *-dec entry. + pdp10-*) + os=-tops20 + ;; + pdp11-*) + os=-none + ;; + *-dec | vax-*) + os=-ultrix4.2 + ;; + m68*-apollo) + os=-domain + ;; + i386-sun) + os=-sunos4.0.2 + ;; + m68000-sun) + os=-sunos3 + ;; + m68*-cisco) + os=-aout + ;; + mep-*) + os=-elf + ;; + mips*-cisco) + os=-elf + ;; + mips*-*) + os=-elf + ;; + or32-*) + os=-coff + ;; + *-tti) # must be before sparc entry or we get the wrong os. + os=-sysv3 + ;; + sparc-* | *-sun) + os=-sunos4.1.1 + ;; + pru-*) + os=-elf + ;; + *-be) + os=-beos + ;; + *-ibm) + os=-aix + ;; + *-knuth) + os=-mmixware + ;; + *-wec) + os=-proelf + ;; + *-winbond) + os=-proelf + ;; + *-oki) + os=-proelf + ;; + *-hp) + os=-hpux + ;; + *-hitachi) + os=-hiux + ;; + i860-* | *-att | *-ncr | *-altos | *-motorola | *-convergent) + os=-sysv + ;; + *-cbm) + os=-amigaos + ;; + *-dg) + os=-dgux + ;; + *-dolphin) + os=-sysv3 + ;; + m68k-ccur) + os=-rtu + ;; + m88k-omron*) + os=-luna + ;; + *-next) + os=-nextstep + ;; + *-sequent) + os=-ptx + ;; + *-crds) + os=-unos + ;; + *-ns) + os=-genix + ;; + i370-*) + os=-mvs + ;; + *-gould) + os=-sysv + ;; + *-highlevel) + os=-bsd + ;; + *-encore) + os=-bsd + ;; + *-sgi) + os=-irix + ;; + *-siemens) + os=-sysv4 + ;; + *-masscomp) + os=-rtu + ;; + f30[01]-fujitsu | f700-fujitsu) + os=-uxpv + ;; + *-rom68k) + os=-coff + ;; + *-*bug) + os=-coff + ;; + *-apple) + os=-macos + ;; + *-atari*) + os=-mint + ;; + *) + os=-none + ;; +esac +fi + +# Here we handle the case where we know the os, and the CPU type, but not the +# manufacturer. We pick the logical manufacturer. +vendor=unknown +case $basic_machine in + *-unknown) + case $os in + -riscix*) + vendor=acorn + ;; + -sunos*) + vendor=sun + ;; + -cnk*|-aix*) + vendor=ibm + ;; + -beos*) + vendor=be + ;; + -hpux*) + vendor=hp + ;; + -mpeix*) + vendor=hp + ;; + -hiux*) + vendor=hitachi + ;; + -unos*) + vendor=crds + ;; + -dgux*) + vendor=dg + ;; + -luna*) + vendor=omron + ;; + -genix*) + vendor=ns + ;; + -mvs* | -opened*) + vendor=ibm + ;; + -os400*) + vendor=ibm + ;; + -ptx*) + vendor=sequent + ;; + -tpf*) + vendor=ibm + ;; + -vxsim* | -vxworks* | -windiss*) + vendor=wrs + ;; + -aux*) + vendor=apple + ;; + -hms*) + vendor=hitachi + ;; + -mpw* | -macos*) + vendor=apple + ;; + -*mint | -mint[0-9]* | -*MiNT | -MiNT[0-9]*) + vendor=atari + ;; + -vos*) + vendor=stratus + ;; + esac + basic_machine=`echo "$basic_machine" | sed "s/unknown/$vendor/"` + ;; +esac + +echo "$basic_machine$os" +exit + +# Local variables: +# eval: (add-hook 'before-save-hook 'time-stamp) +# time-stamp-start: "timestamp='" +# time-stamp-format: "%:y-%02m-%02d" +# time-stamp-end: "'" +# End: ADDED autosetup/autosetup-find-tclsh Index: autosetup/autosetup-find-tclsh ================================================================== --- /dev/null +++ autosetup/autosetup-find-tclsh @@ -0,0 +1,17 @@ +#!/bin/sh +# Looks for a suitable tclsh or jimsh in the PATH +# If not found, builds a bootstrap jimsh from source +# Prefer $autosetup_tclsh if is set in the environment +d=`dirname "$0"` +{ "$d/jimsh0" "$d/autosetup-test-tclsh"; } 2>/dev/null && exit 0 +PATH="$PATH:$d"; export PATH +for tclsh in $autosetup_tclsh jimsh tclsh tclsh8.5 tclsh8.6; do + { $tclsh "$d/autosetup-test-tclsh"; } 2>/dev/null && exit 0 +done +echo 1>&2 "No installed jimsh or tclsh, building local bootstrap jimsh0" +for cc in ${CC_FOR_BUILD:-cc} gcc; do + { $cc -o "$d/jimsh0" "$d/jimsh0.c"; } 2>/dev/null || continue + "$d/jimsh0" "$d/autosetup-test-tclsh" && exit 0 +done +echo 1>&2 "No working C compiler found. Tried ${CC_FOR_BUILD:-cc} and gcc." +echo false ADDED autosetup/autosetup-test-tclsh Index: autosetup/autosetup-test-tclsh ================================================================== --- /dev/null +++ autosetup/autosetup-test-tclsh @@ -0,0 +1,20 @@ +# A small Tcl script to verify that the chosen +# interpreter works. Sometimes we might e.g. pick up +# an interpreter for a different arch. +# Outputs the full path to the interpreter + +if {[catch {info version} version] == 0} { + # This is Jim Tcl + if {$version >= 0.72} { + # Ensure that regexp works + regexp (a.*?) a + puts [info nameofexecutable] + exit 0 + } +} elseif {[catch {info tclversion} version] == 0} { + if {$version >= 8.5 && ![string match 8.5a* [info patchlevel]]} { + puts [info nameofexecutable] + exit 0 + } +} +exit 1 ADDED autosetup/cc-db.tcl Index: autosetup/cc-db.tcl ================================================================== --- /dev/null +++ autosetup/cc-db.tcl @@ -0,0 +1,15 @@ +# Copyright (c) 2011 WorkWare Systems http://www.workware.net.au/ +# All rights reserved + +# @synopsis: +# +# The 'cc-db' module provides a knowledge-base of system idiosyncrasies. +# In general, this module can always be included. + +use cc + +module-options {} + +# openbsd needs sys/types.h to detect some system headers +cc-include-needs sys/socket.h sys/types.h +cc-include-needs netinet/in.h sys/types.h ADDED autosetup/cc-lib.tcl Index: autosetup/cc-lib.tcl ================================================================== --- /dev/null +++ autosetup/cc-lib.tcl @@ -0,0 +1,189 @@ +# Copyright (c) 2011 WorkWare Systems http://www.workware.net.au/ +# All rights reserved + +# @synopsis: +# +# Provides a library of common tests on top of the 'cc' module. + +use cc + +module-options {} + +# @cc-check-lfs +# +# The equivalent of the 'AC_SYS_LARGEFILE' macro. +# +# defines 'HAVE_LFS' if LFS is available, +# and defines '_FILE_OFFSET_BITS=64' if necessary +# +# Returns 1 if 'LFS' is available or 0 otherwise +# +proc cc-check-lfs {} { + cc-check-includes sys/types.h + msg-checking "Checking if -D_FILE_OFFSET_BITS=64 is needed..." + set lfs 1 + if {[msg-quiet cc-with {-includes sys/types.h} {cc-check-sizeof off_t}] == 8} { + msg-result no + } elseif {[msg-quiet cc-with {-includes sys/types.h -cflags -D_FILE_OFFSET_BITS=64} {cc-check-sizeof off_t}] == 8} { + define _FILE_OFFSET_BITS 64 + msg-result yes + } else { + set lfs 0 + msg-result none + } + define-feature lfs $lfs + return $lfs +} + +# @cc-check-endian +# +# The equivalent of the 'AC_C_BIGENDIAN' macro. +# +# defines 'HAVE_BIG_ENDIAN' if endian is known to be big, +# or 'HAVE_LITTLE_ENDIAN' if endian is known to be little. +# +# Returns 1 if determined, or 0 if not. +# +proc cc-check-endian {} { + cc-check-includes sys/types.h sys/param.h + set rc 0 + msg-checking "Checking endian..." + cc-with {-includes {sys/types.h sys/param.h}} { + if {[cctest -code { + #if !defined(BIG_ENDIAN) || !defined(BYTE_ORDER) + #error unknown + #elif BYTE_ORDER != BIG_ENDIAN + #error little + #endif + }]} { + define-feature big-endian + msg-result "big" + set rc 1 + } elseif {[cctest -code { + #if !defined(LITTLE_ENDIAN) || !defined(BYTE_ORDER) + #error unknown + #elif BYTE_ORDER != LITTLE_ENDIAN + #error big + #endif + }]} { + define-feature little-endian + msg-result "little" + set rc 1 + } else { + msg-result "unknown" + } + } + return $rc +} + +# @cc-check-flags flag ?...? +# +# Checks whether the given C/C++ compiler flags can be used. Defines feature +# names prefixed with 'HAVE_CFLAG' and 'HAVE_CXXFLAG' respectively, and +# appends working flags to '-cflags' and 'CFLAGS' or 'CXXFLAGS'. +proc cc-check-flags {args} { + set result 1 + array set opts [cc-get-settings] + switch -exact -- $opts(-lang) { + c++ { + set lang C++ + set prefix CXXFLAG + } + c { + set lang C + set prefix CFLAG + } + default { + autosetup-error "cc-check-flags failed with unknown language: $opts(-lang)" + } + } + foreach flag $args { + msg-checking "Checking whether the $lang compiler accepts $flag..." + if {[cctest -cflags $flag]} { + msg-result yes + define-feature $prefix$flag + cc-with [list -cflags [list $flag]] + define-append ${prefix}S $flag + } else { + msg-result no + set result 0 + } + } + return $result +} + +# @cc-check-standards ver ?...? +# +# Checks whether the C/C++ compiler accepts one of the specified '-std=$ver' +# options, and appends the first working one to '-cflags' and 'CFLAGS' or +# 'CXXFLAGS'. +proc cc-check-standards {args} { + array set opts [cc-get-settings] + foreach std $args { + if {[cc-check-flags -std=$std]} { + return $std + } + } + return "" +} + +# Checks whether $keyword is usable as alignof +proc cctest_alignof {keyword} { + msg-checking "Checking for $keyword..." + if {[cctest -code "int x = ${keyword}(char), y = ${keyword}('x');"]} then { + msg-result ok + define-feature $keyword + } else { + msg-result "not found" + } +} + +# @cc-check-c11 +# +# Checks for several C11/C++11 extensions and their alternatives. Currently +# checks for '_Static_assert', '_Alignof', '__alignof__', '__alignof'. +proc cc-check-c11 {} { + msg-checking "Checking for _Static_assert..." + if {[cctest -code { + _Static_assert(1, "static assertions are available"); + }]} then { + msg-result ok + define-feature _Static_assert + } else { + msg-result "not found" + } + + cctest_alignof _Alignof + cctest_alignof __alignof__ + cctest_alignof __alignof +} + +# @cc-check-alloca +# +# The equivalent of the 'AC_FUNC_ALLOCA' macro. +# +# Checks for the existence of 'alloca' +# defines 'HAVE_ALLOCA' and returns 1 if it exists. +proc cc-check-alloca {} { + cc-check-some-feature alloca { + cctest -includes alloca.h -code { alloca (2 * sizeof (int)); } + } +} + +# @cc-signal-return-type +# +# The equivalent of the 'AC_TYPE_SIGNAL' macro. +# +# defines 'RETSIGTYPE' to 'int' or 'void'. +proc cc-signal-return-type {} { + msg-checking "Checking return type of signal handlers..." + cc-with {-includes {sys/types.h signal.h}} { + if {[cctest -code {return *(signal (0, 0)) (0) == 1;}]} { + set type int + } else { + set type void + } + define RETSIGTYPE $type + msg-result $type + } +} ADDED autosetup/cc-shared.tcl Index: autosetup/cc-shared.tcl ================================================================== --- /dev/null +++ autosetup/cc-shared.tcl @@ -0,0 +1,113 @@ +# Copyright (c) 2010 WorkWare Systems http://www.workware.net.au/ +# All rights reserved + +# @synopsis: +# +# The 'cc-shared' module provides support for shared libraries and shared objects. +# It defines the following variables: +# +## SH_CFLAGS Flags to use compiling sources destined for a shared library +## SH_LDFLAGS Flags to use linking (creating) a shared library +## SH_SOPREFIX Prefix to use to set the soname when creating a shared library +## SH_SOFULLPATH Set to 1 if the shared library soname should include the full install path +## SH_SOEXT Extension for shared libs +## SH_SOEXTVER Format for versioned shared libs - %s = version +## SHOBJ_CFLAGS Flags to use compiling sources destined for a shared object +## SHOBJ_LDFLAGS Flags to use linking a shared object, undefined symbols allowed +## SHOBJ_LDFLAGS_R - as above, but all symbols must be resolved +## SH_LINKRPATH Format for setting the rpath when linking an executable, %s = path +## SH_LINKFLAGS Flags to use linking an executable which will load shared objects +## LD_LIBRARY_PATH Environment variable which specifies path to shared libraries +## STRIPLIBFLAGS Arguments to strip a dynamic library + +module-options {} + +# Defaults: gcc on unix +define SHOBJ_CFLAGS -fPIC +define SHOBJ_LDFLAGS -shared +define SH_CFLAGS -fPIC +define SH_LDFLAGS -shared +define SH_LINKFLAGS -rdynamic +define SH_LINKRPATH "-Wl,-rpath -Wl,%s" +define SH_SOEXT .so +define SH_SOEXTVER .so.%s +define SH_SOPREFIX -Wl,-soname, +define LD_LIBRARY_PATH LD_LIBRARY_PATH +define STRIPLIBFLAGS --strip-unneeded + +# Note: This is a helpful reference for identifying the toolchain +# http://sourceforge.net/apps/mediawiki/predef/index.php?title=Compilers + +switch -glob -- [get-define host] { + *-*-darwin* { + define SHOBJ_CFLAGS "-dynamic -fno-common" + define SHOBJ_LDFLAGS "-bundle -undefined dynamic_lookup" + define SHOBJ_LDFLAGS_R -bundle + define SH_CFLAGS -dynamic + define SH_LDFLAGS -dynamiclib + define SH_LINKFLAGS "" + define SH_SOEXT .dylib + define SH_SOEXTVER .%s.dylib + define SH_SOPREFIX -Wl,-install_name, + define SH_SOFULLPATH + define LD_LIBRARY_PATH DYLD_LIBRARY_PATH + define STRIPLIBFLAGS -x + } + *-*-ming* - *-*-cygwin - *-*-msys { + define SHOBJ_CFLAGS "" + define SHOBJ_LDFLAGS -shared + define SH_CFLAGS "" + define SH_LDFLAGS -shared + define SH_LINKRPATH "" + define SH_LINKFLAGS "" + define SH_SOEXT .dll + define SH_SOEXTVER .dll + define SH_SOPREFIX "" + define LD_LIBRARY_PATH PATH + } + sparc* { + if {[msg-quiet cc-check-decls __SUNPRO_C]} { + msg-result "Found sun stdio compiler" + # sun stdio compiler + # XXX: These haven't been fully tested. + define SHOBJ_CFLAGS -KPIC + define SHOBJ_LDFLAGS "-G" + define SH_CFLAGS -KPIC + define SH_LINKFLAGS -Wl,-export-dynamic + define SH_SOPREFIX -Wl,-h, + } + } + *-*-solaris* { + if {[msg-quiet cc-check-decls __SUNPRO_C]} { + msg-result "Found sun stdio compiler" + # sun stdio compiler + # XXX: These haven't been fully tested. + define SHOBJ_CFLAGS -KPIC + define SHOBJ_LDFLAGS "-G" + define SH_CFLAGS -KPIC + define SH_LINKFLAGS -Wl,-export-dynamic + define SH_SOPREFIX -Wl,-h, + } + } + *-*-hpux { + # XXX: These haven't been tested + define SHOBJ_CFLAGS "+O3 +z" + define SHOBJ_LDFLAGS -b + define SH_CFLAGS +z + define SH_LINKFLAGS -Wl,+s + define LD_LIBRARY_PATH SHLIB_PATH + } + *-*-haiku { + define SHOBJ_CFLAGS "" + define SHOBJ_LDFLAGS -shared + define SH_CFLAGS "" + define SH_LDFLAGS -shared + define SH_LINKFLAGS "" + define SH_SOPREFIX "" + define LD_LIBRARY_PATH LIBRARY_PATH + } +} + +if {![is-defined SHOBJ_LDFLAGS_R]} { + define SHOBJ_LDFLAGS_R [get-define SHOBJ_LDFLAGS] +} ADDED autosetup/cc.tcl Index: autosetup/cc.tcl ================================================================== --- /dev/null +++ autosetup/cc.tcl @@ -0,0 +1,733 @@ +# Copyright (c) 2010 WorkWare Systems http://www.workware.net.au/ +# All rights reserved + +# @synopsis: +# +# The 'cc' module supports checking various 'features' of the C or C++ +# compiler/linker environment. Common commands are 'cc-check-includes', +# 'cc-check-types', 'cc-check-functions', 'cc-with', 'make-config-header' and 'make-template'. +# +# The following environment variables are used if set: +# +## CC - C compiler +## CXX - C++ compiler +## CPP - C preprocessor +## CCACHE - Set to "none" to disable automatic use of ccache +## CFLAGS - Additional C compiler flags +## CXXFLAGS - Additional C++ compiler flags +## LDFLAGS - Additional compiler flags during linking +## LIBS - Additional libraries to use (for all tests) +## CROSS - Tool prefix for cross compilation +# +# The following variables are defined from the corresponding +# environment variables if set. +# +## CPPFLAGS +## LINKFLAGS +## CC_FOR_BUILD +## LD + +use system + +module-options {} + +# Checks for the existence of the given function by linking +# +proc cctest_function {function} { + cctest -link 1 -declare "extern void $function\(void);" -code "$function\();" +} + +# Checks for the existence of the given type by compiling +proc cctest_type {type} { + cctest -code "$type _x;" +} + +# Checks for the existence of the given type/structure member. +# e.g. "struct stat.st_mtime" +proc cctest_member {struct_member} { + # split at the first dot + regexp {^([^.]+)[.](.*)$} $struct_member -> struct member + cctest -code "static $struct _s; return sizeof(_s.$member);" +} + +# Checks for the existence of the given define by compiling +# +proc cctest_define {name} { + cctest -code "#ifndef $name\n#error not defined\n#endif" +} + +# Checks for the existence of the given name either as +# a macro (#define) or an rvalue (such as an enum) +# +proc cctest_decl {name} { + cctest -code "#ifndef $name\n(void)$name;\n#endif" +} + +# @cc-check-sizeof type ... +# +# Checks the size of the given types (between 1 and 32, inclusive). +# Defines a variable with the size determined, or 'unknown' otherwise. +# e.g. for type 'long long', defines 'SIZEOF_LONG_LONG'. +# Returns the size of the last type. +# +proc cc-check-sizeof {args} { + foreach type $args { + msg-checking "Checking for sizeof $type..." + set size unknown + # Try the most common sizes first + foreach i {4 8 1 2 16 32} { + if {[cctest -code "static int _x\[sizeof($type) == $i ? 1 : -1\] = { 1 };"]} { + set size $i + break + } + } + msg-result $size + set define [feature-define-name $type SIZEOF_] + define $define $size + } + # Return the last result + get-define $define +} + +# Checks for each feature in $list by using the given script. +# +# When the script is evaluated, $each is set to the feature +# being checked, and $extra is set to any additional cctest args. +# +# Returns 1 if all features were found, or 0 otherwise. +proc cc-check-some-feature {list script} { + set ret 1 + foreach each $list { + if {![check-feature $each $script]} { + set ret 0 + } + } + return $ret +} + +# @cc-check-includes includes ... +# +# Checks that the given include files can be used. +proc cc-check-includes {args} { + cc-check-some-feature $args { + set with {} + if {[dict exists $::autosetup(cc-include-deps) $each]} { + set deps [dict keys [dict get $::autosetup(cc-include-deps) $each]] + msg-quiet cc-check-includes {*}$deps + foreach i $deps { + if {[have-feature $i]} { + lappend with $i + } + } + } + if {[llength $with]} { + cc-with [list -includes $with] { + cctest -includes $each + } + } else { + cctest -includes $each + } + } +} + +# @cc-include-needs include required ... +# +# Ensures that when checking for '$include', a check is first +# made for each '$required' file, and if found, it is included with '#include'. +proc cc-include-needs {file args} { + foreach depfile $args { + dict set ::autosetup(cc-include-deps) $file $depfile 1 + } +} + +# @cc-check-types type ... +# +# Checks that the types exist. +proc cc-check-types {args} { + cc-check-some-feature $args { + cctest_type $each + } +} + +# @cc-check-defines define ... +# +# Checks that the given preprocessor symbols are defined. +proc cc-check-defines {args} { + cc-check-some-feature $args { + cctest_define $each + } +} + +# @cc-check-decls name ... +# +# Checks that each given name is either a preprocessor symbol or rvalue +# such as an enum. Note that the define used is 'HAVE_DECL_xxx' +# rather than 'HAVE_xxx'. +proc cc-check-decls {args} { + set ret 1 + foreach name $args { + msg-checking "Checking for $name..." + set r [cctest_decl $name] + define-feature "decl $name" $r + if {$r} { + msg-result "ok" + } else { + msg-result "not found" + set ret 0 + } + } + return $ret +} + +# @cc-check-functions function ... +# +# Checks that the given functions exist (can be linked). +proc cc-check-functions {args} { + cc-check-some-feature $args { + cctest_function $each + } +} + +# @cc-check-members type.member ... +# +# Checks that the given type/structure members exist. +# A structure member is of the form 'struct stat.st_mtime'. +proc cc-check-members {args} { + cc-check-some-feature $args { + cctest_member $each + } +} + +# @cc-check-function-in-lib function libs ?otherlibs? +# +# Checks that the given function can be found in one of the libs. +# +# First checks for no library required, then checks each of the libraries +# in turn. +# +# If the function is found, the feature is defined and 'lib_$function' is defined +# to '-l$lib' where the function was found, or "" if no library required. +# In addition, '-l$lib' is prepended to the 'LIBS' define. +# +# If additional libraries may be needed for linking, they should be specified +# with '$extralibs' as '-lotherlib1 -lotherlib2'. +# These libraries are not automatically added to 'LIBS'. +# +# Returns 1 if found or 0 if not. +# +proc cc-check-function-in-lib {function libs {otherlibs {}}} { + msg-checking "Checking libs for $function..." + set found 0 + cc-with [list -libs $otherlibs] { + if {[cctest_function $function]} { + msg-result "none needed" + define lib_$function "" + incr found + } else { + foreach lib $libs { + cc-with [list -libs -l$lib] { + if {[cctest_function $function]} { + msg-result -l$lib + define lib_$function -l$lib + # prepend to LIBS + define LIBS "-l$lib [get-define LIBS]" + incr found + break + } + } + } + } + } + define-feature $function $found + if {!$found} { + msg-result "no" + } + return $found +} + +# @cc-check-tools tool ... +# +# Checks for existence of the given compiler tools, taking +# into account any cross compilation prefix. +# +# For example, when checking for 'ar', first 'AR' is checked on the command +# line and then in the environment. If not found, '${host}-ar' or +# simply 'ar' is assumed depending upon whether cross compiling. +# The path is searched for this executable, and if found 'AR' is defined +# to the executable name. +# Note that even when cross compiling, the simple 'ar' is used as a fallback, +# but a warning is generated. This is necessary for some toolchains. +# +# It is an error if the executable is not found. +# +proc cc-check-tools {args} { + foreach tool $args { + set TOOL [string toupper $tool] + set exe [get-env $TOOL [get-define cross]$tool] + if {[find-executable {*}$exe]} { + define $TOOL $exe + continue + } + if {[find-executable {*}$tool]} { + msg-result "Warning: Failed to find $exe, falling back to $tool which may be incorrect" + define $TOOL $tool + continue + } + user-error "Failed to find $exe" + } +} + +# @cc-check-progs prog ... +# +# Checks for existence of the given executables on the path. +# +# For example, when checking for 'grep', the path is searched for +# the executable, 'grep', and if found 'GREP' is defined as 'grep'. +# +# If the executable is not found, the variable is defined as 'false'. +# Returns 1 if all programs were found, or 0 otherwise. +# +proc cc-check-progs {args} { + set failed 0 + foreach prog $args { + set PROG [string toupper $prog] + msg-checking "Checking for $prog..." + if {![find-executable $prog]} { + msg-result no + define $PROG false + incr failed + } else { + msg-result ok + define $PROG $prog + } + } + expr {!$failed} +} + +# @cc-path-progs prog ... +# +# Like cc-check-progs, but sets the define to the full path rather +# than just the program name. +# +proc cc-path-progs {args} { + set failed 0 + foreach prog $args { + set PROG [string toupper $prog] + msg-checking "Checking for $prog..." + set path [find-executable-path $prog] + if {$path eq ""} { + msg-result no + define $PROG false + incr failed + } else { + msg-result $path + define $PROG $path + } + } + expr {!$failed} +} + +# Adds the given settings to $::autosetup(ccsettings) and +# returns the old settings. +# +proc cc-add-settings {settings} { + if {[llength $settings] % 2} { + autosetup-error "settings list is missing a value: $settings" + } + + set prev [cc-get-settings] + # workaround a bug in some versions of jimsh by forcing + # conversion of $prev to a list + llength $prev + + array set new $prev + + foreach {name value} $settings { + switch -exact -- $name { + -cflags - -includes { + # These are given as lists + lappend new($name) {*}[list-non-empty $value] + } + -declare { + lappend new($name) $value + } + -libs { + # Note that new libraries are added before previous libraries + set new($name) [list {*}[list-non-empty $value] {*}$new($name)] + } + -link - -lang - -nooutput { + set new($name) $value + } + -source - -sourcefile - -code { + # XXX: These probably are only valid directly from cctest + set new($name) $value + } + default { + autosetup-error "unknown cctest setting: $name" + } + } + } + + cc-store-settings [array get new] + + return $prev +} + +proc cc-store-settings {new} { + set ::autosetup(ccsettings) $new +} + +proc cc-get-settings {} { + return $::autosetup(ccsettings) +} + +# Similar to cc-add-settings, but each given setting +# simply replaces the existing value. +# +# Returns the previous settings +proc cc-update-settings {args} { + set prev [cc-get-settings] + cc-store-settings [dict merge $prev $args] + return $prev +} + +# @cc-with settings ?{ script }? +# +# Sets the given 'cctest' settings and then runs the tests in '$script'. +# Note that settings such as '-lang' replace the current setting, while +# those such as '-includes' are appended to the existing setting. +# +# If no script is given, the settings become the default for the remainder +# of the 'auto.def' file. +# +## cc-with {-lang c++} { +## # This will check with the C++ compiler +## cc-check-types bool +## cc-with {-includes signal.h} { +## # This will check with the C++ compiler, signal.h and any existing includes. +## ... +## } +## # back to just the C++ compiler +## } +# +# The '-libs' setting is special in that newer values are added *before* earlier ones. +# +## cc-with {-libs {-lc -lm}} { +## cc-with {-libs -ldl} { +## cctest -libs -lsocket ... +## # libs will be in this order: -lsocket -ldl -lc -lm +## } +## } +proc cc-with {settings args} { + if {[llength $args] == 0} { + cc-add-settings $settings + } elseif {[llength $args] > 1} { + autosetup-error "usage: cc-with settings ?script?" + } else { + set save [cc-add-settings $settings] + set rc [catch {uplevel 1 [lindex $args 0]} result info] + cc-store-settings $save + if {$rc != 0} { + return -code [dict get $info -code] $result + } + return $result + } +} + +# @cctest ?settings? +# +# Low level C/C++ compiler checker. Compiles and or links a small C program +# according to the arguments and returns 1 if OK, or 0 if not. +# +# Supported settings are: +# +## -cflags cflags A list of flags to pass to the compiler +## -includes list A list of includes, e.g. {stdlib.h stdio.h} +## -declare code Code to declare before main() +## -link 1 Don't just compile, link too +## -lang c|c++ Use the C (default) or C++ compiler +## -libs liblist List of libraries to link, e.g. {-ldl -lm} +## -code code Code to compile in the body of main() +## -source code Compile a complete program. Ignore -includes, -declare and -code +## -sourcefile file Shorthand for -source [readfile [get-define srcdir]/$file] +## -nooutput 1 Treat any compiler output (e.g. a warning) as an error +# +# Unless '-source' or '-sourcefile' is specified, the C program looks like: +# +## #include /* same for remaining includes in the list */ +## +## declare-code /* any code in -declare, verbatim */ +## +## int main(void) { +## code /* any code in -code, verbatim */ +## return 0; +## } +# +# Any failures are recorded in 'config.log' +# +proc cctest {args} { + set tmp conftest__ + + # Easiest way to merge in the settings + cc-with $args { + array set opts [cc-get-settings] + } + + if {[info exists opts(-sourcefile)]} { + set opts(-source) [readfile [get-define srcdir]/$opts(-sourcefile) "#error can't find $opts(-sourcefile)"] + } + if {[info exists opts(-source)]} { + set lines $opts(-source) + } else { + foreach i $opts(-includes) { + if {$opts(-code) ne "" && ![feature-checked $i]} { + # Compiling real code with an unchecked header file + # Quickly (and silently) check for it now + + # Remove all -includes from settings before checking + set saveopts [cc-update-settings -includes {}] + msg-quiet cc-check-includes $i + cc-store-settings $saveopts + } + if {$opts(-code) eq "" || [have-feature $i]} { + lappend source "#include <$i>" + } + } + lappend source {*}$opts(-declare) + lappend source "int main(void) {" + lappend source $opts(-code) + lappend source "return 0;" + lappend source "}" + + set lines [join $source \n] + } + + # Build the command line + set cmdline {} + lappend cmdline {*}[get-define CCACHE] + switch -exact -- $opts(-lang) { + c++ { + set src conftest__.cpp + lappend cmdline {*}[get-define CXX] {*}[get-define CXXFLAGS] + } + c { + set src conftest__.c + lappend cmdline {*}[get-define CC] {*}[get-define CFLAGS] + } + default { + autosetup-error "cctest called with unknown language: $opts(-lang)" + } + } + + if {$opts(-link)} { + lappend cmdline {*}[get-define LDFLAGS] + } else { + set tmp conftest__.o + lappend cmdline -c + } + lappend cmdline {*}$opts(-cflags) {*}[get-define cc-default-debug ""] + lappend cmdline $src -o $tmp {*}$opts(-libs) + if {$opts(-link)} { + lappend cmdline {*}[get-define LIBS] + } + + # At this point we have the complete command line and the + # complete source to be compiled. Get the result from cache if + # we can + if {[info exists ::cc_cache($cmdline,$lines)]} { + msg-checking "(cached) " + set ok $::cc_cache($cmdline,$lines) + if {$::autosetup(debug)} { + configlog "From cache (ok=$ok): [join $cmdline]" + configlog "============" + configlog $lines + configlog "============" + } + return $ok + } + + writefile $src $lines\n + + set ok 1 + set err [catch {exec-with-stderr {*}$cmdline} result errinfo] + if {$err || ($opts(-nooutput) && [string length $result])} { + configlog "Failed: [join $cmdline]" + configlog $result + configlog "============" + configlog "The failed code was:" + configlog $lines + configlog "============" + set ok 0 + } elseif {$::autosetup(debug)} { + configlog "Compiled OK: [join $cmdline]" + configlog "============" + configlog $lines + configlog "============" + } + file delete $src + file delete $tmp + + # cache it + set ::cc_cache($cmdline,$lines) $ok + + return $ok +} + +# @make-autoconf-h outfile ?auto-patterns=HAVE_*? ?bare-patterns=SIZEOF_*? +# +# Deprecated - see 'make-config-header' +proc make-autoconf-h {file {autopatterns {HAVE_*}} {barepatterns {SIZEOF_* HAVE_DECL_*}}} { + user-notice "*** make-autoconf-h is deprecated -- use make-config-header instead" + make-config-header $file -auto $autopatterns -bare $barepatterns +} + +# @make-config-header outfile ?-auto patternlist? ?-bare patternlist? ?-none patternlist? ?-str patternlist? ... +# +# Examines all defined variables which match the given patterns +# and writes an include file, '$file', which defines each of these. +# Variables which match '-auto' are output as follows: +# - defines which have the value '0' are ignored. +# - defines which have integer values are defined as the integer value. +# - any other value is defined as a string, e.g. '"value"' +# Variables which match '-bare' are defined as-is. +# Variables which match '-str' are defined as a string, e.g. '"value"' +# Variables which match '-none' are omitted. +# +# Note that order is important. The first pattern that matches is selected. +# Default behaviour is: +# +## -bare {SIZEOF_* HAVE_DECL_*} -auto HAVE_* -none * +# +# If the file would be unchanged, it is not written. +proc make-config-header {file args} { + set guard _[string toupper [regsub -all {[^a-zA-Z0-9]} [file tail $file] _]] + file mkdir [file dirname $file] + set lines {} + lappend lines "#ifndef $guard" + lappend lines "#define $guard" + + # Add some defaults + lappend args -bare {SIZEOF_* HAVE_DECL_*} -auto HAVE_* + + foreach n [lsort [dict keys [all-defines]]] { + set value [get-define $n] + set type [calc-define-output-type $n $args] + switch -exact -- $type { + -bare { + # Just output the value unchanged + } + -none { + continue + } + -str { + set value \"[string map [list \\ \\\\ \" \\\"] $value]\" + } + -auto { + # Automatically determine the type + if {$value eq "0"} { + lappend lines "/* #undef $n */" + continue + } + if {![string is integer -strict $value]} { + set value \"[string map [list \\ \\\\ \" \\\"] $value]\" + } + } + "" { + continue + } + default { + autosetup-error "Unknown type in make-config-header: $type" + } + } + lappend lines "#define $n $value" + } + lappend lines "#endif" + set buf [join $lines \n] + write-if-changed $file $buf { + msg-result "Created $file" + } +} + +proc calc-define-output-type {name spec} { + foreach {type patterns} $spec { + foreach pattern $patterns { + if {[string match $pattern $name]} { + return $type + } + } + } + return "" +} + +# Initialise some values from the environment or commandline or default settings +foreach i {LDFLAGS LIBS CPPFLAGS LINKFLAGS {CFLAGS "-g -O2"}} { + lassign $i var default + define $var [get-env $var $default] +} + +if {[env-is-set CC]} { + # Set by the user, so don't try anything else + set try [list [get-env CC ""]] +} else { + # Try some reasonable options + set try [list [get-define cross]cc [get-define cross]gcc] +} +define CC [find-an-executable {*}$try] +if {[get-define CC] eq ""} { + user-error "Could not find a C compiler. Tried: [join $try ", "]" +} + +define CPP [get-env CPP "[get-define CC] -E"] + +# XXX: Could avoid looking for a C++ compiler until requested +# Note that if CXX isn't found, we just set it to "false". It might not be needed. +if {[env-is-set CXX]} { + define CXX [find-an-executable -required [get-env CXX ""]] +} else { + define CXX [find-an-executable [get-define cross]c++ [get-define cross]g++ false] +} + +# CXXFLAGS default to CFLAGS if not specified +define CXXFLAGS [get-env CXXFLAGS [get-define CFLAGS]] + +# May need a CC_FOR_BUILD, so look for one +define CC_FOR_BUILD [find-an-executable [get-env CC_FOR_BUILD ""] cc gcc false] + +if {[get-define CC] eq ""} { + user-error "Could not find a C compiler. Tried: [join $try ", "]" +} + +define CCACHE [find-an-executable [get-env CCACHE ccache]] + +# If any of these are set in the environment, propagate them to the AUTOREMAKE commandline +foreach i {CC CXX CCACHE CPP CFLAGS CXXFLAGS CXXFLAGS LDFLAGS LIBS CROSS CPPFLAGS LINKFLAGS CC_FOR_BUILD LD} { + if {[env-is-set $i]} { + # Note: If the variable is set on the command line, get-env will return that value + # so the command line will continue to override the environment + define-append AUTOREMAKE [quote-if-needed $i=[get-env $i ""]] + } +} + +# Initial cctest settings +cc-store-settings {-cflags {} -includes {} -declare {} -link 0 -lang c -libs {} -code {} -nooutput 0} +set autosetup(cc-include-deps) {} + +msg-result "C compiler...[get-define CCACHE] [get-define CC] [get-define CFLAGS]" +if {[get-define CXX] ne "false"} { + msg-result "C++ compiler...[get-define CCACHE] [get-define CXX] [get-define CXXFLAGS]" +} +msg-result "Build C compiler...[get-define CC_FOR_BUILD]" + +# On Darwin, we prefer to use -g0 to avoid creating .dSYM directories +# but some compilers may not support it, so test here. +switch -glob -- [get-define host] { + *-*-darwin* { + if {[cctest -cflags {-g0}]} { + define cc-default-debug -g0 + } + } +} + +if {![cc-check-includes stdlib.h]} { + user-error "Compiler does not work. See config.log" +} ADDED autosetup/default.auto Index: autosetup/default.auto ================================================================== --- /dev/null +++ autosetup/default.auto @@ -0,0 +1,25 @@ +# Copyright (c) 2012 WorkWare Systems http://www.workware.net.au/ +# All rights reserved + +# Auto-load module for 'make' build system integration + +use init + +autosetup_add_init_type make {Simple "make" build system} { + autosetup_check_create auto.def \ +{# Initial auto.def created by 'autosetup --init=make' + +use cc + +# Add any user options here +options { +} + +make-config-header config.h +make-template Makefile.in +} + + if {![file exists Makefile.in]} { + puts "Note: I don't see Makefile.in. You will probably need to create one." + } +} ADDED autosetup/jimsh0.c Index: autosetup/jimsh0.c ================================================================== --- /dev/null +++ autosetup/jimsh0.c @@ -0,0 +1,22509 @@ +/* This is single source file, bootstrap version of Jim Tcl. See http://jim.tcl.tk/ */ +#define JIM_TCL_COMPAT +#define JIM_ANSIC +#define JIM_REGEXP +#define HAVE_NO_AUTOCONF +#define _JIMAUTOCONF_H +#define TCL_LIBRARY "." +#define jim_ext_bootstrap +#define jim_ext_aio +#define jim_ext_readdir +#define jim_ext_regexp +#define jim_ext_file +#define jim_ext_glob +#define jim_ext_exec +#define jim_ext_clock +#define jim_ext_array +#define jim_ext_stdlib +#define jim_ext_tclcompat +#if defined(_MSC_VER) +#define TCL_PLATFORM_OS "windows" +#define TCL_PLATFORM_PLATFORM "windows" +#define TCL_PLATFORM_PATH_SEPARATOR ";" +#define HAVE_MKDIR_ONE_ARG +#define HAVE_SYSTEM +#elif defined(__MINGW32__) +#define TCL_PLATFORM_OS "mingw" +#define TCL_PLATFORM_PLATFORM "windows" +#define TCL_PLATFORM_PATH_SEPARATOR ";" +#define HAVE_MKDIR_ONE_ARG +#define HAVE_SYSTEM +#define HAVE_SYS_TIME_H +#define HAVE_DIRENT_H +#define HAVE_UNISTD_H +#define HAVE_UMASK +#include +#ifndef S_IRWXG +#define S_IRWXG 0 +#endif +#ifndef S_IRWXO +#define S_IRWXO 0 +#endif +#else +#define TCL_PLATFORM_OS "unknown" +#define TCL_PLATFORM_PLATFORM "unix" +#define TCL_PLATFORM_PATH_SEPARATOR ":" +#ifdef _MINIX +#define vfork fork +#define _POSIX_SOURCE +#else +#define _GNU_SOURCE +#endif +#define HAVE_VFORK +#define HAVE_WAITPID +#define HAVE_ISATTY +#define HAVE_MKSTEMP +#define HAVE_LINK +#define HAVE_SYS_TIME_H +#define HAVE_DIRENT_H +#define HAVE_UNISTD_H +#define HAVE_UMASK +#endif +#define JIM_VERSION 78 +#ifndef JIM_WIN32COMPAT_H +#define JIM_WIN32COMPAT_H + + + +#ifdef __cplusplus +extern "C" { +#endif + + +#if defined(_WIN32) || defined(WIN32) + +#define HAVE_DLOPEN +void *dlopen(const char *path, int mode); +int dlclose(void *handle); +void *dlsym(void *handle, const char *symbol); +char *dlerror(void); + + +#if defined(__MINGW32__) + #define JIM_SPRINTF_DOUBLE_NEEDS_FIX +#endif + +#ifdef _MSC_VER + + +#if _MSC_VER >= 1000 + #pragma warning(disable:4146) +#endif + +#include +#define jim_wide _int64 +#ifndef LLONG_MAX + #define LLONG_MAX 9223372036854775807I64 +#endif +#ifndef LLONG_MIN + #define LLONG_MIN (-LLONG_MAX - 1I64) +#endif +#define JIM_WIDE_MIN LLONG_MIN +#define JIM_WIDE_MAX LLONG_MAX +#define JIM_WIDE_MODIFIER "I64d" +#define strcasecmp _stricmp +#define strtoull _strtoui64 + +#include + +struct timeval { + long tv_sec; + long tv_usec; +}; + +int gettimeofday(struct timeval *tv, void *unused); + +#define HAVE_OPENDIR +struct dirent { + char *d_name; +}; + +typedef struct DIR { + long handle; + struct _finddata_t info; + struct dirent result; + char *name; +} DIR; + +DIR *opendir(const char *name); +int closedir(DIR *dir); +struct dirent *readdir(DIR *dir); + +#endif + +#endif + +#ifdef __cplusplus +} +#endif + +#endif +#ifndef UTF8_UTIL_H +#define UTF8_UTIL_H + +#ifdef __cplusplus +extern "C" { +#endif + + + +#define MAX_UTF8_LEN 4 + +int utf8_fromunicode(char *p, unsigned uc); + +#ifndef JIM_UTF8 +#include + + +#define utf8_strlen(S, B) ((B) < 0 ? (int)strlen(S) : (B)) +#define utf8_strwidth(S, B) utf8_strlen((S), (B)) +#define utf8_tounicode(S, CP) (*(CP) = (unsigned char)*(S), 1) +#define utf8_getchars(CP, C) (*(CP) = (C), 1) +#define utf8_upper(C) toupper(C) +#define utf8_title(C) toupper(C) +#define utf8_lower(C) tolower(C) +#define utf8_index(C, I) (I) +#define utf8_charlen(C) 1 +#define utf8_prev_len(S, L) 1 +#define utf8_width(C) 1 + +#else + +#endif + +#ifdef __cplusplus +} +#endif + +#endif + +#ifndef __JIM__H +#define __JIM__H + +#ifdef __cplusplus +extern "C" { +#endif + +#include +#include +#include +#include +#include + + +#ifndef HAVE_NO_AUTOCONF +#endif + + + +#ifndef jim_wide +# ifdef HAVE_LONG_LONG +# define jim_wide long long +# ifndef LLONG_MAX +# define LLONG_MAX 9223372036854775807LL +# endif +# ifndef LLONG_MIN +# define LLONG_MIN (-LLONG_MAX - 1LL) +# endif +# define JIM_WIDE_MIN LLONG_MIN +# define JIM_WIDE_MAX LLONG_MAX +# else +# define jim_wide long +# define JIM_WIDE_MIN LONG_MIN +# define JIM_WIDE_MAX LONG_MAX +# endif + + +# ifdef HAVE_LONG_LONG +# define JIM_WIDE_MODIFIER "lld" +# else +# define JIM_WIDE_MODIFIER "ld" +# define strtoull strtoul +# endif +#endif + +#define UCHAR(c) ((unsigned char)(c)) + + +#define JIM_OK 0 +#define JIM_ERR 1 +#define JIM_RETURN 2 +#define JIM_BREAK 3 +#define JIM_CONTINUE 4 +#define JIM_SIGNAL 5 +#define JIM_EXIT 6 + +#define JIM_EVAL 7 + +#define JIM_MAX_CALLFRAME_DEPTH 1000 +#define JIM_MAX_EVAL_DEPTH 2000 + + +#define JIM_PRIV_FLAG_SHIFT 20 + +#define JIM_NONE 0 +#define JIM_ERRMSG 1 +#define JIM_ENUM_ABBREV 2 +#define JIM_UNSHARED 4 +#define JIM_MUSTEXIST 8 + + +#define JIM_SUBST_NOVAR 1 +#define JIM_SUBST_NOCMD 2 +#define JIM_SUBST_NOESC 4 +#define JIM_SUBST_FLAG 128 + + +#define JIM_CASESENS 0 +#define JIM_NOCASE 1 + + +#define JIM_PATH_LEN 1024 + + +#define JIM_NOTUSED(V) ((void) V) + +#define JIM_LIBPATH "auto_path" +#define JIM_INTERACTIVE "tcl_interactive" + + +typedef struct Jim_Stack { + int len; + int maxlen; + void **vector; +} Jim_Stack; + + +typedef struct Jim_HashEntry { + void *key; + union { + void *val; + int intval; + } u; + struct Jim_HashEntry *next; +} Jim_HashEntry; + +typedef struct Jim_HashTableType { + unsigned int (*hashFunction)(const void *key); + void *(*keyDup)(void *privdata, const void *key); + void *(*valDup)(void *privdata, const void *obj); + int (*keyCompare)(void *privdata, const void *key1, const void *key2); + void (*keyDestructor)(void *privdata, void *key); + void (*valDestructor)(void *privdata, void *obj); +} Jim_HashTableType; + +typedef struct Jim_HashTable { + Jim_HashEntry **table; + const Jim_HashTableType *type; + void *privdata; + unsigned int size; + unsigned int sizemask; + unsigned int used; + unsigned int collisions; + unsigned int uniq; +} Jim_HashTable; + +typedef struct Jim_HashTableIterator { + Jim_HashTable *ht; + Jim_HashEntry *entry, *nextEntry; + int index; +} Jim_HashTableIterator; + + +#define JIM_HT_INITIAL_SIZE 16 + + +#define Jim_FreeEntryVal(ht, entry) \ + if ((ht)->type->valDestructor) \ + (ht)->type->valDestructor((ht)->privdata, (entry)->u.val) + +#define Jim_SetHashVal(ht, entry, _val_) do { \ + if ((ht)->type->valDup) \ + (entry)->u.val = (ht)->type->valDup((ht)->privdata, (_val_)); \ + else \ + (entry)->u.val = (_val_); \ +} while(0) + +#define Jim_FreeEntryKey(ht, entry) \ + if ((ht)->type->keyDestructor) \ + (ht)->type->keyDestructor((ht)->privdata, (entry)->key) + +#define Jim_SetHashKey(ht, entry, _key_) do { \ + if ((ht)->type->keyDup) \ + (entry)->key = (ht)->type->keyDup((ht)->privdata, (_key_)); \ + else \ + (entry)->key = (void *)(_key_); \ +} while(0) + +#define Jim_CompareHashKeys(ht, key1, key2) \ + (((ht)->type->keyCompare) ? \ + (ht)->type->keyCompare((ht)->privdata, (key1), (key2)) : \ + (key1) == (key2)) + +#define Jim_HashKey(ht, key) ((ht)->type->hashFunction(key) + (ht)->uniq) + +#define Jim_GetHashEntryKey(he) ((he)->key) +#define Jim_GetHashEntryVal(he) ((he)->u.val) +#define Jim_GetHashTableCollisions(ht) ((ht)->collisions) +#define Jim_GetHashTableSize(ht) ((ht)->size) +#define Jim_GetHashTableUsed(ht) ((ht)->used) + + +typedef struct Jim_Obj { + char *bytes; + const struct Jim_ObjType *typePtr; + int refCount; + int length; + + union { + + jim_wide wideValue; + + int intValue; + + double doubleValue; + + void *ptr; + + struct { + void *ptr1; + void *ptr2; + } twoPtrValue; + + struct { + void *ptr; + int int1; + int int2; + } ptrIntValue; + + struct { + struct Jim_Var *varPtr; + unsigned long callFrameId; + int global; + } varValue; + + struct { + struct Jim_Obj *nsObj; + struct Jim_Cmd *cmdPtr; + unsigned long procEpoch; + } cmdValue; + + struct { + struct Jim_Obj **ele; + int len; + int maxLen; + } listValue; + + struct { + int maxLength; + int charLength; + } strValue; + + struct { + unsigned long id; + struct Jim_Reference *refPtr; + } refValue; + + struct { + struct Jim_Obj *fileNameObj; + int lineNumber; + } sourceValue; + + struct { + struct Jim_Obj *varNameObjPtr; + struct Jim_Obj *indexObjPtr; + } dictSubstValue; + struct { + int line; + int argc; + } scriptLineValue; + } internalRep; + struct Jim_Obj *prevObjPtr; + struct Jim_Obj *nextObjPtr; +} Jim_Obj; + + +#define Jim_IncrRefCount(objPtr) \ + ++(objPtr)->refCount +#define Jim_DecrRefCount(interp, objPtr) \ + if (--(objPtr)->refCount <= 0) Jim_FreeObj(interp, objPtr) +#define Jim_IsShared(objPtr) \ + ((objPtr)->refCount > 1) + +#define Jim_FreeNewObj Jim_FreeObj + + +#define Jim_FreeIntRep(i,o) \ + if ((o)->typePtr && (o)->typePtr->freeIntRepProc) \ + (o)->typePtr->freeIntRepProc(i, o) + + +#define Jim_GetIntRepPtr(o) (o)->internalRep.ptr + + +#define Jim_SetIntRepPtr(o, p) \ + (o)->internalRep.ptr = (p) + + +struct Jim_Interp; + +typedef void (Jim_FreeInternalRepProc)(struct Jim_Interp *interp, + struct Jim_Obj *objPtr); +typedef void (Jim_DupInternalRepProc)(struct Jim_Interp *interp, + struct Jim_Obj *srcPtr, Jim_Obj *dupPtr); +typedef void (Jim_UpdateStringProc)(struct Jim_Obj *objPtr); + +typedef struct Jim_ObjType { + const char *name; + Jim_FreeInternalRepProc *freeIntRepProc; + Jim_DupInternalRepProc *dupIntRepProc; + Jim_UpdateStringProc *updateStringProc; + int flags; +} Jim_ObjType; + + +#define JIM_TYPE_NONE 0 +#define JIM_TYPE_REFERENCES 1 + + + +typedef struct Jim_CallFrame { + unsigned long id; + int level; + struct Jim_HashTable vars; + struct Jim_HashTable *staticVars; + struct Jim_CallFrame *parent; + Jim_Obj *const *argv; + int argc; + Jim_Obj *procArgsObjPtr; + Jim_Obj *procBodyObjPtr; + struct Jim_CallFrame *next; + Jim_Obj *nsObj; + Jim_Obj *fileNameObj; + int line; + Jim_Stack *localCommands; + struct Jim_Obj *tailcallObj; + struct Jim_Cmd *tailcallCmd; +} Jim_CallFrame; + +typedef struct Jim_Var { + Jim_Obj *objPtr; + struct Jim_CallFrame *linkFramePtr; +} Jim_Var; + + +typedef int Jim_CmdProc(struct Jim_Interp *interp, int argc, + Jim_Obj *const *argv); +typedef void Jim_DelCmdProc(struct Jim_Interp *interp, void *privData); + + + +typedef struct Jim_Cmd { + int inUse; + int isproc; + struct Jim_Cmd *prevCmd; + union { + struct { + + Jim_CmdProc *cmdProc; + Jim_DelCmdProc *delProc; + void *privData; + } native; + struct { + + Jim_Obj *argListObjPtr; + Jim_Obj *bodyObjPtr; + Jim_HashTable *staticVars; + int argListLen; + int reqArity; + int optArity; + int argsPos; + int upcall; + struct Jim_ProcArg { + Jim_Obj *nameObjPtr; + Jim_Obj *defaultObjPtr; + } *arglist; + Jim_Obj *nsObj; + } proc; + } u; +} Jim_Cmd; + + +typedef struct Jim_PrngState { + unsigned char sbox[256]; + unsigned int i, j; +} Jim_PrngState; + +typedef struct Jim_Interp { + Jim_Obj *result; + int errorLine; + Jim_Obj *errorFileNameObj; + int addStackTrace; + int maxCallFrameDepth; + int maxEvalDepth; + int evalDepth; + int returnCode; + int returnLevel; + int exitCode; + long id; + int signal_level; + jim_wide sigmask; + int (*signal_set_result)(struct Jim_Interp *interp, jim_wide sigmask); + Jim_CallFrame *framePtr; + Jim_CallFrame *topFramePtr; + struct Jim_HashTable commands; + unsigned long procEpoch; /* Incremented every time the result + of procedures names lookup caching + may no longer be valid. */ + unsigned long callFrameEpoch; /* Incremented every time a new + callframe is created. This id is used for the + 'ID' field contained in the Jim_CallFrame + structure. */ + int local; + Jim_Obj *liveList; + Jim_Obj *freeList; + Jim_Obj *currentScriptObj; + Jim_Obj *nullScriptObj; + Jim_Obj *emptyObj; + Jim_Obj *trueObj; + Jim_Obj *falseObj; + unsigned long referenceNextId; + struct Jim_HashTable references; + unsigned long lastCollectId; /* reference max Id of the last GC + execution. It's set to ~0 while the collection + is running as sentinel to avoid to recursive + calls via the [collect] command inside + finalizers. */ + time_t lastCollectTime; + Jim_Obj *stackTrace; + Jim_Obj *errorProc; + Jim_Obj *unknown; + int unknown_called; + int errorFlag; + void *cmdPrivData; /* Used to pass the private data pointer to + a command. It is set to what the user specified + via Jim_CreateCommand(). */ + + struct Jim_CallFrame *freeFramesList; + struct Jim_HashTable assocData; + Jim_PrngState *prngState; + struct Jim_HashTable packages; + Jim_Stack *loadHandles; +} Jim_Interp; + +#define Jim_InterpIncrProcEpoch(i) (i)->procEpoch++ +#define Jim_SetResultString(i,s,l) Jim_SetResult(i, Jim_NewStringObj(i,s,l)) +#define Jim_SetResultInt(i,intval) Jim_SetResult(i, Jim_NewIntObj(i,intval)) + +#define Jim_SetResultBool(i,b) Jim_SetResultInt(i, b) +#define Jim_SetEmptyResult(i) Jim_SetResult(i, (i)->emptyObj) +#define Jim_GetResult(i) ((i)->result) +#define Jim_CmdPrivData(i) ((i)->cmdPrivData) + +#define Jim_SetResult(i,o) do { \ + Jim_Obj *_resultObjPtr_ = (o); \ + Jim_IncrRefCount(_resultObjPtr_); \ + Jim_DecrRefCount(i,(i)->result); \ + (i)->result = _resultObjPtr_; \ +} while(0) + + +#define Jim_GetId(i) (++(i)->id) + + +#define JIM_REFERENCE_TAGLEN 7 /* The tag is fixed-length, because the reference + string representation must be fixed length. */ +typedef struct Jim_Reference { + Jim_Obj *objPtr; + Jim_Obj *finalizerCmdNamePtr; + char tag[JIM_REFERENCE_TAGLEN+1]; +} Jim_Reference; + + +#define Jim_NewEmptyStringObj(i) Jim_NewStringObj(i, "", 0) +#define Jim_FreeHashTableIterator(iter) Jim_Free(iter) + +#define JIM_EXPORT + + +JIM_EXPORT void *Jim_Alloc (int size); +JIM_EXPORT void *Jim_Realloc(void *ptr, int size); +JIM_EXPORT void Jim_Free (void *ptr); +JIM_EXPORT char * Jim_StrDup (const char *s); +JIM_EXPORT char *Jim_StrDupLen(const char *s, int l); + + +JIM_EXPORT char **Jim_GetEnviron(void); +JIM_EXPORT void Jim_SetEnviron(char **env); +JIM_EXPORT int Jim_MakeTempFile(Jim_Interp *interp, const char *filename_template, int unlink_file); + + +JIM_EXPORT int Jim_Eval(Jim_Interp *interp, const char *script); + + +JIM_EXPORT int Jim_EvalSource(Jim_Interp *interp, const char *filename, int lineno, const char *script); + +#define Jim_Eval_Named(I, S, F, L) Jim_EvalSource((I), (F), (L), (S)) + +JIM_EXPORT int Jim_EvalGlobal(Jim_Interp *interp, const char *script); +JIM_EXPORT int Jim_EvalFile(Jim_Interp *interp, const char *filename); +JIM_EXPORT int Jim_EvalFileGlobal(Jim_Interp *interp, const char *filename); +JIM_EXPORT int Jim_EvalObj (Jim_Interp *interp, Jim_Obj *scriptObjPtr); +JIM_EXPORT int Jim_EvalObjVector (Jim_Interp *interp, int objc, + Jim_Obj *const *objv); +JIM_EXPORT int Jim_EvalObjList(Jim_Interp *interp, Jim_Obj *listObj); +JIM_EXPORT int Jim_EvalObjPrefix(Jim_Interp *interp, Jim_Obj *prefix, + int objc, Jim_Obj *const *objv); +#define Jim_EvalPrefix(i, p, oc, ov) Jim_EvalObjPrefix((i), Jim_NewStringObj((i), (p), -1), (oc), (ov)) +JIM_EXPORT int Jim_EvalNamespace(Jim_Interp *interp, Jim_Obj *scriptObj, Jim_Obj *nsObj); +JIM_EXPORT int Jim_SubstObj (Jim_Interp *interp, Jim_Obj *substObjPtr, + Jim_Obj **resObjPtrPtr, int flags); + + +JIM_EXPORT void Jim_InitStack(Jim_Stack *stack); +JIM_EXPORT void Jim_FreeStack(Jim_Stack *stack); +JIM_EXPORT int Jim_StackLen(Jim_Stack *stack); +JIM_EXPORT void Jim_StackPush(Jim_Stack *stack, void *element); +JIM_EXPORT void * Jim_StackPop(Jim_Stack *stack); +JIM_EXPORT void * Jim_StackPeek(Jim_Stack *stack); +JIM_EXPORT void Jim_FreeStackElements(Jim_Stack *stack, void (*freeFunc)(void *ptr)); + + +JIM_EXPORT int Jim_InitHashTable (Jim_HashTable *ht, + const Jim_HashTableType *type, void *privdata); +JIM_EXPORT void Jim_ExpandHashTable (Jim_HashTable *ht, + unsigned int size); +JIM_EXPORT int Jim_AddHashEntry (Jim_HashTable *ht, const void *key, + void *val); +JIM_EXPORT int Jim_ReplaceHashEntry (Jim_HashTable *ht, + const void *key, void *val); +JIM_EXPORT int Jim_DeleteHashEntry (Jim_HashTable *ht, + const void *key); +JIM_EXPORT int Jim_FreeHashTable (Jim_HashTable *ht); +JIM_EXPORT Jim_HashEntry * Jim_FindHashEntry (Jim_HashTable *ht, + const void *key); +JIM_EXPORT void Jim_ResizeHashTable (Jim_HashTable *ht); +JIM_EXPORT Jim_HashTableIterator *Jim_GetHashTableIterator + (Jim_HashTable *ht); +JIM_EXPORT Jim_HashEntry * Jim_NextHashEntry + (Jim_HashTableIterator *iter); + + +JIM_EXPORT Jim_Obj * Jim_NewObj (Jim_Interp *interp); +JIM_EXPORT void Jim_FreeObj (Jim_Interp *interp, Jim_Obj *objPtr); +JIM_EXPORT void Jim_InvalidateStringRep (Jim_Obj *objPtr); +JIM_EXPORT Jim_Obj * Jim_DuplicateObj (Jim_Interp *interp, + Jim_Obj *objPtr); +JIM_EXPORT const char * Jim_GetString(Jim_Obj *objPtr, + int *lenPtr); +JIM_EXPORT const char *Jim_String(Jim_Obj *objPtr); +JIM_EXPORT int Jim_Length(Jim_Obj *objPtr); + + +JIM_EXPORT Jim_Obj * Jim_NewStringObj (Jim_Interp *interp, + const char *s, int len); +JIM_EXPORT Jim_Obj *Jim_NewStringObjUtf8(Jim_Interp *interp, + const char *s, int charlen); +JIM_EXPORT Jim_Obj * Jim_NewStringObjNoAlloc (Jim_Interp *interp, + char *s, int len); +JIM_EXPORT void Jim_AppendString (Jim_Interp *interp, Jim_Obj *objPtr, + const char *str, int len); +JIM_EXPORT void Jim_AppendObj (Jim_Interp *interp, Jim_Obj *objPtr, + Jim_Obj *appendObjPtr); +JIM_EXPORT void Jim_AppendStrings (Jim_Interp *interp, + Jim_Obj *objPtr, ...); +JIM_EXPORT int Jim_StringEqObj(Jim_Obj *aObjPtr, Jim_Obj *bObjPtr); +JIM_EXPORT int Jim_StringMatchObj (Jim_Interp *interp, Jim_Obj *patternObjPtr, + Jim_Obj *objPtr, int nocase); +JIM_EXPORT Jim_Obj * Jim_StringRangeObj (Jim_Interp *interp, + Jim_Obj *strObjPtr, Jim_Obj *firstObjPtr, + Jim_Obj *lastObjPtr); +JIM_EXPORT Jim_Obj * Jim_FormatString (Jim_Interp *interp, + Jim_Obj *fmtObjPtr, int objc, Jim_Obj *const *objv); +JIM_EXPORT Jim_Obj * Jim_ScanString (Jim_Interp *interp, Jim_Obj *strObjPtr, + Jim_Obj *fmtObjPtr, int flags); +JIM_EXPORT int Jim_CompareStringImmediate (Jim_Interp *interp, + Jim_Obj *objPtr, const char *str); +JIM_EXPORT int Jim_StringCompareObj(Jim_Interp *interp, Jim_Obj *firstObjPtr, + Jim_Obj *secondObjPtr, int nocase); +JIM_EXPORT int Jim_StringCompareLenObj(Jim_Interp *interp, Jim_Obj *firstObjPtr, + Jim_Obj *secondObjPtr, int nocase); +JIM_EXPORT int Jim_Utf8Length(Jim_Interp *interp, Jim_Obj *objPtr); + + +JIM_EXPORT Jim_Obj * Jim_NewReference (Jim_Interp *interp, + Jim_Obj *objPtr, Jim_Obj *tagPtr, Jim_Obj *cmdNamePtr); +JIM_EXPORT Jim_Reference * Jim_GetReference (Jim_Interp *interp, + Jim_Obj *objPtr); +JIM_EXPORT int Jim_SetFinalizer (Jim_Interp *interp, Jim_Obj *objPtr, Jim_Obj *cmdNamePtr); +JIM_EXPORT int Jim_GetFinalizer (Jim_Interp *interp, Jim_Obj *objPtr, Jim_Obj **cmdNamePtrPtr); + + +JIM_EXPORT Jim_Interp * Jim_CreateInterp (void); +JIM_EXPORT void Jim_FreeInterp (Jim_Interp *i); +JIM_EXPORT int Jim_GetExitCode (Jim_Interp *interp); +JIM_EXPORT const char *Jim_ReturnCode(int code); +JIM_EXPORT void Jim_SetResultFormatted(Jim_Interp *interp, const char *format, ...); + + +JIM_EXPORT void Jim_RegisterCoreCommands (Jim_Interp *interp); +JIM_EXPORT int Jim_CreateCommand (Jim_Interp *interp, + const char *cmdName, Jim_CmdProc *cmdProc, void *privData, + Jim_DelCmdProc *delProc); +JIM_EXPORT int Jim_DeleteCommand (Jim_Interp *interp, + const char *cmdName); +JIM_EXPORT int Jim_RenameCommand (Jim_Interp *interp, + const char *oldName, const char *newName); +JIM_EXPORT Jim_Cmd * Jim_GetCommand (Jim_Interp *interp, + Jim_Obj *objPtr, int flags); +JIM_EXPORT int Jim_SetVariable (Jim_Interp *interp, + Jim_Obj *nameObjPtr, Jim_Obj *valObjPtr); +JIM_EXPORT int Jim_SetVariableStr (Jim_Interp *interp, + const char *name, Jim_Obj *objPtr); +JIM_EXPORT int Jim_SetGlobalVariableStr (Jim_Interp *interp, + const char *name, Jim_Obj *objPtr); +JIM_EXPORT int Jim_SetVariableStrWithStr (Jim_Interp *interp, + const char *name, const char *val); +JIM_EXPORT int Jim_SetVariableLink (Jim_Interp *interp, + Jim_Obj *nameObjPtr, Jim_Obj *targetNameObjPtr, + Jim_CallFrame *targetCallFrame); +JIM_EXPORT Jim_Obj * Jim_MakeGlobalNamespaceName(Jim_Interp *interp, + Jim_Obj *nameObjPtr); +JIM_EXPORT Jim_Obj * Jim_GetVariable (Jim_Interp *interp, + Jim_Obj *nameObjPtr, int flags); +JIM_EXPORT Jim_Obj * Jim_GetGlobalVariable (Jim_Interp *interp, + Jim_Obj *nameObjPtr, int flags); +JIM_EXPORT Jim_Obj * Jim_GetVariableStr (Jim_Interp *interp, + const char *name, int flags); +JIM_EXPORT Jim_Obj * Jim_GetGlobalVariableStr (Jim_Interp *interp, + const char *name, int flags); +JIM_EXPORT int Jim_UnsetVariable (Jim_Interp *interp, + Jim_Obj *nameObjPtr, int flags); + + +JIM_EXPORT Jim_CallFrame *Jim_GetCallFrameByLevel(Jim_Interp *interp, + Jim_Obj *levelObjPtr); + + +JIM_EXPORT int Jim_Collect (Jim_Interp *interp); +JIM_EXPORT void Jim_CollectIfNeeded (Jim_Interp *interp); + + +JIM_EXPORT int Jim_GetIndex (Jim_Interp *interp, Jim_Obj *objPtr, + int *indexPtr); + + +JIM_EXPORT Jim_Obj * Jim_NewListObj (Jim_Interp *interp, + Jim_Obj *const *elements, int len); +JIM_EXPORT void Jim_ListInsertElements (Jim_Interp *interp, + Jim_Obj *listPtr, int listindex, int objc, Jim_Obj *const *objVec); +JIM_EXPORT void Jim_ListAppendElement (Jim_Interp *interp, + Jim_Obj *listPtr, Jim_Obj *objPtr); +JIM_EXPORT void Jim_ListAppendList (Jim_Interp *interp, + Jim_Obj *listPtr, Jim_Obj *appendListPtr); +JIM_EXPORT int Jim_ListLength (Jim_Interp *interp, Jim_Obj *objPtr); +JIM_EXPORT int Jim_ListIndex (Jim_Interp *interp, Jim_Obj *listPrt, + int listindex, Jim_Obj **objPtrPtr, int seterr); +JIM_EXPORT Jim_Obj *Jim_ListGetIndex(Jim_Interp *interp, Jim_Obj *listPtr, int idx); +JIM_EXPORT int Jim_SetListIndex (Jim_Interp *interp, + Jim_Obj *varNamePtr, Jim_Obj *const *indexv, int indexc, + Jim_Obj *newObjPtr); +JIM_EXPORT Jim_Obj * Jim_ConcatObj (Jim_Interp *interp, int objc, + Jim_Obj *const *objv); +JIM_EXPORT Jim_Obj *Jim_ListJoin(Jim_Interp *interp, + Jim_Obj *listObjPtr, const char *joinStr, int joinStrLen); + + +JIM_EXPORT Jim_Obj * Jim_NewDictObj (Jim_Interp *interp, + Jim_Obj *const *elements, int len); +JIM_EXPORT int Jim_DictKey (Jim_Interp *interp, Jim_Obj *dictPtr, + Jim_Obj *keyPtr, Jim_Obj **objPtrPtr, int flags); +JIM_EXPORT int Jim_DictKeysVector (Jim_Interp *interp, + Jim_Obj *dictPtr, Jim_Obj *const *keyv, int keyc, + Jim_Obj **objPtrPtr, int flags); +JIM_EXPORT int Jim_SetDictKeysVector (Jim_Interp *interp, + Jim_Obj *varNamePtr, Jim_Obj *const *keyv, int keyc, + Jim_Obj *newObjPtr, int flags); +JIM_EXPORT int Jim_DictPairs(Jim_Interp *interp, + Jim_Obj *dictPtr, Jim_Obj ***objPtrPtr, int *len); +JIM_EXPORT int Jim_DictAddElement(Jim_Interp *interp, Jim_Obj *objPtr, + Jim_Obj *keyObjPtr, Jim_Obj *valueObjPtr); + +#define JIM_DICTMATCH_KEYS 0x0001 +#define JIM_DICTMATCH_VALUES 0x002 + +JIM_EXPORT int Jim_DictMatchTypes(Jim_Interp *interp, Jim_Obj *objPtr, Jim_Obj *patternObj, int match_type, int return_types); +JIM_EXPORT int Jim_DictSize(Jim_Interp *interp, Jim_Obj *objPtr); +JIM_EXPORT int Jim_DictInfo(Jim_Interp *interp, Jim_Obj *objPtr); +JIM_EXPORT Jim_Obj *Jim_DictMerge(Jim_Interp *interp, int objc, Jim_Obj *const *objv); + + +JIM_EXPORT int Jim_GetReturnCode (Jim_Interp *interp, Jim_Obj *objPtr, + int *intPtr); + + +JIM_EXPORT int Jim_EvalExpression (Jim_Interp *interp, + Jim_Obj *exprObjPtr); +JIM_EXPORT int Jim_GetBoolFromExpr (Jim_Interp *interp, + Jim_Obj *exprObjPtr, int *boolPtr); + + +JIM_EXPORT int Jim_GetBoolean(Jim_Interp *interp, Jim_Obj *objPtr, + int *booleanPtr); + + +JIM_EXPORT int Jim_GetWide (Jim_Interp *interp, Jim_Obj *objPtr, + jim_wide *widePtr); +JIM_EXPORT int Jim_GetLong (Jim_Interp *interp, Jim_Obj *objPtr, + long *longPtr); +#define Jim_NewWideObj Jim_NewIntObj +JIM_EXPORT Jim_Obj * Jim_NewIntObj (Jim_Interp *interp, + jim_wide wideValue); + + +JIM_EXPORT int Jim_GetDouble(Jim_Interp *interp, Jim_Obj *objPtr, + double *doublePtr); +JIM_EXPORT void Jim_SetDouble(Jim_Interp *interp, Jim_Obj *objPtr, + double doubleValue); +JIM_EXPORT Jim_Obj * Jim_NewDoubleObj(Jim_Interp *interp, double doubleValue); + + +JIM_EXPORT void Jim_WrongNumArgs (Jim_Interp *interp, int argc, + Jim_Obj *const *argv, const char *msg); +JIM_EXPORT int Jim_GetEnum (Jim_Interp *interp, Jim_Obj *objPtr, + const char * const *tablePtr, int *indexPtr, const char *name, int flags); +JIM_EXPORT int Jim_CheckShowCommands(Jim_Interp *interp, Jim_Obj *objPtr, + const char *const *tablePtr); +JIM_EXPORT int Jim_ScriptIsComplete(Jim_Interp *interp, + Jim_Obj *scriptObj, char *stateCharPtr); + +JIM_EXPORT int Jim_FindByName(const char *name, const char * const array[], size_t len); + + +typedef void (Jim_InterpDeleteProc)(Jim_Interp *interp, void *data); +JIM_EXPORT void * Jim_GetAssocData(Jim_Interp *interp, const char *key); +JIM_EXPORT int Jim_SetAssocData(Jim_Interp *interp, const char *key, + Jim_InterpDeleteProc *delProc, void *data); +JIM_EXPORT int Jim_DeleteAssocData(Jim_Interp *interp, const char *key); + + + +JIM_EXPORT int Jim_PackageProvide (Jim_Interp *interp, + const char *name, const char *ver, int flags); +JIM_EXPORT int Jim_PackageRequire (Jim_Interp *interp, + const char *name, int flags); + + +JIM_EXPORT void Jim_MakeErrorMessage (Jim_Interp *interp); + + +JIM_EXPORT int Jim_InteractivePrompt (Jim_Interp *interp); +JIM_EXPORT void Jim_HistoryLoad(const char *filename); +JIM_EXPORT void Jim_HistorySave(const char *filename); +JIM_EXPORT char *Jim_HistoryGetline(Jim_Interp *interp, const char *prompt); +JIM_EXPORT void Jim_HistorySetCompletion(Jim_Interp *interp, Jim_Obj *commandObj); +JIM_EXPORT void Jim_HistoryAdd(const char *line); +JIM_EXPORT void Jim_HistoryShow(void); + + +JIM_EXPORT int Jim_InitStaticExtensions(Jim_Interp *interp); +JIM_EXPORT int Jim_StringToWide(const char *str, jim_wide *widePtr, int base); +JIM_EXPORT int Jim_IsBigEndian(void); + +#define Jim_CheckSignal(i) ((i)->signal_level && (i)->sigmask) + + +JIM_EXPORT int Jim_LoadLibrary(Jim_Interp *interp, const char *pathName); +JIM_EXPORT void Jim_FreeLoadHandles(Jim_Interp *interp); + + +JIM_EXPORT FILE *Jim_AioFilehandle(Jim_Interp *interp, Jim_Obj *command); + + +JIM_EXPORT int Jim_IsDict(Jim_Obj *objPtr); +JIM_EXPORT int Jim_IsList(Jim_Obj *objPtr); + +#ifdef __cplusplus +} +#endif + +#endif + +#ifndef JIM_SUBCMD_H +#define JIM_SUBCMD_H + + +#ifdef __cplusplus +extern "C" { +#endif + + +#define JIM_MODFLAG_HIDDEN 0x0001 +#define JIM_MODFLAG_FULLARGV 0x0002 + + + +typedef int jim_subcmd_function(Jim_Interp *interp, int argc, Jim_Obj *const *argv); + +typedef struct { + const char *cmd; + const char *args; + jim_subcmd_function *function; + short minargs; + short maxargs; + unsigned short flags; +} jim_subcmd_type; + +const jim_subcmd_type * +Jim_ParseSubCmd(Jim_Interp *interp, const jim_subcmd_type *command_table, int argc, Jim_Obj *const *argv); + +int Jim_SubCmdProc(Jim_Interp *interp, int argc, Jim_Obj *const *argv); + +int Jim_CallSubCmd(Jim_Interp *interp, const jim_subcmd_type *ct, int argc, Jim_Obj *const *argv); + +#ifdef __cplusplus +} +#endif + +#endif +#ifndef JIMREGEXP_H +#define JIMREGEXP_H + + +#ifdef __cplusplus +extern "C" { +#endif + +#include + +typedef struct { + int rm_so; + int rm_eo; +} regmatch_t; + + +typedef struct regexp { + + int re_nsub; + + + int cflags; + int err; + int regstart; + int reganch; + int regmust; + int regmlen; + int *program; + + + const char *regparse; + int p; + int proglen; + + + int eflags; + const char *start; + const char *reginput; + const char *regbol; + + + regmatch_t *pmatch; + int nmatch; +} regexp; + +typedef regexp regex_t; + +#define REG_EXTENDED 0 +#define REG_NEWLINE 1 +#define REG_ICASE 2 + +#define REG_NOTBOL 16 + +enum { + REG_NOERROR, + REG_NOMATCH, + REG_BADPAT, + REG_ERR_NULL_ARGUMENT, + REG_ERR_UNKNOWN, + REG_ERR_TOO_BIG, + REG_ERR_NOMEM, + REG_ERR_TOO_MANY_PAREN, + REG_ERR_UNMATCHED_PAREN, + REG_ERR_UNMATCHED_BRACES, + REG_ERR_BAD_COUNT, + REG_ERR_JUNK_ON_END, + REG_ERR_OPERAND_COULD_BE_EMPTY, + REG_ERR_NESTED_COUNT, + REG_ERR_INTERNAL, + REG_ERR_COUNT_FOLLOWS_NOTHING, + REG_ERR_TRAILING_BACKSLASH, + REG_ERR_CORRUPTED, + REG_ERR_NULL_CHAR, + REG_ERR_NUM +}; + +int regcomp(regex_t *preg, const char *regex, int cflags); +int regexec(regex_t *preg, const char *string, size_t nmatch, regmatch_t pmatch[], int eflags); +size_t regerror(int errcode, const regex_t *preg, char *errbuf, size_t errbuf_size); +void regfree(regex_t *preg); + +#ifdef __cplusplus +} +#endif + +#endif +#ifndef JIM_SIGNAL_H +#define JIM_SIGNAL_H + +#ifdef __cplusplus +extern "C" { +#endif + +const char *Jim_SignalId(int sig); + +#ifdef __cplusplus +} +#endif + +#endif +#ifndef JIMIOCOMPAT_H +#define JIMIOCOMPAT_H + + +#include +#include + + +void Jim_SetResultErrno(Jim_Interp *interp, const char *msg); + +int Jim_OpenForWrite(const char *filename, int append); + +int Jim_OpenForRead(const char *filename); + +#if defined(__MINGW32__) + #ifndef STRICT + #define STRICT + #endif + #define WIN32_LEAN_AND_MEAN + #include + #include + #include + #include + + typedef HANDLE pidtype; + #define JIM_BAD_PID INVALID_HANDLE_VALUE + + #define JIM_NO_PID INVALID_HANDLE_VALUE + + + #define WIFEXITED(STATUS) (((STATUS) & 0xff00) == 0) + #define WEXITSTATUS(STATUS) ((STATUS) & 0x00ff) + #define WIFSIGNALED(STATUS) (((STATUS) & 0xff00) != 0) + #define WTERMSIG(STATUS) (((STATUS) >> 8) & 0xff) + #define WNOHANG 1 + + int Jim_Errno(void); + pidtype waitpid(pidtype pid, int *status, int nohang); + + #define HAVE_PIPE + #define pipe(P) _pipe((P), 0, O_NOINHERIT) + +#elif defined(HAVE_UNISTD_H) + #include + #include + #include + #include + + typedef int pidtype; + #define Jim_Errno() errno + #define JIM_BAD_PID -1 + #define JIM_NO_PID 0 + + #ifndef HAVE_EXECVPE + #define execvpe(ARG0, ARGV, ENV) execvp(ARG0, ARGV) + #endif +#endif + +#endif +int Jim_bootstrapInit(Jim_Interp *interp) +{ + if (Jim_PackageProvide(interp, "bootstrap", "1.0", JIM_ERRMSG)) + return JIM_ERR; + + return Jim_EvalSource(interp, "bootstrap.tcl", 1, +"\n" +"\n" +"proc package {cmd pkg args} {\n" +" if {$cmd eq \"require\"} {\n" +" foreach path $::auto_path {\n" +" set pkgpath $path/$pkg.tcl\n" +" if {$path eq \".\"} {\n" +" set pkgpath $pkg.tcl\n" +" }\n" +" if {[file exists $pkgpath]} {\n" +" uplevel #0 [list source $pkgpath]\n" +" return\n" +" }\n" +" }\n" +" }\n" +"}\n" +); +} +int Jim_initjimshInit(Jim_Interp *interp) +{ + if (Jim_PackageProvide(interp, "initjimsh", "1.0", JIM_ERRMSG)) + return JIM_ERR; + + return Jim_EvalSource(interp, "initjimsh.tcl", 1, +"\n" +"\n" +"\n" +"proc _jimsh_init {} {\n" +" rename _jimsh_init {}\n" +" global jim::exe jim::argv0 tcl_interactive auto_path tcl_platform\n" +"\n" +"\n" +" if {[exists jim::argv0]} {\n" +" if {[string match \"*/*\" $jim::argv0]} {\n" +" set jim::exe [file join [pwd] $jim::argv0]\n" +" } else {\n" +" foreach path [split [env PATH \"\"] $tcl_platform(pathSeparator)] {\n" +" set exec [file join [pwd] [string map {\\\\ /} $path] $jim::argv0]\n" +" if {[file executable $exec]} {\n" +" set jim::exe $exec\n" +" break\n" +" }\n" +" }\n" +" }\n" +" }\n" +"\n" +"\n" +" lappend p {*}[split [env JIMLIB {}] $tcl_platform(pathSeparator)]\n" +" if {[exists jim::exe]} {\n" +" lappend p [file dirname $jim::exe]\n" +" }\n" +" lappend p {*}$auto_path\n" +" set auto_path $p\n" +"\n" +" if {$tcl_interactive && [env HOME {}] ne \"\"} {\n" +" foreach src {.jimrc jimrc.tcl} {\n" +" if {[file exists [env HOME]/$src]} {\n" +" uplevel #0 source [env HOME]/$src\n" +" break\n" +" }\n" +" }\n" +" }\n" +" return \"\"\n" +"}\n" +"\n" +"if {$tcl_platform(platform) eq \"windows\"} {\n" +" set jim::argv0 [string map {\\\\ /} $jim::argv0]\n" +"}\n" +"\n" +"\n" +"set tcl::autocomplete_commands {info tcl::prefix socket namespace array clock file package string dict signal history}\n" +"\n" +"\n" +"\n" +"proc tcl::autocomplete {prefix} {\n" +" if {[set space [string first \" \" $prefix]] != -1} {\n" +" set cmd [string range $prefix 0 $space-1]\n" +" if {$cmd in $::tcl::autocomplete_commands || [info channel $cmd] ne \"\"} {\n" +" set arg [string range $prefix $space+1 end]\n" +"\n" +" return [lmap p [$cmd -commands] {\n" +" if {![string match \"${arg}*\" $p]} continue\n" +" function \"$cmd $p\"\n" +" }]\n" +" }\n" +" }\n" +"\n" +" if {[string match \"source *\" $prefix]} {\n" +" set path [string range $prefix 7 end]\n" +" return [lmap p [glob -nocomplain \"${path}*\"] {\n" +" function \"source $p\"\n" +" }]\n" +" }\n" +"\n" +" return [lmap p [lsort [info commands $prefix*]] {\n" +" if {[string match \"* *\" $p]} {\n" +" continue\n" +" }\n" +" function $p\n" +" }]\n" +"}\n" +"\n" +"_jimsh_init\n" +); +} +int Jim_globInit(Jim_Interp *interp) +{ + if (Jim_PackageProvide(interp, "glob", "1.0", JIM_ERRMSG)) + return JIM_ERR; + + return Jim_EvalSource(interp, "glob.tcl", 1, +"\n" +"\n" +"\n" +"\n" +"\n" +"\n" +"\n" +"package require readdir\n" +"\n" +"\n" +"proc glob.globdir {dir pattern} {\n" +" if {[file exists $dir/$pattern]} {\n" +"\n" +" return [list $pattern]\n" +" }\n" +"\n" +" set result {}\n" +" set files [readdir $dir]\n" +" lappend files . ..\n" +"\n" +" foreach name $files {\n" +" if {[string match $pattern $name]} {\n" +"\n" +" if {[string index $name 0] eq \".\" && [string index $pattern 0] ne \".\"} {\n" +" continue\n" +" }\n" +" lappend result $name\n" +" }\n" +" }\n" +"\n" +" return $result\n" +"}\n" +"\n" +"\n" +"\n" +"\n" +"proc glob.explode {pattern} {\n" +" set oldexp {}\n" +" set newexp {\"\"}\n" +"\n" +" while 1 {\n" +" set oldexp $newexp\n" +" set newexp {}\n" +" set ob [string first \\{ $pattern]\n" +" set cb [string first \\} $pattern]\n" +"\n" +" if {$ob < $cb && $ob != -1} {\n" +" set mid [string range $pattern 0 $ob-1]\n" +" set subexp [lassign [glob.explode [string range $pattern $ob+1 end]] pattern]\n" +" if {$pattern eq \"\"} {\n" +" error \"unmatched open brace in glob pattern\"\n" +" }\n" +" set pattern [string range $pattern 1 end]\n" +"\n" +" foreach subs $subexp {\n" +" foreach sub [split $subs ,] {\n" +" foreach old $oldexp {\n" +" lappend newexp $old$mid$sub\n" +" }\n" +" }\n" +" }\n" +" } elseif {$cb != -1} {\n" +" set suf [string range $pattern 0 $cb-1]\n" +" set rest [string range $pattern $cb end]\n" +" break\n" +" } else {\n" +" set suf $pattern\n" +" set rest \"\"\n" +" break\n" +" }\n" +" }\n" +"\n" +" foreach old $oldexp {\n" +" lappend newexp $old$suf\n" +" }\n" +" list $rest {*}$newexp\n" +"}\n" +"\n" +"\n" +"\n" +"proc glob.glob {base pattern} {\n" +" set dir [file dirname $pattern]\n" +" if {$pattern eq $dir || $pattern eq \"\"} {\n" +" return [list [file join $base $dir] $pattern]\n" +" } elseif {$pattern eq [file tail $pattern]} {\n" +" set dir \"\"\n" +" }\n" +"\n" +"\n" +" set dirlist [glob.glob $base $dir]\n" +" set pattern [file tail $pattern]\n" +"\n" +"\n" +" set result {}\n" +" foreach {realdir dir} $dirlist {\n" +" if {![file isdir $realdir]} {\n" +" continue\n" +" }\n" +" if {[string index $dir end] ne \"/\" && $dir ne \"\"} {\n" +" append dir /\n" +" }\n" +" foreach name [glob.globdir $realdir $pattern] {\n" +" lappend result [file join $realdir $name] $dir$name\n" +" }\n" +" }\n" +" return $result\n" +"}\n" +"\n" +"\n" +"\n" +"\n" +"\n" +"\n" +"\n" +"\n" +"\n" +"\n" +"\n" +"\n" +"proc glob {args} {\n" +" set nocomplain 0\n" +" set base \"\"\n" +" set tails 0\n" +"\n" +" set n 0\n" +" foreach arg $args {\n" +" if {[info exists param]} {\n" +" set $param $arg\n" +" unset param\n" +" incr n\n" +" continue\n" +" }\n" +" switch -glob -- $arg {\n" +" -d* {\n" +" set switch $arg\n" +" set param base\n" +" }\n" +" -n* {\n" +" set nocomplain 1\n" +" }\n" +" -ta* {\n" +" set tails 1\n" +" }\n" +" -- {\n" +" incr n\n" +" break\n" +" }\n" +" -* {\n" +" return -code error \"bad option \\\"$arg\\\": must be -directory, -nocomplain, -tails, or --\"\n" +" }\n" +" * {\n" +" break\n" +" }\n" +" }\n" +" incr n\n" +" }\n" +" if {[info exists param]} {\n" +" return -code error \"missing argument to \\\"$switch\\\"\"\n" +" }\n" +" if {[llength $args] <= $n} {\n" +" return -code error \"wrong # args: should be \\\"glob ?options? pattern ?pattern ...?\\\"\"\n" +" }\n" +"\n" +" set args [lrange $args $n end]\n" +"\n" +" set result {}\n" +" foreach pattern $args {\n" +" set escpattern [string map {\n" +" \\\\\\\\ \\x01 \\\\\\{ \\x02 \\\\\\} \\x03 \\\\, \\x04\n" +" } $pattern]\n" +" set patexps [lassign [glob.explode $escpattern] rest]\n" +" if {$rest ne \"\"} {\n" +" return -code error \"unmatched close brace in glob pattern\"\n" +" }\n" +" foreach patexp $patexps {\n" +" set patexp [string map {\n" +" \\x01 \\\\\\\\ \\x02 \\{ \\x03 \\} \\x04 ,\n" +" } $patexp]\n" +" foreach {realname name} [glob.glob $base $patexp] {\n" +" incr n\n" +" if {$tails} {\n" +" lappend result $name\n" +" } else {\n" +" lappend result [file join $base $name]\n" +" }\n" +" }\n" +" }\n" +" }\n" +"\n" +" if {!$nocomplain && [llength $result] == 0} {\n" +" set s $(([llength $args] > 1) ? \"s\" : \"\")\n" +" return -code error \"no files matched glob pattern$s \\\"[join $args]\\\"\"\n" +" }\n" +"\n" +" return $result\n" +"}\n" +); +} +int Jim_stdlibInit(Jim_Interp *interp) +{ + if (Jim_PackageProvide(interp, "stdlib", "1.0", JIM_ERRMSG)) + return JIM_ERR; + + return Jim_EvalSource(interp, "stdlib.tcl", 1, +"\n" +"\n" +"if {![exists -command ref]} {\n" +"\n" +" proc ref {args} {{count 0}} {\n" +" format %08x [incr count]\n" +" }\n" +"}\n" +"\n" +"\n" +"proc lambda {arglist args} {\n" +" tailcall proc [ref {} function lambda.finalizer] $arglist {*}$args\n" +"}\n" +"\n" +"proc lambda.finalizer {name val} {\n" +" rename $name {}\n" +"}\n" +"\n" +"\n" +"proc curry {args} {\n" +" alias [ref {} function lambda.finalizer] {*}$args\n" +"}\n" +"\n" +"\n" +"\n" +"\n" +"\n" +"\n" +"\n" +"\n" +"\n" +"proc function {value} {\n" +" return $value\n" +"}\n" +"\n" +"\n" +"\n" +"\n" +"proc stacktrace {{skip 0}} {\n" +" set trace {}\n" +" incr skip\n" +" foreach level [range $skip [info level]] {\n" +" lappend trace {*}[info frame -$level]\n" +" }\n" +" return $trace\n" +"}\n" +"\n" +"\n" +"proc stackdump {stacktrace} {\n" +" set lines {}\n" +" foreach {l f p} [lreverse $stacktrace] {\n" +" set line {}\n" +" if {$p ne \"\"} {\n" +" append line \"in procedure '$p' \"\n" +" if {$f ne \"\"} {\n" +" append line \"called \"\n" +" }\n" +" }\n" +" if {$f ne \"\"} {\n" +" append line \"at file \\\"$f\\\", line $l\"\n" +" }\n" +" if {$line ne \"\"} {\n" +" lappend lines $line\n" +" }\n" +" }\n" +" join $lines \\n\n" +"}\n" +"\n" +"\n" +"\n" +"proc defer {script} {\n" +" upvar jim::defer v\n" +" lappend v $script\n" +"}\n" +"\n" +"\n" +"\n" +"proc errorInfo {msg {stacktrace \"\"}} {\n" +" if {$stacktrace eq \"\"} {\n" +"\n" +" set stacktrace [info stacktrace]\n" +"\n" +" lappend stacktrace {*}[stacktrace 1]\n" +" }\n" +" lassign $stacktrace p f l\n" +" if {$f ne \"\"} {\n" +" set result \"$f:$l: Error: \"\n" +" }\n" +" append result \"$msg\\n\"\n" +" append result [stackdump $stacktrace]\n" +"\n" +"\n" +" string trim $result\n" +"}\n" +"\n" +"\n" +"\n" +"proc {info nameofexecutable} {} {\n" +" if {[exists ::jim::exe]} {\n" +" return $::jim::exe\n" +" }\n" +"}\n" +"\n" +"\n" +"proc {dict update} {&varName args script} {\n" +" set keys {}\n" +" foreach {n v} $args {\n" +" upvar $v var_$v\n" +" if {[dict exists $varName $n]} {\n" +" set var_$v [dict get $varName $n]\n" +" }\n" +" }\n" +" catch {uplevel 1 $script} msg opts\n" +" if {[info exists varName]} {\n" +" foreach {n v} $args {\n" +" if {[info exists var_$v]} {\n" +" dict set varName $n [set var_$v]\n" +" } else {\n" +" dict unset varName $n\n" +" }\n" +" }\n" +" }\n" +" return {*}$opts $msg\n" +"}\n" +"\n" +"proc {dict replace} {dictionary {args {key value}}} {\n" +" if {[llength ${key value}] % 2} {\n" +" tailcall {dict replace}\n" +" }\n" +" tailcall dict merge $dictionary ${key value}\n" +"}\n" +"\n" +"\n" +"proc {dict lappend} {varName key {args value}} {\n" +" upvar $varName dict\n" +" if {[exists dict] && [dict exists $dict $key]} {\n" +" set list [dict get $dict $key]\n" +" }\n" +" lappend list {*}$value\n" +" dict set dict $key $list\n" +"}\n" +"\n" +"\n" +"proc {dict append} {varName key {args value}} {\n" +" upvar $varName dict\n" +" if {[exists dict] && [dict exists $dict $key]} {\n" +" set str [dict get $dict $key]\n" +" }\n" +" append str {*}$value\n" +" dict set dict $key $str\n" +"}\n" +"\n" +"\n" +"proc {dict incr} {varName key {increment 1}} {\n" +" upvar $varName dict\n" +" if {[exists dict] && [dict exists $dict $key]} {\n" +" set value [dict get $dict $key]\n" +" }\n" +" incr value $increment\n" +" dict set dict $key $value\n" +"}\n" +"\n" +"\n" +"proc {dict remove} {dictionary {args key}} {\n" +" foreach k $key {\n" +" dict unset dictionary $k\n" +" }\n" +" return $dictionary\n" +"}\n" +"\n" +"\n" +"proc {dict for} {vars dictionary script} {\n" +" if {[llength $vars] != 2} {\n" +" return -code error \"must have exactly two variable names\"\n" +" }\n" +" dict size $dictionary\n" +" tailcall foreach $vars $dictionary $script\n" +"}\n" +); +} +int Jim_tclcompatInit(Jim_Interp *interp) +{ + if (Jim_PackageProvide(interp, "tclcompat", "1.0", JIM_ERRMSG)) + return JIM_ERR; + + return Jim_EvalSource(interp, "tclcompat.tcl", 1, +"\n" +"\n" +"\n" +"\n" +"\n" +"\n" +"\n" +"\n" +"set env [env]\n" +"\n" +"\n" +"if {[info commands stdout] ne \"\"} {\n" +"\n" +" foreach p {gets flush close eof seek tell} {\n" +" proc $p {chan args} {p} {\n" +" tailcall $chan $p {*}$args\n" +" }\n" +" }\n" +" unset p\n" +"\n" +"\n" +"\n" +" proc puts {{-nonewline {}} {chan stdout} msg} {\n" +" if {${-nonewline} ni {-nonewline {}}} {\n" +" tailcall ${-nonewline} puts $msg\n" +" }\n" +" tailcall $chan puts {*}${-nonewline} $msg\n" +" }\n" +"\n" +"\n" +"\n" +"\n" +"\n" +" proc read {{-nonewline {}} chan} {\n" +" if {${-nonewline} ni {-nonewline {}}} {\n" +" tailcall ${-nonewline} read {*}${chan}\n" +" }\n" +" tailcall $chan read {*}${-nonewline}\n" +" }\n" +"\n" +" proc fconfigure {f args} {\n" +" foreach {n v} $args {\n" +" switch -glob -- $n {\n" +" -bl* {\n" +" $f ndelay $(!$v)\n" +" }\n" +" -bu* {\n" +" $f buffering $v\n" +" }\n" +" -tr* {\n" +"\n" +" }\n" +" default {\n" +" return -code error \"fconfigure: unknown option $n\"\n" +" }\n" +" }\n" +" }\n" +" }\n" +"}\n" +"\n" +"\n" +"proc fileevent {args} {\n" +" tailcall {*}$args\n" +"}\n" +"\n" +"\n" +"\n" +"proc parray {arrayname {pattern *} {puts puts}} {\n" +" upvar $arrayname a\n" +"\n" +" set max 0\n" +" foreach name [array names a $pattern]] {\n" +" if {[string length $name] > $max} {\n" +" set max [string length $name]\n" +" }\n" +" }\n" +" incr max [string length $arrayname]\n" +" incr max 2\n" +" foreach name [lsort [array names a $pattern]] {\n" +" $puts [format \"%-${max}s = %s\" $arrayname\\($name\\) $a($name)]\n" +" }\n" +"}\n" +"\n" +"\n" +"proc {file copy} {{force {}} source target} {\n" +" try {\n" +" if {$force ni {{} -force}} {\n" +" error \"bad option \\\"$force\\\": should be -force\"\n" +" }\n" +"\n" +" set in [open $source rb]\n" +"\n" +" if {[file exists $target]} {\n" +" if {$force eq \"\"} {\n" +" error \"error copying \\\"$source\\\" to \\\"$target\\\": file already exists\"\n" +" }\n" +"\n" +" if {$source eq $target} {\n" +" return\n" +" }\n" +"\n" +"\n" +" file stat $source ss\n" +" file stat $target ts\n" +" if {$ss(dev) == $ts(dev) && $ss(ino) == $ts(ino) && $ss(ino)} {\n" +" return\n" +" }\n" +" }\n" +" set out [open $target wb]\n" +" $in copyto $out\n" +" $out close\n" +" } on error {msg opts} {\n" +" incr opts(-level)\n" +" return {*}$opts $msg\n" +" } finally {\n" +" catch {$in close}\n" +" }\n" +"}\n" +"\n" +"\n" +"\n" +"proc popen {cmd {mode r}} {\n" +" lassign [pipe] r w\n" +" try {\n" +" if {[string match \"w*\" $mode]} {\n" +" lappend cmd <@$r &\n" +" set pids [exec {*}$cmd]\n" +" $r close\n" +" set f $w\n" +" } else {\n" +" lappend cmd >@$w &\n" +" set pids [exec {*}$cmd]\n" +" $w close\n" +" set f $r\n" +" }\n" +" lambda {cmd args} {f pids} {\n" +" if {$cmd eq \"pid\"} {\n" +" return $pids\n" +" }\n" +" if {$cmd eq \"getfd\"} {\n" +" $f getfd\n" +" }\n" +" if {$cmd eq \"close\"} {\n" +" $f close\n" +"\n" +" set retopts {}\n" +" foreach p $pids {\n" +" lassign [wait $p] status - rc\n" +" if {$status eq \"CHILDSTATUS\"} {\n" +" if {$rc == 0} {\n" +" continue\n" +" }\n" +" set msg \"child process exited abnormally\"\n" +" } else {\n" +" set msg \"child killed: received signal\"\n" +" }\n" +" set retopts [list -code error -errorcode [list $status $p $rc] $msg]\n" +" }\n" +" return {*}$retopts\n" +" }\n" +" tailcall $f $cmd {*}$args\n" +" }\n" +" } on error {error opts} {\n" +" $r close\n" +" $w close\n" +" error $error\n" +" }\n" +"}\n" +"\n" +"\n" +"local proc pid {{channelId {}}} {\n" +" if {$channelId eq \"\"} {\n" +" tailcall upcall pid\n" +" }\n" +" if {[catch {$channelId tell}]} {\n" +" return -code error \"can not find channel named \\\"$channelId\\\"\"\n" +" }\n" +" if {[catch {$channelId pid} pids]} {\n" +" return \"\"\n" +" }\n" +" return $pids\n" +"}\n" +"\n" +"\n" +"\n" +"\n" +"\n" +"\n" +"\n" +"\n" +"\n" +"\n" +"proc try {args} {\n" +" set catchopts {}\n" +" while {[string match -* [lindex $args 0]]} {\n" +" set args [lassign $args opt]\n" +" if {$opt eq \"--\"} {\n" +" break\n" +" }\n" +" lappend catchopts $opt\n" +" }\n" +" if {[llength $args] == 0} {\n" +" return -code error {wrong # args: should be \"try ?options? script ?argument ...?\"}\n" +" }\n" +" set args [lassign $args script]\n" +" set code [catch -eval {*}$catchopts {uplevel 1 $script} msg opts]\n" +"\n" +" set handled 0\n" +"\n" +" foreach {on codes vars script} $args {\n" +" switch -- $on \\\n" +" on {\n" +" if {!$handled && ($codes eq \"*\" || [info returncode $code] in $codes)} {\n" +" lassign $vars msgvar optsvar\n" +" if {$msgvar ne \"\"} {\n" +" upvar $msgvar hmsg\n" +" set hmsg $msg\n" +" }\n" +" if {$optsvar ne \"\"} {\n" +" upvar $optsvar hopts\n" +" set hopts $opts\n" +" }\n" +"\n" +" set code [catch {uplevel 1 $script} msg opts]\n" +" incr handled\n" +" }\n" +" } \\\n" +" finally {\n" +" set finalcode [catch {uplevel 1 $codes} finalmsg finalopts]\n" +" if {$finalcode} {\n" +"\n" +" set code $finalcode\n" +" set msg $finalmsg\n" +" set opts $finalopts\n" +" }\n" +" break\n" +" } \\\n" +" default {\n" +" return -code error \"try: expected 'on' or 'finally', got '$on'\"\n" +" }\n" +" }\n" +"\n" +" if {$code} {\n" +" incr opts(-level)\n" +" return {*}$opts $msg\n" +" }\n" +" return $msg\n" +"}\n" +"\n" +"\n" +"\n" +"proc throw {code {msg \"\"}} {\n" +" return -code $code $msg\n" +"}\n" +"\n" +"\n" +"proc {file delete force} {path} {\n" +" foreach e [readdir $path] {\n" +" file delete -force $path/$e\n" +" }\n" +" file delete $path\n" +"}\n" +); +} + + +#ifndef _GNU_SOURCE +#define _GNU_SOURCE +#endif +#include +#include +#include +#include +#ifdef HAVE_UNISTD_H +#include +#include +#endif + + +#if defined(HAVE_SYS_SOCKET_H) && defined(HAVE_SELECT) && defined(HAVE_NETINET_IN_H) && defined(HAVE_NETDB_H) && defined(HAVE_ARPA_INET_H) +#include +#include +#include +#include +#include +#ifdef HAVE_SYS_UN_H +#include +#endif +#define HAVE_SOCKETS +#elif defined (__MINGW32__) + +#else +#define JIM_ANSIC +#endif + +#if defined(JIM_SSL) +#include +#include +#endif + +#ifdef HAVE_TERMIOS_H +#endif + + +#define AIO_CMD_LEN 32 +#define AIO_BUF_LEN 256 + +#ifndef HAVE_FTELLO + #define ftello ftell +#endif +#ifndef HAVE_FSEEKO + #define fseeko fseek +#endif + +#define AIO_KEEPOPEN 1 + +#if defined(JIM_IPV6) +#define IPV6 1 +#else +#define IPV6 0 +#ifndef PF_INET6 +#define PF_INET6 0 +#endif +#endif + +#ifdef JIM_ANSIC + +#undef HAVE_PIPE +#undef HAVE_SOCKETPAIR +#endif + + +struct AioFile; + +typedef struct { + int (*writer)(struct AioFile *af, const char *buf, int len); + int (*reader)(struct AioFile *af, char *buf, int len); + const char *(*getline)(struct AioFile *af, char *buf, int len); + int (*error)(const struct AioFile *af); + const char *(*strerror)(struct AioFile *af); + int (*verify)(struct AioFile *af); +} JimAioFopsType; + +typedef struct AioFile +{ + FILE *fp; + Jim_Obj *filename; + int type; + int openFlags; + int fd; + Jim_Obj *rEvent; + Jim_Obj *wEvent; + Jim_Obj *eEvent; + int addr_family; + void *ssl; + const JimAioFopsType *fops; +} AioFile; + +static int stdio_writer(struct AioFile *af, const char *buf, int len) +{ + return fwrite(buf, 1, len, af->fp); +} + +static int stdio_reader(struct AioFile *af, char *buf, int len) +{ + return fread(buf, 1, len, af->fp); +} + +static const char *stdio_getline(struct AioFile *af, char *buf, int len) +{ + return fgets(buf, len, af->fp); +} + +static int stdio_error(const AioFile *af) +{ + if (!ferror(af->fp)) { + return JIM_OK; + } + clearerr(af->fp); + + if (feof(af->fp) || errno == EAGAIN || errno == EINTR) { + return JIM_OK; + } +#ifdef ECONNRESET + if (errno == ECONNRESET) { + return JIM_OK; + } +#endif +#ifdef ECONNABORTED + if (errno == ECONNABORTED) { + return JIM_OK; + } +#endif + return JIM_ERR; +} + +static const char *stdio_strerror(struct AioFile *af) +{ + return strerror(errno); +} + +static const JimAioFopsType stdio_fops = { + stdio_writer, + stdio_reader, + stdio_getline, + stdio_error, + stdio_strerror, + NULL +}; + + +static int JimAioSubCmdProc(Jim_Interp *interp, int argc, Jim_Obj *const *argv); +static AioFile *JimMakeChannel(Jim_Interp *interp, FILE *fh, int fd, Jim_Obj *filename, + const char *hdlfmt, int family, const char *mode); + + +static const char *JimAioErrorString(AioFile *af) +{ + if (af && af->fops) + return af->fops->strerror(af); + + return strerror(errno); +} + +static void JimAioSetError(Jim_Interp *interp, Jim_Obj *name) +{ + AioFile *af = Jim_CmdPrivData(interp); + + if (name) { + Jim_SetResultFormatted(interp, "%#s: %s", name, JimAioErrorString(af)); + } + else { + Jim_SetResultString(interp, JimAioErrorString(af), -1); + } +} + +static int JimCheckStreamError(Jim_Interp *interp, AioFile *af) +{ + int ret = af->fops->error(af); + if (ret) { + JimAioSetError(interp, af->filename); + } + return ret; +} + +static void JimAioDelProc(Jim_Interp *interp, void *privData) +{ + AioFile *af = privData; + + JIM_NOTUSED(interp); + + Jim_DecrRefCount(interp, af->filename); + +#ifdef jim_ext_eventloop + + Jim_DeleteFileHandler(interp, af->fd, JIM_EVENT_READABLE | JIM_EVENT_WRITABLE | JIM_EVENT_EXCEPTION); +#endif + +#if defined(JIM_SSL) + if (af->ssl != NULL) { + SSL_free(af->ssl); + } +#endif + if (!(af->openFlags & AIO_KEEPOPEN)) { + fclose(af->fp); + } + + Jim_Free(af); +} + +static int aio_cmd_read(Jim_Interp *interp, int argc, Jim_Obj *const *argv) +{ + AioFile *af = Jim_CmdPrivData(interp); + char buf[AIO_BUF_LEN]; + Jim_Obj *objPtr; + int nonewline = 0; + jim_wide neededLen = -1; + + if (argc && Jim_CompareStringImmediate(interp, argv[0], "-nonewline")) { + nonewline = 1; + argv++; + argc--; + } + if (argc == 1) { + if (Jim_GetWide(interp, argv[0], &neededLen) != JIM_OK) + return JIM_ERR; + if (neededLen < 0) { + Jim_SetResultString(interp, "invalid parameter: negative len", -1); + return JIM_ERR; + } + } + else if (argc) { + return -1; + } + objPtr = Jim_NewStringObj(interp, NULL, 0); + while (neededLen != 0) { + int retval; + int readlen; + + if (neededLen == -1) { + readlen = AIO_BUF_LEN; + } + else { + readlen = (neededLen > AIO_BUF_LEN ? AIO_BUF_LEN : neededLen); + } + retval = af->fops->reader(af, buf, readlen); + if (retval > 0) { + Jim_AppendString(interp, objPtr, buf, retval); + if (neededLen != -1) { + neededLen -= retval; + } + } + if (retval != readlen) + break; + } + + if (JimCheckStreamError(interp, af)) { + Jim_FreeNewObj(interp, objPtr); + return JIM_ERR; + } + if (nonewline) { + int len; + const char *s = Jim_GetString(objPtr, &len); + + if (len > 0 && s[len - 1] == '\n') { + objPtr->length--; + objPtr->bytes[objPtr->length] = '\0'; + } + } + Jim_SetResult(interp, objPtr); + return JIM_OK; +} + +AioFile *Jim_AioFile(Jim_Interp *interp, Jim_Obj *command) +{ + Jim_Cmd *cmdPtr = Jim_GetCommand(interp, command, JIM_ERRMSG); + + + if (cmdPtr && !cmdPtr->isproc && cmdPtr->u.native.cmdProc == JimAioSubCmdProc) { + return (AioFile *) cmdPtr->u.native.privData; + } + Jim_SetResultFormatted(interp, "Not a filehandle: \"%#s\"", command); + return NULL; +} + +FILE *Jim_AioFilehandle(Jim_Interp *interp, Jim_Obj *command) +{ + AioFile *af; + + af = Jim_AioFile(interp, command); + if (af == NULL) { + return NULL; + } + + return af->fp; +} + +static int aio_cmd_getfd(Jim_Interp *interp, int argc, Jim_Obj *const *argv) +{ + AioFile *af = Jim_CmdPrivData(interp); + + fflush(af->fp); + Jim_SetResultInt(interp, fileno(af->fp)); + + return JIM_OK; +} + +static int aio_cmd_copy(Jim_Interp *interp, int argc, Jim_Obj *const *argv) +{ + AioFile *af = Jim_CmdPrivData(interp); + jim_wide count = 0; + jim_wide maxlen = JIM_WIDE_MAX; + AioFile *outf = Jim_AioFile(interp, argv[0]); + + if (outf == NULL) { + return JIM_ERR; + } + + if (argc == 2) { + if (Jim_GetWide(interp, argv[1], &maxlen) != JIM_OK) { + return JIM_ERR; + } + } + + while (count < maxlen) { + char ch; + + if (af->fops->reader(af, &ch, 1) != 1) { + break; + } + if (outf->fops->writer(outf, &ch, 1) != 1) { + break; + } + count++; + } + + if (JimCheckStreamError(interp, af) || JimCheckStreamError(interp, outf)) { + return JIM_ERR; + } + + Jim_SetResultInt(interp, count); + + return JIM_OK; +} + +static int aio_cmd_gets(Jim_Interp *interp, int argc, Jim_Obj *const *argv) +{ + AioFile *af = Jim_CmdPrivData(interp); + char buf[AIO_BUF_LEN]; + Jim_Obj *objPtr; + int len; + + errno = 0; + + objPtr = Jim_NewStringObj(interp, NULL, 0); + while (1) { + buf[AIO_BUF_LEN - 1] = '_'; + + if (af->fops->getline(af, buf, AIO_BUF_LEN) == NULL) + break; + + if (buf[AIO_BUF_LEN - 1] == '\0' && buf[AIO_BUF_LEN - 2] != '\n') { + Jim_AppendString(interp, objPtr, buf, AIO_BUF_LEN - 1); + } + else { + len = strlen(buf); + + if (len && (buf[len - 1] == '\n')) { + + len--; + } + + Jim_AppendString(interp, objPtr, buf, len); + break; + } + } + + if (JimCheckStreamError(interp, af)) { + + Jim_FreeNewObj(interp, objPtr); + return JIM_ERR; + } + + if (argc) { + if (Jim_SetVariable(interp, argv[0], objPtr) != JIM_OK) { + Jim_FreeNewObj(interp, objPtr); + return JIM_ERR; + } + + len = Jim_Length(objPtr); + + if (len == 0 && feof(af->fp)) { + + len = -1; + } + Jim_SetResultInt(interp, len); + } + else { + Jim_SetResult(interp, objPtr); + } + return JIM_OK; +} + +static int aio_cmd_puts(Jim_Interp *interp, int argc, Jim_Obj *const *argv) +{ + AioFile *af = Jim_CmdPrivData(interp); + int wlen; + const char *wdata; + Jim_Obj *strObj; + + if (argc == 2) { + if (!Jim_CompareStringImmediate(interp, argv[0], "-nonewline")) { + return -1; + } + strObj = argv[1]; + } + else { + strObj = argv[0]; + } + + wdata = Jim_GetString(strObj, &wlen); + if (af->fops->writer(af, wdata, wlen) == wlen) { + if (argc == 2 || af->fops->writer(af, "\n", 1) == 1) { + return JIM_OK; + } + } + JimAioSetError(interp, af->filename); + return JIM_ERR; +} + +static int aio_cmd_isatty(Jim_Interp *interp, int argc, Jim_Obj *const *argv) +{ +#ifdef HAVE_ISATTY + AioFile *af = Jim_CmdPrivData(interp); + Jim_SetResultInt(interp, isatty(fileno(af->fp))); +#else + Jim_SetResultInt(interp, 0); +#endif + + return JIM_OK; +} + + +static int aio_cmd_flush(Jim_Interp *interp, int argc, Jim_Obj *const *argv) +{ + AioFile *af = Jim_CmdPrivData(interp); + + if (fflush(af->fp) == EOF) { + JimAioSetError(interp, af->filename); + return JIM_ERR; + } + return JIM_OK; +} + +static int aio_cmd_eof(Jim_Interp *interp, int argc, Jim_Obj *const *argv) +{ + AioFile *af = Jim_CmdPrivData(interp); + + Jim_SetResultInt(interp, feof(af->fp)); + return JIM_OK; +} + +static int aio_cmd_close(Jim_Interp *interp, int argc, Jim_Obj *const *argv) +{ + if (argc == 3) { +#if defined(HAVE_SOCKETS) && defined(HAVE_SHUTDOWN) + static const char * const options[] = { "r", "w", NULL }; + enum { OPT_R, OPT_W, }; + int option; + AioFile *af = Jim_CmdPrivData(interp); + + if (Jim_GetEnum(interp, argv[2], options, &option, NULL, JIM_ERRMSG) != JIM_OK) { + return JIM_ERR; + } + if (shutdown(af->fd, option == OPT_R ? SHUT_RD : SHUT_WR) == 0) { + return JIM_OK; + } + JimAioSetError(interp, NULL); +#else + Jim_SetResultString(interp, "async close not supported", -1); +#endif + return JIM_ERR; + } + + return Jim_DeleteCommand(interp, Jim_String(argv[0])); +} + +static int aio_cmd_seek(Jim_Interp *interp, int argc, Jim_Obj *const *argv) +{ + AioFile *af = Jim_CmdPrivData(interp); + int orig = SEEK_SET; + jim_wide offset; + + if (argc == 2) { + if (Jim_CompareStringImmediate(interp, argv[1], "start")) + orig = SEEK_SET; + else if (Jim_CompareStringImmediate(interp, argv[1], "current")) + orig = SEEK_CUR; + else if (Jim_CompareStringImmediate(interp, argv[1], "end")) + orig = SEEK_END; + else { + return -1; + } + } + if (Jim_GetWide(interp, argv[0], &offset) != JIM_OK) { + return JIM_ERR; + } + if (fseeko(af->fp, offset, orig) == -1) { + JimAioSetError(interp, af->filename); + return JIM_ERR; + } + return JIM_OK; +} + +static int aio_cmd_tell(Jim_Interp *interp, int argc, Jim_Obj *const *argv) +{ + AioFile *af = Jim_CmdPrivData(interp); + + Jim_SetResultInt(interp, ftello(af->fp)); + return JIM_OK; +} + +static int aio_cmd_filename(Jim_Interp *interp, int argc, Jim_Obj *const *argv) +{ + AioFile *af = Jim_CmdPrivData(interp); + + Jim_SetResult(interp, af->filename); + return JIM_OK; +} + +#ifdef O_NDELAY +static int aio_cmd_ndelay(Jim_Interp *interp, int argc, Jim_Obj *const *argv) +{ + AioFile *af = Jim_CmdPrivData(interp); + + int fmode = fcntl(af->fd, F_GETFL); + + if (argc) { + long nb; + + if (Jim_GetLong(interp, argv[0], &nb) != JIM_OK) { + return JIM_ERR; + } + if (nb) { + fmode |= O_NDELAY; + } + else { + fmode &= ~O_NDELAY; + } + (void)fcntl(af->fd, F_SETFL, fmode); + } + Jim_SetResultInt(interp, (fmode & O_NONBLOCK) ? 1 : 0); + return JIM_OK; +} +#endif + + +#ifdef HAVE_FSYNC +static int aio_cmd_sync(Jim_Interp *interp, int argc, Jim_Obj *const *argv) +{ + AioFile *af = Jim_CmdPrivData(interp); + + fflush(af->fp); + fsync(af->fd); + return JIM_OK; +} +#endif + +static int aio_cmd_buffering(Jim_Interp *interp, int argc, Jim_Obj *const *argv) +{ + AioFile *af = Jim_CmdPrivData(interp); + + static const char * const options[] = { + "none", + "line", + "full", + NULL + }; + enum + { + OPT_NONE, + OPT_LINE, + OPT_FULL, + }; + int option; + + if (Jim_GetEnum(interp, argv[0], options, &option, NULL, JIM_ERRMSG) != JIM_OK) { + return JIM_ERR; + } + switch (option) { + case OPT_NONE: + setvbuf(af->fp, NULL, _IONBF, 0); + break; + case OPT_LINE: + setvbuf(af->fp, NULL, _IOLBF, BUFSIZ); + break; + case OPT_FULL: + setvbuf(af->fp, NULL, _IOFBF, BUFSIZ); + break; + } + return JIM_OK; +} + +#ifdef jim_ext_eventloop +static void JimAioFileEventFinalizer(Jim_Interp *interp, void *clientData) +{ + Jim_Obj **objPtrPtr = clientData; + + Jim_DecrRefCount(interp, *objPtrPtr); + *objPtrPtr = NULL; +} + +static int JimAioFileEventHandler(Jim_Interp *interp, void *clientData, int mask) +{ + Jim_Obj **objPtrPtr = clientData; + + return Jim_EvalObjBackground(interp, *objPtrPtr); +} + +static int aio_eventinfo(Jim_Interp *interp, AioFile * af, unsigned mask, Jim_Obj **scriptHandlerObj, + int argc, Jim_Obj * const *argv) +{ + if (argc == 0) { + + if (*scriptHandlerObj) { + Jim_SetResult(interp, *scriptHandlerObj); + } + return JIM_OK; + } + + if (*scriptHandlerObj) { + + Jim_DeleteFileHandler(interp, af->fd, mask); + } + + + if (Jim_Length(argv[0]) == 0) { + + return JIM_OK; + } + + + Jim_IncrRefCount(argv[0]); + *scriptHandlerObj = argv[0]; + + Jim_CreateFileHandler(interp, af->fd, mask, + JimAioFileEventHandler, scriptHandlerObj, JimAioFileEventFinalizer); + + return JIM_OK; +} + +static int aio_cmd_readable(Jim_Interp *interp, int argc, Jim_Obj *const *argv) +{ + AioFile *af = Jim_CmdPrivData(interp); + + return aio_eventinfo(interp, af, JIM_EVENT_READABLE, &af->rEvent, argc, argv); +} + +static int aio_cmd_writable(Jim_Interp *interp, int argc, Jim_Obj *const *argv) +{ + AioFile *af = Jim_CmdPrivData(interp); + + return aio_eventinfo(interp, af, JIM_EVENT_WRITABLE, &af->wEvent, argc, argv); +} + +static int aio_cmd_onexception(Jim_Interp *interp, int argc, Jim_Obj *const *argv) +{ + AioFile *af = Jim_CmdPrivData(interp); + + return aio_eventinfo(interp, af, JIM_EVENT_EXCEPTION, &af->eEvent, argc, argv); +} +#endif + + + + +static const jim_subcmd_type aio_command_table[] = { + { "read", + "?-nonewline? ?len?", + aio_cmd_read, + 0, + 2, + + }, + { "copyto", + "handle ?size?", + aio_cmd_copy, + 1, + 2, + + }, + { "getfd", + NULL, + aio_cmd_getfd, + 0, + 0, + + }, + { "gets", + "?var?", + aio_cmd_gets, + 0, + 1, + + }, + { "puts", + "?-nonewline? str", + aio_cmd_puts, + 1, + 2, + + }, + { "isatty", + NULL, + aio_cmd_isatty, + 0, + 0, + + }, + { "flush", + NULL, + aio_cmd_flush, + 0, + 0, + + }, + { "eof", + NULL, + aio_cmd_eof, + 0, + 0, + + }, + { "close", + "?r(ead)|w(rite)?", + aio_cmd_close, + 0, + 1, + JIM_MODFLAG_FULLARGV, + + }, + { "seek", + "offset ?start|current|end", + aio_cmd_seek, + 1, + 2, + + }, + { "tell", + NULL, + aio_cmd_tell, + 0, + 0, + + }, + { "filename", + NULL, + aio_cmd_filename, + 0, + 0, + + }, +#ifdef O_NDELAY + { "ndelay", + "?0|1?", + aio_cmd_ndelay, + 0, + 1, + + }, +#endif +#ifdef HAVE_FSYNC + { "sync", + NULL, + aio_cmd_sync, + 0, + 0, + + }, +#endif + { "buffering", + "none|line|full", + aio_cmd_buffering, + 1, + 1, + + }, +#ifdef jim_ext_eventloop + { "readable", + "?readable-script?", + aio_cmd_readable, + 0, + 1, + + }, + { "writable", + "?writable-script?", + aio_cmd_writable, + 0, + 1, + + }, + { "onexception", + "?exception-script?", + aio_cmd_onexception, + 0, + 1, + + }, +#endif + { NULL } +}; + +static int JimAioSubCmdProc(Jim_Interp *interp, int argc, Jim_Obj *const *argv) +{ + return Jim_CallSubCmd(interp, Jim_ParseSubCmd(interp, aio_command_table, argc, argv), argc, argv); +} + +static int JimAioOpenCommand(Jim_Interp *interp, int argc, + Jim_Obj *const *argv) +{ + const char *mode; + + if (argc != 2 && argc != 3) { + Jim_WrongNumArgs(interp, 1, argv, "filename ?mode?"); + return JIM_ERR; + } + + mode = (argc == 3) ? Jim_String(argv[2]) : "r"; + +#ifdef jim_ext_tclcompat + { + const char *filename = Jim_String(argv[1]); + + + if (*filename == '|') { + Jim_Obj *evalObj[3]; + + evalObj[0] = Jim_NewStringObj(interp, "::popen", -1); + evalObj[1] = Jim_NewStringObj(interp, filename + 1, -1); + evalObj[2] = Jim_NewStringObj(interp, mode, -1); + + return Jim_EvalObjVector(interp, 3, evalObj); + } + } +#endif + return JimMakeChannel(interp, NULL, -1, argv[1], "aio.handle%ld", 0, mode) ? JIM_OK : JIM_ERR; +} + + +static AioFile *JimMakeChannel(Jim_Interp *interp, FILE *fh, int fd, Jim_Obj *filename, + const char *hdlfmt, int family, const char *mode) +{ + AioFile *af; + char buf[AIO_CMD_LEN]; + int openFlags = 0; + + snprintf(buf, sizeof(buf), hdlfmt, Jim_GetId(interp)); + + if (fh) { + openFlags = AIO_KEEPOPEN; + } + + snprintf(buf, sizeof(buf), hdlfmt, Jim_GetId(interp)); + if (!filename) { + filename = Jim_NewStringObj(interp, buf, -1); + } + + Jim_IncrRefCount(filename); + + if (fh == NULL) { + if (fd >= 0) { +#ifndef JIM_ANSIC + fh = fdopen(fd, mode); +#endif + } + else + fh = fopen(Jim_String(filename), mode); + + if (fh == NULL) { + JimAioSetError(interp, filename); +#ifndef JIM_ANSIC + if (fd >= 0) { + close(fd); + } +#endif + Jim_DecrRefCount(interp, filename); + return NULL; + } + } + + + af = Jim_Alloc(sizeof(*af)); + memset(af, 0, sizeof(*af)); + af->fp = fh; + af->filename = filename; + af->openFlags = openFlags; +#ifndef JIM_ANSIC + af->fd = fileno(fh); +#ifdef FD_CLOEXEC + if ((openFlags & AIO_KEEPOPEN) == 0) { + (void)fcntl(af->fd, F_SETFD, FD_CLOEXEC); + } +#endif +#endif + af->addr_family = family; + af->fops = &stdio_fops; + af->ssl = NULL; + + Jim_CreateCommand(interp, buf, JimAioSubCmdProc, af, JimAioDelProc); + + Jim_SetResult(interp, Jim_MakeGlobalNamespaceName(interp, Jim_NewStringObj(interp, buf, -1))); + + return af; +} + +#if defined(HAVE_PIPE) || (defined(HAVE_SOCKETPAIR) && defined(HAVE_SYS_UN_H)) +static int JimMakeChannelPair(Jim_Interp *interp, int p[2], Jim_Obj *filename, + const char *hdlfmt, int family, const char *mode[2]) +{ + if (JimMakeChannel(interp, NULL, p[0], filename, hdlfmt, family, mode[0])) { + Jim_Obj *objPtr = Jim_NewListObj(interp, NULL, 0); + Jim_ListAppendElement(interp, objPtr, Jim_GetResult(interp)); + if (JimMakeChannel(interp, NULL, p[1], filename, hdlfmt, family, mode[1])) { + Jim_ListAppendElement(interp, objPtr, Jim_GetResult(interp)); + Jim_SetResult(interp, objPtr); + return JIM_OK; + } + } + + + close(p[0]); + close(p[1]); + JimAioSetError(interp, NULL); + return JIM_ERR; +} +#endif + +#ifdef HAVE_PIPE +static int JimAioPipeCommand(Jim_Interp *interp, int argc, Jim_Obj *const *argv) +{ + int p[2]; + static const char *mode[2] = { "r", "w" }; + + if (argc != 1) { + Jim_WrongNumArgs(interp, 1, argv, ""); + return JIM_ERR; + } + + if (pipe(p) != 0) { + JimAioSetError(interp, NULL); + return JIM_ERR; + } + + return JimMakeChannelPair(interp, p, argv[0], "aio.pipe%ld", 0, mode); +} +#endif + + + +int Jim_aioInit(Jim_Interp *interp) +{ + if (Jim_PackageProvide(interp, "aio", "1.0", JIM_ERRMSG)) + return JIM_ERR; + +#if defined(JIM_SSL) + Jim_CreateCommand(interp, "load_ssl_certs", JimAioLoadSSLCertsCommand, NULL, NULL); +#endif + + Jim_CreateCommand(interp, "open", JimAioOpenCommand, NULL, NULL); +#ifdef HAVE_SOCKETS + Jim_CreateCommand(interp, "socket", JimAioSockCommand, NULL, NULL); +#endif +#ifdef HAVE_PIPE + Jim_CreateCommand(interp, "pipe", JimAioPipeCommand, NULL, NULL); +#endif + + + JimMakeChannel(interp, stdin, -1, NULL, "stdin", 0, "r"); + JimMakeChannel(interp, stdout, -1, NULL, "stdout", 0, "w"); + JimMakeChannel(interp, stderr, -1, NULL, "stderr", 0, "w"); + + return JIM_OK; +} + +#include +#include +#include + + +#ifdef HAVE_DIRENT_H +#include +#endif + +int Jim_ReaddirCmd(Jim_Interp *interp, int argc, Jim_Obj *const *argv) +{ + const char *dirPath; + DIR *dirPtr; + struct dirent *entryPtr; + int nocomplain = 0; + + if (argc == 3 && Jim_CompareStringImmediate(interp, argv[1], "-nocomplain")) { + nocomplain = 1; + } + if (argc != 2 && !nocomplain) { + Jim_WrongNumArgs(interp, 1, argv, "?-nocomplain? dirPath"); + return JIM_ERR; + } + + dirPath = Jim_String(argv[1 + nocomplain]); + + dirPtr = opendir(dirPath); + if (dirPtr == NULL) { + if (nocomplain) { + return JIM_OK; + } + Jim_SetResultString(interp, strerror(errno), -1); + return JIM_ERR; + } + else { + Jim_Obj *listObj = Jim_NewListObj(interp, NULL, 0); + + while ((entryPtr = readdir(dirPtr)) != NULL) { + if (entryPtr->d_name[0] == '.') { + if (entryPtr->d_name[1] == '\0') { + continue; + } + if ((entryPtr->d_name[1] == '.') && (entryPtr->d_name[2] == '\0')) + continue; + } + Jim_ListAppendElement(interp, listObj, Jim_NewStringObj(interp, entryPtr->d_name, -1)); + } + closedir(dirPtr); + + Jim_SetResult(interp, listObj); + + return JIM_OK; + } +} + +int Jim_readdirInit(Jim_Interp *interp) +{ + if (Jim_PackageProvide(interp, "readdir", "1.0", JIM_ERRMSG)) + return JIM_ERR; + + Jim_CreateCommand(interp, "readdir", Jim_ReaddirCmd, NULL, NULL); + return JIM_OK; +} + +#include +#include + +#if defined(JIM_REGEXP) +#else + #include +#endif + +static void FreeRegexpInternalRep(Jim_Interp *interp, Jim_Obj *objPtr) +{ + regfree(objPtr->internalRep.ptrIntValue.ptr); + Jim_Free(objPtr->internalRep.ptrIntValue.ptr); +} + +static const Jim_ObjType regexpObjType = { + "regexp", + FreeRegexpInternalRep, + NULL, + NULL, + JIM_TYPE_NONE +}; + +static regex_t *SetRegexpFromAny(Jim_Interp *interp, Jim_Obj *objPtr, unsigned flags) +{ + regex_t *compre; + const char *pattern; + int ret; + + + if (objPtr->typePtr == ®expObjType && + objPtr->internalRep.ptrIntValue.ptr && objPtr->internalRep.ptrIntValue.int1 == flags) { + + return objPtr->internalRep.ptrIntValue.ptr; + } + + + + + pattern = Jim_String(objPtr); + compre = Jim_Alloc(sizeof(regex_t)); + + if ((ret = regcomp(compre, pattern, REG_EXTENDED | flags)) != 0) { + char buf[100]; + + regerror(ret, compre, buf, sizeof(buf)); + Jim_SetResultFormatted(interp, "couldn't compile regular expression pattern: %s", buf); + regfree(compre); + Jim_Free(compre); + return NULL; + } + + Jim_FreeIntRep(interp, objPtr); + + objPtr->typePtr = ®expObjType; + objPtr->internalRep.ptrIntValue.int1 = flags; + objPtr->internalRep.ptrIntValue.ptr = compre; + + return compre; +} + +int Jim_RegexpCmd(Jim_Interp *interp, int argc, Jim_Obj *const *argv) +{ + int opt_indices = 0; + int opt_all = 0; + int opt_inline = 0; + regex_t *regex; + int match, i, j; + int offset = 0; + regmatch_t *pmatch = NULL; + int source_len; + int result = JIM_OK; + const char *pattern; + const char *source_str; + int num_matches = 0; + int num_vars; + Jim_Obj *resultListObj = NULL; + int regcomp_flags = 0; + int eflags = 0; + int option; + enum { + OPT_INDICES, OPT_NOCASE, OPT_LINE, OPT_ALL, OPT_INLINE, OPT_START, OPT_END + }; + static const char * const options[] = { + "-indices", "-nocase", "-line", "-all", "-inline", "-start", "--", NULL + }; + + if (argc < 3) { + wrongNumArgs: + Jim_WrongNumArgs(interp, 1, argv, + "?-switch ...? exp string ?matchVar? ?subMatchVar ...?"); + return JIM_ERR; + } + + for (i = 1; i < argc; i++) { + const char *opt = Jim_String(argv[i]); + + if (*opt != '-') { + break; + } + if (Jim_GetEnum(interp, argv[i], options, &option, "switch", JIM_ERRMSG | JIM_ENUM_ABBREV) != JIM_OK) { + return JIM_ERR; + } + if (option == OPT_END) { + i++; + break; + } + switch (option) { + case OPT_INDICES: + opt_indices = 1; + break; + + case OPT_NOCASE: + regcomp_flags |= REG_ICASE; + break; + + case OPT_LINE: + regcomp_flags |= REG_NEWLINE; + break; + + case OPT_ALL: + opt_all = 1; + break; + + case OPT_INLINE: + opt_inline = 1; + break; + + case OPT_START: + if (++i == argc) { + goto wrongNumArgs; + } + if (Jim_GetIndex(interp, argv[i], &offset) != JIM_OK) { + return JIM_ERR; + } + break; + } + } + if (argc - i < 2) { + goto wrongNumArgs; + } + + regex = SetRegexpFromAny(interp, argv[i], regcomp_flags); + if (!regex) { + return JIM_ERR; + } + + pattern = Jim_String(argv[i]); + source_str = Jim_GetString(argv[i + 1], &source_len); + + num_vars = argc - i - 2; + + if (opt_inline) { + if (num_vars) { + Jim_SetResultString(interp, "regexp match variables not allowed when using -inline", + -1); + result = JIM_ERR; + goto done; + } + num_vars = regex->re_nsub + 1; + } + + pmatch = Jim_Alloc((num_vars + 1) * sizeof(*pmatch)); + + if (offset) { + if (offset < 0) { + offset += source_len + 1; + } + if (offset > source_len) { + source_str += source_len; + } + else if (offset > 0) { + source_str += offset; + } + eflags |= REG_NOTBOL; + } + + if (opt_inline) { + resultListObj = Jim_NewListObj(interp, NULL, 0); + } + + next_match: + match = regexec(regex, source_str, num_vars + 1, pmatch, eflags); + if (match >= REG_BADPAT) { + char buf[100]; + + regerror(match, regex, buf, sizeof(buf)); + Jim_SetResultFormatted(interp, "error while matching pattern: %s", buf); + result = JIM_ERR; + goto done; + } + + if (match == REG_NOMATCH) { + goto done; + } + + num_matches++; + + if (opt_all && !opt_inline) { + + goto try_next_match; + } + + + j = 0; + for (i += 2; opt_inline ? j < num_vars : i < argc; i++, j++) { + Jim_Obj *resultObj; + + if (opt_indices) { + resultObj = Jim_NewListObj(interp, NULL, 0); + } + else { + resultObj = Jim_NewStringObj(interp, "", 0); + } + + if (pmatch[j].rm_so == -1) { + if (opt_indices) { + Jim_ListAppendElement(interp, resultObj, Jim_NewIntObj(interp, -1)); + Jim_ListAppendElement(interp, resultObj, Jim_NewIntObj(interp, -1)); + } + } + else { + int len = pmatch[j].rm_eo - pmatch[j].rm_so; + + if (opt_indices) { + Jim_ListAppendElement(interp, resultObj, Jim_NewIntObj(interp, + offset + pmatch[j].rm_so)); + Jim_ListAppendElement(interp, resultObj, Jim_NewIntObj(interp, + offset + pmatch[j].rm_so + len - 1)); + } + else { + Jim_AppendString(interp, resultObj, source_str + pmatch[j].rm_so, len); + } + } + + if (opt_inline) { + Jim_ListAppendElement(interp, resultListObj, resultObj); + } + else { + + result = Jim_SetVariable(interp, argv[i], resultObj); + + if (result != JIM_OK) { + Jim_FreeObj(interp, resultObj); + break; + } + } + } + + try_next_match: + if (opt_all && (pattern[0] != '^' || (regcomp_flags & REG_NEWLINE)) && *source_str) { + if (pmatch[0].rm_eo) { + offset += pmatch[0].rm_eo; + source_str += pmatch[0].rm_eo; + } + else { + source_str++; + offset++; + } + if (*source_str) { + eflags = REG_NOTBOL; + goto next_match; + } + } + + done: + if (result == JIM_OK) { + if (opt_inline) { + Jim_SetResult(interp, resultListObj); + } + else { + Jim_SetResultInt(interp, num_matches); + } + } + + Jim_Free(pmatch); + return result; +} + +#define MAX_SUB_MATCHES 50 + +int Jim_RegsubCmd(Jim_Interp *interp, int argc, Jim_Obj *const *argv) +{ + int regcomp_flags = 0; + int regexec_flags = 0; + int opt_all = 0; + int offset = 0; + regex_t *regex; + const char *p; + int result; + regmatch_t pmatch[MAX_SUB_MATCHES + 1]; + int num_matches = 0; + + int i, j, n; + Jim_Obj *varname; + Jim_Obj *resultObj; + const char *source_str; + int source_len; + const char *replace_str; + int replace_len; + const char *pattern; + int option; + enum { + OPT_NOCASE, OPT_LINE, OPT_ALL, OPT_START, OPT_END + }; + static const char * const options[] = { + "-nocase", "-line", "-all", "-start", "--", NULL + }; + + if (argc < 4) { + wrongNumArgs: + Jim_WrongNumArgs(interp, 1, argv, + "?-switch ...? exp string subSpec ?varName?"); + return JIM_ERR; + } + + for (i = 1; i < argc; i++) { + const char *opt = Jim_String(argv[i]); + + if (*opt != '-') { + break; + } + if (Jim_GetEnum(interp, argv[i], options, &option, "switch", JIM_ERRMSG | JIM_ENUM_ABBREV) != JIM_OK) { + return JIM_ERR; + } + if (option == OPT_END) { + i++; + break; + } + switch (option) { + case OPT_NOCASE: + regcomp_flags |= REG_ICASE; + break; + + case OPT_LINE: + regcomp_flags |= REG_NEWLINE; + break; + + case OPT_ALL: + opt_all = 1; + break; + + case OPT_START: + if (++i == argc) { + goto wrongNumArgs; + } + if (Jim_GetIndex(interp, argv[i], &offset) != JIM_OK) { + return JIM_ERR; + } + break; + } + } + if (argc - i != 3 && argc - i != 4) { + goto wrongNumArgs; + } + + regex = SetRegexpFromAny(interp, argv[i], regcomp_flags); + if (!regex) { + return JIM_ERR; + } + pattern = Jim_String(argv[i]); + + source_str = Jim_GetString(argv[i + 1], &source_len); + replace_str = Jim_GetString(argv[i + 2], &replace_len); + varname = argv[i + 3]; + + + resultObj = Jim_NewStringObj(interp, "", 0); + + if (offset) { + if (offset < 0) { + offset += source_len + 1; + } + if (offset > source_len) { + offset = source_len; + } + else if (offset < 0) { + offset = 0; + } + } + + + Jim_AppendString(interp, resultObj, source_str, offset); + + + n = source_len - offset; + p = source_str + offset; + do { + int match = regexec(regex, p, MAX_SUB_MATCHES, pmatch, regexec_flags); + + if (match >= REG_BADPAT) { + char buf[100]; + + regerror(match, regex, buf, sizeof(buf)); + Jim_SetResultFormatted(interp, "error while matching pattern: %s", buf); + return JIM_ERR; + } + if (match == REG_NOMATCH) { + break; + } + + num_matches++; + + Jim_AppendString(interp, resultObj, p, pmatch[0].rm_so); + + + for (j = 0; j < replace_len; j++) { + int idx; + int c = replace_str[j]; + + if (c == '&') { + idx = 0; + } + else if (c == '\\' && j < replace_len) { + c = replace_str[++j]; + if ((c >= '0') && (c <= '9')) { + idx = c - '0'; + } + else if ((c == '\\') || (c == '&')) { + Jim_AppendString(interp, resultObj, replace_str + j, 1); + continue; + } + else { + Jim_AppendString(interp, resultObj, replace_str + j - 1, (j == replace_len) ? 1 : 2); + continue; + } + } + else { + Jim_AppendString(interp, resultObj, replace_str + j, 1); + continue; + } + if ((idx < MAX_SUB_MATCHES) && pmatch[idx].rm_so != -1 && pmatch[idx].rm_eo != -1) { + Jim_AppendString(interp, resultObj, p + pmatch[idx].rm_so, + pmatch[idx].rm_eo - pmatch[idx].rm_so); + } + } + + p += pmatch[0].rm_eo; + n -= pmatch[0].rm_eo; + + + if (!opt_all || n == 0) { + break; + } + + + if ((regcomp_flags & REG_NEWLINE) == 0 && pattern[0] == '^') { + break; + } + + + if (pattern[0] == '\0' && n) { + + Jim_AppendString(interp, resultObj, p, 1); + p++; + n--; + } + + regexec_flags |= REG_NOTBOL; + } while (n); + + Jim_AppendString(interp, resultObj, p, -1); + + + if (argc - i == 4) { + result = Jim_SetVariable(interp, varname, resultObj); + + if (result == JIM_OK) { + Jim_SetResultInt(interp, num_matches); + } + else { + Jim_FreeObj(interp, resultObj); + } + } + else { + Jim_SetResult(interp, resultObj); + result = JIM_OK; + } + + return result; +} + +int Jim_regexpInit(Jim_Interp *interp) +{ + if (Jim_PackageProvide(interp, "regexp", "1.0", JIM_ERRMSG)) + return JIM_ERR; + + Jim_CreateCommand(interp, "regexp", Jim_RegexpCmd, NULL, NULL); + Jim_CreateCommand(interp, "regsub", Jim_RegsubCmd, NULL, NULL); + return JIM_OK; +} + +#include +#include +#include +#include +#include +#include + + +#ifdef HAVE_UTIMES +#include +#endif +#ifdef HAVE_UNISTD_H +#include +#elif defined(_MSC_VER) +#include +#define F_OK 0 +#define W_OK 2 +#define R_OK 4 +#define S_ISREG(m) (((m) & S_IFMT) == S_IFREG) +#define S_ISDIR(m) (((m) & S_IFMT) == S_IFDIR) +#endif + +# ifndef MAXPATHLEN +# define MAXPATHLEN JIM_PATH_LEN +# endif + +#if defined(__MINGW32__) || defined(__MSYS__) || defined(_MSC_VER) +#define ISWINDOWS 1 +#else +#define ISWINDOWS 0 +#endif + + +#if defined(HAVE_STRUCT_STAT_ST_MTIMESPEC) + #define STAT_MTIME_US(STAT) ((STAT).st_mtimespec.tv_sec * 1000000ll + (STAT).st_mtimespec.tv_nsec / 1000) +#elif defined(HAVE_STRUCT_STAT_ST_MTIM) + #define STAT_MTIME_US(STAT) ((STAT).st_mtim.tv_sec * 1000000ll + (STAT).st_mtim.tv_nsec / 1000) +#endif + + +static const char *JimGetFileType(int mode) +{ + if (S_ISREG(mode)) { + return "file"; + } + else if (S_ISDIR(mode)) { + return "directory"; + } +#ifdef S_ISCHR + else if (S_ISCHR(mode)) { + return "characterSpecial"; + } +#endif +#ifdef S_ISBLK + else if (S_ISBLK(mode)) { + return "blockSpecial"; + } +#endif +#ifdef S_ISFIFO + else if (S_ISFIFO(mode)) { + return "fifo"; + } +#endif +#ifdef S_ISLNK + else if (S_ISLNK(mode)) { + return "link"; + } +#endif +#ifdef S_ISSOCK + else if (S_ISSOCK(mode)) { + return "socket"; + } +#endif + return "unknown"; +} + +static void AppendStatElement(Jim_Interp *interp, Jim_Obj *listObj, const char *key, jim_wide value) +{ + Jim_ListAppendElement(interp, listObj, Jim_NewStringObj(interp, key, -1)); + Jim_ListAppendElement(interp, listObj, Jim_NewIntObj(interp, value)); +} + +static int StoreStatData(Jim_Interp *interp, Jim_Obj *varName, const struct stat *sb) +{ + + Jim_Obj *listObj = Jim_NewListObj(interp, NULL, 0); + + AppendStatElement(interp, listObj, "dev", sb->st_dev); + AppendStatElement(interp, listObj, "ino", sb->st_ino); + AppendStatElement(interp, listObj, "mode", sb->st_mode); + AppendStatElement(interp, listObj, "nlink", sb->st_nlink); + AppendStatElement(interp, listObj, "uid", sb->st_uid); + AppendStatElement(interp, listObj, "gid", sb->st_gid); + AppendStatElement(interp, listObj, "size", sb->st_size); + AppendStatElement(interp, listObj, "atime", sb->st_atime); + AppendStatElement(interp, listObj, "mtime", sb->st_mtime); + AppendStatElement(interp, listObj, "ctime", sb->st_ctime); +#ifdef STAT_MTIME_US + AppendStatElement(interp, listObj, "mtimeus", STAT_MTIME_US(*sb)); +#endif + Jim_ListAppendElement(interp, listObj, Jim_NewStringObj(interp, "type", -1)); + Jim_ListAppendElement(interp, listObj, Jim_NewStringObj(interp, JimGetFileType((int)sb->st_mode), -1)); + + + if (varName) { + Jim_Obj *objPtr; + objPtr = Jim_GetVariable(interp, varName, JIM_NONE); + + if (objPtr) { + Jim_Obj *objv[2]; + + objv[0] = objPtr; + objv[1] = listObj; + + objPtr = Jim_DictMerge(interp, 2, objv); + if (objPtr == NULL) { + + Jim_SetResultFormatted(interp, "can't set \"%#s(dev)\": variable isn't array", varName); + Jim_FreeNewObj(interp, listObj); + return JIM_ERR; + } + + Jim_InvalidateStringRep(objPtr); + + Jim_FreeNewObj(interp, listObj); + listObj = objPtr; + } + Jim_SetVariable(interp, varName, listObj); + } + + + Jim_SetResult(interp, listObj); + + return JIM_OK; +} + +static int file_cmd_dirname(Jim_Interp *interp, int argc, Jim_Obj *const *argv) +{ + const char *path = Jim_String(argv[0]); + const char *p = strrchr(path, '/'); + + if (!p && path[0] == '.' && path[1] == '.' && path[2] == '\0') { + Jim_SetResultString(interp, "..", -1); + } else if (!p) { + Jim_SetResultString(interp, ".", -1); + } + else if (p == path) { + Jim_SetResultString(interp, "/", -1); + } + else if (ISWINDOWS && p[-1] == ':') { + + Jim_SetResultString(interp, path, p - path + 1); + } + else { + Jim_SetResultString(interp, path, p - path); + } + return JIM_OK; +} + +static int file_cmd_rootname(Jim_Interp *interp, int argc, Jim_Obj *const *argv) +{ + const char *path = Jim_String(argv[0]); + const char *lastSlash = strrchr(path, '/'); + const char *p = strrchr(path, '.'); + + if (p == NULL || (lastSlash != NULL && lastSlash > p)) { + Jim_SetResult(interp, argv[0]); + } + else { + Jim_SetResultString(interp, path, p - path); + } + return JIM_OK; +} + +static int file_cmd_extension(Jim_Interp *interp, int argc, Jim_Obj *const *argv) +{ + const char *path = Jim_String(argv[0]); + const char *lastSlash = strrchr(path, '/'); + const char *p = strrchr(path, '.'); + + if (p == NULL || (lastSlash != NULL && lastSlash >= p)) { + p = ""; + } + Jim_SetResultString(interp, p, -1); + return JIM_OK; +} + +static int file_cmd_tail(Jim_Interp *interp, int argc, Jim_Obj *const *argv) +{ + const char *path = Jim_String(argv[0]); + const char *lastSlash = strrchr(path, '/'); + + if (lastSlash) { + Jim_SetResultString(interp, lastSlash + 1, -1); + } + else { + Jim_SetResult(interp, argv[0]); + } + return JIM_OK; +} + +static int file_cmd_normalize(Jim_Interp *interp, int argc, Jim_Obj *const *argv) +{ +#ifdef HAVE_REALPATH + const char *path = Jim_String(argv[0]); + char *newname = Jim_Alloc(MAXPATHLEN + 1); + + if (realpath(path, newname)) { + Jim_SetResult(interp, Jim_NewStringObjNoAlloc(interp, newname, -1)); + return JIM_OK; + } + else { + Jim_Free(newname); + Jim_SetResultFormatted(interp, "can't normalize \"%#s\": %s", argv[0], strerror(errno)); + return JIM_ERR; + } +#else + Jim_SetResultString(interp, "Not implemented", -1); + return JIM_ERR; +#endif +} + +static int file_cmd_join(Jim_Interp *interp, int argc, Jim_Obj *const *argv) +{ + int i; + char *newname = Jim_Alloc(MAXPATHLEN + 1); + char *last = newname; + + *newname = 0; + + + for (i = 0; i < argc; i++) { + int len; + const char *part = Jim_GetString(argv[i], &len); + + if (*part == '/') { + + last = newname; + } + else if (ISWINDOWS && strchr(part, ':')) { + + last = newname; + } + else if (part[0] == '.') { + if (part[1] == '/') { + part += 2; + len -= 2; + } + else if (part[1] == 0 && last != newname) { + + continue; + } + } + + + if (last != newname && last[-1] != '/') { + *last++ = '/'; + } + + if (len) { + if (last + len - newname >= MAXPATHLEN) { + Jim_Free(newname); + Jim_SetResultString(interp, "Path too long", -1); + return JIM_ERR; + } + memcpy(last, part, len); + last += len; + } + + + if (last > newname + 1 && last[-1] == '/') { + + if (!ISWINDOWS || !(last > newname + 2 && last[-2] == ':')) { + *--last = 0; + } + } + } + + *last = 0; + + + + Jim_SetResult(interp, Jim_NewStringObjNoAlloc(interp, newname, last - newname)); + + return JIM_OK; +} + +static int file_access(Jim_Interp *interp, Jim_Obj *filename, int mode) +{ + Jim_SetResultBool(interp, access(Jim_String(filename), mode) != -1); + + return JIM_OK; +} + +static int file_cmd_readable(Jim_Interp *interp, int argc, Jim_Obj *const *argv) +{ + return file_access(interp, argv[0], R_OK); +} + +static int file_cmd_writable(Jim_Interp *interp, int argc, Jim_Obj *const *argv) +{ + return file_access(interp, argv[0], W_OK); +} + +static int file_cmd_executable(Jim_Interp *interp, int argc, Jim_Obj *const *argv) +{ +#ifdef X_OK + return file_access(interp, argv[0], X_OK); +#else + + Jim_SetResultBool(interp, 1); + return JIM_OK; +#endif +} + +static int file_cmd_exists(Jim_Interp *interp, int argc, Jim_Obj *const *argv) +{ + return file_access(interp, argv[0], F_OK); +} + +static int file_cmd_delete(Jim_Interp *interp, int argc, Jim_Obj *const *argv) +{ + int force = Jim_CompareStringImmediate(interp, argv[0], "-force"); + + if (force || Jim_CompareStringImmediate(interp, argv[0], "--")) { + argc++; + argv--; + } + + while (argc--) { + const char *path = Jim_String(argv[0]); + + if (unlink(path) == -1 && errno != ENOENT) { + if (rmdir(path) == -1) { + + if (!force || Jim_EvalPrefix(interp, "file delete force", 1, argv) != JIM_OK) { + Jim_SetResultFormatted(interp, "couldn't delete file \"%s\": %s", path, + strerror(errno)); + return JIM_ERR; + } + } + } + argv++; + } + return JIM_OK; +} + +#ifdef HAVE_MKDIR_ONE_ARG +#define MKDIR_DEFAULT(PATHNAME) mkdir(PATHNAME) +#else +#define MKDIR_DEFAULT(PATHNAME) mkdir(PATHNAME, 0755) +#endif + +static int mkdir_all(char *path) +{ + int ok = 1; + + + goto first; + + while (ok--) { + + { + char *slash = strrchr(path, '/'); + + if (slash && slash != path) { + *slash = 0; + if (mkdir_all(path) != 0) { + return -1; + } + *slash = '/'; + } + } + first: + if (MKDIR_DEFAULT(path) == 0) { + return 0; + } + if (errno == ENOENT) { + + continue; + } + + if (errno == EEXIST) { + struct stat sb; + + if (stat(path, &sb) == 0 && S_ISDIR(sb.st_mode)) { + return 0; + } + + errno = EEXIST; + } + + break; + } + return -1; +} + +static int file_cmd_mkdir(Jim_Interp *interp, int argc, Jim_Obj *const *argv) +{ + while (argc--) { + char *path = Jim_StrDup(Jim_String(argv[0])); + int rc = mkdir_all(path); + + Jim_Free(path); + if (rc != 0) { + Jim_SetResultFormatted(interp, "can't create directory \"%#s\": %s", argv[0], + strerror(errno)); + return JIM_ERR; + } + argv++; + } + return JIM_OK; +} + +static int file_cmd_tempfile(Jim_Interp *interp, int argc, Jim_Obj *const *argv) +{ + int fd = Jim_MakeTempFile(interp, (argc >= 1) ? Jim_String(argv[0]) : NULL, 0); + + if (fd < 0) { + return JIM_ERR; + } + close(fd); + + return JIM_OK; +} + +static int file_cmd_rename(Jim_Interp *interp, int argc, Jim_Obj *const *argv) +{ + const char *source; + const char *dest; + int force = 0; + + if (argc == 3) { + if (!Jim_CompareStringImmediate(interp, argv[0], "-force")) { + return -1; + } + force++; + argv++; + argc--; + } + + source = Jim_String(argv[0]); + dest = Jim_String(argv[1]); + + if (!force && access(dest, F_OK) == 0) { + Jim_SetResultFormatted(interp, "error renaming \"%#s\" to \"%#s\": target exists", argv[0], + argv[1]); + return JIM_ERR; + } + + if (rename(source, dest) != 0) { + Jim_SetResultFormatted(interp, "error renaming \"%#s\" to \"%#s\": %s", argv[0], argv[1], + strerror(errno)); + return JIM_ERR; + } + + return JIM_OK; +} + +#if defined(HAVE_LINK) && defined(HAVE_SYMLINK) +static int file_cmd_link(Jim_Interp *interp, int argc, Jim_Obj *const *argv) +{ + int ret; + const char *source; + const char *dest; + static const char * const options[] = { "-hard", "-symbolic", NULL }; + enum { OPT_HARD, OPT_SYMBOLIC, }; + int option = OPT_HARD; + + if (argc == 3) { + if (Jim_GetEnum(interp, argv[0], options, &option, NULL, JIM_ENUM_ABBREV | JIM_ERRMSG) != JIM_OK) { + return JIM_ERR; + } + argv++; + argc--; + } + + dest = Jim_String(argv[0]); + source = Jim_String(argv[1]); + + if (option == OPT_HARD) { + ret = link(source, dest); + } + else { + ret = symlink(source, dest); + } + + if (ret != 0) { + Jim_SetResultFormatted(interp, "error linking \"%#s\" to \"%#s\": %s", argv[0], argv[1], + strerror(errno)); + return JIM_ERR; + } + + return JIM_OK; +} +#endif + +static int file_stat(Jim_Interp *interp, Jim_Obj *filename, struct stat *sb) +{ + const char *path = Jim_String(filename); + + if (stat(path, sb) == -1) { + Jim_SetResultFormatted(interp, "could not read \"%#s\": %s", filename, strerror(errno)); + return JIM_ERR; + } + return JIM_OK; +} + +#ifdef HAVE_LSTAT +static int file_lstat(Jim_Interp *interp, Jim_Obj *filename, struct stat *sb) +{ + const char *path = Jim_String(filename); + + if (lstat(path, sb) == -1) { + Jim_SetResultFormatted(interp, "could not read \"%#s\": %s", filename, strerror(errno)); + return JIM_ERR; + } + return JIM_OK; +} +#else +#define file_lstat file_stat +#endif + +static int file_cmd_atime(Jim_Interp *interp, int argc, Jim_Obj *const *argv) +{ + struct stat sb; + + if (file_stat(interp, argv[0], &sb) != JIM_OK) { + return JIM_ERR; + } + Jim_SetResultInt(interp, sb.st_atime); + return JIM_OK; +} + +static int JimSetFileTimes(Jim_Interp *interp, const char *filename, jim_wide us) +{ +#ifdef HAVE_UTIMES + struct timeval times[2]; + + times[1].tv_sec = times[0].tv_sec = us / 1000000; + times[1].tv_usec = times[0].tv_usec = us % 1000000; + + if (utimes(filename, times) != 0) { + Jim_SetResultFormatted(interp, "can't set time on \"%s\": %s", filename, strerror(errno)); + return JIM_ERR; + } + return JIM_OK; +#else + Jim_SetResultString(interp, "Not implemented", -1); + return JIM_ERR; +#endif +} + +static int file_cmd_mtime(Jim_Interp *interp, int argc, Jim_Obj *const *argv) +{ + struct stat sb; + + if (argc == 2) { + jim_wide secs; + if (Jim_GetWide(interp, argv[1], &secs) != JIM_OK) { + return JIM_ERR; + } + return JimSetFileTimes(interp, Jim_String(argv[0]), secs * 1000000); + } + if (file_stat(interp, argv[0], &sb) != JIM_OK) { + return JIM_ERR; + } + Jim_SetResultInt(interp, sb.st_mtime); + return JIM_OK; +} + +#ifdef STAT_MTIME_US +static int file_cmd_mtimeus(Jim_Interp *interp, int argc, Jim_Obj *const *argv) +{ + struct stat sb; + + if (argc == 2) { + jim_wide us; + if (Jim_GetWide(interp, argv[1], &us) != JIM_OK) { + return JIM_ERR; + } + return JimSetFileTimes(interp, Jim_String(argv[0]), us); + } + if (file_stat(interp, argv[0], &sb) != JIM_OK) { + return JIM_ERR; + } + Jim_SetResultInt(interp, STAT_MTIME_US(sb)); + return JIM_OK; +} +#endif + +static int file_cmd_copy(Jim_Interp *interp, int argc, Jim_Obj *const *argv) +{ + return Jim_EvalPrefix(interp, "file copy", argc, argv); +} + +static int file_cmd_size(Jim_Interp *interp, int argc, Jim_Obj *const *argv) +{ + struct stat sb; + + if (file_stat(interp, argv[0], &sb) != JIM_OK) { + return JIM_ERR; + } + Jim_SetResultInt(interp, sb.st_size); + return JIM_OK; +} + +static int file_cmd_isdirectory(Jim_Interp *interp, int argc, Jim_Obj *const *argv) +{ + struct stat sb; + int ret = 0; + + if (file_stat(interp, argv[0], &sb) == JIM_OK) { + ret = S_ISDIR(sb.st_mode); + } + Jim_SetResultInt(interp, ret); + return JIM_OK; +} + +static int file_cmd_isfile(Jim_Interp *interp, int argc, Jim_Obj *const *argv) +{ + struct stat sb; + int ret = 0; + + if (file_stat(interp, argv[0], &sb) == JIM_OK) { + ret = S_ISREG(sb.st_mode); + } + Jim_SetResultInt(interp, ret); + return JIM_OK; +} + +#ifdef HAVE_GETEUID +static int file_cmd_owned(Jim_Interp *interp, int argc, Jim_Obj *const *argv) +{ + struct stat sb; + int ret = 0; + + if (file_stat(interp, argv[0], &sb) == JIM_OK) { + ret = (geteuid() == sb.st_uid); + } + Jim_SetResultInt(interp, ret); + return JIM_OK; +} +#endif + +#if defined(HAVE_READLINK) +static int file_cmd_readlink(Jim_Interp *interp, int argc, Jim_Obj *const *argv) +{ + const char *path = Jim_String(argv[0]); + char *linkValue = Jim_Alloc(MAXPATHLEN + 1); + + int linkLength = readlink(path, linkValue, MAXPATHLEN); + + if (linkLength == -1) { + Jim_Free(linkValue); + Jim_SetResultFormatted(interp, "couldn't readlink \"%#s\": %s", argv[0], strerror(errno)); + return JIM_ERR; + } + linkValue[linkLength] = 0; + Jim_SetResult(interp, Jim_NewStringObjNoAlloc(interp, linkValue, linkLength)); + return JIM_OK; +} +#endif + +static int file_cmd_type(Jim_Interp *interp, int argc, Jim_Obj *const *argv) +{ + struct stat sb; + + if (file_lstat(interp, argv[0], &sb) != JIM_OK) { + return JIM_ERR; + } + Jim_SetResultString(interp, JimGetFileType((int)sb.st_mode), -1); + return JIM_OK; +} + +#ifdef HAVE_LSTAT +static int file_cmd_lstat(Jim_Interp *interp, int argc, Jim_Obj *const *argv) +{ + struct stat sb; + + if (file_lstat(interp, argv[0], &sb) != JIM_OK) { + return JIM_ERR; + } + return StoreStatData(interp, argc == 2 ? argv[1] : NULL, &sb); +} +#else +#define file_cmd_lstat file_cmd_stat +#endif + +static int file_cmd_stat(Jim_Interp *interp, int argc, Jim_Obj *const *argv) +{ + struct stat sb; + + if (file_stat(interp, argv[0], &sb) != JIM_OK) { + return JIM_ERR; + } + return StoreStatData(interp, argc == 2 ? argv[1] : NULL, &sb); +} + +static const jim_subcmd_type file_command_table[] = { + { "atime", + "name", + file_cmd_atime, + 1, + 1, + + }, + { "mtime", + "name ?time?", + file_cmd_mtime, + 1, + 2, + + }, +#ifdef STAT_MTIME_US + { "mtimeus", + "name ?time?", + file_cmd_mtimeus, + 1, + 2, + + }, +#endif + { "copy", + "?-force? source dest", + file_cmd_copy, + 2, + 3, + + }, + { "dirname", + "name", + file_cmd_dirname, + 1, + 1, + + }, + { "rootname", + "name", + file_cmd_rootname, + 1, + 1, + + }, + { "extension", + "name", + file_cmd_extension, + 1, + 1, + + }, + { "tail", + "name", + file_cmd_tail, + 1, + 1, + + }, + { "normalize", + "name", + file_cmd_normalize, + 1, + 1, + + }, + { "join", + "name ?name ...?", + file_cmd_join, + 1, + -1, + + }, + { "readable", + "name", + file_cmd_readable, + 1, + 1, + + }, + { "writable", + "name", + file_cmd_writable, + 1, + 1, + + }, + { "executable", + "name", + file_cmd_executable, + 1, + 1, + + }, + { "exists", + "name", + file_cmd_exists, + 1, + 1, + + }, + { "delete", + "?-force|--? name ...", + file_cmd_delete, + 1, + -1, + + }, + { "mkdir", + "dir ...", + file_cmd_mkdir, + 1, + -1, + + }, + { "tempfile", + "?template?", + file_cmd_tempfile, + 0, + 1, + + }, + { "rename", + "?-force? source dest", + file_cmd_rename, + 2, + 3, + + }, +#if defined(HAVE_LINK) && defined(HAVE_SYMLINK) + { "link", + "?-symbolic|-hard? newname target", + file_cmd_link, + 2, + 3, + + }, +#endif +#if defined(HAVE_READLINK) + { "readlink", + "name", + file_cmd_readlink, + 1, + 1, + + }, +#endif + { "size", + "name", + file_cmd_size, + 1, + 1, + + }, + { "stat", + "name ?var?", + file_cmd_stat, + 1, + 2, + + }, + { "lstat", + "name ?var?", + file_cmd_lstat, + 1, + 2, + + }, + { "type", + "name", + file_cmd_type, + 1, + 1, + + }, +#ifdef HAVE_GETEUID + { "owned", + "name", + file_cmd_owned, + 1, + 1, + + }, +#endif + { "isdirectory", + "name", + file_cmd_isdirectory, + 1, + 1, + + }, + { "isfile", + "name", + file_cmd_isfile, + 1, + 1, + + }, + { + NULL + } +}; + +static int Jim_CdCmd(Jim_Interp *interp, int argc, Jim_Obj *const *argv) +{ + const char *path; + + if (argc != 2) { + Jim_WrongNumArgs(interp, 1, argv, "dirname"); + return JIM_ERR; + } + + path = Jim_String(argv[1]); + + if (chdir(path) != 0) { + Jim_SetResultFormatted(interp, "couldn't change working directory to \"%s\": %s", path, + strerror(errno)); + return JIM_ERR; + } + return JIM_OK; +} + +static int Jim_PwdCmd(Jim_Interp *interp, int argc, Jim_Obj *const *argv) +{ + char *cwd = Jim_Alloc(MAXPATHLEN); + + if (getcwd(cwd, MAXPATHLEN) == NULL) { + Jim_SetResultString(interp, "Failed to get pwd", -1); + Jim_Free(cwd); + return JIM_ERR; + } + else if (ISWINDOWS) { + + char *p = cwd; + while ((p = strchr(p, '\\')) != NULL) { + *p++ = '/'; + } + } + + Jim_SetResultString(interp, cwd, -1); + + Jim_Free(cwd); + return JIM_OK; +} + +int Jim_fileInit(Jim_Interp *interp) +{ + if (Jim_PackageProvide(interp, "file", "1.0", JIM_ERRMSG)) + return JIM_ERR; + + Jim_CreateCommand(interp, "file", Jim_SubCmdProc, (void *)file_command_table, NULL); + Jim_CreateCommand(interp, "pwd", Jim_PwdCmd, NULL, NULL); + Jim_CreateCommand(interp, "cd", Jim_CdCmd, NULL, NULL); + return JIM_OK; +} + +#ifndef _GNU_SOURCE +#define _GNU_SOURCE +#endif +#include +#include + + +#if (!defined(HAVE_VFORK) || !defined(HAVE_WAITPID)) && !defined(__MINGW32__) +static int Jim_ExecCmd(Jim_Interp *interp, int argc, Jim_Obj *const *argv) +{ + Jim_Obj *cmdlineObj = Jim_NewEmptyStringObj(interp); + int i, j; + int rc; + + + for (i = 1; i < argc; i++) { + int len; + const char *arg = Jim_GetString(argv[i], &len); + + if (i > 1) { + Jim_AppendString(interp, cmdlineObj, " ", 1); + } + if (strpbrk(arg, "\\\" ") == NULL) { + + Jim_AppendString(interp, cmdlineObj, arg, len); + continue; + } + + Jim_AppendString(interp, cmdlineObj, "\"", 1); + for (j = 0; j < len; j++) { + if (arg[j] == '\\' || arg[j] == '"') { + Jim_AppendString(interp, cmdlineObj, "\\", 1); + } + Jim_AppendString(interp, cmdlineObj, &arg[j], 1); + } + Jim_AppendString(interp, cmdlineObj, "\"", 1); + } + rc = system(Jim_String(cmdlineObj)); + + Jim_FreeNewObj(interp, cmdlineObj); + + if (rc) { + Jim_Obj *errorCode = Jim_NewListObj(interp, NULL, 0); + Jim_ListAppendElement(interp, errorCode, Jim_NewStringObj(interp, "CHILDSTATUS", -1)); + Jim_ListAppendElement(interp, errorCode, Jim_NewIntObj(interp, 0)); + Jim_ListAppendElement(interp, errorCode, Jim_NewIntObj(interp, rc)); + Jim_SetGlobalVariableStr(interp, "errorCode", errorCode); + return JIM_ERR; + } + + return JIM_OK; +} + +int Jim_execInit(Jim_Interp *interp) +{ + if (Jim_PackageProvide(interp, "exec", "1.0", JIM_ERRMSG)) + return JIM_ERR; + + Jim_CreateCommand(interp, "exec", Jim_ExecCmd, NULL, NULL); + return JIM_OK; +} +#else + + +#include +#include +#include + +struct WaitInfoTable; + +static char **JimOriginalEnviron(void); +static char **JimSaveEnv(char **env); +static void JimRestoreEnv(char **env); +static int JimCreatePipeline(Jim_Interp *interp, int argc, Jim_Obj *const *argv, + pidtype **pidArrayPtr, int *inPipePtr, int *outPipePtr, int *errFilePtr); +static void JimDetachPids(struct WaitInfoTable *table, int numPids, const pidtype *pidPtr); +static int JimCleanupChildren(Jim_Interp *interp, int numPids, pidtype *pidPtr, Jim_Obj *errStrObj); +static int Jim_WaitCommand(Jim_Interp *interp, int argc, Jim_Obj *const *argv); + +#if defined(__MINGW32__) +static pidtype JimStartWinProcess(Jim_Interp *interp, char **argv, char **env, int inputId, int outputId, int errorId); +#endif + +static void Jim_RemoveTrailingNewline(Jim_Obj *objPtr) +{ + int len; + const char *s = Jim_GetString(objPtr, &len); + + if (len > 0 && s[len - 1] == '\n') { + objPtr->length--; + objPtr->bytes[objPtr->length] = '\0'; + } +} + +static int JimAppendStreamToString(Jim_Interp *interp, int fd, Jim_Obj *strObj) +{ + char buf[256]; + FILE *fh = fdopen(fd, "r"); + int ret = 0; + + if (fh == NULL) { + return -1; + } + + while (1) { + int retval = fread(buf, 1, sizeof(buf), fh); + if (retval > 0) { + ret = 1; + Jim_AppendString(interp, strObj, buf, retval); + } + if (retval != sizeof(buf)) { + break; + } + } + fclose(fh); + return ret; +} + +static char **JimBuildEnv(Jim_Interp *interp) +{ + int i; + int size; + int num; + int n; + char **envptr; + char *envdata; + + Jim_Obj *objPtr = Jim_GetGlobalVariableStr(interp, "env", JIM_NONE); + + if (!objPtr) { + return JimOriginalEnviron(); + } + + + + num = Jim_ListLength(interp, objPtr); + if (num % 2) { + + num--; + } + size = Jim_Length(objPtr) + 2; + + envptr = Jim_Alloc(sizeof(*envptr) * (num / 2 + 1) + size); + envdata = (char *)&envptr[num / 2 + 1]; + + n = 0; + for (i = 0; i < num; i += 2) { + const char *s1, *s2; + Jim_Obj *elemObj; + + Jim_ListIndex(interp, objPtr, i, &elemObj, JIM_NONE); + s1 = Jim_String(elemObj); + Jim_ListIndex(interp, objPtr, i + 1, &elemObj, JIM_NONE); + s2 = Jim_String(elemObj); + + envptr[n] = envdata; + envdata += sprintf(envdata, "%s=%s", s1, s2); + envdata++; + n++; + } + envptr[n] = NULL; + *envdata = 0; + + return envptr; +} + +static void JimFreeEnv(char **env, char **original_environ) +{ + if (env != original_environ) { + Jim_Free(env); + } +} + +static Jim_Obj *JimMakeErrorCode(Jim_Interp *interp, pidtype pid, int waitStatus, Jim_Obj *errStrObj) +{ + Jim_Obj *errorCode = Jim_NewListObj(interp, NULL, 0); + + if (pid == JIM_BAD_PID || pid == JIM_NO_PID) { + Jim_ListAppendElement(interp, errorCode, Jim_NewStringObj(interp, "NONE", -1)); + Jim_ListAppendElement(interp, errorCode, Jim_NewIntObj(interp, (long)pid)); + Jim_ListAppendElement(interp, errorCode, Jim_NewIntObj(interp, -1)); + } + else if (WIFEXITED(waitStatus)) { + Jim_ListAppendElement(interp, errorCode, Jim_NewStringObj(interp, "CHILDSTATUS", -1)); + Jim_ListAppendElement(interp, errorCode, Jim_NewIntObj(interp, (long)pid)); + Jim_ListAppendElement(interp, errorCode, Jim_NewIntObj(interp, WEXITSTATUS(waitStatus))); + } + else { + const char *type; + const char *action; + const char *signame; + + if (WIFSIGNALED(waitStatus)) { + type = "CHILDKILLED"; + action = "killed"; + signame = Jim_SignalId(WTERMSIG(waitStatus)); + } + else { + type = "CHILDSUSP"; + action = "suspended"; + signame = "none"; + } + + Jim_ListAppendElement(interp, errorCode, Jim_NewStringObj(interp, type, -1)); + + if (errStrObj) { + Jim_AppendStrings(interp, errStrObj, "child ", action, " by signal ", Jim_SignalId(WTERMSIG(waitStatus)), "\n", NULL); + } + + Jim_ListAppendElement(interp, errorCode, Jim_NewIntObj(interp, (long)pid)); + Jim_ListAppendElement(interp, errorCode, Jim_NewStringObj(interp, signame, -1)); + } + return errorCode; +} + +static int JimCheckWaitStatus(Jim_Interp *interp, pidtype pid, int waitStatus, Jim_Obj *errStrObj) +{ + if (WIFEXITED(waitStatus) && WEXITSTATUS(waitStatus) == 0) { + return JIM_OK; + } + Jim_SetGlobalVariableStr(interp, "errorCode", JimMakeErrorCode(interp, pid, waitStatus, errStrObj)); + + return JIM_ERR; +} + + +struct WaitInfo +{ + pidtype pid; + int status; + int flags; +}; + + +struct WaitInfoTable { + struct WaitInfo *info; + int size; + int used; + int refcount; +}; + + +#define WI_DETACHED 2 + +#define WAIT_TABLE_GROW_BY 4 + +static void JimFreeWaitInfoTable(struct Jim_Interp *interp, void *privData) +{ + struct WaitInfoTable *table = privData; + + if (--table->refcount == 0) { + Jim_Free(table->info); + Jim_Free(table); + } +} + +static struct WaitInfoTable *JimAllocWaitInfoTable(void) +{ + struct WaitInfoTable *table = Jim_Alloc(sizeof(*table)); + table->info = NULL; + table->size = table->used = 0; + table->refcount = 1; + + return table; +} + +static int JimWaitRemove(struct WaitInfoTable *table, pidtype pid) +{ + int i; + + + for (i = 0; i < table->used; i++) { + if (pid == table->info[i].pid) { + if (i != table->used - 1) { + table->info[i] = table->info[table->used - 1]; + } + table->used--; + return 0; + } + } + return -1; +} + +static int Jim_ExecCmd(Jim_Interp *interp, int argc, Jim_Obj *const *argv) +{ + int outputId; + int errorId; + pidtype *pidPtr; + int numPids, result; + int child_siginfo = 1; + Jim_Obj *childErrObj; + Jim_Obj *errStrObj; + struct WaitInfoTable *table = Jim_CmdPrivData(interp); + + if (argc > 1 && Jim_CompareStringImmediate(interp, argv[argc - 1], "&")) { + Jim_Obj *listObj; + int i; + + argc--; + numPids = JimCreatePipeline(interp, argc - 1, argv + 1, &pidPtr, NULL, NULL, NULL); + if (numPids < 0) { + return JIM_ERR; + } + + listObj = Jim_NewListObj(interp, NULL, 0); + for (i = 0; i < numPids; i++) { + Jim_ListAppendElement(interp, listObj, Jim_NewIntObj(interp, (long)pidPtr[i])); + } + Jim_SetResult(interp, listObj); + JimDetachPids(table, numPids, pidPtr); + Jim_Free(pidPtr); + return JIM_OK; + } + + numPids = + JimCreatePipeline(interp, argc - 1, argv + 1, &pidPtr, NULL, &outputId, &errorId); + + if (numPids < 0) { + return JIM_ERR; + } + + result = JIM_OK; + + errStrObj = Jim_NewStringObj(interp, "", 0); + + + if (outputId != -1) { + if (JimAppendStreamToString(interp, outputId, errStrObj) < 0) { + result = JIM_ERR; + Jim_SetResultErrno(interp, "error reading from output pipe"); + } + } + + + childErrObj = Jim_NewStringObj(interp, "", 0); + Jim_IncrRefCount(childErrObj); + + if (JimCleanupChildren(interp, numPids, pidPtr, childErrObj) != JIM_OK) { + result = JIM_ERR; + } + + if (errorId != -1) { + int ret; + lseek(errorId, 0, SEEK_SET); + ret = JimAppendStreamToString(interp, errorId, errStrObj); + if (ret < 0) { + Jim_SetResultErrno(interp, "error reading from error pipe"); + result = JIM_ERR; + } + else if (ret > 0) { + + child_siginfo = 0; + } + } + + if (child_siginfo) { + + Jim_AppendObj(interp, errStrObj, childErrObj); + } + Jim_DecrRefCount(interp, childErrObj); + + + Jim_RemoveTrailingNewline(errStrObj); + + + Jim_SetResult(interp, errStrObj); + + return result; +} + +static pidtype JimWaitForProcess(struct WaitInfoTable *table, pidtype pid, int *statusPtr) +{ + if (JimWaitRemove(table, pid) == 0) { + + waitpid(pid, statusPtr, 0); + return pid; + } + + + return JIM_BAD_PID; +} + +static void JimDetachPids(struct WaitInfoTable *table, int numPids, const pidtype *pidPtr) +{ + int j; + + for (j = 0; j < numPids; j++) { + + int i; + for (i = 0; i < table->used; i++) { + if (pidPtr[j] == table->info[i].pid) { + table->info[i].flags |= WI_DETACHED; + break; + } + } + } +} + +static int JimGetChannelFd(Jim_Interp *interp, const char *name) +{ + Jim_Obj *objv[2]; + + objv[0] = Jim_NewStringObj(interp, name, -1); + objv[1] = Jim_NewStringObj(interp, "getfd", -1); + + if (Jim_EvalObjVector(interp, 2, objv) == JIM_OK) { + jim_wide fd; + if (Jim_GetWide(interp, Jim_GetResult(interp), &fd) == JIM_OK) { + return fd; + } + } + return -1; +} + +static void JimReapDetachedPids(struct WaitInfoTable *table) +{ + struct WaitInfo *waitPtr; + int count; + int dest; + + if (!table) { + return; + } + + waitPtr = table->info; + dest = 0; + for (count = table->used; count > 0; waitPtr++, count--) { + if (waitPtr->flags & WI_DETACHED) { + int status; + pidtype pid = waitpid(waitPtr->pid, &status, WNOHANG); + if (pid == waitPtr->pid) { + + table->used--; + continue; + } + } + if (waitPtr != &table->info[dest]) { + table->info[dest] = *waitPtr; + } + dest++; + } +} + +static int Jim_WaitCommand(Jim_Interp *interp, int argc, Jim_Obj *const *argv) +{ + struct WaitInfoTable *table = Jim_CmdPrivData(interp); + int nohang = 0; + pidtype pid; + long pidarg; + int status; + Jim_Obj *errCodeObj; + + + if (argc == 1) { + JimReapDetachedPids(table); + return JIM_OK; + } + + if (argc > 1 && Jim_CompareStringImmediate(interp, argv[1], "-nohang")) { + nohang = 1; + } + if (argc != nohang + 2) { + Jim_WrongNumArgs(interp, 1, argv, "?-nohang? ?pid?"); + return JIM_ERR; + } + if (Jim_GetLong(interp, argv[nohang + 1], &pidarg) != JIM_OK) { + return JIM_ERR; + } + + pid = waitpid((pidtype)pidarg, &status, nohang ? WNOHANG : 0); + + errCodeObj = JimMakeErrorCode(interp, pid, status, NULL); + + if (pid != JIM_BAD_PID && (WIFEXITED(status) || WIFSIGNALED(status))) { + + JimWaitRemove(table, pid); + } + Jim_SetResult(interp, errCodeObj); + return JIM_OK; +} + +static int Jim_PidCommand(Jim_Interp *interp, int argc, Jim_Obj *const *argv) +{ + if (argc != 1) { + Jim_WrongNumArgs(interp, 1, argv, ""); + return JIM_ERR; + } + + Jim_SetResultInt(interp, (jim_wide)getpid()); + return JIM_OK; +} + +static int +JimCreatePipeline(Jim_Interp *interp, int argc, Jim_Obj *const *argv, pidtype **pidArrayPtr, + int *inPipePtr, int *outPipePtr, int *errFilePtr) +{ + pidtype *pidPtr = NULL; /* Points to malloc-ed array holding all + * the pids of child processes. */ + int numPids = 0; /* Actual number of processes that exist + * at *pidPtr right now. */ + int cmdCount; /* Count of number of distinct commands + * found in argc/argv. */ + const char *input = NULL; /* Describes input for pipeline, depending + * on "inputFile". NULL means take input + * from stdin/pipe. */ + int input_len = 0; + +#define FILE_NAME 0 +#define FILE_APPEND 1 +#define FILE_HANDLE 2 +#define FILE_TEXT 3 + + int inputFile = FILE_NAME; /* 1 means input is name of input file. + * 2 means input is filehandle name. + * 0 means input holds actual + * text to be input to command. */ + + int outputFile = FILE_NAME; /* 0 means output is the name of output file. + * 1 means output is the name of output file, and append. + * 2 means output is filehandle name. + * All this is ignored if output is NULL + */ + int errorFile = FILE_NAME; /* 0 means error is the name of error file. + * 1 means error is the name of error file, and append. + * 2 means error is filehandle name. + * All this is ignored if error is NULL + */ + const char *output = NULL; /* Holds name of output file to pipe to, + * or NULL if output goes to stdout/pipe. */ + const char *error = NULL; /* Holds name of stderr file to pipe to, + * or NULL if stderr goes to stderr/pipe. */ + int inputId = -1; + int outputId = -1; + int errorId = -1; + int lastOutputId = -1; + int pipeIds[2]; + int firstArg, lastArg; /* Indexes of first and last arguments in + * current command. */ + int lastBar; + int i; + pidtype pid; + char **save_environ; +#ifndef __MINGW32__ + char **child_environ; +#endif + struct WaitInfoTable *table = Jim_CmdPrivData(interp); + + + char **arg_array = Jim_Alloc(sizeof(*arg_array) * (argc + 1)); + int arg_count = 0; + + if (inPipePtr != NULL) { + *inPipePtr = -1; + } + if (outPipePtr != NULL) { + *outPipePtr = -1; + } + if (errFilePtr != NULL) { + *errFilePtr = -1; + } + pipeIds[0] = pipeIds[1] = -1; + + cmdCount = 1; + lastBar = -1; + for (i = 0; i < argc; i++) { + const char *arg = Jim_String(argv[i]); + + if (arg[0] == '<') { + inputFile = FILE_NAME; + input = arg + 1; + if (*input == '<') { + inputFile = FILE_TEXT; + input_len = Jim_Length(argv[i]) - 2; + input++; + } + else if (*input == '@') { + inputFile = FILE_HANDLE; + input++; + } + + if (!*input && ++i < argc) { + input = Jim_GetString(argv[i], &input_len); + } + } + else if (arg[0] == '>') { + int dup_error = 0; + + outputFile = FILE_NAME; + + output = arg + 1; + if (*output == '>') { + outputFile = FILE_APPEND; + output++; + } + if (*output == '&') { + + output++; + dup_error = 1; + } + if (*output == '@') { + outputFile = FILE_HANDLE; + output++; + } + if (!*output && ++i < argc) { + output = Jim_String(argv[i]); + } + if (dup_error) { + errorFile = outputFile; + error = output; + } + } + else if (arg[0] == '2' && arg[1] == '>') { + error = arg + 2; + errorFile = FILE_NAME; + + if (*error == '@') { + errorFile = FILE_HANDLE; + error++; + } + else if (*error == '>') { + errorFile = FILE_APPEND; + error++; + } + if (!*error && ++i < argc) { + error = Jim_String(argv[i]); + } + } + else { + if (strcmp(arg, "|") == 0 || strcmp(arg, "|&") == 0) { + if (i == lastBar + 1 || i == argc - 1) { + Jim_SetResultString(interp, "illegal use of | or |& in command", -1); + goto badargs; + } + lastBar = i; + cmdCount++; + } + + arg_array[arg_count++] = (char *)arg; + continue; + } + + if (i >= argc) { + Jim_SetResultFormatted(interp, "can't specify \"%s\" as last word in command", arg); + goto badargs; + } + } + + if (arg_count == 0) { + Jim_SetResultString(interp, "didn't specify command to execute", -1); +badargs: + Jim_Free(arg_array); + return -1; + } + + + save_environ = JimSaveEnv(JimBuildEnv(interp)); + + if (input != NULL) { + if (inputFile == FILE_TEXT) { + inputId = Jim_MakeTempFile(interp, NULL, 1); + if (inputId == -1) { + goto error; + } + if (write(inputId, input, input_len) != input_len) { + Jim_SetResultErrno(interp, "couldn't write temp file"); + close(inputId); + goto error; + } + lseek(inputId, 0L, SEEK_SET); + } + else if (inputFile == FILE_HANDLE) { + int fd = JimGetChannelFd(interp, input); + + if (fd < 0) { + goto error; + } + inputId = dup(fd); + } + else { + inputId = Jim_OpenForRead(input); + if (inputId == -1) { + Jim_SetResultFormatted(interp, "couldn't read file \"%s\": %s", input, strerror(Jim_Errno())); + goto error; + } + } + } + else if (inPipePtr != NULL) { + if (pipe(pipeIds) != 0) { + Jim_SetResultErrno(interp, "couldn't create input pipe for command"); + goto error; + } + inputId = pipeIds[0]; + *inPipePtr = pipeIds[1]; + pipeIds[0] = pipeIds[1] = -1; + } + + if (output != NULL) { + if (outputFile == FILE_HANDLE) { + int fd = JimGetChannelFd(interp, output); + if (fd < 0) { + goto error; + } + lastOutputId = dup(fd); + } + else { + lastOutputId = Jim_OpenForWrite(output, outputFile == FILE_APPEND); + if (lastOutputId == -1) { + Jim_SetResultFormatted(interp, "couldn't write file \"%s\": %s", output, strerror(Jim_Errno())); + goto error; + } + } + } + else if (outPipePtr != NULL) { + if (pipe(pipeIds) != 0) { + Jim_SetResultErrno(interp, "couldn't create output pipe"); + goto error; + } + lastOutputId = pipeIds[1]; + *outPipePtr = pipeIds[0]; + pipeIds[0] = pipeIds[1] = -1; + } + + if (error != NULL) { + if (errorFile == FILE_HANDLE) { + if (strcmp(error, "1") == 0) { + + if (lastOutputId != -1) { + errorId = dup(lastOutputId); + } + else { + + error = "stdout"; + } + } + if (errorId == -1) { + int fd = JimGetChannelFd(interp, error); + if (fd < 0) { + goto error; + } + errorId = dup(fd); + } + } + else { + errorId = Jim_OpenForWrite(error, errorFile == FILE_APPEND); + if (errorId == -1) { + Jim_SetResultFormatted(interp, "couldn't write file \"%s\": %s", error, strerror(Jim_Errno())); + goto error; + } + } + } + else if (errFilePtr != NULL) { + errorId = Jim_MakeTempFile(interp, NULL, 1); + if (errorId == -1) { + goto error; + } + *errFilePtr = dup(errorId); + } + + + pidPtr = Jim_Alloc(cmdCount * sizeof(*pidPtr)); + for (i = 0; i < numPids; i++) { + pidPtr[i] = JIM_BAD_PID; + } + for (firstArg = 0; firstArg < arg_count; numPids++, firstArg = lastArg + 1) { + int pipe_dup_err = 0; + int origErrorId = errorId; + + for (lastArg = firstArg; lastArg < arg_count; lastArg++) { + if (strcmp(arg_array[lastArg], "|") == 0) { + break; + } + if (strcmp(arg_array[lastArg], "|&") == 0) { + pipe_dup_err = 1; + break; + } + } + + if (lastArg == firstArg) { + Jim_SetResultString(interp, "missing command to exec", -1); + goto error; + } + + + arg_array[lastArg] = NULL; + if (lastArg == arg_count) { + outputId = lastOutputId; + lastOutputId = -1; + } + else { + if (pipe(pipeIds) != 0) { + Jim_SetResultErrno(interp, "couldn't create pipe"); + goto error; + } + outputId = pipeIds[1]; + } + + + if (pipe_dup_err) { + errorId = outputId; + } + + + +#ifdef __MINGW32__ + pid = JimStartWinProcess(interp, &arg_array[firstArg], save_environ, inputId, outputId, errorId); + if (pid == JIM_BAD_PID) { + Jim_SetResultFormatted(interp, "couldn't exec \"%s\"", arg_array[firstArg]); + goto error; + } +#else + i = strlen(arg_array[firstArg]); + + child_environ = Jim_GetEnviron(); + pid = vfork(); + if (pid < 0) { + Jim_SetResultErrno(interp, "couldn't fork child process"); + goto error; + } + if (pid == 0) { + + + if (inputId != -1) { + dup2(inputId, fileno(stdin)); + close(inputId); + } + if (outputId != -1) { + dup2(outputId, fileno(stdout)); + if (outputId != errorId) { + close(outputId); + } + } + if (errorId != -1) { + dup2(errorId, fileno(stderr)); + close(errorId); + } + + if (outPipePtr) { + close(*outPipePtr); + } + if (errFilePtr) { + close(*errFilePtr); + } + if (pipeIds[0] != -1) { + close(pipeIds[0]); + } + if (lastOutputId != -1) { + close(lastOutputId); + } + + + (void)signal(SIGPIPE, SIG_DFL); + + execvpe(arg_array[firstArg], &arg_array[firstArg], child_environ); + + if (write(fileno(stderr), "couldn't exec \"", 15) && + write(fileno(stderr), arg_array[firstArg], i) && + write(fileno(stderr), "\"\n", 2)) { + + } +#ifdef JIM_MAINTAINER + { + + static char *const false_argv[2] = {"false", NULL}; + execvp(false_argv[0],false_argv); + } +#endif + _exit(127); + } +#endif + + + + if (table->used == table->size) { + table->size += WAIT_TABLE_GROW_BY; + table->info = Jim_Realloc(table->info, table->size * sizeof(*table->info)); + } + + table->info[table->used].pid = pid; + table->info[table->used].flags = 0; + table->used++; + + pidPtr[numPids] = pid; + + + errorId = origErrorId; + + + if (inputId != -1) { + close(inputId); + } + if (outputId != -1) { + close(outputId); + } + inputId = pipeIds[0]; + pipeIds[0] = pipeIds[1] = -1; + } + *pidArrayPtr = pidPtr; + + + cleanup: + if (inputId != -1) { + close(inputId); + } + if (lastOutputId != -1) { + close(lastOutputId); + } + if (errorId != -1) { + close(errorId); + } + Jim_Free(arg_array); + + JimRestoreEnv(save_environ); + + return numPids; + + + error: + if ((inPipePtr != NULL) && (*inPipePtr != -1)) { + close(*inPipePtr); + *inPipePtr = -1; + } + if ((outPipePtr != NULL) && (*outPipePtr != -1)) { + close(*outPipePtr); + *outPipePtr = -1; + } + if ((errFilePtr != NULL) && (*errFilePtr != -1)) { + close(*errFilePtr); + *errFilePtr = -1; + } + if (pipeIds[0] != -1) { + close(pipeIds[0]); + } + if (pipeIds[1] != -1) { + close(pipeIds[1]); + } + if (pidPtr != NULL) { + for (i = 0; i < numPids; i++) { + if (pidPtr[i] != JIM_BAD_PID) { + JimDetachPids(table, 1, &pidPtr[i]); + } + } + Jim_Free(pidPtr); + } + numPids = -1; + goto cleanup; +} + + +static int JimCleanupChildren(Jim_Interp *interp, int numPids, pidtype *pidPtr, Jim_Obj *errStrObj) +{ + struct WaitInfoTable *table = Jim_CmdPrivData(interp); + int result = JIM_OK; + int i; + + + for (i = 0; i < numPids; i++) { + int waitStatus = 0; + if (JimWaitForProcess(table, pidPtr[i], &waitStatus) != JIM_BAD_PID) { + if (JimCheckWaitStatus(interp, pidPtr[i], waitStatus, errStrObj) != JIM_OK) { + result = JIM_ERR; + } + } + } + Jim_Free(pidPtr); + + return result; +} + +int Jim_execInit(Jim_Interp *interp) +{ + struct WaitInfoTable *waitinfo; + if (Jim_PackageProvide(interp, "exec", "1.0", JIM_ERRMSG)) + return JIM_ERR; + +#ifdef SIGPIPE + (void)signal(SIGPIPE, SIG_IGN); +#endif + + waitinfo = JimAllocWaitInfoTable(); + Jim_CreateCommand(interp, "exec", Jim_ExecCmd, waitinfo, JimFreeWaitInfoTable); + waitinfo->refcount++; + Jim_CreateCommand(interp, "wait", Jim_WaitCommand, waitinfo, JimFreeWaitInfoTable); + Jim_CreateCommand(interp, "pid", Jim_PidCommand, 0, 0); + + return JIM_OK; +} + +#if defined(__MINGW32__) + + +static int +JimWinFindExecutable(const char *originalName, char fullPath[MAX_PATH]) +{ + int i; + static char extensions[][5] = {".exe", "", ".bat"}; + + for (i = 0; i < (int) (sizeof(extensions) / sizeof(extensions[0])); i++) { + snprintf(fullPath, MAX_PATH, "%s%s", originalName, extensions[i]); + + if (SearchPath(NULL, fullPath, NULL, MAX_PATH, fullPath, NULL) == 0) { + continue; + } + if (GetFileAttributes(fullPath) & FILE_ATTRIBUTE_DIRECTORY) { + continue; + } + return 0; + } + + return -1; +} + +static char **JimSaveEnv(char **env) +{ + return env; +} + +static void JimRestoreEnv(char **env) +{ + JimFreeEnv(env, Jim_GetEnviron()); +} + +static char **JimOriginalEnviron(void) +{ + return NULL; +} + +static Jim_Obj * +JimWinBuildCommandLine(Jim_Interp *interp, char **argv) +{ + char *start, *special; + int quote, i; + + Jim_Obj *strObj = Jim_NewStringObj(interp, "", 0); + + for (i = 0; argv[i]; i++) { + if (i > 0) { + Jim_AppendString(interp, strObj, " ", 1); + } + + if (argv[i][0] == '\0') { + quote = 1; + } + else { + quote = 0; + for (start = argv[i]; *start != '\0'; start++) { + if (isspace(UCHAR(*start))) { + quote = 1; + break; + } + } + } + if (quote) { + Jim_AppendString(interp, strObj, "\"" , 1); + } + + start = argv[i]; + for (special = argv[i]; ; ) { + if ((*special == '\\') && (special[1] == '\\' || + special[1] == '"' || (quote && special[1] == '\0'))) { + Jim_AppendString(interp, strObj, start, special - start); + start = special; + while (1) { + special++; + if (*special == '"' || (quote && *special == '\0')) { + + Jim_AppendString(interp, strObj, start, special - start); + break; + } + if (*special != '\\') { + break; + } + } + Jim_AppendString(interp, strObj, start, special - start); + start = special; + } + if (*special == '"') { + if (special == start) { + Jim_AppendString(interp, strObj, "\"", 1); + } + else { + Jim_AppendString(interp, strObj, start, special - start); + } + Jim_AppendString(interp, strObj, "\\\"", 2); + start = special + 1; + } + if (*special == '\0') { + break; + } + special++; + } + Jim_AppendString(interp, strObj, start, special - start); + if (quote) { + Jim_AppendString(interp, strObj, "\"", 1); + } + } + return strObj; +} + +static pidtype +JimStartWinProcess(Jim_Interp *interp, char **argv, char **env, int inputId, int outputId, int errorId) +{ + STARTUPINFO startInfo; + PROCESS_INFORMATION procInfo; + HANDLE hProcess; + char execPath[MAX_PATH]; + pidtype pid = JIM_BAD_PID; + Jim_Obj *cmdLineObj; + char *winenv; + + if (JimWinFindExecutable(argv[0], execPath) < 0) { + return JIM_BAD_PID; + } + argv[0] = execPath; + + hProcess = GetCurrentProcess(); + cmdLineObj = JimWinBuildCommandLine(interp, argv); + + + ZeroMemory(&startInfo, sizeof(startInfo)); + startInfo.cb = sizeof(startInfo); + startInfo.dwFlags = STARTF_USESTDHANDLES; + startInfo.hStdInput = INVALID_HANDLE_VALUE; + startInfo.hStdOutput= INVALID_HANDLE_VALUE; + startInfo.hStdError = INVALID_HANDLE_VALUE; + + if (inputId == -1) { + inputId = _fileno(stdin); + } + DuplicateHandle(hProcess, (HANDLE)_get_osfhandle(inputId), hProcess, &startInfo.hStdInput, + 0, TRUE, DUPLICATE_SAME_ACCESS); + if (startInfo.hStdInput == INVALID_HANDLE_VALUE) { + goto end; + } + + if (outputId == -1) { + outputId = _fileno(stdout); + } + DuplicateHandle(hProcess, (HANDLE)_get_osfhandle(outputId), hProcess, &startInfo.hStdOutput, + 0, TRUE, DUPLICATE_SAME_ACCESS); + if (startInfo.hStdOutput == INVALID_HANDLE_VALUE) { + goto end; + } + + + if (errorId == -1) { + errorId = _fileno(stderr); + } + DuplicateHandle(hProcess, (HANDLE)_get_osfhandle(errorId), hProcess, &startInfo.hStdError, + 0, TRUE, DUPLICATE_SAME_ACCESS); + if (startInfo.hStdError == INVALID_HANDLE_VALUE) { + goto end; + } + + if (env == NULL) { + + winenv = NULL; + } + else if (env[0] == NULL) { + winenv = (char *)"\0"; + } + else { + winenv = env[0]; + } + + if (!CreateProcess(NULL, (char *)Jim_String(cmdLineObj), NULL, NULL, TRUE, + 0, winenv, NULL, &startInfo, &procInfo)) { + goto end; + } + + + WaitForInputIdle(procInfo.hProcess, 5000); + CloseHandle(procInfo.hThread); + + pid = procInfo.hProcess; + + end: + Jim_FreeNewObj(interp, cmdLineObj); + if (startInfo.hStdInput != INVALID_HANDLE_VALUE) { + CloseHandle(startInfo.hStdInput); + } + if (startInfo.hStdOutput != INVALID_HANDLE_VALUE) { + CloseHandle(startInfo.hStdOutput); + } + if (startInfo.hStdError != INVALID_HANDLE_VALUE) { + CloseHandle(startInfo.hStdError); + } + return pid; +} + +#else + +static char **JimOriginalEnviron(void) +{ + return Jim_GetEnviron(); +} + +static char **JimSaveEnv(char **env) +{ + char **saveenv = Jim_GetEnviron(); + Jim_SetEnviron(env); + return saveenv; +} + +static void JimRestoreEnv(char **env) +{ + JimFreeEnv(Jim_GetEnviron(), env); + Jim_SetEnviron(env); +} +#endif +#endif + + + +#ifdef STRPTIME_NEEDS_XOPEN_SOURCE +#ifndef _XOPEN_SOURCE +#define _XOPEN_SOURCE 500 +#endif +#endif + + +#ifndef _GNU_SOURCE +#define _GNU_SOURCE +#endif + +#include +#include +#include +#include + + +#ifdef HAVE_SYS_TIME_H +#include +#endif + +struct clock_options { + int gmt; + const char *format; +}; + +static int parse_clock_options(Jim_Interp *interp, int argc, Jim_Obj *const *argv, struct clock_options *opts) +{ + static const char * const options[] = { "-gmt", "-format", NULL }; + enum { OPT_GMT, OPT_FORMAT, }; + int i; + + for (i = 0; i < argc; i += 2) { + int option; + if (Jim_GetEnum(interp, argv[i], options, &option, NULL, JIM_ERRMSG | JIM_ENUM_ABBREV) != JIM_OK) { + return JIM_ERR; + } + switch (option) { + case OPT_GMT: + if (Jim_GetBoolean(interp, argv[i + 1], &opts->gmt) != JIM_OK) { + return JIM_ERR; + } + break; + case OPT_FORMAT: + opts->format = Jim_String(argv[i + 1]); + break; + } + } + return JIM_OK; +} + +static int clock_cmd_format(Jim_Interp *interp, int argc, Jim_Obj *const *argv) +{ + + char buf[100]; + time_t t; + jim_wide seconds; + struct clock_options options = { 0, "%a %b %d %H:%M:%S %Z %Y" }; + struct tm *tm; + + if (Jim_GetWide(interp, argv[0], &seconds) != JIM_OK) { + return JIM_ERR; + } + if (argc % 2 == 0) { + return -1; + } + if (parse_clock_options(interp, argc - 1, argv + 1, &options) == JIM_ERR) { + return JIM_ERR; + } + + t = seconds; + tm = options.gmt ? gmtime(&t) : localtime(&t); + + if (tm == NULL || strftime(buf, sizeof(buf), options.format, tm) == 0) { + Jim_SetResultString(interp, "format string too long or invalid time", -1); + return JIM_ERR; + } + + Jim_SetResultString(interp, buf, -1); + + return JIM_OK; +} + +#ifdef HAVE_STRPTIME +static time_t jim_timegm(const struct tm *tm) +{ + int m = tm->tm_mon + 1; + int y = 1900 + tm->tm_year - (m <= 2); + int era = (y >= 0 ? y : y - 399) / 400; + unsigned yoe = (unsigned)(y - era * 400); + unsigned doy = (153 * (m + (m > 2 ? -3 : 9)) + 2) / 5 + tm->tm_mday - 1; + unsigned doe = yoe * 365 + yoe / 4 - yoe / 100 + doy; + long days = (era * 146097 + (int)doe - 719468); + int secs = tm->tm_hour * 3600 + tm->tm_min * 60 + tm->tm_sec; + + return days * 24 * 60 * 60 + secs; +} + +static int clock_cmd_scan(Jim_Interp *interp, int argc, Jim_Obj *const *argv) +{ + char *pt; + struct tm tm; + time_t now = time(NULL); + + struct clock_options options = { 0, NULL }; + + if (argc % 2 == 0) { + return -1; + } + + if (parse_clock_options(interp, argc - 1, argv + 1, &options) == JIM_ERR) { + return JIM_ERR; + } + if (options.format == NULL) { + return -1; + } + + localtime_r(&now, &tm); + + pt = strptime(Jim_String(argv[0]), options.format, &tm); + if (pt == 0 || *pt != 0) { + Jim_SetResultString(interp, "Failed to parse time according to format", -1); + return JIM_ERR; + } + + + Jim_SetResultInt(interp, options.gmt ? jim_timegm(&tm) : mktime(&tm)); + + return JIM_OK; +} +#endif + +static int clock_cmd_seconds(Jim_Interp *interp, int argc, Jim_Obj *const *argv) +{ + Jim_SetResultInt(interp, time(NULL)); + + return JIM_OK; +} + +static int clock_cmd_micros(Jim_Interp *interp, int argc, Jim_Obj *const *argv) +{ + struct timeval tv; + + gettimeofday(&tv, NULL); + + Jim_SetResultInt(interp, (jim_wide) tv.tv_sec * 1000000 + tv.tv_usec); + + return JIM_OK; +} + +static int clock_cmd_millis(Jim_Interp *interp, int argc, Jim_Obj *const *argv) +{ + struct timeval tv; + + gettimeofday(&tv, NULL); + + Jim_SetResultInt(interp, (jim_wide) tv.tv_sec * 1000 + tv.tv_usec / 1000); + + return JIM_OK; +} + +static const jim_subcmd_type clock_command_table[] = { + { "clicks", + NULL, + clock_cmd_micros, + 0, + 0, + + }, + { "format", + "seconds ?-format string? ?-gmt boolean?", + clock_cmd_format, + 1, + 5, + + }, + { "microseconds", + NULL, + clock_cmd_micros, + 0, + 0, + + }, + { "milliseconds", + NULL, + clock_cmd_millis, + 0, + 0, + + }, +#ifdef HAVE_STRPTIME + { "scan", + "str -format format ?-gmt boolean?", + clock_cmd_scan, + 3, + 5, + + }, +#endif + { "seconds", + NULL, + clock_cmd_seconds, + 0, + 0, + + }, + { NULL } +}; + +int Jim_clockInit(Jim_Interp *interp) +{ + if (Jim_PackageProvide(interp, "clock", "1.0", JIM_ERRMSG)) + return JIM_ERR; + + Jim_CreateCommand(interp, "clock", Jim_SubCmdProc, (void *)clock_command_table, NULL); + return JIM_OK; +} + +#include +#include +#include +#include +#include + + +static int array_cmd_exists(Jim_Interp *interp, int argc, Jim_Obj *const *argv) +{ + + Jim_Obj *dictObj = Jim_GetVariable(interp, argv[0], JIM_UNSHARED); + Jim_SetResultInt(interp, dictObj && Jim_DictSize(interp, dictObj) != -1); + return JIM_OK; +} + +static int array_cmd_get(Jim_Interp *interp, int argc, Jim_Obj *const *argv) +{ + Jim_Obj *objPtr = Jim_GetVariable(interp, argv[0], JIM_NONE); + Jim_Obj *patternObj; + + if (!objPtr) { + return JIM_OK; + } + + patternObj = (argc == 1) ? NULL : argv[1]; + + + if (patternObj == NULL || Jim_CompareStringImmediate(interp, patternObj, "*")) { + if (Jim_IsList(objPtr) && Jim_ListLength(interp, objPtr) % 2 == 0) { + + Jim_SetResult(interp, objPtr); + return JIM_OK; + } + } + + return Jim_DictMatchTypes(interp, objPtr, patternObj, JIM_DICTMATCH_KEYS, JIM_DICTMATCH_KEYS | JIM_DICTMATCH_VALUES); +} + +static int array_cmd_names(Jim_Interp *interp, int argc, Jim_Obj *const *argv) +{ + Jim_Obj *objPtr = Jim_GetVariable(interp, argv[0], JIM_NONE); + + if (!objPtr) { + return JIM_OK; + } + + return Jim_DictMatchTypes(interp, objPtr, argc == 1 ? NULL : argv[1], JIM_DICTMATCH_KEYS, JIM_DICTMATCH_KEYS); +} + +static int array_cmd_unset(Jim_Interp *interp, int argc, Jim_Obj *const *argv) +{ + int i; + int len; + Jim_Obj *resultObj; + Jim_Obj *objPtr; + Jim_Obj **dictValuesObj; + + if (argc == 1 || Jim_CompareStringImmediate(interp, argv[1], "*")) { + + Jim_UnsetVariable(interp, argv[0], JIM_NONE); + return JIM_OK; + } + + objPtr = Jim_GetVariable(interp, argv[0], JIM_NONE); + + if (objPtr == NULL) { + + return JIM_OK; + } + + if (Jim_DictPairs(interp, objPtr, &dictValuesObj, &len) != JIM_OK) { + + Jim_SetResultString(interp, "", -1); + return JIM_OK; + } + + + resultObj = Jim_NewDictObj(interp, NULL, 0); + + for (i = 0; i < len; i += 2) { + if (!Jim_StringMatchObj(interp, argv[1], dictValuesObj[i], 0)) { + Jim_DictAddElement(interp, resultObj, dictValuesObj[i], dictValuesObj[i + 1]); + } + } + Jim_Free(dictValuesObj); + + Jim_SetVariable(interp, argv[0], resultObj); + return JIM_OK; +} + +static int array_cmd_size(Jim_Interp *interp, int argc, Jim_Obj *const *argv) +{ + Jim_Obj *objPtr; + int len = 0; + + + objPtr = Jim_GetVariable(interp, argv[0], JIM_NONE); + if (objPtr) { + len = Jim_DictSize(interp, objPtr); + if (len < 0) { + + Jim_SetResultInt(interp, 0); + return JIM_OK; + } + } + + Jim_SetResultInt(interp, len); + + return JIM_OK; +} + +static int array_cmd_stat(Jim_Interp *interp, int argc, Jim_Obj *const *argv) +{ + Jim_Obj *objPtr = Jim_GetVariable(interp, argv[0], JIM_NONE); + if (objPtr) { + return Jim_DictInfo(interp, objPtr); + } + Jim_SetResultFormatted(interp, "\"%#s\" isn't an array", argv[0], NULL); + return JIM_ERR; +} + +static int array_cmd_set(Jim_Interp *interp, int argc, Jim_Obj *const *argv) +{ + int i; + int len; + Jim_Obj *listObj = argv[1]; + Jim_Obj *dictObj; + + len = Jim_ListLength(interp, listObj); + if (len % 2) { + Jim_SetResultString(interp, "list must have an even number of elements", -1); + return JIM_ERR; + } + + dictObj = Jim_GetVariable(interp, argv[0], JIM_UNSHARED); + if (!dictObj) { + + return Jim_SetVariable(interp, argv[0], listObj); + } + else if (Jim_DictSize(interp, dictObj) < 0) { + return JIM_ERR; + } + + if (Jim_IsShared(dictObj)) { + dictObj = Jim_DuplicateObj(interp, dictObj); + } + + for (i = 0; i < len; i += 2) { + Jim_Obj *nameObj; + Jim_Obj *valueObj; + + Jim_ListIndex(interp, listObj, i, &nameObj, JIM_NONE); + Jim_ListIndex(interp, listObj, i + 1, &valueObj, JIM_NONE); + + Jim_DictAddElement(interp, dictObj, nameObj, valueObj); + } + return Jim_SetVariable(interp, argv[0], dictObj); +} + +static const jim_subcmd_type array_command_table[] = { + { "exists", + "arrayName", + array_cmd_exists, + 1, + 1, + + }, + { "get", + "arrayName ?pattern?", + array_cmd_get, + 1, + 2, + + }, + { "names", + "arrayName ?pattern?", + array_cmd_names, + 1, + 2, + + }, + { "set", + "arrayName list", + array_cmd_set, + 2, + 2, + + }, + { "size", + "arrayName", + array_cmd_size, + 1, + 1, + + }, + { "stat", + "arrayName", + array_cmd_stat, + 1, + 1, + + }, + { "unset", + "arrayName ?pattern?", + array_cmd_unset, + 1, + 2, + + }, + { NULL + } +}; + +int Jim_arrayInit(Jim_Interp *interp) +{ + if (Jim_PackageProvide(interp, "array", "1.0", JIM_ERRMSG)) + return JIM_ERR; + + Jim_CreateCommand(interp, "array", Jim_SubCmdProc, (void *)array_command_table, NULL); + return JIM_OK; +} +int Jim_InitStaticExtensions(Jim_Interp *interp) +{ +extern int Jim_bootstrapInit(Jim_Interp *); +extern int Jim_aioInit(Jim_Interp *); +extern int Jim_readdirInit(Jim_Interp *); +extern int Jim_regexpInit(Jim_Interp *); +extern int Jim_fileInit(Jim_Interp *); +extern int Jim_globInit(Jim_Interp *); +extern int Jim_execInit(Jim_Interp *); +extern int Jim_clockInit(Jim_Interp *); +extern int Jim_arrayInit(Jim_Interp *); +extern int Jim_stdlibInit(Jim_Interp *); +extern int Jim_tclcompatInit(Jim_Interp *); +Jim_bootstrapInit(interp); +Jim_aioInit(interp); +Jim_readdirInit(interp); +Jim_regexpInit(interp); +Jim_fileInit(interp); +Jim_globInit(interp); +Jim_execInit(interp); +Jim_clockInit(interp); +Jim_arrayInit(interp); +Jim_stdlibInit(interp); +Jim_tclcompatInit(interp); +return JIM_OK; +} +#define JIM_OPTIMIZATION +#ifndef _GNU_SOURCE +#define _GNU_SOURCE +#endif + +#include +#include + +#include +#include +#include +#include +#include +#include +#include +#include + + +#ifdef HAVE_SYS_TIME_H +#include +#endif +#ifdef HAVE_BACKTRACE +#include +#endif +#ifdef HAVE_CRT_EXTERNS_H +#include +#endif + + +#include + + + + + +#ifndef TCL_LIBRARY +#define TCL_LIBRARY "." +#endif +#ifndef TCL_PLATFORM_OS +#define TCL_PLATFORM_OS "unknown" +#endif +#ifndef TCL_PLATFORM_PLATFORM +#define TCL_PLATFORM_PLATFORM "unknown" +#endif +#ifndef TCL_PLATFORM_PATH_SEPARATOR +#define TCL_PLATFORM_PATH_SEPARATOR ":" +#endif + + + + + + + +#ifdef JIM_MAINTAINER +#define JIM_DEBUG_COMMAND +#define JIM_DEBUG_PANIC +#endif + + + +#define JIM_INTEGER_SPACE 24 + +const char *jim_tt_name(int type); + +#ifdef JIM_DEBUG_PANIC +static void JimPanicDump(int fail_condition, const char *fmt, ...); +#define JimPanic(X) JimPanicDump X +#else +#define JimPanic(X) +#endif + +#ifdef JIM_OPTIMIZATION +#define JIM_IF_OPTIM(X) X +#else +#define JIM_IF_OPTIM(X) +#endif + + +static char JimEmptyStringRep[] = ""; + +static void JimFreeCallFrame(Jim_Interp *interp, Jim_CallFrame *cf, int action); +static int ListSetIndex(Jim_Interp *interp, Jim_Obj *listPtr, int listindex, Jim_Obj *newObjPtr, + int flags); +static int JimDeleteLocalProcs(Jim_Interp *interp, Jim_Stack *localCommands); +static Jim_Obj *JimExpandDictSugar(Jim_Interp *interp, Jim_Obj *objPtr); +static void SetDictSubstFromAny(Jim_Interp *interp, Jim_Obj *objPtr); +static Jim_Obj **JimDictPairs(Jim_Obj *dictPtr, int *len); +static void JimSetFailedEnumResult(Jim_Interp *interp, const char *arg, const char *badtype, + const char *prefix, const char *const *tablePtr, const char *name); +static int JimCallProcedure(Jim_Interp *interp, Jim_Cmd *cmd, int argc, Jim_Obj *const *argv); +static int JimGetWideNoErr(Jim_Interp *interp, Jim_Obj *objPtr, jim_wide * widePtr); +static int JimSign(jim_wide w); +static int JimValidName(Jim_Interp *interp, const char *type, Jim_Obj *nameObjPtr); +static void JimPrngSeed(Jim_Interp *interp, unsigned char *seed, int seedLen); +static void JimRandomBytes(Jim_Interp *interp, void *dest, unsigned int len); + + + +#define JimWideValue(objPtr) (objPtr)->internalRep.wideValue + +#define JimObjTypeName(O) ((O)->typePtr ? (O)->typePtr->name : "none") + +static int utf8_tounicode_case(const char *s, int *uc, int upper) +{ + int l = utf8_tounicode(s, uc); + if (upper) { + *uc = utf8_upper(*uc); + } + return l; +} + + +#define JIM_CHARSET_SCAN 2 +#define JIM_CHARSET_GLOB 0 + +static const char *JimCharsetMatch(const char *pattern, int c, int flags) +{ + int not = 0; + int pchar; + int match = 0; + int nocase = 0; + + if (flags & JIM_NOCASE) { + nocase++; + c = utf8_upper(c); + } + + if (flags & JIM_CHARSET_SCAN) { + if (*pattern == '^') { + not++; + pattern++; + } + + + if (*pattern == ']') { + goto first; + } + } + + while (*pattern && *pattern != ']') { + + if (pattern[0] == '\\') { +first: + pattern += utf8_tounicode_case(pattern, &pchar, nocase); + } + else { + + int start; + int end; + + pattern += utf8_tounicode_case(pattern, &start, nocase); + if (pattern[0] == '-' && pattern[1]) { + + pattern++; + pattern += utf8_tounicode_case(pattern, &end, nocase); + + + if ((c >= start && c <= end) || (c >= end && c <= start)) { + match = 1; + } + continue; + } + pchar = start; + } + + if (pchar == c) { + match = 1; + } + } + if (not) { + match = !match; + } + + return match ? pattern : NULL; +} + + + +static int JimGlobMatch(const char *pattern, const char *string, int nocase) +{ + int c; + int pchar; + while (*pattern) { + switch (pattern[0]) { + case '*': + while (pattern[1] == '*') { + pattern++; + } + pattern++; + if (!pattern[0]) { + return 1; + } + while (*string) { + + if (JimGlobMatch(pattern, string, nocase)) + return 1; + string += utf8_tounicode(string, &c); + } + return 0; + + case '?': + string += utf8_tounicode(string, &c); + break; + + case '[': { + string += utf8_tounicode(string, &c); + pattern = JimCharsetMatch(pattern + 1, c, nocase ? JIM_NOCASE : 0); + if (!pattern) { + return 0; + } + if (!*pattern) { + + continue; + } + break; + } + case '\\': + if (pattern[1]) { + pattern++; + } + + default: + string += utf8_tounicode_case(string, &c, nocase); + utf8_tounicode_case(pattern, &pchar, nocase); + if (pchar != c) { + return 0; + } + break; + } + pattern += utf8_tounicode_case(pattern, &pchar, nocase); + if (!*string) { + while (*pattern == '*') { + pattern++; + } + break; + } + } + if (!*pattern && !*string) { + return 1; + } + return 0; +} + +static int JimStringCompare(const char *s1, int l1, const char *s2, int l2) +{ + if (l1 < l2) { + return memcmp(s1, s2, l1) <= 0 ? -1 : 1; + } + else if (l2 < l1) { + return memcmp(s1, s2, l2) >= 0 ? 1 : -1; + } + else { + return JimSign(memcmp(s1, s2, l1)); + } +} + +static int JimStringCompareLen(const char *s1, const char *s2, int maxchars, int nocase) +{ + while (*s1 && *s2 && maxchars) { + int c1, c2; + s1 += utf8_tounicode_case(s1, &c1, nocase); + s2 += utf8_tounicode_case(s2, &c2, nocase); + if (c1 != c2) { + return JimSign(c1 - c2); + } + maxchars--; + } + if (!maxchars) { + return 0; + } + + if (*s1) { + return 1; + } + if (*s2) { + return -1; + } + return 0; +} + +static int JimStringFirst(const char *s1, int l1, const char *s2, int l2, int idx) +{ + int i; + int l1bytelen; + + if (!l1 || !l2 || l1 > l2) { + return -1; + } + if (idx < 0) + idx = 0; + s2 += utf8_index(s2, idx); + + l1bytelen = utf8_index(s1, l1); + + for (i = idx; i <= l2 - l1; i++) { + int c; + if (memcmp(s2, s1, l1bytelen) == 0) { + return i; + } + s2 += utf8_tounicode(s2, &c); + } + return -1; +} + +static int JimStringLast(const char *s1, int l1, const char *s2, int l2) +{ + const char *p; + + if (!l1 || !l2 || l1 > l2) + return -1; + + + for (p = s2 + l2 - 1; p != s2 - 1; p--) { + if (*p == *s1 && memcmp(s1, p, l1) == 0) { + return p - s2; + } + } + return -1; +} + +#ifdef JIM_UTF8 +static int JimStringLastUtf8(const char *s1, int l1, const char *s2, int l2) +{ + int n = JimStringLast(s1, utf8_index(s1, l1), s2, utf8_index(s2, l2)); + if (n > 0) { + n = utf8_strlen(s2, n); + } + return n; +} +#endif + +static int JimCheckConversion(const char *str, const char *endptr) +{ + if (str[0] == '\0' || str == endptr) { + return JIM_ERR; + } + + if (endptr[0] != '\0') { + while (*endptr) { + if (!isspace(UCHAR(*endptr))) { + return JIM_ERR; + } + endptr++; + } + } + return JIM_OK; +} + +static int JimNumberBase(const char *str, int *base, int *sign) +{ + int i = 0; + + *base = 10; + + while (isspace(UCHAR(str[i]))) { + i++; + } + + if (str[i] == '-') { + *sign = -1; + i++; + } + else { + if (str[i] == '+') { + i++; + } + *sign = 1; + } + + if (str[i] != '0') { + + return 0; + } + + + switch (str[i + 1]) { + case 'x': case 'X': *base = 16; break; + case 'o': case 'O': *base = 8; break; + case 'b': case 'B': *base = 2; break; + default: return 0; + } + i += 2; + + if (str[i] != '-' && str[i] != '+' && !isspace(UCHAR(str[i]))) { + + return i; + } + + *base = 10; + return 0; +} + +static long jim_strtol(const char *str, char **endptr) +{ + int sign; + int base; + int i = JimNumberBase(str, &base, &sign); + + if (base != 10) { + long value = strtol(str + i, endptr, base); + if (endptr == NULL || *endptr != str + i) { + return value * sign; + } + } + + + return strtol(str, endptr, 10); +} + + +static jim_wide jim_strtoull(const char *str, char **endptr) +{ +#ifdef HAVE_LONG_LONG + int sign; + int base; + int i = JimNumberBase(str, &base, &sign); + + if (base != 10) { + jim_wide value = strtoull(str + i, endptr, base); + if (endptr == NULL || *endptr != str + i) { + return value * sign; + } + } + + + return strtoull(str, endptr, 10); +#else + return (unsigned long)jim_strtol(str, endptr); +#endif +} + +int Jim_StringToWide(const char *str, jim_wide * widePtr, int base) +{ + char *endptr; + + if (base) { + *widePtr = strtoull(str, &endptr, base); + } + else { + *widePtr = jim_strtoull(str, &endptr); + } + + return JimCheckConversion(str, endptr); +} + +int Jim_StringToDouble(const char *str, double *doublePtr) +{ + char *endptr; + + + errno = 0; + + *doublePtr = strtod(str, &endptr); + + return JimCheckConversion(str, endptr); +} + +static jim_wide JimPowWide(jim_wide b, jim_wide e) +{ + jim_wide res = 1; + + + if (b == 1) { + + return 1; + } + if (e < 0) { + if (b != -1) { + return 0; + } + e = -e; + } + while (e) + { + if (e & 1) { + res *= b; + } + e >>= 1; + b *= b; + } + return res; +} + +#ifdef JIM_DEBUG_PANIC +static void JimPanicDump(int condition, const char *fmt, ...) +{ + va_list ap; + + if (!condition) { + return; + } + + va_start(ap, fmt); + + fprintf(stderr, "\nJIM INTERPRETER PANIC: "); + vfprintf(stderr, fmt, ap); + fprintf(stderr, "\n\n"); + va_end(ap); + +#ifdef HAVE_BACKTRACE + { + void *array[40]; + int size, i; + char **strings; + + size = backtrace(array, 40); + strings = backtrace_symbols(array, size); + for (i = 0; i < size; i++) + fprintf(stderr, "[backtrace] %s\n", strings[i]); + fprintf(stderr, "[backtrace] Include the above lines and the output\n"); + fprintf(stderr, "[backtrace] of 'nm ' in the bug report.\n"); + } +#endif + + exit(1); +} +#endif + + +void *Jim_Alloc(int size) +{ + return size ? malloc(size) : NULL; +} + +void Jim_Free(void *ptr) +{ + free(ptr); +} + +void *Jim_Realloc(void *ptr, int size) +{ + return realloc(ptr, size); +} + +char *Jim_StrDup(const char *s) +{ + return strdup(s); +} + +char *Jim_StrDupLen(const char *s, int l) +{ + char *copy = Jim_Alloc(l + 1); + + memcpy(copy, s, l + 1); + copy[l] = 0; + return copy; +} + + + +static jim_wide JimClock(void) +{ + struct timeval tv; + + gettimeofday(&tv, NULL); + return (jim_wide) tv.tv_sec * 1000000 + tv.tv_usec; +} + + + +static void JimExpandHashTableIfNeeded(Jim_HashTable *ht); +static unsigned int JimHashTableNextPower(unsigned int size); +static Jim_HashEntry *JimInsertHashEntry(Jim_HashTable *ht, const void *key, int replace); + + + + +unsigned int Jim_IntHashFunction(unsigned int key) +{ + key += ~(key << 15); + key ^= (key >> 10); + key += (key << 3); + key ^= (key >> 6); + key += ~(key << 11); + key ^= (key >> 16); + return key; +} + +unsigned int Jim_GenHashFunction(const unsigned char *buf, int len) +{ + unsigned int h = 0; + + while (len--) + h += (h << 3) + *buf++; + return h; +} + + + + +static void JimResetHashTable(Jim_HashTable *ht) +{ + ht->table = NULL; + ht->size = 0; + ht->sizemask = 0; + ht->used = 0; + ht->collisions = 0; +#ifdef JIM_RANDOMISE_HASH + ht->uniq = (rand() ^ time(NULL) ^ clock()); +#else + ht->uniq = 0; +#endif +} + +static void JimInitHashTableIterator(Jim_HashTable *ht, Jim_HashTableIterator *iter) +{ + iter->ht = ht; + iter->index = -1; + iter->entry = NULL; + iter->nextEntry = NULL; +} + + +int Jim_InitHashTable(Jim_HashTable *ht, const Jim_HashTableType *type, void *privDataPtr) +{ + JimResetHashTable(ht); + ht->type = type; + ht->privdata = privDataPtr; + return JIM_OK; +} + +void Jim_ResizeHashTable(Jim_HashTable *ht) +{ + int minimal = ht->used; + + if (minimal < JIM_HT_INITIAL_SIZE) + minimal = JIM_HT_INITIAL_SIZE; + Jim_ExpandHashTable(ht, minimal); +} + + +void Jim_ExpandHashTable(Jim_HashTable *ht, unsigned int size) +{ + Jim_HashTable n; + unsigned int realsize = JimHashTableNextPower(size), i; + + if (size <= ht->used) + return; + + Jim_InitHashTable(&n, ht->type, ht->privdata); + n.size = realsize; + n.sizemask = realsize - 1; + n.table = Jim_Alloc(realsize * sizeof(Jim_HashEntry *)); + + n.uniq = ht->uniq; + + + memset(n.table, 0, realsize * sizeof(Jim_HashEntry *)); + + n.used = ht->used; + for (i = 0; ht->used > 0; i++) { + Jim_HashEntry *he, *nextHe; + + if (ht->table[i] == NULL) + continue; + + + he = ht->table[i]; + while (he) { + unsigned int h; + + nextHe = he->next; + + h = Jim_HashKey(ht, he->key) & n.sizemask; + he->next = n.table[h]; + n.table[h] = he; + ht->used--; + + he = nextHe; + } + } + assert(ht->used == 0); + Jim_Free(ht->table); + + + *ht = n; +} + + +int Jim_AddHashEntry(Jim_HashTable *ht, const void *key, void *val) +{ + Jim_HashEntry *entry; + + entry = JimInsertHashEntry(ht, key, 0); + if (entry == NULL) + return JIM_ERR; + + + Jim_SetHashKey(ht, entry, key); + Jim_SetHashVal(ht, entry, val); + return JIM_OK; +} + + +int Jim_ReplaceHashEntry(Jim_HashTable *ht, const void *key, void *val) +{ + int existed; + Jim_HashEntry *entry; + + entry = JimInsertHashEntry(ht, key, 1); + if (entry->key) { + if (ht->type->valDestructor && ht->type->valDup) { + void *newval = ht->type->valDup(ht->privdata, val); + ht->type->valDestructor(ht->privdata, entry->u.val); + entry->u.val = newval; + } + else { + Jim_FreeEntryVal(ht, entry); + Jim_SetHashVal(ht, entry, val); + } + existed = 1; + } + else { + + Jim_SetHashKey(ht, entry, key); + Jim_SetHashVal(ht, entry, val); + existed = 0; + } + + return existed; +} + + +int Jim_DeleteHashEntry(Jim_HashTable *ht, const void *key) +{ + unsigned int h; + Jim_HashEntry *he, *prevHe; + + if (ht->used == 0) + return JIM_ERR; + h = Jim_HashKey(ht, key) & ht->sizemask; + he = ht->table[h]; + + prevHe = NULL; + while (he) { + if (Jim_CompareHashKeys(ht, key, he->key)) { + + if (prevHe) + prevHe->next = he->next; + else + ht->table[h] = he->next; + Jim_FreeEntryKey(ht, he); + Jim_FreeEntryVal(ht, he); + Jim_Free(he); + ht->used--; + return JIM_OK; + } + prevHe = he; + he = he->next; + } + return JIM_ERR; +} + + +int Jim_FreeHashTable(Jim_HashTable *ht) +{ + unsigned int i; + + + for (i = 0; ht->used > 0; i++) { + Jim_HashEntry *he, *nextHe; + + if ((he = ht->table[i]) == NULL) + continue; + while (he) { + nextHe = he->next; + Jim_FreeEntryKey(ht, he); + Jim_FreeEntryVal(ht, he); + Jim_Free(he); + ht->used--; + he = nextHe; + } + } + + Jim_Free(ht->table); + + JimResetHashTable(ht); + return JIM_OK; +} + +Jim_HashEntry *Jim_FindHashEntry(Jim_HashTable *ht, const void *key) +{ + Jim_HashEntry *he; + unsigned int h; + + if (ht->used == 0) + return NULL; + h = Jim_HashKey(ht, key) & ht->sizemask; + he = ht->table[h]; + while (he) { + if (Jim_CompareHashKeys(ht, key, he->key)) + return he; + he = he->next; + } + return NULL; +} + +Jim_HashTableIterator *Jim_GetHashTableIterator(Jim_HashTable *ht) +{ + Jim_HashTableIterator *iter = Jim_Alloc(sizeof(*iter)); + JimInitHashTableIterator(ht, iter); + return iter; +} + +Jim_HashEntry *Jim_NextHashEntry(Jim_HashTableIterator *iter) +{ + while (1) { + if (iter->entry == NULL) { + iter->index++; + if (iter->index >= (signed)iter->ht->size) + break; + iter->entry = iter->ht->table[iter->index]; + } + else { + iter->entry = iter->nextEntry; + } + if (iter->entry) { + iter->nextEntry = iter->entry->next; + return iter->entry; + } + } + return NULL; +} + + + + +static void JimExpandHashTableIfNeeded(Jim_HashTable *ht) +{ + if (ht->size == 0) + Jim_ExpandHashTable(ht, JIM_HT_INITIAL_SIZE); + if (ht->size == ht->used) + Jim_ExpandHashTable(ht, ht->size * 2); +} + + +static unsigned int JimHashTableNextPower(unsigned int size) +{ + unsigned int i = JIM_HT_INITIAL_SIZE; + + if (size >= 2147483648U) + return 2147483648U; + while (1) { + if (i >= size) + return i; + i *= 2; + } +} + +static Jim_HashEntry *JimInsertHashEntry(Jim_HashTable *ht, const void *key, int replace) +{ + unsigned int h; + Jim_HashEntry *he; + + + JimExpandHashTableIfNeeded(ht); + + + h = Jim_HashKey(ht, key) & ht->sizemask; + + he = ht->table[h]; + while (he) { + if (Jim_CompareHashKeys(ht, key, he->key)) + return replace ? he : NULL; + he = he->next; + } + + + he = Jim_Alloc(sizeof(*he)); + he->next = ht->table[h]; + ht->table[h] = he; + ht->used++; + he->key = NULL; + + return he; +} + + + +static unsigned int JimStringCopyHTHashFunction(const void *key) +{ + return Jim_GenHashFunction(key, strlen(key)); +} + +static void *JimStringCopyHTDup(void *privdata, const void *key) +{ + return Jim_StrDup(key); +} + +static int JimStringCopyHTKeyCompare(void *privdata, const void *key1, const void *key2) +{ + return strcmp(key1, key2) == 0; +} + +static void JimStringCopyHTKeyDestructor(void *privdata, void *key) +{ + Jim_Free(key); +} + +static const Jim_HashTableType JimPackageHashTableType = { + JimStringCopyHTHashFunction, + JimStringCopyHTDup, + NULL, + JimStringCopyHTKeyCompare, + JimStringCopyHTKeyDestructor, + NULL +}; + +typedef struct AssocDataValue +{ + Jim_InterpDeleteProc *delProc; + void *data; +} AssocDataValue; + +static void JimAssocDataHashTableValueDestructor(void *privdata, void *data) +{ + AssocDataValue *assocPtr = (AssocDataValue *) data; + + if (assocPtr->delProc != NULL) + assocPtr->delProc((Jim_Interp *)privdata, assocPtr->data); + Jim_Free(data); +} + +static const Jim_HashTableType JimAssocDataHashTableType = { + JimStringCopyHTHashFunction, + JimStringCopyHTDup, + NULL, + JimStringCopyHTKeyCompare, + JimStringCopyHTKeyDestructor, + JimAssocDataHashTableValueDestructor +}; + +void Jim_InitStack(Jim_Stack *stack) +{ + stack->len = 0; + stack->maxlen = 0; + stack->vector = NULL; +} + +void Jim_FreeStack(Jim_Stack *stack) +{ + Jim_Free(stack->vector); +} + +int Jim_StackLen(Jim_Stack *stack) +{ + return stack->len; +} + +void Jim_StackPush(Jim_Stack *stack, void *element) +{ + int neededLen = stack->len + 1; + + if (neededLen > stack->maxlen) { + stack->maxlen = neededLen < 20 ? 20 : neededLen * 2; + stack->vector = Jim_Realloc(stack->vector, sizeof(void *) * stack->maxlen); + } + stack->vector[stack->len] = element; + stack->len++; +} + +void *Jim_StackPop(Jim_Stack *stack) +{ + if (stack->len == 0) + return NULL; + stack->len--; + return stack->vector[stack->len]; +} + +void *Jim_StackPeek(Jim_Stack *stack) +{ + if (stack->len == 0) + return NULL; + return stack->vector[stack->len - 1]; +} + +void Jim_FreeStackElements(Jim_Stack *stack, void (*freeFunc) (void *ptr)) +{ + int i; + + for (i = 0; i < stack->len; i++) + freeFunc(stack->vector[i]); +} + + + +#define JIM_TT_NONE 0 +#define JIM_TT_STR 1 +#define JIM_TT_ESC 2 +#define JIM_TT_VAR 3 +#define JIM_TT_DICTSUGAR 4 +#define JIM_TT_CMD 5 + +#define JIM_TT_SEP 6 +#define JIM_TT_EOL 7 +#define JIM_TT_EOF 8 + +#define JIM_TT_LINE 9 +#define JIM_TT_WORD 10 + + +#define JIM_TT_SUBEXPR_START 11 +#define JIM_TT_SUBEXPR_END 12 +#define JIM_TT_SUBEXPR_COMMA 13 +#define JIM_TT_EXPR_INT 14 +#define JIM_TT_EXPR_DOUBLE 15 +#define JIM_TT_EXPR_BOOLEAN 16 + +#define JIM_TT_EXPRSUGAR 17 + + +#define JIM_TT_EXPR_OP 20 + +#define TOKEN_IS_SEP(type) (type >= JIM_TT_SEP && type <= JIM_TT_EOF) + +#define TOKEN_IS_EXPR_START(type) (type == JIM_TT_NONE || type == JIM_TT_SUBEXPR_START || type == JIM_TT_SUBEXPR_COMMA) + +#define TOKEN_IS_EXPR_OP(type) (type >= JIM_TT_EXPR_OP) + +struct JimParseMissing { + int ch; + int line; +}; + +struct JimParserCtx +{ + const char *p; + int len; + int linenr; + const char *tstart; + const char *tend; + int tline; + int tt; + int eof; + int inquote; + int comment; + struct JimParseMissing missing; +}; + +static int JimParseScript(struct JimParserCtx *pc); +static int JimParseSep(struct JimParserCtx *pc); +static int JimParseEol(struct JimParserCtx *pc); +static int JimParseCmd(struct JimParserCtx *pc); +static int JimParseQuote(struct JimParserCtx *pc); +static int JimParseVar(struct JimParserCtx *pc); +static int JimParseBrace(struct JimParserCtx *pc); +static int JimParseStr(struct JimParserCtx *pc); +static int JimParseComment(struct JimParserCtx *pc); +static void JimParseSubCmd(struct JimParserCtx *pc); +static int JimParseSubQuote(struct JimParserCtx *pc); +static Jim_Obj *JimParserGetTokenObj(Jim_Interp *interp, struct JimParserCtx *pc); + +static void JimParserInit(struct JimParserCtx *pc, const char *prg, int len, int linenr) +{ + pc->p = prg; + pc->len = len; + pc->tstart = NULL; + pc->tend = NULL; + pc->tline = 0; + pc->tt = JIM_TT_NONE; + pc->eof = 0; + pc->inquote = 0; + pc->linenr = linenr; + pc->comment = 1; + pc->missing.ch = ' '; + pc->missing.line = linenr; +} + +static int JimParseScript(struct JimParserCtx *pc) +{ + while (1) { + if (!pc->len) { + pc->tstart = pc->p; + pc->tend = pc->p - 1; + pc->tline = pc->linenr; + pc->tt = JIM_TT_EOL; + pc->eof = 1; + return JIM_OK; + } + switch (*(pc->p)) { + case '\\': + if (*(pc->p + 1) == '\n' && !pc->inquote) { + return JimParseSep(pc); + } + pc->comment = 0; + return JimParseStr(pc); + case ' ': + case '\t': + case '\r': + case '\f': + if (!pc->inquote) + return JimParseSep(pc); + pc->comment = 0; + return JimParseStr(pc); + case '\n': + case ';': + pc->comment = 1; + if (!pc->inquote) + return JimParseEol(pc); + return JimParseStr(pc); + case '[': + pc->comment = 0; + return JimParseCmd(pc); + case '$': + pc->comment = 0; + if (JimParseVar(pc) == JIM_ERR) { + + pc->tstart = pc->tend = pc->p++; + pc->len--; + pc->tt = JIM_TT_ESC; + } + return JIM_OK; + case '#': + if (pc->comment) { + JimParseComment(pc); + continue; + } + return JimParseStr(pc); + default: + pc->comment = 0; + return JimParseStr(pc); + } + return JIM_OK; + } +} + +static int JimParseSep(struct JimParserCtx *pc) +{ + pc->tstart = pc->p; + pc->tline = pc->linenr; + while (isspace(UCHAR(*pc->p)) || (*pc->p == '\\' && *(pc->p + 1) == '\n')) { + if (*pc->p == '\n') { + break; + } + if (*pc->p == '\\') { + pc->p++; + pc->len--; + pc->linenr++; + } + pc->p++; + pc->len--; + } + pc->tend = pc->p - 1; + pc->tt = JIM_TT_SEP; + return JIM_OK; +} + +static int JimParseEol(struct JimParserCtx *pc) +{ + pc->tstart = pc->p; + pc->tline = pc->linenr; + while (isspace(UCHAR(*pc->p)) || *pc->p == ';') { + if (*pc->p == '\n') + pc->linenr++; + pc->p++; + pc->len--; + } + pc->tend = pc->p - 1; + pc->tt = JIM_TT_EOL; + return JIM_OK; +} + + +static void JimParseSubBrace(struct JimParserCtx *pc) +{ + int level = 1; + + + pc->p++; + pc->len--; + while (pc->len) { + switch (*pc->p) { + case '\\': + if (pc->len > 1) { + if (*++pc->p == '\n') { + pc->linenr++; + } + pc->len--; + } + break; + + case '{': + level++; + break; + + case '}': + if (--level == 0) { + pc->tend = pc->p - 1; + pc->p++; + pc->len--; + return; + } + break; + + case '\n': + pc->linenr++; + break; + } + pc->p++; + pc->len--; + } + pc->missing.ch = '{'; + pc->missing.line = pc->tline; + pc->tend = pc->p - 1; +} + +static int JimParseSubQuote(struct JimParserCtx *pc) +{ + int tt = JIM_TT_STR; + int line = pc->tline; + + + pc->p++; + pc->len--; + while (pc->len) { + switch (*pc->p) { + case '\\': + if (pc->len > 1) { + if (*++pc->p == '\n') { + pc->linenr++; + } + pc->len--; + tt = JIM_TT_ESC; + } + break; + + case '"': + pc->tend = pc->p - 1; + pc->p++; + pc->len--; + return tt; + + case '[': + JimParseSubCmd(pc); + tt = JIM_TT_ESC; + continue; + + case '\n': + pc->linenr++; + break; + + case '$': + tt = JIM_TT_ESC; + break; + } + pc->p++; + pc->len--; + } + pc->missing.ch = '"'; + pc->missing.line = line; + pc->tend = pc->p - 1; + return tt; +} + +static void JimParseSubCmd(struct JimParserCtx *pc) +{ + int level = 1; + int startofword = 1; + int line = pc->tline; + + + pc->p++; + pc->len--; + while (pc->len) { + switch (*pc->p) { + case '\\': + if (pc->len > 1) { + if (*++pc->p == '\n') { + pc->linenr++; + } + pc->len--; + } + break; + + case '[': + level++; + break; + + case ']': + if (--level == 0) { + pc->tend = pc->p - 1; + pc->p++; + pc->len--; + return; + } + break; + + case '"': + if (startofword) { + JimParseSubQuote(pc); + continue; + } + break; + + case '{': + JimParseSubBrace(pc); + startofword = 0; + continue; + + case '\n': + pc->linenr++; + break; + } + startofword = isspace(UCHAR(*pc->p)); + pc->p++; + pc->len--; + } + pc->missing.ch = '['; + pc->missing.line = line; + pc->tend = pc->p - 1; +} + +static int JimParseBrace(struct JimParserCtx *pc) +{ + pc->tstart = pc->p + 1; + pc->tline = pc->linenr; + pc->tt = JIM_TT_STR; + JimParseSubBrace(pc); + return JIM_OK; +} + +static int JimParseCmd(struct JimParserCtx *pc) +{ + pc->tstart = pc->p + 1; + pc->tline = pc->linenr; + pc->tt = JIM_TT_CMD; + JimParseSubCmd(pc); + return JIM_OK; +} + +static int JimParseQuote(struct JimParserCtx *pc) +{ + pc->tstart = pc->p + 1; + pc->tline = pc->linenr; + pc->tt = JimParseSubQuote(pc); + return JIM_OK; +} + +static int JimParseVar(struct JimParserCtx *pc) +{ + + pc->p++; + pc->len--; + +#ifdef EXPRSUGAR_BRACKET + if (*pc->p == '[') { + + JimParseCmd(pc); + pc->tt = JIM_TT_EXPRSUGAR; + return JIM_OK; + } +#endif + + pc->tstart = pc->p; + pc->tt = JIM_TT_VAR; + pc->tline = pc->linenr; + + if (*pc->p == '{') { + pc->tstart = ++pc->p; + pc->len--; + + while (pc->len && *pc->p != '}') { + if (*pc->p == '\n') { + pc->linenr++; + } + pc->p++; + pc->len--; + } + pc->tend = pc->p - 1; + if (pc->len) { + pc->p++; + pc->len--; + } + } + else { + while (1) { + + if (pc->p[0] == ':' && pc->p[1] == ':') { + while (*pc->p == ':') { + pc->p++; + pc->len--; + } + continue; + } + if (isalnum(UCHAR(*pc->p)) || *pc->p == '_' || UCHAR(*pc->p) >= 0x80) { + pc->p++; + pc->len--; + continue; + } + break; + } + + if (*pc->p == '(') { + int count = 1; + const char *paren = NULL; + + pc->tt = JIM_TT_DICTSUGAR; + + while (count && pc->len) { + pc->p++; + pc->len--; + if (*pc->p == '\\' && pc->len >= 1) { + pc->p++; + pc->len--; + } + else if (*pc->p == '(') { + count++; + } + else if (*pc->p == ')') { + paren = pc->p; + count--; + } + } + if (count == 0) { + pc->p++; + pc->len--; + } + else if (paren) { + + paren++; + pc->len += (pc->p - paren); + pc->p = paren; + } +#ifndef EXPRSUGAR_BRACKET + if (*pc->tstart == '(') { + pc->tt = JIM_TT_EXPRSUGAR; + } +#endif + } + pc->tend = pc->p - 1; + } + if (pc->tstart == pc->p) { + pc->p--; + pc->len++; + return JIM_ERR; + } + return JIM_OK; +} + +static int JimParseStr(struct JimParserCtx *pc) +{ + if (pc->tt == JIM_TT_SEP || pc->tt == JIM_TT_EOL || + pc->tt == JIM_TT_NONE || pc->tt == JIM_TT_STR) { + + if (*pc->p == '{') { + return JimParseBrace(pc); + } + if (*pc->p == '"') { + pc->inquote = 1; + pc->p++; + pc->len--; + + pc->missing.line = pc->tline; + } + } + pc->tstart = pc->p; + pc->tline = pc->linenr; + while (1) { + if (pc->len == 0) { + if (pc->inquote) { + pc->missing.ch = '"'; + } + pc->tend = pc->p - 1; + pc->tt = JIM_TT_ESC; + return JIM_OK; + } + switch (*pc->p) { + case '\\': + if (!pc->inquote && *(pc->p + 1) == '\n') { + pc->tend = pc->p - 1; + pc->tt = JIM_TT_ESC; + return JIM_OK; + } + if (pc->len >= 2) { + if (*(pc->p + 1) == '\n') { + pc->linenr++; + } + pc->p++; + pc->len--; + } + else if (pc->len == 1) { + + pc->missing.ch = '\\'; + } + break; + case '(': + + if (pc->len > 1 && pc->p[1] != '$') { + break; + } + + case ')': + + if (*pc->p == '(' || pc->tt == JIM_TT_VAR) { + if (pc->p == pc->tstart) { + + pc->p++; + pc->len--; + } + pc->tend = pc->p - 1; + pc->tt = JIM_TT_ESC; + return JIM_OK; + } + break; + + case '$': + case '[': + pc->tend = pc->p - 1; + pc->tt = JIM_TT_ESC; + return JIM_OK; + case ' ': + case '\t': + case '\n': + case '\r': + case '\f': + case ';': + if (!pc->inquote) { + pc->tend = pc->p - 1; + pc->tt = JIM_TT_ESC; + return JIM_OK; + } + else if (*pc->p == '\n') { + pc->linenr++; + } + break; + case '"': + if (pc->inquote) { + pc->tend = pc->p - 1; + pc->tt = JIM_TT_ESC; + pc->p++; + pc->len--; + pc->inquote = 0; + return JIM_OK; + } + break; + } + pc->p++; + pc->len--; + } + return JIM_OK; +} + +static int JimParseComment(struct JimParserCtx *pc) +{ + while (*pc->p) { + if (*pc->p == '\\') { + pc->p++; + pc->len--; + if (pc->len == 0) { + pc->missing.ch = '\\'; + return JIM_OK; + } + if (*pc->p == '\n') { + pc->linenr++; + } + } + else if (*pc->p == '\n') { + pc->p++; + pc->len--; + pc->linenr++; + break; + } + pc->p++; + pc->len--; + } + return JIM_OK; +} + + +static int xdigitval(int c) +{ + if (c >= '0' && c <= '9') + return c - '0'; + if (c >= 'a' && c <= 'f') + return c - 'a' + 10; + if (c >= 'A' && c <= 'F') + return c - 'A' + 10; + return -1; +} + +static int odigitval(int c) +{ + if (c >= '0' && c <= '7') + return c - '0'; + return -1; +} + +static int JimEscape(char *dest, const char *s, int slen) +{ + char *p = dest; + int i, len; + + for (i = 0; i < slen; i++) { + switch (s[i]) { + case '\\': + switch (s[i + 1]) { + case 'a': + *p++ = 0x7; + i++; + break; + case 'b': + *p++ = 0x8; + i++; + break; + case 'f': + *p++ = 0xc; + i++; + break; + case 'n': + *p++ = 0xa; + i++; + break; + case 'r': + *p++ = 0xd; + i++; + break; + case 't': + *p++ = 0x9; + i++; + break; + case 'u': + case 'U': + case 'x': + { + unsigned val = 0; + int k; + int maxchars = 2; + + i++; + + if (s[i] == 'U') { + maxchars = 8; + } + else if (s[i] == 'u') { + if (s[i + 1] == '{') { + maxchars = 6; + i++; + } + else { + maxchars = 4; + } + } + + for (k = 0; k < maxchars; k++) { + int c = xdigitval(s[i + k + 1]); + if (c == -1) { + break; + } + val = (val << 4) | c; + } + + if (s[i] == '{') { + if (k == 0 || val > 0x1fffff || s[i + k + 1] != '}') { + + i--; + k = 0; + } + else { + + k++; + } + } + if (k) { + + if (s[i] == 'x') { + *p++ = val; + } + else { + p += utf8_fromunicode(p, val); + } + i += k; + break; + } + + *p++ = s[i]; + } + break; + case 'v': + *p++ = 0xb; + i++; + break; + case '\0': + *p++ = '\\'; + i++; + break; + case '\n': + + *p++ = ' '; + do { + i++; + } while (s[i + 1] == ' ' || s[i + 1] == '\t'); + break; + case '0': + case '1': + case '2': + case '3': + case '4': + case '5': + case '6': + case '7': + + { + int val = 0; + int c = odigitval(s[i + 1]); + + val = c; + c = odigitval(s[i + 2]); + if (c == -1) { + *p++ = val; + i++; + break; + } + val = (val * 8) + c; + c = odigitval(s[i + 3]); + if (c == -1) { + *p++ = val; + i += 2; + break; + } + val = (val * 8) + c; + *p++ = val; + i += 3; + } + break; + default: + *p++ = s[i + 1]; + i++; + break; + } + break; + default: + *p++ = s[i]; + break; + } + } + len = p - dest; + *p = '\0'; + return len; +} + +static Jim_Obj *JimParserGetTokenObj(Jim_Interp *interp, struct JimParserCtx *pc) +{ + const char *start, *end; + char *token; + int len; + + start = pc->tstart; + end = pc->tend; + len = (end - start) + 1; + if (len < 0) { + len = 0; + } + token = Jim_Alloc(len + 1); + if (pc->tt != JIM_TT_ESC) { + + memcpy(token, start, len); + token[len] = '\0'; + } + else { + + len = JimEscape(token, start, len); + } + + return Jim_NewStringObjNoAlloc(interp, token, len); +} + +static int JimParseListSep(struct JimParserCtx *pc); +static int JimParseListStr(struct JimParserCtx *pc); +static int JimParseListQuote(struct JimParserCtx *pc); + +static int JimParseList(struct JimParserCtx *pc) +{ + if (isspace(UCHAR(*pc->p))) { + return JimParseListSep(pc); + } + switch (*pc->p) { + case '"': + return JimParseListQuote(pc); + + case '{': + return JimParseBrace(pc); + + default: + if (pc->len) { + return JimParseListStr(pc); + } + break; + } + + pc->tstart = pc->tend = pc->p; + pc->tline = pc->linenr; + pc->tt = JIM_TT_EOL; + pc->eof = 1; + return JIM_OK; +} + +static int JimParseListSep(struct JimParserCtx *pc) +{ + pc->tstart = pc->p; + pc->tline = pc->linenr; + while (isspace(UCHAR(*pc->p))) { + if (*pc->p == '\n') { + pc->linenr++; + } + pc->p++; + pc->len--; + } + pc->tend = pc->p - 1; + pc->tt = JIM_TT_SEP; + return JIM_OK; +} + +static int JimParseListQuote(struct JimParserCtx *pc) +{ + pc->p++; + pc->len--; + + pc->tstart = pc->p; + pc->tline = pc->linenr; + pc->tt = JIM_TT_STR; + + while (pc->len) { + switch (*pc->p) { + case '\\': + pc->tt = JIM_TT_ESC; + if (--pc->len == 0) { + + pc->tend = pc->p; + return JIM_OK; + } + pc->p++; + break; + case '\n': + pc->linenr++; + break; + case '"': + pc->tend = pc->p - 1; + pc->p++; + pc->len--; + return JIM_OK; + } + pc->p++; + pc->len--; + } + + pc->tend = pc->p - 1; + return JIM_OK; +} + +static int JimParseListStr(struct JimParserCtx *pc) +{ + pc->tstart = pc->p; + pc->tline = pc->linenr; + pc->tt = JIM_TT_STR; + + while (pc->len) { + if (isspace(UCHAR(*pc->p))) { + pc->tend = pc->p - 1; + return JIM_OK; + } + if (*pc->p == '\\') { + if (--pc->len == 0) { + + pc->tend = pc->p; + return JIM_OK; + } + pc->tt = JIM_TT_ESC; + pc->p++; + } + pc->p++; + pc->len--; + } + pc->tend = pc->p - 1; + return JIM_OK; +} + + + +Jim_Obj *Jim_NewObj(Jim_Interp *interp) +{ + Jim_Obj *objPtr; + + + if (interp->freeList != NULL) { + + objPtr = interp->freeList; + interp->freeList = objPtr->nextObjPtr; + } + else { + + objPtr = Jim_Alloc(sizeof(*objPtr)); + } + + objPtr->refCount = 0; + + + objPtr->prevObjPtr = NULL; + objPtr->nextObjPtr = interp->liveList; + if (interp->liveList) + interp->liveList->prevObjPtr = objPtr; + interp->liveList = objPtr; + + return objPtr; +} + +void Jim_FreeObj(Jim_Interp *interp, Jim_Obj *objPtr) +{ + + JimPanic((objPtr->refCount != 0, "!!!Object %p freed with bad refcount %d, type=%s", objPtr, + objPtr->refCount, objPtr->typePtr ? objPtr->typePtr->name : "")); + + + Jim_FreeIntRep(interp, objPtr); + + if (objPtr->bytes != NULL) { + if (objPtr->bytes != JimEmptyStringRep) + Jim_Free(objPtr->bytes); + } + + if (objPtr->prevObjPtr) + objPtr->prevObjPtr->nextObjPtr = objPtr->nextObjPtr; + if (objPtr->nextObjPtr) + objPtr->nextObjPtr->prevObjPtr = objPtr->prevObjPtr; + if (interp->liveList == objPtr) + interp->liveList = objPtr->nextObjPtr; +#ifdef JIM_DISABLE_OBJECT_POOL + Jim_Free(objPtr); +#else + + objPtr->prevObjPtr = NULL; + objPtr->nextObjPtr = interp->freeList; + if (interp->freeList) + interp->freeList->prevObjPtr = objPtr; + interp->freeList = objPtr; + objPtr->refCount = -1; +#endif +} + + +void Jim_InvalidateStringRep(Jim_Obj *objPtr) +{ + if (objPtr->bytes != NULL) { + if (objPtr->bytes != JimEmptyStringRep) + Jim_Free(objPtr->bytes); + } + objPtr->bytes = NULL; +} + + +Jim_Obj *Jim_DuplicateObj(Jim_Interp *interp, Jim_Obj *objPtr) +{ + Jim_Obj *dupPtr; + + dupPtr = Jim_NewObj(interp); + if (objPtr->bytes == NULL) { + + dupPtr->bytes = NULL; + } + else if (objPtr->length == 0) { + dupPtr->bytes = JimEmptyStringRep; + dupPtr->length = 0; + dupPtr->typePtr = NULL; + return dupPtr; + } + else { + dupPtr->bytes = Jim_Alloc(objPtr->length + 1); + dupPtr->length = objPtr->length; + + memcpy(dupPtr->bytes, objPtr->bytes, objPtr->length + 1); + } + + + dupPtr->typePtr = objPtr->typePtr; + if (objPtr->typePtr != NULL) { + if (objPtr->typePtr->dupIntRepProc == NULL) { + dupPtr->internalRep = objPtr->internalRep; + } + else { + + objPtr->typePtr->dupIntRepProc(interp, objPtr, dupPtr); + } + } + return dupPtr; +} + +const char *Jim_GetString(Jim_Obj *objPtr, int *lenPtr) +{ + if (objPtr->bytes == NULL) { + + JimPanic((objPtr->typePtr->updateStringProc == NULL, "UpdateStringProc called against '%s' type.", objPtr->typePtr->name)); + objPtr->typePtr->updateStringProc(objPtr); + } + if (lenPtr) + *lenPtr = objPtr->length; + return objPtr->bytes; +} + + +int Jim_Length(Jim_Obj *objPtr) +{ + if (objPtr->bytes == NULL) { + + Jim_GetString(objPtr, NULL); + } + return objPtr->length; +} + + +const char *Jim_String(Jim_Obj *objPtr) +{ + if (objPtr->bytes == NULL) { + + Jim_GetString(objPtr, NULL); + } + return objPtr->bytes; +} + +static void JimSetStringBytes(Jim_Obj *objPtr, const char *str) +{ + objPtr->bytes = Jim_StrDup(str); + objPtr->length = strlen(str); +} + +static void FreeDictSubstInternalRep(Jim_Interp *interp, Jim_Obj *objPtr); +static void DupDictSubstInternalRep(Jim_Interp *interp, Jim_Obj *srcPtr, Jim_Obj *dupPtr); + +static const Jim_ObjType dictSubstObjType = { + "dict-substitution", + FreeDictSubstInternalRep, + DupDictSubstInternalRep, + NULL, + JIM_TYPE_NONE, +}; + +static void FreeInterpolatedInternalRep(Jim_Interp *interp, Jim_Obj *objPtr); +static void DupInterpolatedInternalRep(Jim_Interp *interp, Jim_Obj *srcPtr, Jim_Obj *dupPtr); + +static const Jim_ObjType interpolatedObjType = { + "interpolated", + FreeInterpolatedInternalRep, + DupInterpolatedInternalRep, + NULL, + JIM_TYPE_NONE, +}; + +static void FreeInterpolatedInternalRep(Jim_Interp *interp, Jim_Obj *objPtr) +{ + Jim_DecrRefCount(interp, objPtr->internalRep.dictSubstValue.indexObjPtr); +} + +static void DupInterpolatedInternalRep(Jim_Interp *interp, Jim_Obj *srcPtr, Jim_Obj *dupPtr) +{ + + dupPtr->internalRep = srcPtr->internalRep; + + Jim_IncrRefCount(dupPtr->internalRep.dictSubstValue.indexObjPtr); +} + +static void DupStringInternalRep(Jim_Interp *interp, Jim_Obj *srcPtr, Jim_Obj *dupPtr); +static int SetStringFromAny(Jim_Interp *interp, struct Jim_Obj *objPtr); + +static const Jim_ObjType stringObjType = { + "string", + NULL, + DupStringInternalRep, + NULL, + JIM_TYPE_REFERENCES, +}; + +static void DupStringInternalRep(Jim_Interp *interp, Jim_Obj *srcPtr, Jim_Obj *dupPtr) +{ + JIM_NOTUSED(interp); + + dupPtr->internalRep.strValue.maxLength = srcPtr->length; + dupPtr->internalRep.strValue.charLength = srcPtr->internalRep.strValue.charLength; +} + +static int SetStringFromAny(Jim_Interp *interp, Jim_Obj *objPtr) +{ + if (objPtr->typePtr != &stringObjType) { + + if (objPtr->bytes == NULL) { + + JimPanic((objPtr->typePtr->updateStringProc == NULL, "UpdateStringProc called against '%s' type.", objPtr->typePtr->name)); + objPtr->typePtr->updateStringProc(objPtr); + } + + Jim_FreeIntRep(interp, objPtr); + + objPtr->typePtr = &stringObjType; + objPtr->internalRep.strValue.maxLength = objPtr->length; + + objPtr->internalRep.strValue.charLength = -1; + } + return JIM_OK; +} + +int Jim_Utf8Length(Jim_Interp *interp, Jim_Obj *objPtr) +{ +#ifdef JIM_UTF8 + SetStringFromAny(interp, objPtr); + + if (objPtr->internalRep.strValue.charLength < 0) { + objPtr->internalRep.strValue.charLength = utf8_strlen(objPtr->bytes, objPtr->length); + } + return objPtr->internalRep.strValue.charLength; +#else + return Jim_Length(objPtr); +#endif +} + + +Jim_Obj *Jim_NewStringObj(Jim_Interp *interp, const char *s, int len) +{ + Jim_Obj *objPtr = Jim_NewObj(interp); + + + if (len == -1) + len = strlen(s); + + if (len == 0) { + objPtr->bytes = JimEmptyStringRep; + } + else { + objPtr->bytes = Jim_StrDupLen(s, len); + } + objPtr->length = len; + + + objPtr->typePtr = NULL; + return objPtr; +} + + +Jim_Obj *Jim_NewStringObjUtf8(Jim_Interp *interp, const char *s, int charlen) +{ +#ifdef JIM_UTF8 + + int bytelen = utf8_index(s, charlen); + + Jim_Obj *objPtr = Jim_NewStringObj(interp, s, bytelen); + + + objPtr->typePtr = &stringObjType; + objPtr->internalRep.strValue.maxLength = bytelen; + objPtr->internalRep.strValue.charLength = charlen; + + return objPtr; +#else + return Jim_NewStringObj(interp, s, charlen); +#endif +} + +Jim_Obj *Jim_NewStringObjNoAlloc(Jim_Interp *interp, char *s, int len) +{ + Jim_Obj *objPtr = Jim_NewObj(interp); + + objPtr->bytes = s; + objPtr->length = (len == -1) ? strlen(s) : len; + objPtr->typePtr = NULL; + return objPtr; +} + +static void StringAppendString(Jim_Obj *objPtr, const char *str, int len) +{ + int needlen; + + if (len == -1) + len = strlen(str); + needlen = objPtr->length + len; + if (objPtr->internalRep.strValue.maxLength < needlen || + objPtr->internalRep.strValue.maxLength == 0) { + needlen *= 2; + + if (needlen < 7) { + needlen = 7; + } + if (objPtr->bytes == JimEmptyStringRep) { + objPtr->bytes = Jim_Alloc(needlen + 1); + } + else { + objPtr->bytes = Jim_Realloc(objPtr->bytes, needlen + 1); + } + objPtr->internalRep.strValue.maxLength = needlen; + } + memcpy(objPtr->bytes + objPtr->length, str, len); + objPtr->bytes[objPtr->length + len] = '\0'; + + if (objPtr->internalRep.strValue.charLength >= 0) { + + objPtr->internalRep.strValue.charLength += utf8_strlen(objPtr->bytes + objPtr->length, len); + } + objPtr->length += len; +} + +void Jim_AppendString(Jim_Interp *interp, Jim_Obj *objPtr, const char *str, int len) +{ + JimPanic((Jim_IsShared(objPtr), "Jim_AppendString called with shared object")); + SetStringFromAny(interp, objPtr); + StringAppendString(objPtr, str, len); +} + +void Jim_AppendObj(Jim_Interp *interp, Jim_Obj *objPtr, Jim_Obj *appendObjPtr) +{ + int len; + const char *str = Jim_GetString(appendObjPtr, &len); + Jim_AppendString(interp, objPtr, str, len); +} + +void Jim_AppendStrings(Jim_Interp *interp, Jim_Obj *objPtr, ...) +{ + va_list ap; + + SetStringFromAny(interp, objPtr); + va_start(ap, objPtr); + while (1) { + const char *s = va_arg(ap, const char *); + + if (s == NULL) + break; + Jim_AppendString(interp, objPtr, s, -1); + } + va_end(ap); +} + +int Jim_StringEqObj(Jim_Obj *aObjPtr, Jim_Obj *bObjPtr) +{ + if (aObjPtr == bObjPtr) { + return 1; + } + else { + int Alen, Blen; + const char *sA = Jim_GetString(aObjPtr, &Alen); + const char *sB = Jim_GetString(bObjPtr, &Blen); + + return Alen == Blen && memcmp(sA, sB, Alen) == 0; + } +} + +int Jim_StringMatchObj(Jim_Interp *interp, Jim_Obj *patternObjPtr, Jim_Obj *objPtr, int nocase) +{ + return JimGlobMatch(Jim_String(patternObjPtr), Jim_String(objPtr), nocase); +} + +int Jim_StringCompareObj(Jim_Interp *interp, Jim_Obj *firstObjPtr, Jim_Obj *secondObjPtr, int nocase) +{ + int l1, l2; + const char *s1 = Jim_GetString(firstObjPtr, &l1); + const char *s2 = Jim_GetString(secondObjPtr, &l2); + + if (nocase) { + + return JimStringCompareLen(s1, s2, -1, nocase); + } + return JimStringCompare(s1, l1, s2, l2); +} + +int Jim_StringCompareLenObj(Jim_Interp *interp, Jim_Obj *firstObjPtr, Jim_Obj *secondObjPtr, int nocase) +{ + const char *s1 = Jim_String(firstObjPtr); + const char *s2 = Jim_String(secondObjPtr); + + return JimStringCompareLen(s1, s2, Jim_Utf8Length(interp, firstObjPtr), nocase); +} + +static int JimRelToAbsIndex(int len, int idx) +{ + if (idx < 0) + return len + idx; + return idx; +} + +static void JimRelToAbsRange(int len, int *firstPtr, int *lastPtr, int *rangeLenPtr) +{ + int rangeLen; + + if (*firstPtr > *lastPtr) { + rangeLen = 0; + } + else { + rangeLen = *lastPtr - *firstPtr + 1; + if (rangeLen) { + if (*firstPtr < 0) { + rangeLen += *firstPtr; + *firstPtr = 0; + } + if (*lastPtr >= len) { + rangeLen -= (*lastPtr - (len - 1)); + *lastPtr = len - 1; + } + } + } + if (rangeLen < 0) + rangeLen = 0; + + *rangeLenPtr = rangeLen; +} + +static int JimStringGetRange(Jim_Interp *interp, Jim_Obj *firstObjPtr, Jim_Obj *lastObjPtr, + int len, int *first, int *last, int *range) +{ + if (Jim_GetIndex(interp, firstObjPtr, first) != JIM_OK) { + return JIM_ERR; + } + if (Jim_GetIndex(interp, lastObjPtr, last) != JIM_OK) { + return JIM_ERR; + } + *first = JimRelToAbsIndex(len, *first); + *last = JimRelToAbsIndex(len, *last); + JimRelToAbsRange(len, first, last, range); + return JIM_OK; +} + +Jim_Obj *Jim_StringByteRangeObj(Jim_Interp *interp, + Jim_Obj *strObjPtr, Jim_Obj *firstObjPtr, Jim_Obj *lastObjPtr) +{ + int first, last; + const char *str; + int rangeLen; + int bytelen; + + str = Jim_GetString(strObjPtr, &bytelen); + + if (JimStringGetRange(interp, firstObjPtr, lastObjPtr, bytelen, &first, &last, &rangeLen) != JIM_OK) { + return NULL; + } + + if (first == 0 && rangeLen == bytelen) { + return strObjPtr; + } + return Jim_NewStringObj(interp, str + first, rangeLen); +} + +Jim_Obj *Jim_StringRangeObj(Jim_Interp *interp, + Jim_Obj *strObjPtr, Jim_Obj *firstObjPtr, Jim_Obj *lastObjPtr) +{ +#ifdef JIM_UTF8 + int first, last; + const char *str; + int len, rangeLen; + int bytelen; + + str = Jim_GetString(strObjPtr, &bytelen); + len = Jim_Utf8Length(interp, strObjPtr); + + if (JimStringGetRange(interp, firstObjPtr, lastObjPtr, len, &first, &last, &rangeLen) != JIM_OK) { + return NULL; + } + + if (first == 0 && rangeLen == len) { + return strObjPtr; + } + if (len == bytelen) { + + return Jim_NewStringObj(interp, str + first, rangeLen); + } + return Jim_NewStringObjUtf8(interp, str + utf8_index(str, first), rangeLen); +#else + return Jim_StringByteRangeObj(interp, strObjPtr, firstObjPtr, lastObjPtr); +#endif +} + +Jim_Obj *JimStringReplaceObj(Jim_Interp *interp, + Jim_Obj *strObjPtr, Jim_Obj *firstObjPtr, Jim_Obj *lastObjPtr, Jim_Obj *newStrObj) +{ + int first, last; + const char *str; + int len, rangeLen; + Jim_Obj *objPtr; + + len = Jim_Utf8Length(interp, strObjPtr); + + if (JimStringGetRange(interp, firstObjPtr, lastObjPtr, len, &first, &last, &rangeLen) != JIM_OK) { + return NULL; + } + + if (last < first) { + return strObjPtr; + } + + str = Jim_String(strObjPtr); + + + objPtr = Jim_NewStringObjUtf8(interp, str, first); + + + if (newStrObj) { + Jim_AppendObj(interp, objPtr, newStrObj); + } + + + Jim_AppendString(interp, objPtr, str + utf8_index(str, last + 1), len - last - 1); + + return objPtr; +} + +static void JimStrCopyUpperLower(char *dest, const char *str, int uc) +{ + while (*str) { + int c; + str += utf8_tounicode(str, &c); + dest += utf8_getchars(dest, uc ? utf8_upper(c) : utf8_lower(c)); + } + *dest = 0; +} + +static Jim_Obj *JimStringToLower(Jim_Interp *interp, Jim_Obj *strObjPtr) +{ + char *buf; + int len; + const char *str; + + str = Jim_GetString(strObjPtr, &len); + +#ifdef JIM_UTF8 + len *= 2; +#endif + buf = Jim_Alloc(len + 1); + JimStrCopyUpperLower(buf, str, 0); + return Jim_NewStringObjNoAlloc(interp, buf, -1); +} + +static Jim_Obj *JimStringToUpper(Jim_Interp *interp, Jim_Obj *strObjPtr) +{ + char *buf; + const char *str; + int len; + + str = Jim_GetString(strObjPtr, &len); + +#ifdef JIM_UTF8 + len *= 2; +#endif + buf = Jim_Alloc(len + 1); + JimStrCopyUpperLower(buf, str, 1); + return Jim_NewStringObjNoAlloc(interp, buf, -1); +} + +static Jim_Obj *JimStringToTitle(Jim_Interp *interp, Jim_Obj *strObjPtr) +{ + char *buf, *p; + int len; + int c; + const char *str; + + str = Jim_GetString(strObjPtr, &len); + +#ifdef JIM_UTF8 + len *= 2; +#endif + buf = p = Jim_Alloc(len + 1); + + str += utf8_tounicode(str, &c); + p += utf8_getchars(p, utf8_title(c)); + + JimStrCopyUpperLower(p, str, 0); + + return Jim_NewStringObjNoAlloc(interp, buf, -1); +} + +static const char *utf8_memchr(const char *str, int len, int c) +{ +#ifdef JIM_UTF8 + while (len) { + int sc; + int n = utf8_tounicode(str, &sc); + if (sc == c) { + return str; + } + str += n; + len -= n; + } + return NULL; +#else + return memchr(str, c, len); +#endif +} + +static const char *JimFindTrimLeft(const char *str, int len, const char *trimchars, int trimlen) +{ + while (len) { + int c; + int n = utf8_tounicode(str, &c); + + if (utf8_memchr(trimchars, trimlen, c) == NULL) { + + break; + } + str += n; + len -= n; + } + return str; +} + +static const char *JimFindTrimRight(const char *str, int len, const char *trimchars, int trimlen) +{ + str += len; + + while (len) { + int c; + int n = utf8_prev_len(str, len); + + len -= n; + str -= n; + + n = utf8_tounicode(str, &c); + + if (utf8_memchr(trimchars, trimlen, c) == NULL) { + return str + n; + } + } + + return NULL; +} + +static const char default_trim_chars[] = " \t\n\r"; + +static int default_trim_chars_len = sizeof(default_trim_chars); + +static Jim_Obj *JimStringTrimLeft(Jim_Interp *interp, Jim_Obj *strObjPtr, Jim_Obj *trimcharsObjPtr) +{ + int len; + const char *str = Jim_GetString(strObjPtr, &len); + const char *trimchars = default_trim_chars; + int trimcharslen = default_trim_chars_len; + const char *newstr; + + if (trimcharsObjPtr) { + trimchars = Jim_GetString(trimcharsObjPtr, &trimcharslen); + } + + newstr = JimFindTrimLeft(str, len, trimchars, trimcharslen); + if (newstr == str) { + return strObjPtr; + } + + return Jim_NewStringObj(interp, newstr, len - (newstr - str)); +} + +static Jim_Obj *JimStringTrimRight(Jim_Interp *interp, Jim_Obj *strObjPtr, Jim_Obj *trimcharsObjPtr) +{ + int len; + const char *trimchars = default_trim_chars; + int trimcharslen = default_trim_chars_len; + const char *nontrim; + + if (trimcharsObjPtr) { + trimchars = Jim_GetString(trimcharsObjPtr, &trimcharslen); + } + + SetStringFromAny(interp, strObjPtr); + + len = Jim_Length(strObjPtr); + nontrim = JimFindTrimRight(strObjPtr->bytes, len, trimchars, trimcharslen); + + if (nontrim == NULL) { + + return Jim_NewEmptyStringObj(interp); + } + if (nontrim == strObjPtr->bytes + len) { + + return strObjPtr; + } + + if (Jim_IsShared(strObjPtr)) { + strObjPtr = Jim_NewStringObj(interp, strObjPtr->bytes, (nontrim - strObjPtr->bytes)); + } + else { + + strObjPtr->bytes[nontrim - strObjPtr->bytes] = 0; + strObjPtr->length = (nontrim - strObjPtr->bytes); + } + + return strObjPtr; +} + +static Jim_Obj *JimStringTrim(Jim_Interp *interp, Jim_Obj *strObjPtr, Jim_Obj *trimcharsObjPtr) +{ + + Jim_Obj *objPtr = JimStringTrimLeft(interp, strObjPtr, trimcharsObjPtr); + + + strObjPtr = JimStringTrimRight(interp, objPtr, trimcharsObjPtr); + + + if (objPtr != strObjPtr && objPtr->refCount == 0) { + + Jim_FreeNewObj(interp, objPtr); + } + + return strObjPtr; +} + + +#ifdef HAVE_ISASCII +#define jim_isascii isascii +#else +static int jim_isascii(int c) +{ + return !(c & ~0x7f); +} +#endif + +static int JimStringIs(Jim_Interp *interp, Jim_Obj *strObjPtr, Jim_Obj *strClass, int strict) +{ + static const char * const strclassnames[] = { + "integer", "alpha", "alnum", "ascii", "digit", + "double", "lower", "upper", "space", "xdigit", + "control", "print", "graph", "punct", "boolean", + NULL + }; + enum { + STR_IS_INTEGER, STR_IS_ALPHA, STR_IS_ALNUM, STR_IS_ASCII, STR_IS_DIGIT, + STR_IS_DOUBLE, STR_IS_LOWER, STR_IS_UPPER, STR_IS_SPACE, STR_IS_XDIGIT, + STR_IS_CONTROL, STR_IS_PRINT, STR_IS_GRAPH, STR_IS_PUNCT, STR_IS_BOOLEAN, + }; + int strclass; + int len; + int i; + const char *str; + int (*isclassfunc)(int c) = NULL; + + if (Jim_GetEnum(interp, strClass, strclassnames, &strclass, "class", JIM_ERRMSG | JIM_ENUM_ABBREV) != JIM_OK) { + return JIM_ERR; + } + + str = Jim_GetString(strObjPtr, &len); + if (len == 0) { + Jim_SetResultBool(interp, !strict); + return JIM_OK; + } + + switch (strclass) { + case STR_IS_INTEGER: + { + jim_wide w; + Jim_SetResultBool(interp, JimGetWideNoErr(interp, strObjPtr, &w) == JIM_OK); + return JIM_OK; + } + + case STR_IS_DOUBLE: + { + double d; + Jim_SetResultBool(interp, Jim_GetDouble(interp, strObjPtr, &d) == JIM_OK && errno != ERANGE); + return JIM_OK; + } + + case STR_IS_BOOLEAN: + { + int b; + Jim_SetResultBool(interp, Jim_GetBoolean(interp, strObjPtr, &b) == JIM_OK); + return JIM_OK; + } + + case STR_IS_ALPHA: isclassfunc = isalpha; break; + case STR_IS_ALNUM: isclassfunc = isalnum; break; + case STR_IS_ASCII: isclassfunc = jim_isascii; break; + case STR_IS_DIGIT: isclassfunc = isdigit; break; + case STR_IS_LOWER: isclassfunc = islower; break; + case STR_IS_UPPER: isclassfunc = isupper; break; + case STR_IS_SPACE: isclassfunc = isspace; break; + case STR_IS_XDIGIT: isclassfunc = isxdigit; break; + case STR_IS_CONTROL: isclassfunc = iscntrl; break; + case STR_IS_PRINT: isclassfunc = isprint; break; + case STR_IS_GRAPH: isclassfunc = isgraph; break; + case STR_IS_PUNCT: isclassfunc = ispunct; break; + default: + return JIM_ERR; + } + + for (i = 0; i < len; i++) { + if (!isclassfunc(UCHAR(str[i]))) { + Jim_SetResultBool(interp, 0); + return JIM_OK; + } + } + Jim_SetResultBool(interp, 1); + return JIM_OK; +} + + + +static const Jim_ObjType comparedStringObjType = { + "compared-string", + NULL, + NULL, + NULL, + JIM_TYPE_REFERENCES, +}; + +int Jim_CompareStringImmediate(Jim_Interp *interp, Jim_Obj *objPtr, const char *str) +{ + if (objPtr->typePtr == &comparedStringObjType && objPtr->internalRep.ptr == str) { + return 1; + } + else { + if (strcmp(str, Jim_String(objPtr)) != 0) + return 0; + + if (objPtr->typePtr != &comparedStringObjType) { + Jim_FreeIntRep(interp, objPtr); + objPtr->typePtr = &comparedStringObjType; + } + objPtr->internalRep.ptr = (char *)str; + return 1; + } +} + +static int qsortCompareStringPointers(const void *a, const void *b) +{ + char *const *sa = (char *const *)a; + char *const *sb = (char *const *)b; + + return strcmp(*sa, *sb); +} + + + +static void FreeSourceInternalRep(Jim_Interp *interp, Jim_Obj *objPtr); +static void DupSourceInternalRep(Jim_Interp *interp, Jim_Obj *srcPtr, Jim_Obj *dupPtr); + +static const Jim_ObjType sourceObjType = { + "source", + FreeSourceInternalRep, + DupSourceInternalRep, + NULL, + JIM_TYPE_REFERENCES, +}; + +void FreeSourceInternalRep(Jim_Interp *interp, Jim_Obj *objPtr) +{ + Jim_DecrRefCount(interp, objPtr->internalRep.sourceValue.fileNameObj); +} + +void DupSourceInternalRep(Jim_Interp *interp, Jim_Obj *srcPtr, Jim_Obj *dupPtr) +{ + dupPtr->internalRep.sourceValue = srcPtr->internalRep.sourceValue; + Jim_IncrRefCount(dupPtr->internalRep.sourceValue.fileNameObj); +} + +static void JimSetSourceInfo(Jim_Interp *interp, Jim_Obj *objPtr, + Jim_Obj *fileNameObj, int lineNumber) +{ + JimPanic((Jim_IsShared(objPtr), "JimSetSourceInfo called with shared object")); + JimPanic((objPtr->typePtr != NULL, "JimSetSourceInfo called with typed object")); + Jim_IncrRefCount(fileNameObj); + objPtr->internalRep.sourceValue.fileNameObj = fileNameObj; + objPtr->internalRep.sourceValue.lineNumber = lineNumber; + objPtr->typePtr = &sourceObjType; +} + +static const Jim_ObjType scriptLineObjType = { + "scriptline", + NULL, + NULL, + NULL, + JIM_NONE, +}; + +static Jim_Obj *JimNewScriptLineObj(Jim_Interp *interp, int argc, int line) +{ + Jim_Obj *objPtr; + +#ifdef DEBUG_SHOW_SCRIPT + char buf[100]; + snprintf(buf, sizeof(buf), "line=%d, argc=%d", line, argc); + objPtr = Jim_NewStringObj(interp, buf, -1); +#else + objPtr = Jim_NewEmptyStringObj(interp); +#endif + objPtr->typePtr = &scriptLineObjType; + objPtr->internalRep.scriptLineValue.argc = argc; + objPtr->internalRep.scriptLineValue.line = line; + + return objPtr; +} + +static void FreeScriptInternalRep(Jim_Interp *interp, Jim_Obj *objPtr); +static void DupScriptInternalRep(Jim_Interp *interp, Jim_Obj *srcPtr, Jim_Obj *dupPtr); + +static const Jim_ObjType scriptObjType = { + "script", + FreeScriptInternalRep, + DupScriptInternalRep, + NULL, + JIM_TYPE_REFERENCES, +}; + +typedef struct ScriptToken +{ + Jim_Obj *objPtr; + int type; +} ScriptToken; + +typedef struct ScriptObj +{ + ScriptToken *token; + Jim_Obj *fileNameObj; + int len; + int substFlags; + int inUse; /* Used to share a ScriptObj. Currently + only used by Jim_EvalObj() as protection against + shimmering of the currently evaluated object. */ + int firstline; + int linenr; + int missing; +} ScriptObj; + +static void JimSetScriptFromAny(Jim_Interp *interp, struct Jim_Obj *objPtr); +static int JimParseCheckMissing(Jim_Interp *interp, int ch); +static ScriptObj *JimGetScript(Jim_Interp *interp, Jim_Obj *objPtr); + +void FreeScriptInternalRep(Jim_Interp *interp, Jim_Obj *objPtr) +{ + int i; + struct ScriptObj *script = (void *)objPtr->internalRep.ptr; + + if (--script->inUse != 0) + return; + for (i = 0; i < script->len; i++) { + Jim_DecrRefCount(interp, script->token[i].objPtr); + } + Jim_Free(script->token); + Jim_DecrRefCount(interp, script->fileNameObj); + Jim_Free(script); +} + +void DupScriptInternalRep(Jim_Interp *interp, Jim_Obj *srcPtr, Jim_Obj *dupPtr) +{ + JIM_NOTUSED(interp); + JIM_NOTUSED(srcPtr); + + dupPtr->typePtr = NULL; +} + +typedef struct +{ + const char *token; + int len; + int type; + int line; +} ParseToken; + +typedef struct +{ + + ParseToken *list; + int size; + int count; + ParseToken static_list[20]; +} ParseTokenList; + +static void ScriptTokenListInit(ParseTokenList *tokenlist) +{ + tokenlist->list = tokenlist->static_list; + tokenlist->size = sizeof(tokenlist->static_list) / sizeof(ParseToken); + tokenlist->count = 0; +} + +static void ScriptTokenListFree(ParseTokenList *tokenlist) +{ + if (tokenlist->list != tokenlist->static_list) { + Jim_Free(tokenlist->list); + } +} + +static void ScriptAddToken(ParseTokenList *tokenlist, const char *token, int len, int type, + int line) +{ + ParseToken *t; + + if (tokenlist->count == tokenlist->size) { + + tokenlist->size *= 2; + if (tokenlist->list != tokenlist->static_list) { + tokenlist->list = + Jim_Realloc(tokenlist->list, tokenlist->size * sizeof(*tokenlist->list)); + } + else { + + tokenlist->list = Jim_Alloc(tokenlist->size * sizeof(*tokenlist->list)); + memcpy(tokenlist->list, tokenlist->static_list, + tokenlist->count * sizeof(*tokenlist->list)); + } + } + t = &tokenlist->list[tokenlist->count++]; + t->token = token; + t->len = len; + t->type = type; + t->line = line; +} + +static int JimCountWordTokens(struct ScriptObj *script, ParseToken *t) +{ + int expand = 1; + int count = 0; + + + if (t->type == JIM_TT_STR && !TOKEN_IS_SEP(t[1].type)) { + if ((t->len == 1 && *t->token == '*') || (t->len == 6 && strncmp(t->token, "expand", 6) == 0)) { + + expand = -1; + t++; + } + else { + if (script->missing == ' ') { + + script->missing = '}'; + script->linenr = t[1].line; + } + } + } + + + while (!TOKEN_IS_SEP(t->type)) { + t++; + count++; + } + + return count * expand; +} + +static Jim_Obj *JimMakeScriptObj(Jim_Interp *interp, const ParseToken *t) +{ + Jim_Obj *objPtr; + + if (t->type == JIM_TT_ESC && memchr(t->token, '\\', t->len) != NULL) { + + int len = t->len; + char *str = Jim_Alloc(len + 1); + len = JimEscape(str, t->token, len); + objPtr = Jim_NewStringObjNoAlloc(interp, str, len); + } + else { + objPtr = Jim_NewStringObj(interp, t->token, t->len); + } + return objPtr; +} + +static void ScriptObjAddTokens(Jim_Interp *interp, struct ScriptObj *script, + ParseTokenList *tokenlist) +{ + int i; + struct ScriptToken *token; + + int lineargs = 0; + + ScriptToken *linefirst; + int count; + int linenr; + +#ifdef DEBUG_SHOW_SCRIPT_TOKENS + printf("==== Tokens ====\n"); + for (i = 0; i < tokenlist->count; i++) { + printf("[%2d]@%d %s '%.*s'\n", i, tokenlist->list[i].line, jim_tt_name(tokenlist->list[i].type), + tokenlist->list[i].len, tokenlist->list[i].token); + } +#endif + + + count = tokenlist->count; + for (i = 0; i < tokenlist->count; i++) { + if (tokenlist->list[i].type == JIM_TT_EOL) { + count++; + } + } + linenr = script->firstline = tokenlist->list[0].line; + + token = script->token = Jim_Alloc(sizeof(ScriptToken) * count); + + + linefirst = token++; + + for (i = 0; i < tokenlist->count; ) { + + int wordtokens; + + + while (tokenlist->list[i].type == JIM_TT_SEP) { + i++; + } + + wordtokens = JimCountWordTokens(script, tokenlist->list + i); + + if (wordtokens == 0) { + + if (lineargs) { + linefirst->type = JIM_TT_LINE; + linefirst->objPtr = JimNewScriptLineObj(interp, lineargs, linenr); + Jim_IncrRefCount(linefirst->objPtr); + + + lineargs = 0; + linefirst = token++; + } + i++; + continue; + } + else if (wordtokens != 1) { + + token->type = JIM_TT_WORD; + token->objPtr = Jim_NewIntObj(interp, wordtokens); + Jim_IncrRefCount(token->objPtr); + token++; + if (wordtokens < 0) { + + i++; + wordtokens = -wordtokens - 1; + lineargs--; + } + } + + if (lineargs == 0) { + + linenr = tokenlist->list[i].line; + } + lineargs++; + + + while (wordtokens--) { + const ParseToken *t = &tokenlist->list[i++]; + + token->type = t->type; + token->objPtr = JimMakeScriptObj(interp, t); + Jim_IncrRefCount(token->objPtr); + + JimSetSourceInfo(interp, token->objPtr, script->fileNameObj, t->line); + token++; + } + } + + if (lineargs == 0) { + token--; + } + + script->len = token - script->token; + + JimPanic((script->len >= count, "allocated script array is too short")); + +#ifdef DEBUG_SHOW_SCRIPT + printf("==== Script (%s) ====\n", Jim_String(script->fileNameObj)); + for (i = 0; i < script->len; i++) { + const ScriptToken *t = &script->token[i]; + printf("[%2d] %s %s\n", i, jim_tt_name(t->type), Jim_String(t->objPtr)); + } +#endif + +} + +int Jim_ScriptIsComplete(Jim_Interp *interp, Jim_Obj *scriptObj, char *stateCharPtr) +{ + ScriptObj *script = JimGetScript(interp, scriptObj); + if (stateCharPtr) { + *stateCharPtr = script->missing; + } + return script->missing == ' ' || script->missing == '}'; +} + +static int JimParseCheckMissing(Jim_Interp *interp, int ch) +{ + const char *msg; + + switch (ch) { + case '\\': + case ' ': + return JIM_OK; + + case '[': + msg = "unmatched \"[\""; + break; + case '{': + msg = "missing close-brace"; + break; + case '}': + msg = "extra characters after close-brace"; + break; + case '"': + default: + msg = "missing quote"; + break; + } + + Jim_SetResultString(interp, msg, -1); + return JIM_ERR; +} + +static void SubstObjAddTokens(Jim_Interp *interp, struct ScriptObj *script, + ParseTokenList *tokenlist) +{ + int i; + struct ScriptToken *token; + + token = script->token = Jim_Alloc(sizeof(ScriptToken) * tokenlist->count); + + for (i = 0; i < tokenlist->count; i++) { + const ParseToken *t = &tokenlist->list[i]; + + + token->type = t->type; + token->objPtr = JimMakeScriptObj(interp, t); + Jim_IncrRefCount(token->objPtr); + token++; + } + + script->len = i; +} + +static void JimSetScriptFromAny(Jim_Interp *interp, struct Jim_Obj *objPtr) +{ + int scriptTextLen; + const char *scriptText = Jim_GetString(objPtr, &scriptTextLen); + struct JimParserCtx parser; + struct ScriptObj *script; + ParseTokenList tokenlist; + int line = 1; + + + if (objPtr->typePtr == &sourceObjType) { + line = objPtr->internalRep.sourceValue.lineNumber; + } + + + ScriptTokenListInit(&tokenlist); + + JimParserInit(&parser, scriptText, scriptTextLen, line); + while (!parser.eof) { + JimParseScript(&parser); + ScriptAddToken(&tokenlist, parser.tstart, parser.tend - parser.tstart + 1, parser.tt, + parser.tline); + } + + + ScriptAddToken(&tokenlist, scriptText + scriptTextLen, 0, JIM_TT_EOF, 0); + + + script = Jim_Alloc(sizeof(*script)); + memset(script, 0, sizeof(*script)); + script->inUse = 1; + if (objPtr->typePtr == &sourceObjType) { + script->fileNameObj = objPtr->internalRep.sourceValue.fileNameObj; + } + else { + script->fileNameObj = interp->emptyObj; + } + Jim_IncrRefCount(script->fileNameObj); + script->missing = parser.missing.ch; + script->linenr = parser.missing.line; + + ScriptObjAddTokens(interp, script, &tokenlist); + + + ScriptTokenListFree(&tokenlist); + + + Jim_FreeIntRep(interp, objPtr); + Jim_SetIntRepPtr(objPtr, script); + objPtr->typePtr = &scriptObjType; +} + +static void JimAddErrorToStack(Jim_Interp *interp, ScriptObj *script); + +static ScriptObj *JimGetScript(Jim_Interp *interp, Jim_Obj *objPtr) +{ + if (objPtr == interp->emptyObj) { + + objPtr = interp->nullScriptObj; + } + + if (objPtr->typePtr != &scriptObjType || ((struct ScriptObj *)Jim_GetIntRepPtr(objPtr))->substFlags) { + JimSetScriptFromAny(interp, objPtr); + } + + return (ScriptObj *)Jim_GetIntRepPtr(objPtr); +} + +static int JimScriptValid(Jim_Interp *interp, ScriptObj *script) +{ + if (JimParseCheckMissing(interp, script->missing) == JIM_ERR) { + JimAddErrorToStack(interp, script); + return 0; + } + return 1; +} + + +static void JimIncrCmdRefCount(Jim_Cmd *cmdPtr) +{ + cmdPtr->inUse++; +} + +static void JimDecrCmdRefCount(Jim_Interp *interp, Jim_Cmd *cmdPtr) +{ + if (--cmdPtr->inUse == 0) { + if (cmdPtr->isproc) { + Jim_DecrRefCount(interp, cmdPtr->u.proc.argListObjPtr); + Jim_DecrRefCount(interp, cmdPtr->u.proc.bodyObjPtr); + Jim_DecrRefCount(interp, cmdPtr->u.proc.nsObj); + if (cmdPtr->u.proc.staticVars) { + Jim_FreeHashTable(cmdPtr->u.proc.staticVars); + Jim_Free(cmdPtr->u.proc.staticVars); + } + } + else { + + if (cmdPtr->u.native.delProc) { + cmdPtr->u.native.delProc(interp, cmdPtr->u.native.privData); + } + } + if (cmdPtr->prevCmd) { + + JimDecrCmdRefCount(interp, cmdPtr->prevCmd); + } + Jim_Free(cmdPtr); + } +} + +static void JimVariablesHTValDestructor(void *interp, void *val) +{ + Jim_DecrRefCount(interp, ((Jim_Var *)val)->objPtr); + Jim_Free(val); +} + +static const Jim_HashTableType JimVariablesHashTableType = { + JimStringCopyHTHashFunction, + JimStringCopyHTDup, + NULL, + JimStringCopyHTKeyCompare, + JimStringCopyHTKeyDestructor, + JimVariablesHTValDestructor +}; + +static void JimCommandsHT_ValDestructor(void *interp, void *val) +{ + JimDecrCmdRefCount(interp, val); +} + +static const Jim_HashTableType JimCommandsHashTableType = { + JimStringCopyHTHashFunction, + JimStringCopyHTDup, + NULL, + JimStringCopyHTKeyCompare, + JimStringCopyHTKeyDestructor, + JimCommandsHT_ValDestructor +}; + + + +#ifdef jim_ext_namespace +static Jim_Obj *JimQualifyNameObj(Jim_Interp *interp, Jim_Obj *nsObj) +{ + const char *name = Jim_String(nsObj); + if (name[0] == ':' && name[1] == ':') { + + while (*++name == ':') { + } + nsObj = Jim_NewStringObj(interp, name, -1); + } + else if (Jim_Length(interp->framePtr->nsObj)) { + + nsObj = Jim_DuplicateObj(interp, interp->framePtr->nsObj); + Jim_AppendStrings(interp, nsObj, "::", name, NULL); + } + return nsObj; +} + +Jim_Obj *Jim_MakeGlobalNamespaceName(Jim_Interp *interp, Jim_Obj *nameObjPtr) +{ + Jim_Obj *resultObj; + + const char *name = Jim_String(nameObjPtr); + if (name[0] == ':' && name[1] == ':') { + return nameObjPtr; + } + Jim_IncrRefCount(nameObjPtr); + resultObj = Jim_NewStringObj(interp, "::", -1); + Jim_AppendObj(interp, resultObj, nameObjPtr); + Jim_DecrRefCount(interp, nameObjPtr); + + return resultObj; +} + +static const char *JimQualifyName(Jim_Interp *interp, const char *name, Jim_Obj **objPtrPtr) +{ + Jim_Obj *objPtr = interp->emptyObj; + + if (name[0] == ':' && name[1] == ':') { + + while (*++name == ':') { + } + } + else if (Jim_Length(interp->framePtr->nsObj)) { + + objPtr = Jim_DuplicateObj(interp, interp->framePtr->nsObj); + Jim_AppendStrings(interp, objPtr, "::", name, NULL); + name = Jim_String(objPtr); + } + Jim_IncrRefCount(objPtr); + *objPtrPtr = objPtr; + return name; +} + + #define JimFreeQualifiedName(INTERP, OBJ) Jim_DecrRefCount((INTERP), (OBJ)) + +#else + + #define JimQualifyName(INTERP, NAME, DUMMY) (((NAME)[0] == ':' && (NAME)[1] == ':') ? (NAME) + 2 : (NAME)) + #define JimFreeQualifiedName(INTERP, DUMMY) (void)(DUMMY) + +Jim_Obj *Jim_MakeGlobalNamespaceName(Jim_Interp *interp, Jim_Obj *nameObjPtr) +{ + return nameObjPtr; +} +#endif + +static int JimCreateCommand(Jim_Interp *interp, const char *name, Jim_Cmd *cmd) +{ + Jim_HashEntry *he = Jim_FindHashEntry(&interp->commands, name); + if (he) { + + Jim_InterpIncrProcEpoch(interp); + } + + if (he && interp->local) { + + cmd->prevCmd = Jim_GetHashEntryVal(he); + Jim_SetHashVal(&interp->commands, he, cmd); + } + else { + if (he) { + + Jim_DeleteHashEntry(&interp->commands, name); + } + + Jim_AddHashEntry(&interp->commands, name, cmd); + } + return JIM_OK; +} + + +int Jim_CreateCommand(Jim_Interp *interp, const char *cmdNameStr, + Jim_CmdProc *cmdProc, void *privData, Jim_DelCmdProc *delProc) +{ + Jim_Cmd *cmdPtr = Jim_Alloc(sizeof(*cmdPtr)); + + + memset(cmdPtr, 0, sizeof(*cmdPtr)); + cmdPtr->inUse = 1; + cmdPtr->u.native.delProc = delProc; + cmdPtr->u.native.cmdProc = cmdProc; + cmdPtr->u.native.privData = privData; + + JimCreateCommand(interp, cmdNameStr, cmdPtr); + + return JIM_OK; +} + +static int JimCreateProcedureStatics(Jim_Interp *interp, Jim_Cmd *cmdPtr, Jim_Obj *staticsListObjPtr) +{ + int len, i; + + len = Jim_ListLength(interp, staticsListObjPtr); + if (len == 0) { + return JIM_OK; + } + + cmdPtr->u.proc.staticVars = Jim_Alloc(sizeof(Jim_HashTable)); + Jim_InitHashTable(cmdPtr->u.proc.staticVars, &JimVariablesHashTableType, interp); + for (i = 0; i < len; i++) { + Jim_Obj *objPtr, *initObjPtr, *nameObjPtr; + Jim_Var *varPtr; + int subLen; + + objPtr = Jim_ListGetIndex(interp, staticsListObjPtr, i); + + subLen = Jim_ListLength(interp, objPtr); + if (subLen == 1 || subLen == 2) { + nameObjPtr = Jim_ListGetIndex(interp, objPtr, 0); + if (subLen == 1) { + initObjPtr = Jim_GetVariable(interp, nameObjPtr, JIM_NONE); + if (initObjPtr == NULL) { + Jim_SetResultFormatted(interp, + "variable for initialization of static \"%#s\" not found in the local context", + nameObjPtr); + return JIM_ERR; + } + } + else { + initObjPtr = Jim_ListGetIndex(interp, objPtr, 1); + } + if (JimValidName(interp, "static variable", nameObjPtr) != JIM_OK) { + return JIM_ERR; + } + + varPtr = Jim_Alloc(sizeof(*varPtr)); + varPtr->objPtr = initObjPtr; + Jim_IncrRefCount(initObjPtr); + varPtr->linkFramePtr = NULL; + if (Jim_AddHashEntry(cmdPtr->u.proc.staticVars, + Jim_String(nameObjPtr), varPtr) != JIM_OK) { + Jim_SetResultFormatted(interp, + "static variable name \"%#s\" duplicated in statics list", nameObjPtr); + Jim_DecrRefCount(interp, initObjPtr); + Jim_Free(varPtr); + return JIM_ERR; + } + } + else { + Jim_SetResultFormatted(interp, "too many fields in static specifier \"%#s\"", + objPtr); + return JIM_ERR; + } + } + return JIM_OK; +} + +static void JimUpdateProcNamespace(Jim_Interp *interp, Jim_Cmd *cmdPtr, const char *cmdname) +{ +#ifdef jim_ext_namespace + if (cmdPtr->isproc) { + + const char *pt = strrchr(cmdname, ':'); + if (pt && pt != cmdname && pt[-1] == ':') { + Jim_DecrRefCount(interp, cmdPtr->u.proc.nsObj); + cmdPtr->u.proc.nsObj = Jim_NewStringObj(interp, cmdname, pt - cmdname - 1); + Jim_IncrRefCount(cmdPtr->u.proc.nsObj); + + if (Jim_FindHashEntry(&interp->commands, pt + 1)) { + + Jim_InterpIncrProcEpoch(interp); + } + } + } +#endif +} + +static Jim_Cmd *JimCreateProcedureCmd(Jim_Interp *interp, Jim_Obj *argListObjPtr, + Jim_Obj *staticsListObjPtr, Jim_Obj *bodyObjPtr, Jim_Obj *nsObj) +{ + Jim_Cmd *cmdPtr; + int argListLen; + int i; + + argListLen = Jim_ListLength(interp, argListObjPtr); + + + cmdPtr = Jim_Alloc(sizeof(*cmdPtr) + sizeof(struct Jim_ProcArg) * argListLen); + memset(cmdPtr, 0, sizeof(*cmdPtr)); + cmdPtr->inUse = 1; + cmdPtr->isproc = 1; + cmdPtr->u.proc.argListObjPtr = argListObjPtr; + cmdPtr->u.proc.argListLen = argListLen; + cmdPtr->u.proc.bodyObjPtr = bodyObjPtr; + cmdPtr->u.proc.argsPos = -1; + cmdPtr->u.proc.arglist = (struct Jim_ProcArg *)(cmdPtr + 1); + cmdPtr->u.proc.nsObj = nsObj ? nsObj : interp->emptyObj; + Jim_IncrRefCount(argListObjPtr); + Jim_IncrRefCount(bodyObjPtr); + Jim_IncrRefCount(cmdPtr->u.proc.nsObj); + + + if (staticsListObjPtr && JimCreateProcedureStatics(interp, cmdPtr, staticsListObjPtr) != JIM_OK) { + goto err; + } + + + + for (i = 0; i < argListLen; i++) { + Jim_Obj *argPtr; + Jim_Obj *nameObjPtr; + Jim_Obj *defaultObjPtr; + int len; + + + argPtr = Jim_ListGetIndex(interp, argListObjPtr, i); + len = Jim_ListLength(interp, argPtr); + if (len == 0) { + Jim_SetResultString(interp, "argument with no name", -1); +err: + JimDecrCmdRefCount(interp, cmdPtr); + return NULL; + } + if (len > 2) { + Jim_SetResultFormatted(interp, "too many fields in argument specifier \"%#s\"", argPtr); + goto err; + } + + if (len == 2) { + + nameObjPtr = Jim_ListGetIndex(interp, argPtr, 0); + defaultObjPtr = Jim_ListGetIndex(interp, argPtr, 1); + } + else { + + nameObjPtr = argPtr; + defaultObjPtr = NULL; + } + + + if (Jim_CompareStringImmediate(interp, nameObjPtr, "args")) { + if (cmdPtr->u.proc.argsPos >= 0) { + Jim_SetResultString(interp, "'args' specified more than once", -1); + goto err; + } + cmdPtr->u.proc.argsPos = i; + } + else { + if (len == 2) { + cmdPtr->u.proc.optArity++; + } + else { + cmdPtr->u.proc.reqArity++; + } + } + + cmdPtr->u.proc.arglist[i].nameObjPtr = nameObjPtr; + cmdPtr->u.proc.arglist[i].defaultObjPtr = defaultObjPtr; + } + + return cmdPtr; +} + +int Jim_DeleteCommand(Jim_Interp *interp, const char *name) +{ + int ret = JIM_OK; + Jim_Obj *qualifiedNameObj; + const char *qualname = JimQualifyName(interp, name, &qualifiedNameObj); + + if (Jim_DeleteHashEntry(&interp->commands, qualname) == JIM_ERR) { + Jim_SetResultFormatted(interp, "can't delete \"%s\": command doesn't exist", name); + ret = JIM_ERR; + } + else { + Jim_InterpIncrProcEpoch(interp); + } + + JimFreeQualifiedName(interp, qualifiedNameObj); + + return ret; +} + +int Jim_RenameCommand(Jim_Interp *interp, const char *oldName, const char *newName) +{ + int ret = JIM_ERR; + Jim_HashEntry *he; + Jim_Cmd *cmdPtr; + Jim_Obj *qualifiedOldNameObj; + Jim_Obj *qualifiedNewNameObj; + const char *fqold; + const char *fqnew; + + if (newName[0] == 0) { + return Jim_DeleteCommand(interp, oldName); + } + + fqold = JimQualifyName(interp, oldName, &qualifiedOldNameObj); + fqnew = JimQualifyName(interp, newName, &qualifiedNewNameObj); + + + he = Jim_FindHashEntry(&interp->commands, fqold); + if (he == NULL) { + Jim_SetResultFormatted(interp, "can't rename \"%s\": command doesn't exist", oldName); + } + else if (Jim_FindHashEntry(&interp->commands, fqnew)) { + Jim_SetResultFormatted(interp, "can't rename to \"%s\": command already exists", newName); + } + else { + + cmdPtr = Jim_GetHashEntryVal(he); + JimIncrCmdRefCount(cmdPtr); + JimUpdateProcNamespace(interp, cmdPtr, fqnew); + Jim_AddHashEntry(&interp->commands, fqnew, cmdPtr); + + + Jim_DeleteHashEntry(&interp->commands, fqold); + + + Jim_InterpIncrProcEpoch(interp); + + ret = JIM_OK; + } + + JimFreeQualifiedName(interp, qualifiedOldNameObj); + JimFreeQualifiedName(interp, qualifiedNewNameObj); + + return ret; +} + + +static void FreeCommandInternalRep(Jim_Interp *interp, Jim_Obj *objPtr) +{ + Jim_DecrRefCount(interp, objPtr->internalRep.cmdValue.nsObj); +} + +static void DupCommandInternalRep(Jim_Interp *interp, Jim_Obj *srcPtr, Jim_Obj *dupPtr) +{ + dupPtr->internalRep.cmdValue = srcPtr->internalRep.cmdValue; + dupPtr->typePtr = srcPtr->typePtr; + Jim_IncrRefCount(dupPtr->internalRep.cmdValue.nsObj); +} + +static const Jim_ObjType commandObjType = { + "command", + FreeCommandInternalRep, + DupCommandInternalRep, + NULL, + JIM_TYPE_REFERENCES, +}; + +Jim_Cmd *Jim_GetCommand(Jim_Interp *interp, Jim_Obj *objPtr, int flags) +{ + Jim_Cmd *cmd; + + if (objPtr->typePtr != &commandObjType || + objPtr->internalRep.cmdValue.procEpoch != interp->procEpoch +#ifdef jim_ext_namespace + || !Jim_StringEqObj(objPtr->internalRep.cmdValue.nsObj, interp->framePtr->nsObj) +#endif + ) { + + + + const char *name = Jim_String(objPtr); + Jim_HashEntry *he; + + if (name[0] == ':' && name[1] == ':') { + while (*++name == ':') { + } + } +#ifdef jim_ext_namespace + else if (Jim_Length(interp->framePtr->nsObj)) { + + Jim_Obj *nameObj = Jim_DuplicateObj(interp, interp->framePtr->nsObj); + Jim_AppendStrings(interp, nameObj, "::", name, NULL); + he = Jim_FindHashEntry(&interp->commands, Jim_String(nameObj)); + Jim_FreeNewObj(interp, nameObj); + if (he) { + goto found; + } + } +#endif + + + he = Jim_FindHashEntry(&interp->commands, name); + if (he == NULL) { + if (flags & JIM_ERRMSG) { + Jim_SetResultFormatted(interp, "invalid command name \"%#s\"", objPtr); + } + return NULL; + } +#ifdef jim_ext_namespace +found: +#endif + cmd = Jim_GetHashEntryVal(he); + + + Jim_FreeIntRep(interp, objPtr); + objPtr->typePtr = &commandObjType; + objPtr->internalRep.cmdValue.procEpoch = interp->procEpoch; + objPtr->internalRep.cmdValue.cmdPtr = cmd; + objPtr->internalRep.cmdValue.nsObj = interp->framePtr->nsObj; + Jim_IncrRefCount(interp->framePtr->nsObj); + } + else { + cmd = objPtr->internalRep.cmdValue.cmdPtr; + } + while (cmd->u.proc.upcall) { + cmd = cmd->prevCmd; + } + return cmd; +} + + + +#define JIM_DICT_SUGAR 100 + +static int SetVariableFromAny(Jim_Interp *interp, struct Jim_Obj *objPtr); + +static const Jim_ObjType variableObjType = { + "variable", + NULL, + NULL, + NULL, + JIM_TYPE_REFERENCES, +}; + +static int JimValidName(Jim_Interp *interp, const char *type, Jim_Obj *nameObjPtr) +{ + + if (nameObjPtr->typePtr != &variableObjType) { + int len; + const char *str = Jim_GetString(nameObjPtr, &len); + if (memchr(str, '\0', len)) { + Jim_SetResultFormatted(interp, "%s name contains embedded null", type); + return JIM_ERR; + } + } + return JIM_OK; +} + +static int SetVariableFromAny(Jim_Interp *interp, struct Jim_Obj *objPtr) +{ + const char *varName; + Jim_CallFrame *framePtr; + Jim_HashEntry *he; + int global; + int len; + + + if (objPtr->typePtr == &variableObjType) { + framePtr = objPtr->internalRep.varValue.global ? interp->topFramePtr : interp->framePtr; + if (objPtr->internalRep.varValue.callFrameId == framePtr->id) { + + return JIM_OK; + } + + } + else if (objPtr->typePtr == &dictSubstObjType) { + return JIM_DICT_SUGAR; + } + else if (JimValidName(interp, "variable", objPtr) != JIM_OK) { + return JIM_ERR; + } + + + varName = Jim_GetString(objPtr, &len); + + + if (len && varName[len - 1] == ')' && strchr(varName, '(') != NULL) { + return JIM_DICT_SUGAR; + } + + if (varName[0] == ':' && varName[1] == ':') { + while (*++varName == ':') { + } + global = 1; + framePtr = interp->topFramePtr; + } + else { + global = 0; + framePtr = interp->framePtr; + } + + + he = Jim_FindHashEntry(&framePtr->vars, varName); + if (he == NULL) { + if (!global && framePtr->staticVars) { + + he = Jim_FindHashEntry(framePtr->staticVars, varName); + } + if (he == NULL) { + return JIM_ERR; + } + } + + + Jim_FreeIntRep(interp, objPtr); + objPtr->typePtr = &variableObjType; + objPtr->internalRep.varValue.callFrameId = framePtr->id; + objPtr->internalRep.varValue.varPtr = Jim_GetHashEntryVal(he); + objPtr->internalRep.varValue.global = global; + return JIM_OK; +} + + +static int JimDictSugarSet(Jim_Interp *interp, Jim_Obj *ObjPtr, Jim_Obj *valObjPtr); +static Jim_Obj *JimDictSugarGet(Jim_Interp *interp, Jim_Obj *ObjPtr, int flags); + +static Jim_Var *JimCreateVariable(Jim_Interp *interp, Jim_Obj *nameObjPtr, Jim_Obj *valObjPtr) +{ + const char *name; + Jim_CallFrame *framePtr; + int global; + + + Jim_Var *var = Jim_Alloc(sizeof(*var)); + + var->objPtr = valObjPtr; + Jim_IncrRefCount(valObjPtr); + var->linkFramePtr = NULL; + + name = Jim_String(nameObjPtr); + if (name[0] == ':' && name[1] == ':') { + while (*++name == ':') { + } + framePtr = interp->topFramePtr; + global = 1; + } + else { + framePtr = interp->framePtr; + global = 0; + } + + + Jim_AddHashEntry(&framePtr->vars, name, var); + + + Jim_FreeIntRep(interp, nameObjPtr); + nameObjPtr->typePtr = &variableObjType; + nameObjPtr->internalRep.varValue.callFrameId = framePtr->id; + nameObjPtr->internalRep.varValue.varPtr = var; + nameObjPtr->internalRep.varValue.global = global; + + return var; +} + + +int Jim_SetVariable(Jim_Interp *interp, Jim_Obj *nameObjPtr, Jim_Obj *valObjPtr) +{ + int err; + Jim_Var *var; + + switch (SetVariableFromAny(interp, nameObjPtr)) { + case JIM_DICT_SUGAR: + return JimDictSugarSet(interp, nameObjPtr, valObjPtr); + + case JIM_ERR: + if (JimValidName(interp, "variable", nameObjPtr) != JIM_OK) { + return JIM_ERR; + } + JimCreateVariable(interp, nameObjPtr, valObjPtr); + break; + + case JIM_OK: + var = nameObjPtr->internalRep.varValue.varPtr; + if (var->linkFramePtr == NULL) { + Jim_IncrRefCount(valObjPtr); + Jim_DecrRefCount(interp, var->objPtr); + var->objPtr = valObjPtr; + } + else { + Jim_CallFrame *savedCallFrame; + + savedCallFrame = interp->framePtr; + interp->framePtr = var->linkFramePtr; + err = Jim_SetVariable(interp, var->objPtr, valObjPtr); + interp->framePtr = savedCallFrame; + if (err != JIM_OK) + return err; + } + } + return JIM_OK; +} + +int Jim_SetVariableStr(Jim_Interp *interp, const char *name, Jim_Obj *objPtr) +{ + Jim_Obj *nameObjPtr; + int result; + + nameObjPtr = Jim_NewStringObj(interp, name, -1); + Jim_IncrRefCount(nameObjPtr); + result = Jim_SetVariable(interp, nameObjPtr, objPtr); + Jim_DecrRefCount(interp, nameObjPtr); + return result; +} + +int Jim_SetGlobalVariableStr(Jim_Interp *interp, const char *name, Jim_Obj *objPtr) +{ + Jim_CallFrame *savedFramePtr; + int result; + + savedFramePtr = interp->framePtr; + interp->framePtr = interp->topFramePtr; + result = Jim_SetVariableStr(interp, name, objPtr); + interp->framePtr = savedFramePtr; + return result; +} + +int Jim_SetVariableStrWithStr(Jim_Interp *interp, const char *name, const char *val) +{ + Jim_Obj *valObjPtr; + int result; + + valObjPtr = Jim_NewStringObj(interp, val, -1); + Jim_IncrRefCount(valObjPtr); + result = Jim_SetVariableStr(interp, name, valObjPtr); + Jim_DecrRefCount(interp, valObjPtr); + return result; +} + +int Jim_SetVariableLink(Jim_Interp *interp, Jim_Obj *nameObjPtr, + Jim_Obj *targetNameObjPtr, Jim_CallFrame *targetCallFrame) +{ + const char *varName; + const char *targetName; + Jim_CallFrame *framePtr; + Jim_Var *varPtr; + + + switch (SetVariableFromAny(interp, nameObjPtr)) { + case JIM_DICT_SUGAR: + + Jim_SetResultFormatted(interp, "bad variable name \"%#s\": upvar won't create a scalar variable that looks like an array element", nameObjPtr); + return JIM_ERR; + + case JIM_OK: + varPtr = nameObjPtr->internalRep.varValue.varPtr; + + if (varPtr->linkFramePtr == NULL) { + Jim_SetResultFormatted(interp, "variable \"%#s\" already exists", nameObjPtr); + return JIM_ERR; + } + + + varPtr->linkFramePtr = NULL; + break; + } + + + + varName = Jim_String(nameObjPtr); + + if (varName[0] == ':' && varName[1] == ':') { + while (*++varName == ':') { + } + + framePtr = interp->topFramePtr; + } + else { + framePtr = interp->framePtr; + } + + targetName = Jim_String(targetNameObjPtr); + if (targetName[0] == ':' && targetName[1] == ':') { + while (*++targetName == ':') { + } + targetNameObjPtr = Jim_NewStringObj(interp, targetName, -1); + targetCallFrame = interp->topFramePtr; + } + Jim_IncrRefCount(targetNameObjPtr); + + if (framePtr->level < targetCallFrame->level) { + Jim_SetResultFormatted(interp, + "bad variable name \"%#s\": upvar won't create namespace variable that refers to procedure variable", + nameObjPtr); + Jim_DecrRefCount(interp, targetNameObjPtr); + return JIM_ERR; + } + + + if (framePtr == targetCallFrame) { + Jim_Obj *objPtr = targetNameObjPtr; + + + while (1) { + if (strcmp(Jim_String(objPtr), varName) == 0) { + Jim_SetResultString(interp, "can't upvar from variable to itself", -1); + Jim_DecrRefCount(interp, targetNameObjPtr); + return JIM_ERR; + } + if (SetVariableFromAny(interp, objPtr) != JIM_OK) + break; + varPtr = objPtr->internalRep.varValue.varPtr; + if (varPtr->linkFramePtr != targetCallFrame) + break; + objPtr = varPtr->objPtr; + } + } + + + Jim_SetVariable(interp, nameObjPtr, targetNameObjPtr); + + nameObjPtr->internalRep.varValue.varPtr->linkFramePtr = targetCallFrame; + Jim_DecrRefCount(interp, targetNameObjPtr); + return JIM_OK; +} + +Jim_Obj *Jim_GetVariable(Jim_Interp *interp, Jim_Obj *nameObjPtr, int flags) +{ + switch (SetVariableFromAny(interp, nameObjPtr)) { + case JIM_OK:{ + Jim_Var *varPtr = nameObjPtr->internalRep.varValue.varPtr; + + if (varPtr->linkFramePtr == NULL) { + return varPtr->objPtr; + } + else { + Jim_Obj *objPtr; + + + Jim_CallFrame *savedCallFrame = interp->framePtr; + + interp->framePtr = varPtr->linkFramePtr; + objPtr = Jim_GetVariable(interp, varPtr->objPtr, flags); + interp->framePtr = savedCallFrame; + if (objPtr) { + return objPtr; + } + + } + } + break; + + case JIM_DICT_SUGAR: + + return JimDictSugarGet(interp, nameObjPtr, flags); + } + if (flags & JIM_ERRMSG) { + Jim_SetResultFormatted(interp, "can't read \"%#s\": no such variable", nameObjPtr); + } + return NULL; +} + +Jim_Obj *Jim_GetGlobalVariable(Jim_Interp *interp, Jim_Obj *nameObjPtr, int flags) +{ + Jim_CallFrame *savedFramePtr; + Jim_Obj *objPtr; + + savedFramePtr = interp->framePtr; + interp->framePtr = interp->topFramePtr; + objPtr = Jim_GetVariable(interp, nameObjPtr, flags); + interp->framePtr = savedFramePtr; + + return objPtr; +} + +Jim_Obj *Jim_GetVariableStr(Jim_Interp *interp, const char *name, int flags) +{ + Jim_Obj *nameObjPtr, *varObjPtr; + + nameObjPtr = Jim_NewStringObj(interp, name, -1); + Jim_IncrRefCount(nameObjPtr); + varObjPtr = Jim_GetVariable(interp, nameObjPtr, flags); + Jim_DecrRefCount(interp, nameObjPtr); + return varObjPtr; +} + +Jim_Obj *Jim_GetGlobalVariableStr(Jim_Interp *interp, const char *name, int flags) +{ + Jim_CallFrame *savedFramePtr; + Jim_Obj *objPtr; + + savedFramePtr = interp->framePtr; + interp->framePtr = interp->topFramePtr; + objPtr = Jim_GetVariableStr(interp, name, flags); + interp->framePtr = savedFramePtr; + + return objPtr; +} + +int Jim_UnsetVariable(Jim_Interp *interp, Jim_Obj *nameObjPtr, int flags) +{ + Jim_Var *varPtr; + int retval; + Jim_CallFrame *framePtr; + + retval = SetVariableFromAny(interp, nameObjPtr); + if (retval == JIM_DICT_SUGAR) { + + return JimDictSugarSet(interp, nameObjPtr, NULL); + } + else if (retval == JIM_OK) { + varPtr = nameObjPtr->internalRep.varValue.varPtr; + + + if (varPtr->linkFramePtr) { + framePtr = interp->framePtr; + interp->framePtr = varPtr->linkFramePtr; + retval = Jim_UnsetVariable(interp, varPtr->objPtr, JIM_NONE); + interp->framePtr = framePtr; + } + else { + const char *name = Jim_String(nameObjPtr); + if (nameObjPtr->internalRep.varValue.global) { + name += 2; + framePtr = interp->topFramePtr; + } + else { + framePtr = interp->framePtr; + } + + retval = Jim_DeleteHashEntry(&framePtr->vars, name); + if (retval == JIM_OK) { + + framePtr->id = interp->callFrameEpoch++; + } + } + } + if (retval != JIM_OK && (flags & JIM_ERRMSG)) { + Jim_SetResultFormatted(interp, "can't unset \"%#s\": no such variable", nameObjPtr); + } + return retval; +} + + + +static void JimDictSugarParseVarKey(Jim_Interp *interp, Jim_Obj *objPtr, + Jim_Obj **varPtrPtr, Jim_Obj **keyPtrPtr) +{ + const char *str, *p; + int len, keyLen; + Jim_Obj *varObjPtr, *keyObjPtr; + + str = Jim_GetString(objPtr, &len); + + p = strchr(str, '('); + JimPanic((p == NULL, "JimDictSugarParseVarKey() called for non-dict-sugar (%s)", str)); + + varObjPtr = Jim_NewStringObj(interp, str, p - str); + + p++; + keyLen = (str + len) - p; + if (str[len - 1] == ')') { + keyLen--; + } + + + keyObjPtr = Jim_NewStringObj(interp, p, keyLen); + + Jim_IncrRefCount(varObjPtr); + Jim_IncrRefCount(keyObjPtr); + *varPtrPtr = varObjPtr; + *keyPtrPtr = keyObjPtr; +} + +static int JimDictSugarSet(Jim_Interp *interp, Jim_Obj *objPtr, Jim_Obj *valObjPtr) +{ + int err; + + SetDictSubstFromAny(interp, objPtr); + + err = Jim_SetDictKeysVector(interp, objPtr->internalRep.dictSubstValue.varNameObjPtr, + &objPtr->internalRep.dictSubstValue.indexObjPtr, 1, valObjPtr, JIM_MUSTEXIST); + + if (err == JIM_OK) { + + Jim_SetEmptyResult(interp); + } + else { + if (!valObjPtr) { + + if (Jim_GetVariable(interp, objPtr->internalRep.dictSubstValue.varNameObjPtr, JIM_NONE)) { + Jim_SetResultFormatted(interp, "can't unset \"%#s\": no such element in array", + objPtr); + return err; + } + } + + Jim_SetResultFormatted(interp, "can't %s \"%#s\": variable isn't array", + (valObjPtr ? "set" : "unset"), objPtr); + } + return err; +} + +static Jim_Obj *JimDictExpandArrayVariable(Jim_Interp *interp, Jim_Obj *varObjPtr, + Jim_Obj *keyObjPtr, int flags) +{ + Jim_Obj *dictObjPtr; + Jim_Obj *resObjPtr = NULL; + int ret; + + dictObjPtr = Jim_GetVariable(interp, varObjPtr, JIM_ERRMSG); + if (!dictObjPtr) { + return NULL; + } + + ret = Jim_DictKey(interp, dictObjPtr, keyObjPtr, &resObjPtr, JIM_NONE); + if (ret != JIM_OK) { + Jim_SetResultFormatted(interp, + "can't read \"%#s(%#s)\": %s array", varObjPtr, keyObjPtr, + ret < 0 ? "variable isn't" : "no such element in"); + } + else if ((flags & JIM_UNSHARED) && Jim_IsShared(dictObjPtr)) { + + Jim_SetVariable(interp, varObjPtr, Jim_DuplicateObj(interp, dictObjPtr)); + } + + return resObjPtr; +} + + +static Jim_Obj *JimDictSugarGet(Jim_Interp *interp, Jim_Obj *objPtr, int flags) +{ + SetDictSubstFromAny(interp, objPtr); + + return JimDictExpandArrayVariable(interp, + objPtr->internalRep.dictSubstValue.varNameObjPtr, + objPtr->internalRep.dictSubstValue.indexObjPtr, flags); +} + + + +void FreeDictSubstInternalRep(Jim_Interp *interp, Jim_Obj *objPtr) +{ + Jim_DecrRefCount(interp, objPtr->internalRep.dictSubstValue.varNameObjPtr); + Jim_DecrRefCount(interp, objPtr->internalRep.dictSubstValue.indexObjPtr); +} + +static void DupDictSubstInternalRep(Jim_Interp *interp, Jim_Obj *srcPtr, Jim_Obj *dupPtr) +{ + + dupPtr->internalRep = srcPtr->internalRep; + + Jim_IncrRefCount(dupPtr->internalRep.dictSubstValue.varNameObjPtr); + Jim_IncrRefCount(dupPtr->internalRep.dictSubstValue.indexObjPtr); +} + + +static void SetDictSubstFromAny(Jim_Interp *interp, Jim_Obj *objPtr) +{ + if (objPtr->typePtr != &dictSubstObjType) { + Jim_Obj *varObjPtr, *keyObjPtr; + + if (objPtr->typePtr == &interpolatedObjType) { + + + varObjPtr = objPtr->internalRep.dictSubstValue.varNameObjPtr; + keyObjPtr = objPtr->internalRep.dictSubstValue.indexObjPtr; + + Jim_IncrRefCount(varObjPtr); + Jim_IncrRefCount(keyObjPtr); + } + else { + JimDictSugarParseVarKey(interp, objPtr, &varObjPtr, &keyObjPtr); + } + + Jim_FreeIntRep(interp, objPtr); + objPtr->typePtr = &dictSubstObjType; + objPtr->internalRep.dictSubstValue.varNameObjPtr = varObjPtr; + objPtr->internalRep.dictSubstValue.indexObjPtr = keyObjPtr; + } +} + +static Jim_Obj *JimExpandDictSugar(Jim_Interp *interp, Jim_Obj *objPtr) +{ + Jim_Obj *resObjPtr = NULL; + Jim_Obj *substKeyObjPtr = NULL; + + SetDictSubstFromAny(interp, objPtr); + + if (Jim_SubstObj(interp, objPtr->internalRep.dictSubstValue.indexObjPtr, + &substKeyObjPtr, JIM_NONE) + != JIM_OK) { + return NULL; + } + Jim_IncrRefCount(substKeyObjPtr); + resObjPtr = + JimDictExpandArrayVariable(interp, objPtr->internalRep.dictSubstValue.varNameObjPtr, + substKeyObjPtr, 0); + Jim_DecrRefCount(interp, substKeyObjPtr); + + return resObjPtr; +} + +static Jim_Obj *JimExpandExprSugar(Jim_Interp *interp, Jim_Obj *objPtr) +{ + if (Jim_EvalExpression(interp, objPtr) == JIM_OK) { + return Jim_GetResult(interp); + } + return NULL; +} + + +static Jim_CallFrame *JimCreateCallFrame(Jim_Interp *interp, Jim_CallFrame *parent, Jim_Obj *nsObj) +{ + Jim_CallFrame *cf; + + if (interp->freeFramesList) { + cf = interp->freeFramesList; + interp->freeFramesList = cf->next; + + cf->argv = NULL; + cf->argc = 0; + cf->procArgsObjPtr = NULL; + cf->procBodyObjPtr = NULL; + cf->next = NULL; + cf->staticVars = NULL; + cf->localCommands = NULL; + cf->tailcallObj = NULL; + cf->tailcallCmd = NULL; + } + else { + cf = Jim_Alloc(sizeof(*cf)); + memset(cf, 0, sizeof(*cf)); + + Jim_InitHashTable(&cf->vars, &JimVariablesHashTableType, interp); + } + + cf->id = interp->callFrameEpoch++; + cf->parent = parent; + cf->level = parent ? parent->level + 1 : 0; + cf->nsObj = nsObj; + Jim_IncrRefCount(nsObj); + + return cf; +} + +static int JimDeleteLocalProcs(Jim_Interp *interp, Jim_Stack *localCommands) +{ + + if (localCommands) { + Jim_Obj *cmdNameObj; + + while ((cmdNameObj = Jim_StackPop(localCommands)) != NULL) { + Jim_HashEntry *he; + Jim_Obj *fqObjName; + Jim_HashTable *ht = &interp->commands; + + const char *fqname = JimQualifyName(interp, Jim_String(cmdNameObj), &fqObjName); + + he = Jim_FindHashEntry(ht, fqname); + + if (he) { + Jim_Cmd *cmd = Jim_GetHashEntryVal(he); + if (cmd->prevCmd) { + Jim_Cmd *prevCmd = cmd->prevCmd; + cmd->prevCmd = NULL; + + + JimDecrCmdRefCount(interp, cmd); + + + Jim_SetHashVal(ht, he, prevCmd); + } + else { + Jim_DeleteHashEntry(ht, fqname); + } + Jim_InterpIncrProcEpoch(interp); + } + Jim_DecrRefCount(interp, cmdNameObj); + JimFreeQualifiedName(interp, fqObjName); + } + Jim_FreeStack(localCommands); + Jim_Free(localCommands); + } + return JIM_OK; +} + +static int JimInvokeDefer(Jim_Interp *interp, int retcode) +{ + Jim_Obj *objPtr; + + + if (Jim_FindHashEntry(&interp->framePtr->vars, "jim::defer") == NULL) { + return retcode; + } + + objPtr = Jim_GetVariableStr(interp, "jim::defer", JIM_NONE); + + if (objPtr) { + int ret = JIM_OK; + int i; + int listLen = Jim_ListLength(interp, objPtr); + Jim_Obj *resultObjPtr; + + Jim_IncrRefCount(objPtr); + + resultObjPtr = Jim_GetResult(interp); + Jim_IncrRefCount(resultObjPtr); + Jim_SetEmptyResult(interp); + + + for (i = listLen; i > 0; i--) { + + Jim_Obj *scriptObjPtr = Jim_ListGetIndex(interp, objPtr, i - 1); + ret = Jim_EvalObj(interp, scriptObjPtr); + if (ret != JIM_OK) { + break; + } + } + + if (ret == JIM_OK || retcode == JIM_ERR) { + + Jim_SetResult(interp, resultObjPtr); + } + else { + retcode = ret; + } + + Jim_DecrRefCount(interp, resultObjPtr); + Jim_DecrRefCount(interp, objPtr); + } + return retcode; +} + +#define JIM_FCF_FULL 0 +#define JIM_FCF_REUSE 1 +static void JimFreeCallFrame(Jim_Interp *interp, Jim_CallFrame *cf, int action) + { + JimDeleteLocalProcs(interp, cf->localCommands); + + if (cf->procArgsObjPtr) + Jim_DecrRefCount(interp, cf->procArgsObjPtr); + if (cf->procBodyObjPtr) + Jim_DecrRefCount(interp, cf->procBodyObjPtr); + Jim_DecrRefCount(interp, cf->nsObj); + if (action == JIM_FCF_FULL || cf->vars.size != JIM_HT_INITIAL_SIZE) + Jim_FreeHashTable(&cf->vars); + else { + int i; + Jim_HashEntry **table = cf->vars.table, *he; + + for (i = 0; i < JIM_HT_INITIAL_SIZE; i++) { + he = table[i]; + while (he != NULL) { + Jim_HashEntry *nextEntry = he->next; + Jim_Var *varPtr = Jim_GetHashEntryVal(he); + + Jim_DecrRefCount(interp, varPtr->objPtr); + Jim_Free(Jim_GetHashEntryKey(he)); + Jim_Free(varPtr); + Jim_Free(he); + table[i] = NULL; + he = nextEntry; + } + } + cf->vars.used = 0; + } + cf->next = interp->freeFramesList; + interp->freeFramesList = cf; +} + + + +int Jim_IsBigEndian(void) +{ + union { + unsigned short s; + unsigned char c[2]; + } uval = {0x0102}; + + return uval.c[0] == 1; +} + + +Jim_Interp *Jim_CreateInterp(void) +{ + Jim_Interp *i = Jim_Alloc(sizeof(*i)); + + memset(i, 0, sizeof(*i)); + + i->maxCallFrameDepth = JIM_MAX_CALLFRAME_DEPTH; + i->maxEvalDepth = JIM_MAX_EVAL_DEPTH; + i->lastCollectTime = time(NULL); + + Jim_InitHashTable(&i->commands, &JimCommandsHashTableType, i); +#ifdef JIM_REFERENCES + Jim_InitHashTable(&i->references, &JimReferencesHashTableType, i); +#endif + Jim_InitHashTable(&i->assocData, &JimAssocDataHashTableType, i); + Jim_InitHashTable(&i->packages, &JimPackageHashTableType, NULL); + i->emptyObj = Jim_NewEmptyStringObj(i); + i->trueObj = Jim_NewIntObj(i, 1); + i->falseObj = Jim_NewIntObj(i, 0); + i->framePtr = i->topFramePtr = JimCreateCallFrame(i, NULL, i->emptyObj); + i->errorFileNameObj = i->emptyObj; + i->result = i->emptyObj; + i->stackTrace = Jim_NewListObj(i, NULL, 0); + i->unknown = Jim_NewStringObj(i, "unknown", -1); + i->errorProc = i->emptyObj; + i->currentScriptObj = Jim_NewEmptyStringObj(i); + i->nullScriptObj = Jim_NewEmptyStringObj(i); + Jim_IncrRefCount(i->emptyObj); + Jim_IncrRefCount(i->errorFileNameObj); + Jim_IncrRefCount(i->result); + Jim_IncrRefCount(i->stackTrace); + Jim_IncrRefCount(i->unknown); + Jim_IncrRefCount(i->currentScriptObj); + Jim_IncrRefCount(i->nullScriptObj); + Jim_IncrRefCount(i->errorProc); + Jim_IncrRefCount(i->trueObj); + Jim_IncrRefCount(i->falseObj); + + + Jim_SetVariableStrWithStr(i, JIM_LIBPATH, TCL_LIBRARY); + Jim_SetVariableStrWithStr(i, JIM_INTERACTIVE, "0"); + + Jim_SetVariableStrWithStr(i, "tcl_platform(engine)", "Jim"); + Jim_SetVariableStrWithStr(i, "tcl_platform(os)", TCL_PLATFORM_OS); + Jim_SetVariableStrWithStr(i, "tcl_platform(platform)", TCL_PLATFORM_PLATFORM); + Jim_SetVariableStrWithStr(i, "tcl_platform(pathSeparator)", TCL_PLATFORM_PATH_SEPARATOR); + Jim_SetVariableStrWithStr(i, "tcl_platform(byteOrder)", Jim_IsBigEndian() ? "bigEndian" : "littleEndian"); + Jim_SetVariableStrWithStr(i, "tcl_platform(threaded)", "0"); + Jim_SetVariableStr(i, "tcl_platform(pointerSize)", Jim_NewIntObj(i, sizeof(void *))); + Jim_SetVariableStr(i, "tcl_platform(wordSize)", Jim_NewIntObj(i, sizeof(jim_wide))); + + return i; +} + +void Jim_FreeInterp(Jim_Interp *i) +{ + Jim_CallFrame *cf, *cfx; + + Jim_Obj *objPtr, *nextObjPtr; + + + for (cf = i->framePtr; cf; cf = cfx) { + + JimInvokeDefer(i, JIM_OK); + cfx = cf->parent; + JimFreeCallFrame(i, cf, JIM_FCF_FULL); + } + + Jim_DecrRefCount(i, i->emptyObj); + Jim_DecrRefCount(i, i->trueObj); + Jim_DecrRefCount(i, i->falseObj); + Jim_DecrRefCount(i, i->result); + Jim_DecrRefCount(i, i->stackTrace); + Jim_DecrRefCount(i, i->errorProc); + Jim_DecrRefCount(i, i->unknown); + Jim_DecrRefCount(i, i->errorFileNameObj); + Jim_DecrRefCount(i, i->currentScriptObj); + Jim_DecrRefCount(i, i->nullScriptObj); + Jim_FreeHashTable(&i->commands); +#ifdef JIM_REFERENCES + Jim_FreeHashTable(&i->references); +#endif + Jim_FreeHashTable(&i->packages); + Jim_Free(i->prngState); + Jim_FreeHashTable(&i->assocData); + +#ifdef JIM_MAINTAINER + if (i->liveList != NULL) { + objPtr = i->liveList; + + printf("\n-------------------------------------\n"); + printf("Objects still in the free list:\n"); + while (objPtr) { + const char *type = objPtr->typePtr ? objPtr->typePtr->name : "string"; + Jim_String(objPtr); + + if (objPtr->bytes && strlen(objPtr->bytes) > 20) { + printf("%p (%d) %-10s: '%.20s...'\n", + (void *)objPtr, objPtr->refCount, type, objPtr->bytes); + } + else { + printf("%p (%d) %-10s: '%s'\n", + (void *)objPtr, objPtr->refCount, type, objPtr->bytes ? objPtr->bytes : "(null)"); + } + if (objPtr->typePtr == &sourceObjType) { + printf("FILE %s LINE %d\n", + Jim_String(objPtr->internalRep.sourceValue.fileNameObj), + objPtr->internalRep.sourceValue.lineNumber); + } + objPtr = objPtr->nextObjPtr; + } + printf("-------------------------------------\n\n"); + JimPanic((1, "Live list non empty freeing the interpreter! Leak?")); + } +#endif + + + objPtr = i->freeList; + while (objPtr) { + nextObjPtr = objPtr->nextObjPtr; + Jim_Free(objPtr); + objPtr = nextObjPtr; + } + + + for (cf = i->freeFramesList; cf; cf = cfx) { + cfx = cf->next; + if (cf->vars.table) + Jim_FreeHashTable(&cf->vars); + Jim_Free(cf); + } + + + Jim_Free(i); +} + +Jim_CallFrame *Jim_GetCallFrameByLevel(Jim_Interp *interp, Jim_Obj *levelObjPtr) +{ + long level; + const char *str; + Jim_CallFrame *framePtr; + + if (levelObjPtr) { + str = Jim_String(levelObjPtr); + if (str[0] == '#') { + char *endptr; + + level = jim_strtol(str + 1, &endptr); + if (str[1] == '\0' || endptr[0] != '\0') { + level = -1; + } + } + else { + if (Jim_GetLong(interp, levelObjPtr, &level) != JIM_OK || level < 0) { + level = -1; + } + else { + + level = interp->framePtr->level - level; + } + } + } + else { + str = "1"; + level = interp->framePtr->level - 1; + } + + if (level == 0) { + return interp->topFramePtr; + } + if (level > 0) { + + for (framePtr = interp->framePtr; framePtr; framePtr = framePtr->parent) { + if (framePtr->level == level) { + return framePtr; + } + } + } + + Jim_SetResultFormatted(interp, "bad level \"%s\"", str); + return NULL; +} + +static Jim_CallFrame *JimGetCallFrameByInteger(Jim_Interp *interp, Jim_Obj *levelObjPtr) +{ + long level; + Jim_CallFrame *framePtr; + + if (Jim_GetLong(interp, levelObjPtr, &level) == JIM_OK) { + if (level <= 0) { + + level = interp->framePtr->level + level; + } + + if (level == 0) { + return interp->topFramePtr; + } + + + for (framePtr = interp->framePtr; framePtr; framePtr = framePtr->parent) { + if (framePtr->level == level) { + return framePtr; + } + } + } + + Jim_SetResultFormatted(interp, "bad level \"%#s\"", levelObjPtr); + return NULL; +} + +static void JimResetStackTrace(Jim_Interp *interp) +{ + Jim_DecrRefCount(interp, interp->stackTrace); + interp->stackTrace = Jim_NewListObj(interp, NULL, 0); + Jim_IncrRefCount(interp->stackTrace); +} + +static void JimSetStackTrace(Jim_Interp *interp, Jim_Obj *stackTraceObj) +{ + int len; + + + Jim_IncrRefCount(stackTraceObj); + Jim_DecrRefCount(interp, interp->stackTrace); + interp->stackTrace = stackTraceObj; + interp->errorFlag = 1; + + len = Jim_ListLength(interp, interp->stackTrace); + if (len >= 3) { + if (Jim_Length(Jim_ListGetIndex(interp, interp->stackTrace, len - 2)) == 0) { + interp->addStackTrace = 1; + } + } +} + +static void JimAppendStackTrace(Jim_Interp *interp, const char *procname, + Jim_Obj *fileNameObj, int linenr) +{ + if (strcmp(procname, "unknown") == 0) { + procname = ""; + } + if (!*procname && !Jim_Length(fileNameObj)) { + + return; + } + + if (Jim_IsShared(interp->stackTrace)) { + Jim_DecrRefCount(interp, interp->stackTrace); + interp->stackTrace = Jim_DuplicateObj(interp, interp->stackTrace); + Jim_IncrRefCount(interp->stackTrace); + } + + + if (!*procname && Jim_Length(fileNameObj)) { + + int len = Jim_ListLength(interp, interp->stackTrace); + + if (len >= 3) { + Jim_Obj *objPtr = Jim_ListGetIndex(interp, interp->stackTrace, len - 3); + if (Jim_Length(objPtr)) { + + objPtr = Jim_ListGetIndex(interp, interp->stackTrace, len - 2); + if (Jim_Length(objPtr) == 0) { + + ListSetIndex(interp, interp->stackTrace, len - 2, fileNameObj, 0); + ListSetIndex(interp, interp->stackTrace, len - 1, Jim_NewIntObj(interp, linenr), 0); + return; + } + } + } + } + + Jim_ListAppendElement(interp, interp->stackTrace, Jim_NewStringObj(interp, procname, -1)); + Jim_ListAppendElement(interp, interp->stackTrace, fileNameObj); + Jim_ListAppendElement(interp, interp->stackTrace, Jim_NewIntObj(interp, linenr)); +} + +int Jim_SetAssocData(Jim_Interp *interp, const char *key, Jim_InterpDeleteProc * delProc, + void *data) +{ + AssocDataValue *assocEntryPtr = (AssocDataValue *) Jim_Alloc(sizeof(AssocDataValue)); + + assocEntryPtr->delProc = delProc; + assocEntryPtr->data = data; + return Jim_AddHashEntry(&interp->assocData, key, assocEntryPtr); +} + +void *Jim_GetAssocData(Jim_Interp *interp, const char *key) +{ + Jim_HashEntry *entryPtr = Jim_FindHashEntry(&interp->assocData, key); + + if (entryPtr != NULL) { + AssocDataValue *assocEntryPtr = Jim_GetHashEntryVal(entryPtr); + return assocEntryPtr->data; + } + return NULL; +} + +int Jim_DeleteAssocData(Jim_Interp *interp, const char *key) +{ + return Jim_DeleteHashEntry(&interp->assocData, key); +} + +int Jim_GetExitCode(Jim_Interp *interp) +{ + return interp->exitCode; +} + +static void UpdateStringOfInt(struct Jim_Obj *objPtr); +static int SetIntFromAny(Jim_Interp *interp, Jim_Obj *objPtr, int flags); + +static const Jim_ObjType intObjType = { + "int", + NULL, + NULL, + UpdateStringOfInt, + JIM_TYPE_NONE, +}; + +static const Jim_ObjType coercedDoubleObjType = { + "coerced-double", + NULL, + NULL, + UpdateStringOfInt, + JIM_TYPE_NONE, +}; + + +static void UpdateStringOfInt(struct Jim_Obj *objPtr) +{ + char buf[JIM_INTEGER_SPACE + 1]; + jim_wide wideValue = JimWideValue(objPtr); + int pos = 0; + + if (wideValue == 0) { + buf[pos++] = '0'; + } + else { + char tmp[JIM_INTEGER_SPACE]; + int num = 0; + int i; + + if (wideValue < 0) { + buf[pos++] = '-'; + i = wideValue % 10; + tmp[num++] = (i > 0) ? (10 - i) : -i; + wideValue /= -10; + } + + while (wideValue) { + tmp[num++] = wideValue % 10; + wideValue /= 10; + } + + for (i = 0; i < num; i++) { + buf[pos++] = '0' + tmp[num - i - 1]; + } + } + buf[pos] = 0; + + JimSetStringBytes(objPtr, buf); +} + +static int SetIntFromAny(Jim_Interp *interp, Jim_Obj *objPtr, int flags) +{ + jim_wide wideValue; + const char *str; + + if (objPtr->typePtr == &coercedDoubleObjType) { + + objPtr->typePtr = &intObjType; + return JIM_OK; + } + + + str = Jim_String(objPtr); + + if (Jim_StringToWide(str, &wideValue, 0) != JIM_OK) { + if (flags & JIM_ERRMSG) { + Jim_SetResultFormatted(interp, "expected integer but got \"%#s\"", objPtr); + } + return JIM_ERR; + } + if ((wideValue == JIM_WIDE_MIN || wideValue == JIM_WIDE_MAX) && errno == ERANGE) { + Jim_SetResultString(interp, "Integer value too big to be represented", -1); + return JIM_ERR; + } + + Jim_FreeIntRep(interp, objPtr); + objPtr->typePtr = &intObjType; + objPtr->internalRep.wideValue = wideValue; + return JIM_OK; +} + +#ifdef JIM_OPTIMIZATION +static int JimIsWide(Jim_Obj *objPtr) +{ + return objPtr->typePtr == &intObjType; +} +#endif + +int Jim_GetWide(Jim_Interp *interp, Jim_Obj *objPtr, jim_wide * widePtr) +{ + if (objPtr->typePtr != &intObjType && SetIntFromAny(interp, objPtr, JIM_ERRMSG) == JIM_ERR) + return JIM_ERR; + *widePtr = JimWideValue(objPtr); + return JIM_OK; +} + + +static int JimGetWideNoErr(Jim_Interp *interp, Jim_Obj *objPtr, jim_wide * widePtr) +{ + if (objPtr->typePtr != &intObjType && SetIntFromAny(interp, objPtr, JIM_NONE) == JIM_ERR) + return JIM_ERR; + *widePtr = JimWideValue(objPtr); + return JIM_OK; +} + +int Jim_GetLong(Jim_Interp *interp, Jim_Obj *objPtr, long *longPtr) +{ + jim_wide wideValue; + int retval; + + retval = Jim_GetWide(interp, objPtr, &wideValue); + if (retval == JIM_OK) { + *longPtr = (long)wideValue; + return JIM_OK; + } + return JIM_ERR; +} + +Jim_Obj *Jim_NewIntObj(Jim_Interp *interp, jim_wide wideValue) +{ + Jim_Obj *objPtr; + + objPtr = Jim_NewObj(interp); + objPtr->typePtr = &intObjType; + objPtr->bytes = NULL; + objPtr->internalRep.wideValue = wideValue; + return objPtr; +} + +#define JIM_DOUBLE_SPACE 30 + +static void UpdateStringOfDouble(struct Jim_Obj *objPtr); +static int SetDoubleFromAny(Jim_Interp *interp, Jim_Obj *objPtr); + +static const Jim_ObjType doubleObjType = { + "double", + NULL, + NULL, + UpdateStringOfDouble, + JIM_TYPE_NONE, +}; + +#ifndef HAVE_ISNAN +#undef isnan +#define isnan(X) ((X) != (X)) +#endif +#ifndef HAVE_ISINF +#undef isinf +#define isinf(X) (1.0 / (X) == 0.0) +#endif + +static void UpdateStringOfDouble(struct Jim_Obj *objPtr) +{ + double value = objPtr->internalRep.doubleValue; + + if (isnan(value)) { + JimSetStringBytes(objPtr, "NaN"); + return; + } + if (isinf(value)) { + if (value < 0) { + JimSetStringBytes(objPtr, "-Inf"); + } + else { + JimSetStringBytes(objPtr, "Inf"); + } + return; + } + { + char buf[JIM_DOUBLE_SPACE + 1]; + int i; + int len = sprintf(buf, "%.12g", value); + + + for (i = 0; i < len; i++) { + if (buf[i] == '.' || buf[i] == 'e') { +#if defined(JIM_SPRINTF_DOUBLE_NEEDS_FIX) + char *e = strchr(buf, 'e'); + if (e && (e[1] == '-' || e[1] == '+') && e[2] == '0') { + + e += 2; + memmove(e, e + 1, len - (e - buf)); + } +#endif + break; + } + } + if (buf[i] == '\0') { + buf[i++] = '.'; + buf[i++] = '0'; + buf[i] = '\0'; + } + JimSetStringBytes(objPtr, buf); + } +} + +static int SetDoubleFromAny(Jim_Interp *interp, Jim_Obj *objPtr) +{ + double doubleValue; + jim_wide wideValue; + const char *str; + +#ifdef HAVE_LONG_LONG + +#define MIN_INT_IN_DOUBLE -(1LL << 53) +#define MAX_INT_IN_DOUBLE -(MIN_INT_IN_DOUBLE + 1) + + if (objPtr->typePtr == &intObjType + && JimWideValue(objPtr) >= MIN_INT_IN_DOUBLE + && JimWideValue(objPtr) <= MAX_INT_IN_DOUBLE) { + + + objPtr->typePtr = &coercedDoubleObjType; + return JIM_OK; + } +#endif + str = Jim_String(objPtr); + + if (Jim_StringToWide(str, &wideValue, 10) == JIM_OK) { + + Jim_FreeIntRep(interp, objPtr); + objPtr->typePtr = &coercedDoubleObjType; + objPtr->internalRep.wideValue = wideValue; + return JIM_OK; + } + else { + + if (Jim_StringToDouble(str, &doubleValue) != JIM_OK) { + Jim_SetResultFormatted(interp, "expected floating-point number but got \"%#s\"", objPtr); + return JIM_ERR; + } + + Jim_FreeIntRep(interp, objPtr); + } + objPtr->typePtr = &doubleObjType; + objPtr->internalRep.doubleValue = doubleValue; + return JIM_OK; +} + +int Jim_GetDouble(Jim_Interp *interp, Jim_Obj *objPtr, double *doublePtr) +{ + if (objPtr->typePtr == &coercedDoubleObjType) { + *doublePtr = JimWideValue(objPtr); + return JIM_OK; + } + if (objPtr->typePtr != &doubleObjType && SetDoubleFromAny(interp, objPtr) == JIM_ERR) + return JIM_ERR; + + if (objPtr->typePtr == &coercedDoubleObjType) { + *doublePtr = JimWideValue(objPtr); + } + else { + *doublePtr = objPtr->internalRep.doubleValue; + } + return JIM_OK; +} + +Jim_Obj *Jim_NewDoubleObj(Jim_Interp *interp, double doubleValue) +{ + Jim_Obj *objPtr; + + objPtr = Jim_NewObj(interp); + objPtr->typePtr = &doubleObjType; + objPtr->bytes = NULL; + objPtr->internalRep.doubleValue = doubleValue; + return objPtr; +} + +static int SetBooleanFromAny(Jim_Interp *interp, Jim_Obj *objPtr, int flags); + +int Jim_GetBoolean(Jim_Interp *interp, Jim_Obj *objPtr, int * booleanPtr) +{ + if (objPtr->typePtr != &intObjType && SetBooleanFromAny(interp, objPtr, JIM_ERRMSG) == JIM_ERR) + return JIM_ERR; + *booleanPtr = (int) JimWideValue(objPtr); + return JIM_OK; +} + +static int SetBooleanFromAny(Jim_Interp *interp, Jim_Obj *objPtr, int flags) +{ + static const char * const falses[] = { + "0", "false", "no", "off", NULL + }; + static const char * const trues[] = { + "1", "true", "yes", "on", NULL + }; + + int boolean; + + int index; + if (Jim_GetEnum(interp, objPtr, falses, &index, NULL, 0) == JIM_OK) { + boolean = 0; + } else if (Jim_GetEnum(interp, objPtr, trues, &index, NULL, 0) == JIM_OK) { + boolean = 1; + } else { + if (flags & JIM_ERRMSG) { + Jim_SetResultFormatted(interp, "expected boolean but got \"%#s\"", objPtr); + } + return JIM_ERR; + } + + + Jim_FreeIntRep(interp, objPtr); + objPtr->typePtr = &intObjType; + objPtr->internalRep.wideValue = boolean; + return JIM_OK; +} + +static void ListInsertElements(Jim_Obj *listPtr, int idx, int elemc, Jim_Obj *const *elemVec); +static void ListAppendElement(Jim_Obj *listPtr, Jim_Obj *objPtr); +static void FreeListInternalRep(Jim_Interp *interp, Jim_Obj *objPtr); +static void DupListInternalRep(Jim_Interp *interp, Jim_Obj *srcPtr, Jim_Obj *dupPtr); +static void UpdateStringOfList(struct Jim_Obj *objPtr); +static int SetListFromAny(Jim_Interp *interp, struct Jim_Obj *objPtr); + +static const Jim_ObjType listObjType = { + "list", + FreeListInternalRep, + DupListInternalRep, + UpdateStringOfList, + JIM_TYPE_NONE, +}; + +void FreeListInternalRep(Jim_Interp *interp, Jim_Obj *objPtr) +{ + int i; + + for (i = 0; i < objPtr->internalRep.listValue.len; i++) { + Jim_DecrRefCount(interp, objPtr->internalRep.listValue.ele[i]); + } + Jim_Free(objPtr->internalRep.listValue.ele); +} + +void DupListInternalRep(Jim_Interp *interp, Jim_Obj *srcPtr, Jim_Obj *dupPtr) +{ + int i; + + JIM_NOTUSED(interp); + + dupPtr->internalRep.listValue.len = srcPtr->internalRep.listValue.len; + dupPtr->internalRep.listValue.maxLen = srcPtr->internalRep.listValue.maxLen; + dupPtr->internalRep.listValue.ele = + Jim_Alloc(sizeof(Jim_Obj *) * srcPtr->internalRep.listValue.maxLen); + memcpy(dupPtr->internalRep.listValue.ele, srcPtr->internalRep.listValue.ele, + sizeof(Jim_Obj *) * srcPtr->internalRep.listValue.len); + for (i = 0; i < dupPtr->internalRep.listValue.len; i++) { + Jim_IncrRefCount(dupPtr->internalRep.listValue.ele[i]); + } + dupPtr->typePtr = &listObjType; +} + +#define JIM_ELESTR_SIMPLE 0 +#define JIM_ELESTR_BRACE 1 +#define JIM_ELESTR_QUOTE 2 +static unsigned char ListElementQuotingType(const char *s, int len) +{ + int i, level, blevel, trySimple = 1; + + + if (len == 0) + return JIM_ELESTR_BRACE; + if (s[0] == '"' || s[0] == '{') { + trySimple = 0; + goto testbrace; + } + for (i = 0; i < len; i++) { + switch (s[i]) { + case ' ': + case '$': + case '"': + case '[': + case ']': + case ';': + case '\\': + case '\r': + case '\n': + case '\t': + case '\f': + case '\v': + trySimple = 0; + + case '{': + case '}': + goto testbrace; + } + } + return JIM_ELESTR_SIMPLE; + + testbrace: + + if (s[len - 1] == '\\') + return JIM_ELESTR_QUOTE; + level = 0; + blevel = 0; + for (i = 0; i < len; i++) { + switch (s[i]) { + case '{': + level++; + break; + case '}': + level--; + if (level < 0) + return JIM_ELESTR_QUOTE; + break; + case '[': + blevel++; + break; + case ']': + blevel--; + break; + case '\\': + if (s[i + 1] == '\n') + return JIM_ELESTR_QUOTE; + else if (s[i + 1] != '\0') + i++; + break; + } + } + if (blevel < 0) { + return JIM_ELESTR_QUOTE; + } + + if (level == 0) { + if (!trySimple) + return JIM_ELESTR_BRACE; + for (i = 0; i < len; i++) { + switch (s[i]) { + case ' ': + case '$': + case '"': + case '[': + case ']': + case ';': + case '\\': + case '\r': + case '\n': + case '\t': + case '\f': + case '\v': + return JIM_ELESTR_BRACE; + break; + } + } + return JIM_ELESTR_SIMPLE; + } + return JIM_ELESTR_QUOTE; +} + +static int BackslashQuoteString(const char *s, int len, char *q) +{ + char *p = q; + + while (len--) { + switch (*s) { + case ' ': + case '$': + case '"': + case '[': + case ']': + case '{': + case '}': + case ';': + case '\\': + *p++ = '\\'; + *p++ = *s++; + break; + case '\n': + *p++ = '\\'; + *p++ = 'n'; + s++; + break; + case '\r': + *p++ = '\\'; + *p++ = 'r'; + s++; + break; + case '\t': + *p++ = '\\'; + *p++ = 't'; + s++; + break; + case '\f': + *p++ = '\\'; + *p++ = 'f'; + s++; + break; + case '\v': + *p++ = '\\'; + *p++ = 'v'; + s++; + break; + default: + *p++ = *s++; + break; + } + } + *p = '\0'; + + return p - q; +} + +static void JimMakeListStringRep(Jim_Obj *objPtr, Jim_Obj **objv, int objc) +{ + #define STATIC_QUOTING_LEN 32 + int i, bufLen, realLength; + const char *strRep; + char *p; + unsigned char *quotingType, staticQuoting[STATIC_QUOTING_LEN]; + + + if (objc > STATIC_QUOTING_LEN) { + quotingType = Jim_Alloc(objc); + } + else { + quotingType = staticQuoting; + } + bufLen = 0; + for (i = 0; i < objc; i++) { + int len; + + strRep = Jim_GetString(objv[i], &len); + quotingType[i] = ListElementQuotingType(strRep, len); + switch (quotingType[i]) { + case JIM_ELESTR_SIMPLE: + if (i != 0 || strRep[0] != '#') { + bufLen += len; + break; + } + + quotingType[i] = JIM_ELESTR_BRACE; + + case JIM_ELESTR_BRACE: + bufLen += len + 2; + break; + case JIM_ELESTR_QUOTE: + bufLen += len * 2; + break; + } + bufLen++; + } + bufLen++; + + + p = objPtr->bytes = Jim_Alloc(bufLen + 1); + realLength = 0; + for (i = 0; i < objc; i++) { + int len, qlen; + + strRep = Jim_GetString(objv[i], &len); + + switch (quotingType[i]) { + case JIM_ELESTR_SIMPLE: + memcpy(p, strRep, len); + p += len; + realLength += len; + break; + case JIM_ELESTR_BRACE: + *p++ = '{'; + memcpy(p, strRep, len); + p += len; + *p++ = '}'; + realLength += len + 2; + break; + case JIM_ELESTR_QUOTE: + if (i == 0 && strRep[0] == '#') { + *p++ = '\\'; + realLength++; + } + qlen = BackslashQuoteString(strRep, len, p); + p += qlen; + realLength += qlen; + break; + } + + if (i + 1 != objc) { + *p++ = ' '; + realLength++; + } + } + *p = '\0'; + objPtr->length = realLength; + + if (quotingType != staticQuoting) { + Jim_Free(quotingType); + } +} + +static void UpdateStringOfList(struct Jim_Obj *objPtr) +{ + JimMakeListStringRep(objPtr, objPtr->internalRep.listValue.ele, objPtr->internalRep.listValue.len); +} + +static int SetListFromAny(Jim_Interp *interp, struct Jim_Obj *objPtr) +{ + struct JimParserCtx parser; + const char *str; + int strLen; + Jim_Obj *fileNameObj; + int linenr; + + if (objPtr->typePtr == &listObjType) { + return JIM_OK; + } + + if (Jim_IsDict(objPtr) && objPtr->bytes == NULL) { + Jim_Obj **listObjPtrPtr; + int len; + int i; + + listObjPtrPtr = JimDictPairs(objPtr, &len); + for (i = 0; i < len; i++) { + Jim_IncrRefCount(listObjPtrPtr[i]); + } + + + Jim_FreeIntRep(interp, objPtr); + objPtr->typePtr = &listObjType; + objPtr->internalRep.listValue.len = len; + objPtr->internalRep.listValue.maxLen = len; + objPtr->internalRep.listValue.ele = listObjPtrPtr; + + return JIM_OK; + } + + + if (objPtr->typePtr == &sourceObjType) { + fileNameObj = objPtr->internalRep.sourceValue.fileNameObj; + linenr = objPtr->internalRep.sourceValue.lineNumber; + } + else { + fileNameObj = interp->emptyObj; + linenr = 1; + } + Jim_IncrRefCount(fileNameObj); + + + str = Jim_GetString(objPtr, &strLen); + + Jim_FreeIntRep(interp, objPtr); + objPtr->typePtr = &listObjType; + objPtr->internalRep.listValue.len = 0; + objPtr->internalRep.listValue.maxLen = 0; + objPtr->internalRep.listValue.ele = NULL; + + + if (strLen) { + JimParserInit(&parser, str, strLen, linenr); + while (!parser.eof) { + Jim_Obj *elementPtr; + + JimParseList(&parser); + if (parser.tt != JIM_TT_STR && parser.tt != JIM_TT_ESC) + continue; + elementPtr = JimParserGetTokenObj(interp, &parser); + JimSetSourceInfo(interp, elementPtr, fileNameObj, parser.tline); + ListAppendElement(objPtr, elementPtr); + } + } + Jim_DecrRefCount(interp, fileNameObj); + return JIM_OK; +} + +Jim_Obj *Jim_NewListObj(Jim_Interp *interp, Jim_Obj *const *elements, int len) +{ + Jim_Obj *objPtr; + + objPtr = Jim_NewObj(interp); + objPtr->typePtr = &listObjType; + objPtr->bytes = NULL; + objPtr->internalRep.listValue.ele = NULL; + objPtr->internalRep.listValue.len = 0; + objPtr->internalRep.listValue.maxLen = 0; + + if (len) { + ListInsertElements(objPtr, 0, len, elements); + } + + return objPtr; +} + +static void JimListGetElements(Jim_Interp *interp, Jim_Obj *listObj, int *listLen, + Jim_Obj ***listVec) +{ + *listLen = Jim_ListLength(interp, listObj); + *listVec = listObj->internalRep.listValue.ele; +} + + +static int JimSign(jim_wide w) +{ + if (w == 0) { + return 0; + } + else if (w < 0) { + return -1; + } + return 1; +} + + +struct lsort_info { + jmp_buf jmpbuf; + Jim_Obj *command; + Jim_Interp *interp; + enum { + JIM_LSORT_ASCII, + JIM_LSORT_NOCASE, + JIM_LSORT_INTEGER, + JIM_LSORT_REAL, + JIM_LSORT_COMMAND + } type; + int order; + int index; + int indexed; + int unique; + int (*subfn)(Jim_Obj **, Jim_Obj **); +}; + +static struct lsort_info *sort_info; + +static int ListSortIndexHelper(Jim_Obj **lhsObj, Jim_Obj **rhsObj) +{ + Jim_Obj *lObj, *rObj; + + if (Jim_ListIndex(sort_info->interp, *lhsObj, sort_info->index, &lObj, JIM_ERRMSG) != JIM_OK || + Jim_ListIndex(sort_info->interp, *rhsObj, sort_info->index, &rObj, JIM_ERRMSG) != JIM_OK) { + longjmp(sort_info->jmpbuf, JIM_ERR); + } + return sort_info->subfn(&lObj, &rObj); +} + + +static int ListSortString(Jim_Obj **lhsObj, Jim_Obj **rhsObj) +{ + return Jim_StringCompareObj(sort_info->interp, *lhsObj, *rhsObj, 0) * sort_info->order; +} + +static int ListSortStringNoCase(Jim_Obj **lhsObj, Jim_Obj **rhsObj) +{ + return Jim_StringCompareObj(sort_info->interp, *lhsObj, *rhsObj, 1) * sort_info->order; +} + +static int ListSortInteger(Jim_Obj **lhsObj, Jim_Obj **rhsObj) +{ + jim_wide lhs = 0, rhs = 0; + + if (Jim_GetWide(sort_info->interp, *lhsObj, &lhs) != JIM_OK || + Jim_GetWide(sort_info->interp, *rhsObj, &rhs) != JIM_OK) { + longjmp(sort_info->jmpbuf, JIM_ERR); + } + + return JimSign(lhs - rhs) * sort_info->order; +} + +static int ListSortReal(Jim_Obj **lhsObj, Jim_Obj **rhsObj) +{ + double lhs = 0, rhs = 0; + + if (Jim_GetDouble(sort_info->interp, *lhsObj, &lhs) != JIM_OK || + Jim_GetDouble(sort_info->interp, *rhsObj, &rhs) != JIM_OK) { + longjmp(sort_info->jmpbuf, JIM_ERR); + } + if (lhs == rhs) { + return 0; + } + if (lhs > rhs) { + return sort_info->order; + } + return -sort_info->order; +} + +static int ListSortCommand(Jim_Obj **lhsObj, Jim_Obj **rhsObj) +{ + Jim_Obj *compare_script; + int rc; + + jim_wide ret = 0; + + + compare_script = Jim_DuplicateObj(sort_info->interp, sort_info->command); + Jim_ListAppendElement(sort_info->interp, compare_script, *lhsObj); + Jim_ListAppendElement(sort_info->interp, compare_script, *rhsObj); + + rc = Jim_EvalObj(sort_info->interp, compare_script); + + if (rc != JIM_OK || Jim_GetWide(sort_info->interp, Jim_GetResult(sort_info->interp), &ret) != JIM_OK) { + longjmp(sort_info->jmpbuf, rc); + } + + return JimSign(ret) * sort_info->order; +} + +static void ListRemoveDuplicates(Jim_Obj *listObjPtr, int (*comp)(Jim_Obj **lhs, Jim_Obj **rhs)) +{ + int src; + int dst = 0; + Jim_Obj **ele = listObjPtr->internalRep.listValue.ele; + + for (src = 1; src < listObjPtr->internalRep.listValue.len; src++) { + if (comp(&ele[dst], &ele[src]) == 0) { + + Jim_DecrRefCount(sort_info->interp, ele[dst]); + } + else { + + dst++; + } + ele[dst] = ele[src]; + } + + + dst++; + if (dst < listObjPtr->internalRep.listValue.len) { + ele[dst] = ele[src]; + } + + + listObjPtr->internalRep.listValue.len = dst; +} + + +static int ListSortElements(Jim_Interp *interp, Jim_Obj *listObjPtr, struct lsort_info *info) +{ + struct lsort_info *prev_info; + + typedef int (qsort_comparator) (const void *, const void *); + int (*fn) (Jim_Obj **, Jim_Obj **); + Jim_Obj **vector; + int len; + int rc; + + JimPanic((Jim_IsShared(listObjPtr), "ListSortElements called with shared object")); + SetListFromAny(interp, listObjPtr); + + + prev_info = sort_info; + sort_info = info; + + vector = listObjPtr->internalRep.listValue.ele; + len = listObjPtr->internalRep.listValue.len; + switch (info->type) { + case JIM_LSORT_ASCII: + fn = ListSortString; + break; + case JIM_LSORT_NOCASE: + fn = ListSortStringNoCase; + break; + case JIM_LSORT_INTEGER: + fn = ListSortInteger; + break; + case JIM_LSORT_REAL: + fn = ListSortReal; + break; + case JIM_LSORT_COMMAND: + fn = ListSortCommand; + break; + default: + fn = NULL; + JimPanic((1, "ListSort called with invalid sort type")); + return -1; + } + + if (info->indexed) { + + info->subfn = fn; + fn = ListSortIndexHelper; + } + + if ((rc = setjmp(info->jmpbuf)) == 0) { + qsort(vector, len, sizeof(Jim_Obj *), (qsort_comparator *) fn); + + if (info->unique && len > 1) { + ListRemoveDuplicates(listObjPtr, fn); + } + + Jim_InvalidateStringRep(listObjPtr); + } + sort_info = prev_info; + + return rc; +} + +static void ListInsertElements(Jim_Obj *listPtr, int idx, int elemc, Jim_Obj *const *elemVec) +{ + int currentLen = listPtr->internalRep.listValue.len; + int requiredLen = currentLen + elemc; + int i; + Jim_Obj **point; + + if (requiredLen > listPtr->internalRep.listValue.maxLen) { + if (requiredLen < 2) { + + requiredLen = 4; + } + else { + requiredLen *= 2; + } + + listPtr->internalRep.listValue.ele = Jim_Realloc(listPtr->internalRep.listValue.ele, + sizeof(Jim_Obj *) * requiredLen); + + listPtr->internalRep.listValue.maxLen = requiredLen; + } + if (idx < 0) { + idx = currentLen; + } + point = listPtr->internalRep.listValue.ele + idx; + memmove(point + elemc, point, (currentLen - idx) * sizeof(Jim_Obj *)); + for (i = 0; i < elemc; ++i) { + point[i] = elemVec[i]; + Jim_IncrRefCount(point[i]); + } + listPtr->internalRep.listValue.len += elemc; +} + +static void ListAppendElement(Jim_Obj *listPtr, Jim_Obj *objPtr) +{ + ListInsertElements(listPtr, -1, 1, &objPtr); +} + +static void ListAppendList(Jim_Obj *listPtr, Jim_Obj *appendListPtr) +{ + ListInsertElements(listPtr, -1, + appendListPtr->internalRep.listValue.len, appendListPtr->internalRep.listValue.ele); +} + +void Jim_ListAppendElement(Jim_Interp *interp, Jim_Obj *listPtr, Jim_Obj *objPtr) +{ + JimPanic((Jim_IsShared(listPtr), "Jim_ListAppendElement called with shared object")); + SetListFromAny(interp, listPtr); + Jim_InvalidateStringRep(listPtr); + ListAppendElement(listPtr, objPtr); +} + +void Jim_ListAppendList(Jim_Interp *interp, Jim_Obj *listPtr, Jim_Obj *appendListPtr) +{ + JimPanic((Jim_IsShared(listPtr), "Jim_ListAppendList called with shared object")); + SetListFromAny(interp, listPtr); + SetListFromAny(interp, appendListPtr); + Jim_InvalidateStringRep(listPtr); + ListAppendList(listPtr, appendListPtr); +} + +int Jim_ListLength(Jim_Interp *interp, Jim_Obj *objPtr) +{ + SetListFromAny(interp, objPtr); + return objPtr->internalRep.listValue.len; +} + +void Jim_ListInsertElements(Jim_Interp *interp, Jim_Obj *listPtr, int idx, + int objc, Jim_Obj *const *objVec) +{ + JimPanic((Jim_IsShared(listPtr), "Jim_ListInsertElement called with shared object")); + SetListFromAny(interp, listPtr); + if (idx >= 0 && idx > listPtr->internalRep.listValue.len) + idx = listPtr->internalRep.listValue.len; + else if (idx < 0) + idx = 0; + Jim_InvalidateStringRep(listPtr); + ListInsertElements(listPtr, idx, objc, objVec); +} + +Jim_Obj *Jim_ListGetIndex(Jim_Interp *interp, Jim_Obj *listPtr, int idx) +{ + SetListFromAny(interp, listPtr); + if ((idx >= 0 && idx >= listPtr->internalRep.listValue.len) || + (idx < 0 && (-idx - 1) >= listPtr->internalRep.listValue.len)) { + return NULL; + } + if (idx < 0) + idx = listPtr->internalRep.listValue.len + idx; + return listPtr->internalRep.listValue.ele[idx]; +} + +int Jim_ListIndex(Jim_Interp *interp, Jim_Obj *listPtr, int idx, Jim_Obj **objPtrPtr, int flags) +{ + *objPtrPtr = Jim_ListGetIndex(interp, listPtr, idx); + if (*objPtrPtr == NULL) { + if (flags & JIM_ERRMSG) { + Jim_SetResultString(interp, "list index out of range", -1); + } + return JIM_ERR; + } + return JIM_OK; +} + +static int ListSetIndex(Jim_Interp *interp, Jim_Obj *listPtr, int idx, + Jim_Obj *newObjPtr, int flags) +{ + SetListFromAny(interp, listPtr); + if ((idx >= 0 && idx >= listPtr->internalRep.listValue.len) || + (idx < 0 && (-idx - 1) >= listPtr->internalRep.listValue.len)) { + if (flags & JIM_ERRMSG) { + Jim_SetResultString(interp, "list index out of range", -1); + } + return JIM_ERR; + } + if (idx < 0) + idx = listPtr->internalRep.listValue.len + idx; + Jim_DecrRefCount(interp, listPtr->internalRep.listValue.ele[idx]); + listPtr->internalRep.listValue.ele[idx] = newObjPtr; + Jim_IncrRefCount(newObjPtr); + return JIM_OK; +} + +int Jim_ListSetIndex(Jim_Interp *interp, Jim_Obj *varNamePtr, + Jim_Obj *const *indexv, int indexc, Jim_Obj *newObjPtr) +{ + Jim_Obj *varObjPtr, *objPtr, *listObjPtr; + int shared, i, idx; + + varObjPtr = objPtr = Jim_GetVariable(interp, varNamePtr, JIM_ERRMSG | JIM_UNSHARED); + if (objPtr == NULL) + return JIM_ERR; + if ((shared = Jim_IsShared(objPtr))) + varObjPtr = objPtr = Jim_DuplicateObj(interp, objPtr); + for (i = 0; i < indexc - 1; i++) { + listObjPtr = objPtr; + if (Jim_GetIndex(interp, indexv[i], &idx) != JIM_OK) + goto err; + if (Jim_ListIndex(interp, listObjPtr, idx, &objPtr, JIM_ERRMSG) != JIM_OK) { + goto err; + } + if (Jim_IsShared(objPtr)) { + objPtr = Jim_DuplicateObj(interp, objPtr); + ListSetIndex(interp, listObjPtr, idx, objPtr, JIM_NONE); + } + Jim_InvalidateStringRep(listObjPtr); + } + if (Jim_GetIndex(interp, indexv[indexc - 1], &idx) != JIM_OK) + goto err; + if (ListSetIndex(interp, objPtr, idx, newObjPtr, JIM_ERRMSG) == JIM_ERR) + goto err; + Jim_InvalidateStringRep(objPtr); + Jim_InvalidateStringRep(varObjPtr); + if (Jim_SetVariable(interp, varNamePtr, varObjPtr) != JIM_OK) + goto err; + Jim_SetResult(interp, varObjPtr); + return JIM_OK; + err: + if (shared) { + Jim_FreeNewObj(interp, varObjPtr); + } + return JIM_ERR; +} + +Jim_Obj *Jim_ListJoin(Jim_Interp *interp, Jim_Obj *listObjPtr, const char *joinStr, int joinStrLen) +{ + int i; + int listLen = Jim_ListLength(interp, listObjPtr); + Jim_Obj *resObjPtr = Jim_NewEmptyStringObj(interp); + + for (i = 0; i < listLen; ) { + Jim_AppendObj(interp, resObjPtr, Jim_ListGetIndex(interp, listObjPtr, i)); + if (++i != listLen) { + Jim_AppendString(interp, resObjPtr, joinStr, joinStrLen); + } + } + return resObjPtr; +} + +Jim_Obj *Jim_ConcatObj(Jim_Interp *interp, int objc, Jim_Obj *const *objv) +{ + int i; + + for (i = 0; i < objc; i++) { + if (!Jim_IsList(objv[i])) + break; + } + if (i == objc) { + Jim_Obj *objPtr = Jim_NewListObj(interp, NULL, 0); + + for (i = 0; i < objc; i++) + ListAppendList(objPtr, objv[i]); + return objPtr; + } + else { + + int len = 0, objLen; + char *bytes, *p; + + + for (i = 0; i < objc; i++) { + len += Jim_Length(objv[i]); + } + if (objc) + len += objc - 1; + + p = bytes = Jim_Alloc(len + 1); + for (i = 0; i < objc; i++) { + const char *s = Jim_GetString(objv[i], &objLen); + + + while (objLen && isspace(UCHAR(*s))) { + s++; + objLen--; + len--; + } + + while (objLen && isspace(UCHAR(s[objLen - 1]))) { + + if (objLen > 1 && s[objLen - 2] == '\\') { + break; + } + objLen--; + len--; + } + memcpy(p, s, objLen); + p += objLen; + if (i + 1 != objc) { + if (objLen) + *p++ = ' '; + else { + len--; + } + } + } + *p = '\0'; + return Jim_NewStringObjNoAlloc(interp, bytes, len); + } +} + +Jim_Obj *Jim_ListRange(Jim_Interp *interp, Jim_Obj *listObjPtr, Jim_Obj *firstObjPtr, + Jim_Obj *lastObjPtr) +{ + int first, last; + int len, rangeLen; + + if (Jim_GetIndex(interp, firstObjPtr, &first) != JIM_OK || + Jim_GetIndex(interp, lastObjPtr, &last) != JIM_OK) + return NULL; + len = Jim_ListLength(interp, listObjPtr); + first = JimRelToAbsIndex(len, first); + last = JimRelToAbsIndex(len, last); + JimRelToAbsRange(len, &first, &last, &rangeLen); + if (first == 0 && last == len) { + return listObjPtr; + } + return Jim_NewListObj(interp, listObjPtr->internalRep.listValue.ele + first, rangeLen); +} + +static void FreeDictInternalRep(Jim_Interp *interp, Jim_Obj *objPtr); +static void DupDictInternalRep(Jim_Interp *interp, Jim_Obj *srcPtr, Jim_Obj *dupPtr); +static void UpdateStringOfDict(struct Jim_Obj *objPtr); +static int SetDictFromAny(Jim_Interp *interp, struct Jim_Obj *objPtr); + + +static unsigned int JimObjectHTHashFunction(const void *key) +{ + int len; + const char *str = Jim_GetString((Jim_Obj *)key, &len); + return Jim_GenHashFunction((const unsigned char *)str, len); +} + +static int JimObjectHTKeyCompare(void *privdata, const void *key1, const void *key2) +{ + return Jim_StringEqObj((Jim_Obj *)key1, (Jim_Obj *)key2); +} + +static void *JimObjectHTKeyValDup(void *privdata, const void *val) +{ + Jim_IncrRefCount((Jim_Obj *)val); + return (void *)val; +} + +static void JimObjectHTKeyValDestructor(void *interp, void *val) +{ + Jim_DecrRefCount(interp, (Jim_Obj *)val); +} + +static const Jim_HashTableType JimDictHashTableType = { + JimObjectHTHashFunction, + JimObjectHTKeyValDup, + JimObjectHTKeyValDup, + JimObjectHTKeyCompare, + JimObjectHTKeyValDestructor, + JimObjectHTKeyValDestructor +}; + +static const Jim_ObjType dictObjType = { + "dict", + FreeDictInternalRep, + DupDictInternalRep, + UpdateStringOfDict, + JIM_TYPE_NONE, +}; + +void FreeDictInternalRep(Jim_Interp *interp, Jim_Obj *objPtr) +{ + JIM_NOTUSED(interp); + + Jim_FreeHashTable(objPtr->internalRep.ptr); + Jim_Free(objPtr->internalRep.ptr); +} + +void DupDictInternalRep(Jim_Interp *interp, Jim_Obj *srcPtr, Jim_Obj *dupPtr) +{ + Jim_HashTable *ht, *dupHt; + Jim_HashTableIterator htiter; + Jim_HashEntry *he; + + + ht = srcPtr->internalRep.ptr; + dupHt = Jim_Alloc(sizeof(*dupHt)); + Jim_InitHashTable(dupHt, &JimDictHashTableType, interp); + if (ht->size != 0) + Jim_ExpandHashTable(dupHt, ht->size); + + JimInitHashTableIterator(ht, &htiter); + while ((he = Jim_NextHashEntry(&htiter)) != NULL) { + Jim_AddHashEntry(dupHt, he->key, he->u.val); + } + + dupPtr->internalRep.ptr = dupHt; + dupPtr->typePtr = &dictObjType; +} + +static Jim_Obj **JimDictPairs(Jim_Obj *dictPtr, int *len) +{ + Jim_HashTable *ht; + Jim_HashTableIterator htiter; + Jim_HashEntry *he; + Jim_Obj **objv; + int i; + + ht = dictPtr->internalRep.ptr; + + + objv = Jim_Alloc((ht->used * 2) * sizeof(Jim_Obj *)); + JimInitHashTableIterator(ht, &htiter); + i = 0; + while ((he = Jim_NextHashEntry(&htiter)) != NULL) { + objv[i++] = Jim_GetHashEntryKey(he); + objv[i++] = Jim_GetHashEntryVal(he); + } + *len = i; + return objv; +} + +static void UpdateStringOfDict(struct Jim_Obj *objPtr) +{ + + int len; + Jim_Obj **objv = JimDictPairs(objPtr, &len); + + + JimMakeListStringRep(objPtr, objv, len); + + Jim_Free(objv); +} + +static int SetDictFromAny(Jim_Interp *interp, struct Jim_Obj *objPtr) +{ + int listlen; + + if (objPtr->typePtr == &dictObjType) { + return JIM_OK; + } + + if (Jim_IsList(objPtr) && Jim_IsShared(objPtr)) { + Jim_String(objPtr); + } + + + listlen = Jim_ListLength(interp, objPtr); + if (listlen % 2) { + Jim_SetResultString(interp, "missing value to go with key", -1); + return JIM_ERR; + } + else { + + Jim_HashTable *ht; + int i; + + ht = Jim_Alloc(sizeof(*ht)); + Jim_InitHashTable(ht, &JimDictHashTableType, interp); + + for (i = 0; i < listlen; i += 2) { + Jim_Obj *keyObjPtr = Jim_ListGetIndex(interp, objPtr, i); + Jim_Obj *valObjPtr = Jim_ListGetIndex(interp, objPtr, i + 1); + + Jim_ReplaceHashEntry(ht, keyObjPtr, valObjPtr); + } + + Jim_FreeIntRep(interp, objPtr); + objPtr->typePtr = &dictObjType; + objPtr->internalRep.ptr = ht; + + return JIM_OK; + } +} + + + +static int DictAddElement(Jim_Interp *interp, Jim_Obj *objPtr, + Jim_Obj *keyObjPtr, Jim_Obj *valueObjPtr) +{ + Jim_HashTable *ht = objPtr->internalRep.ptr; + + if (valueObjPtr == NULL) { + return Jim_DeleteHashEntry(ht, keyObjPtr); + } + Jim_ReplaceHashEntry(ht, keyObjPtr, valueObjPtr); + return JIM_OK; +} + +int Jim_DictAddElement(Jim_Interp *interp, Jim_Obj *objPtr, + Jim_Obj *keyObjPtr, Jim_Obj *valueObjPtr) +{ + JimPanic((Jim_IsShared(objPtr), "Jim_DictAddElement called with shared object")); + if (SetDictFromAny(interp, objPtr) != JIM_OK) { + return JIM_ERR; + } + Jim_InvalidateStringRep(objPtr); + return DictAddElement(interp, objPtr, keyObjPtr, valueObjPtr); +} + +Jim_Obj *Jim_NewDictObj(Jim_Interp *interp, Jim_Obj *const *elements, int len) +{ + Jim_Obj *objPtr; + int i; + + JimPanic((len % 2, "Jim_NewDictObj() 'len' argument must be even")); + + objPtr = Jim_NewObj(interp); + objPtr->typePtr = &dictObjType; + objPtr->bytes = NULL; + objPtr->internalRep.ptr = Jim_Alloc(sizeof(Jim_HashTable)); + Jim_InitHashTable(objPtr->internalRep.ptr, &JimDictHashTableType, interp); + for (i = 0; i < len; i += 2) + DictAddElement(interp, objPtr, elements[i], elements[i + 1]); + return objPtr; +} + +int Jim_DictKey(Jim_Interp *interp, Jim_Obj *dictPtr, Jim_Obj *keyPtr, + Jim_Obj **objPtrPtr, int flags) +{ + Jim_HashEntry *he; + Jim_HashTable *ht; + + if (SetDictFromAny(interp, dictPtr) != JIM_OK) { + return -1; + } + ht = dictPtr->internalRep.ptr; + if ((he = Jim_FindHashEntry(ht, keyPtr)) == NULL) { + if (flags & JIM_ERRMSG) { + Jim_SetResultFormatted(interp, "key \"%#s\" not known in dictionary", keyPtr); + } + return JIM_ERR; + } + else { + *objPtrPtr = Jim_GetHashEntryVal(he); + return JIM_OK; + } +} + + +int Jim_DictPairs(Jim_Interp *interp, Jim_Obj *dictPtr, Jim_Obj ***objPtrPtr, int *len) +{ + if (SetDictFromAny(interp, dictPtr) != JIM_OK) { + return JIM_ERR; + } + *objPtrPtr = JimDictPairs(dictPtr, len); + + return JIM_OK; +} + + + +int Jim_DictKeysVector(Jim_Interp *interp, Jim_Obj *dictPtr, + Jim_Obj *const *keyv, int keyc, Jim_Obj **objPtrPtr, int flags) +{ + int i; + + if (keyc == 0) { + *objPtrPtr = dictPtr; + return JIM_OK; + } + + for (i = 0; i < keyc; i++) { + Jim_Obj *objPtr; + + int rc = Jim_DictKey(interp, dictPtr, keyv[i], &objPtr, flags); + if (rc != JIM_OK) { + return rc; + } + dictPtr = objPtr; + } + *objPtrPtr = dictPtr; + return JIM_OK; +} + +int Jim_SetDictKeysVector(Jim_Interp *interp, Jim_Obj *varNamePtr, + Jim_Obj *const *keyv, int keyc, Jim_Obj *newObjPtr, int flags) +{ + Jim_Obj *varObjPtr, *objPtr, *dictObjPtr; + int shared, i; + + varObjPtr = objPtr = Jim_GetVariable(interp, varNamePtr, flags); + if (objPtr == NULL) { + if (newObjPtr == NULL && (flags & JIM_MUSTEXIST)) { + + return JIM_ERR; + } + varObjPtr = objPtr = Jim_NewDictObj(interp, NULL, 0); + if (Jim_SetVariable(interp, varNamePtr, objPtr) != JIM_OK) { + Jim_FreeNewObj(interp, varObjPtr); + return JIM_ERR; + } + } + if ((shared = Jim_IsShared(objPtr))) + varObjPtr = objPtr = Jim_DuplicateObj(interp, objPtr); + for (i = 0; i < keyc; i++) { + dictObjPtr = objPtr; + + + if (SetDictFromAny(interp, dictObjPtr) != JIM_OK) { + goto err; + } + + if (i == keyc - 1) { + + if (Jim_DictAddElement(interp, objPtr, keyv[keyc - 1], newObjPtr) != JIM_OK) { + if (newObjPtr || (flags & JIM_MUSTEXIST)) { + goto err; + } + } + break; + } + + + Jim_InvalidateStringRep(dictObjPtr); + if (Jim_DictKey(interp, dictObjPtr, keyv[i], &objPtr, + newObjPtr ? JIM_NONE : JIM_ERRMSG) == JIM_OK) { + if (Jim_IsShared(objPtr)) { + objPtr = Jim_DuplicateObj(interp, objPtr); + DictAddElement(interp, dictObjPtr, keyv[i], objPtr); + } + } + else { + if (newObjPtr == NULL) { + goto err; + } + objPtr = Jim_NewDictObj(interp, NULL, 0); + DictAddElement(interp, dictObjPtr, keyv[i], objPtr); + } + } + + Jim_InvalidateStringRep(objPtr); + Jim_InvalidateStringRep(varObjPtr); + if (Jim_SetVariable(interp, varNamePtr, varObjPtr) != JIM_OK) { + goto err; + } + Jim_SetResult(interp, varObjPtr); + return JIM_OK; + err: + if (shared) { + Jim_FreeNewObj(interp, varObjPtr); + } + return JIM_ERR; +} + +static void UpdateStringOfIndex(struct Jim_Obj *objPtr); +static int SetIndexFromAny(Jim_Interp *interp, struct Jim_Obj *objPtr); + +static const Jim_ObjType indexObjType = { + "index", + NULL, + NULL, + UpdateStringOfIndex, + JIM_TYPE_NONE, +}; + +static void UpdateStringOfIndex(struct Jim_Obj *objPtr) +{ + if (objPtr->internalRep.intValue == -1) { + JimSetStringBytes(objPtr, "end"); + } + else { + char buf[JIM_INTEGER_SPACE + 1]; + if (objPtr->internalRep.intValue >= 0) { + sprintf(buf, "%d", objPtr->internalRep.intValue); + } + else { + + sprintf(buf, "end%d", objPtr->internalRep.intValue + 1); + } + JimSetStringBytes(objPtr, buf); + } +} + +static int SetIndexFromAny(Jim_Interp *interp, Jim_Obj *objPtr) +{ + int idx, end = 0; + const char *str; + char *endptr; + + + str = Jim_String(objPtr); + + + if (strncmp(str, "end", 3) == 0) { + end = 1; + str += 3; + idx = 0; + } + else { + idx = jim_strtol(str, &endptr); + + if (endptr == str) { + goto badindex; + } + str = endptr; + } + + + if (*str == '+' || *str == '-') { + int sign = (*str == '+' ? 1 : -1); + + idx += sign * jim_strtol(++str, &endptr); + if (str == endptr || *endptr) { + goto badindex; + } + str = endptr; + } + + while (isspace(UCHAR(*str))) { + str++; + } + if (*str) { + goto badindex; + } + if (end) { + if (idx > 0) { + idx = INT_MAX; + } + else { + + idx--; + } + } + else if (idx < 0) { + idx = -INT_MAX; + } + + + Jim_FreeIntRep(interp, objPtr); + objPtr->typePtr = &indexObjType; + objPtr->internalRep.intValue = idx; + return JIM_OK; + + badindex: + Jim_SetResultFormatted(interp, + "bad index \"%#s\": must be integer?[+-]integer? or end?[+-]integer?", objPtr); + return JIM_ERR; +} + +int Jim_GetIndex(Jim_Interp *interp, Jim_Obj *objPtr, int *indexPtr) +{ + + if (objPtr->typePtr == &intObjType) { + jim_wide val = JimWideValue(objPtr); + + if (val < 0) + *indexPtr = -INT_MAX; + else if (val > INT_MAX) + *indexPtr = INT_MAX; + else + *indexPtr = (int)val; + return JIM_OK; + } + if (objPtr->typePtr != &indexObjType && SetIndexFromAny(interp, objPtr) == JIM_ERR) + return JIM_ERR; + *indexPtr = objPtr->internalRep.intValue; + return JIM_OK; +} + + + +static const char * const jimReturnCodes[] = { + "ok", + "error", + "return", + "break", + "continue", + "signal", + "exit", + "eval", + NULL +}; + +#define jimReturnCodesSize (sizeof(jimReturnCodes)/sizeof(*jimReturnCodes) - 1) + +static const Jim_ObjType returnCodeObjType = { + "return-code", + NULL, + NULL, + NULL, + JIM_TYPE_NONE, +}; + +const char *Jim_ReturnCode(int code) +{ + if (code < 0 || code >= (int)jimReturnCodesSize) { + return "?"; + } + else { + return jimReturnCodes[code]; + } +} + +static int SetReturnCodeFromAny(Jim_Interp *interp, Jim_Obj *objPtr) +{ + int returnCode; + jim_wide wideValue; + + + if (JimGetWideNoErr(interp, objPtr, &wideValue) != JIM_ERR) + returnCode = (int)wideValue; + else if (Jim_GetEnum(interp, objPtr, jimReturnCodes, &returnCode, NULL, JIM_NONE) != JIM_OK) { + Jim_SetResultFormatted(interp, "expected return code but got \"%#s\"", objPtr); + return JIM_ERR; + } + + Jim_FreeIntRep(interp, objPtr); + objPtr->typePtr = &returnCodeObjType; + objPtr->internalRep.intValue = returnCode; + return JIM_OK; +} + +int Jim_GetReturnCode(Jim_Interp *interp, Jim_Obj *objPtr, int *intPtr) +{ + if (objPtr->typePtr != &returnCodeObjType && SetReturnCodeFromAny(interp, objPtr) == JIM_ERR) + return JIM_ERR; + *intPtr = objPtr->internalRep.intValue; + return JIM_OK; +} + +static int JimParseExprOperator(struct JimParserCtx *pc); +static int JimParseExprNumber(struct JimParserCtx *pc); +static int JimParseExprIrrational(struct JimParserCtx *pc); +static int JimParseExprBoolean(struct JimParserCtx *pc); + + +enum +{ + + + + JIM_EXPROP_MUL = JIM_TT_EXPR_OP, + JIM_EXPROP_DIV, + JIM_EXPROP_MOD, + JIM_EXPROP_SUB, + JIM_EXPROP_ADD, + JIM_EXPROP_LSHIFT, + JIM_EXPROP_RSHIFT, + JIM_EXPROP_ROTL, + JIM_EXPROP_ROTR, + JIM_EXPROP_LT, + JIM_EXPROP_GT, + JIM_EXPROP_LTE, + JIM_EXPROP_GTE, + JIM_EXPROP_NUMEQ, + JIM_EXPROP_NUMNE, + JIM_EXPROP_BITAND, + JIM_EXPROP_BITXOR, + JIM_EXPROP_BITOR, + JIM_EXPROP_LOGICAND, + JIM_EXPROP_LOGICOR, + JIM_EXPROP_TERNARY, + JIM_EXPROP_COLON, + JIM_EXPROP_POW, + + + JIM_EXPROP_STREQ, + JIM_EXPROP_STRNE, + JIM_EXPROP_STRIN, + JIM_EXPROP_STRNI, + + + JIM_EXPROP_NOT, + JIM_EXPROP_BITNOT, + JIM_EXPROP_UNARYMINUS, + JIM_EXPROP_UNARYPLUS, + + + JIM_EXPROP_FUNC_INT, + JIM_EXPROP_FUNC_WIDE, + JIM_EXPROP_FUNC_ABS, + JIM_EXPROP_FUNC_DOUBLE, + JIM_EXPROP_FUNC_ROUND, + JIM_EXPROP_FUNC_RAND, + JIM_EXPROP_FUNC_SRAND, + + + JIM_EXPROP_FUNC_SIN, + JIM_EXPROP_FUNC_COS, + JIM_EXPROP_FUNC_TAN, + JIM_EXPROP_FUNC_ASIN, + JIM_EXPROP_FUNC_ACOS, + JIM_EXPROP_FUNC_ATAN, + JIM_EXPROP_FUNC_ATAN2, + JIM_EXPROP_FUNC_SINH, + JIM_EXPROP_FUNC_COSH, + JIM_EXPROP_FUNC_TANH, + JIM_EXPROP_FUNC_CEIL, + JIM_EXPROP_FUNC_FLOOR, + JIM_EXPROP_FUNC_EXP, + JIM_EXPROP_FUNC_LOG, + JIM_EXPROP_FUNC_LOG10, + JIM_EXPROP_FUNC_SQRT, + JIM_EXPROP_FUNC_POW, + JIM_EXPROP_FUNC_HYPOT, + JIM_EXPROP_FUNC_FMOD, +}; + +struct JimExprNode { + int type; + struct Jim_Obj *objPtr; + + struct JimExprNode *left; + struct JimExprNode *right; + struct JimExprNode *ternary; +}; + + +typedef struct Jim_ExprOperator +{ + const char *name; + int (*funcop) (Jim_Interp *interp, struct JimExprNode *opnode); + unsigned char precedence; + unsigned char arity; + unsigned char attr; + unsigned char namelen; +} Jim_ExprOperator; + +static int JimExprGetTerm(Jim_Interp *interp, struct JimExprNode *node, Jim_Obj **objPtrPtr); +static int JimExprGetTermBoolean(Jim_Interp *interp, struct JimExprNode *node); +static int JimExprEvalTermNode(Jim_Interp *interp, struct JimExprNode *node); + +static int JimExprOpNumUnary(Jim_Interp *interp, struct JimExprNode *node) +{ + int intresult = 1; + int rc; + double dA, dC = 0; + jim_wide wA, wC = 0; + Jim_Obj *A; + + if ((rc = JimExprGetTerm(interp, node->left, &A)) != JIM_OK) { + return rc; + } + + if ((A->typePtr != &doubleObjType || A->bytes) && JimGetWideNoErr(interp, A, &wA) == JIM_OK) { + switch (node->type) { + case JIM_EXPROP_FUNC_INT: + case JIM_EXPROP_FUNC_WIDE: + case JIM_EXPROP_FUNC_ROUND: + case JIM_EXPROP_UNARYPLUS: + wC = wA; + break; + case JIM_EXPROP_FUNC_DOUBLE: + dC = wA; + intresult = 0; + break; + case JIM_EXPROP_FUNC_ABS: + wC = wA >= 0 ? wA : -wA; + break; + case JIM_EXPROP_UNARYMINUS: + wC = -wA; + break; + case JIM_EXPROP_NOT: + wC = !wA; + break; + default: + abort(); + } + } + else if ((rc = Jim_GetDouble(interp, A, &dA)) == JIM_OK) { + switch (node->type) { + case JIM_EXPROP_FUNC_INT: + case JIM_EXPROP_FUNC_WIDE: + wC = dA; + break; + case JIM_EXPROP_FUNC_ROUND: + wC = dA < 0 ? (dA - 0.5) : (dA + 0.5); + break; + case JIM_EXPROP_FUNC_DOUBLE: + case JIM_EXPROP_UNARYPLUS: + dC = dA; + intresult = 0; + break; + case JIM_EXPROP_FUNC_ABS: +#ifdef JIM_MATH_FUNCTIONS + dC = fabs(dA); +#else + dC = dA >= 0 ? dA : -dA; +#endif + intresult = 0; + break; + case JIM_EXPROP_UNARYMINUS: + dC = -dA; + intresult = 0; + break; + case JIM_EXPROP_NOT: + wC = !dA; + break; + default: + abort(); + } + } + + if (rc == JIM_OK) { + if (intresult) { + Jim_SetResultInt(interp, wC); + } + else { + Jim_SetResult(interp, Jim_NewDoubleObj(interp, dC)); + } + } + + Jim_DecrRefCount(interp, A); + + return rc; +} + +static double JimRandDouble(Jim_Interp *interp) +{ + unsigned long x; + JimRandomBytes(interp, &x, sizeof(x)); + + return (double)x / (unsigned long)~0; +} + +static int JimExprOpIntUnary(Jim_Interp *interp, struct JimExprNode *node) +{ + jim_wide wA; + Jim_Obj *A; + int rc; + + if ((rc = JimExprGetTerm(interp, node->left, &A)) != JIM_OK) { + return rc; + } + + rc = Jim_GetWide(interp, A, &wA); + if (rc == JIM_OK) { + switch (node->type) { + case JIM_EXPROP_BITNOT: + Jim_SetResultInt(interp, ~wA); + break; + case JIM_EXPROP_FUNC_SRAND: + JimPrngSeed(interp, (unsigned char *)&wA, sizeof(wA)); + Jim_SetResult(interp, Jim_NewDoubleObj(interp, JimRandDouble(interp))); + break; + default: + abort(); + } + } + + Jim_DecrRefCount(interp, A); + + return rc; +} + +static int JimExprOpNone(Jim_Interp *interp, struct JimExprNode *node) +{ + JimPanic((node->type != JIM_EXPROP_FUNC_RAND, "JimExprOpNone only support rand()")); + + Jim_SetResult(interp, Jim_NewDoubleObj(interp, JimRandDouble(interp))); + + return JIM_OK; +} + +#ifdef JIM_MATH_FUNCTIONS +static int JimExprOpDoubleUnary(Jim_Interp *interp, struct JimExprNode *node) +{ + int rc; + double dA, dC; + Jim_Obj *A; + + if ((rc = JimExprGetTerm(interp, node->left, &A)) != JIM_OK) { + return rc; + } + + rc = Jim_GetDouble(interp, A, &dA); + if (rc == JIM_OK) { + switch (node->type) { + case JIM_EXPROP_FUNC_SIN: + dC = sin(dA); + break; + case JIM_EXPROP_FUNC_COS: + dC = cos(dA); + break; + case JIM_EXPROP_FUNC_TAN: + dC = tan(dA); + break; + case JIM_EXPROP_FUNC_ASIN: + dC = asin(dA); + break; + case JIM_EXPROP_FUNC_ACOS: + dC = acos(dA); + break; + case JIM_EXPROP_FUNC_ATAN: + dC = atan(dA); + break; + case JIM_EXPROP_FUNC_SINH: + dC = sinh(dA); + break; + case JIM_EXPROP_FUNC_COSH: + dC = cosh(dA); + break; + case JIM_EXPROP_FUNC_TANH: + dC = tanh(dA); + break; + case JIM_EXPROP_FUNC_CEIL: + dC = ceil(dA); + break; + case JIM_EXPROP_FUNC_FLOOR: + dC = floor(dA); + break; + case JIM_EXPROP_FUNC_EXP: + dC = exp(dA); + break; + case JIM_EXPROP_FUNC_LOG: + dC = log(dA); + break; + case JIM_EXPROP_FUNC_LOG10: + dC = log10(dA); + break; + case JIM_EXPROP_FUNC_SQRT: + dC = sqrt(dA); + break; + default: + abort(); + } + Jim_SetResult(interp, Jim_NewDoubleObj(interp, dC)); + } + + Jim_DecrRefCount(interp, A); + + return rc; +} +#endif + + +static int JimExprOpIntBin(Jim_Interp *interp, struct JimExprNode *node) +{ + jim_wide wA, wB; + int rc; + Jim_Obj *A, *B; + + if ((rc = JimExprGetTerm(interp, node->left, &A)) != JIM_OK) { + return rc; + } + if ((rc = JimExprGetTerm(interp, node->right, &B)) != JIM_OK) { + Jim_DecrRefCount(interp, A); + return rc; + } + + rc = JIM_ERR; + + if (Jim_GetWide(interp, A, &wA) == JIM_OK && Jim_GetWide(interp, B, &wB) == JIM_OK) { + jim_wide wC; + + rc = JIM_OK; + + switch (node->type) { + case JIM_EXPROP_LSHIFT: + wC = wA << wB; + break; + case JIM_EXPROP_RSHIFT: + wC = wA >> wB; + break; + case JIM_EXPROP_BITAND: + wC = wA & wB; + break; + case JIM_EXPROP_BITXOR: + wC = wA ^ wB; + break; + case JIM_EXPROP_BITOR: + wC = wA | wB; + break; + case JIM_EXPROP_MOD: + if (wB == 0) { + wC = 0; + Jim_SetResultString(interp, "Division by zero", -1); + rc = JIM_ERR; + } + else { + int negative = 0; + + if (wB < 0) { + wB = -wB; + wA = -wA; + negative = 1; + } + wC = wA % wB; + if (wC < 0) { + wC += wB; + } + if (negative) { + wC = -wC; + } + } + break; + case JIM_EXPROP_ROTL: + case JIM_EXPROP_ROTR:{ + + unsigned long uA = (unsigned long)wA; + unsigned long uB = (unsigned long)wB; + const unsigned int S = sizeof(unsigned long) * 8; + + + uB %= S; + + if (node->type == JIM_EXPROP_ROTR) { + uB = S - uB; + } + wC = (unsigned long)(uA << uB) | (uA >> (S - uB)); + break; + } + default: + abort(); + } + Jim_SetResultInt(interp, wC); + } + + Jim_DecrRefCount(interp, A); + Jim_DecrRefCount(interp, B); + + return rc; +} + + + +static int JimExprOpBin(Jim_Interp *interp, struct JimExprNode *node) +{ + int rc = JIM_OK; + double dA, dB, dC = 0; + jim_wide wA, wB, wC = 0; + Jim_Obj *A, *B; + + if ((rc = JimExprGetTerm(interp, node->left, &A)) != JIM_OK) { + return rc; + } + if ((rc = JimExprGetTerm(interp, node->right, &B)) != JIM_OK) { + Jim_DecrRefCount(interp, A); + return rc; + } + + if ((A->typePtr != &doubleObjType || A->bytes) && + (B->typePtr != &doubleObjType || B->bytes) && + JimGetWideNoErr(interp, A, &wA) == JIM_OK && JimGetWideNoErr(interp, B, &wB) == JIM_OK) { + + + + switch (node->type) { + case JIM_EXPROP_POW: + case JIM_EXPROP_FUNC_POW: + if (wA == 0 && wB < 0) { + Jim_SetResultString(interp, "exponentiation of zero by negative power", -1); + rc = JIM_ERR; + goto done; + } + wC = JimPowWide(wA, wB); + goto intresult; + case JIM_EXPROP_ADD: + wC = wA + wB; + goto intresult; + case JIM_EXPROP_SUB: + wC = wA - wB; + goto intresult; + case JIM_EXPROP_MUL: + wC = wA * wB; + goto intresult; + case JIM_EXPROP_DIV: + if (wB == 0) { + Jim_SetResultString(interp, "Division by zero", -1); + rc = JIM_ERR; + goto done; + } + else { + if (wB < 0) { + wB = -wB; + wA = -wA; + } + wC = wA / wB; + if (wA % wB < 0) { + wC--; + } + goto intresult; + } + case JIM_EXPROP_LT: + wC = wA < wB; + goto intresult; + case JIM_EXPROP_GT: + wC = wA > wB; + goto intresult; + case JIM_EXPROP_LTE: + wC = wA <= wB; + goto intresult; + case JIM_EXPROP_GTE: + wC = wA >= wB; + goto intresult; + case JIM_EXPROP_NUMEQ: + wC = wA == wB; + goto intresult; + case JIM_EXPROP_NUMNE: + wC = wA != wB; + goto intresult; + } + } + if (Jim_GetDouble(interp, A, &dA) == JIM_OK && Jim_GetDouble(interp, B, &dB) == JIM_OK) { + switch (node->type) { +#ifndef JIM_MATH_FUNCTIONS + case JIM_EXPROP_POW: + case JIM_EXPROP_FUNC_POW: + case JIM_EXPROP_FUNC_ATAN2: + case JIM_EXPROP_FUNC_HYPOT: + case JIM_EXPROP_FUNC_FMOD: + Jim_SetResultString(interp, "unsupported", -1); + rc = JIM_ERR; + goto done; +#else + case JIM_EXPROP_POW: + case JIM_EXPROP_FUNC_POW: + dC = pow(dA, dB); + goto doubleresult; + case JIM_EXPROP_FUNC_ATAN2: + dC = atan2(dA, dB); + goto doubleresult; + case JIM_EXPROP_FUNC_HYPOT: + dC = hypot(dA, dB); + goto doubleresult; + case JIM_EXPROP_FUNC_FMOD: + dC = fmod(dA, dB); + goto doubleresult; +#endif + case JIM_EXPROP_ADD: + dC = dA + dB; + goto doubleresult; + case JIM_EXPROP_SUB: + dC = dA - dB; + goto doubleresult; + case JIM_EXPROP_MUL: + dC = dA * dB; + goto doubleresult; + case JIM_EXPROP_DIV: + if (dB == 0) { +#ifdef INFINITY + dC = dA < 0 ? -INFINITY : INFINITY; +#else + dC = (dA < 0 ? -1.0 : 1.0) * strtod("Inf", NULL); +#endif + } + else { + dC = dA / dB; + } + goto doubleresult; + case JIM_EXPROP_LT: + wC = dA < dB; + goto intresult; + case JIM_EXPROP_GT: + wC = dA > dB; + goto intresult; + case JIM_EXPROP_LTE: + wC = dA <= dB; + goto intresult; + case JIM_EXPROP_GTE: + wC = dA >= dB; + goto intresult; + case JIM_EXPROP_NUMEQ: + wC = dA == dB; + goto intresult; + case JIM_EXPROP_NUMNE: + wC = dA != dB; + goto intresult; + } + } + else { + + + + int i = Jim_StringCompareObj(interp, A, B, 0); + + switch (node->type) { + case JIM_EXPROP_LT: + wC = i < 0; + goto intresult; + case JIM_EXPROP_GT: + wC = i > 0; + goto intresult; + case JIM_EXPROP_LTE: + wC = i <= 0; + goto intresult; + case JIM_EXPROP_GTE: + wC = i >= 0; + goto intresult; + case JIM_EXPROP_NUMEQ: + wC = i == 0; + goto intresult; + case JIM_EXPROP_NUMNE: + wC = i != 0; + goto intresult; + } + } + + rc = JIM_ERR; +done: + Jim_DecrRefCount(interp, A); + Jim_DecrRefCount(interp, B); + return rc; +intresult: + Jim_SetResultInt(interp, wC); + goto done; +doubleresult: + Jim_SetResult(interp, Jim_NewDoubleObj(interp, dC)); + goto done; +} + +static int JimSearchList(Jim_Interp *interp, Jim_Obj *listObjPtr, Jim_Obj *valObj) +{ + int listlen; + int i; + + listlen = Jim_ListLength(interp, listObjPtr); + for (i = 0; i < listlen; i++) { + if (Jim_StringEqObj(Jim_ListGetIndex(interp, listObjPtr, i), valObj)) { + return 1; + } + } + return 0; +} + + + +static int JimExprOpStrBin(Jim_Interp *interp, struct JimExprNode *node) +{ + Jim_Obj *A, *B; + jim_wide wC; + int rc; + + if ((rc = JimExprGetTerm(interp, node->left, &A)) != JIM_OK) { + return rc; + } + if ((rc = JimExprGetTerm(interp, node->right, &B)) != JIM_OK) { + Jim_DecrRefCount(interp, A); + return rc; + } + + switch (node->type) { + case JIM_EXPROP_STREQ: + case JIM_EXPROP_STRNE: + wC = Jim_StringEqObj(A, B); + if (node->type == JIM_EXPROP_STRNE) { + wC = !wC; + } + break; + case JIM_EXPROP_STRIN: + wC = JimSearchList(interp, B, A); + break; + case JIM_EXPROP_STRNI: + wC = !JimSearchList(interp, B, A); + break; + default: + abort(); + } + Jim_SetResultInt(interp, wC); + + Jim_DecrRefCount(interp, A); + Jim_DecrRefCount(interp, B); + + return rc; +} + +static int ExprBool(Jim_Interp *interp, Jim_Obj *obj) +{ + long l; + double d; + int b; + int ret = -1; + + + Jim_IncrRefCount(obj); + + if (Jim_GetLong(interp, obj, &l) == JIM_OK) { + ret = (l != 0); + } + else if (Jim_GetDouble(interp, obj, &d) == JIM_OK) { + ret = (d != 0); + } + else if (Jim_GetBoolean(interp, obj, &b) == JIM_OK) { + ret = (b != 0); + } + + Jim_DecrRefCount(interp, obj); + return ret; +} + +static int JimExprOpAnd(Jim_Interp *interp, struct JimExprNode *node) +{ + + int result = JimExprGetTermBoolean(interp, node->left); + + if (result == 1) { + + result = JimExprGetTermBoolean(interp, node->right); + } + if (result == -1) { + return JIM_ERR; + } + Jim_SetResultInt(interp, result); + return JIM_OK; +} + +static int JimExprOpOr(Jim_Interp *interp, struct JimExprNode *node) +{ + + int result = JimExprGetTermBoolean(interp, node->left); + + if (result == 0) { + + result = JimExprGetTermBoolean(interp, node->right); + } + if (result == -1) { + return JIM_ERR; + } + Jim_SetResultInt(interp, result); + return JIM_OK; +} + +static int JimExprOpTernary(Jim_Interp *interp, struct JimExprNode *node) +{ + + int result = JimExprGetTermBoolean(interp, node->left); + + if (result == 1) { + + return JimExprEvalTermNode(interp, node->right); + } + else if (result == 0) { + + return JimExprEvalTermNode(interp, node->ternary); + } + + return JIM_ERR; +} + +enum +{ + OP_FUNC = 0x0001, + OP_RIGHT_ASSOC = 0x0002, +}; + +#define OPRINIT_ATTR(N, P, ARITY, F, ATTR) {N, F, P, ARITY, ATTR, sizeof(N) - 1} +#define OPRINIT(N, P, ARITY, F) OPRINIT_ATTR(N, P, ARITY, F, 0) + +static const struct Jim_ExprOperator Jim_ExprOperators[] = { + OPRINIT("*", 110, 2, JimExprOpBin), + OPRINIT("/", 110, 2, JimExprOpBin), + OPRINIT("%", 110, 2, JimExprOpIntBin), + + OPRINIT("-", 100, 2, JimExprOpBin), + OPRINIT("+", 100, 2, JimExprOpBin), + + OPRINIT("<<", 90, 2, JimExprOpIntBin), + OPRINIT(">>", 90, 2, JimExprOpIntBin), + + OPRINIT("<<<", 90, 2, JimExprOpIntBin), + OPRINIT(">>>", 90, 2, JimExprOpIntBin), + + OPRINIT("<", 80, 2, JimExprOpBin), + OPRINIT(">", 80, 2, JimExprOpBin), + OPRINIT("<=", 80, 2, JimExprOpBin), + OPRINIT(">=", 80, 2, JimExprOpBin), + + OPRINIT("==", 70, 2, JimExprOpBin), + OPRINIT("!=", 70, 2, JimExprOpBin), + + OPRINIT("&", 50, 2, JimExprOpIntBin), + OPRINIT("^", 49, 2, JimExprOpIntBin), + OPRINIT("|", 48, 2, JimExprOpIntBin), + + OPRINIT("&&", 10, 2, JimExprOpAnd), + OPRINIT("||", 9, 2, JimExprOpOr), + OPRINIT_ATTR("?", 5, 3, JimExprOpTernary, OP_RIGHT_ASSOC), + OPRINIT_ATTR(":", 5, 3, NULL, OP_RIGHT_ASSOC), + + + OPRINIT_ATTR("**", 120, 2, JimExprOpBin, OP_RIGHT_ASSOC), + + OPRINIT("eq", 60, 2, JimExprOpStrBin), + OPRINIT("ne", 60, 2, JimExprOpStrBin), + + OPRINIT("in", 55, 2, JimExprOpStrBin), + OPRINIT("ni", 55, 2, JimExprOpStrBin), + + OPRINIT_ATTR("!", 150, 1, JimExprOpNumUnary, OP_RIGHT_ASSOC), + OPRINIT_ATTR("~", 150, 1, JimExprOpIntUnary, OP_RIGHT_ASSOC), + OPRINIT_ATTR(" -", 150, 1, JimExprOpNumUnary, OP_RIGHT_ASSOC), + OPRINIT_ATTR(" +", 150, 1, JimExprOpNumUnary, OP_RIGHT_ASSOC), + + + + OPRINIT_ATTR("int", 200, 1, JimExprOpNumUnary, OP_FUNC), + OPRINIT_ATTR("wide", 200, 1, JimExprOpNumUnary, OP_FUNC), + OPRINIT_ATTR("abs", 200, 1, JimExprOpNumUnary, OP_FUNC), + OPRINIT_ATTR("double", 200, 1, JimExprOpNumUnary, OP_FUNC), + OPRINIT_ATTR("round", 200, 1, JimExprOpNumUnary, OP_FUNC), + OPRINIT_ATTR("rand", 200, 0, JimExprOpNone, OP_FUNC), + OPRINIT_ATTR("srand", 200, 1, JimExprOpIntUnary, OP_FUNC), + +#ifdef JIM_MATH_FUNCTIONS + OPRINIT_ATTR("sin", 200, 1, JimExprOpDoubleUnary, OP_FUNC), + OPRINIT_ATTR("cos", 200, 1, JimExprOpDoubleUnary, OP_FUNC), + OPRINIT_ATTR("tan", 200, 1, JimExprOpDoubleUnary, OP_FUNC), + OPRINIT_ATTR("asin", 200, 1, JimExprOpDoubleUnary, OP_FUNC), + OPRINIT_ATTR("acos", 200, 1, JimExprOpDoubleUnary, OP_FUNC), + OPRINIT_ATTR("atan", 200, 1, JimExprOpDoubleUnary, OP_FUNC), + OPRINIT_ATTR("atan2", 200, 2, JimExprOpBin, OP_FUNC), + OPRINIT_ATTR("sinh", 200, 1, JimExprOpDoubleUnary, OP_FUNC), + OPRINIT_ATTR("cosh", 200, 1, JimExprOpDoubleUnary, OP_FUNC), + OPRINIT_ATTR("tanh", 200, 1, JimExprOpDoubleUnary, OP_FUNC), + OPRINIT_ATTR("ceil", 200, 1, JimExprOpDoubleUnary, OP_FUNC), + OPRINIT_ATTR("floor", 200, 1, JimExprOpDoubleUnary, OP_FUNC), + OPRINIT_ATTR("exp", 200, 1, JimExprOpDoubleUnary, OP_FUNC), + OPRINIT_ATTR("log", 200, 1, JimExprOpDoubleUnary, OP_FUNC), + OPRINIT_ATTR("log10", 200, 1, JimExprOpDoubleUnary, OP_FUNC), + OPRINIT_ATTR("sqrt", 200, 1, JimExprOpDoubleUnary, OP_FUNC), + OPRINIT_ATTR("pow", 200, 2, JimExprOpBin, OP_FUNC), + OPRINIT_ATTR("hypot", 200, 2, JimExprOpBin, OP_FUNC), + OPRINIT_ATTR("fmod", 200, 2, JimExprOpBin, OP_FUNC), +#endif +}; +#undef OPRINIT +#undef OPRINIT_ATTR + +#define JIM_EXPR_OPERATORS_NUM \ + (sizeof(Jim_ExprOperators)/sizeof(struct Jim_ExprOperator)) + +static int JimParseExpression(struct JimParserCtx *pc) +{ + + while (isspace(UCHAR(*pc->p)) || (*(pc->p) == '\\' && *(pc->p + 1) == '\n')) { + if (*pc->p == '\n') { + pc->linenr++; + } + pc->p++; + pc->len--; + } + + + pc->tline = pc->linenr; + pc->tstart = pc->p; + + if (pc->len == 0) { + pc->tend = pc->p; + pc->tt = JIM_TT_EOL; + pc->eof = 1; + return JIM_OK; + } + switch (*(pc->p)) { + case '(': + pc->tt = JIM_TT_SUBEXPR_START; + goto singlechar; + case ')': + pc->tt = JIM_TT_SUBEXPR_END; + goto singlechar; + case ',': + pc->tt = JIM_TT_SUBEXPR_COMMA; +singlechar: + pc->tend = pc->p; + pc->p++; + pc->len--; + break; + case '[': + return JimParseCmd(pc); + case '$': + if (JimParseVar(pc) == JIM_ERR) + return JimParseExprOperator(pc); + else { + + if (pc->tt == JIM_TT_EXPRSUGAR) { + return JIM_ERR; + } + return JIM_OK; + } + break; + case '0': + case '1': + case '2': + case '3': + case '4': + case '5': + case '6': + case '7': + case '8': + case '9': + case '.': + return JimParseExprNumber(pc); + case '"': + return JimParseQuote(pc); + case '{': + return JimParseBrace(pc); + + case 'N': + case 'I': + case 'n': + case 'i': + if (JimParseExprIrrational(pc) == JIM_ERR) + if (JimParseExprBoolean(pc) == JIM_ERR) + return JimParseExprOperator(pc); + break; + case 't': + case 'f': + case 'o': + case 'y': + if (JimParseExprBoolean(pc) == JIM_ERR) + return JimParseExprOperator(pc); + break; + default: + return JimParseExprOperator(pc); + break; + } + return JIM_OK; +} + +static int JimParseExprNumber(struct JimParserCtx *pc) +{ + char *end; + + + pc->tt = JIM_TT_EXPR_INT; + + jim_strtoull(pc->p, (char **)&pc->p); + + if (strchr("eENnIi.", *pc->p) || pc->p == pc->tstart) { + if (strtod(pc->tstart, &end)) { } + if (end == pc->tstart) + return JIM_ERR; + if (end > pc->p) { + + pc->tt = JIM_TT_EXPR_DOUBLE; + pc->p = end; + } + } + pc->tend = pc->p - 1; + pc->len -= (pc->p - pc->tstart); + return JIM_OK; +} + +static int JimParseExprIrrational(struct JimParserCtx *pc) +{ + const char *irrationals[] = { "NaN", "nan", "NAN", "Inf", "inf", "INF", NULL }; + int i; + + for (i = 0; irrationals[i]; i++) { + const char *irr = irrationals[i]; + + if (strncmp(irr, pc->p, 3) == 0) { + pc->p += 3; + pc->len -= 3; + pc->tend = pc->p - 1; + pc->tt = JIM_TT_EXPR_DOUBLE; + return JIM_OK; + } + } + return JIM_ERR; +} + +static int JimParseExprBoolean(struct JimParserCtx *pc) +{ + const char *booleans[] = { "false", "no", "off", "true", "yes", "on", NULL }; + const int lengths[] = { 5, 2, 3, 4, 3, 2, 0 }; + int i; + + for (i = 0; booleans[i]; i++) { + const char *boolean = booleans[i]; + int length = lengths[i]; + + if (strncmp(boolean, pc->p, length) == 0) { + pc->p += length; + pc->len -= length; + pc->tend = pc->p - 1; + pc->tt = JIM_TT_EXPR_BOOLEAN; + return JIM_OK; + } + } + return JIM_ERR; +} + +static const struct Jim_ExprOperator *JimExprOperatorInfoByOpcode(int opcode) +{ + static Jim_ExprOperator dummy_op; + if (opcode < JIM_TT_EXPR_OP) { + return &dummy_op; + } + return &Jim_ExprOperators[opcode - JIM_TT_EXPR_OP]; +} + +static int JimParseExprOperator(struct JimParserCtx *pc) +{ + int i; + const struct Jim_ExprOperator *bestOp = NULL; + int bestLen = 0; + + + for (i = 0; i < (signed)JIM_EXPR_OPERATORS_NUM; i++) { + const struct Jim_ExprOperator *op = &Jim_ExprOperators[i]; + + if (op->name[0] != pc->p[0]) { + continue; + } + + if (op->namelen > bestLen && strncmp(op->name, pc->p, op->namelen) == 0) { + bestOp = op; + bestLen = op->namelen; + } + } + if (bestOp == NULL) { + return JIM_ERR; + } + + + if (bestOp->attr & OP_FUNC) { + const char *p = pc->p + bestLen; + int len = pc->len - bestLen; + + while (len && isspace(UCHAR(*p))) { + len--; + p++; + } + if (*p != '(') { + return JIM_ERR; + } + } + pc->tend = pc->p + bestLen - 1; + pc->p += bestLen; + pc->len -= bestLen; + + pc->tt = (bestOp - Jim_ExprOperators) + JIM_TT_EXPR_OP; + return JIM_OK; +} + +const char *jim_tt_name(int type) +{ + static const char * const tt_names[JIM_TT_EXPR_OP] = + { "NIL", "STR", "ESC", "VAR", "ARY", "CMD", "SEP", "EOL", "EOF", "LIN", "WRD", "(((", ")))", ",,,", "INT", + "DBL", "BOO", "$()" }; + if (type < JIM_TT_EXPR_OP) { + return tt_names[type]; + } + else if (type == JIM_EXPROP_UNARYMINUS) { + return "-VE"; + } + else if (type == JIM_EXPROP_UNARYPLUS) { + return "+VE"; + } + else { + const struct Jim_ExprOperator *op = JimExprOperatorInfoByOpcode(type); + static char buf[20]; + + if (op->name) { + return op->name; + } + sprintf(buf, "(%d)", type); + return buf; + } +} + +static void FreeExprInternalRep(Jim_Interp *interp, Jim_Obj *objPtr); +static void DupExprInternalRep(Jim_Interp *interp, Jim_Obj *srcPtr, Jim_Obj *dupPtr); +static int SetExprFromAny(Jim_Interp *interp, struct Jim_Obj *objPtr); + +static const Jim_ObjType exprObjType = { + "expression", + FreeExprInternalRep, + DupExprInternalRep, + NULL, + JIM_TYPE_REFERENCES, +}; + + +struct ExprTree +{ + struct JimExprNode *expr; + struct JimExprNode *nodes; + int len; + int inUse; +}; + +static void ExprTreeFreeNodes(Jim_Interp *interp, struct JimExprNode *nodes, int num) +{ + int i; + for (i = 0; i < num; i++) { + if (nodes[i].objPtr) { + Jim_DecrRefCount(interp, nodes[i].objPtr); + } + } + Jim_Free(nodes); +} + +static void ExprTreeFree(Jim_Interp *interp, struct ExprTree *expr) +{ + ExprTreeFreeNodes(interp, expr->nodes, expr->len); + Jim_Free(expr); +} + +static void FreeExprInternalRep(Jim_Interp *interp, Jim_Obj *objPtr) +{ + struct ExprTree *expr = (void *)objPtr->internalRep.ptr; + + if (expr) { + if (--expr->inUse != 0) { + return; + } + + ExprTreeFree(interp, expr); + } +} + +static void DupExprInternalRep(Jim_Interp *interp, Jim_Obj *srcPtr, Jim_Obj *dupPtr) +{ + JIM_NOTUSED(interp); + JIM_NOTUSED(srcPtr); + + + dupPtr->typePtr = NULL; +} + +struct ExprBuilder { + int parencount; + int level; + ParseToken *token; + ParseToken *first_token; + Jim_Stack stack; + Jim_Obj *exprObjPtr; + Jim_Obj *fileNameObj; + struct JimExprNode *nodes; + struct JimExprNode *next; +}; + +#ifdef DEBUG_SHOW_EXPR +static void JimShowExprNode(struct JimExprNode *node, int level) +{ + int i; + for (i = 0; i < level; i++) { + printf(" "); + } + if (TOKEN_IS_EXPR_OP(node->type)) { + printf("%s\n", jim_tt_name(node->type)); + if (node->left) { + JimShowExprNode(node->left, level + 1); + } + if (node->right) { + JimShowExprNode(node->right, level + 1); + } + if (node->ternary) { + JimShowExprNode(node->ternary, level + 1); + } + } + else { + printf("[%s] %s\n", jim_tt_name(node->type), Jim_String(node->objPtr)); + } +} +#endif + +#define EXPR_UNTIL_CLOSE 0x0001 +#define EXPR_FUNC_ARGS 0x0002 +#define EXPR_TERNARY 0x0004 + +static int ExprTreeBuildTree(Jim_Interp *interp, struct ExprBuilder *builder, int precedence, int flags, int exp_numterms) +{ + int rc; + struct JimExprNode *node; + + int exp_stacklen = builder->stack.len + exp_numterms; + + if (builder->level++ > 200) { + Jim_SetResultString(interp, "Expression too complex", -1); + return JIM_ERR; + } + + while (builder->token->type != JIM_TT_EOL) { + ParseToken *t = builder->token++; + int prevtt; + + if (t == builder->first_token) { + prevtt = JIM_TT_NONE; + } + else { + prevtt = t[-1].type; + } + + if (t->type == JIM_TT_SUBEXPR_START) { + if (builder->stack.len == exp_stacklen) { + Jim_SetResultFormatted(interp, "unexpected open parenthesis in expression: \"%#s\"", builder->exprObjPtr); + return JIM_ERR; + } + builder->parencount++; + rc = ExprTreeBuildTree(interp, builder, 0, EXPR_UNTIL_CLOSE, 1); + if (rc != JIM_OK) { + return rc; + } + + } + else if (t->type == JIM_TT_SUBEXPR_END) { + if (!(flags & EXPR_UNTIL_CLOSE)) { + if (builder->stack.len == exp_stacklen && builder->level > 1) { + builder->token--; + builder->level--; + return JIM_OK; + } + Jim_SetResultFormatted(interp, "unexpected closing parenthesis in expression: \"%#s\"", builder->exprObjPtr); + return JIM_ERR; + } + builder->parencount--; + if (builder->stack.len == exp_stacklen) { + + break; + } + } + else if (t->type == JIM_TT_SUBEXPR_COMMA) { + if (!(flags & EXPR_FUNC_ARGS)) { + if (builder->stack.len == exp_stacklen) { + + builder->token--; + builder->level--; + return JIM_OK; + } + Jim_SetResultFormatted(interp, "unexpected comma in expression: \"%#s\"", builder->exprObjPtr); + return JIM_ERR; + } + else { + + if (builder->stack.len > exp_stacklen) { + Jim_SetResultFormatted(interp, "too many arguments to math function"); + return JIM_ERR; + } + } + + } + else if (t->type == JIM_EXPROP_COLON) { + if (!(flags & EXPR_TERNARY)) { + if (builder->level != 1) { + + builder->token--; + builder->level--; + return JIM_OK; + } + Jim_SetResultFormatted(interp, ": without ? in expression: \"%#s\"", builder->exprObjPtr); + return JIM_ERR; + } + if (builder->stack.len == exp_stacklen) { + + builder->token--; + builder->level--; + return JIM_OK; + } + + } + else if (TOKEN_IS_EXPR_OP(t->type)) { + const struct Jim_ExprOperator *op; + + + if (TOKEN_IS_EXPR_OP(prevtt) || TOKEN_IS_EXPR_START(prevtt)) { + if (t->type == JIM_EXPROP_SUB) { + t->type = JIM_EXPROP_UNARYMINUS; + } + else if (t->type == JIM_EXPROP_ADD) { + t->type = JIM_EXPROP_UNARYPLUS; + } + } + + op = JimExprOperatorInfoByOpcode(t->type); + + if (op->precedence < precedence || (!(op->attr & OP_RIGHT_ASSOC) && op->precedence == precedence)) { + + builder->token--; + break; + } + + if (op->attr & OP_FUNC) { + if (builder->token->type != JIM_TT_SUBEXPR_START) { + Jim_SetResultString(interp, "missing arguments for math function", -1); + return JIM_ERR; + } + builder->token++; + if (op->arity == 0) { + if (builder->token->type != JIM_TT_SUBEXPR_END) { + Jim_SetResultString(interp, "too many arguments for math function", -1); + return JIM_ERR; + } + builder->token++; + goto noargs; + } + builder->parencount++; + + + rc = ExprTreeBuildTree(interp, builder, 0, EXPR_FUNC_ARGS | EXPR_UNTIL_CLOSE, op->arity); + } + else if (t->type == JIM_EXPROP_TERNARY) { + + rc = ExprTreeBuildTree(interp, builder, op->precedence, EXPR_TERNARY, 2); + } + else { + rc = ExprTreeBuildTree(interp, builder, op->precedence, 0, 1); + } + + if (rc != JIM_OK) { + return rc; + } + +noargs: + node = builder->next++; + node->type = t->type; + + if (op->arity >= 3) { + node->ternary = Jim_StackPop(&builder->stack); + if (node->ternary == NULL) { + goto missingoperand; + } + } + if (op->arity >= 2) { + node->right = Jim_StackPop(&builder->stack); + if (node->right == NULL) { + goto missingoperand; + } + } + if (op->arity >= 1) { + node->left = Jim_StackPop(&builder->stack); + if (node->left == NULL) { +missingoperand: + Jim_SetResultFormatted(interp, "missing operand to %s in expression: \"%#s\"", op->name, builder->exprObjPtr); + builder->next--; + return JIM_ERR; + + } + } + + + Jim_StackPush(&builder->stack, node); + } + else { + Jim_Obj *objPtr = NULL; + + + + + if (!TOKEN_IS_EXPR_START(prevtt) && !TOKEN_IS_EXPR_OP(prevtt)) { + Jim_SetResultFormatted(interp, "missing operator in expression: \"%#s\"", builder->exprObjPtr); + return JIM_ERR; + } + + + if (t->type == JIM_TT_EXPR_INT || t->type == JIM_TT_EXPR_DOUBLE) { + char *endptr; + if (t->type == JIM_TT_EXPR_INT) { + objPtr = Jim_NewIntObj(interp, jim_strtoull(t->token, &endptr)); + } + else { + objPtr = Jim_NewDoubleObj(interp, strtod(t->token, &endptr)); + } + if (endptr != t->token + t->len) { + + Jim_FreeNewObj(interp, objPtr); + objPtr = NULL; + } + } + + if (!objPtr) { + + objPtr = Jim_NewStringObj(interp, t->token, t->len); + if (t->type == JIM_TT_CMD) { + + JimSetSourceInfo(interp, objPtr, builder->fileNameObj, t->line); + } + } + + + node = builder->next++; + node->objPtr = objPtr; + Jim_IncrRefCount(node->objPtr); + node->type = t->type; + Jim_StackPush(&builder->stack, node); + } + } + + if (builder->stack.len == exp_stacklen) { + builder->level--; + return JIM_OK; + } + + if ((flags & EXPR_FUNC_ARGS)) { + Jim_SetResultFormatted(interp, "too %s arguments for math function", (builder->stack.len < exp_stacklen) ? "few" : "many"); + } + else { + if (builder->stack.len < exp_stacklen) { + if (builder->level == 0) { + Jim_SetResultFormatted(interp, "empty expression"); + } + else { + Jim_SetResultFormatted(interp, "syntax error in expression \"%#s\": premature end of expression", builder->exprObjPtr); + } + } + else { + Jim_SetResultFormatted(interp, "extra terms after expression"); + } + } + + return JIM_ERR; +} + +static struct ExprTree *ExprTreeCreateTree(Jim_Interp *interp, const ParseTokenList *tokenlist, Jim_Obj *exprObjPtr, Jim_Obj *fileNameObj) +{ + struct ExprTree *expr; + struct ExprBuilder builder; + int rc; + struct JimExprNode *top = NULL; + + builder.parencount = 0; + builder.level = 0; + builder.token = builder.first_token = tokenlist->list; + builder.exprObjPtr = exprObjPtr; + builder.fileNameObj = fileNameObj; + + builder.nodes = malloc(sizeof(struct JimExprNode) * (tokenlist->count - 1)); + memset(builder.nodes, 0, sizeof(struct JimExprNode) * (tokenlist->count - 1)); + builder.next = builder.nodes; + Jim_InitStack(&builder.stack); + + rc = ExprTreeBuildTree(interp, &builder, 0, 0, 1); + + if (rc == JIM_OK) { + top = Jim_StackPop(&builder.stack); + + if (builder.parencount) { + Jim_SetResultString(interp, "missing close parenthesis", -1); + rc = JIM_ERR; + } + } + + + Jim_FreeStack(&builder.stack); + + if (rc != JIM_OK) { + ExprTreeFreeNodes(interp, builder.nodes, builder.next - builder.nodes); + return NULL; + } + + expr = Jim_Alloc(sizeof(*expr)); + expr->inUse = 1; + expr->expr = top; + expr->nodes = builder.nodes; + expr->len = builder.next - builder.nodes; + + assert(expr->len <= tokenlist->count - 1); + + return expr; +} + +static int SetExprFromAny(Jim_Interp *interp, struct Jim_Obj *objPtr) +{ + int exprTextLen; + const char *exprText; + struct JimParserCtx parser; + struct ExprTree *expr; + ParseTokenList tokenlist; + int line; + Jim_Obj *fileNameObj; + int rc = JIM_ERR; + + + if (objPtr->typePtr == &sourceObjType) { + fileNameObj = objPtr->internalRep.sourceValue.fileNameObj; + line = objPtr->internalRep.sourceValue.lineNumber; + } + else { + fileNameObj = interp->emptyObj; + line = 1; + } + Jim_IncrRefCount(fileNameObj); + + exprText = Jim_GetString(objPtr, &exprTextLen); + + + ScriptTokenListInit(&tokenlist); + + JimParserInit(&parser, exprText, exprTextLen, line); + while (!parser.eof) { + if (JimParseExpression(&parser) != JIM_OK) { + ScriptTokenListFree(&tokenlist); + Jim_SetResultFormatted(interp, "syntax error in expression: \"%#s\"", objPtr); + expr = NULL; + goto err; + } + + ScriptAddToken(&tokenlist, parser.tstart, parser.tend - parser.tstart + 1, parser.tt, + parser.tline); + } + +#ifdef DEBUG_SHOW_EXPR_TOKENS + { + int i; + printf("==== Expr Tokens (%s) ====\n", Jim_String(fileNameObj)); + for (i = 0; i < tokenlist.count; i++) { + printf("[%2d]@%d %s '%.*s'\n", i, tokenlist.list[i].line, jim_tt_name(tokenlist.list[i].type), + tokenlist.list[i].len, tokenlist.list[i].token); + } + } +#endif + + if (JimParseCheckMissing(interp, parser.missing.ch) == JIM_ERR) { + ScriptTokenListFree(&tokenlist); + Jim_DecrRefCount(interp, fileNameObj); + return JIM_ERR; + } + + + expr = ExprTreeCreateTree(interp, &tokenlist, objPtr, fileNameObj); + + + ScriptTokenListFree(&tokenlist); + + if (!expr) { + goto err; + } + +#ifdef DEBUG_SHOW_EXPR + printf("==== Expr ====\n"); + JimShowExprNode(expr->expr, 0); +#endif + + rc = JIM_OK; + + err: + + Jim_DecrRefCount(interp, fileNameObj); + Jim_FreeIntRep(interp, objPtr); + Jim_SetIntRepPtr(objPtr, expr); + objPtr->typePtr = &exprObjType; + return rc; +} + +static struct ExprTree *JimGetExpression(Jim_Interp *interp, Jim_Obj *objPtr) +{ + if (objPtr->typePtr != &exprObjType) { + if (SetExprFromAny(interp, objPtr) != JIM_OK) { + return NULL; + } + } + return (struct ExprTree *) Jim_GetIntRepPtr(objPtr); +} + +#ifdef JIM_OPTIMIZATION +static Jim_Obj *JimExprIntValOrVar(Jim_Interp *interp, struct JimExprNode *node) +{ + if (node->type == JIM_TT_EXPR_INT) + return node->objPtr; + else if (node->type == JIM_TT_VAR) + return Jim_GetVariable(interp, node->objPtr, JIM_NONE); + else if (node->type == JIM_TT_DICTSUGAR) + return JimExpandDictSugar(interp, node->objPtr); + else + return NULL; +} +#endif + + +static int JimExprEvalTermNode(Jim_Interp *interp, struct JimExprNode *node) +{ + if (TOKEN_IS_EXPR_OP(node->type)) { + const struct Jim_ExprOperator *op = JimExprOperatorInfoByOpcode(node->type); + return op->funcop(interp, node); + } + else { + Jim_Obj *objPtr; + + + switch (node->type) { + case JIM_TT_EXPR_INT: + case JIM_TT_EXPR_DOUBLE: + case JIM_TT_EXPR_BOOLEAN: + case JIM_TT_STR: + Jim_SetResult(interp, node->objPtr); + return JIM_OK; + + case JIM_TT_VAR: + objPtr = Jim_GetVariable(interp, node->objPtr, JIM_ERRMSG); + if (objPtr) { + Jim_SetResult(interp, objPtr); + return JIM_OK; + } + return JIM_ERR; + + case JIM_TT_DICTSUGAR: + objPtr = JimExpandDictSugar(interp, node->objPtr); + if (objPtr) { + Jim_SetResult(interp, objPtr); + return JIM_OK; + } + return JIM_ERR; + + case JIM_TT_ESC: + if (Jim_SubstObj(interp, node->objPtr, &objPtr, JIM_NONE) == JIM_OK) { + Jim_SetResult(interp, objPtr); + return JIM_OK; + } + return JIM_ERR; + + case JIM_TT_CMD: + return Jim_EvalObj(interp, node->objPtr); + + default: + + return JIM_ERR; + } + } +} + +static int JimExprGetTerm(Jim_Interp *interp, struct JimExprNode *node, Jim_Obj **objPtrPtr) +{ + int rc = JimExprEvalTermNode(interp, node); + if (rc == JIM_OK) { + *objPtrPtr = Jim_GetResult(interp); + Jim_IncrRefCount(*objPtrPtr); + } + return rc; +} + +static int JimExprGetTermBoolean(Jim_Interp *interp, struct JimExprNode *node) +{ + if (JimExprEvalTermNode(interp, node) == JIM_OK) { + return ExprBool(interp, Jim_GetResult(interp)); + } + return -1; +} + +int Jim_EvalExpression(Jim_Interp *interp, Jim_Obj *exprObjPtr) +{ + struct ExprTree *expr; + int retcode = JIM_OK; + + expr = JimGetExpression(interp, exprObjPtr); + if (!expr) { + return JIM_ERR; + } + +#ifdef JIM_OPTIMIZATION + { + Jim_Obj *objPtr; + + + switch (expr->len) { + case 1: + objPtr = JimExprIntValOrVar(interp, expr->expr); + if (objPtr) { + Jim_SetResult(interp, objPtr); + return JIM_OK; + } + break; + + case 2: + if (expr->expr->type == JIM_EXPROP_NOT) { + objPtr = JimExprIntValOrVar(interp, expr->expr->left); + + if (objPtr && JimIsWide(objPtr)) { + Jim_SetResult(interp, JimWideValue(objPtr) ? interp->falseObj : interp->trueObj); + return JIM_OK; + } + } + break; + + case 3: + objPtr = JimExprIntValOrVar(interp, expr->expr->left); + if (objPtr && JimIsWide(objPtr)) { + Jim_Obj *objPtr2 = JimExprIntValOrVar(interp, expr->expr->right); + if (objPtr2 && JimIsWide(objPtr2)) { + jim_wide wideValueA = JimWideValue(objPtr); + jim_wide wideValueB = JimWideValue(objPtr2); + int cmpRes; + switch (expr->expr->type) { + case JIM_EXPROP_LT: + cmpRes = wideValueA < wideValueB; + break; + case JIM_EXPROP_LTE: + cmpRes = wideValueA <= wideValueB; + break; + case JIM_EXPROP_GT: + cmpRes = wideValueA > wideValueB; + break; + case JIM_EXPROP_GTE: + cmpRes = wideValueA >= wideValueB; + break; + case JIM_EXPROP_NUMEQ: + cmpRes = wideValueA == wideValueB; + break; + case JIM_EXPROP_NUMNE: + cmpRes = wideValueA != wideValueB; + break; + default: + goto noopt; + } + Jim_SetResult(interp, cmpRes ? interp->trueObj : interp->falseObj); + return JIM_OK; + } + } + break; + } + } +noopt: +#endif + + expr->inUse++; + + + retcode = JimExprEvalTermNode(interp, expr->expr); + + expr->inUse--; + + return retcode; +} + +int Jim_GetBoolFromExpr(Jim_Interp *interp, Jim_Obj *exprObjPtr, int *boolPtr) +{ + int retcode = Jim_EvalExpression(interp, exprObjPtr); + + if (retcode == JIM_OK) { + switch (ExprBool(interp, Jim_GetResult(interp))) { + case 0: + *boolPtr = 0; + break; + + case 1: + *boolPtr = 1; + break; + + case -1: + retcode = JIM_ERR; + break; + } + } + return retcode; +} + + + + +typedef struct ScanFmtPartDescr +{ + const char *arg; + const char *prefix; + size_t width; + int pos; + char type; + char modifier; +} ScanFmtPartDescr; + + +typedef struct ScanFmtStringObj +{ + jim_wide size; + char *stringRep; + size_t count; + size_t convCount; + size_t maxPos; + const char *error; + char *scratch; + ScanFmtPartDescr descr[1]; +} ScanFmtStringObj; + + +static void FreeScanFmtInternalRep(Jim_Interp *interp, Jim_Obj *objPtr); +static void DupScanFmtInternalRep(Jim_Interp *interp, Jim_Obj *srcPtr, Jim_Obj *dupPtr); +static void UpdateStringOfScanFmt(Jim_Obj *objPtr); + +static const Jim_ObjType scanFmtStringObjType = { + "scanformatstring", + FreeScanFmtInternalRep, + DupScanFmtInternalRep, + UpdateStringOfScanFmt, + JIM_TYPE_NONE, +}; + +void FreeScanFmtInternalRep(Jim_Interp *interp, Jim_Obj *objPtr) +{ + JIM_NOTUSED(interp); + Jim_Free((char *)objPtr->internalRep.ptr); + objPtr->internalRep.ptr = 0; +} + +void DupScanFmtInternalRep(Jim_Interp *interp, Jim_Obj *srcPtr, Jim_Obj *dupPtr) +{ + size_t size = (size_t) ((ScanFmtStringObj *) srcPtr->internalRep.ptr)->size; + ScanFmtStringObj *newVec = (ScanFmtStringObj *) Jim_Alloc(size); + + JIM_NOTUSED(interp); + memcpy(newVec, srcPtr->internalRep.ptr, size); + dupPtr->internalRep.ptr = newVec; + dupPtr->typePtr = &scanFmtStringObjType; +} + +static void UpdateStringOfScanFmt(Jim_Obj *objPtr) +{ + JimSetStringBytes(objPtr, ((ScanFmtStringObj *) objPtr->internalRep.ptr)->stringRep); +} + + +static int SetScanFmtFromAny(Jim_Interp *interp, Jim_Obj *objPtr) +{ + ScanFmtStringObj *fmtObj; + char *buffer; + int maxCount, i, approxSize, lastPos = -1; + const char *fmt = Jim_String(objPtr); + int maxFmtLen = Jim_Length(objPtr); + const char *fmtEnd = fmt + maxFmtLen; + int curr; + + Jim_FreeIntRep(interp, objPtr); + + for (i = 0, maxCount = 0; i < maxFmtLen; ++i) + if (fmt[i] == '%') + ++maxCount; + + approxSize = sizeof(ScanFmtStringObj) + +(maxCount + 1) * sizeof(ScanFmtPartDescr) + +maxFmtLen * sizeof(char) + 3 + 1 + + maxFmtLen * sizeof(char) + 1 + + maxFmtLen * sizeof(char) + +(maxCount + 1) * sizeof(char) + +1; + fmtObj = (ScanFmtStringObj *) Jim_Alloc(approxSize); + memset(fmtObj, 0, approxSize); + fmtObj->size = approxSize; + fmtObj->maxPos = 0; + fmtObj->scratch = (char *)&fmtObj->descr[maxCount + 1]; + fmtObj->stringRep = fmtObj->scratch + maxFmtLen + 3 + 1; + memcpy(fmtObj->stringRep, fmt, maxFmtLen); + buffer = fmtObj->stringRep + maxFmtLen + 1; + objPtr->internalRep.ptr = fmtObj; + objPtr->typePtr = &scanFmtStringObjType; + for (i = 0, curr = 0; fmt < fmtEnd; ++fmt) { + int width = 0, skip; + ScanFmtPartDescr *descr = &fmtObj->descr[curr]; + + fmtObj->count++; + descr->width = 0; + + if (*fmt != '%' || fmt[1] == '%') { + descr->type = 0; + descr->prefix = &buffer[i]; + for (; fmt < fmtEnd; ++fmt) { + if (*fmt == '%') { + if (fmt[1] != '%') + break; + ++fmt; + } + buffer[i++] = *fmt; + } + buffer[i++] = 0; + } + + ++fmt; + + if (fmt >= fmtEnd) + goto done; + descr->pos = 0; + if (*fmt == '*') { + descr->pos = -1; + ++fmt; + } + else + fmtObj->convCount++; + + if (sscanf(fmt, "%d%n", &width, &skip) == 1) { + fmt += skip; + + if (descr->pos != -1 && *fmt == '$') { + int prev; + + ++fmt; + descr->pos = width; + width = 0; + + if ((lastPos == 0 && descr->pos > 0) + || (lastPos > 0 && descr->pos == 0)) { + fmtObj->error = "cannot mix \"%\" and \"%n$\" conversion specifiers"; + return JIM_ERR; + } + + for (prev = 0; prev < curr; ++prev) { + if (fmtObj->descr[prev].pos == -1) + continue; + if (fmtObj->descr[prev].pos == descr->pos) { + fmtObj->error = + "variable is assigned by multiple \"%n$\" conversion specifiers"; + return JIM_ERR; + } + } + if (descr->pos < 0) { + fmtObj->error = + "\"%n$\" conversion specifier is negative"; + return JIM_ERR; + } + + if (sscanf(fmt, "%d%n", &width, &skip) == 1) { + descr->width = width; + fmt += skip; + } + if (descr->pos > 0 && (size_t) descr->pos > fmtObj->maxPos) + fmtObj->maxPos = descr->pos; + } + else { + + descr->width = width; + } + } + + if (lastPos == -1) + lastPos = descr->pos; + + if (*fmt == '[') { + int swapped = 1, beg = i, end, j; + + descr->type = '['; + descr->arg = &buffer[i]; + ++fmt; + if (*fmt == '^') + buffer[i++] = *fmt++; + if (*fmt == ']') + buffer[i++] = *fmt++; + while (*fmt && *fmt != ']') + buffer[i++] = *fmt++; + if (*fmt != ']') { + fmtObj->error = "unmatched [ in format string"; + return JIM_ERR; + } + end = i; + buffer[i++] = 0; + + while (swapped) { + swapped = 0; + for (j = beg + 1; j < end - 1; ++j) { + if (buffer[j] == '-' && buffer[j - 1] > buffer[j + 1]) { + char tmp = buffer[j - 1]; + + buffer[j - 1] = buffer[j + 1]; + buffer[j + 1] = tmp; + swapped = 1; + } + } + } + } + else { + + if (fmt < fmtEnd && strchr("hlL", *fmt)) + descr->modifier = tolower((int)*fmt++); + + if (fmt >= fmtEnd) { + fmtObj->error = "missing scan conversion character"; + return JIM_ERR; + } + + descr->type = *fmt; + if (strchr("efgcsndoxui", *fmt) == 0) { + fmtObj->error = "bad scan conversion character"; + return JIM_ERR; + } + else if (*fmt == 'c' && descr->width != 0) { + fmtObj->error = "field width may not be specified in %c " "conversion"; + return JIM_ERR; + } + else if (*fmt == 'u' && descr->modifier == 'l') { + fmtObj->error = "unsigned wide not supported"; + return JIM_ERR; + } + } + curr++; + } + done: + return JIM_OK; +} + + + +#define FormatGetCnvCount(_fo_) \ + ((ScanFmtStringObj*)((_fo_)->internalRep.ptr))->convCount +#define FormatGetMaxPos(_fo_) \ + ((ScanFmtStringObj*)((_fo_)->internalRep.ptr))->maxPos +#define FormatGetError(_fo_) \ + ((ScanFmtStringObj*)((_fo_)->internalRep.ptr))->error + +static Jim_Obj *JimScanAString(Jim_Interp *interp, const char *sdescr, const char *str) +{ + char *buffer = Jim_StrDup(str); + char *p = buffer; + + while (*str) { + int c; + int n; + + if (!sdescr && isspace(UCHAR(*str))) + break; + + n = utf8_tounicode(str, &c); + if (sdescr && !JimCharsetMatch(sdescr, c, JIM_CHARSET_SCAN)) + break; + while (n--) + *p++ = *str++; + } + *p = 0; + return Jim_NewStringObjNoAlloc(interp, buffer, p - buffer); +} + + +static int ScanOneEntry(Jim_Interp *interp, const char *str, int pos, int strLen, + ScanFmtStringObj * fmtObj, long idx, Jim_Obj **valObjPtr) +{ + const char *tok; + const ScanFmtPartDescr *descr = &fmtObj->descr[idx]; + size_t scanned = 0; + size_t anchor = pos; + int i; + Jim_Obj *tmpObj = NULL; + + + *valObjPtr = 0; + if (descr->prefix) { + for (i = 0; pos < strLen && descr->prefix[i]; ++i) { + + if (isspace(UCHAR(descr->prefix[i]))) + while (pos < strLen && isspace(UCHAR(str[pos]))) + ++pos; + else if (descr->prefix[i] != str[pos]) + break; + else + ++pos; + } + if (pos >= strLen) { + return -1; + } + else if (descr->prefix[i] != 0) + return 0; + } + + if (descr->type != 'c' && descr->type != '[' && descr->type != 'n') + while (isspace(UCHAR(str[pos]))) + ++pos; + + scanned = pos - anchor; + + + if (descr->type == 'n') { + + *valObjPtr = Jim_NewIntObj(interp, anchor + scanned); + } + else if (pos >= strLen) { + + return -1; + } + else if (descr->type == 'c') { + int c; + scanned += utf8_tounicode(&str[pos], &c); + *valObjPtr = Jim_NewIntObj(interp, c); + return scanned; + } + else { + + if (descr->width > 0) { + size_t sLen = utf8_strlen(&str[pos], strLen - pos); + size_t tLen = descr->width > sLen ? sLen : descr->width; + + tmpObj = Jim_NewStringObjUtf8(interp, str + pos, tLen); + tok = tmpObj->bytes; + } + else { + + tok = &str[pos]; + } + switch (descr->type) { + case 'd': + case 'o': + case 'x': + case 'u': + case 'i':{ + char *endp; + jim_wide w; + + int base = descr->type == 'o' ? 8 + : descr->type == 'x' ? 16 : descr->type == 'i' ? 0 : 10; + + + if (base == 0) { + w = jim_strtoull(tok, &endp); + } + else { + w = strtoull(tok, &endp, base); + } + + if (endp != tok) { + + *valObjPtr = Jim_NewIntObj(interp, w); + + + scanned += endp - tok; + } + else { + scanned = *tok ? 0 : -1; + } + break; + } + case 's': + case '[':{ + *valObjPtr = JimScanAString(interp, descr->arg, tok); + scanned += Jim_Length(*valObjPtr); + break; + } + case 'e': + case 'f': + case 'g':{ + char *endp; + double value = strtod(tok, &endp); + + if (endp != tok) { + + *valObjPtr = Jim_NewDoubleObj(interp, value); + + scanned += endp - tok; + } + else { + scanned = *tok ? 0 : -1; + } + break; + } + } + if (tmpObj) { + Jim_FreeNewObj(interp, tmpObj); + } + } + return scanned; +} + + +Jim_Obj *Jim_ScanString(Jim_Interp *interp, Jim_Obj *strObjPtr, Jim_Obj *fmtObjPtr, int flags) +{ + size_t i, pos; + int scanned = 1; + const char *str = Jim_String(strObjPtr); + int strLen = Jim_Utf8Length(interp, strObjPtr); + Jim_Obj *resultList = 0; + Jim_Obj **resultVec = 0; + int resultc; + Jim_Obj *emptyStr = 0; + ScanFmtStringObj *fmtObj; + + + JimPanic((fmtObjPtr->typePtr != &scanFmtStringObjType, "Jim_ScanString() for non-scan format")); + + fmtObj = (ScanFmtStringObj *) fmtObjPtr->internalRep.ptr; + + if (fmtObj->error != 0) { + if (flags & JIM_ERRMSG) + Jim_SetResultString(interp, fmtObj->error, -1); + return 0; + } + + emptyStr = Jim_NewEmptyStringObj(interp); + Jim_IncrRefCount(emptyStr); + + resultList = Jim_NewListObj(interp, NULL, 0); + if (fmtObj->maxPos > 0) { + for (i = 0; i < fmtObj->maxPos; ++i) + Jim_ListAppendElement(interp, resultList, emptyStr); + JimListGetElements(interp, resultList, &resultc, &resultVec); + } + + for (i = 0, pos = 0; i < fmtObj->count; ++i) { + ScanFmtPartDescr *descr = &(fmtObj->descr[i]); + Jim_Obj *value = 0; + + + if (descr->type == 0) + continue; + + if (scanned > 0) + scanned = ScanOneEntry(interp, str, pos, strLen, fmtObj, i, &value); + + if (scanned == -1 && i == 0) + goto eof; + + pos += scanned; + + + if (value == 0) + value = Jim_NewEmptyStringObj(interp); + + if (descr->pos == -1) { + Jim_FreeNewObj(interp, value); + } + else if (descr->pos == 0) + + Jim_ListAppendElement(interp, resultList, value); + else if (resultVec[descr->pos - 1] == emptyStr) { + + Jim_DecrRefCount(interp, resultVec[descr->pos - 1]); + Jim_IncrRefCount(value); + resultVec[descr->pos - 1] = value; + } + else { + + Jim_FreeNewObj(interp, value); + goto err; + } + } + Jim_DecrRefCount(interp, emptyStr); + return resultList; + eof: + Jim_DecrRefCount(interp, emptyStr); + Jim_FreeNewObj(interp, resultList); + return (Jim_Obj *)EOF; + err: + Jim_DecrRefCount(interp, emptyStr); + Jim_FreeNewObj(interp, resultList); + return 0; +} + + +static void JimPrngInit(Jim_Interp *interp) +{ +#define PRNG_SEED_SIZE 256 + int i; + unsigned int *seed; + time_t t = time(NULL); + + interp->prngState = Jim_Alloc(sizeof(Jim_PrngState)); + + seed = Jim_Alloc(PRNG_SEED_SIZE * sizeof(*seed)); + for (i = 0; i < PRNG_SEED_SIZE; i++) { + seed[i] = (rand() ^ t ^ clock()); + } + JimPrngSeed(interp, (unsigned char *)seed, PRNG_SEED_SIZE * sizeof(*seed)); + Jim_Free(seed); +} + + +static void JimRandomBytes(Jim_Interp *interp, void *dest, unsigned int len) +{ + Jim_PrngState *prng; + unsigned char *destByte = (unsigned char *)dest; + unsigned int si, sj, x; + + + if (interp->prngState == NULL) + JimPrngInit(interp); + prng = interp->prngState; + + for (x = 0; x < len; x++) { + prng->i = (prng->i + 1) & 0xff; + si = prng->sbox[prng->i]; + prng->j = (prng->j + si) & 0xff; + sj = prng->sbox[prng->j]; + prng->sbox[prng->i] = sj; + prng->sbox[prng->j] = si; + *destByte++ = prng->sbox[(si + sj) & 0xff]; + } +} + + +static void JimPrngSeed(Jim_Interp *interp, unsigned char *seed, int seedLen) +{ + int i; + Jim_PrngState *prng; + + + if (interp->prngState == NULL) + JimPrngInit(interp); + prng = interp->prngState; + + + for (i = 0; i < 256; i++) + prng->sbox[i] = i; + + for (i = 0; i < seedLen; i++) { + unsigned char t; + + t = prng->sbox[i & 0xFF]; + prng->sbox[i & 0xFF] = prng->sbox[seed[i]]; + prng->sbox[seed[i]] = t; + } + prng->i = prng->j = 0; + + for (i = 0; i < 256; i += seedLen) { + JimRandomBytes(interp, seed, seedLen); + } +} + + +static int Jim_IncrCoreCommand(Jim_Interp *interp, int argc, Jim_Obj *const *argv) +{ + jim_wide wideValue, increment = 1; + Jim_Obj *intObjPtr; + + if (argc != 2 && argc != 3) { + Jim_WrongNumArgs(interp, 1, argv, "varName ?increment?"); + return JIM_ERR; + } + if (argc == 3) { + if (Jim_GetWide(interp, argv[2], &increment) != JIM_OK) + return JIM_ERR; + } + intObjPtr = Jim_GetVariable(interp, argv[1], JIM_UNSHARED); + if (!intObjPtr) { + + wideValue = 0; + } + else if (Jim_GetWide(interp, intObjPtr, &wideValue) != JIM_OK) { + return JIM_ERR; + } + if (!intObjPtr || Jim_IsShared(intObjPtr)) { + intObjPtr = Jim_NewIntObj(interp, wideValue + increment); + if (Jim_SetVariable(interp, argv[1], intObjPtr) != JIM_OK) { + Jim_FreeNewObj(interp, intObjPtr); + return JIM_ERR; + } + } + else { + + Jim_InvalidateStringRep(intObjPtr); + JimWideValue(intObjPtr) = wideValue + increment; + + if (argv[1]->typePtr != &variableObjType) { + + Jim_SetVariable(interp, argv[1], intObjPtr); + } + } + Jim_SetResult(interp, intObjPtr); + return JIM_OK; +} + + +#define JIM_EVAL_SARGV_LEN 8 +#define JIM_EVAL_SINTV_LEN 8 + + +static int JimUnknown(Jim_Interp *interp, int argc, Jim_Obj *const *argv) +{ + int retcode; + + if (interp->unknown_called > 50) { + return JIM_ERR; + } + + + + if (Jim_GetCommand(interp, interp->unknown, JIM_NONE) == NULL) + return JIM_ERR; + + interp->unknown_called++; + + retcode = Jim_EvalObjPrefix(interp, interp->unknown, argc, argv); + interp->unknown_called--; + + return retcode; +} + +static int JimInvokeCommand(Jim_Interp *interp, int objc, Jim_Obj *const *objv) +{ + int retcode; + Jim_Cmd *cmdPtr; + void *prevPrivData; + +#if 0 + printf("invoke"); + int j; + for (j = 0; j < objc; j++) { + printf(" '%s'", Jim_String(objv[j])); + } + printf("\n"); +#endif + + if (interp->framePtr->tailcallCmd) { + + cmdPtr = interp->framePtr->tailcallCmd; + interp->framePtr->tailcallCmd = NULL; + } + else { + cmdPtr = Jim_GetCommand(interp, objv[0], JIM_ERRMSG); + if (cmdPtr == NULL) { + return JimUnknown(interp, objc, objv); + } + JimIncrCmdRefCount(cmdPtr); + } + + if (interp->evalDepth == interp->maxEvalDepth) { + Jim_SetResultString(interp, "Infinite eval recursion", -1); + retcode = JIM_ERR; + goto out; + } + interp->evalDepth++; + prevPrivData = interp->cmdPrivData; + + + Jim_SetEmptyResult(interp); + if (cmdPtr->isproc) { + retcode = JimCallProcedure(interp, cmdPtr, objc, objv); + } + else { + interp->cmdPrivData = cmdPtr->u.native.privData; + retcode = cmdPtr->u.native.cmdProc(interp, objc, objv); + } + interp->cmdPrivData = prevPrivData; + interp->evalDepth--; + +out: + JimDecrCmdRefCount(interp, cmdPtr); + + return retcode; +} + +int Jim_EvalObjVector(Jim_Interp *interp, int objc, Jim_Obj *const *objv) +{ + int i, retcode; + + + for (i = 0; i < objc; i++) + Jim_IncrRefCount(objv[i]); + + retcode = JimInvokeCommand(interp, objc, objv); + + + for (i = 0; i < objc; i++) + Jim_DecrRefCount(interp, objv[i]); + + return retcode; +} + +int Jim_EvalObjPrefix(Jim_Interp *interp, Jim_Obj *prefix, int objc, Jim_Obj *const *objv) +{ + int ret; + Jim_Obj **nargv = Jim_Alloc((objc + 1) * sizeof(*nargv)); + + nargv[0] = prefix; + memcpy(&nargv[1], &objv[0], sizeof(nargv[0]) * objc); + ret = Jim_EvalObjVector(interp, objc + 1, nargv); + Jim_Free(nargv); + return ret; +} + +static void JimAddErrorToStack(Jim_Interp *interp, ScriptObj *script) +{ + if (!interp->errorFlag) { + + interp->errorFlag = 1; + Jim_IncrRefCount(script->fileNameObj); + Jim_DecrRefCount(interp, interp->errorFileNameObj); + interp->errorFileNameObj = script->fileNameObj; + interp->errorLine = script->linenr; + + JimResetStackTrace(interp); + + interp->addStackTrace++; + } + + + if (interp->addStackTrace > 0) { + + + JimAppendStackTrace(interp, Jim_String(interp->errorProc), script->fileNameObj, script->linenr); + + if (Jim_Length(script->fileNameObj)) { + interp->addStackTrace = 0; + } + + Jim_DecrRefCount(interp, interp->errorProc); + interp->errorProc = interp->emptyObj; + Jim_IncrRefCount(interp->errorProc); + } +} + +static int JimSubstOneToken(Jim_Interp *interp, const ScriptToken *token, Jim_Obj **objPtrPtr) +{ + Jim_Obj *objPtr; + + switch (token->type) { + case JIM_TT_STR: + case JIM_TT_ESC: + objPtr = token->objPtr; + break; + case JIM_TT_VAR: + objPtr = Jim_GetVariable(interp, token->objPtr, JIM_ERRMSG); + break; + case JIM_TT_DICTSUGAR: + objPtr = JimExpandDictSugar(interp, token->objPtr); + break; + case JIM_TT_EXPRSUGAR: + objPtr = JimExpandExprSugar(interp, token->objPtr); + break; + case JIM_TT_CMD: + switch (Jim_EvalObj(interp, token->objPtr)) { + case JIM_OK: + case JIM_RETURN: + objPtr = interp->result; + break; + case JIM_BREAK: + + return JIM_BREAK; + case JIM_CONTINUE: + + return JIM_CONTINUE; + default: + return JIM_ERR; + } + break; + default: + JimPanic((1, + "default token type (%d) reached " "in Jim_SubstObj().", token->type)); + objPtr = NULL; + break; + } + if (objPtr) { + *objPtrPtr = objPtr; + return JIM_OK; + } + return JIM_ERR; +} + +static Jim_Obj *JimInterpolateTokens(Jim_Interp *interp, const ScriptToken * token, int tokens, int flags) +{ + int totlen = 0, i; + Jim_Obj **intv; + Jim_Obj *sintv[JIM_EVAL_SINTV_LEN]; + Jim_Obj *objPtr; + char *s; + + if (tokens <= JIM_EVAL_SINTV_LEN) + intv = sintv; + else + intv = Jim_Alloc(sizeof(Jim_Obj *) * tokens); + + for (i = 0; i < tokens; i++) { + switch (JimSubstOneToken(interp, &token[i], &intv[i])) { + case JIM_OK: + case JIM_RETURN: + break; + case JIM_BREAK: + if (flags & JIM_SUBST_FLAG) { + + tokens = i; + continue; + } + + + case JIM_CONTINUE: + if (flags & JIM_SUBST_FLAG) { + intv[i] = NULL; + continue; + } + + + default: + while (i--) { + Jim_DecrRefCount(interp, intv[i]); + } + if (intv != sintv) { + Jim_Free(intv); + } + return NULL; + } + Jim_IncrRefCount(intv[i]); + Jim_String(intv[i]); + totlen += intv[i]->length; + } + + + if (tokens == 1 && intv[0] && intv == sintv) { + + intv[0]->refCount--; + return intv[0]; + } + + objPtr = Jim_NewStringObjNoAlloc(interp, NULL, 0); + + if (tokens == 4 && token[0].type == JIM_TT_ESC && token[1].type == JIM_TT_ESC + && token[2].type == JIM_TT_VAR) { + + objPtr->typePtr = &interpolatedObjType; + objPtr->internalRep.dictSubstValue.varNameObjPtr = token[0].objPtr; + objPtr->internalRep.dictSubstValue.indexObjPtr = intv[2]; + Jim_IncrRefCount(intv[2]); + } + else if (tokens && intv[0] && intv[0]->typePtr == &sourceObjType) { + + JimSetSourceInfo(interp, objPtr, intv[0]->internalRep.sourceValue.fileNameObj, intv[0]->internalRep.sourceValue.lineNumber); + } + + + s = objPtr->bytes = Jim_Alloc(totlen + 1); + objPtr->length = totlen; + for (i = 0; i < tokens; i++) { + if (intv[i]) { + memcpy(s, intv[i]->bytes, intv[i]->length); + s += intv[i]->length; + Jim_DecrRefCount(interp, intv[i]); + } + } + objPtr->bytes[totlen] = '\0'; + + if (intv != sintv) { + Jim_Free(intv); + } + + return objPtr; +} + + +static int JimEvalObjList(Jim_Interp *interp, Jim_Obj *listPtr) +{ + int retcode = JIM_OK; + + JimPanic((Jim_IsList(listPtr) == 0, "JimEvalObjList() invoked on non-list.")); + + if (listPtr->internalRep.listValue.len) { + Jim_IncrRefCount(listPtr); + retcode = JimInvokeCommand(interp, + listPtr->internalRep.listValue.len, + listPtr->internalRep.listValue.ele); + Jim_DecrRefCount(interp, listPtr); + } + return retcode; +} + +int Jim_EvalObjList(Jim_Interp *interp, Jim_Obj *listPtr) +{ + SetListFromAny(interp, listPtr); + return JimEvalObjList(interp, listPtr); +} + +int Jim_EvalObj(Jim_Interp *interp, Jim_Obj *scriptObjPtr) +{ + int i; + ScriptObj *script; + ScriptToken *token; + int retcode = JIM_OK; + Jim_Obj *sargv[JIM_EVAL_SARGV_LEN], **argv = NULL; + Jim_Obj *prevScriptObj; + + if (Jim_IsList(scriptObjPtr) && scriptObjPtr->bytes == NULL) { + return JimEvalObjList(interp, scriptObjPtr); + } + + Jim_IncrRefCount(scriptObjPtr); + script = JimGetScript(interp, scriptObjPtr); + if (!JimScriptValid(interp, script)) { + Jim_DecrRefCount(interp, scriptObjPtr); + return JIM_ERR; + } + + Jim_SetEmptyResult(interp); + + token = script->token; + +#ifdef JIM_OPTIMIZATION + if (script->len == 0) { + Jim_DecrRefCount(interp, scriptObjPtr); + return JIM_OK; + } + if (script->len == 3 + && token[1].objPtr->typePtr == &commandObjType + && token[1].objPtr->internalRep.cmdValue.cmdPtr->isproc == 0 + && token[1].objPtr->internalRep.cmdValue.cmdPtr->u.native.cmdProc == Jim_IncrCoreCommand + && token[2].objPtr->typePtr == &variableObjType) { + + Jim_Obj *objPtr = Jim_GetVariable(interp, token[2].objPtr, JIM_NONE); + + if (objPtr && !Jim_IsShared(objPtr) && objPtr->typePtr == &intObjType) { + JimWideValue(objPtr)++; + Jim_InvalidateStringRep(objPtr); + Jim_DecrRefCount(interp, scriptObjPtr); + Jim_SetResult(interp, objPtr); + return JIM_OK; + } + } +#endif + + script->inUse++; + + + prevScriptObj = interp->currentScriptObj; + interp->currentScriptObj = scriptObjPtr; + + interp->errorFlag = 0; + argv = sargv; + + for (i = 0; i < script->len && retcode == JIM_OK; ) { + int argc; + int j; + + + argc = token[i].objPtr->internalRep.scriptLineValue.argc; + script->linenr = token[i].objPtr->internalRep.scriptLineValue.line; + + + if (argc > JIM_EVAL_SARGV_LEN) + argv = Jim_Alloc(sizeof(Jim_Obj *) * argc); + + + i++; + + for (j = 0; j < argc; j++) { + long wordtokens = 1; + int expand = 0; + Jim_Obj *wordObjPtr = NULL; + + if (token[i].type == JIM_TT_WORD) { + wordtokens = JimWideValue(token[i++].objPtr); + if (wordtokens < 0) { + expand = 1; + wordtokens = -wordtokens; + } + } + + if (wordtokens == 1) { + + switch (token[i].type) { + case JIM_TT_ESC: + case JIM_TT_STR: + wordObjPtr = token[i].objPtr; + break; + case JIM_TT_VAR: + wordObjPtr = Jim_GetVariable(interp, token[i].objPtr, JIM_ERRMSG); + break; + case JIM_TT_EXPRSUGAR: + wordObjPtr = JimExpandExprSugar(interp, token[i].objPtr); + break; + case JIM_TT_DICTSUGAR: + wordObjPtr = JimExpandDictSugar(interp, token[i].objPtr); + break; + case JIM_TT_CMD: + retcode = Jim_EvalObj(interp, token[i].objPtr); + if (retcode == JIM_OK) { + wordObjPtr = Jim_GetResult(interp); + } + break; + default: + JimPanic((1, "default token type reached " "in Jim_EvalObj().")); + } + } + else { + wordObjPtr = JimInterpolateTokens(interp, token + i, wordtokens, JIM_NONE); + } + + if (!wordObjPtr) { + if (retcode == JIM_OK) { + retcode = JIM_ERR; + } + break; + } + + Jim_IncrRefCount(wordObjPtr); + i += wordtokens; + + if (!expand) { + argv[j] = wordObjPtr; + } + else { + + int len = Jim_ListLength(interp, wordObjPtr); + int newargc = argc + len - 1; + int k; + + if (len > 1) { + if (argv == sargv) { + if (newargc > JIM_EVAL_SARGV_LEN) { + argv = Jim_Alloc(sizeof(*argv) * newargc); + memcpy(argv, sargv, sizeof(*argv) * j); + } + } + else { + + argv = Jim_Realloc(argv, sizeof(*argv) * newargc); + } + } + + + for (k = 0; k < len; k++) { + argv[j++] = wordObjPtr->internalRep.listValue.ele[k]; + Jim_IncrRefCount(wordObjPtr->internalRep.listValue.ele[k]); + } + + Jim_DecrRefCount(interp, wordObjPtr); + + + j--; + argc += len - 1; + } + } + + if (retcode == JIM_OK && argc) { + + retcode = JimInvokeCommand(interp, argc, argv); + + if (Jim_CheckSignal(interp)) { + retcode = JIM_SIGNAL; + } + } + + + while (j-- > 0) { + Jim_DecrRefCount(interp, argv[j]); + } + + if (argv != sargv) { + Jim_Free(argv); + argv = sargv; + } + } + + + if (retcode == JIM_ERR) { + JimAddErrorToStack(interp, script); + } + + else if (retcode != JIM_RETURN || interp->returnCode != JIM_ERR) { + + interp->addStackTrace = 0; + } + + + interp->currentScriptObj = prevScriptObj; + + Jim_FreeIntRep(interp, scriptObjPtr); + scriptObjPtr->typePtr = &scriptObjType; + Jim_SetIntRepPtr(scriptObjPtr, script); + Jim_DecrRefCount(interp, scriptObjPtr); + + return retcode; +} + +static int JimSetProcArg(Jim_Interp *interp, Jim_Obj *argNameObj, Jim_Obj *argValObj) +{ + int retcode; + + const char *varname = Jim_String(argNameObj); + if (*varname == '&') { + + Jim_Obj *objPtr; + Jim_CallFrame *savedCallFrame = interp->framePtr; + + interp->framePtr = interp->framePtr->parent; + objPtr = Jim_GetVariable(interp, argValObj, JIM_ERRMSG); + interp->framePtr = savedCallFrame; + if (!objPtr) { + return JIM_ERR; + } + + + objPtr = Jim_NewStringObj(interp, varname + 1, -1); + Jim_IncrRefCount(objPtr); + retcode = Jim_SetVariableLink(interp, objPtr, argValObj, interp->framePtr->parent); + Jim_DecrRefCount(interp, objPtr); + } + else { + retcode = Jim_SetVariable(interp, argNameObj, argValObj); + } + return retcode; +} + +static void JimSetProcWrongArgs(Jim_Interp *interp, Jim_Obj *procNameObj, Jim_Cmd *cmd) +{ + + Jim_Obj *argmsg = Jim_NewStringObj(interp, "", 0); + int i; + + for (i = 0; i < cmd->u.proc.argListLen; i++) { + Jim_AppendString(interp, argmsg, " ", 1); + + if (i == cmd->u.proc.argsPos) { + if (cmd->u.proc.arglist[i].defaultObjPtr) { + + Jim_AppendString(interp, argmsg, "?", 1); + Jim_AppendObj(interp, argmsg, cmd->u.proc.arglist[i].defaultObjPtr); + Jim_AppendString(interp, argmsg, " ...?", -1); + } + else { + + Jim_AppendString(interp, argmsg, "?arg...?", -1); + } + } + else { + if (cmd->u.proc.arglist[i].defaultObjPtr) { + Jim_AppendString(interp, argmsg, "?", 1); + Jim_AppendObj(interp, argmsg, cmd->u.proc.arglist[i].nameObjPtr); + Jim_AppendString(interp, argmsg, "?", 1); + } + else { + const char *arg = Jim_String(cmd->u.proc.arglist[i].nameObjPtr); + if (*arg == '&') { + arg++; + } + Jim_AppendString(interp, argmsg, arg, -1); + } + } + } + Jim_SetResultFormatted(interp, "wrong # args: should be \"%#s%#s\"", procNameObj, argmsg); +} + +#ifdef jim_ext_namespace +int Jim_EvalNamespace(Jim_Interp *interp, Jim_Obj *scriptObj, Jim_Obj *nsObj) +{ + Jim_CallFrame *callFramePtr; + int retcode; + + + callFramePtr = JimCreateCallFrame(interp, interp->framePtr, nsObj); + callFramePtr->argv = &interp->emptyObj; + callFramePtr->argc = 0; + callFramePtr->procArgsObjPtr = NULL; + callFramePtr->procBodyObjPtr = scriptObj; + callFramePtr->staticVars = NULL; + callFramePtr->fileNameObj = interp->emptyObj; + callFramePtr->line = 0; + Jim_IncrRefCount(scriptObj); + interp->framePtr = callFramePtr; + + + if (interp->framePtr->level == interp->maxCallFrameDepth) { + Jim_SetResultString(interp, "Too many nested calls. Infinite recursion?", -1); + retcode = JIM_ERR; + } + else { + + retcode = Jim_EvalObj(interp, scriptObj); + } + + + interp->framePtr = interp->framePtr->parent; + JimFreeCallFrame(interp, callFramePtr, JIM_FCF_REUSE); + + return retcode; +} +#endif + +static int JimCallProcedure(Jim_Interp *interp, Jim_Cmd *cmd, int argc, Jim_Obj *const *argv) +{ + Jim_CallFrame *callFramePtr; + int i, d, retcode, optargs; + ScriptObj *script; + + + if (argc - 1 < cmd->u.proc.reqArity || + (cmd->u.proc.argsPos < 0 && argc - 1 > cmd->u.proc.reqArity + cmd->u.proc.optArity)) { + JimSetProcWrongArgs(interp, argv[0], cmd); + return JIM_ERR; + } + + if (Jim_Length(cmd->u.proc.bodyObjPtr) == 0) { + + return JIM_OK; + } + + + if (interp->framePtr->level == interp->maxCallFrameDepth) { + Jim_SetResultString(interp, "Too many nested calls. Infinite recursion?", -1); + return JIM_ERR; + } + + + callFramePtr = JimCreateCallFrame(interp, interp->framePtr, cmd->u.proc.nsObj); + callFramePtr->argv = argv; + callFramePtr->argc = argc; + callFramePtr->procArgsObjPtr = cmd->u.proc.argListObjPtr; + callFramePtr->procBodyObjPtr = cmd->u.proc.bodyObjPtr; + callFramePtr->staticVars = cmd->u.proc.staticVars; + + + script = JimGetScript(interp, interp->currentScriptObj); + callFramePtr->fileNameObj = script->fileNameObj; + callFramePtr->line = script->linenr; + + Jim_IncrRefCount(cmd->u.proc.argListObjPtr); + Jim_IncrRefCount(cmd->u.proc.bodyObjPtr); + interp->framePtr = callFramePtr; + + + optargs = (argc - 1 - cmd->u.proc.reqArity); + + + i = 1; + for (d = 0; d < cmd->u.proc.argListLen; d++) { + Jim_Obj *nameObjPtr = cmd->u.proc.arglist[d].nameObjPtr; + if (d == cmd->u.proc.argsPos) { + + Jim_Obj *listObjPtr; + int argsLen = 0; + if (cmd->u.proc.reqArity + cmd->u.proc.optArity < argc - 1) { + argsLen = argc - 1 - (cmd->u.proc.reqArity + cmd->u.proc.optArity); + } + listObjPtr = Jim_NewListObj(interp, &argv[i], argsLen); + + + if (cmd->u.proc.arglist[d].defaultObjPtr) { + nameObjPtr =cmd->u.proc.arglist[d].defaultObjPtr; + } + retcode = Jim_SetVariable(interp, nameObjPtr, listObjPtr); + if (retcode != JIM_OK) { + goto badargset; + } + + i += argsLen; + continue; + } + + + if (cmd->u.proc.arglist[d].defaultObjPtr == NULL || optargs-- > 0) { + retcode = JimSetProcArg(interp, nameObjPtr, argv[i++]); + } + else { + + retcode = Jim_SetVariable(interp, nameObjPtr, cmd->u.proc.arglist[d].defaultObjPtr); + } + if (retcode != JIM_OK) { + goto badargset; + } + } + + + retcode = Jim_EvalObj(interp, cmd->u.proc.bodyObjPtr); + +badargset: + + + retcode = JimInvokeDefer(interp, retcode); + interp->framePtr = interp->framePtr->parent; + JimFreeCallFrame(interp, callFramePtr, JIM_FCF_REUSE); + + + if (interp->framePtr->tailcallObj) { + do { + Jim_Obj *tailcallObj = interp->framePtr->tailcallObj; + + interp->framePtr->tailcallObj = NULL; + + if (retcode == JIM_EVAL) { + retcode = Jim_EvalObjList(interp, tailcallObj); + if (retcode == JIM_RETURN) { + interp->returnLevel++; + } + } + Jim_DecrRefCount(interp, tailcallObj); + } while (interp->framePtr->tailcallObj); + + + if (interp->framePtr->tailcallCmd) { + JimDecrCmdRefCount(interp, interp->framePtr->tailcallCmd); + interp->framePtr->tailcallCmd = NULL; + } + } + + + if (retcode == JIM_RETURN) { + if (--interp->returnLevel <= 0) { + retcode = interp->returnCode; + interp->returnCode = JIM_OK; + interp->returnLevel = 0; + } + } + else if (retcode == JIM_ERR) { + interp->addStackTrace++; + Jim_DecrRefCount(interp, interp->errorProc); + interp->errorProc = argv[0]; + Jim_IncrRefCount(interp->errorProc); + } + + return retcode; +} + +int Jim_EvalSource(Jim_Interp *interp, const char *filename, int lineno, const char *script) +{ + int retval; + Jim_Obj *scriptObjPtr; + + scriptObjPtr = Jim_NewStringObj(interp, script, -1); + Jim_IncrRefCount(scriptObjPtr); + + if (filename) { + Jim_Obj *prevScriptObj; + + JimSetSourceInfo(interp, scriptObjPtr, Jim_NewStringObj(interp, filename, -1), lineno); + + prevScriptObj = interp->currentScriptObj; + interp->currentScriptObj = scriptObjPtr; + + retval = Jim_EvalObj(interp, scriptObjPtr); + + interp->currentScriptObj = prevScriptObj; + } + else { + retval = Jim_EvalObj(interp, scriptObjPtr); + } + Jim_DecrRefCount(interp, scriptObjPtr); + return retval; +} + +int Jim_Eval(Jim_Interp *interp, const char *script) +{ + return Jim_EvalObj(interp, Jim_NewStringObj(interp, script, -1)); +} + + +int Jim_EvalGlobal(Jim_Interp *interp, const char *script) +{ + int retval; + Jim_CallFrame *savedFramePtr = interp->framePtr; + + interp->framePtr = interp->topFramePtr; + retval = Jim_Eval(interp, script); + interp->framePtr = savedFramePtr; + + return retval; +} + +int Jim_EvalFileGlobal(Jim_Interp *interp, const char *filename) +{ + int retval; + Jim_CallFrame *savedFramePtr = interp->framePtr; + + interp->framePtr = interp->topFramePtr; + retval = Jim_EvalFile(interp, filename); + interp->framePtr = savedFramePtr; + + return retval; +} + +#include + +int Jim_EvalFile(Jim_Interp *interp, const char *filename) +{ + FILE *fp; + char *buf; + Jim_Obj *scriptObjPtr; + Jim_Obj *prevScriptObj; + struct stat sb; + int retcode; + int readlen; + + if (stat(filename, &sb) != 0 || (fp = fopen(filename, "rt")) == NULL) { + Jim_SetResultFormatted(interp, "couldn't read file \"%s\": %s", filename, strerror(errno)); + return JIM_ERR; + } + if (sb.st_size == 0) { + fclose(fp); + return JIM_OK; + } + + buf = Jim_Alloc(sb.st_size + 1); + readlen = fread(buf, 1, sb.st_size, fp); + if (ferror(fp)) { + fclose(fp); + Jim_Free(buf); + Jim_SetResultFormatted(interp, "failed to load file \"%s\": %s", filename, strerror(errno)); + return JIM_ERR; + } + fclose(fp); + buf[readlen] = 0; + + scriptObjPtr = Jim_NewStringObjNoAlloc(interp, buf, readlen); + JimSetSourceInfo(interp, scriptObjPtr, Jim_NewStringObj(interp, filename, -1), 1); + Jim_IncrRefCount(scriptObjPtr); + + prevScriptObj = interp->currentScriptObj; + interp->currentScriptObj = scriptObjPtr; + + retcode = Jim_EvalObj(interp, scriptObjPtr); + + + if (retcode == JIM_RETURN) { + if (--interp->returnLevel <= 0) { + retcode = interp->returnCode; + interp->returnCode = JIM_OK; + interp->returnLevel = 0; + } + } + if (retcode == JIM_ERR) { + + interp->addStackTrace++; + } + + interp->currentScriptObj = prevScriptObj; + + Jim_DecrRefCount(interp, scriptObjPtr); + + return retcode; +} + +static void JimParseSubst(struct JimParserCtx *pc, int flags) +{ + pc->tstart = pc->p; + pc->tline = pc->linenr; + + if (pc->len == 0) { + pc->tend = pc->p; + pc->tt = JIM_TT_EOL; + pc->eof = 1; + return; + } + if (*pc->p == '[' && !(flags & JIM_SUBST_NOCMD)) { + JimParseCmd(pc); + return; + } + if (*pc->p == '$' && !(flags & JIM_SUBST_NOVAR)) { + if (JimParseVar(pc) == JIM_OK) { + return; + } + + pc->tstart = pc->p; + flags |= JIM_SUBST_NOVAR; + } + while (pc->len) { + if (*pc->p == '$' && !(flags & JIM_SUBST_NOVAR)) { + break; + } + if (*pc->p == '[' && !(flags & JIM_SUBST_NOCMD)) { + break; + } + if (*pc->p == '\\' && pc->len > 1) { + pc->p++; + pc->len--; + } + pc->p++; + pc->len--; + } + pc->tend = pc->p - 1; + pc->tt = (flags & JIM_SUBST_NOESC) ? JIM_TT_STR : JIM_TT_ESC; +} + + +static int SetSubstFromAny(Jim_Interp *interp, struct Jim_Obj *objPtr, int flags) +{ + int scriptTextLen; + const char *scriptText = Jim_GetString(objPtr, &scriptTextLen); + struct JimParserCtx parser; + struct ScriptObj *script = Jim_Alloc(sizeof(*script)); + ParseTokenList tokenlist; + + + ScriptTokenListInit(&tokenlist); + + JimParserInit(&parser, scriptText, scriptTextLen, 1); + while (1) { + JimParseSubst(&parser, flags); + if (parser.eof) { + + break; + } + ScriptAddToken(&tokenlist, parser.tstart, parser.tend - parser.tstart + 1, parser.tt, + parser.tline); + } + + + script->inUse = 1; + script->substFlags = flags; + script->fileNameObj = interp->emptyObj; + Jim_IncrRefCount(script->fileNameObj); + SubstObjAddTokens(interp, script, &tokenlist); + + + ScriptTokenListFree(&tokenlist); + +#ifdef DEBUG_SHOW_SUBST + { + int i; + + printf("==== Subst ====\n"); + for (i = 0; i < script->len; i++) { + printf("[%2d] %s '%s'\n", i, jim_tt_name(script->token[i].type), + Jim_String(script->token[i].objPtr)); + } + } +#endif + + + Jim_FreeIntRep(interp, objPtr); + Jim_SetIntRepPtr(objPtr, script); + objPtr->typePtr = &scriptObjType; + return JIM_OK; +} + +static ScriptObj *Jim_GetSubst(Jim_Interp *interp, Jim_Obj *objPtr, int flags) +{ + if (objPtr->typePtr != &scriptObjType || ((ScriptObj *)Jim_GetIntRepPtr(objPtr))->substFlags != flags) + SetSubstFromAny(interp, objPtr, flags); + return (ScriptObj *) Jim_GetIntRepPtr(objPtr); +} + +int Jim_SubstObj(Jim_Interp *interp, Jim_Obj *substObjPtr, Jim_Obj **resObjPtrPtr, int flags) +{ + ScriptObj *script = Jim_GetSubst(interp, substObjPtr, flags); + + Jim_IncrRefCount(substObjPtr); + script->inUse++; + + *resObjPtrPtr = JimInterpolateTokens(interp, script->token, script->len, flags); + + script->inUse--; + Jim_DecrRefCount(interp, substObjPtr); + if (*resObjPtrPtr == NULL) { + return JIM_ERR; + } + return JIM_OK; +} + +void Jim_WrongNumArgs(Jim_Interp *interp, int argc, Jim_Obj *const *argv, const char *msg) +{ + Jim_Obj *objPtr; + Jim_Obj *listObjPtr; + + JimPanic((argc == 0, "Jim_WrongNumArgs() called with argc=0")); + + listObjPtr = Jim_NewListObj(interp, argv, argc); + + if (msg && *msg) { + Jim_ListAppendElement(interp, listObjPtr, Jim_NewStringObj(interp, msg, -1)); + } + Jim_IncrRefCount(listObjPtr); + objPtr = Jim_ListJoin(interp, listObjPtr, " ", 1); + Jim_DecrRefCount(interp, listObjPtr); + + Jim_SetResultFormatted(interp, "wrong # args: should be \"%#s\"", objPtr); +} + +typedef void JimHashtableIteratorCallbackType(Jim_Interp *interp, Jim_Obj *listObjPtr, + Jim_HashEntry *he, int type); + +#define JimTrivialMatch(pattern) (strpbrk((pattern), "*[?\\") == NULL) + +static Jim_Obj *JimHashtablePatternMatch(Jim_Interp *interp, Jim_HashTable *ht, Jim_Obj *patternObjPtr, + JimHashtableIteratorCallbackType *callback, int type) +{ + Jim_HashEntry *he; + Jim_Obj *listObjPtr = Jim_NewListObj(interp, NULL, 0); + + + if (patternObjPtr && JimTrivialMatch(Jim_String(patternObjPtr))) { + he = Jim_FindHashEntry(ht, Jim_String(patternObjPtr)); + if (he) { + callback(interp, listObjPtr, he, type); + } + } + else { + Jim_HashTableIterator htiter; + JimInitHashTableIterator(ht, &htiter); + while ((he = Jim_NextHashEntry(&htiter)) != NULL) { + if (patternObjPtr == NULL || JimGlobMatch(Jim_String(patternObjPtr), he->key, 0)) { + callback(interp, listObjPtr, he, type); + } + } + } + return listObjPtr; +} + + +#define JIM_CMDLIST_COMMANDS 0 +#define JIM_CMDLIST_PROCS 1 +#define JIM_CMDLIST_CHANNELS 2 + +static void JimCommandMatch(Jim_Interp *interp, Jim_Obj *listObjPtr, + Jim_HashEntry *he, int type) +{ + Jim_Cmd *cmdPtr = Jim_GetHashEntryVal(he); + Jim_Obj *objPtr; + + if (type == JIM_CMDLIST_PROCS && !cmdPtr->isproc) { + + return; + } + + objPtr = Jim_NewStringObj(interp, he->key, -1); + Jim_IncrRefCount(objPtr); + + if (type != JIM_CMDLIST_CHANNELS || Jim_AioFilehandle(interp, objPtr)) { + Jim_ListAppendElement(interp, listObjPtr, objPtr); + } + Jim_DecrRefCount(interp, objPtr); +} + + +static Jim_Obj *JimCommandsList(Jim_Interp *interp, Jim_Obj *patternObjPtr, int type) +{ + return JimHashtablePatternMatch(interp, &interp->commands, patternObjPtr, JimCommandMatch, type); +} + + +#define JIM_VARLIST_GLOBALS 0 +#define JIM_VARLIST_LOCALS 1 +#define JIM_VARLIST_VARS 2 + +#define JIM_VARLIST_VALUES 0x1000 + +static void JimVariablesMatch(Jim_Interp *interp, Jim_Obj *listObjPtr, + Jim_HashEntry *he, int type) +{ + Jim_Var *varPtr = Jim_GetHashEntryVal(he); + + if (type != JIM_VARLIST_LOCALS || varPtr->linkFramePtr == NULL) { + Jim_ListAppendElement(interp, listObjPtr, Jim_NewStringObj(interp, he->key, -1)); + if (type & JIM_VARLIST_VALUES) { + Jim_ListAppendElement(interp, listObjPtr, varPtr->objPtr); + } + } +} + + +static Jim_Obj *JimVariablesList(Jim_Interp *interp, Jim_Obj *patternObjPtr, int mode) +{ + if (mode == JIM_VARLIST_LOCALS && interp->framePtr == interp->topFramePtr) { + return interp->emptyObj; + } + else { + Jim_CallFrame *framePtr = (mode == JIM_VARLIST_GLOBALS) ? interp->topFramePtr : interp->framePtr; + return JimHashtablePatternMatch(interp, &framePtr->vars, patternObjPtr, JimVariablesMatch, mode); + } +} + +static int JimInfoLevel(Jim_Interp *interp, Jim_Obj *levelObjPtr, + Jim_Obj **objPtrPtr, int info_level_cmd) +{ + Jim_CallFrame *targetCallFrame; + + targetCallFrame = JimGetCallFrameByInteger(interp, levelObjPtr); + if (targetCallFrame == NULL) { + return JIM_ERR; + } + + if (targetCallFrame == interp->topFramePtr) { + Jim_SetResultFormatted(interp, "bad level \"%#s\"", levelObjPtr); + return JIM_ERR; + } + if (info_level_cmd) { + *objPtrPtr = Jim_NewListObj(interp, targetCallFrame->argv, targetCallFrame->argc); + } + else { + Jim_Obj *listObj = Jim_NewListObj(interp, NULL, 0); + + Jim_ListAppendElement(interp, listObj, targetCallFrame->argv[0]); + Jim_ListAppendElement(interp, listObj, targetCallFrame->fileNameObj); + Jim_ListAppendElement(interp, listObj, Jim_NewIntObj(interp, targetCallFrame->line)); + *objPtrPtr = listObj; + } + return JIM_OK; +} + + + +static int Jim_PutsCoreCommand(Jim_Interp *interp, int argc, Jim_Obj *const *argv) +{ + if (argc != 2 && argc != 3) { + Jim_WrongNumArgs(interp, 1, argv, "?-nonewline? string"); + return JIM_ERR; + } + if (argc == 3) { + if (!Jim_CompareStringImmediate(interp, argv[1], "-nonewline")) { + Jim_SetResultString(interp, "The second argument must " "be -nonewline", -1); + return JIM_ERR; + } + else { + fputs(Jim_String(argv[2]), stdout); + } + } + else { + puts(Jim_String(argv[1])); + } + return JIM_OK; +} + + +static int JimAddMulHelper(Jim_Interp *interp, int argc, Jim_Obj *const *argv, int op) +{ + jim_wide wideValue, res; + double doubleValue, doubleRes; + int i; + + res = (op == JIM_EXPROP_ADD) ? 0 : 1; + + for (i = 1; i < argc; i++) { + if (Jim_GetWide(interp, argv[i], &wideValue) != JIM_OK) + goto trydouble; + if (op == JIM_EXPROP_ADD) + res += wideValue; + else + res *= wideValue; + } + Jim_SetResultInt(interp, res); + return JIM_OK; + trydouble: + doubleRes = (double)res; + for (; i < argc; i++) { + if (Jim_GetDouble(interp, argv[i], &doubleValue) != JIM_OK) + return JIM_ERR; + if (op == JIM_EXPROP_ADD) + doubleRes += doubleValue; + else + doubleRes *= doubleValue; + } + Jim_SetResult(interp, Jim_NewDoubleObj(interp, doubleRes)); + return JIM_OK; +} + + +static int JimSubDivHelper(Jim_Interp *interp, int argc, Jim_Obj *const *argv, int op) +{ + jim_wide wideValue, res = 0; + double doubleValue, doubleRes = 0; + int i = 2; + + if (argc < 2) { + Jim_WrongNumArgs(interp, 1, argv, "number ?number ... number?"); + return JIM_ERR; + } + else if (argc == 2) { + if (Jim_GetWide(interp, argv[1], &wideValue) != JIM_OK) { + if (Jim_GetDouble(interp, argv[1], &doubleValue) != JIM_OK) { + return JIM_ERR; + } + else { + if (op == JIM_EXPROP_SUB) + doubleRes = -doubleValue; + else + doubleRes = 1.0 / doubleValue; + Jim_SetResult(interp, Jim_NewDoubleObj(interp, doubleRes)); + return JIM_OK; + } + } + if (op == JIM_EXPROP_SUB) { + res = -wideValue; + Jim_SetResultInt(interp, res); + } + else { + doubleRes = 1.0 / wideValue; + Jim_SetResult(interp, Jim_NewDoubleObj(interp, doubleRes)); + } + return JIM_OK; + } + else { + if (Jim_GetWide(interp, argv[1], &res) != JIM_OK) { + if (Jim_GetDouble(interp, argv[1], &doubleRes) + != JIM_OK) { + return JIM_ERR; + } + else { + goto trydouble; + } + } + } + for (i = 2; i < argc; i++) { + if (Jim_GetWide(interp, argv[i], &wideValue) != JIM_OK) { + doubleRes = (double)res; + goto trydouble; + } + if (op == JIM_EXPROP_SUB) + res -= wideValue; + else { + if (wideValue == 0) { + Jim_SetResultString(interp, "Division by zero", -1); + return JIM_ERR; + } + res /= wideValue; + } + } + Jim_SetResultInt(interp, res); + return JIM_OK; + trydouble: + for (; i < argc; i++) { + if (Jim_GetDouble(interp, argv[i], &doubleValue) != JIM_OK) + return JIM_ERR; + if (op == JIM_EXPROP_SUB) + doubleRes -= doubleValue; + else + doubleRes /= doubleValue; + } + Jim_SetResult(interp, Jim_NewDoubleObj(interp, doubleRes)); + return JIM_OK; +} + + + +static int Jim_AddCoreCommand(Jim_Interp *interp, int argc, Jim_Obj *const *argv) +{ + return JimAddMulHelper(interp, argc, argv, JIM_EXPROP_ADD); +} + + +static int Jim_MulCoreCommand(Jim_Interp *interp, int argc, Jim_Obj *const *argv) +{ + return JimAddMulHelper(interp, argc, argv, JIM_EXPROP_MUL); +} + + +static int Jim_SubCoreCommand(Jim_Interp *interp, int argc, Jim_Obj *const *argv) +{ + return JimSubDivHelper(interp, argc, argv, JIM_EXPROP_SUB); +} + + +static int Jim_DivCoreCommand(Jim_Interp *interp, int argc, Jim_Obj *const *argv) +{ + return JimSubDivHelper(interp, argc, argv, JIM_EXPROP_DIV); +} + + +static int Jim_SetCoreCommand(Jim_Interp *interp, int argc, Jim_Obj *const *argv) +{ + if (argc != 2 && argc != 3) { + Jim_WrongNumArgs(interp, 1, argv, "varName ?newValue?"); + return JIM_ERR; + } + if (argc == 2) { + Jim_Obj *objPtr; + + objPtr = Jim_GetVariable(interp, argv[1], JIM_ERRMSG); + if (!objPtr) + return JIM_ERR; + Jim_SetResult(interp, objPtr); + return JIM_OK; + } + + if (Jim_SetVariable(interp, argv[1], argv[2]) != JIM_OK) + return JIM_ERR; + Jim_SetResult(interp, argv[2]); + return JIM_OK; +} + +static int Jim_UnsetCoreCommand(Jim_Interp *interp, int argc, Jim_Obj *const *argv) +{ + int i = 1; + int complain = 1; + + while (i < argc) { + if (Jim_CompareStringImmediate(interp, argv[i], "--")) { + i++; + break; + } + if (Jim_CompareStringImmediate(interp, argv[i], "-nocomplain")) { + complain = 0; + i++; + continue; + } + break; + } + + while (i < argc) { + if (Jim_UnsetVariable(interp, argv[i], complain ? JIM_ERRMSG : JIM_NONE) != JIM_OK + && complain) { + return JIM_ERR; + } + i++; + } + return JIM_OK; +} + + +static int Jim_WhileCoreCommand(Jim_Interp *interp, int argc, Jim_Obj *const *argv) +{ + if (argc != 3) { + Jim_WrongNumArgs(interp, 1, argv, "condition body"); + return JIM_ERR; + } + + + while (1) { + int boolean, retval; + + if ((retval = Jim_GetBoolFromExpr(interp, argv[1], &boolean)) != JIM_OK) + return retval; + if (!boolean) + break; + + if ((retval = Jim_EvalObj(interp, argv[2])) != JIM_OK) { + switch (retval) { + case JIM_BREAK: + goto out; + break; + case JIM_CONTINUE: + continue; + break; + default: + return retval; + } + } + } + out: + Jim_SetEmptyResult(interp); + return JIM_OK; +} + + +static int Jim_ForCoreCommand(Jim_Interp *interp, int argc, Jim_Obj *const *argv) +{ + int retval; + int boolean = 1; + Jim_Obj *varNamePtr = NULL; + Jim_Obj *stopVarNamePtr = NULL; + + if (argc != 5) { + Jim_WrongNumArgs(interp, 1, argv, "start test next body"); + return JIM_ERR; + } + + + if ((retval = Jim_EvalObj(interp, argv[1])) != JIM_OK) { + return retval; + } + + retval = Jim_GetBoolFromExpr(interp, argv[2], &boolean); + + +#ifdef JIM_OPTIMIZATION + if (retval == JIM_OK && boolean) { + ScriptObj *incrScript; + struct ExprTree *expr; + jim_wide stop, currentVal; + Jim_Obj *objPtr; + int cmpOffset; + + + expr = JimGetExpression(interp, argv[2]); + incrScript = JimGetScript(interp, argv[3]); + + + if (incrScript == NULL || incrScript->len != 3 || !expr || expr->len != 3) { + goto evalstart; + } + + if (incrScript->token[1].type != JIM_TT_ESC) { + goto evalstart; + } + + if (expr->expr->type == JIM_EXPROP_LT) { + cmpOffset = 0; + } + else if (expr->expr->type == JIM_EXPROP_LTE) { + cmpOffset = 1; + } + else { + goto evalstart; + } + + if (expr->expr->left->type != JIM_TT_VAR) { + goto evalstart; + } + + if (expr->expr->right->type != JIM_TT_VAR && expr->expr->right->type != JIM_TT_EXPR_INT) { + goto evalstart; + } + + + if (!Jim_CompareStringImmediate(interp, incrScript->token[1].objPtr, "incr")) { + goto evalstart; + } + + + if (!Jim_StringEqObj(incrScript->token[2].objPtr, expr->expr->left->objPtr)) { + goto evalstart; + } + + + if (expr->expr->right->type == JIM_TT_EXPR_INT) { + if (Jim_GetWide(interp, expr->expr->right->objPtr, &stop) == JIM_ERR) { + goto evalstart; + } + } + else { + stopVarNamePtr = expr->expr->right->objPtr; + Jim_IncrRefCount(stopVarNamePtr); + + stop = 0; + } + + + varNamePtr = expr->expr->left->objPtr; + Jim_IncrRefCount(varNamePtr); + + objPtr = Jim_GetVariable(interp, varNamePtr, JIM_NONE); + if (objPtr == NULL || Jim_GetWide(interp, objPtr, ¤tVal) != JIM_OK) { + goto testcond; + } + + + while (retval == JIM_OK) { + + + + + if (stopVarNamePtr) { + objPtr = Jim_GetVariable(interp, stopVarNamePtr, JIM_NONE); + if (objPtr == NULL || Jim_GetWide(interp, objPtr, &stop) != JIM_OK) { + goto testcond; + } + } + + if (currentVal >= stop + cmpOffset) { + break; + } + + + retval = Jim_EvalObj(interp, argv[4]); + if (retval == JIM_OK || retval == JIM_CONTINUE) { + retval = JIM_OK; + + objPtr = Jim_GetVariable(interp, varNamePtr, JIM_ERRMSG); + + + if (objPtr == NULL) { + retval = JIM_ERR; + goto out; + } + if (!Jim_IsShared(objPtr) && objPtr->typePtr == &intObjType) { + currentVal = ++JimWideValue(objPtr); + Jim_InvalidateStringRep(objPtr); + } + else { + if (Jim_GetWide(interp, objPtr, ¤tVal) != JIM_OK || + Jim_SetVariable(interp, varNamePtr, Jim_NewIntObj(interp, + ++currentVal)) != JIM_OK) { + goto evalnext; + } + } + } + } + goto out; + } + evalstart: +#endif + + while (boolean && (retval == JIM_OK || retval == JIM_CONTINUE)) { + + retval = Jim_EvalObj(interp, argv[4]); + + if (retval == JIM_OK || retval == JIM_CONTINUE) { + +JIM_IF_OPTIM(evalnext:) + retval = Jim_EvalObj(interp, argv[3]); + if (retval == JIM_OK || retval == JIM_CONTINUE) { + +JIM_IF_OPTIM(testcond:) + retval = Jim_GetBoolFromExpr(interp, argv[2], &boolean); + } + } + } +JIM_IF_OPTIM(out:) + if (stopVarNamePtr) { + Jim_DecrRefCount(interp, stopVarNamePtr); + } + if (varNamePtr) { + Jim_DecrRefCount(interp, varNamePtr); + } + + if (retval == JIM_CONTINUE || retval == JIM_BREAK || retval == JIM_OK) { + Jim_SetEmptyResult(interp); + return JIM_OK; + } + + return retval; +} + + +static int Jim_LoopCoreCommand(Jim_Interp *interp, int argc, Jim_Obj *const *argv) +{ + int retval; + jim_wide i; + jim_wide limit; + jim_wide incr = 1; + Jim_Obj *bodyObjPtr; + + if (argc != 5 && argc != 6) { + Jim_WrongNumArgs(interp, 1, argv, "var first limit ?incr? body"); + return JIM_ERR; + } + + if (Jim_GetWide(interp, argv[2], &i) != JIM_OK || + Jim_GetWide(interp, argv[3], &limit) != JIM_OK || + (argc == 6 && Jim_GetWide(interp, argv[4], &incr) != JIM_OK)) { + return JIM_ERR; + } + bodyObjPtr = (argc == 5) ? argv[4] : argv[5]; + + retval = Jim_SetVariable(interp, argv[1], argv[2]); + + while (((i < limit && incr > 0) || (i > limit && incr < 0)) && retval == JIM_OK) { + retval = Jim_EvalObj(interp, bodyObjPtr); + if (retval == JIM_OK || retval == JIM_CONTINUE) { + Jim_Obj *objPtr = Jim_GetVariable(interp, argv[1], JIM_ERRMSG); + + retval = JIM_OK; + + + i += incr; + + if (objPtr && !Jim_IsShared(objPtr) && objPtr->typePtr == &intObjType) { + if (argv[1]->typePtr != &variableObjType) { + if (Jim_SetVariable(interp, argv[1], objPtr) != JIM_OK) { + return JIM_ERR; + } + } + JimWideValue(objPtr) = i; + Jim_InvalidateStringRep(objPtr); + + if (argv[1]->typePtr != &variableObjType) { + if (Jim_SetVariable(interp, argv[1], objPtr) != JIM_OK) { + retval = JIM_ERR; + break; + } + } + } + else { + objPtr = Jim_NewIntObj(interp, i); + retval = Jim_SetVariable(interp, argv[1], objPtr); + if (retval != JIM_OK) { + Jim_FreeNewObj(interp, objPtr); + } + } + } + } + + if (retval == JIM_OK || retval == JIM_CONTINUE || retval == JIM_BREAK) { + Jim_SetEmptyResult(interp); + return JIM_OK; + } + return retval; +} + +typedef struct { + Jim_Obj *objPtr; + int idx; +} Jim_ListIter; + +static void JimListIterInit(Jim_ListIter *iter, Jim_Obj *objPtr) +{ + iter->objPtr = objPtr; + iter->idx = 0; +} + +static Jim_Obj *JimListIterNext(Jim_Interp *interp, Jim_ListIter *iter) +{ + if (iter->idx >= Jim_ListLength(interp, iter->objPtr)) { + return NULL; + } + return iter->objPtr->internalRep.listValue.ele[iter->idx++]; +} + +static int JimListIterDone(Jim_Interp *interp, Jim_ListIter *iter) +{ + return iter->idx >= Jim_ListLength(interp, iter->objPtr); +} + + +static int JimForeachMapHelper(Jim_Interp *interp, int argc, Jim_Obj *const *argv, int doMap) +{ + int result = JIM_OK; + int i, numargs; + Jim_ListIter twoiters[2]; + Jim_ListIter *iters; + Jim_Obj *script; + Jim_Obj *resultObj; + + if (argc < 4 || argc % 2 != 0) { + Jim_WrongNumArgs(interp, 1, argv, "varList list ?varList list ...? script"); + return JIM_ERR; + } + script = argv[argc - 1]; + numargs = (argc - 1 - 1); + + if (numargs == 2) { + iters = twoiters; + } + else { + iters = Jim_Alloc(numargs * sizeof(*iters)); + } + for (i = 0; i < numargs; i++) { + JimListIterInit(&iters[i], argv[i + 1]); + if (i % 2 == 0 && JimListIterDone(interp, &iters[i])) { + result = JIM_ERR; + } + } + if (result != JIM_OK) { + Jim_SetResultString(interp, "foreach varlist is empty", -1); + goto empty_varlist; + } + + if (doMap) { + resultObj = Jim_NewListObj(interp, NULL, 0); + } + else { + resultObj = interp->emptyObj; + } + Jim_IncrRefCount(resultObj); + + while (1) { + + for (i = 0; i < numargs; i += 2) { + if (!JimListIterDone(interp, &iters[i + 1])) { + break; + } + } + if (i == numargs) { + + break; + } + + + for (i = 0; i < numargs; i += 2) { + Jim_Obj *varName; + + + JimListIterInit(&iters[i], argv[i + 1]); + while ((varName = JimListIterNext(interp, &iters[i])) != NULL) { + Jim_Obj *valObj = JimListIterNext(interp, &iters[i + 1]); + if (!valObj) { + + valObj = interp->emptyObj; + } + + Jim_IncrRefCount(valObj); + result = Jim_SetVariable(interp, varName, valObj); + Jim_DecrRefCount(interp, valObj); + if (result != JIM_OK) { + goto err; + } + } + } + switch (result = Jim_EvalObj(interp, script)) { + case JIM_OK: + if (doMap) { + Jim_ListAppendElement(interp, resultObj, interp->result); + } + break; + case JIM_CONTINUE: + break; + case JIM_BREAK: + goto out; + default: + goto err; + } + } + out: + result = JIM_OK; + Jim_SetResult(interp, resultObj); + err: + Jim_DecrRefCount(interp, resultObj); + empty_varlist: + if (numargs > 2) { + Jim_Free(iters); + } + return result; +} + + +static int Jim_ForeachCoreCommand(Jim_Interp *interp, int argc, Jim_Obj *const *argv) +{ + return JimForeachMapHelper(interp, argc, argv, 0); +} + + +static int Jim_LmapCoreCommand(Jim_Interp *interp, int argc, Jim_Obj *const *argv) +{ + return JimForeachMapHelper(interp, argc, argv, 1); +} + + +static int Jim_LassignCoreCommand(Jim_Interp *interp, int argc, Jim_Obj *const *argv) +{ + int result = JIM_ERR; + int i; + Jim_ListIter iter; + Jim_Obj *resultObj; + + if (argc < 2) { + Jim_WrongNumArgs(interp, 1, argv, "varList list ?varName ...?"); + return JIM_ERR; + } + + JimListIterInit(&iter, argv[1]); + + for (i = 2; i < argc; i++) { + Jim_Obj *valObj = JimListIterNext(interp, &iter); + result = Jim_SetVariable(interp, argv[i], valObj ? valObj : interp->emptyObj); + if (result != JIM_OK) { + return result; + } + } + + resultObj = Jim_NewListObj(interp, NULL, 0); + while (!JimListIterDone(interp, &iter)) { + Jim_ListAppendElement(interp, resultObj, JimListIterNext(interp, &iter)); + } + + Jim_SetResult(interp, resultObj); + + return JIM_OK; +} + + +static int Jim_IfCoreCommand(Jim_Interp *interp, int argc, Jim_Obj *const *argv) +{ + int boolean, retval, current = 1, falsebody = 0; + + if (argc >= 3) { + while (1) { + + if (current >= argc) + goto err; + if ((retval = Jim_GetBoolFromExpr(interp, argv[current++], &boolean)) + != JIM_OK) + return retval; + + if (current >= argc) + goto err; + if (Jim_CompareStringImmediate(interp, argv[current], "then")) + current++; + + if (current >= argc) + goto err; + if (boolean) + return Jim_EvalObj(interp, argv[current]); + + if (++current >= argc) { + Jim_SetResult(interp, Jim_NewEmptyStringObj(interp)); + return JIM_OK; + } + falsebody = current++; + if (Jim_CompareStringImmediate(interp, argv[falsebody], "else")) { + + if (current != argc - 1) + goto err; + return Jim_EvalObj(interp, argv[current]); + } + else if (Jim_CompareStringImmediate(interp, argv[falsebody], "elseif")) + continue; + + else if (falsebody != argc - 1) + goto err; + return Jim_EvalObj(interp, argv[falsebody]); + } + return JIM_OK; + } + err: + Jim_WrongNumArgs(interp, 1, argv, "condition ?then? trueBody ?elseif ...? ?else? falseBody"); + return JIM_ERR; +} + + + +int Jim_CommandMatchObj(Jim_Interp *interp, Jim_Obj *commandObj, Jim_Obj *patternObj, + Jim_Obj *stringObj, int nocase) +{ + Jim_Obj *parms[4]; + int argc = 0; + long eq; + int rc; + + parms[argc++] = commandObj; + if (nocase) { + parms[argc++] = Jim_NewStringObj(interp, "-nocase", -1); + } + parms[argc++] = patternObj; + parms[argc++] = stringObj; + + rc = Jim_EvalObjVector(interp, argc, parms); + + if (rc != JIM_OK || Jim_GetLong(interp, Jim_GetResult(interp), &eq) != JIM_OK) { + eq = -rc; + } + + return eq; +} + + +static int Jim_SwitchCoreCommand(Jim_Interp *interp, int argc, Jim_Obj *const *argv) +{ + enum { SWITCH_EXACT, SWITCH_GLOB, SWITCH_RE, SWITCH_CMD }; + int matchOpt = SWITCH_EXACT, opt = 1, patCount, i; + Jim_Obj *command = NULL, *scriptObj = NULL, *strObj; + Jim_Obj **caseList; + + if (argc < 3) { + wrongnumargs: + Jim_WrongNumArgs(interp, 1, argv, "?options? string " + "pattern body ... ?default body? or " "{pattern body ?pattern body ...?}"); + return JIM_ERR; + } + for (opt = 1; opt < argc; ++opt) { + const char *option = Jim_String(argv[opt]); + + if (*option != '-') + break; + else if (strncmp(option, "--", 2) == 0) { + ++opt; + break; + } + else if (strncmp(option, "-exact", 2) == 0) + matchOpt = SWITCH_EXACT; + else if (strncmp(option, "-glob", 2) == 0) + matchOpt = SWITCH_GLOB; + else if (strncmp(option, "-regexp", 2) == 0) + matchOpt = SWITCH_RE; + else if (strncmp(option, "-command", 2) == 0) { + matchOpt = SWITCH_CMD; + if ((argc - opt) < 2) + goto wrongnumargs; + command = argv[++opt]; + } + else { + Jim_SetResultFormatted(interp, + "bad option \"%#s\": must be -exact, -glob, -regexp, -command procname or --", + argv[opt]); + return JIM_ERR; + } + if ((argc - opt) < 2) + goto wrongnumargs; + } + strObj = argv[opt++]; + patCount = argc - opt; + if (patCount == 1) { + JimListGetElements(interp, argv[opt], &patCount, &caseList); + } + else + caseList = (Jim_Obj **)&argv[opt]; + if (patCount == 0 || patCount % 2 != 0) + goto wrongnumargs; + for (i = 0; scriptObj == NULL && i < patCount; i += 2) { + Jim_Obj *patObj = caseList[i]; + + if (!Jim_CompareStringImmediate(interp, patObj, "default") + || i < (patCount - 2)) { + switch (matchOpt) { + case SWITCH_EXACT: + if (Jim_StringEqObj(strObj, patObj)) + scriptObj = caseList[i + 1]; + break; + case SWITCH_GLOB: + if (Jim_StringMatchObj(interp, patObj, strObj, 0)) + scriptObj = caseList[i + 1]; + break; + case SWITCH_RE: + command = Jim_NewStringObj(interp, "regexp", -1); + + case SWITCH_CMD:{ + int rc = Jim_CommandMatchObj(interp, command, patObj, strObj, 0); + + if (argc - opt == 1) { + JimListGetElements(interp, argv[opt], &patCount, &caseList); + } + + if (rc < 0) { + return -rc; + } + if (rc) + scriptObj = caseList[i + 1]; + break; + } + } + } + else { + scriptObj = caseList[i + 1]; + } + } + for (; i < patCount && Jim_CompareStringImmediate(interp, scriptObj, "-"); i += 2) + scriptObj = caseList[i + 1]; + if (scriptObj && Jim_CompareStringImmediate(interp, scriptObj, "-")) { + Jim_SetResultFormatted(interp, "no body specified for pattern \"%#s\"", caseList[i - 2]); + return JIM_ERR; + } + Jim_SetEmptyResult(interp); + if (scriptObj) { + return Jim_EvalObj(interp, scriptObj); + } + return JIM_OK; +} + + +static int Jim_ListCoreCommand(Jim_Interp *interp, int argc, Jim_Obj *const *argv) +{ + Jim_Obj *listObjPtr; + + listObjPtr = Jim_NewListObj(interp, argv + 1, argc - 1); + Jim_SetResult(interp, listObjPtr); + return JIM_OK; +} + + +static int Jim_LindexCoreCommand(Jim_Interp *interp, int argc, Jim_Obj *const *argv) +{ + Jim_Obj *objPtr, *listObjPtr; + int i; + int idx; + + if (argc < 2) { + Jim_WrongNumArgs(interp, 1, argv, "list ?index ...?"); + return JIM_ERR; + } + objPtr = argv[1]; + Jim_IncrRefCount(objPtr); + for (i = 2; i < argc; i++) { + listObjPtr = objPtr; + if (Jim_GetIndex(interp, argv[i], &idx) != JIM_OK) { + Jim_DecrRefCount(interp, listObjPtr); + return JIM_ERR; + } + if (Jim_ListIndex(interp, listObjPtr, idx, &objPtr, JIM_NONE) != JIM_OK) { + Jim_DecrRefCount(interp, listObjPtr); + Jim_SetEmptyResult(interp); + return JIM_OK; + } + Jim_IncrRefCount(objPtr); + Jim_DecrRefCount(interp, listObjPtr); + } + Jim_SetResult(interp, objPtr); + Jim_DecrRefCount(interp, objPtr); + return JIM_OK; +} + + +static int Jim_LlengthCoreCommand(Jim_Interp *interp, int argc, Jim_Obj *const *argv) +{ + if (argc != 2) { + Jim_WrongNumArgs(interp, 1, argv, "list"); + return JIM_ERR; + } + Jim_SetResultInt(interp, Jim_ListLength(interp, argv[1])); + return JIM_OK; +} + + +static int Jim_LsearchCoreCommand(Jim_Interp *interp, int argc, Jim_Obj *const *argv) +{ + static const char * const options[] = { + "-bool", "-not", "-nocase", "-exact", "-glob", "-regexp", "-all", "-inline", "-command", + NULL + }; + enum + { OPT_BOOL, OPT_NOT, OPT_NOCASE, OPT_EXACT, OPT_GLOB, OPT_REGEXP, OPT_ALL, OPT_INLINE, + OPT_COMMAND }; + int i; + int opt_bool = 0; + int opt_not = 0; + int opt_nocase = 0; + int opt_all = 0; + int opt_inline = 0; + int opt_match = OPT_EXACT; + int listlen; + int rc = JIM_OK; + Jim_Obj *listObjPtr = NULL; + Jim_Obj *commandObj = NULL; + + if (argc < 3) { + wrongargs: + Jim_WrongNumArgs(interp, 1, argv, + "?-exact|-glob|-regexp|-command 'command'? ?-bool|-inline? ?-not? ?-nocase? ?-all? list value"); + return JIM_ERR; + } + + for (i = 1; i < argc - 2; i++) { + int option; + + if (Jim_GetEnum(interp, argv[i], options, &option, NULL, JIM_ERRMSG) != JIM_OK) { + return JIM_ERR; + } + switch (option) { + case OPT_BOOL: + opt_bool = 1; + opt_inline = 0; + break; + case OPT_NOT: + opt_not = 1; + break; + case OPT_NOCASE: + opt_nocase = 1; + break; + case OPT_INLINE: + opt_inline = 1; + opt_bool = 0; + break; + case OPT_ALL: + opt_all = 1; + break; + case OPT_COMMAND: + if (i >= argc - 2) { + goto wrongargs; + } + commandObj = argv[++i]; + + case OPT_EXACT: + case OPT_GLOB: + case OPT_REGEXP: + opt_match = option; + break; + } + } + + argv += i; + + if (opt_all) { + listObjPtr = Jim_NewListObj(interp, NULL, 0); + } + if (opt_match == OPT_REGEXP) { + commandObj = Jim_NewStringObj(interp, "regexp", -1); + } + if (commandObj) { + Jim_IncrRefCount(commandObj); + } + + listlen = Jim_ListLength(interp, argv[0]); + for (i = 0; i < listlen; i++) { + int eq = 0; + Jim_Obj *objPtr = Jim_ListGetIndex(interp, argv[0], i); + + switch (opt_match) { + case OPT_EXACT: + eq = Jim_StringCompareObj(interp, argv[1], objPtr, opt_nocase) == 0; + break; + + case OPT_GLOB: + eq = Jim_StringMatchObj(interp, argv[1], objPtr, opt_nocase); + break; + + case OPT_REGEXP: + case OPT_COMMAND: + eq = Jim_CommandMatchObj(interp, commandObj, argv[1], objPtr, opt_nocase); + if (eq < 0) { + if (listObjPtr) { + Jim_FreeNewObj(interp, listObjPtr); + } + rc = JIM_ERR; + goto done; + } + break; + } + + + if (!eq && opt_bool && opt_not && !opt_all) { + continue; + } + + if ((!opt_bool && eq == !opt_not) || (opt_bool && (eq || opt_all))) { + + Jim_Obj *resultObj; + + if (opt_bool) { + resultObj = Jim_NewIntObj(interp, eq ^ opt_not); + } + else if (!opt_inline) { + resultObj = Jim_NewIntObj(interp, i); + } + else { + resultObj = objPtr; + } + + if (opt_all) { + Jim_ListAppendElement(interp, listObjPtr, resultObj); + } + else { + Jim_SetResult(interp, resultObj); + goto done; + } + } + } + + if (opt_all) { + Jim_SetResult(interp, listObjPtr); + } + else { + + if (opt_bool) { + Jim_SetResultBool(interp, opt_not); + } + else if (!opt_inline) { + Jim_SetResultInt(interp, -1); + } + } + + done: + if (commandObj) { + Jim_DecrRefCount(interp, commandObj); + } + return rc; +} + + +static int Jim_LappendCoreCommand(Jim_Interp *interp, int argc, Jim_Obj *const *argv) +{ + Jim_Obj *listObjPtr; + int new_obj = 0; + int i; + + if (argc < 2) { + Jim_WrongNumArgs(interp, 1, argv, "varName ?value value ...?"); + return JIM_ERR; + } + listObjPtr = Jim_GetVariable(interp, argv[1], JIM_UNSHARED); + if (!listObjPtr) { + + listObjPtr = Jim_NewListObj(interp, NULL, 0); + new_obj = 1; + } + else if (Jim_IsShared(listObjPtr)) { + listObjPtr = Jim_DuplicateObj(interp, listObjPtr); + new_obj = 1; + } + for (i = 2; i < argc; i++) + Jim_ListAppendElement(interp, listObjPtr, argv[i]); + if (Jim_SetVariable(interp, argv[1], listObjPtr) != JIM_OK) { + if (new_obj) + Jim_FreeNewObj(interp, listObjPtr); + return JIM_ERR; + } + Jim_SetResult(interp, listObjPtr); + return JIM_OK; +} + + +static int Jim_LinsertCoreCommand(Jim_Interp *interp, int argc, Jim_Obj *const *argv) +{ + int idx, len; + Jim_Obj *listPtr; + + if (argc < 3) { + Jim_WrongNumArgs(interp, 1, argv, "list index ?element ...?"); + return JIM_ERR; + } + listPtr = argv[1]; + if (Jim_IsShared(listPtr)) + listPtr = Jim_DuplicateObj(interp, listPtr); + if (Jim_GetIndex(interp, argv[2], &idx) != JIM_OK) + goto err; + len = Jim_ListLength(interp, listPtr); + if (idx >= len) + idx = len; + else if (idx < 0) + idx = len + idx + 1; + Jim_ListInsertElements(interp, listPtr, idx, argc - 3, &argv[3]); + Jim_SetResult(interp, listPtr); + return JIM_OK; + err: + if (listPtr != argv[1]) { + Jim_FreeNewObj(interp, listPtr); + } + return JIM_ERR; +} + + +static int Jim_LreplaceCoreCommand(Jim_Interp *interp, int argc, Jim_Obj *const *argv) +{ + int first, last, len, rangeLen; + Jim_Obj *listObj; + Jim_Obj *newListObj; + + if (argc < 4) { + Jim_WrongNumArgs(interp, 1, argv, "list first last ?element ...?"); + return JIM_ERR; + } + if (Jim_GetIndex(interp, argv[2], &first) != JIM_OK || + Jim_GetIndex(interp, argv[3], &last) != JIM_OK) { + return JIM_ERR; + } + + listObj = argv[1]; + len = Jim_ListLength(interp, listObj); + + first = JimRelToAbsIndex(len, first); + last = JimRelToAbsIndex(len, last); + JimRelToAbsRange(len, &first, &last, &rangeLen); + + + if (first > len) { + first = len; + } + + + newListObj = Jim_NewListObj(interp, listObj->internalRep.listValue.ele, first); + + + ListInsertElements(newListObj, -1, argc - 4, argv + 4); + + + ListInsertElements(newListObj, -1, len - first - rangeLen, listObj->internalRep.listValue.ele + first + rangeLen); + + Jim_SetResult(interp, newListObj); + return JIM_OK; +} + + +static int Jim_LsetCoreCommand(Jim_Interp *interp, int argc, Jim_Obj *const *argv) +{ + if (argc < 3) { + Jim_WrongNumArgs(interp, 1, argv, "listVar ?index...? newVal"); + return JIM_ERR; + } + else if (argc == 3) { + + if (Jim_SetVariable(interp, argv[1], argv[2]) != JIM_OK) + return JIM_ERR; + Jim_SetResult(interp, argv[2]); + return JIM_OK; + } + return Jim_ListSetIndex(interp, argv[1], argv + 2, argc - 3, argv[argc - 1]); +} + + +static int Jim_LsortCoreCommand(Jim_Interp *interp, int argc, Jim_Obj *const argv[]) +{ + static const char * const options[] = { + "-ascii", "-nocase", "-increasing", "-decreasing", "-command", "-integer", "-real", "-index", "-unique", NULL + }; + enum + { OPT_ASCII, OPT_NOCASE, OPT_INCREASING, OPT_DECREASING, OPT_COMMAND, OPT_INTEGER, OPT_REAL, OPT_INDEX, OPT_UNIQUE }; + Jim_Obj *resObj; + int i; + int retCode; + int shared; + + struct lsort_info info; + + if (argc < 2) { + Jim_WrongNumArgs(interp, 1, argv, "?options? list"); + return JIM_ERR; + } + + info.type = JIM_LSORT_ASCII; + info.order = 1; + info.indexed = 0; + info.unique = 0; + info.command = NULL; + info.interp = interp; + + for (i = 1; i < (argc - 1); i++) { + int option; + + if (Jim_GetEnum(interp, argv[i], options, &option, NULL, JIM_ENUM_ABBREV | JIM_ERRMSG) + != JIM_OK) + return JIM_ERR; + switch (option) { + case OPT_ASCII: + info.type = JIM_LSORT_ASCII; + break; + case OPT_NOCASE: + info.type = JIM_LSORT_NOCASE; + break; + case OPT_INTEGER: + info.type = JIM_LSORT_INTEGER; + break; + case OPT_REAL: + info.type = JIM_LSORT_REAL; + break; + case OPT_INCREASING: + info.order = 1; + break; + case OPT_DECREASING: + info.order = -1; + break; + case OPT_UNIQUE: + info.unique = 1; + break; + case OPT_COMMAND: + if (i >= (argc - 2)) { + Jim_SetResultString(interp, "\"-command\" option must be followed by comparison command", -1); + return JIM_ERR; + } + info.type = JIM_LSORT_COMMAND; + info.command = argv[i + 1]; + i++; + break; + case OPT_INDEX: + if (i >= (argc - 2)) { + Jim_SetResultString(interp, "\"-index\" option must be followed by list index", -1); + return JIM_ERR; + } + if (Jim_GetIndex(interp, argv[i + 1], &info.index) != JIM_OK) { + return JIM_ERR; + } + info.indexed = 1; + i++; + break; + } + } + resObj = argv[argc - 1]; + if ((shared = Jim_IsShared(resObj))) + resObj = Jim_DuplicateObj(interp, resObj); + retCode = ListSortElements(interp, resObj, &info); + if (retCode == JIM_OK) { + Jim_SetResult(interp, resObj); + } + else if (shared) { + Jim_FreeNewObj(interp, resObj); + } + return retCode; +} + + +static int Jim_AppendCoreCommand(Jim_Interp *interp, int argc, Jim_Obj *const *argv) +{ + Jim_Obj *stringObjPtr; + int i; + + if (argc < 2) { + Jim_WrongNumArgs(interp, 1, argv, "varName ?value ...?"); + return JIM_ERR; + } + if (argc == 2) { + stringObjPtr = Jim_GetVariable(interp, argv[1], JIM_ERRMSG); + if (!stringObjPtr) + return JIM_ERR; + } + else { + int new_obj = 0; + stringObjPtr = Jim_GetVariable(interp, argv[1], JIM_UNSHARED); + if (!stringObjPtr) { + + stringObjPtr = Jim_NewEmptyStringObj(interp); + new_obj = 1; + } + else if (Jim_IsShared(stringObjPtr)) { + new_obj = 1; + stringObjPtr = Jim_DuplicateObj(interp, stringObjPtr); + } + for (i = 2; i < argc; i++) { + Jim_AppendObj(interp, stringObjPtr, argv[i]); + } + if (Jim_SetVariable(interp, argv[1], stringObjPtr) != JIM_OK) { + if (new_obj) { + Jim_FreeNewObj(interp, stringObjPtr); + } + return JIM_ERR; + } + } + Jim_SetResult(interp, stringObjPtr); + return JIM_OK; +} + + + +static int Jim_DebugCoreCommand(Jim_Interp *interp, int argc, Jim_Obj *const *argv) +{ +#if !defined(JIM_DEBUG_COMMAND) + Jim_SetResultString(interp, "unsupported", -1); + return JIM_ERR; +#endif +} + + +static int Jim_EvalCoreCommand(Jim_Interp *interp, int argc, Jim_Obj *const *argv) +{ + int rc; + + if (argc < 2) { + Jim_WrongNumArgs(interp, 1, argv, "arg ?arg ...?"); + return JIM_ERR; + } + + if (argc == 2) { + rc = Jim_EvalObj(interp, argv[1]); + } + else { + rc = Jim_EvalObj(interp, Jim_ConcatObj(interp, argc - 1, argv + 1)); + } + + if (rc == JIM_ERR) { + + interp->addStackTrace++; + } + return rc; +} + + +static int Jim_UplevelCoreCommand(Jim_Interp *interp, int argc, Jim_Obj *const *argv) +{ + if (argc >= 2) { + int retcode; + Jim_CallFrame *savedCallFrame, *targetCallFrame; + const char *str; + + + savedCallFrame = interp->framePtr; + + + str = Jim_String(argv[1]); + if ((str[0] >= '0' && str[0] <= '9') || str[0] == '#') { + targetCallFrame = Jim_GetCallFrameByLevel(interp, argv[1]); + argc--; + argv++; + } + else { + targetCallFrame = Jim_GetCallFrameByLevel(interp, NULL); + } + if (targetCallFrame == NULL) { + return JIM_ERR; + } + if (argc < 2) { + Jim_WrongNumArgs(interp, 1, argv - 1, "?level? command ?arg ...?"); + return JIM_ERR; + } + + interp->framePtr = targetCallFrame; + if (argc == 2) { + retcode = Jim_EvalObj(interp, argv[1]); + } + else { + retcode = Jim_EvalObj(interp, Jim_ConcatObj(interp, argc - 1, argv + 1)); + } + interp->framePtr = savedCallFrame; + return retcode; + } + else { + Jim_WrongNumArgs(interp, 1, argv, "?level? command ?arg ...?"); + return JIM_ERR; + } +} + + +static int Jim_ExprCoreCommand(Jim_Interp *interp, int argc, Jim_Obj *const *argv) +{ + int retcode; + + if (argc == 2) { + retcode = Jim_EvalExpression(interp, argv[1]); + } + else if (argc > 2) { + Jim_Obj *objPtr; + + objPtr = Jim_ConcatObj(interp, argc - 1, argv + 1); + Jim_IncrRefCount(objPtr); + retcode = Jim_EvalExpression(interp, objPtr); + Jim_DecrRefCount(interp, objPtr); + } + else { + Jim_WrongNumArgs(interp, 1, argv, "expression ?...?"); + return JIM_ERR; + } + if (retcode != JIM_OK) + return retcode; + return JIM_OK; +} + + +static int Jim_BreakCoreCommand(Jim_Interp *interp, int argc, Jim_Obj *const *argv) +{ + if (argc != 1) { + Jim_WrongNumArgs(interp, 1, argv, ""); + return JIM_ERR; + } + return JIM_BREAK; +} + + +static int Jim_ContinueCoreCommand(Jim_Interp *interp, int argc, Jim_Obj *const *argv) +{ + if (argc != 1) { + Jim_WrongNumArgs(interp, 1, argv, ""); + return JIM_ERR; + } + return JIM_CONTINUE; +} + + +static int Jim_ReturnCoreCommand(Jim_Interp *interp, int argc, Jim_Obj *const *argv) +{ + int i; + Jim_Obj *stackTraceObj = NULL; + Jim_Obj *errorCodeObj = NULL; + int returnCode = JIM_OK; + long level = 1; + + for (i = 1; i < argc - 1; i += 2) { + if (Jim_CompareStringImmediate(interp, argv[i], "-code")) { + if (Jim_GetReturnCode(interp, argv[i + 1], &returnCode) == JIM_ERR) { + return JIM_ERR; + } + } + else if (Jim_CompareStringImmediate(interp, argv[i], "-errorinfo")) { + stackTraceObj = argv[i + 1]; + } + else if (Jim_CompareStringImmediate(interp, argv[i], "-errorcode")) { + errorCodeObj = argv[i + 1]; + } + else if (Jim_CompareStringImmediate(interp, argv[i], "-level")) { + if (Jim_GetLong(interp, argv[i + 1], &level) != JIM_OK || level < 0) { + Jim_SetResultFormatted(interp, "bad level \"%#s\"", argv[i + 1]); + return JIM_ERR; + } + } + else { + break; + } + } + + if (i != argc - 1 && i != argc) { + Jim_WrongNumArgs(interp, 1, argv, + "?-code code? ?-errorinfo stacktrace? ?-level level? ?result?"); + } + + + if (stackTraceObj && returnCode == JIM_ERR) { + JimSetStackTrace(interp, stackTraceObj); + } + + if (errorCodeObj && returnCode == JIM_ERR) { + Jim_SetGlobalVariableStr(interp, "errorCode", errorCodeObj); + } + interp->returnCode = returnCode; + interp->returnLevel = level; + + if (i == argc - 1) { + Jim_SetResult(interp, argv[i]); + } + return JIM_RETURN; +} + + +static int Jim_TailcallCoreCommand(Jim_Interp *interp, int argc, Jim_Obj *const *argv) +{ + if (interp->framePtr->level == 0) { + Jim_SetResultString(interp, "tailcall can only be called from a proc or lambda", -1); + return JIM_ERR; + } + else if (argc >= 2) { + + Jim_CallFrame *cf = interp->framePtr->parent; + + Jim_Cmd *cmdPtr = Jim_GetCommand(interp, argv[1], JIM_ERRMSG); + if (cmdPtr == NULL) { + return JIM_ERR; + } + + JimPanic((cf->tailcallCmd != NULL, "Already have a tailcallCmd")); + + + JimIncrCmdRefCount(cmdPtr); + cf->tailcallCmd = cmdPtr; + + + JimPanic((cf->tailcallObj != NULL, "Already have a tailcallobj")); + + cf->tailcallObj = Jim_NewListObj(interp, argv + 1, argc - 1); + Jim_IncrRefCount(cf->tailcallObj); + + + return JIM_EVAL; + } + return JIM_OK; +} + +static int JimAliasCmd(Jim_Interp *interp, int argc, Jim_Obj *const *argv) +{ + Jim_Obj *cmdList; + Jim_Obj *prefixListObj = Jim_CmdPrivData(interp); + + + cmdList = Jim_DuplicateObj(interp, prefixListObj); + Jim_ListInsertElements(interp, cmdList, Jim_ListLength(interp, cmdList), argc - 1, argv + 1); + + return JimEvalObjList(interp, cmdList); +} + +static void JimAliasCmdDelete(Jim_Interp *interp, void *privData) +{ + Jim_Obj *prefixListObj = privData; + Jim_DecrRefCount(interp, prefixListObj); +} + +static int Jim_AliasCoreCommand(Jim_Interp *interp, int argc, Jim_Obj *const *argv) +{ + Jim_Obj *prefixListObj; + const char *newname; + + if (argc < 3) { + Jim_WrongNumArgs(interp, 1, argv, "newname command ?args ...?"); + return JIM_ERR; + } + + prefixListObj = Jim_NewListObj(interp, argv + 2, argc - 2); + Jim_IncrRefCount(prefixListObj); + newname = Jim_String(argv[1]); + if (newname[0] == ':' && newname[1] == ':') { + while (*++newname == ':') { + } + } + + Jim_SetResult(interp, argv[1]); + + return Jim_CreateCommand(interp, newname, JimAliasCmd, prefixListObj, JimAliasCmdDelete); +} + + +static int Jim_ProcCoreCommand(Jim_Interp *interp, int argc, Jim_Obj *const *argv) +{ + Jim_Cmd *cmd; + + if (argc != 4 && argc != 5) { + Jim_WrongNumArgs(interp, 1, argv, "name arglist ?statics? body"); + return JIM_ERR; + } + + if (JimValidName(interp, "procedure", argv[1]) != JIM_OK) { + return JIM_ERR; + } + + if (argc == 4) { + cmd = JimCreateProcedureCmd(interp, argv[2], NULL, argv[3], NULL); + } + else { + cmd = JimCreateProcedureCmd(interp, argv[2], argv[3], argv[4], NULL); + } + + if (cmd) { + + Jim_Obj *qualifiedCmdNameObj; + const char *cmdname = JimQualifyName(interp, Jim_String(argv[1]), &qualifiedCmdNameObj); + + JimCreateCommand(interp, cmdname, cmd); + + + JimUpdateProcNamespace(interp, cmd, cmdname); + + JimFreeQualifiedName(interp, qualifiedCmdNameObj); + + + Jim_SetResult(interp, argv[1]); + return JIM_OK; + } + return JIM_ERR; +} + + +static int Jim_LocalCoreCommand(Jim_Interp *interp, int argc, Jim_Obj *const *argv) +{ + int retcode; + + if (argc < 2) { + Jim_WrongNumArgs(interp, 1, argv, "cmd ?args ...?"); + return JIM_ERR; + } + + + interp->local++; + retcode = Jim_EvalObjVector(interp, argc - 1, argv + 1); + interp->local--; + + + + if (retcode == 0) { + Jim_Obj *cmdNameObj = Jim_GetResult(interp); + + if (Jim_GetCommand(interp, cmdNameObj, JIM_ERRMSG) == NULL) { + return JIM_ERR; + } + if (interp->framePtr->localCommands == NULL) { + interp->framePtr->localCommands = Jim_Alloc(sizeof(*interp->framePtr->localCommands)); + Jim_InitStack(interp->framePtr->localCommands); + } + Jim_IncrRefCount(cmdNameObj); + Jim_StackPush(interp->framePtr->localCommands, cmdNameObj); + } + + return retcode; +} + + +static int Jim_UpcallCoreCommand(Jim_Interp *interp, int argc, Jim_Obj *const *argv) +{ + if (argc < 2) { + Jim_WrongNumArgs(interp, 1, argv, "cmd ?args ...?"); + return JIM_ERR; + } + else { + int retcode; + + Jim_Cmd *cmdPtr = Jim_GetCommand(interp, argv[1], JIM_ERRMSG); + if (cmdPtr == NULL || !cmdPtr->isproc || !cmdPtr->prevCmd) { + Jim_SetResultFormatted(interp, "no previous command: \"%#s\"", argv[1]); + return JIM_ERR; + } + + cmdPtr->u.proc.upcall++; + JimIncrCmdRefCount(cmdPtr); + + + retcode = Jim_EvalObjVector(interp, argc - 1, argv + 1); + + + cmdPtr->u.proc.upcall--; + JimDecrCmdRefCount(interp, cmdPtr); + + return retcode; + } +} + + +static int Jim_ApplyCoreCommand(Jim_Interp *interp, int argc, Jim_Obj *const *argv) +{ + if (argc < 2) { + Jim_WrongNumArgs(interp, 1, argv, "lambdaExpr ?arg ...?"); + return JIM_ERR; + } + else { + int ret; + Jim_Cmd *cmd; + Jim_Obj *argListObjPtr; + Jim_Obj *bodyObjPtr; + Jim_Obj *nsObj = NULL; + Jim_Obj **nargv; + + int len = Jim_ListLength(interp, argv[1]); + if (len != 2 && len != 3) { + Jim_SetResultFormatted(interp, "can't interpret \"%#s\" as a lambda expression", argv[1]); + return JIM_ERR; + } + + if (len == 3) { +#ifdef jim_ext_namespace + + nsObj = JimQualifyNameObj(interp, Jim_ListGetIndex(interp, argv[1], 2)); +#else + Jim_SetResultString(interp, "namespaces not enabled", -1); + return JIM_ERR; +#endif + } + argListObjPtr = Jim_ListGetIndex(interp, argv[1], 0); + bodyObjPtr = Jim_ListGetIndex(interp, argv[1], 1); + + cmd = JimCreateProcedureCmd(interp, argListObjPtr, NULL, bodyObjPtr, nsObj); + + if (cmd) { + + nargv = Jim_Alloc((argc - 2 + 1) * sizeof(*nargv)); + nargv[0] = Jim_NewStringObj(interp, "apply lambdaExpr", -1); + Jim_IncrRefCount(nargv[0]); + memcpy(&nargv[1], argv + 2, (argc - 2) * sizeof(*nargv)); + ret = JimCallProcedure(interp, cmd, argc - 2 + 1, nargv); + Jim_DecrRefCount(interp, nargv[0]); + Jim_Free(nargv); + + JimDecrCmdRefCount(interp, cmd); + return ret; + } + return JIM_ERR; + } +} + + + +static int Jim_ConcatCoreCommand(Jim_Interp *interp, int argc, Jim_Obj *const *argv) +{ + Jim_SetResult(interp, Jim_ConcatObj(interp, argc - 1, argv + 1)); + return JIM_OK; +} + + +static int Jim_UpvarCoreCommand(Jim_Interp *interp, int argc, Jim_Obj *const *argv) +{ + int i; + Jim_CallFrame *targetCallFrame; + + + if (argc > 3 && (argc % 2 == 0)) { + targetCallFrame = Jim_GetCallFrameByLevel(interp, argv[1]); + argc--; + argv++; + } + else { + targetCallFrame = Jim_GetCallFrameByLevel(interp, NULL); + } + if (targetCallFrame == NULL) { + return JIM_ERR; + } + + + if (argc < 3) { + Jim_WrongNumArgs(interp, 1, argv, "?level? otherVar localVar ?otherVar localVar ...?"); + return JIM_ERR; + } + + + for (i = 1; i < argc; i += 2) { + if (Jim_SetVariableLink(interp, argv[i + 1], argv[i], targetCallFrame) != JIM_OK) + return JIM_ERR; + } + return JIM_OK; +} + + +static int Jim_GlobalCoreCommand(Jim_Interp *interp, int argc, Jim_Obj *const *argv) +{ + int i; + + if (argc < 2) { + Jim_WrongNumArgs(interp, 1, argv, "varName ?varName ...?"); + return JIM_ERR; + } + + if (interp->framePtr->level == 0) + return JIM_OK; + for (i = 1; i < argc; i++) { + + const char *name = Jim_String(argv[i]); + if (name[0] != ':' || name[1] != ':') { + if (Jim_SetVariableLink(interp, argv[i], argv[i], interp->topFramePtr) != JIM_OK) + return JIM_ERR; + } + } + return JIM_OK; +} + +static Jim_Obj *JimStringMap(Jim_Interp *interp, Jim_Obj *mapListObjPtr, + Jim_Obj *objPtr, int nocase) +{ + int numMaps; + const char *str, *noMatchStart = NULL; + int strLen, i; + Jim_Obj *resultObjPtr; + + numMaps = Jim_ListLength(interp, mapListObjPtr); + if (numMaps % 2) { + Jim_SetResultString(interp, "list must contain an even number of elements", -1); + return NULL; + } + + str = Jim_String(objPtr); + strLen = Jim_Utf8Length(interp, objPtr); + + + resultObjPtr = Jim_NewStringObj(interp, "", 0); + while (strLen) { + for (i = 0; i < numMaps; i += 2) { + Jim_Obj *eachObjPtr; + const char *k; + int kl; + + eachObjPtr = Jim_ListGetIndex(interp, mapListObjPtr, i); + k = Jim_String(eachObjPtr); + kl = Jim_Utf8Length(interp, eachObjPtr); + + if (strLen >= kl && kl) { + int rc; + rc = JimStringCompareLen(str, k, kl, nocase); + if (rc == 0) { + if (noMatchStart) { + Jim_AppendString(interp, resultObjPtr, noMatchStart, str - noMatchStart); + noMatchStart = NULL; + } + Jim_AppendObj(interp, resultObjPtr, Jim_ListGetIndex(interp, mapListObjPtr, i + 1)); + str += utf8_index(str, kl); + strLen -= kl; + break; + } + } + } + if (i == numMaps) { + int c; + if (noMatchStart == NULL) + noMatchStart = str; + str += utf8_tounicode(str, &c); + strLen--; + } + } + if (noMatchStart) { + Jim_AppendString(interp, resultObjPtr, noMatchStart, str - noMatchStart); + } + return resultObjPtr; +} + + +static int Jim_StringCoreCommand(Jim_Interp *interp, int argc, Jim_Obj *const *argv) +{ + int len; + int opt_case = 1; + int option; + static const char * const options[] = { + "bytelength", "length", "compare", "match", "equal", "is", "byterange", "range", "replace", + "map", "repeat", "reverse", "index", "first", "last", "cat", + "trim", "trimleft", "trimright", "tolower", "toupper", "totitle", NULL + }; + enum + { + OPT_BYTELENGTH, OPT_LENGTH, OPT_COMPARE, OPT_MATCH, OPT_EQUAL, OPT_IS, OPT_BYTERANGE, OPT_RANGE, OPT_REPLACE, + OPT_MAP, OPT_REPEAT, OPT_REVERSE, OPT_INDEX, OPT_FIRST, OPT_LAST, OPT_CAT, + OPT_TRIM, OPT_TRIMLEFT, OPT_TRIMRIGHT, OPT_TOLOWER, OPT_TOUPPER, OPT_TOTITLE + }; + static const char * const nocase_options[] = { + "-nocase", NULL + }; + static const char * const nocase_length_options[] = { + "-nocase", "-length", NULL + }; + + if (argc < 2) { + Jim_WrongNumArgs(interp, 1, argv, "option ?arguments ...?"); + return JIM_ERR; + } + if (Jim_GetEnum(interp, argv[1], options, &option, NULL, + JIM_ERRMSG | JIM_ENUM_ABBREV) != JIM_OK) + return Jim_CheckShowCommands(interp, argv[1], options); + + switch (option) { + case OPT_LENGTH: + case OPT_BYTELENGTH: + if (argc != 3) { + Jim_WrongNumArgs(interp, 2, argv, "string"); + return JIM_ERR; + } + if (option == OPT_LENGTH) { + len = Jim_Utf8Length(interp, argv[2]); + } + else { + len = Jim_Length(argv[2]); + } + Jim_SetResultInt(interp, len); + return JIM_OK; + + case OPT_CAT:{ + Jim_Obj *objPtr; + if (argc == 3) { + + objPtr = argv[2]; + } + else { + int i; + + objPtr = Jim_NewStringObj(interp, "", 0); + + for (i = 2; i < argc; i++) { + Jim_AppendObj(interp, objPtr, argv[i]); + } + } + Jim_SetResult(interp, objPtr); + return JIM_OK; + } + + case OPT_COMPARE: + case OPT_EQUAL: + { + + long opt_length = -1; + int n = argc - 4; + int i = 2; + while (n > 0) { + int subopt; + if (Jim_GetEnum(interp, argv[i++], nocase_length_options, &subopt, NULL, + JIM_ENUM_ABBREV) != JIM_OK) { +badcompareargs: + Jim_WrongNumArgs(interp, 2, argv, "?-nocase? ?-length int? string1 string2"); + return JIM_ERR; + } + if (subopt == 0) { + + opt_case = 0; + n--; + } + else { + + if (n < 2) { + goto badcompareargs; + } + if (Jim_GetLong(interp, argv[i++], &opt_length) != JIM_OK) { + return JIM_ERR; + } + n -= 2; + } + } + if (n) { + goto badcompareargs; + } + argv += argc - 2; + if (opt_length < 0 && option != OPT_COMPARE && opt_case) { + + Jim_SetResultBool(interp, Jim_StringEqObj(argv[0], argv[1])); + } + else { + if (opt_length >= 0) { + n = JimStringCompareLen(Jim_String(argv[0]), Jim_String(argv[1]), opt_length, !opt_case); + } + else { + n = Jim_StringCompareObj(interp, argv[0], argv[1], !opt_case); + } + Jim_SetResultInt(interp, option == OPT_COMPARE ? n : n == 0); + } + return JIM_OK; + } + + case OPT_MATCH: + if (argc != 4 && + (argc != 5 || + Jim_GetEnum(interp, argv[2], nocase_options, &opt_case, NULL, + JIM_ENUM_ABBREV) != JIM_OK)) { + Jim_WrongNumArgs(interp, 2, argv, "?-nocase? pattern string"); + return JIM_ERR; + } + if (opt_case == 0) { + argv++; + } + Jim_SetResultBool(interp, Jim_StringMatchObj(interp, argv[2], argv[3], !opt_case)); + return JIM_OK; + + case OPT_MAP:{ + Jim_Obj *objPtr; + + if (argc != 4 && + (argc != 5 || + Jim_GetEnum(interp, argv[2], nocase_options, &opt_case, NULL, + JIM_ENUM_ABBREV) != JIM_OK)) { + Jim_WrongNumArgs(interp, 2, argv, "?-nocase? mapList string"); + return JIM_ERR; + } + + if (opt_case == 0) { + argv++; + } + objPtr = JimStringMap(interp, argv[2], argv[3], !opt_case); + if (objPtr == NULL) { + return JIM_ERR; + } + Jim_SetResult(interp, objPtr); + return JIM_OK; + } + + case OPT_RANGE: + case OPT_BYTERANGE:{ + Jim_Obj *objPtr; + + if (argc != 5) { + Jim_WrongNumArgs(interp, 2, argv, "string first last"); + return JIM_ERR; + } + if (option == OPT_RANGE) { + objPtr = Jim_StringRangeObj(interp, argv[2], argv[3], argv[4]); + } + else + { + objPtr = Jim_StringByteRangeObj(interp, argv[2], argv[3], argv[4]); + } + + if (objPtr == NULL) { + return JIM_ERR; + } + Jim_SetResult(interp, objPtr); + return JIM_OK; + } + + case OPT_REPLACE:{ + Jim_Obj *objPtr; + + if (argc != 5 && argc != 6) { + Jim_WrongNumArgs(interp, 2, argv, "string first last ?string?"); + return JIM_ERR; + } + objPtr = JimStringReplaceObj(interp, argv[2], argv[3], argv[4], argc == 6 ? argv[5] : NULL); + if (objPtr == NULL) { + return JIM_ERR; + } + Jim_SetResult(interp, objPtr); + return JIM_OK; + } + + + case OPT_REPEAT:{ + Jim_Obj *objPtr; + jim_wide count; + + if (argc != 4) { + Jim_WrongNumArgs(interp, 2, argv, "string count"); + return JIM_ERR; + } + if (Jim_GetWide(interp, argv[3], &count) != JIM_OK) { + return JIM_ERR; + } + objPtr = Jim_NewStringObj(interp, "", 0); + if (count > 0) { + while (count--) { + Jim_AppendObj(interp, objPtr, argv[2]); + } + } + Jim_SetResult(interp, objPtr); + return JIM_OK; + } + + case OPT_REVERSE:{ + char *buf, *p; + const char *str; + int i; + + if (argc != 3) { + Jim_WrongNumArgs(interp, 2, argv, "string"); + return JIM_ERR; + } + + str = Jim_GetString(argv[2], &len); + buf = Jim_Alloc(len + 1); + p = buf + len; + *p = 0; + for (i = 0; i < len; ) { + int c; + int l = utf8_tounicode(str, &c); + memcpy(p - l, str, l); + p -= l; + i += l; + str += l; + } + Jim_SetResult(interp, Jim_NewStringObjNoAlloc(interp, buf, len)); + return JIM_OK; + } + + case OPT_INDEX:{ + int idx; + const char *str; + + if (argc != 4) { + Jim_WrongNumArgs(interp, 2, argv, "string index"); + return JIM_ERR; + } + if (Jim_GetIndex(interp, argv[3], &idx) != JIM_OK) { + return JIM_ERR; + } + str = Jim_String(argv[2]); + len = Jim_Utf8Length(interp, argv[2]); + if (idx != INT_MIN && idx != INT_MAX) { + idx = JimRelToAbsIndex(len, idx); + } + if (idx < 0 || idx >= len || str == NULL) { + Jim_SetResultString(interp, "", 0); + } + else if (len == Jim_Length(argv[2])) { + + Jim_SetResultString(interp, str + idx, 1); + } + else { + int c; + int i = utf8_index(str, idx); + Jim_SetResultString(interp, str + i, utf8_tounicode(str + i, &c)); + } + return JIM_OK; + } + + case OPT_FIRST: + case OPT_LAST:{ + int idx = 0, l1, l2; + const char *s1, *s2; + + if (argc != 4 && argc != 5) { + Jim_WrongNumArgs(interp, 2, argv, "subString string ?index?"); + return JIM_ERR; + } + s1 = Jim_String(argv[2]); + s2 = Jim_String(argv[3]); + l1 = Jim_Utf8Length(interp, argv[2]); + l2 = Jim_Utf8Length(interp, argv[3]); + if (argc == 5) { + if (Jim_GetIndex(interp, argv[4], &idx) != JIM_OK) { + return JIM_ERR; + } + idx = JimRelToAbsIndex(l2, idx); + } + else if (option == OPT_LAST) { + idx = l2; + } + if (option == OPT_FIRST) { + Jim_SetResultInt(interp, JimStringFirst(s1, l1, s2, l2, idx)); + } + else { +#ifdef JIM_UTF8 + Jim_SetResultInt(interp, JimStringLastUtf8(s1, l1, s2, idx)); +#else + Jim_SetResultInt(interp, JimStringLast(s1, l1, s2, idx)); +#endif + } + return JIM_OK; + } + + case OPT_TRIM: + case OPT_TRIMLEFT: + case OPT_TRIMRIGHT:{ + Jim_Obj *trimchars; + + if (argc != 3 && argc != 4) { + Jim_WrongNumArgs(interp, 2, argv, "string ?trimchars?"); + return JIM_ERR; + } + trimchars = (argc == 4 ? argv[3] : NULL); + if (option == OPT_TRIM) { + Jim_SetResult(interp, JimStringTrim(interp, argv[2], trimchars)); + } + else if (option == OPT_TRIMLEFT) { + Jim_SetResult(interp, JimStringTrimLeft(interp, argv[2], trimchars)); + } + else if (option == OPT_TRIMRIGHT) { + Jim_SetResult(interp, JimStringTrimRight(interp, argv[2], trimchars)); + } + return JIM_OK; + } + + case OPT_TOLOWER: + case OPT_TOUPPER: + case OPT_TOTITLE: + if (argc != 3) { + Jim_WrongNumArgs(interp, 2, argv, "string"); + return JIM_ERR; + } + if (option == OPT_TOLOWER) { + Jim_SetResult(interp, JimStringToLower(interp, argv[2])); + } + else if (option == OPT_TOUPPER) { + Jim_SetResult(interp, JimStringToUpper(interp, argv[2])); + } + else { + Jim_SetResult(interp, JimStringToTitle(interp, argv[2])); + } + return JIM_OK; + + case OPT_IS: + if (argc == 4 || (argc == 5 && Jim_CompareStringImmediate(interp, argv[3], "-strict"))) { + return JimStringIs(interp, argv[argc - 1], argv[2], argc == 5); + } + Jim_WrongNumArgs(interp, 2, argv, "class ?-strict? str"); + return JIM_ERR; + } + return JIM_OK; +} + + +static int Jim_TimeCoreCommand(Jim_Interp *interp, int argc, Jim_Obj *const *argv) +{ + long i, count = 1; + jim_wide start, elapsed; + char buf[60]; + const char *fmt = "%" JIM_WIDE_MODIFIER " microseconds per iteration"; + + if (argc < 2) { + Jim_WrongNumArgs(interp, 1, argv, "script ?count?"); + return JIM_ERR; + } + if (argc == 3) { + if (Jim_GetLong(interp, argv[2], &count) != JIM_OK) + return JIM_ERR; + } + if (count < 0) + return JIM_OK; + i = count; + start = JimClock(); + while (i-- > 0) { + int retval; + + retval = Jim_EvalObj(interp, argv[1]); + if (retval != JIM_OK) { + return retval; + } + } + elapsed = JimClock() - start; + sprintf(buf, fmt, count == 0 ? 0 : elapsed / count); + Jim_SetResultString(interp, buf, -1); + return JIM_OK; +} + + +static int Jim_ExitCoreCommand(Jim_Interp *interp, int argc, Jim_Obj *const *argv) +{ + long exitCode = 0; + + if (argc > 2) { + Jim_WrongNumArgs(interp, 1, argv, "?exitCode?"); + return JIM_ERR; + } + if (argc == 2) { + if (Jim_GetLong(interp, argv[1], &exitCode) != JIM_OK) + return JIM_ERR; + } + interp->exitCode = exitCode; + return JIM_EXIT; +} + + +static int Jim_CatchCoreCommand(Jim_Interp *interp, int argc, Jim_Obj *const *argv) +{ + int exitCode = 0; + int i; + int sig = 0; + + + jim_wide ignore_mask = (1 << JIM_EXIT) | (1 << JIM_EVAL) | (1 << JIM_SIGNAL); + static const int max_ignore_code = sizeof(ignore_mask) * 8; + + Jim_SetGlobalVariableStr(interp, "errorCode", Jim_NewStringObj(interp, "NONE", -1)); + + for (i = 1; i < argc - 1; i++) { + const char *arg = Jim_String(argv[i]); + jim_wide option; + int ignore; + + + if (strcmp(arg, "--") == 0) { + i++; + break; + } + if (*arg != '-') { + break; + } + + if (strncmp(arg, "-no", 3) == 0) { + arg += 3; + ignore = 1; + } + else { + arg++; + ignore = 0; + } + + if (Jim_StringToWide(arg, &option, 10) != JIM_OK) { + option = -1; + } + if (option < 0) { + option = Jim_FindByName(arg, jimReturnCodes, jimReturnCodesSize); + } + if (option < 0) { + goto wrongargs; + } + + if (ignore) { + ignore_mask |= ((jim_wide)1 << option); + } + else { + ignore_mask &= (~((jim_wide)1 << option)); + } + } + + argc -= i; + if (argc < 1 || argc > 3) { + wrongargs: + Jim_WrongNumArgs(interp, 1, argv, + "?-?no?code ... --? script ?resultVarName? ?optionVarName?"); + return JIM_ERR; + } + argv += i; + + if ((ignore_mask & (1 << JIM_SIGNAL)) == 0) { + sig++; + } + + interp->signal_level += sig; + if (Jim_CheckSignal(interp)) { + + exitCode = JIM_SIGNAL; + } + else { + exitCode = Jim_EvalObj(interp, argv[0]); + + interp->errorFlag = 0; + } + interp->signal_level -= sig; + + + if (exitCode >= 0 && exitCode < max_ignore_code && (((unsigned jim_wide)1 << exitCode) & ignore_mask)) { + + return exitCode; + } + + if (sig && exitCode == JIM_SIGNAL) { + + if (interp->signal_set_result) { + interp->signal_set_result(interp, interp->sigmask); + } + else { + Jim_SetResultInt(interp, interp->sigmask); + } + interp->sigmask = 0; + } + + if (argc >= 2) { + if (Jim_SetVariable(interp, argv[1], Jim_GetResult(interp)) != JIM_OK) { + return JIM_ERR; + } + if (argc == 3) { + Jim_Obj *optListObj = Jim_NewListObj(interp, NULL, 0); + + Jim_ListAppendElement(interp, optListObj, Jim_NewStringObj(interp, "-code", -1)); + Jim_ListAppendElement(interp, optListObj, + Jim_NewIntObj(interp, exitCode == JIM_RETURN ? interp->returnCode : exitCode)); + Jim_ListAppendElement(interp, optListObj, Jim_NewStringObj(interp, "-level", -1)); + Jim_ListAppendElement(interp, optListObj, Jim_NewIntObj(interp, interp->returnLevel)); + if (exitCode == JIM_ERR) { + Jim_Obj *errorCode; + Jim_ListAppendElement(interp, optListObj, Jim_NewStringObj(interp, "-errorinfo", + -1)); + Jim_ListAppendElement(interp, optListObj, interp->stackTrace); + + errorCode = Jim_GetGlobalVariableStr(interp, "errorCode", JIM_NONE); + if (errorCode) { + Jim_ListAppendElement(interp, optListObj, Jim_NewStringObj(interp, "-errorcode", -1)); + Jim_ListAppendElement(interp, optListObj, errorCode); + } + } + if (Jim_SetVariable(interp, argv[2], optListObj) != JIM_OK) { + return JIM_ERR; + } + } + } + Jim_SetResultInt(interp, exitCode); + return JIM_OK; +} + + + +static int Jim_RenameCoreCommand(Jim_Interp *interp, int argc, Jim_Obj *const *argv) +{ + if (argc != 3) { + Jim_WrongNumArgs(interp, 1, argv, "oldName newName"); + return JIM_ERR; + } + + if (JimValidName(interp, "new procedure", argv[2])) { + return JIM_ERR; + } + + return Jim_RenameCommand(interp, Jim_String(argv[1]), Jim_String(argv[2])); +} + +#define JIM_DICTMATCH_KEYS 0x0001 +#define JIM_DICTMATCH_VALUES 0x002 + +int Jim_DictMatchTypes(Jim_Interp *interp, Jim_Obj *objPtr, Jim_Obj *patternObj, int match_type, int return_types) +{ + Jim_HashEntry *he; + Jim_Obj *listObjPtr; + Jim_HashTableIterator htiter; + + if (SetDictFromAny(interp, objPtr) != JIM_OK) { + return JIM_ERR; + } + + listObjPtr = Jim_NewListObj(interp, NULL, 0); + + JimInitHashTableIterator(objPtr->internalRep.ptr, &htiter); + while ((he = Jim_NextHashEntry(&htiter)) != NULL) { + if (patternObj) { + Jim_Obj *matchObj = (match_type == JIM_DICTMATCH_KEYS) ? (Jim_Obj *)he->key : Jim_GetHashEntryVal(he); + if (!JimGlobMatch(Jim_String(patternObj), Jim_String(matchObj), 0)) { + + continue; + } + } + if (return_types & JIM_DICTMATCH_KEYS) { + Jim_ListAppendElement(interp, listObjPtr, (Jim_Obj *)he->key); + } + if (return_types & JIM_DICTMATCH_VALUES) { + Jim_ListAppendElement(interp, listObjPtr, Jim_GetHashEntryVal(he)); + } + } + + Jim_SetResult(interp, listObjPtr); + return JIM_OK; +} + +int Jim_DictSize(Jim_Interp *interp, Jim_Obj *objPtr) +{ + if (SetDictFromAny(interp, objPtr) != JIM_OK) { + return -1; + } + return ((Jim_HashTable *)objPtr->internalRep.ptr)->used; +} + +Jim_Obj *Jim_DictMerge(Jim_Interp *interp, int objc, Jim_Obj *const *objv) +{ + Jim_Obj *objPtr = Jim_NewDictObj(interp, NULL, 0); + int i; + + JimPanic((objc == 0, "Jim_DictMerge called with objc=0")); + + + + for (i = 0; i < objc; i++) { + Jim_HashTable *ht; + Jim_HashTableIterator htiter; + Jim_HashEntry *he; + + if (SetDictFromAny(interp, objv[i]) != JIM_OK) { + Jim_FreeNewObj(interp, objPtr); + return NULL; + } + ht = objv[i]->internalRep.ptr; + JimInitHashTableIterator(ht, &htiter); + while ((he = Jim_NextHashEntry(&htiter)) != NULL) { + Jim_ReplaceHashEntry(objPtr->internalRep.ptr, Jim_GetHashEntryKey(he), Jim_GetHashEntryVal(he)); + } + } + return objPtr; +} + +int Jim_DictInfo(Jim_Interp *interp, Jim_Obj *objPtr) +{ + Jim_HashTable *ht; + unsigned int i; + char buffer[100]; + int sum = 0; + int nonzero_count = 0; + Jim_Obj *output; + int bucket_counts[11] = { 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0 }; + + if (SetDictFromAny(interp, objPtr) != JIM_OK) { + return JIM_ERR; + } + + ht = (Jim_HashTable *)objPtr->internalRep.ptr; + + + snprintf(buffer, sizeof(buffer), "%d entries in table, %d buckets\n", ht->used, ht->size); + output = Jim_NewStringObj(interp, buffer, -1); + + for (i = 0; i < ht->size; i++) { + Jim_HashEntry *he = ht->table[i]; + int entries = 0; + while (he) { + entries++; + he = he->next; + } + if (entries > 9) { + bucket_counts[10]++; + } + else { + bucket_counts[entries]++; + } + if (entries) { + sum += entries; + nonzero_count++; + } + } + for (i = 0; i < 10; i++) { + snprintf(buffer, sizeof(buffer), "number of buckets with %d entries: %d\n", i, bucket_counts[i]); + Jim_AppendString(interp, output, buffer, -1); + } + snprintf(buffer, sizeof(buffer), "number of buckets with 10 or more entries: %d\n", bucket_counts[10]); + Jim_AppendString(interp, output, buffer, -1); + snprintf(buffer, sizeof(buffer), "average search distance for entry: %.1f", nonzero_count ? (double)sum / nonzero_count : 0.0); + Jim_AppendString(interp, output, buffer, -1); + Jim_SetResult(interp, output); + return JIM_OK; +} + +static int Jim_EvalEnsemble(Jim_Interp *interp, const char *basecmd, const char *subcmd, int argc, Jim_Obj *const *argv) +{ + Jim_Obj *prefixObj = Jim_NewStringObj(interp, basecmd, -1); + + Jim_AppendString(interp, prefixObj, " ", 1); + Jim_AppendString(interp, prefixObj, subcmd, -1); + + return Jim_EvalObjPrefix(interp, prefixObj, argc, argv); +} + +static int JimDictWith(Jim_Interp *interp, Jim_Obj *dictVarName, Jim_Obj *const *keyv, int keyc, Jim_Obj *scriptObj) +{ + int i; + Jim_Obj *objPtr; + Jim_Obj *dictObj; + Jim_Obj **dictValues; + int len; + int ret = JIM_OK; + + + dictObj = Jim_GetVariable(interp, dictVarName, JIM_ERRMSG); + if (dictObj == NULL || Jim_DictKeysVector(interp, dictObj, keyv, keyc, &objPtr, JIM_ERRMSG) != JIM_OK) { + return JIM_ERR; + } + + if (Jim_DictPairs(interp, objPtr, &dictValues, &len) == JIM_ERR) { + return JIM_ERR; + } + for (i = 0; i < len; i += 2) { + if (Jim_SetVariable(interp, dictValues[i], dictValues[i + 1]) == JIM_ERR) { + Jim_Free(dictValues); + return JIM_ERR; + } + } + + + if (Jim_Length(scriptObj)) { + ret = Jim_EvalObj(interp, scriptObj); + + + if (ret == JIM_OK && Jim_GetVariable(interp, dictVarName, 0) != NULL) { + + Jim_Obj **newkeyv = Jim_Alloc(sizeof(*newkeyv) * (keyc + 1)); + for (i = 0; i < keyc; i++) { + newkeyv[i] = keyv[i]; + } + + for (i = 0; i < len; i += 2) { + + objPtr = Jim_GetVariable(interp, dictValues[i], 0); + newkeyv[keyc] = dictValues[i]; + Jim_SetDictKeysVector(interp, dictVarName, newkeyv, keyc + 1, objPtr, 0); + } + Jim_Free(newkeyv); + } + } + + Jim_Free(dictValues); + + return ret; +} + + +static int Jim_DictCoreCommand(Jim_Interp *interp, int argc, Jim_Obj *const *argv) +{ + Jim_Obj *objPtr; + int types = JIM_DICTMATCH_KEYS; + int option; + static const char * const options[] = { + "create", "get", "set", "unset", "exists", "keys", "size", "info", + "merge", "with", "append", "lappend", "incr", "remove", "values", "for", + "replace", "update", NULL + }; + enum + { + OPT_CREATE, OPT_GET, OPT_SET, OPT_UNSET, OPT_EXISTS, OPT_KEYS, OPT_SIZE, OPT_INFO, + OPT_MERGE, OPT_WITH, OPT_APPEND, OPT_LAPPEND, OPT_INCR, OPT_REMOVE, OPT_VALUES, OPT_FOR, + OPT_REPLACE, OPT_UPDATE, + }; + + if (argc < 2) { + Jim_WrongNumArgs(interp, 1, argv, "subcommand ?arguments ...?"); + return JIM_ERR; + } + + if (Jim_GetEnum(interp, argv[1], options, &option, "subcommand", JIM_ERRMSG) != JIM_OK) { + return Jim_CheckShowCommands(interp, argv[1], options); + } + + switch (option) { + case OPT_GET: + if (argc < 3) { + Jim_WrongNumArgs(interp, 2, argv, "dictionary ?key ...?"); + return JIM_ERR; + } + if (Jim_DictKeysVector(interp, argv[2], argv + 3, argc - 3, &objPtr, + JIM_ERRMSG) != JIM_OK) { + return JIM_ERR; + } + Jim_SetResult(interp, objPtr); + return JIM_OK; + + case OPT_SET: + if (argc < 5) { + Jim_WrongNumArgs(interp, 2, argv, "varName key ?key ...? value"); + return JIM_ERR; + } + return Jim_SetDictKeysVector(interp, argv[2], argv + 3, argc - 4, argv[argc - 1], JIM_ERRMSG); + + case OPT_EXISTS: + if (argc < 4) { + Jim_WrongNumArgs(interp, 2, argv, "dictionary key ?key ...?"); + return JIM_ERR; + } + else { + int rc = Jim_DictKeysVector(interp, argv[2], argv + 3, argc - 3, &objPtr, JIM_ERRMSG); + if (rc < 0) { + return JIM_ERR; + } + Jim_SetResultBool(interp, rc == JIM_OK); + return JIM_OK; + } + + case OPT_UNSET: + if (argc < 4) { + Jim_WrongNumArgs(interp, 2, argv, "varName key ?key ...?"); + return JIM_ERR; + } + if (Jim_SetDictKeysVector(interp, argv[2], argv + 3, argc - 3, NULL, 0) != JIM_OK) { + return JIM_ERR; + } + return JIM_OK; + + case OPT_VALUES: + types = JIM_DICTMATCH_VALUES; + + case OPT_KEYS: + if (argc != 3 && argc != 4) { + Jim_WrongNumArgs(interp, 2, argv, "dictionary ?pattern?"); + return JIM_ERR; + } + return Jim_DictMatchTypes(interp, argv[2], argc == 4 ? argv[3] : NULL, types, types); + + case OPT_SIZE: + if (argc != 3) { + Jim_WrongNumArgs(interp, 2, argv, "dictionary"); + return JIM_ERR; + } + else if (Jim_DictSize(interp, argv[2]) < 0) { + return JIM_ERR; + } + Jim_SetResultInt(interp, Jim_DictSize(interp, argv[2])); + return JIM_OK; + + case OPT_MERGE: + if (argc == 2) { + return JIM_OK; + } + objPtr = Jim_DictMerge(interp, argc - 2, argv + 2); + if (objPtr == NULL) { + return JIM_ERR; + } + Jim_SetResult(interp, objPtr); + return JIM_OK; + + case OPT_UPDATE: + if (argc < 6 || argc % 2) { + + argc = 2; + } + break; + + case OPT_CREATE: + if (argc % 2) { + Jim_WrongNumArgs(interp, 2, argv, "?key value ...?"); + return JIM_ERR; + } + objPtr = Jim_NewDictObj(interp, argv + 2, argc - 2); + Jim_SetResult(interp, objPtr); + return JIM_OK; + + case OPT_INFO: + if (argc != 3) { + Jim_WrongNumArgs(interp, 2, argv, "dictionary"); + return JIM_ERR; + } + return Jim_DictInfo(interp, argv[2]); + + case OPT_WITH: + if (argc < 4) { + Jim_WrongNumArgs(interp, 2, argv, "dictVar ?key ...? script"); + return JIM_ERR; + } + return JimDictWith(interp, argv[2], argv + 3, argc - 4, argv[argc - 1]); + } + + return Jim_EvalEnsemble(interp, "dict", options[option], argc - 2, argv + 2); +} + + +static int Jim_SubstCoreCommand(Jim_Interp *interp, int argc, Jim_Obj *const *argv) +{ + static const char * const options[] = { + "-nobackslashes", "-nocommands", "-novariables", NULL + }; + enum + { OPT_NOBACKSLASHES, OPT_NOCOMMANDS, OPT_NOVARIABLES }; + int i; + int flags = JIM_SUBST_FLAG; + Jim_Obj *objPtr; + + if (argc < 2) { + Jim_WrongNumArgs(interp, 1, argv, "?options? string"); + return JIM_ERR; + } + for (i = 1; i < (argc - 1); i++) { + int option; + + if (Jim_GetEnum(interp, argv[i], options, &option, NULL, + JIM_ERRMSG | JIM_ENUM_ABBREV) != JIM_OK) { + return JIM_ERR; + } + switch (option) { + case OPT_NOBACKSLASHES: + flags |= JIM_SUBST_NOESC; + break; + case OPT_NOCOMMANDS: + flags |= JIM_SUBST_NOCMD; + break; + case OPT_NOVARIABLES: + flags |= JIM_SUBST_NOVAR; + break; + } + } + if (Jim_SubstObj(interp, argv[argc - 1], &objPtr, flags) != JIM_OK) { + return JIM_ERR; + } + Jim_SetResult(interp, objPtr); + return JIM_OK; +} + + +static int Jim_InfoCoreCommand(Jim_Interp *interp, int argc, Jim_Obj *const *argv) +{ + int cmd; + Jim_Obj *objPtr; + int mode = 0; + + static const char * const commands[] = { + "body", "statics", "commands", "procs", "channels", "exists", "globals", "level", "frame", "locals", + "vars", "version", "patchlevel", "complete", "args", "hostname", + "script", "source", "stacktrace", "nameofexecutable", "returncodes", + "references", "alias", NULL + }; + enum + { INFO_BODY, INFO_STATICS, INFO_COMMANDS, INFO_PROCS, INFO_CHANNELS, INFO_EXISTS, INFO_GLOBALS, INFO_LEVEL, + INFO_FRAME, INFO_LOCALS, INFO_VARS, INFO_VERSION, INFO_PATCHLEVEL, INFO_COMPLETE, INFO_ARGS, + INFO_HOSTNAME, INFO_SCRIPT, INFO_SOURCE, INFO_STACKTRACE, INFO_NAMEOFEXECUTABLE, + INFO_RETURNCODES, INFO_REFERENCES, INFO_ALIAS, + }; + +#ifdef jim_ext_namespace + int nons = 0; + + if (argc > 2 && Jim_CompareStringImmediate(interp, argv[1], "-nons")) { + + argc--; + argv++; + nons = 1; + } +#endif + + if (argc < 2) { + Jim_WrongNumArgs(interp, 1, argv, "subcommand ?args ...?"); + return JIM_ERR; + } + if (Jim_GetEnum(interp, argv[1], commands, &cmd, "subcommand", JIM_ERRMSG | JIM_ENUM_ABBREV) != JIM_OK) { + return Jim_CheckShowCommands(interp, argv[1], commands); + } + + + switch (cmd) { + case INFO_EXISTS: + if (argc != 3) { + Jim_WrongNumArgs(interp, 2, argv, "varName"); + return JIM_ERR; + } + Jim_SetResultBool(interp, Jim_GetVariable(interp, argv[2], 0) != NULL); + break; + + case INFO_ALIAS:{ + Jim_Cmd *cmdPtr; + + if (argc != 3) { + Jim_WrongNumArgs(interp, 2, argv, "command"); + return JIM_ERR; + } + if ((cmdPtr = Jim_GetCommand(interp, argv[2], JIM_ERRMSG)) == NULL) { + return JIM_ERR; + } + if (cmdPtr->isproc || cmdPtr->u.native.cmdProc != JimAliasCmd) { + Jim_SetResultFormatted(interp, "command \"%#s\" is not an alias", argv[2]); + return JIM_ERR; + } + Jim_SetResult(interp, (Jim_Obj *)cmdPtr->u.native.privData); + return JIM_OK; + } + + case INFO_CHANNELS: + mode++; +#ifndef jim_ext_aio + Jim_SetResultString(interp, "aio not enabled", -1); + return JIM_ERR; +#endif + + case INFO_PROCS: + mode++; + + case INFO_COMMANDS: + + if (argc != 2 && argc != 3) { + Jim_WrongNumArgs(interp, 2, argv, "?pattern?"); + return JIM_ERR; + } +#ifdef jim_ext_namespace + if (!nons) { + if (Jim_Length(interp->framePtr->nsObj) || (argc == 3 && JimGlobMatch("::*", Jim_String(argv[2]), 0))) { + return Jim_EvalPrefix(interp, "namespace info", argc - 1, argv + 1); + } + } +#endif + Jim_SetResult(interp, JimCommandsList(interp, (argc == 3) ? argv[2] : NULL, mode)); + break; + + case INFO_VARS: + mode++; + + case INFO_LOCALS: + mode++; + + case INFO_GLOBALS: + + if (argc != 2 && argc != 3) { + Jim_WrongNumArgs(interp, 2, argv, "?pattern?"); + return JIM_ERR; + } +#ifdef jim_ext_namespace + if (!nons) { + if (Jim_Length(interp->framePtr->nsObj) || (argc == 3 && JimGlobMatch("::*", Jim_String(argv[2]), 0))) { + return Jim_EvalPrefix(interp, "namespace info", argc - 1, argv + 1); + } + } +#endif + Jim_SetResult(interp, JimVariablesList(interp, argc == 3 ? argv[2] : NULL, mode)); + break; + + case INFO_SCRIPT: + if (argc != 2) { + Jim_WrongNumArgs(interp, 2, argv, ""); + return JIM_ERR; + } + Jim_SetResult(interp, JimGetScript(interp, interp->currentScriptObj)->fileNameObj); + break; + + case INFO_SOURCE:{ + jim_wide line; + Jim_Obj *resObjPtr; + Jim_Obj *fileNameObj; + + if (argc != 3 && argc != 5) { + Jim_WrongNumArgs(interp, 2, argv, "source ?filename line?"); + return JIM_ERR; + } + if (argc == 5) { + if (Jim_GetWide(interp, argv[4], &line) != JIM_OK) { + return JIM_ERR; + } + resObjPtr = Jim_NewStringObj(interp, Jim_String(argv[2]), Jim_Length(argv[2])); + JimSetSourceInfo(interp, resObjPtr, argv[3], line); + } + else { + if (argv[2]->typePtr == &sourceObjType) { + fileNameObj = argv[2]->internalRep.sourceValue.fileNameObj; + line = argv[2]->internalRep.sourceValue.lineNumber; + } + else if (argv[2]->typePtr == &scriptObjType) { + ScriptObj *script = JimGetScript(interp, argv[2]); + fileNameObj = script->fileNameObj; + line = script->firstline; + } + else { + fileNameObj = interp->emptyObj; + line = 1; + } + resObjPtr = Jim_NewListObj(interp, NULL, 0); + Jim_ListAppendElement(interp, resObjPtr, fileNameObj); + Jim_ListAppendElement(interp, resObjPtr, Jim_NewIntObj(interp, line)); + } + Jim_SetResult(interp, resObjPtr); + break; + } + + case INFO_STACKTRACE: + Jim_SetResult(interp, interp->stackTrace); + break; + + case INFO_LEVEL: + case INFO_FRAME: + switch (argc) { + case 2: + Jim_SetResultInt(interp, interp->framePtr->level); + break; + + case 3: + if (JimInfoLevel(interp, argv[2], &objPtr, cmd == INFO_LEVEL) != JIM_OK) { + return JIM_ERR; + } + Jim_SetResult(interp, objPtr); + break; + + default: + Jim_WrongNumArgs(interp, 2, argv, "?levelNum?"); + return JIM_ERR; + } + break; + + case INFO_BODY: + case INFO_STATICS: + case INFO_ARGS:{ + Jim_Cmd *cmdPtr; + + if (argc != 3) { + Jim_WrongNumArgs(interp, 2, argv, "procname"); + return JIM_ERR; + } + if ((cmdPtr = Jim_GetCommand(interp, argv[2], JIM_ERRMSG)) == NULL) { + return JIM_ERR; + } + if (!cmdPtr->isproc) { + Jim_SetResultFormatted(interp, "command \"%#s\" is not a procedure", argv[2]); + return JIM_ERR; + } + switch (cmd) { + case INFO_BODY: + Jim_SetResult(interp, cmdPtr->u.proc.bodyObjPtr); + break; + case INFO_ARGS: + Jim_SetResult(interp, cmdPtr->u.proc.argListObjPtr); + break; + case INFO_STATICS: + if (cmdPtr->u.proc.staticVars) { + Jim_SetResult(interp, JimHashtablePatternMatch(interp, cmdPtr->u.proc.staticVars, + NULL, JimVariablesMatch, JIM_VARLIST_LOCALS | JIM_VARLIST_VALUES)); + } + break; + } + break; + } + + case INFO_VERSION: + case INFO_PATCHLEVEL:{ + char buf[(JIM_INTEGER_SPACE * 2) + 1]; + + sprintf(buf, "%d.%d", JIM_VERSION / 100, JIM_VERSION % 100); + Jim_SetResultString(interp, buf, -1); + break; + } + + case INFO_COMPLETE: + if (argc != 3 && argc != 4) { + Jim_WrongNumArgs(interp, 2, argv, "script ?missing?"); + return JIM_ERR; + } + else { + char missing; + + Jim_SetResultBool(interp, Jim_ScriptIsComplete(interp, argv[2], &missing)); + if (missing != ' ' && argc == 4) { + Jim_SetVariable(interp, argv[3], Jim_NewStringObj(interp, &missing, 1)); + } + } + break; + + case INFO_HOSTNAME: + + return Jim_Eval(interp, "os.gethostname"); + + case INFO_NAMEOFEXECUTABLE: + + return Jim_Eval(interp, "{info nameofexecutable}"); + + case INFO_RETURNCODES: + if (argc == 2) { + int i; + Jim_Obj *listObjPtr = Jim_NewListObj(interp, NULL, 0); + + for (i = 0; jimReturnCodes[i]; i++) { + Jim_ListAppendElement(interp, listObjPtr, Jim_NewIntObj(interp, i)); + Jim_ListAppendElement(interp, listObjPtr, Jim_NewStringObj(interp, + jimReturnCodes[i], -1)); + } + + Jim_SetResult(interp, listObjPtr); + } + else if (argc == 3) { + long code; + const char *name; + + if (Jim_GetLong(interp, argv[2], &code) != JIM_OK) { + return JIM_ERR; + } + name = Jim_ReturnCode(code); + if (*name == '?') { + Jim_SetResultInt(interp, code); + } + else { + Jim_SetResultString(interp, name, -1); + } + } + else { + Jim_WrongNumArgs(interp, 2, argv, "?code?"); + return JIM_ERR; + } + break; + case INFO_REFERENCES: +#ifdef JIM_REFERENCES + return JimInfoReferences(interp, argc, argv); +#else + Jim_SetResultString(interp, "not supported", -1); + return JIM_ERR; +#endif + } + return JIM_OK; +} + + +static int Jim_ExistsCoreCommand(Jim_Interp *interp, int argc, Jim_Obj *const *argv) +{ + Jim_Obj *objPtr; + int result = 0; + + static const char * const options[] = { + "-command", "-proc", "-alias", "-var", NULL + }; + enum + { + OPT_COMMAND, OPT_PROC, OPT_ALIAS, OPT_VAR + }; + int option; + + if (argc == 2) { + option = OPT_VAR; + objPtr = argv[1]; + } + else if (argc == 3) { + if (Jim_GetEnum(interp, argv[1], options, &option, NULL, JIM_ERRMSG | JIM_ENUM_ABBREV) != JIM_OK) { + return JIM_ERR; + } + objPtr = argv[2]; + } + else { + Jim_WrongNumArgs(interp, 1, argv, "?option? name"); + return JIM_ERR; + } + + if (option == OPT_VAR) { + result = Jim_GetVariable(interp, objPtr, 0) != NULL; + } + else { + + Jim_Cmd *cmd = Jim_GetCommand(interp, objPtr, JIM_NONE); + + if (cmd) { + switch (option) { + case OPT_COMMAND: + result = 1; + break; + + case OPT_ALIAS: + result = cmd->isproc == 0 && cmd->u.native.cmdProc == JimAliasCmd; + break; + + case OPT_PROC: + result = cmd->isproc; + break; + } + } + } + Jim_SetResultBool(interp, result); + return JIM_OK; +} + + +static int Jim_SplitCoreCommand(Jim_Interp *interp, int argc, Jim_Obj *const *argv) +{ + const char *str, *splitChars, *noMatchStart; + int splitLen, strLen; + Jim_Obj *resObjPtr; + int c; + int len; + + if (argc != 2 && argc != 3) { + Jim_WrongNumArgs(interp, 1, argv, "string ?splitChars?"); + return JIM_ERR; + } + + str = Jim_GetString(argv[1], &len); + if (len == 0) { + return JIM_OK; + } + strLen = Jim_Utf8Length(interp, argv[1]); + + + if (argc == 2) { + splitChars = " \n\t\r"; + splitLen = 4; + } + else { + splitChars = Jim_String(argv[2]); + splitLen = Jim_Utf8Length(interp, argv[2]); + } + + noMatchStart = str; + resObjPtr = Jim_NewListObj(interp, NULL, 0); + + + if (splitLen) { + Jim_Obj *objPtr; + while (strLen--) { + const char *sc = splitChars; + int scLen = splitLen; + int sl = utf8_tounicode(str, &c); + while (scLen--) { + int pc; + sc += utf8_tounicode(sc, &pc); + if (c == pc) { + objPtr = Jim_NewStringObj(interp, noMatchStart, (str - noMatchStart)); + Jim_ListAppendElement(interp, resObjPtr, objPtr); + noMatchStart = str + sl; + break; + } + } + str += sl; + } + objPtr = Jim_NewStringObj(interp, noMatchStart, (str - noMatchStart)); + Jim_ListAppendElement(interp, resObjPtr, objPtr); + } + else { + Jim_Obj **commonObj = NULL; +#define NUM_COMMON (128 - 9) + while (strLen--) { + int n = utf8_tounicode(str, &c); +#ifdef JIM_OPTIMIZATION + if (c >= 9 && c < 128) { + + c -= 9; + if (!commonObj) { + commonObj = Jim_Alloc(sizeof(*commonObj) * NUM_COMMON); + memset(commonObj, 0, sizeof(*commonObj) * NUM_COMMON); + } + if (!commonObj[c]) { + commonObj[c] = Jim_NewStringObj(interp, str, 1); + } + Jim_ListAppendElement(interp, resObjPtr, commonObj[c]); + str++; + continue; + } +#endif + Jim_ListAppendElement(interp, resObjPtr, Jim_NewStringObjUtf8(interp, str, 1)); + str += n; + } + Jim_Free(commonObj); + } + + Jim_SetResult(interp, resObjPtr); + return JIM_OK; +} + + +static int Jim_JoinCoreCommand(Jim_Interp *interp, int argc, Jim_Obj *const *argv) +{ + const char *joinStr; + int joinStrLen; + + if (argc != 2 && argc != 3) { + Jim_WrongNumArgs(interp, 1, argv, "list ?joinString?"); + return JIM_ERR; + } + + if (argc == 2) { + joinStr = " "; + joinStrLen = 1; + } + else { + joinStr = Jim_GetString(argv[2], &joinStrLen); + } + Jim_SetResult(interp, Jim_ListJoin(interp, argv[1], joinStr, joinStrLen)); + return JIM_OK; +} + + +static int Jim_FormatCoreCommand(Jim_Interp *interp, int argc, Jim_Obj *const *argv) +{ + Jim_Obj *objPtr; + + if (argc < 2) { + Jim_WrongNumArgs(interp, 1, argv, "formatString ?arg arg ...?"); + return JIM_ERR; + } + objPtr = Jim_FormatString(interp, argv[1], argc - 2, argv + 2); + if (objPtr == NULL) + return JIM_ERR; + Jim_SetResult(interp, objPtr); + return JIM_OK; +} + + +static int Jim_ScanCoreCommand(Jim_Interp *interp, int argc, Jim_Obj *const *argv) +{ + Jim_Obj *listPtr, **outVec; + int outc, i; + + if (argc < 3) { + Jim_WrongNumArgs(interp, 1, argv, "string format ?varName varName ...?"); + return JIM_ERR; + } + if (argv[2]->typePtr != &scanFmtStringObjType) + SetScanFmtFromAny(interp, argv[2]); + if (FormatGetError(argv[2]) != 0) { + Jim_SetResultString(interp, FormatGetError(argv[2]), -1); + return JIM_ERR; + } + if (argc > 3) { + int maxPos = FormatGetMaxPos(argv[2]); + int count = FormatGetCnvCount(argv[2]); + + if (maxPos > argc - 3) { + Jim_SetResultString(interp, "\"%n$\" argument index out of range", -1); + return JIM_ERR; + } + else if (count > argc - 3) { + Jim_SetResultString(interp, "different numbers of variable names and " + "field specifiers", -1); + return JIM_ERR; + } + else if (count < argc - 3) { + Jim_SetResultString(interp, "variable is not assigned by any " + "conversion specifiers", -1); + return JIM_ERR; + } + } + listPtr = Jim_ScanString(interp, argv[1], argv[2], JIM_ERRMSG); + if (listPtr == 0) + return JIM_ERR; + if (argc > 3) { + int rc = JIM_OK; + int count = 0; + + if (listPtr != 0 && listPtr != (Jim_Obj *)EOF) { + int len = Jim_ListLength(interp, listPtr); + + if (len != 0) { + JimListGetElements(interp, listPtr, &outc, &outVec); + for (i = 0; i < outc; ++i) { + if (Jim_Length(outVec[i]) > 0) { + ++count; + if (Jim_SetVariable(interp, argv[3 + i], outVec[i]) != JIM_OK) { + rc = JIM_ERR; + } + } + } + } + Jim_FreeNewObj(interp, listPtr); + } + else { + count = -1; + } + if (rc == JIM_OK) { + Jim_SetResultInt(interp, count); + } + return rc; + } + else { + if (listPtr == (Jim_Obj *)EOF) { + Jim_SetResult(interp, Jim_NewListObj(interp, 0, 0)); + return JIM_OK; + } + Jim_SetResult(interp, listPtr); + } + return JIM_OK; +} + + +static int Jim_ErrorCoreCommand(Jim_Interp *interp, int argc, Jim_Obj *const *argv) +{ + if (argc != 2 && argc != 3) { + Jim_WrongNumArgs(interp, 1, argv, "message ?stacktrace?"); + return JIM_ERR; + } + Jim_SetResult(interp, argv[1]); + if (argc == 3) { + JimSetStackTrace(interp, argv[2]); + return JIM_ERR; + } + interp->addStackTrace++; + return JIM_ERR; +} + + +static int Jim_LrangeCoreCommand(Jim_Interp *interp, int argc, Jim_Obj *const *argv) +{ + Jim_Obj *objPtr; + + if (argc != 4) { + Jim_WrongNumArgs(interp, 1, argv, "list first last"); + return JIM_ERR; + } + if ((objPtr = Jim_ListRange(interp, argv[1], argv[2], argv[3])) == NULL) + return JIM_ERR; + Jim_SetResult(interp, objPtr); + return JIM_OK; +} + + +static int Jim_LrepeatCoreCommand(Jim_Interp *interp, int argc, Jim_Obj *const *argv) +{ + Jim_Obj *objPtr; + long count; + + if (argc < 2 || Jim_GetLong(interp, argv[1], &count) != JIM_OK || count < 0) { + Jim_WrongNumArgs(interp, 1, argv, "count ?value ...?"); + return JIM_ERR; + } + + if (count == 0 || argc == 2) { + return JIM_OK; + } + + argc -= 2; + argv += 2; + + objPtr = Jim_NewListObj(interp, argv, argc); + while (--count) { + ListInsertElements(objPtr, -1, argc, argv); + } + + Jim_SetResult(interp, objPtr); + return JIM_OK; +} + +char **Jim_GetEnviron(void) +{ +#if defined(HAVE__NSGETENVIRON) + return *_NSGetEnviron(); +#else + #if !defined(NO_ENVIRON_EXTERN) + extern char **environ; + #endif + + return environ; +#endif +} + +void Jim_SetEnviron(char **env) +{ +#if defined(HAVE__NSGETENVIRON) + *_NSGetEnviron() = env; +#else + #if !defined(NO_ENVIRON_EXTERN) + extern char **environ; + #endif + + environ = env; +#endif +} + + +static int Jim_EnvCoreCommand(Jim_Interp *interp, int argc, Jim_Obj *const *argv) +{ + const char *key; + const char *val; + + if (argc == 1) { + char **e = Jim_GetEnviron(); + + int i; + Jim_Obj *listObjPtr = Jim_NewListObj(interp, NULL, 0); + + for (i = 0; e[i]; i++) { + const char *equals = strchr(e[i], '='); + + if (equals) { + Jim_ListAppendElement(interp, listObjPtr, Jim_NewStringObj(interp, e[i], + equals - e[i])); + Jim_ListAppendElement(interp, listObjPtr, Jim_NewStringObj(interp, equals + 1, -1)); + } + } + + Jim_SetResult(interp, listObjPtr); + return JIM_OK; + } + + if (argc < 2) { + Jim_WrongNumArgs(interp, 1, argv, "varName ?default?"); + return JIM_ERR; + } + key = Jim_String(argv[1]); + val = getenv(key); + if (val == NULL) { + if (argc < 3) { + Jim_SetResultFormatted(interp, "environment variable \"%#s\" does not exist", argv[1]); + return JIM_ERR; + } + val = Jim_String(argv[2]); + } + Jim_SetResult(interp, Jim_NewStringObj(interp, val, -1)); + return JIM_OK; +} + + +static int Jim_SourceCoreCommand(Jim_Interp *interp, int argc, Jim_Obj *const *argv) +{ + int retval; + + if (argc != 2) { + Jim_WrongNumArgs(interp, 1, argv, "fileName"); + return JIM_ERR; + } + retval = Jim_EvalFile(interp, Jim_String(argv[1])); + if (retval == JIM_RETURN) + return JIM_OK; + return retval; +} + + +static int Jim_LreverseCoreCommand(Jim_Interp *interp, int argc, Jim_Obj *const *argv) +{ + Jim_Obj *revObjPtr, **ele; + int len; + + if (argc != 2) { + Jim_WrongNumArgs(interp, 1, argv, "list"); + return JIM_ERR; + } + JimListGetElements(interp, argv[1], &len, &ele); + len--; + revObjPtr = Jim_NewListObj(interp, NULL, 0); + while (len >= 0) + ListAppendElement(revObjPtr, ele[len--]); + Jim_SetResult(interp, revObjPtr); + return JIM_OK; +} + +static int JimRangeLen(jim_wide start, jim_wide end, jim_wide step) +{ + jim_wide len; + + if (step == 0) + return -1; + if (start == end) + return 0; + else if (step > 0 && start > end) + return -1; + else if (step < 0 && end > start) + return -1; + len = end - start; + if (len < 0) + len = -len; + if (step < 0) + step = -step; + len = 1 + ((len - 1) / step); + if (len > INT_MAX) + len = INT_MAX; + return (int)((len < 0) ? -1 : len); +} + + +static int Jim_RangeCoreCommand(Jim_Interp *interp, int argc, Jim_Obj *const *argv) +{ + jim_wide start = 0, end, step = 1; + int len, i; + Jim_Obj *objPtr; + + if (argc < 2 || argc > 4) { + Jim_WrongNumArgs(interp, 1, argv, "?start? end ?step?"); + return JIM_ERR; + } + if (argc == 2) { + if (Jim_GetWide(interp, argv[1], &end) != JIM_OK) + return JIM_ERR; + } + else { + if (Jim_GetWide(interp, argv[1], &start) != JIM_OK || + Jim_GetWide(interp, argv[2], &end) != JIM_OK) + return JIM_ERR; + if (argc == 4 && Jim_GetWide(interp, argv[3], &step) != JIM_OK) + return JIM_ERR; + } + if ((len = JimRangeLen(start, end, step)) == -1) { + Jim_SetResultString(interp, "Invalid (infinite?) range specified", -1); + return JIM_ERR; + } + objPtr = Jim_NewListObj(interp, NULL, 0); + for (i = 0; i < len; i++) + ListAppendElement(objPtr, Jim_NewIntObj(interp, start + i * step)); + Jim_SetResult(interp, objPtr); + return JIM_OK; +} + + +static int Jim_RandCoreCommand(Jim_Interp *interp, int argc, Jim_Obj *const *argv) +{ + jim_wide min = 0, max = 0, len, maxMul; + + if (argc < 1 || argc > 3) { + Jim_WrongNumArgs(interp, 1, argv, "?min? max"); + return JIM_ERR; + } + if (argc == 1) { + max = JIM_WIDE_MAX; + } else if (argc == 2) { + if (Jim_GetWide(interp, argv[1], &max) != JIM_OK) + return JIM_ERR; + } else if (argc == 3) { + if (Jim_GetWide(interp, argv[1], &min) != JIM_OK || + Jim_GetWide(interp, argv[2], &max) != JIM_OK) + return JIM_ERR; + } + len = max-min; + if (len < 0) { + Jim_SetResultString(interp, "Invalid arguments (max < min)", -1); + return JIM_ERR; + } + maxMul = JIM_WIDE_MAX - (len ? (JIM_WIDE_MAX%len) : 0); + while (1) { + jim_wide r; + + JimRandomBytes(interp, &r, sizeof(jim_wide)); + if (r < 0 || r >= maxMul) continue; + r = (len == 0) ? 0 : r%len; + Jim_SetResultInt(interp, min+r); + return JIM_OK; + } +} + +static const struct { + const char *name; + Jim_CmdProc *cmdProc; +} Jim_CoreCommandsTable[] = { + {"alias", Jim_AliasCoreCommand}, + {"set", Jim_SetCoreCommand}, + {"unset", Jim_UnsetCoreCommand}, + {"puts", Jim_PutsCoreCommand}, + {"+", Jim_AddCoreCommand}, + {"*", Jim_MulCoreCommand}, + {"-", Jim_SubCoreCommand}, + {"/", Jim_DivCoreCommand}, + {"incr", Jim_IncrCoreCommand}, + {"while", Jim_WhileCoreCommand}, + {"loop", Jim_LoopCoreCommand}, + {"for", Jim_ForCoreCommand}, + {"foreach", Jim_ForeachCoreCommand}, + {"lmap", Jim_LmapCoreCommand}, + {"lassign", Jim_LassignCoreCommand}, + {"if", Jim_IfCoreCommand}, + {"switch", Jim_SwitchCoreCommand}, + {"list", Jim_ListCoreCommand}, + {"lindex", Jim_LindexCoreCommand}, + {"lset", Jim_LsetCoreCommand}, + {"lsearch", Jim_LsearchCoreCommand}, + {"llength", Jim_LlengthCoreCommand}, + {"lappend", Jim_LappendCoreCommand}, + {"linsert", Jim_LinsertCoreCommand}, + {"lreplace", Jim_LreplaceCoreCommand}, + {"lsort", Jim_LsortCoreCommand}, + {"append", Jim_AppendCoreCommand}, + {"debug", Jim_DebugCoreCommand}, + {"eval", Jim_EvalCoreCommand}, + {"uplevel", Jim_UplevelCoreCommand}, + {"expr", Jim_ExprCoreCommand}, + {"break", Jim_BreakCoreCommand}, + {"continue", Jim_ContinueCoreCommand}, + {"proc", Jim_ProcCoreCommand}, + {"concat", Jim_ConcatCoreCommand}, + {"return", Jim_ReturnCoreCommand}, + {"upvar", Jim_UpvarCoreCommand}, + {"global", Jim_GlobalCoreCommand}, + {"string", Jim_StringCoreCommand}, + {"time", Jim_TimeCoreCommand}, + {"exit", Jim_ExitCoreCommand}, + {"catch", Jim_CatchCoreCommand}, +#ifdef JIM_REFERENCES + {"ref", Jim_RefCoreCommand}, + {"getref", Jim_GetrefCoreCommand}, + {"setref", Jim_SetrefCoreCommand}, + {"finalize", Jim_FinalizeCoreCommand}, + {"collect", Jim_CollectCoreCommand}, +#endif + {"rename", Jim_RenameCoreCommand}, + {"dict", Jim_DictCoreCommand}, + {"subst", Jim_SubstCoreCommand}, + {"info", Jim_InfoCoreCommand}, + {"exists", Jim_ExistsCoreCommand}, + {"split", Jim_SplitCoreCommand}, + {"join", Jim_JoinCoreCommand}, + {"format", Jim_FormatCoreCommand}, + {"scan", Jim_ScanCoreCommand}, + {"error", Jim_ErrorCoreCommand}, + {"lrange", Jim_LrangeCoreCommand}, + {"lrepeat", Jim_LrepeatCoreCommand}, + {"env", Jim_EnvCoreCommand}, + {"source", Jim_SourceCoreCommand}, + {"lreverse", Jim_LreverseCoreCommand}, + {"range", Jim_RangeCoreCommand}, + {"rand", Jim_RandCoreCommand}, + {"tailcall", Jim_TailcallCoreCommand}, + {"local", Jim_LocalCoreCommand}, + {"upcall", Jim_UpcallCoreCommand}, + {"apply", Jim_ApplyCoreCommand}, + {NULL, NULL}, +}; + +void Jim_RegisterCoreCommands(Jim_Interp *interp) +{ + int i = 0; + + while (Jim_CoreCommandsTable[i].name != NULL) { + Jim_CreateCommand(interp, + Jim_CoreCommandsTable[i].name, Jim_CoreCommandsTable[i].cmdProc, NULL, NULL); + i++; + } +} + +void Jim_MakeErrorMessage(Jim_Interp *interp) +{ + Jim_Obj *argv[2]; + + argv[0] = Jim_NewStringObj(interp, "errorInfo", -1); + argv[1] = interp->result; + + Jim_EvalObjVector(interp, 2, argv); +} + +static char **JimSortStringTable(const char *const *tablePtr) +{ + int count; + char **tablePtrSorted; + + + for (count = 0; tablePtr[count]; count++) { + } + + + tablePtrSorted = Jim_Alloc(sizeof(char *) * (count + 1)); + memcpy(tablePtrSorted, tablePtr, sizeof(char *) * count); + qsort(tablePtrSorted, count, sizeof(char *), qsortCompareStringPointers); + tablePtrSorted[count] = NULL; + + return tablePtrSorted; +} + +static void JimSetFailedEnumResult(Jim_Interp *interp, const char *arg, const char *badtype, + const char *prefix, const char *const *tablePtr, const char *name) +{ + char **tablePtrSorted; + int i; + + if (name == NULL) { + name = "option"; + } + + Jim_SetResultFormatted(interp, "%s%s \"%s\": must be ", badtype, name, arg); + tablePtrSorted = JimSortStringTable(tablePtr); + for (i = 0; tablePtrSorted[i]; i++) { + if (tablePtrSorted[i + 1] == NULL && i > 0) { + Jim_AppendString(interp, Jim_GetResult(interp), "or ", -1); + } + Jim_AppendStrings(interp, Jim_GetResult(interp), prefix, tablePtrSorted[i], NULL); + if (tablePtrSorted[i + 1]) { + Jim_AppendString(interp, Jim_GetResult(interp), ", ", -1); + } + } + Jim_Free(tablePtrSorted); +} + + +int Jim_CheckShowCommands(Jim_Interp *interp, Jim_Obj *objPtr, const char *const *tablePtr) +{ + if (Jim_CompareStringImmediate(interp, objPtr, "-commands")) { + int i; + char **tablePtrSorted = JimSortStringTable(tablePtr); + Jim_SetResult(interp, Jim_NewListObj(interp, NULL, 0)); + for (i = 0; tablePtrSorted[i]; i++) { + Jim_ListAppendElement(interp, Jim_GetResult(interp), Jim_NewStringObj(interp, tablePtrSorted[i], -1)); + } + Jim_Free(tablePtrSorted); + return JIM_OK; + } + return JIM_ERR; +} + +static const Jim_ObjType getEnumObjType = { + "get-enum", + NULL, + NULL, + NULL, + JIM_TYPE_REFERENCES +}; + +int Jim_GetEnum(Jim_Interp *interp, Jim_Obj *objPtr, + const char *const *tablePtr, int *indexPtr, const char *name, int flags) +{ + const char *bad = "bad "; + const char *const *entryPtr = NULL; + int i; + int match = -1; + int arglen; + const char *arg; + + if (objPtr->typePtr == &getEnumObjType) { + if (objPtr->internalRep.ptrIntValue.ptr == tablePtr && objPtr->internalRep.ptrIntValue.int1 == flags) { + *indexPtr = objPtr->internalRep.ptrIntValue.int2; + return JIM_OK; + } + } + + arg = Jim_GetString(objPtr, &arglen); + + *indexPtr = -1; + + for (entryPtr = tablePtr, i = 0; *entryPtr != NULL; entryPtr++, i++) { + if (Jim_CompareStringImmediate(interp, objPtr, *entryPtr)) { + + match = i; + goto found; + } + if (flags & JIM_ENUM_ABBREV) { + if (strncmp(arg, *entryPtr, arglen) == 0) { + if (*arg == '-' && arglen == 1) { + break; + } + if (match >= 0) { + bad = "ambiguous "; + goto ambiguous; + } + match = i; + } + } + } + + + if (match >= 0) { + found: + + Jim_FreeIntRep(interp, objPtr); + objPtr->typePtr = &getEnumObjType; + objPtr->internalRep.ptrIntValue.ptr = (void *)tablePtr; + objPtr->internalRep.ptrIntValue.int1 = flags; + objPtr->internalRep.ptrIntValue.int2 = match; + + *indexPtr = match; + return JIM_OK; + } + + ambiguous: + if (flags & JIM_ERRMSG) { + JimSetFailedEnumResult(interp, arg, bad, "", tablePtr, name); + } + return JIM_ERR; +} + +int Jim_FindByName(const char *name, const char * const array[], size_t len) +{ + int i; + + for (i = 0; i < (int)len; i++) { + if (array[i] && strcmp(array[i], name) == 0) { + return i; + } + } + return -1; +} + +int Jim_IsDict(Jim_Obj *objPtr) +{ + return objPtr->typePtr == &dictObjType; +} + +int Jim_IsList(Jim_Obj *objPtr) +{ + return objPtr->typePtr == &listObjType; +} + +void Jim_SetResultFormatted(Jim_Interp *interp, const char *format, ...) +{ + + int len = strlen(format); + int extra = 0; + int n = 0; + const char *params[5]; + int nobjparam = 0; + Jim_Obj *objparam[5]; + char *buf; + va_list args; + int i; + + va_start(args, format); + + for (i = 0; i < len && n < 5; i++) { + int l; + + if (strncmp(format + i, "%s", 2) == 0) { + params[n] = va_arg(args, char *); + + l = strlen(params[n]); + } + else if (strncmp(format + i, "%#s", 3) == 0) { + Jim_Obj *objPtr = va_arg(args, Jim_Obj *); + + params[n] = Jim_GetString(objPtr, &l); + objparam[nobjparam++] = objPtr; + Jim_IncrRefCount(objPtr); + } + else { + if (format[i] == '%') { + i++; + } + continue; + } + n++; + extra += l; + } + + len += extra; + buf = Jim_Alloc(len + 1); + len = snprintf(buf, len + 1, format, params[0], params[1], params[2], params[3], params[4]); + + va_end(args); + + Jim_SetResult(interp, Jim_NewStringObjNoAlloc(interp, buf, len)); + + for (i = 0; i < nobjparam; i++) { + Jim_DecrRefCount(interp, objparam[i]); + } +} + + +#ifndef jim_ext_package +int Jim_PackageProvide(Jim_Interp *interp, const char *name, const char *ver, int flags) +{ + return JIM_OK; +} +#endif +#ifndef jim_ext_aio +FILE *Jim_AioFilehandle(Jim_Interp *interp, Jim_Obj *fhObj) +{ + Jim_SetResultString(interp, "aio not enabled", -1); + return NULL; +} +#endif + + +#include +#include + + +static int subcmd_null(Jim_Interp *interp, int argc, Jim_Obj *const *argv) +{ + + return JIM_OK; +} + +static const jim_subcmd_type dummy_subcmd = { + "dummy", NULL, subcmd_null, 0, 0, JIM_MODFLAG_HIDDEN +}; + +static void add_commands(Jim_Interp *interp, const jim_subcmd_type * ct, const char *sep) +{ + const char *s = ""; + + for (; ct->cmd; ct++) { + if (!(ct->flags & JIM_MODFLAG_HIDDEN)) { + Jim_AppendStrings(interp, Jim_GetResult(interp), s, ct->cmd, NULL); + s = sep; + } + } +} + +static void bad_subcmd(Jim_Interp *interp, const jim_subcmd_type * command_table, const char *type, + Jim_Obj *cmd, Jim_Obj *subcmd) +{ + Jim_SetResultFormatted(interp, "%#s, %s command \"%#s\": should be ", cmd, type, subcmd); + add_commands(interp, command_table, ", "); +} + +static void show_cmd_usage(Jim_Interp *interp, const jim_subcmd_type * command_table, int argc, + Jim_Obj *const *argv) +{ + Jim_SetResultFormatted(interp, "Usage: \"%#s command ... \", where command is one of: ", argv[0]); + add_commands(interp, command_table, ", "); +} + +static void add_cmd_usage(Jim_Interp *interp, const jim_subcmd_type * ct, Jim_Obj *cmd) +{ + if (cmd) { + Jim_AppendStrings(interp, Jim_GetResult(interp), Jim_String(cmd), " ", NULL); + } + Jim_AppendStrings(interp, Jim_GetResult(interp), ct->cmd, NULL); + if (ct->args && *ct->args) { + Jim_AppendStrings(interp, Jim_GetResult(interp), " ", ct->args, NULL); + } +} + +static void set_wrong_args(Jim_Interp *interp, const jim_subcmd_type * command_table, Jim_Obj *subcmd) +{ + Jim_SetResultString(interp, "wrong # args: should be \"", -1); + add_cmd_usage(interp, command_table, subcmd); + Jim_AppendStrings(interp, Jim_GetResult(interp), "\"", NULL); +} + +static const Jim_ObjType subcmdLookupObjType = { + "subcmd-lookup", + NULL, + NULL, + NULL, + JIM_TYPE_REFERENCES +}; + +const jim_subcmd_type *Jim_ParseSubCmd(Jim_Interp *interp, const jim_subcmd_type * command_table, + int argc, Jim_Obj *const *argv) +{ + const jim_subcmd_type *ct; + const jim_subcmd_type *partial = 0; + int cmdlen; + Jim_Obj *cmd; + const char *cmdstr; + int help = 0; + + if (argc < 2) { + Jim_SetResultFormatted(interp, "wrong # args: should be \"%#s command ...\"\n" + "Use \"%#s -help ?command?\" for help", argv[0], argv[0]); + return 0; + } + + cmd = argv[1]; + + + if (cmd->typePtr == &subcmdLookupObjType) { + if (cmd->internalRep.ptrIntValue.ptr == command_table) { + ct = command_table + cmd->internalRep.ptrIntValue.int1; + goto found; + } + } + + + if (Jim_CompareStringImmediate(interp, cmd, "-help")) { + if (argc == 2) { + + show_cmd_usage(interp, command_table, argc, argv); + return &dummy_subcmd; + } + help = 1; + + + cmd = argv[2]; + } + + + if (Jim_CompareStringImmediate(interp, cmd, "-commands")) { + + Jim_SetResult(interp, Jim_NewEmptyStringObj(interp)); + add_commands(interp, command_table, " "); + return &dummy_subcmd; + } + + cmdstr = Jim_GetString(cmd, &cmdlen); + + for (ct = command_table; ct->cmd; ct++) { + if (Jim_CompareStringImmediate(interp, cmd, ct->cmd)) { + + break; + } + if (strncmp(cmdstr, ct->cmd, cmdlen) == 0) { + if (partial) { + + if (help) { + + show_cmd_usage(interp, command_table, argc, argv); + return &dummy_subcmd; + } + bad_subcmd(interp, command_table, "ambiguous", argv[0], argv[1 + help]); + return 0; + } + partial = ct; + } + continue; + } + + + if (partial && !ct->cmd) { + ct = partial; + } + + if (!ct->cmd) { + + if (help) { + + show_cmd_usage(interp, command_table, argc, argv); + return &dummy_subcmd; + } + bad_subcmd(interp, command_table, "unknown", argv[0], argv[1 + help]); + return 0; + } + + if (help) { + Jim_SetResultString(interp, "Usage: ", -1); + + add_cmd_usage(interp, ct, argv[0]); + return &dummy_subcmd; + } + + + Jim_FreeIntRep(interp, cmd); + cmd->typePtr = &subcmdLookupObjType; + cmd->internalRep.ptrIntValue.ptr = (void *)command_table; + cmd->internalRep.ptrIntValue.int1 = ct - command_table; + +found: + + if (argc - 2 < ct->minargs || (ct->maxargs >= 0 && argc - 2 > ct->maxargs)) { + Jim_SetResultString(interp, "wrong # args: should be \"", -1); + + add_cmd_usage(interp, ct, argv[0]); + Jim_AppendStrings(interp, Jim_GetResult(interp), "\"", NULL); + + return 0; + } + + + return ct; +} + +int Jim_CallSubCmd(Jim_Interp *interp, const jim_subcmd_type * ct, int argc, Jim_Obj *const *argv) +{ + int ret = JIM_ERR; + + if (ct) { + if (ct->flags & JIM_MODFLAG_FULLARGV) { + ret = ct->function(interp, argc, argv); + } + else { + ret = ct->function(interp, argc - 2, argv + 2); + } + if (ret < 0) { + set_wrong_args(interp, ct, argv[0]); + ret = JIM_ERR; + } + } + return ret; +} + +int Jim_SubCmdProc(Jim_Interp *interp, int argc, Jim_Obj *const *argv) +{ + const jim_subcmd_type *ct = + Jim_ParseSubCmd(interp, (const jim_subcmd_type *)Jim_CmdPrivData(interp), argc, argv); + + return Jim_CallSubCmd(interp, ct, argc, argv); +} + +#include +#include +#include +#include +#include + + +int utf8_fromunicode(char *p, unsigned uc) +{ + if (uc <= 0x7f) { + *p = uc; + return 1; + } + else if (uc <= 0x7ff) { + *p++ = 0xc0 | ((uc & 0x7c0) >> 6); + *p = 0x80 | (uc & 0x3f); + return 2; + } + else if (uc <= 0xffff) { + *p++ = 0xe0 | ((uc & 0xf000) >> 12); + *p++ = 0x80 | ((uc & 0xfc0) >> 6); + *p = 0x80 | (uc & 0x3f); + return 3; + } + + else { + *p++ = 0xf0 | ((uc & 0x1c0000) >> 18); + *p++ = 0x80 | ((uc & 0x3f000) >> 12); + *p++ = 0x80 | ((uc & 0xfc0) >> 6); + *p = 0x80 | (uc & 0x3f); + return 4; + } +} + +#include +#include + + +#define JIM_INTEGER_SPACE 24 +#define MAX_FLOAT_WIDTH 320 + +Jim_Obj *Jim_FormatString(Jim_Interp *interp, Jim_Obj *fmtObjPtr, int objc, Jim_Obj *const *objv) +{ + const char *span, *format, *formatEnd, *msg; + int numBytes = 0, objIndex = 0, gotXpg = 0, gotSequential = 0; + static const char * const mixedXPG = + "cannot mix \"%\" and \"%n$\" conversion specifiers"; + static const char * const badIndex[2] = { + "not enough arguments for all format specifiers", + "\"%n$\" argument index out of range" + }; + int formatLen; + Jim_Obj *resultPtr; + + char *num_buffer = NULL; + int num_buffer_size = 0; + + span = format = Jim_GetString(fmtObjPtr, &formatLen); + formatEnd = format + formatLen; + resultPtr = Jim_NewEmptyStringObj(interp); + + while (format != formatEnd) { + char *end; + int gotMinus, sawFlag; + int gotPrecision, useShort; + long width, precision; + int newXpg; + int ch; + int step; + int doubleType; + char pad = ' '; + char spec[2*JIM_INTEGER_SPACE + 12]; + char *p; + + int formatted_chars; + int formatted_bytes; + const char *formatted_buf; + + step = utf8_tounicode(format, &ch); + format += step; + if (ch != '%') { + numBytes += step; + continue; + } + if (numBytes) { + Jim_AppendString(interp, resultPtr, span, numBytes); + numBytes = 0; + } + + + step = utf8_tounicode(format, &ch); + if (ch == '%') { + span = format; + numBytes = step; + format += step; + continue; + } + + + newXpg = 0; + if (isdigit(ch)) { + int position = strtoul(format, &end, 10); + if (*end == '$') { + newXpg = 1; + objIndex = position - 1; + format = end + 1; + step = utf8_tounicode(format, &ch); + } + } + if (newXpg) { + if (gotSequential) { + msg = mixedXPG; + goto errorMsg; + } + gotXpg = 1; + } else { + if (gotXpg) { + msg = mixedXPG; + goto errorMsg; + } + gotSequential = 1; + } + if ((objIndex < 0) || (objIndex >= objc)) { + msg = badIndex[gotXpg]; + goto errorMsg; + } + + p = spec; + *p++ = '%'; + + gotMinus = 0; + sawFlag = 1; + do { + switch (ch) { + case '-': + gotMinus = 1; + break; + case '0': + pad = ch; + break; + case ' ': + case '+': + case '#': + break; + default: + sawFlag = 0; + continue; + } + *p++ = ch; + format += step; + step = utf8_tounicode(format, &ch); + + } while (sawFlag && (p - spec <= 5)); + + + width = 0; + if (isdigit(ch)) { + width = strtoul(format, &end, 10); + format = end; + step = utf8_tounicode(format, &ch); + } else if (ch == '*') { + if (objIndex >= objc - 1) { + msg = badIndex[gotXpg]; + goto errorMsg; + } + if (Jim_GetLong(interp, objv[objIndex], &width) != JIM_OK) { + goto error; + } + if (width < 0) { + width = -width; + if (!gotMinus) { + *p++ = '-'; + gotMinus = 1; + } + } + objIndex++; + format += step; + step = utf8_tounicode(format, &ch); + } + + + gotPrecision = precision = 0; + if (ch == '.') { + gotPrecision = 1; + format += step; + step = utf8_tounicode(format, &ch); + } + if (isdigit(ch)) { + precision = strtoul(format, &end, 10); + format = end; + step = utf8_tounicode(format, &ch); + } else if (ch == '*') { + if (objIndex >= objc - 1) { + msg = badIndex[gotXpg]; + goto errorMsg; + } + if (Jim_GetLong(interp, objv[objIndex], &precision) != JIM_OK) { + goto error; + } + + + if (precision < 0) { + precision = 0; + } + objIndex++; + format += step; + step = utf8_tounicode(format, &ch); + } + + + useShort = 0; + if (ch == 'h') { + useShort = 1; + format += step; + step = utf8_tounicode(format, &ch); + } else if (ch == 'l') { + + format += step; + step = utf8_tounicode(format, &ch); + if (ch == 'l') { + format += step; + step = utf8_tounicode(format, &ch); + } + } + + format += step; + span = format; + + + if (ch == 'i') { + ch = 'd'; + } + + doubleType = 0; + + switch (ch) { + case '\0': + msg = "format string ended in middle of field specifier"; + goto errorMsg; + case 's': { + formatted_buf = Jim_GetString(objv[objIndex], &formatted_bytes); + formatted_chars = Jim_Utf8Length(interp, objv[objIndex]); + if (gotPrecision && (precision < formatted_chars)) { + + formatted_chars = precision; + formatted_bytes = utf8_index(formatted_buf, precision); + } + break; + } + case 'c': { + jim_wide code; + + if (Jim_GetWide(interp, objv[objIndex], &code) != JIM_OK) { + goto error; + } + + formatted_bytes = utf8_getchars(spec, code); + formatted_buf = spec; + formatted_chars = 1; + break; + } + case 'b': { + unsigned jim_wide w; + int length; + int i; + int j; + + if (Jim_GetWide(interp, objv[objIndex], (jim_wide *)&w) != JIM_OK) { + goto error; + } + length = sizeof(w) * 8; + + + + if (num_buffer_size < length + 1) { + num_buffer_size = length + 1; + num_buffer = Jim_Realloc(num_buffer, num_buffer_size); + } + + j = 0; + for (i = length; i > 0; ) { + i--; + if (w & ((unsigned jim_wide)1 << i)) { + num_buffer[j++] = '1'; + } + else if (j || i == 0) { + num_buffer[j++] = '0'; + } + } + num_buffer[j] = 0; + formatted_chars = formatted_bytes = j; + formatted_buf = num_buffer; + break; + } + + case 'e': + case 'E': + case 'f': + case 'g': + case 'G': + doubleType = 1; + + case 'd': + case 'u': + case 'o': + case 'x': + case 'X': { + jim_wide w; + double d; + int length; + + + if (width) { + p += sprintf(p, "%ld", width); + } + if (gotPrecision) { + p += sprintf(p, ".%ld", precision); + } + + + if (doubleType) { + if (Jim_GetDouble(interp, objv[objIndex], &d) != JIM_OK) { + goto error; + } + length = MAX_FLOAT_WIDTH; + } + else { + if (Jim_GetWide(interp, objv[objIndex], &w) != JIM_OK) { + goto error; + } + length = JIM_INTEGER_SPACE; + if (useShort) { + if (ch == 'd') { + w = (short)w; + } + else { + w = (unsigned short)w; + } + } + *p++ = 'l'; +#ifdef HAVE_LONG_LONG + if (sizeof(long long) == sizeof(jim_wide)) { + *p++ = 'l'; + } +#endif + } + + *p++ = (char) ch; + *p = '\0'; + + + if (width > 10000 || length > 10000 || precision > 10000) { + Jim_SetResultString(interp, "format too long", -1); + goto error; + } + + + + if (width > length) { + length = width; + } + if (gotPrecision) { + length += precision; + } + + + if (num_buffer_size < length + 1) { + num_buffer_size = length + 1; + num_buffer = Jim_Realloc(num_buffer, num_buffer_size); + } + + if (doubleType) { + snprintf(num_buffer, length + 1, spec, d); + } + else { + formatted_bytes = snprintf(num_buffer, length + 1, spec, w); + } + formatted_chars = formatted_bytes = strlen(num_buffer); + formatted_buf = num_buffer; + break; + } + + default: { + + spec[0] = ch; + spec[1] = '\0'; + Jim_SetResultFormatted(interp, "bad field specifier \"%s\"", spec); + goto error; + } + } + + if (!gotMinus) { + while (formatted_chars < width) { + Jim_AppendString(interp, resultPtr, &pad, 1); + formatted_chars++; + } + } + + Jim_AppendString(interp, resultPtr, formatted_buf, formatted_bytes); + + while (formatted_chars < width) { + Jim_AppendString(interp, resultPtr, &pad, 1); + formatted_chars++; + } + + objIndex += gotSequential; + } + if (numBytes) { + Jim_AppendString(interp, resultPtr, span, numBytes); + } + + Jim_Free(num_buffer); + return resultPtr; + + errorMsg: + Jim_SetResultString(interp, msg, -1); + error: + Jim_FreeNewObj(interp, resultPtr); + Jim_Free(num_buffer); + return NULL; +} + + +#if defined(JIM_REGEXP) +#include +#include +#include +#include + + + +#define REG_MAX_PAREN 100 + + + +#define END 0 +#define BOL 1 +#define EOL 2 +#define ANY 3 +#define ANYOF 4 +#define ANYBUT 5 +#define BRANCH 6 +#define BACK 7 +#define EXACTLY 8 +#define NOTHING 9 +#define REP 10 +#define REPMIN 11 +#define REPX 12 +#define REPXMIN 13 +#define BOLX 14 +#define EOLX 15 +#define WORDA 16 +#define WORDZ 17 + +#define OPENNC 1000 +#define OPEN 1001 + + + + +#define CLOSENC 2000 +#define CLOSE 2001 +#define CLOSE_END (CLOSE+REG_MAX_PAREN) + +#define REG_MAGIC 0xFADED00D + + +#define OP(preg, p) (preg->program[p]) +#define NEXT(preg, p) (preg->program[p + 1]) +#define OPERAND(p) ((p) + 2) + + + + +#define FAIL(R,M) { (R)->err = (M); return (M); } +#define ISMULT(c) ((c) == '*' || (c) == '+' || (c) == '?' || (c) == '{') +#define META "^$.[()|?{+*" + +#define HASWIDTH 1 +#define SIMPLE 2 +#define SPSTART 4 +#define WORST 0 + +#define MAX_REP_COUNT 1000000 + +static int reg(regex_t *preg, int paren, int *flagp ); +static int regpiece(regex_t *preg, int *flagp ); +static int regbranch(regex_t *preg, int *flagp ); +static int regatom(regex_t *preg, int *flagp ); +static int regnode(regex_t *preg, int op ); +static int regnext(regex_t *preg, int p ); +static void regc(regex_t *preg, int b ); +static int reginsert(regex_t *preg, int op, int size, int opnd ); +static void regtail(regex_t *preg, int p, int val); +static void regoptail(regex_t *preg, int p, int val ); +static int regopsize(regex_t *preg, int p ); + +static int reg_range_find(const int *string, int c); +static const char *str_find(const char *string, int c, int nocase); +static int prefix_cmp(const int *prog, int proglen, const char *string, int nocase); + + +#ifdef DEBUG +static int regnarrate = 0; +static void regdump(regex_t *preg); +static const char *regprop( int op ); +#endif + + +static int str_int_len(const int *seq) +{ + int n = 0; + while (*seq++) { + n++; + } + return n; +} + +int regcomp(regex_t *preg, const char *exp, int cflags) +{ + int scan; + int longest; + unsigned len; + int flags; + +#ifdef DEBUG + fprintf(stderr, "Compiling: '%s'\n", exp); +#endif + memset(preg, 0, sizeof(*preg)); + + if (exp == NULL) + FAIL(preg, REG_ERR_NULL_ARGUMENT); + + + preg->cflags = cflags; + preg->regparse = exp; + + + preg->proglen = (strlen(exp) + 1) * 5; + preg->program = malloc(preg->proglen * sizeof(int)); + if (preg->program == NULL) + FAIL(preg, REG_ERR_NOMEM); + + regc(preg, REG_MAGIC); + if (reg(preg, 0, &flags) == 0) { + return preg->err; + } + + + if (preg->re_nsub >= REG_MAX_PAREN) + FAIL(preg,REG_ERR_TOO_BIG); + + + preg->regstart = 0; + preg->reganch = 0; + preg->regmust = 0; + preg->regmlen = 0; + scan = 1; + if (OP(preg, regnext(preg, scan)) == END) { + scan = OPERAND(scan); + + + if (OP(preg, scan) == EXACTLY) { + preg->regstart = preg->program[OPERAND(scan)]; + } + else if (OP(preg, scan) == BOL) + preg->reganch++; + + if (flags&SPSTART) { + longest = 0; + len = 0; + for (; scan != 0; scan = regnext(preg, scan)) { + if (OP(preg, scan) == EXACTLY) { + int plen = str_int_len(preg->program + OPERAND(scan)); + if (plen >= len) { + longest = OPERAND(scan); + len = plen; + } + } + } + preg->regmust = longest; + preg->regmlen = len; + } + } + +#ifdef DEBUG + regdump(preg); +#endif + + return 0; +} + +static int reg(regex_t *preg, int paren, int *flagp ) +{ + int ret; + int br; + int ender; + int parno = 0; + int flags; + + *flagp = HASWIDTH; + + + if (paren) { + if (preg->regparse[0] == '?' && preg->regparse[1] == ':') { + + preg->regparse += 2; + parno = -1; + } + else { + parno = ++preg->re_nsub; + } + ret = regnode(preg, OPEN+parno); + } else + ret = 0; + + + br = regbranch(preg, &flags); + if (br == 0) + return 0; + if (ret != 0) + regtail(preg, ret, br); + else + ret = br; + if (!(flags&HASWIDTH)) + *flagp &= ~HASWIDTH; + *flagp |= flags&SPSTART; + while (*preg->regparse == '|') { + preg->regparse++; + br = regbranch(preg, &flags); + if (br == 0) + return 0; + regtail(preg, ret, br); + if (!(flags&HASWIDTH)) + *flagp &= ~HASWIDTH; + *flagp |= flags&SPSTART; + } + + + ender = regnode(preg, (paren) ? CLOSE+parno : END); + regtail(preg, ret, ender); + + + for (br = ret; br != 0; br = regnext(preg, br)) + regoptail(preg, br, ender); + + + if (paren && *preg->regparse++ != ')') { + preg->err = REG_ERR_UNMATCHED_PAREN; + return 0; + } else if (!paren && *preg->regparse != '\0') { + if (*preg->regparse == ')') { + preg->err = REG_ERR_UNMATCHED_PAREN; + return 0; + } else { + preg->err = REG_ERR_JUNK_ON_END; + return 0; + } + } + + return(ret); +} + +static int regbranch(regex_t *preg, int *flagp ) +{ + int ret; + int chain; + int latest; + int flags; + + *flagp = WORST; + + ret = regnode(preg, BRANCH); + chain = 0; + while (*preg->regparse != '\0' && *preg->regparse != ')' && + *preg->regparse != '|') { + latest = regpiece(preg, &flags); + if (latest == 0) + return 0; + *flagp |= flags&HASWIDTH; + if (chain == 0) { + *flagp |= flags&SPSTART; + } + else { + regtail(preg, chain, latest); + } + chain = latest; + } + if (chain == 0) + (void) regnode(preg, NOTHING); + + return(ret); +} + +static int regpiece(regex_t *preg, int *flagp) +{ + int ret; + char op; + int next; + int flags; + int min; + int max; + + ret = regatom(preg, &flags); + if (ret == 0) + return 0; + + op = *preg->regparse; + if (!ISMULT(op)) { + *flagp = flags; + return(ret); + } + + if (!(flags&HASWIDTH) && op != '?') { + preg->err = REG_ERR_OPERAND_COULD_BE_EMPTY; + return 0; + } + + + if (op == '{') { + char *end; + + min = strtoul(preg->regparse + 1, &end, 10); + if (end == preg->regparse + 1) { + preg->err = REG_ERR_BAD_COUNT; + return 0; + } + if (*end == '}') { + max = min; + } + else if (*end == '\0') { + preg->err = REG_ERR_UNMATCHED_BRACES; + return 0; + } + else { + preg->regparse = end; + max = strtoul(preg->regparse + 1, &end, 10); + if (*end != '}') { + preg->err = REG_ERR_UNMATCHED_BRACES; + return 0; + } + } + if (end == preg->regparse + 1) { + max = MAX_REP_COUNT; + } + else if (max < min || max >= 100) { + preg->err = REG_ERR_BAD_COUNT; + return 0; + } + if (min >= 100) { + preg->err = REG_ERR_BAD_COUNT; + return 0; + } + + preg->regparse = strchr(preg->regparse, '}'); + } + else { + min = (op == '+'); + max = (op == '?' ? 1 : MAX_REP_COUNT); + } + + if (preg->regparse[1] == '?') { + preg->regparse++; + next = reginsert(preg, flags & SIMPLE ? REPMIN : REPXMIN, 5, ret); + } + else { + next = reginsert(preg, flags & SIMPLE ? REP: REPX, 5, ret); + } + preg->program[ret + 2] = max; + preg->program[ret + 3] = min; + preg->program[ret + 4] = 0; + + *flagp = (min) ? (WORST|HASWIDTH) : (WORST|SPSTART); + + if (!(flags & SIMPLE)) { + int back = regnode(preg, BACK); + regtail(preg, back, ret); + regtail(preg, next, back); + } + + preg->regparse++; + if (ISMULT(*preg->regparse)) { + preg->err = REG_ERR_NESTED_COUNT; + return 0; + } + + return ret; +} + +static void reg_addrange(regex_t *preg, int lower, int upper) +{ + if (lower > upper) { + reg_addrange(preg, upper, lower); + } + + regc(preg, upper - lower + 1); + regc(preg, lower); +} + +static void reg_addrange_str(regex_t *preg, const char *str) +{ + while (*str) { + reg_addrange(preg, *str, *str); + str++; + } +} + +static int reg_utf8_tounicode_case(const char *s, int *uc, int upper) +{ + int l = utf8_tounicode(s, uc); + if (upper) { + *uc = utf8_upper(*uc); + } + return l; +} + +static int hexdigitval(int c) +{ + if (c >= '0' && c <= '9') + return c - '0'; + if (c >= 'a' && c <= 'f') + return c - 'a' + 10; + if (c >= 'A' && c <= 'F') + return c - 'A' + 10; + return -1; +} + +static int parse_hex(const char *s, int n, int *uc) +{ + int val = 0; + int k; + + for (k = 0; k < n; k++) { + int c = hexdigitval(*s++); + if (c == -1) { + break; + } + val = (val << 4) | c; + } + if (k) { + *uc = val; + } + return k; +} + +static int reg_decode_escape(const char *s, int *ch) +{ + int n; + const char *s0 = s; + + *ch = *s++; + + switch (*ch) { + case 'b': *ch = '\b'; break; + case 'e': *ch = 27; break; + case 'f': *ch = '\f'; break; + case 'n': *ch = '\n'; break; + case 'r': *ch = '\r'; break; + case 't': *ch = '\t'; break; + case 'v': *ch = '\v'; break; + case 'u': + if (*s == '{') { + + n = parse_hex(s + 1, 6, ch); + if (n > 0 && s[n + 1] == '}' && *ch >= 0 && *ch <= 0x1fffff) { + s += n + 2; + } + else { + + *ch = 'u'; + } + } + else if ((n = parse_hex(s, 4, ch)) > 0) { + s += n; + } + break; + case 'U': + if ((n = parse_hex(s, 8, ch)) > 0) { + s += n; + } + break; + case 'x': + if ((n = parse_hex(s, 2, ch)) > 0) { + s += n; + } + break; + case '\0': + s--; + *ch = '\\'; + break; + } + return s - s0; +} + +static int regatom(regex_t *preg, int *flagp) +{ + int ret; + int flags; + int nocase = (preg->cflags & REG_ICASE); + + int ch; + int n = reg_utf8_tounicode_case(preg->regparse, &ch, nocase); + + *flagp = WORST; + + preg->regparse += n; + switch (ch) { + + case '^': + ret = regnode(preg, BOL); + break; + case '$': + ret = regnode(preg, EOL); + break; + case '.': + ret = regnode(preg, ANY); + *flagp |= HASWIDTH|SIMPLE; + break; + case '[': { + const char *pattern = preg->regparse; + + if (*pattern == '^') { + ret = regnode(preg, ANYBUT); + pattern++; + } else + ret = regnode(preg, ANYOF); + + + if (*pattern == ']' || *pattern == '-') { + reg_addrange(preg, *pattern, *pattern); + pattern++; + } + + while (*pattern && *pattern != ']') { + + int start; + int end; + + enum { + CC_ALPHA, CC_ALNUM, CC_SPACE, CC_BLANK, CC_UPPER, CC_LOWER, + CC_DIGIT, CC_XDIGIT, CC_CNTRL, CC_GRAPH, CC_PRINT, CC_PUNCT, + CC_NUM + }; + int cc; + + pattern += reg_utf8_tounicode_case(pattern, &start, nocase); + if (start == '\\') { + + switch (*pattern) { + case 's': + pattern++; + cc = CC_SPACE; + goto cc_switch; + case 'd': + pattern++; + cc = CC_DIGIT; + goto cc_switch; + case 'w': + pattern++; + reg_addrange(preg, '_', '_'); + cc = CC_ALNUM; + goto cc_switch; + } + pattern += reg_decode_escape(pattern, &start); + if (start == 0) { + preg->err = REG_ERR_NULL_CHAR; + return 0; + } + } + if (pattern[0] == '-' && pattern[1] && pattern[1] != ']') { + + pattern += utf8_tounicode(pattern, &end); + pattern += reg_utf8_tounicode_case(pattern, &end, nocase); + if (end == '\\') { + pattern += reg_decode_escape(pattern, &end); + if (end == 0) { + preg->err = REG_ERR_NULL_CHAR; + return 0; + } + } + + reg_addrange(preg, start, end); + continue; + } + if (start == '[' && pattern[0] == ':') { + static const char *character_class[] = { + ":alpha:", ":alnum:", ":space:", ":blank:", ":upper:", ":lower:", + ":digit:", ":xdigit:", ":cntrl:", ":graph:", ":print:", ":punct:", + }; + + for (cc = 0; cc < CC_NUM; cc++) { + n = strlen(character_class[cc]); + if (strncmp(pattern, character_class[cc], n) == 0) { + + pattern += n + 1; + break; + } + } + if (cc != CC_NUM) { +cc_switch: + switch (cc) { + case CC_ALNUM: + reg_addrange(preg, '0', '9'); + + case CC_ALPHA: + if ((preg->cflags & REG_ICASE) == 0) { + reg_addrange(preg, 'a', 'z'); + } + reg_addrange(preg, 'A', 'Z'); + break; + case CC_SPACE: + reg_addrange_str(preg, " \t\r\n\f\v"); + break; + case CC_BLANK: + reg_addrange_str(preg, " \t"); + break; + case CC_UPPER: + reg_addrange(preg, 'A', 'Z'); + break; + case CC_LOWER: + reg_addrange(preg, 'a', 'z'); + break; + case CC_XDIGIT: + reg_addrange(preg, 'a', 'f'); + reg_addrange(preg, 'A', 'F'); + + case CC_DIGIT: + reg_addrange(preg, '0', '9'); + break; + case CC_CNTRL: + reg_addrange(preg, 0, 31); + reg_addrange(preg, 127, 127); + break; + case CC_PRINT: + reg_addrange(preg, ' ', '~'); + break; + case CC_GRAPH: + reg_addrange(preg, '!', '~'); + break; + case CC_PUNCT: + reg_addrange(preg, '!', '/'); + reg_addrange(preg, ':', '@'); + reg_addrange(preg, '[', '`'); + reg_addrange(preg, '{', '~'); + break; + } + continue; + } + } + + reg_addrange(preg, start, start); + } + regc(preg, '\0'); + + if (*pattern) { + pattern++; + } + preg->regparse = pattern; + + *flagp |= HASWIDTH|SIMPLE; + } + break; + case '(': + ret = reg(preg, 1, &flags); + if (ret == 0) + return 0; + *flagp |= flags&(HASWIDTH|SPSTART); + break; + case '\0': + case '|': + case ')': + preg->err = REG_ERR_INTERNAL; + return 0; + case '?': + case '+': + case '*': + case '{': + preg->err = REG_ERR_COUNT_FOLLOWS_NOTHING; + return 0; + case '\\': + ch = *preg->regparse++; + switch (ch) { + case '\0': + preg->err = REG_ERR_TRAILING_BACKSLASH; + return 0; + case 'A': + ret = regnode(preg, BOLX); + break; + case 'Z': + ret = regnode(preg, EOLX); + break; + case '<': + case 'm': + ret = regnode(preg, WORDA); + break; + case '>': + case 'M': + ret = regnode(preg, WORDZ); + break; + case 'd': + case 'D': + ret = regnode(preg, ch == 'd' ? ANYOF : ANYBUT); + reg_addrange(preg, '0', '9'); + regc(preg, '\0'); + *flagp |= HASWIDTH|SIMPLE; + break; + case 'w': + case 'W': + ret = regnode(preg, ch == 'w' ? ANYOF : ANYBUT); + if ((preg->cflags & REG_ICASE) == 0) { + reg_addrange(preg, 'a', 'z'); + } + reg_addrange(preg, 'A', 'Z'); + reg_addrange(preg, '0', '9'); + reg_addrange(preg, '_', '_'); + regc(preg, '\0'); + *flagp |= HASWIDTH|SIMPLE; + break; + case 's': + case 'S': + ret = regnode(preg, ch == 's' ? ANYOF : ANYBUT); + reg_addrange_str(preg," \t\r\n\f\v"); + regc(preg, '\0'); + *flagp |= HASWIDTH|SIMPLE; + break; + + default: + + + preg->regparse--; + goto de_fault; + } + break; + de_fault: + default: { + int added = 0; + + + preg->regparse -= n; + + ret = regnode(preg, EXACTLY); + + + + while (*preg->regparse && strchr(META, *preg->regparse) == NULL) { + n = reg_utf8_tounicode_case(preg->regparse, &ch, (preg->cflags & REG_ICASE)); + if (ch == '\\' && preg->regparse[n]) { + if (strchr("<>mMwWdDsSAZ", preg->regparse[n])) { + + break; + } + n += reg_decode_escape(preg->regparse + n, &ch); + if (ch == 0) { + preg->err = REG_ERR_NULL_CHAR; + return 0; + } + } + + + if (ISMULT(preg->regparse[n])) { + + if (added) { + + break; + } + + regc(preg, ch); + added++; + preg->regparse += n; + break; + } + + + regc(preg, ch); + added++; + preg->regparse += n; + } + regc(preg, '\0'); + + *flagp |= HASWIDTH; + if (added == 1) + *flagp |= SIMPLE; + break; + } + break; + } + + return(ret); +} + +static void reg_grow(regex_t *preg, int n) +{ + if (preg->p + n >= preg->proglen) { + preg->proglen = (preg->p + n) * 2; + preg->program = realloc(preg->program, preg->proglen * sizeof(int)); + } +} + + +static int regnode(regex_t *preg, int op) +{ + reg_grow(preg, 2); + + + preg->program[preg->p++] = op; + preg->program[preg->p++] = 0; + + + return preg->p - 2; +} + +static void regc(regex_t *preg, int b ) +{ + reg_grow(preg, 1); + preg->program[preg->p++] = b; +} + +static int reginsert(regex_t *preg, int op, int size, int opnd ) +{ + reg_grow(preg, size); + + + memmove(preg->program + opnd + size, preg->program + opnd, sizeof(int) * (preg->p - opnd)); + + memset(preg->program + opnd, 0, sizeof(int) * size); + + preg->program[opnd] = op; + + preg->p += size; + + return opnd + size; +} + +static void regtail(regex_t *preg, int p, int val) +{ + int scan; + int temp; + int offset; + + + scan = p; + for (;;) { + temp = regnext(preg, scan); + if (temp == 0) + break; + scan = temp; + } + + if (OP(preg, scan) == BACK) + offset = scan - val; + else + offset = val - scan; + + preg->program[scan + 1] = offset; +} + + +static void regoptail(regex_t *preg, int p, int val ) +{ + + if (p != 0 && OP(preg, p) == BRANCH) { + regtail(preg, OPERAND(p), val); + } +} + + +static int regtry(regex_t *preg, const char *string ); +static int regmatch(regex_t *preg, int prog); +static int regrepeat(regex_t *preg, int p, int max); + +int regexec(regex_t *preg, const char *string, size_t nmatch, regmatch_t pmatch[], int eflags) +{ + const char *s; + int scan; + + + if (preg == NULL || preg->program == NULL || string == NULL) { + return REG_ERR_NULL_ARGUMENT; + } + + + if (*preg->program != REG_MAGIC) { + return REG_ERR_CORRUPTED; + } + +#ifdef DEBUG + fprintf(stderr, "regexec: %s\n", string); + regdump(preg); +#endif + + preg->eflags = eflags; + preg->pmatch = pmatch; + preg->nmatch = nmatch; + preg->start = string; + + + for (scan = OPERAND(1); scan != 0; scan += regopsize(preg, scan)) { + int op = OP(preg, scan); + if (op == END) + break; + if (op == REPX || op == REPXMIN) + preg->program[scan + 4] = 0; + } + + + if (preg->regmust != 0) { + s = string; + while ((s = str_find(s, preg->program[preg->regmust], preg->cflags & REG_ICASE)) != NULL) { + if (prefix_cmp(preg->program + preg->regmust, preg->regmlen, s, preg->cflags & REG_ICASE) >= 0) { + break; + } + s++; + } + if (s == NULL) + return REG_NOMATCH; + } + + + preg->regbol = string; + + + if (preg->reganch) { + if (eflags & REG_NOTBOL) { + + goto nextline; + } + while (1) { + if (regtry(preg, string)) { + return REG_NOERROR; + } + if (*string) { +nextline: + if (preg->cflags & REG_NEWLINE) { + + string = strchr(string, '\n'); + if (string) { + preg->regbol = ++string; + continue; + } + } + } + return REG_NOMATCH; + } + } + + + s = string; + if (preg->regstart != '\0') { + + while ((s = str_find(s, preg->regstart, preg->cflags & REG_ICASE)) != NULL) { + if (regtry(preg, s)) + return REG_NOERROR; + s++; + } + } + else + + while (1) { + if (regtry(preg, s)) + return REG_NOERROR; + if (*s == '\0') { + break; + } + else { + int c; + s += utf8_tounicode(s, &c); + } + } + + + return REG_NOMATCH; +} + + +static int regtry( regex_t *preg, const char *string ) +{ + int i; + + preg->reginput = string; + + for (i = 0; i < preg->nmatch; i++) { + preg->pmatch[i].rm_so = -1; + preg->pmatch[i].rm_eo = -1; + } + if (regmatch(preg, 1)) { + preg->pmatch[0].rm_so = string - preg->start; + preg->pmatch[0].rm_eo = preg->reginput - preg->start; + return(1); + } else + return(0); +} + +static int prefix_cmp(const int *prog, int proglen, const char *string, int nocase) +{ + const char *s = string; + while (proglen && *s) { + int ch; + int n = reg_utf8_tounicode_case(s, &ch, nocase); + if (ch != *prog) { + return -1; + } + prog++; + s += n; + proglen--; + } + if (proglen == 0) { + return s - string; + } + return -1; +} + +static int reg_range_find(const int *range, int c) +{ + while (*range) { + + if (c >= range[1] && c <= (range[0] + range[1] - 1)) { + return 1; + } + range += 2; + } + return 0; +} + +static const char *str_find(const char *string, int c, int nocase) +{ + if (nocase) { + + c = utf8_upper(c); + } + while (*string) { + int ch; + int n = reg_utf8_tounicode_case(string, &ch, nocase); + if (c == ch) { + return string; + } + string += n; + } + return NULL; +} + +static int reg_iseol(regex_t *preg, int ch) +{ + if (preg->cflags & REG_NEWLINE) { + return ch == '\0' || ch == '\n'; + } + else { + return ch == '\0'; + } +} + +static int regmatchsimplerepeat(regex_t *preg, int scan, int matchmin) +{ + int nextch = '\0'; + const char *save; + int no; + int c; + + int max = preg->program[scan + 2]; + int min = preg->program[scan + 3]; + int next = regnext(preg, scan); + + if (OP(preg, next) == EXACTLY) { + nextch = preg->program[OPERAND(next)]; + } + save = preg->reginput; + no = regrepeat(preg, scan + 5, max); + if (no < min) { + return 0; + } + if (matchmin) { + + max = no; + no = min; + } + + while (1) { + if (matchmin) { + if (no > max) { + break; + } + } + else { + if (no < min) { + break; + } + } + preg->reginput = save + utf8_index(save, no); + reg_utf8_tounicode_case(preg->reginput, &c, (preg->cflags & REG_ICASE)); + + if (reg_iseol(preg, nextch) || c == nextch) { + if (regmatch(preg, next)) { + return(1); + } + } + if (matchmin) { + + no++; + } + else { + + no--; + } + } + return(0); +} + +static int regmatchrepeat(regex_t *preg, int scan, int matchmin) +{ + int *scanpt = preg->program + scan; + + int max = scanpt[2]; + int min = scanpt[3]; + + + if (scanpt[4] < min) { + + scanpt[4]++; + if (regmatch(preg, scan + 5)) { + return 1; + } + scanpt[4]--; + return 0; + } + if (scanpt[4] > max) { + return 0; + } + + if (matchmin) { + + if (regmatch(preg, regnext(preg, scan))) { + return 1; + } + + scanpt[4]++; + if (regmatch(preg, scan + 5)) { + return 1; + } + scanpt[4]--; + return 0; + } + + if (scanpt[4] < max) { + scanpt[4]++; + if (regmatch(preg, scan + 5)) { + return 1; + } + scanpt[4]--; + } + + return regmatch(preg, regnext(preg, scan)); +} + + +static int regmatch(regex_t *preg, int prog) +{ + int scan; + int next; + const char *save; + + scan = prog; + +#ifdef DEBUG + if (scan != 0 && regnarrate) + fprintf(stderr, "%s(\n", regprop(scan)); +#endif + while (scan != 0) { + int n; + int c; +#ifdef DEBUG + if (regnarrate) { + fprintf(stderr, "%3d: %s...\n", scan, regprop(OP(preg, scan))); + } +#endif + next = regnext(preg, scan); + n = reg_utf8_tounicode_case(preg->reginput, &c, (preg->cflags & REG_ICASE)); + + switch (OP(preg, scan)) { + case BOLX: + if ((preg->eflags & REG_NOTBOL)) { + return(0); + } + + case BOL: + if (preg->reginput != preg->regbol) { + return(0); + } + break; + case EOLX: + if (c != 0) { + + return 0; + } + break; + case EOL: + if (!reg_iseol(preg, c)) { + return(0); + } + break; + case WORDA: + + if ((!isalnum(UCHAR(c))) && c != '_') + return(0); + + if (preg->reginput > preg->regbol && + (isalnum(UCHAR(preg->reginput[-1])) || preg->reginput[-1] == '_')) + return(0); + break; + case WORDZ: + + if (preg->reginput > preg->regbol) { + + if (reg_iseol(preg, c) || !isalnum(UCHAR(c)) || c != '_') { + c = preg->reginput[-1]; + + if (isalnum(UCHAR(c)) || c == '_') { + break; + } + } + } + + return(0); + + case ANY: + if (reg_iseol(preg, c)) + return 0; + preg->reginput += n; + break; + case EXACTLY: { + int opnd; + int len; + int slen; + + opnd = OPERAND(scan); + len = str_int_len(preg->program + opnd); + + slen = prefix_cmp(preg->program + opnd, len, preg->reginput, preg->cflags & REG_ICASE); + if (slen < 0) { + return(0); + } + preg->reginput += slen; + } + break; + case ANYOF: + if (reg_iseol(preg, c) || reg_range_find(preg->program + OPERAND(scan), c) == 0) { + return(0); + } + preg->reginput += n; + break; + case ANYBUT: + if (reg_iseol(preg, c) || reg_range_find(preg->program + OPERAND(scan), c) != 0) { + return(0); + } + preg->reginput += n; + break; + case NOTHING: + break; + case BACK: + break; + case BRANCH: + if (OP(preg, next) != BRANCH) + next = OPERAND(scan); + else { + do { + save = preg->reginput; + if (regmatch(preg, OPERAND(scan))) { + return(1); + } + preg->reginput = save; + scan = regnext(preg, scan); + } while (scan != 0 && OP(preg, scan) == BRANCH); + return(0); + + } + break; + case REP: + case REPMIN: + return regmatchsimplerepeat(preg, scan, OP(preg, scan) == REPMIN); + + case REPX: + case REPXMIN: + return regmatchrepeat(preg, scan, OP(preg, scan) == REPXMIN); + + case END: + return 1; + + case OPENNC: + case CLOSENC: + return regmatch(preg, next); + + default: + if (OP(preg, scan) >= OPEN+1 && OP(preg, scan) < CLOSE_END) { + save = preg->reginput; + if (regmatch(preg, next)) { + if (OP(preg, scan) < CLOSE) { + int no = OP(preg, scan) - OPEN; + if (no < preg->nmatch && preg->pmatch[no].rm_so == -1) { + preg->pmatch[no].rm_so = save - preg->start; + } + } + else { + int no = OP(preg, scan) - CLOSE; + if (no < preg->nmatch && preg->pmatch[no].rm_eo == -1) { + preg->pmatch[no].rm_eo = save - preg->start; + } + } + return(1); + } + return(0); + } + return REG_ERR_INTERNAL; + } + + scan = next; + } + + return REG_ERR_INTERNAL; +} + +static int regrepeat(regex_t *preg, int p, int max) +{ + int count = 0; + const char *scan; + int opnd; + int ch; + int n; + + scan = preg->reginput; + opnd = OPERAND(p); + switch (OP(preg, p)) { + case ANY: + + while (!reg_iseol(preg, *scan) && count < max) { + count++; + scan++; + } + break; + case EXACTLY: + while (count < max) { + n = reg_utf8_tounicode_case(scan, &ch, preg->cflags & REG_ICASE); + if (preg->program[opnd] != ch) { + break; + } + count++; + scan += n; + } + break; + case ANYOF: + while (count < max) { + n = reg_utf8_tounicode_case(scan, &ch, preg->cflags & REG_ICASE); + if (reg_iseol(preg, ch) || reg_range_find(preg->program + opnd, ch) == 0) { + break; + } + count++; + scan += n; + } + break; + case ANYBUT: + while (count < max) { + n = reg_utf8_tounicode_case(scan, &ch, preg->cflags & REG_ICASE); + if (reg_iseol(preg, ch) || reg_range_find(preg->program + opnd, ch) != 0) { + break; + } + count++; + scan += n; + } + break; + default: + preg->err = REG_ERR_INTERNAL; + count = 0; + break; + } + preg->reginput = scan; + + return(count); +} + +static int regnext(regex_t *preg, int p ) +{ + int offset; + + offset = NEXT(preg, p); + + if (offset == 0) + return 0; + + if (OP(preg, p) == BACK) + return(p-offset); + else + return(p+offset); +} + +static int regopsize(regex_t *preg, int p ) +{ + + switch (OP(preg, p)) { + case REP: + case REPMIN: + case REPX: + case REPXMIN: + return 5; + + case ANYOF: + case ANYBUT: + case EXACTLY: { + int s = p + 2; + while (preg->program[s++]) { + } + return s - p; + } + } + return 2; +} + + +size_t regerror(int errcode, const regex_t *preg, char *errbuf, size_t errbuf_size) +{ + static const char *error_strings[] = { + "success", + "no match", + "bad pattern", + "null argument", + "unknown error", + "too big", + "out of memory", + "too many ()", + "parentheses () not balanced", + "braces {} not balanced", + "invalid repetition count(s)", + "extra characters", + "*+ of empty atom", + "nested count", + "internal error", + "count follows nothing", + "trailing backslash", + "corrupted program", + "contains null char", + }; + const char *err; + + if (errcode < 0 || errcode >= REG_ERR_NUM) { + err = "Bad error code"; + } + else { + err = error_strings[errcode]; + } + + return snprintf(errbuf, errbuf_size, "%s", err); +} + +void regfree(regex_t *preg) +{ + free(preg->program); +} + +#endif +#include + +void Jim_SetResultErrno(Jim_Interp *interp, const char *msg) +{ + Jim_SetResultFormatted(interp, "%s: %s", msg, strerror(Jim_Errno())); +} + +#if defined(__MINGW32__) +#include + +int Jim_Errno(void) +{ + switch (GetLastError()) { + case ERROR_FILE_NOT_FOUND: return ENOENT; + case ERROR_PATH_NOT_FOUND: return ENOENT; + case ERROR_TOO_MANY_OPEN_FILES: return EMFILE; + case ERROR_ACCESS_DENIED: return EACCES; + case ERROR_INVALID_HANDLE: return EBADF; + case ERROR_BAD_ENVIRONMENT: return E2BIG; + case ERROR_BAD_FORMAT: return ENOEXEC; + case ERROR_INVALID_ACCESS: return EACCES; + case ERROR_INVALID_DRIVE: return ENOENT; + case ERROR_CURRENT_DIRECTORY: return EACCES; + case ERROR_NOT_SAME_DEVICE: return EXDEV; + case ERROR_NO_MORE_FILES: return ENOENT; + case ERROR_WRITE_PROTECT: return EROFS; + case ERROR_BAD_UNIT: return ENXIO; + case ERROR_NOT_READY: return EBUSY; + case ERROR_BAD_COMMAND: return EIO; + case ERROR_CRC: return EIO; + case ERROR_BAD_LENGTH: return EIO; + case ERROR_SEEK: return EIO; + case ERROR_WRITE_FAULT: return EIO; + case ERROR_READ_FAULT: return EIO; + case ERROR_GEN_FAILURE: return EIO; + case ERROR_SHARING_VIOLATION: return EACCES; + case ERROR_LOCK_VIOLATION: return EACCES; + case ERROR_SHARING_BUFFER_EXCEEDED: return ENFILE; + case ERROR_HANDLE_DISK_FULL: return ENOSPC; + case ERROR_NOT_SUPPORTED: return ENODEV; + case ERROR_REM_NOT_LIST: return EBUSY; + case ERROR_DUP_NAME: return EEXIST; + case ERROR_BAD_NETPATH: return ENOENT; + case ERROR_NETWORK_BUSY: return EBUSY; + case ERROR_DEV_NOT_EXIST: return ENODEV; + case ERROR_TOO_MANY_CMDS: return EAGAIN; + case ERROR_ADAP_HDW_ERR: return EIO; + case ERROR_BAD_NET_RESP: return EIO; + case ERROR_UNEXP_NET_ERR: return EIO; + case ERROR_NETNAME_DELETED: return ENOENT; + case ERROR_NETWORK_ACCESS_DENIED: return EACCES; + case ERROR_BAD_DEV_TYPE: return ENODEV; + case ERROR_BAD_NET_NAME: return ENOENT; + case ERROR_TOO_MANY_NAMES: return ENFILE; + case ERROR_TOO_MANY_SESS: return EIO; + case ERROR_SHARING_PAUSED: return EAGAIN; + case ERROR_REDIR_PAUSED: return EAGAIN; + case ERROR_FILE_EXISTS: return EEXIST; + case ERROR_CANNOT_MAKE: return ENOSPC; + case ERROR_OUT_OF_STRUCTURES: return ENFILE; + case ERROR_ALREADY_ASSIGNED: return EEXIST; + case ERROR_INVALID_PASSWORD: return EPERM; + case ERROR_NET_WRITE_FAULT: return EIO; + case ERROR_NO_PROC_SLOTS: return EAGAIN; + case ERROR_DISK_CHANGE: return EXDEV; + case ERROR_BROKEN_PIPE: return EPIPE; + case ERROR_OPEN_FAILED: return ENOENT; + case ERROR_DISK_FULL: return ENOSPC; + case ERROR_NO_MORE_SEARCH_HANDLES: return EMFILE; + case ERROR_INVALID_TARGET_HANDLE: return EBADF; + case ERROR_INVALID_NAME: return ENOENT; + case ERROR_PROC_NOT_FOUND: return ESRCH; + case ERROR_WAIT_NO_CHILDREN: return ECHILD; + case ERROR_CHILD_NOT_COMPLETE: return ECHILD; + case ERROR_DIRECT_ACCESS_HANDLE: return EBADF; + case ERROR_SEEK_ON_DEVICE: return ESPIPE; + case ERROR_BUSY_DRIVE: return EAGAIN; + case ERROR_DIR_NOT_EMPTY: return EEXIST; + case ERROR_NOT_LOCKED: return EACCES; + case ERROR_BAD_PATHNAME: return ENOENT; + case ERROR_LOCK_FAILED: return EACCES; + case ERROR_ALREADY_EXISTS: return EEXIST; + case ERROR_FILENAME_EXCED_RANGE: return ENAMETOOLONG; + case ERROR_BAD_PIPE: return EPIPE; + case ERROR_PIPE_BUSY: return EAGAIN; + case ERROR_PIPE_NOT_CONNECTED: return EPIPE; + case ERROR_DIRECTORY: return ENOTDIR; + } + return EINVAL; +} + +pidtype waitpid(pidtype pid, int *status, int nohang) +{ + DWORD ret = WaitForSingleObject(pid, nohang ? 0 : INFINITE); + if (ret == WAIT_TIMEOUT || ret == WAIT_FAILED) { + + return JIM_BAD_PID; + } + GetExitCodeProcess(pid, &ret); + *status = ret; + CloseHandle(pid); + return pid; +} + +int Jim_MakeTempFile(Jim_Interp *interp, const char *filename_template, int unlink_file) +{ + char name[MAX_PATH]; + HANDLE handle; + + if (!GetTempPath(MAX_PATH, name) || !GetTempFileName(name, filename_template ? filename_template : "JIM", 0, name)) { + return -1; + } + + handle = CreateFile(name, GENERIC_READ | GENERIC_WRITE, 0, NULL, + CREATE_ALWAYS, FILE_ATTRIBUTE_TEMPORARY | (unlink_file ? FILE_FLAG_DELETE_ON_CLOSE : 0), + NULL); + + if (handle == INVALID_HANDLE_VALUE) { + goto error; + } + + Jim_SetResultString(interp, name, -1); + return _open_osfhandle((int)handle, _O_RDWR | _O_TEXT); + + error: + Jim_SetResultErrno(interp, name); + DeleteFile(name); + return -1; +} + +int Jim_OpenForWrite(const char *filename, int append) +{ + if (strcmp(filename, "/dev/null") == 0) { + filename = "nul:"; + } + int fd = _open(filename, _O_WRONLY | _O_CREAT | _O_TEXT | (append ? _O_APPEND : _O_TRUNC), _S_IREAD | _S_IWRITE); + if (fd >= 0 && append) { + + _lseek(fd, 0L, SEEK_END); + } + return fd; +} + +int Jim_OpenForRead(const char *filename) +{ + if (strcmp(filename, "/dev/null") == 0) { + filename = "nul:"; + } + return _open(filename, _O_RDONLY | _O_TEXT, 0); +} + +#elif defined(HAVE_UNISTD_H) + + + +int Jim_MakeTempFile(Jim_Interp *interp, const char *filename_template, int unlink_file) +{ + int fd; + mode_t mask; + Jim_Obj *filenameObj; + + if (filename_template == NULL) { + const char *tmpdir = getenv("TMPDIR"); + if (tmpdir == NULL || *tmpdir == '\0' || access(tmpdir, W_OK) != 0) { + tmpdir = "/tmp/"; + } + filenameObj = Jim_NewStringObj(interp, tmpdir, -1); + if (tmpdir[0] && tmpdir[strlen(tmpdir) - 1] != '/') { + Jim_AppendString(interp, filenameObj, "/", 1); + } + Jim_AppendString(interp, filenameObj, "tcl.tmp.XXXXXX", -1); + } + else { + filenameObj = Jim_NewStringObj(interp, filename_template, -1); + } + + + mask = umask(S_IXUSR | S_IRWXG | S_IRWXO); +#ifdef HAVE_MKSTEMP + fd = mkstemp(filenameObj->bytes); +#else + if (mktemp(filenameObj->bytes) == NULL) { + fd = -1; + } + else { + fd = open(filenameObj->bytes, O_RDWR | O_CREAT | O_TRUNC); + } +#endif + umask(mask); + if (fd < 0) { + Jim_SetResultErrno(interp, Jim_String(filenameObj)); + Jim_FreeNewObj(interp, filenameObj); + return -1; + } + if (unlink_file) { + remove(Jim_String(filenameObj)); + } + + Jim_SetResult(interp, filenameObj); + return fd; +} + +int Jim_OpenForWrite(const char *filename, int append) +{ + return open(filename, O_WRONLY | O_CREAT | (append ? O_APPEND : O_TRUNC), 0666); +} + +int Jim_OpenForRead(const char *filename) +{ + return open(filename, O_RDONLY, 0); +} + +#endif + +#if defined(_WIN32) || defined(WIN32) +#ifndef STRICT +#define STRICT +#endif +#define WIN32_LEAN_AND_MEAN +#include + +#if defined(HAVE_DLOPEN_COMPAT) +void *dlopen(const char *path, int mode) +{ + JIM_NOTUSED(mode); + + return (void *)LoadLibraryA(path); +} + +int dlclose(void *handle) +{ + FreeLibrary((HANDLE)handle); + return 0; +} + +void *dlsym(void *handle, const char *symbol) +{ + return GetProcAddress((HMODULE)handle, symbol); +} + +char *dlerror(void) +{ + static char msg[121]; + FormatMessageA(FORMAT_MESSAGE_FROM_SYSTEM, NULL, GetLastError(), + LANG_NEUTRAL, msg, sizeof(msg) - 1, NULL); + return msg; +} +#endif + +#ifdef _MSC_VER + +#include + + +int gettimeofday(struct timeval *tv, void *unused) +{ + struct _timeb tb; + + _ftime(&tb); + tv->tv_sec = tb.time; + tv->tv_usec = tb.millitm * 1000; + + return 0; +} + + +DIR *opendir(const char *name) +{ + DIR *dir = 0; + + if (name && name[0]) { + size_t base_length = strlen(name); + const char *all = + strchr("/\\", name[base_length - 1]) ? "*" : "/*"; + + if ((dir = (DIR *) Jim_Alloc(sizeof *dir)) != 0 && + (dir->name = (char *)Jim_Alloc(base_length + strlen(all) + 1)) != 0) { + strcat(strcpy(dir->name, name), all); + + if ((dir->handle = (long)_findfirst(dir->name, &dir->info)) != -1) + dir->result.d_name = 0; + else { + Jim_Free(dir->name); + Jim_Free(dir); + dir = 0; + } + } + else { + Jim_Free(dir); + dir = 0; + errno = ENOMEM; + } + } + else { + errno = EINVAL; + } + return dir; +} + +int closedir(DIR * dir) +{ + int result = -1; + + if (dir) { + if (dir->handle != -1) + result = _findclose(dir->handle); + Jim_Free(dir->name); + Jim_Free(dir); + } + if (result == -1) + errno = EBADF; + return result; +} + +struct dirent *readdir(DIR * dir) +{ + struct dirent *result = 0; + + if (dir && dir->handle != -1) { + if (!dir->result.d_name || _findnext(dir->handle, &dir->info) != -1) { + result = &dir->result; + result->d_name = dir->info.name; + } + } + else { + errno = EBADF; + } + return result; +} +#endif +#endif +#include +#include + + + + + + +#ifndef SIGPIPE +#define SIGPIPE 13 +#endif +#ifndef SIGINT +#define SIGINT 2 +#endif + +const char *Jim_SignalId(int sig) +{ + static char buf[10]; + switch (sig) { + case SIGINT: return "SIGINT"; + case SIGPIPE: return "SIGPIPE"; + + } + snprintf(buf, sizeof(buf), "%d", sig); + return buf; +} +#ifndef JIM_BOOTSTRAP_LIB_ONLY +#include +#include + + +#ifdef USE_LINENOISE +#ifdef HAVE_UNISTD_H + #include +#endif +#ifdef HAVE_SYS_STAT_H + #include +#endif +#include "linenoise.h" +#else +#define MAX_LINE_LEN 512 +#endif + +#ifdef USE_LINENOISE +static void JimCompletionCallback(const char *prefix, linenoiseCompletions *comp, void *userdata); +static const char completion_callback_assoc_key[] = "interactive-completion"; +#endif + +char *Jim_HistoryGetline(Jim_Interp *interp, const char *prompt) +{ +#ifdef USE_LINENOISE + struct JimCompletionInfo *compinfo = Jim_GetAssocData(interp, completion_callback_assoc_key); + char *result; + Jim_Obj *objPtr; + long mlmode = 0; + if (compinfo) { + linenoiseSetCompletionCallback(JimCompletionCallback, compinfo); + } + objPtr = Jim_GetVariableStr(interp, "history::multiline", JIM_NONE); + if (objPtr && Jim_GetLong(interp, objPtr, &mlmode) == JIM_NONE) { + linenoiseSetMultiLine(mlmode); + } + + result = linenoise(prompt); + + linenoiseSetCompletionCallback(NULL, NULL); + return result; +#else + int len; + char *line = malloc(MAX_LINE_LEN); + + fputs(prompt, stdout); + fflush(stdout); + + if (fgets(line, MAX_LINE_LEN, stdin) == NULL) { + free(line); + return NULL; + } + len = strlen(line); + if (len && line[len - 1] == '\n') { + line[len - 1] = '\0'; + } + return line; +#endif +} + +void Jim_HistoryLoad(const char *filename) +{ +#ifdef USE_LINENOISE + linenoiseHistoryLoad(filename); +#endif +} + +void Jim_HistoryAdd(const char *line) +{ +#ifdef USE_LINENOISE + linenoiseHistoryAdd(line); +#endif +} + +void Jim_HistorySave(const char *filename) +{ +#ifdef USE_LINENOISE +#ifdef HAVE_UMASK + mode_t mask; + + mask = umask(S_IXUSR | S_IRWXG | S_IRWXO); +#endif + linenoiseHistorySave(filename); +#ifdef HAVE_UMASK + umask(mask); +#endif +#endif +} + +void Jim_HistoryShow(void) +{ +#ifdef USE_LINENOISE + + int i; + int len; + char **history = linenoiseHistory(&len); + for (i = 0; i < len; i++) { + printf("%4d %s\n", i + 1, history[i]); + } +#endif +} + +#ifdef USE_LINENOISE +struct JimCompletionInfo { + Jim_Interp *interp; + Jim_Obj *command; +}; + +static void JimCompletionCallback(const char *prefix, linenoiseCompletions *comp, void *userdata) +{ + struct JimCompletionInfo *info = (struct JimCompletionInfo *)userdata; + Jim_Obj *objv[2]; + int ret; + + objv[0] = info->command; + objv[1] = Jim_NewStringObj(info->interp, prefix, -1); + + ret = Jim_EvalObjVector(info->interp, 2, objv); + + + if (ret == JIM_OK) { + int i; + Jim_Obj *listObj = Jim_GetResult(info->interp); + int len = Jim_ListLength(info->interp, listObj); + for (i = 0; i < len; i++) { + linenoiseAddCompletion(comp, Jim_String(Jim_ListGetIndex(info->interp, listObj, i))); + } + } +} + +static void JimHistoryFreeCompletion(Jim_Interp *interp, void *data) +{ + struct JimCompletionInfo *compinfo = data; + + Jim_DecrRefCount(interp, compinfo->command); + + Jim_Free(compinfo); +} +#endif + +void Jim_HistorySetCompletion(Jim_Interp *interp, Jim_Obj *commandObj) +{ +#ifdef USE_LINENOISE + if (commandObj) { + + Jim_IncrRefCount(commandObj); + } + + Jim_DeleteAssocData(interp, completion_callback_assoc_key); + + if (commandObj) { + struct JimCompletionInfo *compinfo = Jim_Alloc(sizeof(*compinfo)); + compinfo->interp = interp; + compinfo->command = commandObj; + + Jim_SetAssocData(interp, completion_callback_assoc_key, JimHistoryFreeCompletion, compinfo); + } +#endif +} + +int Jim_InteractivePrompt(Jim_Interp *interp) +{ + int retcode = JIM_OK; + char *history_file = NULL; +#ifdef USE_LINENOISE + const char *home; + + home = getenv("HOME"); + if (home && isatty(STDIN_FILENO)) { + int history_len = strlen(home) + sizeof("/.jim_history"); + history_file = Jim_Alloc(history_len); + snprintf(history_file, history_len, "%s/.jim_history", home); + Jim_HistoryLoad(history_file); + } + + Jim_HistorySetCompletion(interp, Jim_NewStringObj(interp, "tcl::autocomplete", -1)); +#endif + + printf("Welcome to Jim version %d.%d\n", + JIM_VERSION / 100, JIM_VERSION % 100); + Jim_SetVariableStrWithStr(interp, JIM_INTERACTIVE, "1"); + + while (1) { + Jim_Obj *scriptObjPtr; + const char *result; + int reslen; + char prompt[20]; + + if (retcode != JIM_OK) { + const char *retcodestr = Jim_ReturnCode(retcode); + + if (*retcodestr == '?') { + snprintf(prompt, sizeof(prompt) - 3, "[%d] . ", retcode); + } + else { + snprintf(prompt, sizeof(prompt) - 3, "[%s] . ", retcodestr); + } + } + else { + strcpy(prompt, ". "); + } + + scriptObjPtr = Jim_NewStringObj(interp, "", 0); + Jim_IncrRefCount(scriptObjPtr); + while (1) { + char state; + char *line; + + line = Jim_HistoryGetline(interp, prompt); + if (line == NULL) { + if (errno == EINTR) { + continue; + } + Jim_DecrRefCount(interp, scriptObjPtr); + retcode = JIM_OK; + goto out; + } + if (Jim_Length(scriptObjPtr) != 0) { + + Jim_AppendString(interp, scriptObjPtr, "\n", 1); + } + Jim_AppendString(interp, scriptObjPtr, line, -1); + free(line); + if (Jim_ScriptIsComplete(interp, scriptObjPtr, &state)) + break; + + snprintf(prompt, sizeof(prompt), "%c> ", state); + } +#ifdef USE_LINENOISE + if (strcmp(Jim_String(scriptObjPtr), "h") == 0) { + + Jim_HistoryShow(); + Jim_DecrRefCount(interp, scriptObjPtr); + continue; + } + + Jim_HistoryAdd(Jim_String(scriptObjPtr)); + if (history_file) { + Jim_HistorySave(history_file); + } +#endif + retcode = Jim_EvalObj(interp, scriptObjPtr); + Jim_DecrRefCount(interp, scriptObjPtr); + + if (retcode == JIM_EXIT) { + break; + } + if (retcode == JIM_ERR) { + Jim_MakeErrorMessage(interp); + } + result = Jim_GetString(Jim_GetResult(interp), &reslen); + if (reslen) { + printf("%s\n", result); + } + } + out: + Jim_Free(history_file); + + return retcode; +} + +#include +#include +#include + + + +extern int Jim_initjimshInit(Jim_Interp *interp); + +static void JimSetArgv(Jim_Interp *interp, int argc, char *const argv[]) +{ + int n; + Jim_Obj *listObj = Jim_NewListObj(interp, NULL, 0); + + + for (n = 0; n < argc; n++) { + Jim_Obj *obj = Jim_NewStringObj(interp, argv[n], -1); + + Jim_ListAppendElement(interp, listObj, obj); + } + + Jim_SetVariableStr(interp, "argv", listObj); + Jim_SetVariableStr(interp, "argc", Jim_NewIntObj(interp, argc)); +} + +static void JimPrintErrorMessage(Jim_Interp *interp) +{ + Jim_MakeErrorMessage(interp); + fprintf(stderr, "%s\n", Jim_String(Jim_GetResult(interp))); +} + +void usage(const char* executable_name) +{ + printf("jimsh version %d.%d\n", JIM_VERSION / 100, JIM_VERSION % 100); + printf("Usage: %s\n", executable_name); + printf("or : %s [options] [filename]\n", executable_name); + printf("\n"); + printf("Without options: Interactive mode\n"); + printf("\n"); + printf("Options:\n"); + printf(" --version : prints the version string\n"); + printf(" --help : prints this text\n"); + printf(" -e CMD : executes command CMD\n"); + printf(" NOTE: all subsequent options will be passed as arguments to the command\n"); + printf(" [filename|-] : executes the script contained in the named file, or from stdin if \"-\"\n"); + printf(" NOTE: all subsequent options will be passed to the script\n\n"); +} + +int main(int argc, char *const argv[]) +{ + int retcode; + Jim_Interp *interp; + char *const orig_argv0 = argv[0]; + + + if (argc > 1 && strcmp(argv[1], "--version") == 0) { + printf("%d.%d\n", JIM_VERSION / 100, JIM_VERSION % 100); + return 0; + } + else if (argc > 1 && strcmp(argv[1], "--help") == 0) { + usage(argv[0]); + return 0; + } + + + interp = Jim_CreateInterp(); + Jim_RegisterCoreCommands(interp); + + + if (Jim_InitStaticExtensions(interp) != JIM_OK) { + JimPrintErrorMessage(interp); + } + + Jim_SetVariableStrWithStr(interp, "jim::argv0", orig_argv0); + Jim_SetVariableStrWithStr(interp, JIM_INTERACTIVE, argc == 1 ? "1" : "0"); + retcode = Jim_initjimshInit(interp); + + if (argc == 1) { + + if (retcode == JIM_ERR) { + JimPrintErrorMessage(interp); + } + if (retcode != JIM_EXIT) { + JimSetArgv(interp, 0, NULL); + retcode = Jim_InteractivePrompt(interp); + } + } + else { + + if (argc > 2 && strcmp(argv[1], "-e") == 0) { + + JimSetArgv(interp, argc - 3, argv + 3); + retcode = Jim_Eval(interp, argv[2]); + if (retcode != JIM_ERR) { + printf("%s\n", Jim_String(Jim_GetResult(interp))); + } + } + else { + Jim_SetVariableStr(interp, "argv0", Jim_NewStringObj(interp, argv[1], -1)); + JimSetArgv(interp, argc - 2, argv + 2); + if (strcmp(argv[1], "-") == 0) { + retcode = Jim_Eval(interp, "eval [info source [stdin read] stdin 1]"); + } else { + retcode = Jim_EvalFile(interp, argv[1]); + } + } + if (retcode == JIM_ERR) { + JimPrintErrorMessage(interp); + } + } + if (retcode == JIM_EXIT) { + retcode = Jim_GetExitCode(interp); + } + else if (retcode == JIM_ERR) { + retcode = 1; + } + else { + retcode = 0; + } + Jim_FreeInterp(interp); + return retcode; +} +#endif ADDED autosetup/local.tcl Index: autosetup/local.tcl ================================================================== --- /dev/null +++ autosetup/local.tcl @@ -0,0 +1,31 @@ +# For this project, disable the pager for --help and --ref +# The user can still enable by using --nopager=0 or --disable-nopager +dict set autosetup(optdefault) nopager 1 + +# Searches for a usable Tcl (prefer 8.6, 8.5, 8.4) in the given paths +# Returns a dictionary of the contents of the tclConfig.sh file, or +# empty if not found +proc parse-tclconfig-sh {args} { + foreach p $args { + # Allow pointing directly to the path containing tclConfig.sh + if {[file exists $p/tclConfig.sh]} { + return [parse-tclconfig-sh-file $p/tclConfig.sh] + } + # Some systems allow for multiple versions + foreach libpath {lib/tcl8.6 lib/tcl8.5 lib/tcl8.4 lib/tcl tcl lib} { + if {[file exists $p/$libpath/tclConfig.sh]} { + return [parse-tclconfig-sh-file $p/$libpath/tclConfig.sh] + } + } + } +} + +proc parse-tclconfig-sh-file {filename} { + foreach line [split [readfile $filename] \n] { + if {[regexp {^(TCL_[^=]*)=(.*)$} $line -> name value]} { + set value [regsub -all {\$\{.*\}} $value ""] + set tclconfig($name) [string trim $value '] + } + } + return [array get tclconfig] +} ADDED autosetup/pkg-config.tcl Index: autosetup/pkg-config.tcl ================================================================== --- /dev/null +++ autosetup/pkg-config.tcl @@ -0,0 +1,135 @@ +# Copyright (c) 2016 WorkWare Systems http://www.workware.net.au/ +# All rights reserved + +# @synopsis: +# +# The 'pkg-config' module allows package information to be found via 'pkg-config'. +# +# If not cross-compiling, the package path should be determined automatically +# by 'pkg-config'. +# If cross-compiling, the default package path is the compiler sysroot. +# If the C compiler doesn't support '-print-sysroot', the path can be supplied +# by the '--sysroot' option or by defining 'SYSROOT'. +# +# 'PKG_CONFIG' may be set to use an alternative to 'pkg-config'. + +use cc + +module-options { + sysroot:dir => "Override compiler sysroot for pkg-config search path" +} + +# @pkg-config-init ?required? +# +# Initialises the 'pkg-config' system. Unless '$required' is set to 0, +# it is a fatal error if a usable 'pkg-config' is not found . +# +# This command will normally be called automatically as required, +# but it may be invoked explicitly if lack of 'pkg-config' is acceptable. +# +# Returns 1 if ok, or 0 if 'pkg-config' not found/usable (only if '$required' is 0). +# +proc pkg-config-init {{required 1}} { + if {[is-defined HAVE_PKG_CONFIG]} { + return [get-define HAVE_PKG_CONFIG] + } + set found 0 + + define PKG_CONFIG [get-env PKG_CONFIG pkg-config] + msg-checking "Checking for pkg-config..." + + if {[catch {exec [get-define PKG_CONFIG] --version} version]} { + msg-result "[get-define PKG_CONFIG] (not found)" + if {$required} { + user-error "No usable pkg-config" + } + } else { + msg-result $version + define PKG_CONFIG_VERSION $version + + set found 1 + + if {[opt-str sysroot o]} { + define SYSROOT [file-normalize $o] + msg-result "Using specified sysroot [get-define SYSROOT]" + } elseif {[get-define build] ne [get-define host]} { + if {[catch {exec-with-stderr [get-define CC] -print-sysroot} result errinfo] == 0} { + # Use the compiler sysroot, if there is one + define SYSROOT $result + msg-result "Found compiler sysroot $result" + } else { + set msg "pkg-config: Cross compiling, but no compiler sysroot and no --sysroot supplied" + if {$required} { + user-error $msg + } else { + msg-result $msg + } + set found 0 + } + } + if {[is-defined SYSROOT]} { + set sysroot [get-define SYSROOT] + + # XXX: It's possible that these should be set only when invoking pkg-config + global env + set env(PKG_CONFIG_DIR) "" + # Do we need to try /usr/local as well or instead? + set env(PKG_CONFIG_LIBDIR) $sysroot/usr/lib/pkgconfig:$sysroot/usr/share/pkgconfig + set env(PKG_CONFIG_SYSROOT_DIR) $sysroot + } + } + define HAVE_PKG_CONFIG $found + return $found +} + +# @pkg-config module ?requirements? +# +# Use 'pkg-config' to find the given module meeting the given requirements. +# e.g. +# +## pkg-config pango >= 1.37.0 +# +# If found, returns 1 and sets 'HAVE_PKG_PANGO' to 1 along with: +# +## PKG_PANGO_VERSION to the found version +## PKG_PANGO_LIBS to the required libs (--libs-only-l) +## PKG_PANGO_LDFLAGS to the required linker flags (--libs-only-L) +## PKG_PANGO_CFLAGS to the required compiler flags (--cflags) +# +# If not found, returns 0. +# +proc pkg-config {module args} { + set ok [pkg-config-init] + + msg-checking "Checking for $module $args..." + + if {!$ok} { + msg-result "no pkg-config" + return 0 + } + + if {[catch {exec [get-define PKG_CONFIG] --modversion "$module $args"} version]} { + msg-result "not found" + configlog "pkg-config --modversion $module $args: $version" + return 0 + } + msg-result $version + set prefix [feature-define-name $module PKG_] + define HAVE_${prefix} + define ${prefix}_VERSION $version + define ${prefix}_LIBS [exec pkg-config --libs-only-l $module] + define ${prefix}_LDFLAGS [exec pkg-config --libs-only-L $module] + define ${prefix}_CFLAGS [exec pkg-config --cflags $module] + return 1 +} + +# @pkg-config-get module setting +# +# Convenience access to the results of 'pkg-config'. +# +# For example, '[pkg-config-get pango CFLAGS]' returns +# the value of 'PKG_PANGO_CFLAGS', or '""' if not defined. +proc pkg-config-get {module name} { + set prefix [feature-define-name $module PKG_] + get-define ${prefix}_${name} "" +} ADDED autosetup/system.tcl Index: autosetup/system.tcl ================================================================== --- /dev/null +++ autosetup/system.tcl @@ -0,0 +1,407 @@ +# Copyright (c) 2010 WorkWare Systems http://www.workware.net.au/ +# All rights reserved + +# @synopsis: +# +# This module supports common system interrogation and options +# such as '--host', '--build', '--prefix', and setting 'srcdir', 'builddir', and 'EXEEXT'. +# +# It also support the "feature" naming convention, where searching +# for a feature such as 'sys/type.h' defines 'HAVE_SYS_TYPES_H'. +# +# It defines the following variables, based on '--prefix' unless overridden by the user: +# +## datadir +## sysconfdir +## sharedstatedir +## localstatedir +## infodir +## mandir +## includedir +# +# If '--prefix' is not supplied, it defaults to '/usr/local' unless 'defaultprefix' is defined *before* +# including the 'system' module. + +if {[is-defined defaultprefix]} { + user-notice "Note: defaultprefix is deprecated. Use options-defaults to set default options" + options-defaults [list prefix [get-define defaultprefix]] +} + +module-options [subst -noc -nob { + host:host-alias => {a complete or partial cpu-vendor-opsys for the system where + the application will run (defaults to the same value as --build)} + build:build-alias => {a complete or partial cpu-vendor-opsys for the system + where the application will be built (defaults to the + result of running config.guess)} + prefix:dir=/usr/local => {the target directory for the build (default: '@default@')} + + # These (hidden) options are supported for autoconf/automake compatibility + exec-prefix: + bindir: + sbindir: + includedir: + mandir: + infodir: + libexecdir: + datadir: + libdir: + sysconfdir: + sharedstatedir: + localstatedir: + runstatedir: + maintainer-mode=0 + dependency-tracking=0 + silent-rules=0 +}] + +# @check-feature name { script } +# +# defines feature '$name' to the return value of '$script', +# which should be 1 if found or 0 if not found. +# +# e.g. the following will define 'HAVE_CONST' to 0 or 1. +# +## check-feature const { +## cctest -code {const int _x = 0;} +## } +proc check-feature {name code} { + msg-checking "Checking for $name..." + set r [uplevel 1 $code] + define-feature $name $r + if {$r} { + msg-result "ok" + } else { + msg-result "not found" + } + return $r +} + +# @have-feature name ?default=0? +# +# Returns the value of feature '$name' if defined, or '$default' if not. +# +# See 'feature-define-name' for how the "feature" name +# is translated into the "define" name. +# +proc have-feature {name {default 0}} { + get-define [feature-define-name $name] $default +} + +# @define-feature name ?value=1? +# +# Sets the feature 'define' to '$value'. +# +# See 'feature-define-name' for how the "feature" name +# is translated into the "define" name. +# +proc define-feature {name {value 1}} { + define [feature-define-name $name] $value +} + +# @feature-checked name +# +# Returns 1 if feature '$name' has been checked, whether true or not. +# +proc feature-checked {name} { + is-defined [feature-define-name $name] +} + +# @feature-define-name name ?prefix=HAVE_? +# +# Converts a "feature" name to the corresponding "define", +# e.g. 'sys/stat.h' becomes 'HAVE_SYS_STAT_H'. +# +# Converts '*' to 'P' and all non-alphanumeric to underscore. +# +proc feature-define-name {name {prefix HAVE_}} { + string toupper $prefix[regsub -all {[^a-zA-Z0-9]} [regsub -all {[*]} $name p] _] +} + +# @write-if-changed filename contents ?script? +# +# If '$filename' doesn't exist, or it's contents are different to '$contents', +# the file is written and '$script' is evaluated. +# +# Otherwise a "file is unchanged" message is displayed. +proc write-if-changed {file buf {script {}}} { + set old [readfile $file ""] + if {$old eq $buf && [file exists $file]} { + msg-result "$file is unchanged" + } else { + writefile $file $buf\n + uplevel 1 $script + } +} + + +# @include-file infile mapping +# +# The core of make-template, called recursively for each @include +# directive found within that template so that this proc's result +# is the fully-expanded template. +# +# The mapping parameter is how we expand @varname@ within the template. +# We do that inline within this step only for @include directives which +# can have variables in the filename arg. A separate substitution pass +# happens when this recursive function returns, expanding the rest of +# the variables. +# +proc include-file {infile mapping} { + # A stack of true/false conditions, one for each nested conditional + # starting with "true" + set condstack {1} + set result {} + set linenum 0 + foreach line [split [readfile $infile] \n] { + incr linenum + if {[regexp {^@(if|else|endif)(\s*)(.*)} $line -> condtype condspace condargs]} { + if {$condtype eq "if"} { + if {[string length $condspace] == 0} { + autosetup-error "$infile:$linenum: Invalid expression: $line" + } + if {[llength $condargs] == 1} { + # ABC => [get-define ABC] ni {0 ""} + # !ABC => [get-define ABC] in {0 ""} + lassign $condargs condvar + if {[regexp {^!(.*)} $condvar -> condvar]} { + set op in + } else { + set op ni + } + set condexpr "\[[list get-define $condvar]\] $op {0 {}}" + } else { + # Translate alphanumeric ABC into [get-define ABC] and leave the + # rest of the expression untouched + regsub -all {([A-Z][[:alnum:]_]*)} $condargs {[get-define \1]} condexpr + } + if {[catch [list expr $condexpr] condval]} { + dputs $condval + autosetup-error "$infile:$linenum: Invalid expression: $line" + } + dputs "@$condtype: $condexpr => $condval" + } + if {$condtype ne "if"} { + if {[llength $condstack] <= 1} { + autosetup-error "$infile:$linenum: Error: @$condtype missing @if" + } elseif {[string length $condargs] && [string index $condargs 0] ne "#"} { + autosetup-error "$infile:$linenum: Error: Extra arguments after @$condtype" + } + } + switch -exact $condtype { + if { + # push condval + lappend condstack $condval + } + else { + # Toggle the last entry + set condval [lpop condstack] + set condval [expr {!$condval}] + lappend condstack $condval + } + endif { + if {[llength $condstack] == 0} { + user-notice "$infile:$linenum: Error: @endif missing @if" + } + lpop condstack + } + } + continue + } elseif {[regexp {^@include\s+(.*)} $line -> filearg]} { + set incfile [string map $mapping $filearg] + if {[file exists $incfile]} { + lappend ::autosetup(deps) [file-normalize $incfile] + lappend result {*}[include-file $incfile $mapping] + } else { + user-error "$infile:$linenum: Include file $incfile is missing" + } + continue + } elseif {[regexp {^@define\s+(\w+)\s+(.*)} $line -> var val]} { + define $var $val + continue + } + # Only output this line if the stack contains all "true" + if {"0" in $condstack} { + continue + } + lappend result $line + } + return $result +} + + +# @make-template template ?outfile? +# +# Reads the input file '/$template' and writes the output file '$outfile' +# (unless unchanged). +# If '$outfile' is blank/omitted, '$template' should end with '.in' which +# is removed to create the output file name. +# +# Each pattern of the form '@define@' is replaced with the corresponding +# "define", if it exists, or left unchanged if not. +# +# The special value '@srcdir@' is substituted with the relative +# path to the source directory from the directory where the output +# file is created, while the special value '@top_srcdir@' is substituted +# with the relative path to the top level source directory. +# +# Conditional sections may be specified as follows: +## @if NAME eq "value" +## lines +## @else +## lines +## @endif +# +# Where 'NAME' is a defined variable name and '@else' is optional. +# Note that variables names *must* start with an uppercase letter. +# If the expression does not match, all lines through '@endif' are ignored. +# +# The alternative forms may also be used: +## @if NAME (true if the variable is defined, but not empty and not "0") +## @if !NAME (opposite of the form above) +## @if +# +# In the general Tcl expression, any words beginning with an uppercase letter +# are translated into [get-define NAME] +# +# Expressions may be nested +# +proc make-template {template {out {}}} { + set infile [file join $::autosetup(srcdir) $template] + + if {![file exists $infile]} { + user-error "Template $template is missing" + } + + # Define this as late as possible + define AUTODEPS $::autosetup(deps) + + if {$out eq ""} { + if {[file ext $template] ne ".in"} { + autosetup-error "make_template $template has no target file and can't guess" + } + set out [file rootname $template] + } + + set outdir [file dirname $out] + + # Make sure the directory exists + file mkdir $outdir + + # Set up srcdir and top_srcdir to be relative to the target dir + define srcdir [relative-path [file join $::autosetup(srcdir) $outdir] $outdir] + define top_srcdir [relative-path $::autosetup(srcdir) $outdir] + + # Build map from global defines to their values so they can be + # substituted into @include file names. + proc build-define-mapping {} { + set mapping {} + foreach {n v} [array get ::define] { + lappend mapping @$n@ $v + } + return $mapping + } + set mapping [build-define-mapping] + + set result [include-file $infile $mapping] + + # Rebuild the define mapping in case we ran across @define + # directives in the template or a file it @included, then + # apply that mapping to the expanded template. + set mapping [build-define-mapping] + write-if-changed $out [string map $mapping [join $result \n]] { + msg-result "Created [relative-path $out] from [relative-path $template]" + } +} + +# build/host tuples and cross-compilation prefix +opt-str build build "" +define build_alias $build +if {$build eq ""} { + define build [config_guess] +} else { + define build [config_sub $build] +} + +opt-str host host "" +define host_alias $host +if {$host eq ""} { + define host [get-define build] + set cross "" +} else { + define host [config_sub $host] + set cross $host- +} +define cross [get-env CROSS $cross] + +# build/host _cpu, _vendor and _os +foreach type {build host} { + set v [get-define $type] + if {![regexp {^([^-]+)-([^-]+)-(.*)$} $v -> cpu vendor os]} { + user-error "Invalid canonical $type: $v" + } + define ${type}_cpu $cpu + define ${type}_vendor $vendor + define ${type}_os $os +} + +opt-str prefix prefix /usr/local + +# These are for compatibility with autoconf +define target [get-define host] +define prefix $prefix +define builddir $autosetup(builddir) +define srcdir $autosetup(srcdir) +define top_srcdir $autosetup(srcdir) +define abs_top_srcdir [file-normalize $autosetup(srcdir)] +define abs_top_builddir [file-normalize $autosetup(builddir)] + +# autoconf supports all of these +define exec_prefix [opt-str exec-prefix exec_prefix $prefix] +foreach {name defpath} { + bindir /bin + sbindir /sbin + libexecdir /libexec + libdir /lib +} { + define $name [opt-str $name o $exec_prefix$defpath] +} +foreach {name defpath} { + datadir /share + sharedstatedir /com + infodir /share/info + mandir /share/man + includedir /include +} { + define $name [opt-str $name o $prefix$defpath] +} +if {$prefix ne {/usr}} { + opt-str sysconfdir sysconfdir $prefix/etc +} else { + opt-str sysconfdir sysconfdir /etc +} +define sysconfdir $sysconfdir + +define localstatedir [opt-str localstatedir o /var] +define runstatedir [opt-str runstatedir o /run] + +define SHELL [get-env SHELL [find-an-executable sh bash ksh]] + +# These could be used to generate Makefiles following some automake conventions +define AM_SILENT_RULES [opt-bool silent-rules] +define AM_MAINTAINER_MODE [opt-bool maintainer-mode] +define AM_DEPENDENCY_TRACKING [opt-bool dependency-tracking] + +# Windows vs. non-Windows +switch -glob -- [get-define host] { + *-*-ming* - *-*-cygwin - *-*-msys { + define-feature windows + define EXEEXT .exe + } + default { + define EXEEXT "" + } +} + +# Display +msg-result "Host System...[get-define host]" +msg-result "Build System...[get-define build]" ADDED autosetup/tmake.auto Index: autosetup/tmake.auto ================================================================== --- /dev/null +++ autosetup/tmake.auto @@ -0,0 +1,55 @@ +# Copyright (c) 2016 WorkWare Systems http://www.workware.net.au/ +# All rights reserved + +# Auto-load module for 'tmake' build system integration + +use init + +autosetup_add_init_type tmake "Tcl-based tmake build system" { + autosetup_check_create auto.def \ +{# Initial auto.def created by 'autosetup --init=tmake' +# vim:set syntax=tcl: + +use cc cc-lib cc-db cc-shared +use tmake + +# Add any user options here +# Really want a --configure that takes over the rest of the command line +options { +} + +cc-check-tools ar ranlib + +set objdir [get-env BUILDDIR objdir] + +make-config-header $objdir/include/autoconf.h +make-tmake-settings $objdir/settings.conf {[A-Z]*} *dir lib_* +} + + autosetup_check_create project.spec \ +{# Initial project.spec created by 'autosetup --init=tmake' + +tmake-require-version 0.7.3 + +# vim:set syntax=tcl: +define? DESTDIR _install + +# XXX If configure creates additional/different files than include/autoconf.h +# that should be reflected here +Autosetup include/autoconf.h + +# e.g. for autoconf.h +IncludePaths include + +ifconfig !CONFIGURED { + # Not configured, so don't process subdirs + AutoSubDirs off + # And don't process this file any further + ifconfig false +} +} + + if {![file exists build.spec]} { + puts "Note: I don't see build.spec. Try running: tmake --genie" + } +} ADDED autosetup/tmake.tcl Index: autosetup/tmake.tcl ================================================================== --- /dev/null +++ autosetup/tmake.tcl @@ -0,0 +1,52 @@ +# Copyright (c) 2011 WorkWare Systems http://www.workware.net.au/ +# All rights reserved + +# @synopsis: +# +# The 'tmake' module makes it easy to support the tmake build system. +# +# The following variables are set: +# +## CONFIGURED - to indicate that the project is configured + +use system + +module-options {} + +define CONFIGURED + +# @make-tmake-settings outfile patterns ... +# +# Examines all defined variables which match the given patterns (defaults to '*') +# and writes a tmake-compatible .conf file defining those variables. +# For example, if 'ABC' is '"3 monkeys"' and 'ABC' matches a pattern, then the file will include: +# +## define ABC {3 monkeys} +# +# If the file would be unchanged, it is not written. +# +# Typical usage is: +# +## make-tmake-settings [get-env BUILDDIR objdir]/settings.conf {[A-Z]*} +proc make-tmake-settings {file args} { + file mkdir [file dirname $file] + set lines {} + + if {[llength $args] == 0} { + set args * + } + + foreach n [lsort [dict keys [all-defines]]] { + foreach p $args { + if {[string match $p $n]} { + set value [get-define $n] + lappend lines "define $n [list $value]" + break + } + } + } + set buf [join $lines \n] + write-if-changed $file $buf { + msg-result "Created $file" + } +} DELETED ci_cvs.txt Index: ci_cvs.txt ================================================================== --- ci_cvs.txt +++ /dev/null @@ -1,192 +0,0 @@ -=============================================================================== - -First experimental codes ... - -tools/import-cvs.tcl -tools/lib/rcsparser.tcl - -No actual import, right now only working on getting csets right. The -code uses CVSROOT/history as foundation, and augments that with data -from the individual RCS files (commit messages). - -Statistics of a run ... - 3516 csets. - - 1545 breaks on user change - 558 breaks on file duplicate - 13 breaks on branch/trunk change - 1402 breaks on commit message change - -Time statistics ... - 3297 were processed in <= 1 seconds (93.77%) - 217 were processed in between 2 seconds and 14 minutes. - 1 was processed in ~41 minutes - 1 was processed in ~22 hours - -Time fuzz - Differences between csets range from 0 seconds to 66 -days. Needs stats analysis to see if there is an obvious break. Even -so the times within csets and between csets overlap a great deal, -making time a bad criterium for cset separation, IMHO. - -Leaving that topic, back to the current cset separator ... - -It has a problem: - The history file is not starting at the root! - -Examples: - The first three changesets are - - =============================/user - M {Wed Nov 22 09:28:49 AM PST 2000} ericm 1.4 tcllib/modules/ftpd/ChangeLog - M {Wed Nov 22 09:28:49 AM PST 2000} ericm 1.7 tcllib/modules/ftpd/ftpd.tcl - files: 2 - delta: 0 - range: 0 seconds - =============================/cmsg - M {Wed Nov 29 02:14:33 PM PST 2000} ericm 1.3 tcllib/aclocal.m4 - files: 1 - delta: - range: 0 seconds - =============================/cmsg - M {Sun Feb 04 12:28:35 AM PST 2001} ericm 1.9 tcllib/modules/mime/ChangeLog - M {Sun Feb 04 12:28:35 AM PST 2001} ericm 1.12 tcllib/modules/mime/mime.tcl - files: 2 - delta: 0 - range: 0 seconds - -All csets modify files which already have several revisions. We have -no csets from before that in the history, but these csets are in the -RCS files. - -I wonder, is SF maybe removing old entries from the history when it -grows too large ? - -This also affects incremental import ... I cannot assume that the -history always grows. It may shrink ... I cannot keep an offset, will -have to record the time of the last entry, or even the full entry -processed last, to allow me to skip ahead to anything not known yet. - -I might have to try to implement the algorithm outlined below, -matching the revision trees of the individual RCS files to each other -to form the global tree of revisions. Maybe we can use the history to -help in the matchup, for the parts where we do have it. - -Wait. This might be easier ... Take the delta information from the RCS -files and generate a fake history ... Actually, this might even allow -us to create a total history ... No, not quite, the merge entries the -actual history may contain will be missing. These we can mix in from -the actual history, as much as we have. - -Still, lets try that, a fake history, and then run this script on it -to see if/where are differences. - -=============================================================================== - - -Notes about CVS import, regarding CVS. - -- Problem: CVS does not really track changesets, but only individual - revisions of files. To recover changesets it is necessary to look at - author, branch, timestamp information, and the commit messages. Even - so this is only heuristic, not foolproof. - - Existing tool: cvsps. - - Processes the output of 'cvs log' to recover changesets. Problem: - Sees only a linear list of revisions, does not see branchpoints, - etc. Cannot use the tree structure to help in making the decisions. - -- Problem: CVS does not track merge-points at all. Recovery through - heuristics is brittle at best, looking for keywords in commit - messages which might indicate that a branch was merged with some - other. - - -Ideas regarding an algorithm to recover changesets. - -Key feature: Uses the per-file revision trees to help in uncovering -the underlying changesets and global revision tree G. - -The per-file revision tree for a file X is in essence the global -revision tree with all nodes not pertaining to X removed from it. In -the reverse this allows us to built up the global revision tree from -the per-file trees by matching nodes to each other and extending. - -Start with the per file revision tree of a single file as initial -approximation of the global tree. All nodes of this tree refer to the -revision of the file belonging to it, and through that the file -itself. At each step the global tree contains the nodes for a finite -set of files, and all nodes in the tree refer to revisions of all -files in the set, making the mapping total. - -To add a file X to the tree take the per-file revision tree R and -performs the following actions: - -- For each node N in R use the tuple - to identify a set of nodes in G which may match N. Use the timestamp - to locate the node nearest in time. - -- This process will leave nodes in N unmapped. If there are unmapped - nodes which have no neighbouring mapped nodes we have to - abort. - - Otherwise take the nodes which have mapped neighbours. Trace the - edges and see which of these nodes are connected in the local - tree. Then look at the identified neighbours and trace their - connections. - - If two global nodes have a direct connection, but a multi-edge - connection in the local tree insert global nodes mapping to the - local nodes and map them together. This expands the global tree to - hold the revisions added by the new file. - - Otherwise, both sides have multi-edge connections then abort. This - looks like a merge of two different branches, but there are no such - in CVS ... Wait ... sort the nodes over time and fit the new nodes - in between the other nodes, per the timestamps. We have overlapping - / alternating changes to one file and others. - - A last possibility is that a node is only connected to a mapped - parent. This may be a new branch, or again an alternating change on - the given line. Symbols on the revisions will help to map this. - -- We now have an extended global tree which incorporates the revisions - of the new file. However new nodes will refer only to the new file, - and old nodes may not refer to the new file. This has to be fixed, - as all nodes have to refer to all files. - - Run over the tree and look at each parent/child pair. If a file is - not referenced in the child, but the parent, then copy a reference - to the file revision on the parent forward to the child. This - signals that the file did not change in the given revision. - -- After all files have been integrated in this manner we have global - revision tree capturing all changesets, including the unchanged - files per changeset. - - -This algorithm has to be refined to also take Attic/ files into -account. - -------------------------------------------------------------------------- - -Two archive files mapping to the same user file. How are they -interleaved ? - -(a) sqlite/src/os_unix.h,v -(b) sqlite/src/Attic/os_unix.h,v - -Problem: Max version of (a) is 1.9 - Max version of (b) is 1.11 - cvs co 1.10 -> no longer in the repository. - -This seems to indicate that the non-Attic file is relevant. - --------------------------------------------------------------------------- - -tcllib - more problems - tklib/pie.tcl,v - - -invalid change text in -/home/aku/Projects/Tcl/Fossil/Devel/Examples/cvs-tcllib/tklib/modules/tkpiechart/pie.tcl,v - -Possibly braces ? DELETED ci_fossil.txt Index: ci_fossil.txt ================================================================== --- ci_fossil.txt +++ /dev/null @@ -1,127 +0,0 @@ - -To perform CVS imports for fossil we need at least the ability to -parse CVS files, i.e. RCS files, with slight differences. - -For the general architecture of the import facility we have two major -paths to choose between. - -One is to use an external tool which processes a cvs repository and -drives fossil through its CLI to insert the found changesets. - -The other is to integrate the whole facility into the fossil binary -itself. - -I dislike the second choice. It may be faster, as the implementation -can use all internal functionality of fossil to perform the import, -however it will also bloat the binary with functionality not needed -most of the time. Which becomes especially obvious if more importers -are to be written, like for monotone, bazaar, mercurial, bitkeeper, -git, SVN, Arc, etc. Keeping all this out of the core fossil binary is -IMHO more beneficial in the long term, also from a maintenance point -of view. The tools can evolve separately. Especially important for CVS -as it will have to deal with lots of broken repositories, all -different. - -However, nothing speaks against looking for common parts in all -possible import tools, and having these in the fossil core, as a -general backend all importer may use. Something like that has already -been proposed: The deconstruct|reconstruct methods. For us, actually -only reconstruct is important. Taking an unordered collection of files -(data, and manifests) it generates a proper fossil repository. With -that method implemented all import tools only have to generate the -necessary collection and then leave the main work of filling the -database to fossil itself. - -The disadvantage of this method is however that it will gobble up a -lot of temporary space in the filesystem to hold all unique revisions -of all files in their expanded form. - -It might be worthwhile to consider an extension of 'reconstruct' which -is able to incrementally add a set of files to an existing fossil -repository already containing revisions. In that case the import tool -can be changed to incrementally generate the collection for a -particular revision, import it, and iterate over all revisions in the -origin repository. This is of course also dependent on the origin -repository itself, how well it supports such incremental export. - -This also leads to a possible method for performing the import using -only existing functionality ('reconstruct' has not been implemented -yet). Instead generating an unordered collection for each revision -generate a properly setup workspace, simply commit it. This will -require use of rm, add and update methods as well, to remove old and -enter new files, and point the fossil repository to the correct parent -revision from the new revision is derived. - -The relative efficiency (in time) of these incremental methods versus -importing a complete collection of files encoding the entire origin -repository however is not clear. - ----------------------------------- - -reconstruct - -The core logic for handling content is in the file "content.c", in -particular the functions 'content_put' and 'content_deltify'. One of -the main users of these functions is in the file "checkin.c", see the -function 'commit_cmd'. - -The logic is clear. The new modified files are simply stored without -delta-compression, using 'content_put'. And should fosssil have an id -for the _previous_ revision of the committed file it uses -'content_deltify' to convert the already stored data for that revision -into a delta with the just stored new revision as origin. - -In other words, fossil produces reverse deltas, with leaf revisions -stored just zip-compressed (plain) and older revisions using both zip- -and delta-compression. - -Of note is that the underlying logic in 'content_deltify' gives up on -delta compression if the involved files are either not large enough, -or if the achieved compression factor was not high enough. In that -case the old revision of the file is left plain. - -The scheme can thus be called a 'truncated reverse delta'. - -The manifest is created and committed after the modified files. It -uses the same logic as for the regular files. The new leaf is stored -plain, and storage of the parent manifest is modified to be a delta -with the current as origin. - -Further note that for a checkin of a merge result oonly the primary -parent is modified in that way. The secondary parent, the one merged -into the current revision is not touched. I.e. from the storage layer -point of view this revision is still a leaf and the data is kept -stored plain, not delta-compressed. - - - -Now the "reconstruct" can be done like so: - -- Scan the files in the indicated directory, and look for a manifest. - -- When the manifest has been found parse its contents and follow the - chain of parent links to locate the root manifest (no parent). - -- Import the files referenced by the root manifest, then the manifest - itself. This can be done using a modified form of the 'commit_cmd' - which does not have to construct a manifest on its own from vfile, - vmerge, etc. - -- After that recursively apply the import of the previous step to the - children of the root, and so on. - -For an incremental "reconstruct" the collection of files would not be -a single tree with a root, but a forest, and the roots to look for are -not manifests without parent, but with a parent which is already -present in the repository. After one such root has been found and -processed the unprocessed files have to be searched further for more -roots, and only if no such are found anymore will the remaining files -be considered as superfluous. - -We can use the functions in "manifest.c" for the parsing and following -the parental chain. - -Hm. But we have no direct child information. So the above algorithm -has to be modified, we have to scan all manifests before we start -importing, and we have to create a reverse index, from manifest to -children so that we can perform the import from root to leaves. ADDED compat/tcl-8.6/generic/tcl.h Index: compat/tcl-8.6/generic/tcl.h ================================================================== --- /dev/null +++ compat/tcl-8.6/generic/tcl.h @@ -0,0 +1,2653 @@ +/* + * tcl.h -- + * + * This header file describes the externally-visible facilities of the + * Tcl interpreter. + * + * Copyright (c) 1987-1994 The Regents of the University of California. + * Copyright (c) 1993-1996 Lucent Technologies. + * Copyright (c) 1994-1998 Sun Microsystems, Inc. + * Copyright (c) 1998-2000 by Scriptics Corporation. + * Copyright (c) 2002 by Kevin B. Kenny. All rights reserved. + * + * See the file "license.terms" for information on usage and redistribution of + * this file, and for a DISCLAIMER OF ALL WARRANTIES. + */ + +#ifndef _TCL +#define _TCL + +/* + * For C++ compilers, use extern "C" + */ + +#ifdef __cplusplus +extern "C" { +#endif + +/* + * The following defines are used to indicate the various release levels. + */ + +#define TCL_ALPHA_RELEASE 0 +#define TCL_BETA_RELEASE 1 +#define TCL_FINAL_RELEASE 2 + +/* + * When version numbers change here, must also go into the following files and + * update the version numbers: + * + * library/init.tcl (1 LOC patch) + * unix/configure.in (2 LOC Major, 2 LOC minor, 1 LOC patch) + * win/configure.in (as above) + * win/tcl.m4 (not patchlevel) + * win/makefile.bc (not patchlevel) 2 LOC + * README (sections 0 and 2, with and without separator) + * macosx/Tcl.pbproj/project.pbxproj (not patchlevel) 1 LOC + * macosx/Tcl.pbproj/default.pbxuser (not patchlevel) 1 LOC + * macosx/Tcl.xcode/project.pbxproj (not patchlevel) 2 LOC + * macosx/Tcl.xcode/default.pbxuser (not patchlevel) 1 LOC + * macosx/Tcl-Common.xcconfig (not patchlevel) 1 LOC + * win/README (not patchlevel) (sections 0 and 2) + * unix/tcl.spec (1 LOC patch) + * tools/tcl.hpj.in (not patchlevel, for windows installer) + */ + +#define TCL_MAJOR_VERSION 8 +#define TCL_MINOR_VERSION 6 +#define TCL_RELEASE_LEVEL TCL_FINAL_RELEASE +#define TCL_RELEASE_SERIAL 0 + +#define TCL_VERSION "8.6" +#define TCL_PATCH_LEVEL "8.6.0" + +/* + *---------------------------------------------------------------------------- + * The following definitions set up the proper options for Windows compilers. + * We use this method because there is no autoconf equivalent. + */ + +#ifndef __WIN32__ +# if defined(_WIN32) || defined(WIN32) || defined(__MINGW32__) || defined(__BORLANDC__) || (defined(__WATCOMC__) && defined(__WINDOWS_386__)) +# define __WIN32__ +# ifndef WIN32 +# define WIN32 +# endif +# ifndef _WIN32 +# define _WIN32 +# endif +# endif +#endif + +/* + * STRICT: See MSDN Article Q83456 + */ + +#ifdef __WIN32__ +# ifndef STRICT +# define STRICT +# endif +#endif /* __WIN32__ */ + +/* + * Utility macros: STRINGIFY takes an argument and wraps it in "" (double + * quotation marks), JOIN joins two arguments. + */ + +#ifndef STRINGIFY +# define STRINGIFY(x) STRINGIFY1(x) +# define STRINGIFY1(x) #x +#endif +#ifndef JOIN +# define JOIN(a,b) JOIN1(a,b) +# define JOIN1(a,b) a##b +#endif + +/* + * A special definition used to allow this header file to be included from + * windows resource files so that they can obtain version information. + * RC_INVOKED is defined by default by the windows RC tool. + * + * Resource compilers don't like all the C stuff, like typedefs and function + * declarations, that occur below, so block them out. + */ + +#ifndef RC_INVOKED + +/* + * Special macro to define mutexes, that doesn't do anything if we are not + * using threads. + */ + +#ifdef TCL_THREADS +#define TCL_DECLARE_MUTEX(name) static Tcl_Mutex name; +#else +#define TCL_DECLARE_MUTEX(name) +#endif + +/* + * Tcl's public routine Tcl_FSSeek() uses the values SEEK_SET, SEEK_CUR, and + * SEEK_END, all #define'd by stdio.h . + * + * Also, many extensions need stdio.h, and they've grown accustomed to tcl.h + * providing it for them rather than #include-ing it themselves as they + * should, so also for their sake, we keep the #include to be consistent with + * prior Tcl releases. + */ + +#include + +/* + *---------------------------------------------------------------------------- + * Support for functions with a variable number of arguments. + * + * The following TCL_VARARGS* macros are to support old extensions + * written for older versions of Tcl where the macros permitted + * support for the varargs.h system as well as stdarg.h . + * + * New code should just directly be written to use stdarg.h conventions. + */ + +#include +#ifndef TCL_NO_DEPRECATED +# define TCL_VARARGS(type, name) (type name, ...) +# define TCL_VARARGS_DEF(type, name) (type name, ...) +# define TCL_VARARGS_START(type, name, list) (va_start(list, name), name) +#endif +#if defined(__GNUC__) && (__GNUC__ > 2) +# define TCL_FORMAT_PRINTF(a,b) __attribute__ ((__format__ (__printf__, a, b))) +#else +# define TCL_FORMAT_PRINTF(a,b) +#endif + +/* + * Allow a part of Tcl's API to be explicitly marked as deprecated. + * + * Used to make TIP 330/336 generate moans even if people use the + * compatibility macros. Change your code, guys! We won't support you forever. + */ + +#if defined(__GNUC__) && ((__GNUC__ >= 4) || ((__GNUC__ == 3) && (__GNUC_MINOR__ >= 1))) +# if (__GNUC__ > 4) || ((__GNUC__ == 4) && (__GNUC__MINOR__ >= 5)) +# define TCL_DEPRECATED_API(msg) __attribute__ ((__deprecated__ (msg))) +# else +# define TCL_DEPRECATED_API(msg) __attribute__ ((__deprecated__)) +# endif +#else +# define TCL_DEPRECATED_API(msg) /* nothing portable */ +#endif + +/* + *---------------------------------------------------------------------------- + * Macros used to declare a function to be exported by a DLL. Used by Windows, + * maps to no-op declarations on non-Windows systems. The default build on + * windows is for a DLL, which causes the DLLIMPORT and DLLEXPORT macros to be + * nonempty. To build a static library, the macro STATIC_BUILD should be + * defined. + * + * Note: when building static but linking dynamically to MSVCRT we must still + * correctly decorate the C library imported function. Use CRTIMPORT + * for this purpose. _DLL is defined by the compiler when linking to + * MSVCRT. + */ + +#if (defined(__WIN32__) && (defined(_MSC_VER) || (__BORLANDC__ >= 0x0550) || defined(__LCC__) || defined(__WATCOMC__) || (defined(__GNUC__) && defined(__declspec)))) +# define HAVE_DECLSPEC 1 +# ifdef STATIC_BUILD +# define DLLIMPORT +# define DLLEXPORT +# ifdef _DLL +# define CRTIMPORT __declspec(dllimport) +# else +# define CRTIMPORT +# endif +# else +# define DLLIMPORT __declspec(dllimport) +# define DLLEXPORT __declspec(dllexport) +# define CRTIMPORT __declspec(dllimport) +# endif +#else +# define DLLIMPORT +# if defined(__GNUC__) && __GNUC__ > 3 +# define DLLEXPORT __attribute__ ((visibility("default"))) +# else +# define DLLEXPORT +# endif +# define CRTIMPORT +#endif + +/* + * These macros are used to control whether functions are being declared for + * import or export. If a function is being declared while it is being built + * to be included in a shared library, then it should have the DLLEXPORT + * storage class. If is being declared for use by a module that is going to + * link against the shared library, then it should have the DLLIMPORT storage + * class. If the symbol is beind declared for a static build or for use from a + * stub library, then the storage class should be empty. + * + * The convention is that a macro called BUILD_xxxx, where xxxx is the name of + * a library we are building, is set on the compile line for sources that are + * to be placed in the library. When this macro is set, the storage class will + * be set to DLLEXPORT. At the end of the header file, the storage class will + * be reset to DLLIMPORT. + */ + +#undef TCL_STORAGE_CLASS +#ifdef BUILD_tcl +# define TCL_STORAGE_CLASS DLLEXPORT +#else +# ifdef USE_TCL_STUBS +# define TCL_STORAGE_CLASS +# else +# define TCL_STORAGE_CLASS DLLIMPORT +# endif +#endif + +/* + * The following _ANSI_ARGS_ macro is to support old extensions + * written for older versions of Tcl where it permitted support + * for compilers written in the pre-prototype era of C. + * + * New code should use prototypes. + */ + +#ifndef TCL_NO_DEPRECATED +# undef _ANSI_ARGS_ +# define _ANSI_ARGS_(x) x +#endif + +/* + * Definitions that allow this header file to be used either with or without + * ANSI C features. + */ + +#ifndef INLINE +# define INLINE +#endif + +#ifdef NO_CONST +# ifndef const +# define const +# endif +#endif +#ifndef CONST +# define CONST const +#endif + +#ifdef USE_NON_CONST +# ifdef USE_COMPAT_CONST +# error define at most one of USE_NON_CONST and USE_COMPAT_CONST +# endif +# define CONST84 +# define CONST84_RETURN +#else +# ifdef USE_COMPAT_CONST +# define CONST84 +# define CONST84_RETURN const +# else +# define CONST84 const +# define CONST84_RETURN const +# endif +#endif + +#ifndef CONST86 +# define CONST86 CONST84 +#endif + +/* + * Make sure EXTERN isn't defined elsewhere. + */ + +#ifdef EXTERN +# undef EXTERN +#endif /* EXTERN */ + +#ifdef __cplusplus +# define EXTERN extern "C" TCL_STORAGE_CLASS +#else +# define EXTERN extern TCL_STORAGE_CLASS +#endif + +/* + *---------------------------------------------------------------------------- + * The following code is copied from winnt.h. If we don't replicate it here, + * then can't be included after tcl.h, since tcl.h also defines + * VOID. This block is skipped under Cygwin and Mingw. + */ + +#if defined(__WIN32__) && !defined(HAVE_WINNT_IGNORE_VOID) +#ifndef VOID +#define VOID void +typedef char CHAR; +typedef short SHORT; +typedef long LONG; +#endif +#endif /* __WIN32__ && !HAVE_WINNT_IGNORE_VOID */ + +/* + * Macro to use instead of "void" for arguments that must have type "void *" + * in ANSI C; maps them to type "char *" in non-ANSI systems. + */ + +#ifndef NO_VOID +# define VOID void +#else +# define VOID char +#endif + +/* + * Miscellaneous declarations. + */ + +#ifndef _CLIENTDATA +# ifndef NO_VOID + typedef void *ClientData; +# else + typedef int *ClientData; +# endif +# define _CLIENTDATA +#endif + +/* + * Darwin specific configure overrides (to support fat compiles, where + * configure runs only once for multiple architectures): + */ + +#ifdef __APPLE__ +# ifdef __LP64__ +# undef TCL_WIDE_INT_TYPE +# define TCL_WIDE_INT_IS_LONG 1 +# define TCL_CFG_DO64BIT 1 +# else /* !__LP64__ */ +# define TCL_WIDE_INT_TYPE long long +# undef TCL_WIDE_INT_IS_LONG +# undef TCL_CFG_DO64BIT +# endif /* __LP64__ */ +# undef HAVE_STRUCT_STAT64 +#endif /* __APPLE__ */ + +/* + * Define Tcl_WideInt to be a type that is (at least) 64-bits wide, and define + * Tcl_WideUInt to be the unsigned variant of that type (assuming that where + * we have one, we can have the other.) + * + * Also defines the following macros: + * TCL_WIDE_INT_IS_LONG - if wide ints are really longs (i.e. we're on a real + * 64-bit system.) + * Tcl_WideAsLong - forgetful converter from wideInt to long. + * Tcl_LongAsWide - sign-extending converter from long to wideInt. + * Tcl_WideAsDouble - converter from wideInt to double. + * Tcl_DoubleAsWide - converter from double to wideInt. + * + * The following invariant should hold for any long value 'longVal': + * longVal == Tcl_WideAsLong(Tcl_LongAsWide(longVal)) + * + * Note on converting between Tcl_WideInt and strings. This implementation (in + * tclObj.c) depends on the function + * sprintf(...,"%" TCL_LL_MODIFIER "d",...). + */ + +#if !defined(TCL_WIDE_INT_TYPE)&&!defined(TCL_WIDE_INT_IS_LONG) +# if defined(__WIN32__) +# define TCL_WIDE_INT_TYPE __int64 +# ifdef __BORLANDC__ +# define TCL_LL_MODIFIER "L" +# else /* __BORLANDC__ */ +# define TCL_LL_MODIFIER "I64" +# endif /* __BORLANDC__ */ +# elif defined(__GNUC__) +# define TCL_WIDE_INT_TYPE long long +# define TCL_LL_MODIFIER "ll" +# else /* ! __WIN32__ && ! __GNUC__ */ +/* + * Don't know what platform it is and configure hasn't discovered what is + * going on for us. Try to guess... + */ +# ifdef NO_LIMITS_H +# error please define either TCL_WIDE_INT_TYPE or TCL_WIDE_INT_IS_LONG +# else /* !NO_LIMITS_H */ +# include +# if (INT_MAX < LONG_MAX) +# define TCL_WIDE_INT_IS_LONG 1 +# else +# define TCL_WIDE_INT_TYPE long long +# endif +# endif /* NO_LIMITS_H */ +# endif /* __WIN32__ */ +#endif /* !TCL_WIDE_INT_TYPE & !TCL_WIDE_INT_IS_LONG */ +#ifdef TCL_WIDE_INT_IS_LONG +# undef TCL_WIDE_INT_TYPE +# define TCL_WIDE_INT_TYPE long +#endif /* TCL_WIDE_INT_IS_LONG */ + +typedef TCL_WIDE_INT_TYPE Tcl_WideInt; +typedef unsigned TCL_WIDE_INT_TYPE Tcl_WideUInt; + +#ifdef TCL_WIDE_INT_IS_LONG +# define Tcl_WideAsLong(val) ((long)(val)) +# define Tcl_LongAsWide(val) ((long)(val)) +# define Tcl_WideAsDouble(val) ((double)((long)(val))) +# define Tcl_DoubleAsWide(val) ((long)((double)(val))) +# ifndef TCL_LL_MODIFIER +# define TCL_LL_MODIFIER "l" +# endif /* !TCL_LL_MODIFIER */ +#else /* TCL_WIDE_INT_IS_LONG */ +/* + * The next short section of defines are only done when not running on Windows + * or some other strange platform. + */ +# ifndef TCL_LL_MODIFIER +# define TCL_LL_MODIFIER "ll" +# endif /* !TCL_LL_MODIFIER */ +# define Tcl_WideAsLong(val) ((long)((Tcl_WideInt)(val))) +# define Tcl_LongAsWide(val) ((Tcl_WideInt)((long)(val))) +# define Tcl_WideAsDouble(val) ((double)((Tcl_WideInt)(val))) +# define Tcl_DoubleAsWide(val) ((Tcl_WideInt)((double)(val))) +#endif /* TCL_WIDE_INT_IS_LONG */ + +#if defined(__WIN32__) +# ifdef __BORLANDC__ + typedef struct stati64 Tcl_StatBuf; +# elif defined(_WIN64) + typedef struct __stat64 Tcl_StatBuf; +# elif (defined(_MSC_VER) && (_MSC_VER < 1400)) || defined(_USE_32BIT_TIME_T) + typedef struct _stati64 Tcl_StatBuf; +# else + typedef struct _stat32i64 Tcl_StatBuf; +# endif /* _MSC_VER < 1400 */ +#elif defined(__CYGWIN__) + typedef struct _stat32i64 { + dev_t st_dev; + unsigned short st_ino; + unsigned short st_mode; + short st_nlink; + short st_uid; + short st_gid; + /* Here is a 2-byte gap */ + dev_t st_rdev; + /* Here is a 4-byte gap */ + long long st_size; + struct {long tv_sec;} st_atim; + struct {long tv_sec;} st_mtim; + struct {long tv_sec;} st_ctim; + /* Here is a 4-byte gap */ + } Tcl_StatBuf; +#elif defined(HAVE_STRUCT_STAT64) + typedef struct stat64 Tcl_StatBuf; +#else + typedef struct stat Tcl_StatBuf; +#endif + +/* + *---------------------------------------------------------------------------- + * Data structures defined opaquely in this module. The definitions below just + * provide dummy types. A few fields are made visible in Tcl_Interp + * structures, namely those used for returning a string result from commands. + * Direct access to the result field is discouraged in Tcl 8.0. The + * interpreter result is either an object or a string, and the two values are + * kept consistent unless some C code sets interp->result directly. + * Programmers should use either the function Tcl_GetObjResult() or + * Tcl_GetStringResult() to read the interpreter's result. See the SetResult + * man page for details. + * + * Note: any change to the Tcl_Interp definition below must be mirrored in the + * "real" definition in tclInt.h. + * + * Note: Tcl_ObjCmdProc functions do not directly set result and freeProc. + * Instead, they set a Tcl_Obj member in the "real" structure that can be + * accessed with Tcl_GetObjResult() and Tcl_SetObjResult(). + */ + +typedef struct Tcl_Interp +#ifndef TCL_NO_DEPRECATED +{ + /* TIP #330: Strongly discourage extensions from using the string + * result. */ +#ifdef USE_INTERP_RESULT + char *result TCL_DEPRECATED_API("use Tcl_GetResult/Tcl_SetResult"); + /* If the last command returned a string + * result, this points to it. */ + void (*freeProc) (char *blockPtr) + TCL_DEPRECATED_API("use Tcl_GetResult/Tcl_SetResult"); + /* Zero means the string result is statically + * allocated. TCL_DYNAMIC means it was + * allocated with ckalloc and should be freed + * with ckfree. Other values give the address + * of function to invoke to free the result. + * Tcl_Eval must free it before executing next + * command. */ +#else + char *resultDontUse; /* Don't use in extensions! */ + void (*freeProcDontUse) (char *); /* Don't use in extensions! */ +#endif +#ifdef USE_INTERP_ERRORLINE + int errorLine TCL_DEPRECATED_API("use Tcl_GetErrorLine/Tcl_SetErrorLine"); + /* When TCL_ERROR is returned, this gives the + * line number within the command where the + * error occurred (1 if first line). */ +#else + int errorLineDontUse; /* Don't use in extensions! */ +#endif +} +#endif /* TCL_NO_DEPRECATED */ +Tcl_Interp; + +typedef struct Tcl_AsyncHandler_ *Tcl_AsyncHandler; +typedef struct Tcl_Channel_ *Tcl_Channel; +typedef struct Tcl_ChannelTypeVersion_ *Tcl_ChannelTypeVersion; +typedef struct Tcl_Command_ *Tcl_Command; +typedef struct Tcl_Condition_ *Tcl_Condition; +typedef struct Tcl_Dict_ *Tcl_Dict; +typedef struct Tcl_EncodingState_ *Tcl_EncodingState; +typedef struct Tcl_Encoding_ *Tcl_Encoding; +typedef struct Tcl_Event Tcl_Event; +typedef struct Tcl_InterpState_ *Tcl_InterpState; +typedef struct Tcl_LoadHandle_ *Tcl_LoadHandle; +typedef struct Tcl_Mutex_ *Tcl_Mutex; +typedef struct Tcl_Pid_ *Tcl_Pid; +typedef struct Tcl_RegExp_ *Tcl_RegExp; +typedef struct Tcl_ThreadDataKey_ *Tcl_ThreadDataKey; +typedef struct Tcl_ThreadId_ *Tcl_ThreadId; +typedef struct Tcl_TimerToken_ *Tcl_TimerToken; +typedef struct Tcl_Trace_ *Tcl_Trace; +typedef struct Tcl_Var_ *Tcl_Var; +typedef struct Tcl_ZLibStream_ *Tcl_ZlibStream; + +/* + *---------------------------------------------------------------------------- + * Definition of the interface to functions implementing threads. A function + * following this definition is given to each call of 'Tcl_CreateThread' and + * will be called as the main fuction of the new thread created by that call. + */ + +#if defined __WIN32__ +typedef unsigned (__stdcall Tcl_ThreadCreateProc) (ClientData clientData); +#else +typedef void (Tcl_ThreadCreateProc) (ClientData clientData); +#endif + +/* + * Threading function return types used for abstracting away platform + * differences when writing a Tcl_ThreadCreateProc. See the NewThread function + * in generic/tclThreadTest.c for it's usage. + */ + +#if defined __WIN32__ +# define Tcl_ThreadCreateType unsigned __stdcall +# define TCL_THREAD_CREATE_RETURN return 0 +#else +# define Tcl_ThreadCreateType void +# define TCL_THREAD_CREATE_RETURN +#endif + +/* + * Definition of values for default stacksize and the possible flags to be + * given to Tcl_CreateThread. + */ + +#define TCL_THREAD_STACK_DEFAULT (0) /* Use default size for stack. */ +#define TCL_THREAD_NOFLAGS (0000) /* Standard flags, default + * behaviour. */ +#define TCL_THREAD_JOINABLE (0001) /* Mark the thread as joinable. */ + +/* + * Flag values passed to Tcl_StringCaseMatch. + */ + +#define TCL_MATCH_NOCASE (1<<0) + +/* + * Flag values passed to Tcl_GetRegExpFromObj. + */ + +#define TCL_REG_BASIC 000000 /* BREs (convenience). */ +#define TCL_REG_EXTENDED 000001 /* EREs. */ +#define TCL_REG_ADVF 000002 /* Advanced features in EREs. */ +#define TCL_REG_ADVANCED 000003 /* AREs (which are also EREs). */ +#define TCL_REG_QUOTE 000004 /* No special characters, none. */ +#define TCL_REG_NOCASE 000010 /* Ignore case. */ +#define TCL_REG_NOSUB 000020 /* Don't care about subexpressions. */ +#define TCL_REG_EXPANDED 000040 /* Expanded format, white space & + * comments. */ +#define TCL_REG_NLSTOP 000100 /* \n doesn't match . or [^ ] */ +#define TCL_REG_NLANCH 000200 /* ^ matches after \n, $ before. */ +#define TCL_REG_NEWLINE 000300 /* Newlines are line terminators. */ +#define TCL_REG_CANMATCH 001000 /* Report details on partial/limited + * matches. */ + +/* + * Flags values passed to Tcl_RegExpExecObj. + */ + +#define TCL_REG_NOTBOL 0001 /* Beginning of string does not match ^. */ +#define TCL_REG_NOTEOL 0002 /* End of string does not match $. */ + +/* + * Structures filled in by Tcl_RegExpInfo. Note that all offset values are + * relative to the start of the match string, not the beginning of the entire + * string. + */ + +typedef struct Tcl_RegExpIndices { + long start; /* Character offset of first character in + * match. */ + long end; /* Character offset of first character after + * the match. */ +} Tcl_RegExpIndices; + +typedef struct Tcl_RegExpInfo { + int nsubs; /* Number of subexpressions in the compiled + * expression. */ + Tcl_RegExpIndices *matches; /* Array of nsubs match offset pairs. */ + long extendStart; /* The offset at which a subsequent match + * might begin. */ + long reserved; /* Reserved for later use. */ +} Tcl_RegExpInfo; + +/* + * Picky compilers complain if this typdef doesn't appear before the struct's + * reference in tclDecls.h. + */ + +typedef Tcl_StatBuf *Tcl_Stat_; +typedef struct stat *Tcl_OldStat_; + +/* + *---------------------------------------------------------------------------- + * When a TCL command returns, the interpreter contains a result from the + * command. Programmers are strongly encouraged to use one of the functions + * Tcl_GetObjResult() or Tcl_GetStringResult() to read the interpreter's + * result. See the SetResult man page for details. Besides this result, the + * command function returns an integer code, which is one of the following: + * + * TCL_OK Command completed normally; the interpreter's result + * contains the command's result. + * TCL_ERROR The command couldn't be completed successfully; the + * interpreter's result describes what went wrong. + * TCL_RETURN The command requests that the current function return; + * the interpreter's result contains the function's + * return value. + * TCL_BREAK The command requests that the innermost loop be + * exited; the interpreter's result is meaningless. + * TCL_CONTINUE Go on to the next iteration of the current loop; the + * interpreter's result is meaningless. + */ + +#define TCL_OK 0 +#define TCL_ERROR 1 +#define TCL_RETURN 2 +#define TCL_BREAK 3 +#define TCL_CONTINUE 4 + +#define TCL_RESULT_SIZE 200 + +/* + *---------------------------------------------------------------------------- + * Flags to control what substitutions are performed by Tcl_SubstObj(): + */ + +#define TCL_SUBST_COMMANDS 001 +#define TCL_SUBST_VARIABLES 002 +#define TCL_SUBST_BACKSLASHES 004 +#define TCL_SUBST_ALL 007 + +/* + * Argument descriptors for math function callbacks in expressions: + */ + +typedef enum { + TCL_INT, TCL_DOUBLE, TCL_EITHER, TCL_WIDE_INT +} Tcl_ValueType; + +typedef struct Tcl_Value { + Tcl_ValueType type; /* Indicates intValue or doubleValue is valid, + * or both. */ + long intValue; /* Integer value. */ + double doubleValue; /* Double-precision floating value. */ + Tcl_WideInt wideValue; /* Wide (min. 64-bit) integer value. */ +} Tcl_Value; + +/* + * Forward declaration of Tcl_Obj to prevent an error when the forward + * reference to Tcl_Obj is encountered in the function types declared below. + */ + +struct Tcl_Obj; + +/* + *---------------------------------------------------------------------------- + * Function types defined by Tcl: + */ + +typedef int (Tcl_AppInitProc) (Tcl_Interp *interp); +typedef int (Tcl_AsyncProc) (ClientData clientData, Tcl_Interp *interp, + int code); +typedef void (Tcl_ChannelProc) (ClientData clientData, int mask); +typedef void (Tcl_CloseProc) (ClientData data); +typedef void (Tcl_CmdDeleteProc) (ClientData clientData); +typedef int (Tcl_CmdProc) (ClientData clientData, Tcl_Interp *interp, + int argc, CONST84 char *argv[]); +typedef void (Tcl_CmdTraceProc) (ClientData clientData, Tcl_Interp *interp, + int level, char *command, Tcl_CmdProc *proc, + ClientData cmdClientData, int argc, CONST84 char *argv[]); +typedef int (Tcl_CmdObjTraceProc) (ClientData clientData, Tcl_Interp *interp, + int level, const char *command, Tcl_Command commandInfo, int objc, + struct Tcl_Obj *const *objv); +typedef void (Tcl_CmdObjTraceDeleteProc) (ClientData clientData); +typedef void (Tcl_DupInternalRepProc) (struct Tcl_Obj *srcPtr, + struct Tcl_Obj *dupPtr); +typedef int (Tcl_EncodingConvertProc) (ClientData clientData, const char *src, + int srcLen, int flags, Tcl_EncodingState *statePtr, char *dst, + int dstLen, int *srcReadPtr, int *dstWrotePtr, int *dstCharsPtr); +typedef void (Tcl_EncodingFreeProc) (ClientData clientData); +typedef int (Tcl_EventProc) (Tcl_Event *evPtr, int flags); +typedef void (Tcl_EventCheckProc) (ClientData clientData, int flags); +typedef int (Tcl_EventDeleteProc) (Tcl_Event *evPtr, ClientData clientData); +typedef void (Tcl_EventSetupProc) (ClientData clientData, int flags); +typedef void (Tcl_ExitProc) (ClientData clientData); +typedef void (Tcl_FileProc) (ClientData clientData, int mask); +typedef void (Tcl_FileFreeProc) (ClientData clientData); +typedef void (Tcl_FreeInternalRepProc) (struct Tcl_Obj *objPtr); +typedef void (Tcl_FreeProc) (char *blockPtr); +typedef void (Tcl_IdleProc) (ClientData clientData); +typedef void (Tcl_InterpDeleteProc) (ClientData clientData, + Tcl_Interp *interp); +typedef int (Tcl_MathProc) (ClientData clientData, Tcl_Interp *interp, + Tcl_Value *args, Tcl_Value *resultPtr); +typedef void (Tcl_NamespaceDeleteProc) (ClientData clientData); +typedef int (Tcl_ObjCmdProc) (ClientData clientData, Tcl_Interp *interp, + int objc, struct Tcl_Obj *const *objv); +typedef int (Tcl_PackageInitProc) (Tcl_Interp *interp); +typedef int (Tcl_PackageUnloadProc) (Tcl_Interp *interp, int flags); +typedef void (Tcl_PanicProc) (const char *format, ...); +typedef void (Tcl_TcpAcceptProc) (ClientData callbackData, Tcl_Channel chan, + char *address, int port); +typedef void (Tcl_TimerProc) (ClientData clientData); +typedef int (Tcl_SetFromAnyProc) (Tcl_Interp *interp, struct Tcl_Obj *objPtr); +typedef void (Tcl_UpdateStringProc) (struct Tcl_Obj *objPtr); +typedef char * (Tcl_VarTraceProc) (ClientData clientData, Tcl_Interp *interp, + CONST84 char *part1, CONST84 char *part2, int flags); +typedef void (Tcl_CommandTraceProc) (ClientData clientData, Tcl_Interp *interp, + const char *oldName, const char *newName, int flags); +typedef void (Tcl_CreateFileHandlerProc) (int fd, int mask, Tcl_FileProc *proc, + ClientData clientData); +typedef void (Tcl_DeleteFileHandlerProc) (int fd); +typedef void (Tcl_AlertNotifierProc) (ClientData clientData); +typedef void (Tcl_ServiceModeHookProc) (int mode); +typedef ClientData (Tcl_InitNotifierProc) (void); +typedef void (Tcl_FinalizeNotifierProc) (ClientData clientData); +typedef void (Tcl_MainLoopProc) (void); + +/* + *---------------------------------------------------------------------------- + * The following structure represents a type of object, which is a particular + * internal representation for an object plus a set of functions that provide + * standard operations on objects of that type. + */ + +typedef struct Tcl_ObjType { + const char *name; /* Name of the type, e.g. "int". */ + Tcl_FreeInternalRepProc *freeIntRepProc; + /* Called to free any storage for the type's + * internal rep. NULL if the internal rep does + * not need freeing. */ + Tcl_DupInternalRepProc *dupIntRepProc; + /* Called to create a new object as a copy of + * an existing object. */ + Tcl_UpdateStringProc *updateStringProc; + /* Called to update the string rep from the + * type's internal representation. */ + Tcl_SetFromAnyProc *setFromAnyProc; + /* Called to convert the object's internal rep + * to this type. Frees the internal rep of the + * old type. Returns TCL_ERROR on failure. */ +} Tcl_ObjType; + +/* + * One of the following structures exists for each object in the Tcl system. + * An object stores a value as either a string, some internal representation, + * or both. + */ + +typedef struct Tcl_Obj { + int refCount; /* When 0 the object will be freed. */ + char *bytes; /* This points to the first byte of the + * object's string representation. The array + * must be followed by a null byte (i.e., at + * offset length) but may also contain + * embedded null characters. The array's + * storage is allocated by ckalloc. NULL means + * the string rep is invalid and must be + * regenerated from the internal rep. Clients + * should use Tcl_GetStringFromObj or + * Tcl_GetString to get a pointer to the byte + * array as a readonly value. */ + int length; /* The number of bytes at *bytes, not + * including the terminating null. */ + const Tcl_ObjType *typePtr; /* Denotes the object's type. Always + * corresponds to the type of the object's + * internal rep. NULL indicates the object has + * no internal rep (has no type). */ + union { /* The internal representation: */ + long longValue; /* - an long integer value. */ + double doubleValue; /* - a double-precision floating value. */ + void *otherValuePtr; /* - another, type-specific value. */ + Tcl_WideInt wideValue; /* - a long long value. */ + struct { /* - internal rep as two pointers. */ + void *ptr1; + void *ptr2; + } twoPtrValue; + struct { /* - internal rep as a pointer and a long, + * the main use of which is a bignum's + * tightly packed fields, where the alloc, + * used and signum flags are packed into a + * single word with everything else hung + * off the pointer. */ + void *ptr; + unsigned long value; + } ptrAndLongRep; + } internalRep; +} Tcl_Obj; + +/* + * Macros to increment and decrement a Tcl_Obj's reference count, and to test + * whether an object is shared (i.e. has reference count > 1). Note: clients + * should use Tcl_DecrRefCount() when they are finished using an object, and + * should never call TclFreeObj() directly. TclFreeObj() is only defined and + * made public in tcl.h to support Tcl_DecrRefCount's macro definition. + */ + +void Tcl_IncrRefCount(Tcl_Obj *objPtr); +void Tcl_DecrRefCount(Tcl_Obj *objPtr); +int Tcl_IsShared(Tcl_Obj *objPtr); + +/* + *---------------------------------------------------------------------------- + * The following structure contains the state needed by Tcl_SaveResult. No-one + * outside of Tcl should access any of these fields. This structure is + * typically allocated on the stack. + */ + +typedef struct Tcl_SavedResult { + char *result; + Tcl_FreeProc *freeProc; + Tcl_Obj *objResultPtr; + char *appendResult; + int appendAvl; + int appendUsed; + char resultSpace[TCL_RESULT_SIZE+1]; +} Tcl_SavedResult; + +/* + *---------------------------------------------------------------------------- + * The following definitions support Tcl's namespace facility. Note: the first + * five fields must match exactly the fields in a Namespace structure (see + * tclInt.h). + */ + +typedef struct Tcl_Namespace { + char *name; /* The namespace's name within its parent + * namespace. This contains no ::'s. The name + * of the global namespace is "" although "::" + * is an synonym. */ + char *fullName; /* The namespace's fully qualified name. This + * starts with ::. */ + ClientData clientData; /* Arbitrary value associated with this + * namespace. */ + Tcl_NamespaceDeleteProc *deleteProc; + /* Function invoked when deleting the + * namespace to, e.g., free clientData. */ + struct Tcl_Namespace *parentPtr; + /* Points to the namespace that contains this + * one. NULL if this is the global + * namespace. */ +} Tcl_Namespace; + +/* + *---------------------------------------------------------------------------- + * The following structure represents a call frame, or activation record. A + * call frame defines a naming context for a procedure call: its local scope + * (for local variables) and its namespace scope (used for non-local + * variables; often the global :: namespace). A call frame can also define the + * naming context for a namespace eval or namespace inscope command: the + * namespace in which the command's code should execute. The Tcl_CallFrame + * structures exist only while procedures or namespace eval/inscope's are + * being executed, and provide a Tcl call stack. + * + * A call frame is initialized and pushed using Tcl_PushCallFrame and popped + * using Tcl_PopCallFrame. Storage for a Tcl_CallFrame must be provided by the + * Tcl_PushCallFrame caller, and callers typically allocate them on the C call + * stack for efficiency. For this reason, Tcl_CallFrame is defined as a + * structure and not as an opaque token. However, most Tcl_CallFrame fields + * are hidden since applications should not access them directly; others are + * declared as "dummyX". + * + * WARNING!! The structure definition must be kept consistent with the + * CallFrame structure in tclInt.h. If you change one, change the other. + */ + +typedef struct Tcl_CallFrame { + Tcl_Namespace *nsPtr; + int dummy1; + int dummy2; + void *dummy3; + void *dummy4; + void *dummy5; + int dummy6; + void *dummy7; + void *dummy8; + int dummy9; + void *dummy10; + void *dummy11; + void *dummy12; + void *dummy13; +} Tcl_CallFrame; + +/* + *---------------------------------------------------------------------------- + * Information about commands that is returned by Tcl_GetCommandInfo and + * passed to Tcl_SetCommandInfo. objProc is an objc/objv object-based command + * function while proc is a traditional Tcl argc/argv string-based function. + * Tcl_CreateObjCommand and Tcl_CreateCommand ensure that both objProc and + * proc are non-NULL and can be called to execute the command. However, it may + * be faster to call one instead of the other. The member isNativeObjectProc + * is set to 1 if an object-based function was registered by + * Tcl_CreateObjCommand, and to 0 if a string-based function was registered by + * Tcl_CreateCommand. The other function is typically set to a compatibility + * wrapper that does string-to-object or object-to-string argument conversions + * then calls the other function. + */ + +typedef struct Tcl_CmdInfo { + int isNativeObjectProc; /* 1 if objProc was registered by a call to + * Tcl_CreateObjCommand; 0 otherwise. + * Tcl_SetCmdInfo does not modify this + * field. */ + Tcl_ObjCmdProc *objProc; /* Command's object-based function. */ + ClientData objClientData; /* ClientData for object proc. */ + Tcl_CmdProc *proc; /* Command's string-based function. */ + ClientData clientData; /* ClientData for string proc. */ + Tcl_CmdDeleteProc *deleteProc; + /* Function to call when command is + * deleted. */ + ClientData deleteData; /* Value to pass to deleteProc (usually the + * same as clientData). */ + Tcl_Namespace *namespacePtr;/* Points to the namespace that contains this + * command. Note that Tcl_SetCmdInfo will not + * change a command's namespace; use + * TclRenameCommand or Tcl_Eval (of 'rename') + * to do that. */ +} Tcl_CmdInfo; + +/* + *---------------------------------------------------------------------------- + * The structure defined below is used to hold dynamic strings. The only + * fields that clients should use are string and length, accessible via the + * macros Tcl_DStringValue and Tcl_DStringLength. + */ + +#define TCL_DSTRING_STATIC_SIZE 200 +typedef struct Tcl_DString { + char *string; /* Points to beginning of string: either + * staticSpace below or a malloced array. */ + int length; /* Number of non-NULL characters in the + * string. */ + int spaceAvl; /* Total number of bytes available for the + * string and its terminating NULL char. */ + char staticSpace[TCL_DSTRING_STATIC_SIZE]; + /* Space to use in common case where string is + * small. */ +} Tcl_DString; + +#define Tcl_DStringLength(dsPtr) ((dsPtr)->length) +#define Tcl_DStringValue(dsPtr) ((dsPtr)->string) +#define Tcl_DStringTrunc Tcl_DStringSetLength + +/* + * Definitions for the maximum number of digits of precision that may be + * specified in the "tcl_precision" variable, and the number of bytes of + * buffer space required by Tcl_PrintDouble. + */ + +#define TCL_MAX_PREC 17 +#define TCL_DOUBLE_SPACE (TCL_MAX_PREC+10) + +/* + * Definition for a number of bytes of buffer space sufficient to hold the + * string representation of an integer in base 10 (assuming the existence of + * 64-bit integers). + */ + +#define TCL_INTEGER_SPACE 24 + +/* + * Flag values passed to Tcl_ConvertElement. + * TCL_DONT_USE_BRACES forces it not to enclose the element in braces, but to + * use backslash quoting instead. + * TCL_DONT_QUOTE_HASH disables the default quoting of the '#' character. It + * is safe to leave the hash unquoted when the element is not the first + * element of a list, and this flag can be used by the caller to indicate + * that condition. + */ + +#define TCL_DONT_USE_BRACES 1 +#define TCL_DONT_QUOTE_HASH 8 + +/* + * Flag that may be passed to Tcl_GetIndexFromObj to force it to disallow + * abbreviated strings. + */ + +#define TCL_EXACT 1 + +/* + *---------------------------------------------------------------------------- + * Flag values passed to Tcl_RecordAndEval, Tcl_EvalObj, Tcl_EvalObjv. + * WARNING: these bit choices must not conflict with the bit choices for + * evalFlag bits in tclInt.h! + * + * Meanings: + * TCL_NO_EVAL: Just record this command + * TCL_EVAL_GLOBAL: Execute script in global namespace + * TCL_EVAL_DIRECT: Do not compile this script + * TCL_EVAL_INVOKE: Magical Tcl_EvalObjv mode for aliases/ensembles + * o Run in iPtr->lookupNsPtr or global namespace + * o Cut out of error traces + * o Don't reset the flags controlling ensemble + * error message rewriting. + * TCL_CANCEL_UNWIND: Magical Tcl_CancelEval mode that causes the + * stack for the script in progress to be + * completely unwound. + * TCL_EVAL_NOERR: Do no exception reporting at all, just return + * as the caller will report. + */ + +#define TCL_NO_EVAL 0x010000 +#define TCL_EVAL_GLOBAL 0x020000 +#define TCL_EVAL_DIRECT 0x040000 +#define TCL_EVAL_INVOKE 0x080000 +#define TCL_CANCEL_UNWIND 0x100000 +#define TCL_EVAL_NOERR 0x200000 + +/* + * Special freeProc values that may be passed to Tcl_SetResult (see the man + * page for details): + */ + +#define TCL_VOLATILE ((Tcl_FreeProc *) 1) +#define TCL_STATIC ((Tcl_FreeProc *) 0) +#define TCL_DYNAMIC ((Tcl_FreeProc *) 3) + +/* + * Flag values passed to variable-related functions. + * WARNING: these bit choices must not conflict with the bit choice for + * TCL_CANCEL_UNWIND, above. + */ + +#define TCL_GLOBAL_ONLY 1 +#define TCL_NAMESPACE_ONLY 2 +#define TCL_APPEND_VALUE 4 +#define TCL_LIST_ELEMENT 8 +#define TCL_TRACE_READS 0x10 +#define TCL_TRACE_WRITES 0x20 +#define TCL_TRACE_UNSETS 0x40 +#define TCL_TRACE_DESTROYED 0x80 +#define TCL_INTERP_DESTROYED 0x100 +#define TCL_LEAVE_ERR_MSG 0x200 +#define TCL_TRACE_ARRAY 0x800 +#ifndef TCL_REMOVE_OBSOLETE_TRACES +/* Required to support old variable/vdelete/vinfo traces. */ +#define TCL_TRACE_OLD_STYLE 0x1000 +#endif +/* Indicate the semantics of the result of a trace. */ +#define TCL_TRACE_RESULT_DYNAMIC 0x8000 +#define TCL_TRACE_RESULT_OBJECT 0x10000 + +/* + * Flag values for ensemble commands. + */ + +#define TCL_ENSEMBLE_PREFIX 0x02/* Flag value to say whether to allow + * unambiguous prefixes of commands or to + * require exact matches for command names. */ + +/* + * Flag values passed to command-related functions. + */ + +#define TCL_TRACE_RENAME 0x2000 +#define TCL_TRACE_DELETE 0x4000 + +#define TCL_ALLOW_INLINE_COMPILATION 0x20000 + +/* + * The TCL_PARSE_PART1 flag is deprecated and has no effect. The part1 is now + * always parsed whenever the part2 is NULL. (This is to avoid a common error + * when converting code to use the new object based APIs and forgetting to + * give the flag) + */ + +#ifndef TCL_NO_DEPRECATED +# define TCL_PARSE_PART1 0x400 +#endif + +/* + * Types for linked variables: + */ + +#define TCL_LINK_INT 1 +#define TCL_LINK_DOUBLE 2 +#define TCL_LINK_BOOLEAN 3 +#define TCL_LINK_STRING 4 +#define TCL_LINK_WIDE_INT 5 +#define TCL_LINK_CHAR 6 +#define TCL_LINK_UCHAR 7 +#define TCL_LINK_SHORT 8 +#define TCL_LINK_USHORT 9 +#define TCL_LINK_UINT 10 +#define TCL_LINK_LONG 11 +#define TCL_LINK_ULONG 12 +#define TCL_LINK_FLOAT 13 +#define TCL_LINK_WIDE_UINT 14 +#define TCL_LINK_READ_ONLY 0x80 + +/* + *---------------------------------------------------------------------------- + * Forward declarations of Tcl_HashTable and related types. + */ + +typedef struct Tcl_HashKeyType Tcl_HashKeyType; +typedef struct Tcl_HashTable Tcl_HashTable; +typedef struct Tcl_HashEntry Tcl_HashEntry; + +typedef unsigned (Tcl_HashKeyProc) (Tcl_HashTable *tablePtr, void *keyPtr); +typedef int (Tcl_CompareHashKeysProc) (void *keyPtr, Tcl_HashEntry *hPtr); +typedef Tcl_HashEntry * (Tcl_AllocHashEntryProc) (Tcl_HashTable *tablePtr, + void *keyPtr); +typedef void (Tcl_FreeHashEntryProc) (Tcl_HashEntry *hPtr); + +/* + * This flag controls whether the hash table stores the hash of a key, or + * recalculates it. There should be no reason for turning this flag off as it + * is completely binary and source compatible unless you directly access the + * bucketPtr member of the Tcl_HashTableEntry structure. This member has been + * removed and the space used to store the hash value. + */ + +#ifndef TCL_HASH_KEY_STORE_HASH +# define TCL_HASH_KEY_STORE_HASH 1 +#endif + +/* + * Structure definition for an entry in a hash table. No-one outside Tcl + * should access any of these fields directly; use the macros defined below. + */ + +struct Tcl_HashEntry { + Tcl_HashEntry *nextPtr; /* Pointer to next entry in this hash bucket, + * or NULL for end of chain. */ + Tcl_HashTable *tablePtr; /* Pointer to table containing entry. */ +#if TCL_HASH_KEY_STORE_HASH + void *hash; /* Hash value, stored as pointer to ensure + * that the offsets of the fields in this + * structure are not changed. */ +#else + Tcl_HashEntry **bucketPtr; /* Pointer to bucket that points to first + * entry in this entry's chain: used for + * deleting the entry. */ +#endif + ClientData clientData; /* Application stores something here with + * Tcl_SetHashValue. */ + union { /* Key has one of these forms: */ + char *oneWordValue; /* One-word value for key. */ + Tcl_Obj *objPtr; /* Tcl_Obj * key value. */ + int words[1]; /* Multiple integer words for key. The actual + * size will be as large as necessary for this + * table's keys. */ + char string[1]; /* String for key. The actual size will be as + * large as needed to hold the key. */ + } key; /* MUST BE LAST FIELD IN RECORD!! */ +}; + +/* + * Flags used in Tcl_HashKeyType. + * + * TCL_HASH_KEY_RANDOMIZE_HASH - + * There are some things, pointers for example + * which don't hash well because they do not use + * the lower bits. If this flag is set then the + * hash table will attempt to rectify this by + * randomising the bits and then using the upper + * N bits as the index into the table. + * TCL_HASH_KEY_SYSTEM_HASH - If this flag is set then all memory internally + * allocated for the hash table that is not for an + * entry will use the system heap. + */ + +#define TCL_HASH_KEY_RANDOMIZE_HASH 0x1 +#define TCL_HASH_KEY_SYSTEM_HASH 0x2 + +/* + * Structure definition for the methods associated with a hash table key type. + */ + +#define TCL_HASH_KEY_TYPE_VERSION 1 +struct Tcl_HashKeyType { + int version; /* Version of the table. If this structure is + * extended in future then the version can be + * used to distinguish between different + * structures. */ + int flags; /* Flags, see above for details. */ + Tcl_HashKeyProc *hashKeyProc; + /* Calculates a hash value for the key. If + * this is NULL then the pointer itself is + * used as a hash value. */ + Tcl_CompareHashKeysProc *compareKeysProc; + /* Compares two keys and returns zero if they + * do not match, and non-zero if they do. If + * this is NULL then the pointers are + * compared. */ + Tcl_AllocHashEntryProc *allocEntryProc; + /* Called to allocate memory for a new entry, + * i.e. if the key is a string then this could + * allocate a single block which contains + * enough space for both the entry and the + * string. Only the key field of the allocated + * Tcl_HashEntry structure needs to be filled + * in. If something else needs to be done to + * the key, i.e. incrementing a reference + * count then that should be done by this + * function. If this is NULL then Tcl_Alloc is + * used to allocate enough space for a + * Tcl_HashEntry and the key pointer is + * assigned to key.oneWordValue. */ + Tcl_FreeHashEntryProc *freeEntryProc; + /* Called to free memory associated with an + * entry. If something else needs to be done + * to the key, i.e. decrementing a reference + * count then that should be done by this + * function. If this is NULL then Tcl_Free is + * used to free the Tcl_HashEntry. */ +}; + +/* + * Structure definition for a hash table. Must be in tcl.h so clients can + * allocate space for these structures, but clients should never access any + * fields in this structure. + */ + +#define TCL_SMALL_HASH_TABLE 4 +struct Tcl_HashTable { + Tcl_HashEntry **buckets; /* Pointer to bucket array. Each element + * points to first entry in bucket's hash + * chain, or NULL. */ + Tcl_HashEntry *staticBuckets[TCL_SMALL_HASH_TABLE]; + /* Bucket array used for small tables (to + * avoid mallocs and frees). */ + int numBuckets; /* Total number of buckets allocated at + * **bucketPtr. */ + int numEntries; /* Total number of entries present in + * table. */ + int rebuildSize; /* Enlarge table when numEntries gets to be + * this large. */ + int downShift; /* Shift count used in hashing function. + * Designed to use high-order bits of + * randomized keys. */ + int mask; /* Mask value used in hashing function. */ + int keyType; /* Type of keys used in this table. It's + * either TCL_CUSTOM_KEYS, TCL_STRING_KEYS, + * TCL_ONE_WORD_KEYS, or an integer giving the + * number of ints that is the size of the + * key. */ + Tcl_HashEntry *(*findProc) (Tcl_HashTable *tablePtr, const char *key); + Tcl_HashEntry *(*createProc) (Tcl_HashTable *tablePtr, const char *key, + int *newPtr); + const Tcl_HashKeyType *typePtr; + /* Type of the keys used in the + * Tcl_HashTable. */ +}; + +/* + * Structure definition for information used to keep track of searches through + * hash tables: + */ + +typedef struct Tcl_HashSearch { + Tcl_HashTable *tablePtr; /* Table being searched. */ + int nextIndex; /* Index of next bucket to be enumerated after + * present one. */ + Tcl_HashEntry *nextEntryPtr;/* Next entry to be enumerated in the current + * bucket. */ +} Tcl_HashSearch; + +/* + * Acceptable key types for hash tables: + * + * TCL_STRING_KEYS: The keys are strings, they are copied into the + * entry. + * TCL_ONE_WORD_KEYS: The keys are pointers, the pointer is stored + * in the entry. + * TCL_CUSTOM_TYPE_KEYS: The keys are arbitrary types which are copied + * into the entry. + * TCL_CUSTOM_PTR_KEYS: The keys are pointers to arbitrary types, the + * pointer is stored in the entry. + * + * While maintaining binary compatability the above have to be distinct values + * as they are used to differentiate between old versions of the hash table + * which don't have a typePtr and new ones which do. Once binary compatability + * is discarded in favour of making more wide spread changes TCL_STRING_KEYS + * can be the same as TCL_CUSTOM_TYPE_KEYS, and TCL_ONE_WORD_KEYS can be the + * same as TCL_CUSTOM_PTR_KEYS because they simply determine how the key is + * accessed from the entry and not the behaviour. + */ + +#define TCL_STRING_KEYS (0) +#define TCL_ONE_WORD_KEYS (1) +#define TCL_CUSTOM_TYPE_KEYS (-2) +#define TCL_CUSTOM_PTR_KEYS (-1) + +/* + * Structure definition for information used to keep track of searches through + * dictionaries. These fields should not be accessed by code outside + * tclDictObj.c + */ + +typedef struct { + void *next; /* Search position for underlying hash + * table. */ + int epoch; /* Epoch marker for dictionary being searched, + * or -1 if search has terminated. */ + Tcl_Dict dictionaryPtr; /* Reference to dictionary being searched. */ +} Tcl_DictSearch; + +/* + *---------------------------------------------------------------------------- + * Flag values to pass to Tcl_DoOneEvent to disable searches for some kinds of + * events: + */ + +#define TCL_DONT_WAIT (1<<1) +#define TCL_WINDOW_EVENTS (1<<2) +#define TCL_FILE_EVENTS (1<<3) +#define TCL_TIMER_EVENTS (1<<4) +#define TCL_IDLE_EVENTS (1<<5) /* WAS 0x10 ???? */ +#define TCL_ALL_EVENTS (~TCL_DONT_WAIT) + +/* + * The following structure defines a generic event for the Tcl event system. + * These are the things that are queued in calls to Tcl_QueueEvent and + * serviced later by Tcl_DoOneEvent. There can be many different kinds of + * events with different fields, corresponding to window events, timer events, + * etc. The structure for a particular event consists of a Tcl_Event header + * followed by additional information specific to that event. + */ + +struct Tcl_Event { + Tcl_EventProc *proc; /* Function to call to service this event. */ + struct Tcl_Event *nextPtr; /* Next in list of pending events, or NULL. */ +}; + +/* + * Positions to pass to Tcl_QueueEvent: + */ + +typedef enum { + TCL_QUEUE_TAIL, TCL_QUEUE_HEAD, TCL_QUEUE_MARK +} Tcl_QueuePosition; + +/* + * Values to pass to Tcl_SetServiceMode to specify the behavior of notifier + * event routines. + */ + +#define TCL_SERVICE_NONE 0 +#define TCL_SERVICE_ALL 1 + +/* + * The following structure keeps is used to hold a time value, either as an + * absolute time (the number of seconds from the epoch) or as an elapsed time. + * On Unix systems the epoch is Midnight Jan 1, 1970 GMT. + */ + +typedef struct Tcl_Time { + long sec; /* Seconds. */ + long usec; /* Microseconds. */ +} Tcl_Time; + +typedef void (Tcl_SetTimerProc) (CONST86 Tcl_Time *timePtr); +typedef int (Tcl_WaitForEventProc) (CONST86 Tcl_Time *timePtr); + +/* + * TIP #233 (Virtualized Time) + */ + +typedef void (Tcl_GetTimeProc) (Tcl_Time *timebuf, ClientData clientData); +typedef void (Tcl_ScaleTimeProc) (Tcl_Time *timebuf, ClientData clientData); + +/* + *---------------------------------------------------------------------------- + * Bits to pass to Tcl_CreateFileHandler and Tcl_CreateChannelHandler to + * indicate what sorts of events are of interest: + */ + +#define TCL_READABLE (1<<1) +#define TCL_WRITABLE (1<<2) +#define TCL_EXCEPTION (1<<3) + +/* + * Flag values to pass to Tcl_OpenCommandChannel to indicate the disposition + * of the stdio handles. TCL_STDIN, TCL_STDOUT, TCL_STDERR, are also used in + * Tcl_GetStdChannel. + */ + +#define TCL_STDIN (1<<1) +#define TCL_STDOUT (1<<2) +#define TCL_STDERR (1<<3) +#define TCL_ENFORCE_MODE (1<<4) + +/* + * Bits passed to Tcl_DriverClose2Proc to indicate which side of a channel + * should be closed. + */ + +#define TCL_CLOSE_READ (1<<1) +#define TCL_CLOSE_WRITE (1<<2) + +/* + * Value to use as the closeProc for a channel that supports the close2Proc + * interface. + */ + +#define TCL_CLOSE2PROC ((Tcl_DriverCloseProc *) 1) + +/* + * Channel version tag. This was introduced in 8.3.2/8.4. + */ + +#define TCL_CHANNEL_VERSION_1 ((Tcl_ChannelTypeVersion) 0x1) +#define TCL_CHANNEL_VERSION_2 ((Tcl_ChannelTypeVersion) 0x2) +#define TCL_CHANNEL_VERSION_3 ((Tcl_ChannelTypeVersion) 0x3) +#define TCL_CHANNEL_VERSION_4 ((Tcl_ChannelTypeVersion) 0x4) +#define TCL_CHANNEL_VERSION_5 ((Tcl_ChannelTypeVersion) 0x5) + +/* + * TIP #218: Channel Actions, Ids for Tcl_DriverThreadActionProc. + */ + +#define TCL_CHANNEL_THREAD_INSERT (0) +#define TCL_CHANNEL_THREAD_REMOVE (1) + +/* + * Typedefs for the various operations in a channel type: + */ + +typedef int (Tcl_DriverBlockModeProc) (ClientData instanceData, int mode); +typedef int (Tcl_DriverCloseProc) (ClientData instanceData, + Tcl_Interp *interp); +typedef int (Tcl_DriverClose2Proc) (ClientData instanceData, + Tcl_Interp *interp, int flags); +typedef int (Tcl_DriverInputProc) (ClientData instanceData, char *buf, + int toRead, int *errorCodePtr); +typedef int (Tcl_DriverOutputProc) (ClientData instanceData, + CONST84 char *buf, int toWrite, int *errorCodePtr); +typedef int (Tcl_DriverSeekProc) (ClientData instanceData, long offset, + int mode, int *errorCodePtr); +typedef int (Tcl_DriverSetOptionProc) (ClientData instanceData, + Tcl_Interp *interp, const char *optionName, + const char *value); +typedef int (Tcl_DriverGetOptionProc) (ClientData instanceData, + Tcl_Interp *interp, CONST84 char *optionName, + Tcl_DString *dsPtr); +typedef void (Tcl_DriverWatchProc) (ClientData instanceData, int mask); +typedef int (Tcl_DriverGetHandleProc) (ClientData instanceData, + int direction, ClientData *handlePtr); +typedef int (Tcl_DriverFlushProc) (ClientData instanceData); +typedef int (Tcl_DriverHandlerProc) (ClientData instanceData, + int interestMask); +typedef Tcl_WideInt (Tcl_DriverWideSeekProc) (ClientData instanceData, + Tcl_WideInt offset, int mode, int *errorCodePtr); +/* + * TIP #218, Channel Thread Actions + */ +typedef void (Tcl_DriverThreadActionProc) (ClientData instanceData, + int action); +/* + * TIP #208, File Truncation (etc.) + */ +typedef int (Tcl_DriverTruncateProc) (ClientData instanceData, + Tcl_WideInt length); + +/* + * struct Tcl_ChannelType: + * + * One such structure exists for each type (kind) of channel. It collects + * together in one place all the functions that are part of the specific + * channel type. + * + * It is recommend that the Tcl_Channel* functions are used to access elements + * of this structure, instead of direct accessing. + */ + +typedef struct Tcl_ChannelType { + const char *typeName; /* The name of the channel type in Tcl + * commands. This storage is owned by channel + * type. */ + Tcl_ChannelTypeVersion version; + /* Version of the channel type. */ + Tcl_DriverCloseProc *closeProc; + /* Function to call to close the channel, or + * TCL_CLOSE2PROC if the close2Proc should be + * used instead. */ + Tcl_DriverInputProc *inputProc; + /* Function to call for input on channel. */ + Tcl_DriverOutputProc *outputProc; + /* Function to call for output on channel. */ + Tcl_DriverSeekProc *seekProc; + /* Function to call to seek on the channel. + * May be NULL. */ + Tcl_DriverSetOptionProc *setOptionProc; + /* Set an option on a channel. */ + Tcl_DriverGetOptionProc *getOptionProc; + /* Get an option from a channel. */ + Tcl_DriverWatchProc *watchProc; + /* Set up the notifier to watch for events on + * this channel. */ + Tcl_DriverGetHandleProc *getHandleProc; + /* Get an OS handle from the channel or NULL + * if not supported. */ + Tcl_DriverClose2Proc *close2Proc; + /* Function to call to close the channel if + * the device supports closing the read & + * write sides independently. */ + Tcl_DriverBlockModeProc *blockModeProc; + /* Set blocking mode for the raw channel. May + * be NULL. */ + /* + * Only valid in TCL_CHANNEL_VERSION_2 channels or later. + */ + Tcl_DriverFlushProc *flushProc; + /* Function to call to flush a channel. May be + * NULL. */ + Tcl_DriverHandlerProc *handlerProc; + /* Function to call to handle a channel event. + * This will be passed up the stacked channel + * chain. */ + /* + * Only valid in TCL_CHANNEL_VERSION_3 channels or later. + */ + Tcl_DriverWideSeekProc *wideSeekProc; + /* Function to call to seek on the channel + * which can handle 64-bit offsets. May be + * NULL, and must be NULL if seekProc is + * NULL. */ + /* + * Only valid in TCL_CHANNEL_VERSION_4 channels or later. + * TIP #218, Channel Thread Actions. + */ + Tcl_DriverThreadActionProc *threadActionProc; + /* Function to call to notify the driver of + * thread specific activity for a channel. May + * be NULL. */ + /* + * Only valid in TCL_CHANNEL_VERSION_5 channels or later. + * TIP #208, File Truncation. + */ + Tcl_DriverTruncateProc *truncateProc; + /* Function to call to truncate the underlying + * file to a particular length. May be NULL if + * the channel does not support truncation. */ +} Tcl_ChannelType; + +/* + * The following flags determine whether the blockModeProc above should set + * the channel into blocking or nonblocking mode. They are passed as arguments + * to the blockModeProc function in the above structure. + */ + +#define TCL_MODE_BLOCKING 0 /* Put channel into blocking mode. */ +#define TCL_MODE_NONBLOCKING 1 /* Put channel into nonblocking + * mode. */ + +/* + *---------------------------------------------------------------------------- + * Enum for different types of file paths. + */ + +typedef enum Tcl_PathType { + TCL_PATH_ABSOLUTE, + TCL_PATH_RELATIVE, + TCL_PATH_VOLUME_RELATIVE +} Tcl_PathType; + +/* + * The following structure is used to pass glob type data amongst the various + * glob routines and Tcl_FSMatchInDirectory. + */ + +typedef struct Tcl_GlobTypeData { + int type; /* Corresponds to bcdpfls as in 'find -t'. */ + int perm; /* Corresponds to file permissions. */ + Tcl_Obj *macType; /* Acceptable Mac type. */ + Tcl_Obj *macCreator; /* Acceptable Mac creator. */ +} Tcl_GlobTypeData; + +/* + * Type and permission definitions for glob command. + */ + +#define TCL_GLOB_TYPE_BLOCK (1<<0) +#define TCL_GLOB_TYPE_CHAR (1<<1) +#define TCL_GLOB_TYPE_DIR (1<<2) +#define TCL_GLOB_TYPE_PIPE (1<<3) +#define TCL_GLOB_TYPE_FILE (1<<4) +#define TCL_GLOB_TYPE_LINK (1<<5) +#define TCL_GLOB_TYPE_SOCK (1<<6) +#define TCL_GLOB_TYPE_MOUNT (1<<7) + +#define TCL_GLOB_PERM_RONLY (1<<0) +#define TCL_GLOB_PERM_HIDDEN (1<<1) +#define TCL_GLOB_PERM_R (1<<2) +#define TCL_GLOB_PERM_W (1<<3) +#define TCL_GLOB_PERM_X (1<<4) + +/* + * Flags for the unload callback function. + */ + +#define TCL_UNLOAD_DETACH_FROM_INTERPRETER (1<<0) +#define TCL_UNLOAD_DETACH_FROM_PROCESS (1<<1) + +/* + * Typedefs for the various filesystem operations: + */ + +typedef int (Tcl_FSStatProc) (Tcl_Obj *pathPtr, Tcl_StatBuf *buf); +typedef int (Tcl_FSAccessProc) (Tcl_Obj *pathPtr, int mode); +typedef Tcl_Channel (Tcl_FSOpenFileChannelProc) (Tcl_Interp *interp, + Tcl_Obj *pathPtr, int mode, int permissions); +typedef int (Tcl_FSMatchInDirectoryProc) (Tcl_Interp *interp, Tcl_Obj *result, + Tcl_Obj *pathPtr, const char *pattern, Tcl_GlobTypeData *types); +typedef Tcl_Obj * (Tcl_FSGetCwdProc) (Tcl_Interp *interp); +typedef int (Tcl_FSChdirProc) (Tcl_Obj *pathPtr); +typedef int (Tcl_FSLstatProc) (Tcl_Obj *pathPtr, Tcl_StatBuf *buf); +typedef int (Tcl_FSCreateDirectoryProc) (Tcl_Obj *pathPtr); +typedef int (Tcl_FSDeleteFileProc) (Tcl_Obj *pathPtr); +typedef int (Tcl_FSCopyDirectoryProc) (Tcl_Obj *srcPathPtr, + Tcl_Obj *destPathPtr, Tcl_Obj **errorPtr); +typedef int (Tcl_FSCopyFileProc) (Tcl_Obj *srcPathPtr, Tcl_Obj *destPathPtr); +typedef int (Tcl_FSRemoveDirectoryProc) (Tcl_Obj *pathPtr, int recursive, + Tcl_Obj **errorPtr); +typedef int (Tcl_FSRenameFileProc) (Tcl_Obj *srcPathPtr, Tcl_Obj *destPathPtr); +typedef void (Tcl_FSUnloadFileProc) (Tcl_LoadHandle loadHandle); +typedef Tcl_Obj * (Tcl_FSListVolumesProc) (void); +/* We have to declare the utime structure here. */ +struct utimbuf; +typedef int (Tcl_FSUtimeProc) (Tcl_Obj *pathPtr, struct utimbuf *tval); +typedef int (Tcl_FSNormalizePathProc) (Tcl_Interp *interp, Tcl_Obj *pathPtr, + int nextCheckpoint); +typedef int (Tcl_FSFileAttrsGetProc) (Tcl_Interp *interp, int index, + Tcl_Obj *pathPtr, Tcl_Obj **objPtrRef); +typedef const char *CONST86 * (Tcl_FSFileAttrStringsProc) (Tcl_Obj *pathPtr, + Tcl_Obj **objPtrRef); +typedef int (Tcl_FSFileAttrsSetProc) (Tcl_Interp *interp, int index, + Tcl_Obj *pathPtr, Tcl_Obj *objPtr); +typedef Tcl_Obj * (Tcl_FSLinkProc) (Tcl_Obj *pathPtr, Tcl_Obj *toPtr, + int linkType); +typedef int (Tcl_FSLoadFileProc) (Tcl_Interp *interp, Tcl_Obj *pathPtr, + Tcl_LoadHandle *handlePtr, Tcl_FSUnloadFileProc **unloadProcPtr); +typedef int (Tcl_FSPathInFilesystemProc) (Tcl_Obj *pathPtr, + ClientData *clientDataPtr); +typedef Tcl_Obj * (Tcl_FSFilesystemPathTypeProc) (Tcl_Obj *pathPtr); +typedef Tcl_Obj * (Tcl_FSFilesystemSeparatorProc) (Tcl_Obj *pathPtr); +typedef void (Tcl_FSFreeInternalRepProc) (ClientData clientData); +typedef ClientData (Tcl_FSDupInternalRepProc) (ClientData clientData); +typedef Tcl_Obj * (Tcl_FSInternalToNormalizedProc) (ClientData clientData); +typedef ClientData (Tcl_FSCreateInternalRepProc) (Tcl_Obj *pathPtr); + +typedef struct Tcl_FSVersion_ *Tcl_FSVersion; + +/* + *---------------------------------------------------------------------------- + * Data structures related to hooking into the filesystem + */ + +/* + * Filesystem version tag. This was introduced in 8.4. + */ + +#define TCL_FILESYSTEM_VERSION_1 ((Tcl_FSVersion) 0x1) + +/* + * struct Tcl_Filesystem: + * + * One such structure exists for each type (kind) of filesystem. It collects + * together in one place all the functions that are part of the specific + * filesystem. Tcl always accesses the filesystem through one of these + * structures. + * + * Not all entries need be non-NULL; any which are NULL are simply ignored. + * However, a complete filesystem should provide all of these functions. The + * explanations in the structure show the importance of each function. + */ + +typedef struct Tcl_Filesystem { + const char *typeName; /* The name of the filesystem. */ + int structureLength; /* Length of this structure, so future binary + * compatibility can be assured. */ + Tcl_FSVersion version; /* Version of the filesystem type. */ + Tcl_FSPathInFilesystemProc *pathInFilesystemProc; + /* Function to check whether a path is in this + * filesystem. This is the most important + * filesystem function. */ + Tcl_FSDupInternalRepProc *dupInternalRepProc; + /* Function to duplicate internal fs rep. May + * be NULL (but then fs is less efficient). */ + Tcl_FSFreeInternalRepProc *freeInternalRepProc; + /* Function to free internal fs rep. Must be + * implemented if internal representations + * need freeing, otherwise it can be NULL. */ + Tcl_FSInternalToNormalizedProc *internalToNormalizedProc; + /* Function to convert internal representation + * to a normalized path. Only required if the + * fs creates pure path objects with no + * string/path representation. */ + Tcl_FSCreateInternalRepProc *createInternalRepProc; + /* Function to create a filesystem-specific + * internal representation. May be NULL if + * paths have no internal representation, or + * if the Tcl_FSPathInFilesystemProc for this + * filesystem always immediately creates an + * internal representation for paths it + * accepts. */ + Tcl_FSNormalizePathProc *normalizePathProc; + /* Function to normalize a path. Should be + * implemented for all filesystems which can + * have multiple string representations for + * the same path object. */ + Tcl_FSFilesystemPathTypeProc *filesystemPathTypeProc; + /* Function to determine the type of a path in + * this filesystem. May be NULL. */ + Tcl_FSFilesystemSeparatorProc *filesystemSeparatorProc; + /* Function to return the separator + * character(s) for this filesystem. Must be + * implemented. */ + Tcl_FSStatProc *statProc; /* Function to process a 'Tcl_FSStat()' call. + * Must be implemented for any reasonable + * filesystem. */ + Tcl_FSAccessProc *accessProc; + /* Function to process a 'Tcl_FSAccess()' + * call. Must be implemented for any + * reasonable filesystem. */ + Tcl_FSOpenFileChannelProc *openFileChannelProc; + /* Function to process a + * 'Tcl_FSOpenFileChannel()' call. Must be + * implemented for any reasonable + * filesystem. */ + Tcl_FSMatchInDirectoryProc *matchInDirectoryProc; + /* Function to process a + * 'Tcl_FSMatchInDirectory()'. If not + * implemented, then glob and recursive copy + * functionality will be lacking in the + * filesystem. */ + Tcl_FSUtimeProc *utimeProc; /* Function to process a 'Tcl_FSUtime()' call. + * Required to allow setting (not reading) of + * times with 'file mtime', 'file atime' and + * the open-r/open-w/fcopy implementation of + * 'file copy'. */ + Tcl_FSLinkProc *linkProc; /* Function to process a 'Tcl_FSLink()' call. + * Should be implemented only if the + * filesystem supports links (reading or + * creating). */ + Tcl_FSListVolumesProc *listVolumesProc; + /* Function to list any filesystem volumes + * added by this filesystem. Should be + * implemented only if the filesystem adds + * volumes at the head of the filesystem. */ + Tcl_FSFileAttrStringsProc *fileAttrStringsProc; + /* Function to list all attributes strings + * which are valid for this filesystem. If not + * implemented the filesystem will not support + * the 'file attributes' command. This allows + * arbitrary additional information to be + * attached to files in the filesystem. */ + Tcl_FSFileAttrsGetProc *fileAttrsGetProc; + /* Function to process a + * 'Tcl_FSFileAttrsGet()' call, used by 'file + * attributes'. */ + Tcl_FSFileAttrsSetProc *fileAttrsSetProc; + /* Function to process a + * 'Tcl_FSFileAttrsSet()' call, used by 'file + * attributes'. */ + Tcl_FSCreateDirectoryProc *createDirectoryProc; + /* Function to process a + * 'Tcl_FSCreateDirectory()' call. Should be + * implemented unless the FS is read-only. */ + Tcl_FSRemoveDirectoryProc *removeDirectoryProc; + /* Function to process a + * 'Tcl_FSRemoveDirectory()' call. Should be + * implemented unless the FS is read-only. */ + Tcl_FSDeleteFileProc *deleteFileProc; + /* Function to process a 'Tcl_FSDeleteFile()' + * call. Should be implemented unless the FS + * is read-only. */ + Tcl_FSCopyFileProc *copyFileProc; + /* Function to process a 'Tcl_FSCopyFile()' + * call. If not implemented Tcl will fall back + * on open-r, open-w and fcopy as a copying + * mechanism, for copying actions initiated in + * Tcl (not C). */ + Tcl_FSRenameFileProc *renameFileProc; + /* Function to process a 'Tcl_FSRenameFile()' + * call. If not implemented, Tcl will fall + * back on a copy and delete mechanism, for + * rename actions initiated in Tcl (not C). */ + Tcl_FSCopyDirectoryProc *copyDirectoryProc; + /* Function to process a + * 'Tcl_FSCopyDirectory()' call. If not + * implemented, Tcl will fall back on a + * recursive create-dir, file copy mechanism, + * for copying actions initiated in Tcl (not + * C). */ + Tcl_FSLstatProc *lstatProc; /* Function to process a 'Tcl_FSLstat()' call. + * If not implemented, Tcl will attempt to use + * the 'statProc' defined above instead. */ + Tcl_FSLoadFileProc *loadFileProc; + /* Function to process a 'Tcl_FSLoadFile()' + * call. If not implemented, Tcl will fall + * back on a copy to native-temp followed by a + * Tcl_FSLoadFile on that temporary copy. */ + Tcl_FSGetCwdProc *getCwdProc; + /* Function to process a 'Tcl_FSGetCwd()' + * call. Most filesystems need not implement + * this. It will usually only be called once, + * if 'getcwd' is called before 'chdir'. May + * be NULL. */ + Tcl_FSChdirProc *chdirProc; /* Function to process a 'Tcl_FSChdir()' call. + * If filesystems do not implement this, it + * will be emulated by a series of directory + * access checks. Otherwise, virtual + * filesystems which do implement it need only + * respond with a positive return result if + * the dirName is a valid directory in their + * filesystem. They need not remember the + * result, since that will be automatically + * remembered for use by GetCwd. Real + * filesystems should carry out the correct + * action (i.e. call the correct system + * 'chdir' api). If not implemented, then 'cd' + * and 'pwd' will fail inside the + * filesystem. */ +} Tcl_Filesystem; + +/* + * The following definitions are used as values for the 'linkAction' flag to + * Tcl_FSLink, or the linkProc of any filesystem. Any combination of flags can + * be given. For link creation, the linkProc should create a link which + * matches any of the types given. + * + * TCL_CREATE_SYMBOLIC_LINK - Create a symbolic or soft link. + * TCL_CREATE_HARD_LINK - Create a hard link. + */ + +#define TCL_CREATE_SYMBOLIC_LINK 0x01 +#define TCL_CREATE_HARD_LINK 0x02 + +/* + *---------------------------------------------------------------------------- + * The following structure represents the Notifier functions that you can + * override with the Tcl_SetNotifier call. + */ + +typedef struct Tcl_NotifierProcs { + Tcl_SetTimerProc *setTimerProc; + Tcl_WaitForEventProc *waitForEventProc; + Tcl_CreateFileHandlerProc *createFileHandlerProc; + Tcl_DeleteFileHandlerProc *deleteFileHandlerProc; + Tcl_InitNotifierProc *initNotifierProc; + Tcl_FinalizeNotifierProc *finalizeNotifierProc; + Tcl_AlertNotifierProc *alertNotifierProc; + Tcl_ServiceModeHookProc *serviceModeHookProc; +} Tcl_NotifierProcs; + +/* + *---------------------------------------------------------------------------- + * The following data structures and declarations are for the new Tcl parser. + * + * For each word of a command, and for each piece of a word such as a variable + * reference, one of the following structures is created to describe the + * token. + */ + +typedef struct Tcl_Token { + int type; /* Type of token, such as TCL_TOKEN_WORD; see + * below for valid types. */ + const char *start; /* First character in token. */ + int size; /* Number of bytes in token. */ + int numComponents; /* If this token is composed of other tokens, + * this field tells how many of them there are + * (including components of components, etc.). + * The component tokens immediately follow + * this one. */ +} Tcl_Token; + +/* + * Type values defined for Tcl_Token structures. These values are defined as + * mask bits so that it's easy to check for collections of types. + * + * TCL_TOKEN_WORD - The token describes one word of a command, + * from the first non-blank character of the word + * (which may be " or {) up to but not including + * the space, semicolon, or bracket that + * terminates the word. NumComponents counts the + * total number of sub-tokens that make up the + * word. This includes, for example, sub-tokens + * of TCL_TOKEN_VARIABLE tokens. + * TCL_TOKEN_SIMPLE_WORD - This token is just like TCL_TOKEN_WORD except + * that the word is guaranteed to consist of a + * single TCL_TOKEN_TEXT sub-token. + * TCL_TOKEN_TEXT - The token describes a range of literal text + * that is part of a word. NumComponents is + * always 0. + * TCL_TOKEN_BS - The token describes a backslash sequence that + * must be collapsed. NumComponents is always 0. + * TCL_TOKEN_COMMAND - The token describes a command whose result + * must be substituted into the word. The token + * includes the enclosing brackets. NumComponents + * is always 0. + * TCL_TOKEN_VARIABLE - The token describes a variable substitution, + * including the dollar sign, variable name, and + * array index (if there is one) up through the + * right parentheses. NumComponents tells how + * many additional tokens follow to represent the + * variable name. The first token will be a + * TCL_TOKEN_TEXT token that describes the + * variable name. If the variable is an array + * reference then there will be one or more + * additional tokens, of type TCL_TOKEN_TEXT, + * TCL_TOKEN_BS, TCL_TOKEN_COMMAND, and + * TCL_TOKEN_VARIABLE, that describe the array + * index; numComponents counts the total number + * of nested tokens that make up the variable + * reference, including sub-tokens of + * TCL_TOKEN_VARIABLE tokens. + * TCL_TOKEN_SUB_EXPR - The token describes one subexpression of an + * expression, from the first non-blank character + * of the subexpression up to but not including + * the space, brace, or bracket that terminates + * the subexpression. NumComponents counts the + * total number of following subtokens that make + * up the subexpression; this includes all + * subtokens for any nested TCL_TOKEN_SUB_EXPR + * tokens. For example, a numeric value used as a + * primitive operand is described by a + * TCL_TOKEN_SUB_EXPR token followed by a + * TCL_TOKEN_TEXT token. A binary subexpression + * is described by a TCL_TOKEN_SUB_EXPR token + * followed by the TCL_TOKEN_OPERATOR token for + * the operator, then TCL_TOKEN_SUB_EXPR tokens + * for the left then the right operands. + * TCL_TOKEN_OPERATOR - The token describes one expression operator. + * An operator might be the name of a math + * function such as "abs". A TCL_TOKEN_OPERATOR + * token is always preceeded by one + * TCL_TOKEN_SUB_EXPR token for the operator's + * subexpression, and is followed by zero or more + * TCL_TOKEN_SUB_EXPR tokens for the operator's + * operands. NumComponents is always 0. + * TCL_TOKEN_EXPAND_WORD - This token is just like TCL_TOKEN_WORD except + * that it marks a word that began with the + * literal character prefix "{*}". This word is + * marked to be expanded - that is, broken into + * words after substitution is complete. + */ + +#define TCL_TOKEN_WORD 1 +#define TCL_TOKEN_SIMPLE_WORD 2 +#define TCL_TOKEN_TEXT 4 +#define TCL_TOKEN_BS 8 +#define TCL_TOKEN_COMMAND 16 +#define TCL_TOKEN_VARIABLE 32 +#define TCL_TOKEN_SUB_EXPR 64 +#define TCL_TOKEN_OPERATOR 128 +#define TCL_TOKEN_EXPAND_WORD 256 + +/* + * Parsing error types. On any parsing error, one of these values will be + * stored in the error field of the Tcl_Parse structure defined below. + */ + +#define TCL_PARSE_SUCCESS 0 +#define TCL_PARSE_QUOTE_EXTRA 1 +#define TCL_PARSE_BRACE_EXTRA 2 +#define TCL_PARSE_MISSING_BRACE 3 +#define TCL_PARSE_MISSING_BRACKET 4 +#define TCL_PARSE_MISSING_PAREN 5 +#define TCL_PARSE_MISSING_QUOTE 6 +#define TCL_PARSE_MISSING_VAR_BRACE 7 +#define TCL_PARSE_SYNTAX 8 +#define TCL_PARSE_BAD_NUMBER 9 + +/* + * A structure of the following type is filled in by Tcl_ParseCommand. It + * describes a single command parsed from an input string. + */ + +#define NUM_STATIC_TOKENS 20 + +typedef struct Tcl_Parse { + const char *commentStart; /* Pointer to # that begins the first of one + * or more comments preceding the command. */ + int commentSize; /* Number of bytes in comments (up through + * newline character that terminates the last + * comment). If there were no comments, this + * field is 0. */ + const char *commandStart; /* First character in first word of + * command. */ + int commandSize; /* Number of bytes in command, including first + * character of first word, up through the + * terminating newline, close bracket, or + * semicolon. */ + int numWords; /* Total number of words in command. May be + * 0. */ + Tcl_Token *tokenPtr; /* Pointer to first token representing the + * words of the command. Initially points to + * staticTokens, but may change to point to + * malloc-ed space if command exceeds space in + * staticTokens. */ + int numTokens; /* Total number of tokens in command. */ + int tokensAvailable; /* Total number of tokens available at + * *tokenPtr. */ + int errorType; /* One of the parsing error types defined + * above. */ + + /* + * The fields below are intended only for the private use of the parser. + * They should not be used by functions that invoke Tcl_ParseCommand. + */ + + const char *string; /* The original command string passed to + * Tcl_ParseCommand. */ + const char *end; /* Points to the character just after the last + * one in the command string. */ + Tcl_Interp *interp; /* Interpreter to use for error reporting, or + * NULL. */ + const char *term; /* Points to character in string that + * terminated most recent token. Filled in by + * ParseTokens. If an error occurs, points to + * beginning of region where the error + * occurred (e.g. the open brace if the close + * brace is missing). */ + int incomplete; /* This field is set to 1 by Tcl_ParseCommand + * if the command appears to be incomplete. + * This information is used by + * Tcl_CommandComplete. */ + Tcl_Token staticTokens[NUM_STATIC_TOKENS]; + /* Initial space for tokens for command. This + * space should be large enough to accommodate + * most commands; dynamic space is allocated + * for very large commands that don't fit + * here. */ +} Tcl_Parse; + +/* + *---------------------------------------------------------------------------- + * The following structure represents a user-defined encoding. It collects + * together all the functions that are used by the specific encoding. + */ + +typedef struct Tcl_EncodingType { + const char *encodingName; /* The name of the encoding, e.g. "euc-jp". + * This name is the unique key for this + * encoding type. */ + Tcl_EncodingConvertProc *toUtfProc; + /* Function to convert from external encoding + * into UTF-8. */ + Tcl_EncodingConvertProc *fromUtfProc; + /* Function to convert from UTF-8 into + * external encoding. */ + Tcl_EncodingFreeProc *freeProc; + /* If non-NULL, function to call when this + * encoding is deleted. */ + ClientData clientData; /* Arbitrary value associated with encoding + * type. Passed to conversion functions. */ + int nullSize; /* Number of zero bytes that signify + * end-of-string in this encoding. This number + * is used to determine the source string + * length when the srcLen argument is + * negative. Must be 1 or 2. */ +} Tcl_EncodingType; + +/* + * The following definitions are used as values for the conversion control + * flags argument when converting text from one character set to another: + * + * TCL_ENCODING_START - Signifies that the source buffer is the first + * block in a (potentially multi-block) input + * stream. Tells the conversion function to reset + * to an initial state and perform any + * initialization that needs to occur before the + * first byte is converted. If the source buffer + * contains the entire input stream to be + * converted, this flag should be set. + * TCL_ENCODING_END - Signifies that the source buffer is the last + * block in a (potentially multi-block) input + * stream. Tells the conversion routine to + * perform any finalization that needs to occur + * after the last byte is converted and then to + * reset to an initial state. If the source + * buffer contains the entire input stream to be + * converted, this flag should be set. + * TCL_ENCODING_STOPONERROR - If set, then the converter will return + * immediately upon encountering an invalid byte + * sequence or a source character that has no + * mapping in the target encoding. If clear, then + * the converter will skip the problem, + * substituting one or more "close" characters in + * the destination buffer and then continue to + * convert the source. + */ + +#define TCL_ENCODING_START 0x01 +#define TCL_ENCODING_END 0x02 +#define TCL_ENCODING_STOPONERROR 0x04 + +/* + * The following definitions are the error codes returned by the conversion + * routines: + * + * TCL_OK - All characters were converted. + * TCL_CONVERT_NOSPACE - The output buffer would not have been large + * enough for all of the converted data; as many + * characters as could fit were converted though. + * TCL_CONVERT_MULTIBYTE - The last few bytes in the source string were + * the beginning of a multibyte sequence, but + * more bytes were needed to complete this + * sequence. A subsequent call to the conversion + * routine should pass the beginning of this + * unconverted sequence plus additional bytes + * from the source stream to properly convert the + * formerly split-up multibyte sequence. + * TCL_CONVERT_SYNTAX - The source stream contained an invalid + * character sequence. This may occur if the + * input stream has been damaged or if the input + * encoding method was misidentified. This error + * is reported only if TCL_ENCODING_STOPONERROR + * was specified. + * TCL_CONVERT_UNKNOWN - The source string contained a character that + * could not be represented in the target + * encoding. This error is reported only if + * TCL_ENCODING_STOPONERROR was specified. + */ + +#define TCL_CONVERT_MULTIBYTE (-1) +#define TCL_CONVERT_SYNTAX (-2) +#define TCL_CONVERT_UNKNOWN (-3) +#define TCL_CONVERT_NOSPACE (-4) + +/* + * The maximum number of bytes that are necessary to represent a single + * Unicode character in UTF-8. The valid values should be 3, 4 or 6 + * (or perhaps 1 if we want to support a non-unicode enabled core). If 3 or + * 4, then Tcl_UniChar must be 2-bytes in size (UCS-2) (the default). If 6, + * then Tcl_UniChar must be 4-bytes in size (UCS-4). At this time UCS-2 mode + * is the default and recommended mode. UCS-4 is experimental and not + * recommended. It works for the core, but most extensions expect UCS-2. + */ + +#ifndef TCL_UTF_MAX +#define TCL_UTF_MAX 3 +#endif + +/* + * This represents a Unicode character. Any changes to this should also be + * reflected in regcustom.h. + */ + +#if TCL_UTF_MAX > 4 + /* + * unsigned int isn't 100% accurate as it should be a strict 4-byte value + * (perhaps wchar_t). 64-bit systems may have troubles. The size of this + * value must be reflected correctly in regcustom.h and + * in tclEncoding.c. + * XXX: Tcl is currently UCS-2 and planning UTF-16 for the Unicode + * XXX: string rep that Tcl_UniChar represents. Changing the size + * XXX: of Tcl_UniChar is /not/ supported. + */ +typedef unsigned int Tcl_UniChar; +#else +typedef unsigned short Tcl_UniChar; +#endif + +/* + *---------------------------------------------------------------------------- + * TIP #59: The following structure is used in calls 'Tcl_RegisterConfig' to + * provide the system with the embedded configuration data. + */ + +typedef struct Tcl_Config { + const char *key; /* Configuration key to register. ASCII + * encoded, thus UTF-8. */ + const char *value; /* The value associated with the key. System + * encoding. */ +} Tcl_Config; + +/* + *---------------------------------------------------------------------------- + * Flags for TIP#143 limits, detailing which limits are active in an + * interpreter. Used for Tcl_{Add,Remove}LimitHandler type argument. + */ + +#define TCL_LIMIT_COMMANDS 0x01 +#define TCL_LIMIT_TIME 0x02 + +/* + * Structure containing information about a limit handler to be called when a + * command- or time-limit is exceeded by an interpreter. + */ + +typedef void (Tcl_LimitHandlerProc) (ClientData clientData, Tcl_Interp *interp); +typedef void (Tcl_LimitHandlerDeleteProc) (ClientData clientData); + +/* + *---------------------------------------------------------------------------- + * Override definitions for libtommath. + */ + +typedef struct mp_int mp_int; +#define MP_INT_DECLARED +typedef unsigned int mp_digit; +#define MP_DIGIT_DECLARED + +/* + *---------------------------------------------------------------------------- + * Definitions needed for Tcl_ParseArgvObj routines. + * Based on tkArgv.c. + * Modifications from the original are copyright (c) Sam Bromley 2006 + */ + +typedef struct { + int type; /* Indicates the option type; see below. */ + const char *keyStr; /* The key string that flags the option in the + * argv array. */ + void *srcPtr; /* Value to be used in setting dst; usage + * depends on type.*/ + void *dstPtr; /* Address of value to be modified; usage + * depends on type.*/ + const char *helpStr; /* Documentation message describing this + * option. */ + ClientData clientData; /* Word to pass to function callbacks. */ +} Tcl_ArgvInfo; + +/* + * Legal values for the type field of a Tcl_ArgInfo: see the user + * documentation for details. + */ + +#define TCL_ARGV_CONSTANT 15 +#define TCL_ARGV_INT 16 +#define TCL_ARGV_STRING 17 +#define TCL_ARGV_REST 18 +#define TCL_ARGV_FLOAT 19 +#define TCL_ARGV_FUNC 20 +#define TCL_ARGV_GENFUNC 21 +#define TCL_ARGV_HELP 22 +#define TCL_ARGV_END 23 + +/* + * Types of callback functions for the TCL_ARGV_FUNC and TCL_ARGV_GENFUNC + * argument types: + */ + +typedef int (Tcl_ArgvFuncProc)(ClientData clientData, Tcl_Obj *objPtr, + void *dstPtr); +typedef int (Tcl_ArgvGenFuncProc)(ClientData clientData, Tcl_Interp *interp, + int objc, Tcl_Obj *const *objv, void *dstPtr); + +/* + * Shorthand for commonly used argTable entries. + */ + +#define TCL_ARGV_AUTO_HELP \ + {TCL_ARGV_HELP, "-help", NULL, NULL, \ + "Print summary of command-line options and abort", NULL} +#define TCL_ARGV_AUTO_REST \ + {TCL_ARGV_REST, "--", NULL, NULL, \ + "Marks the end of the options", NULL} +#define TCL_ARGV_TABLE_END \ + {TCL_ARGV_END, NULL, NULL, NULL, NULL, NULL} + +/* + *---------------------------------------------------------------------------- + * Definitions needed for Tcl_Zlib routines. [TIP #234] + * + * Constants for the format flags describing what sort of data format is + * desired/expected for the Tcl_ZlibDeflate, Tcl_ZlibInflate and + * Tcl_ZlibStreamInit functions. + */ + +#define TCL_ZLIB_FORMAT_RAW 1 +#define TCL_ZLIB_FORMAT_ZLIB 2 +#define TCL_ZLIB_FORMAT_GZIP 4 +#define TCL_ZLIB_FORMAT_AUTO 8 + +/* + * Constants that describe whether the stream is to operate in compressing or + * decompressing mode. + */ + +#define TCL_ZLIB_STREAM_DEFLATE 16 +#define TCL_ZLIB_STREAM_INFLATE 32 + +/* + * Constants giving compression levels. Use of TCL_ZLIB_COMPRESS_DEFAULT is + * recommended. + */ + +#define TCL_ZLIB_COMPRESS_NONE 0 +#define TCL_ZLIB_COMPRESS_FAST 1 +#define TCL_ZLIB_COMPRESS_BEST 9 +#define TCL_ZLIB_COMPRESS_DEFAULT (-1) + +/* + * Constants for types of flushing, used with Tcl_ZlibFlush. + */ + +#define TCL_ZLIB_NO_FLUSH 0 +#define TCL_ZLIB_FLUSH 2 +#define TCL_ZLIB_FULLFLUSH 3 +#define TCL_ZLIB_FINALIZE 4 + +/* + *---------------------------------------------------------------------------- + * Definitions needed for the Tcl_LoadFile function. [TIP #416] + */ + +#define TCL_LOAD_GLOBAL 1 +#define TCL_LOAD_LAZY 2 + +/* + *---------------------------------------------------------------------------- + * Single public declaration for NRE. + */ + +typedef int (Tcl_NRPostProc) (ClientData data[], Tcl_Interp *interp, + int result); + +/* + *---------------------------------------------------------------------------- + * The following constant is used to test for older versions of Tcl in the + * stubs tables. + * + * Jan Nijtman's plus patch uses 0xFCA1BACF, so we need to pick a different + * value since the stubs tables don't match. + */ + +#define TCL_STUB_MAGIC ((int) 0xFCA3BACF) + +/* + * The following function is required to be defined in all stubs aware + * extensions. The function is actually implemented in the stub library, not + * the main Tcl library, although there is a trivial implementation in the + * main library in case an extension is statically linked into an application. + */ + +const char * Tcl_InitStubs(Tcl_Interp *interp, const char *version, + int exact); +const char * TclTomMathInitializeStubs(Tcl_Interp *interp, + const char *version, int epoch, int revision); + +/* + * When not using stubs, make it a macro. + */ + +#ifndef USE_TCL_STUBS +#define Tcl_InitStubs(interp, version, exact) \ + Tcl_PkgInitStubsCheck(interp, version, exact) +#endif + +/* + * TODO - tommath stubs export goes here! + */ + +/* + * Public functions that are not accessible via the stubs table. + * Tcl_GetMemoryInfo is needed for AOLserver. [Bug 1868171] + */ + +#define Tcl_Main(argc, argv, proc) Tcl_MainEx(argc, argv, proc, \ + (Tcl_FindExecutable(argv[0]), (Tcl_CreateInterp)())) +EXTERN void Tcl_MainEx(int argc, char **argv, + Tcl_AppInitProc *appInitProc, Tcl_Interp *interp); +EXTERN const char * Tcl_PkgInitStubsCheck(Tcl_Interp *interp, + const char *version, int exact); +#if defined(TCL_THREADS) && defined(USE_THREAD_ALLOC) +EXTERN void Tcl_GetMemoryInfo(Tcl_DString *dsPtr); +#endif + +/* + *---------------------------------------------------------------------------- + * Include the public function declarations that are accessible via the stubs + * table. + */ + +#include "tclDecls.h" + +/* + * Include platform specific public function declarations that are accessible + * via the stubs table. + */ + +#include "tclPlatDecls.h" + +/* + *---------------------------------------------------------------------------- + * The following declarations either map ckalloc and ckfree to malloc and + * free, or they map them to functions with all sorts of debugging hooks + * defined in tclCkalloc.c. + */ + +#ifdef TCL_MEM_DEBUG + +# define ckalloc(x) \ + ((VOID *) Tcl_DbCkalloc((unsigned)(x), __FILE__, __LINE__)) +# define ckfree(x) \ + Tcl_DbCkfree((char *)(x), __FILE__, __LINE__) +# define ckrealloc(x,y) \ + ((VOID *) Tcl_DbCkrealloc((char *)(x), (unsigned)(y), __FILE__, __LINE__)) +# define attemptckalloc(x) \ + ((VOID *) Tcl_AttemptDbCkalloc((unsigned)(x), __FILE__, __LINE__)) +# define attemptckrealloc(x,y) \ + ((VOID *) Tcl_AttemptDbCkrealloc((char *)(x), (unsigned)(y), __FILE__, __LINE__)) + +#else /* !TCL_MEM_DEBUG */ + +/* + * If we are not using the debugging allocator, we should call the Tcl_Alloc, + * et al. routines in order to guarantee that every module is using the same + * memory allocator both inside and outside of the Tcl library. + */ + +# define ckalloc(x) \ + ((VOID *) Tcl_Alloc((unsigned)(x))) +# define ckfree(x) \ + Tcl_Free((char *)(x)) +# define ckrealloc(x,y) \ + ((VOID *) Tcl_Realloc((char *)(x), (unsigned)(y))) +# define attemptckalloc(x) \ + ((VOID *) Tcl_AttemptAlloc((unsigned)(x))) +# define attemptckrealloc(x,y) \ + ((VOID *) Tcl_AttemptRealloc((char *)(x), (unsigned)(y))) +# undef Tcl_InitMemory +# define Tcl_InitMemory(x) +# undef Tcl_DumpActiveMemory +# define Tcl_DumpActiveMemory(x) +# undef Tcl_ValidateAllMemory +# define Tcl_ValidateAllMemory(x,y) + +#endif /* !TCL_MEM_DEBUG */ + +#ifdef TCL_MEM_DEBUG +# define Tcl_IncrRefCount(objPtr) \ + Tcl_DbIncrRefCount(objPtr, __FILE__, __LINE__) +# define Tcl_DecrRefCount(objPtr) \ + Tcl_DbDecrRefCount(objPtr, __FILE__, __LINE__) +# define Tcl_IsShared(objPtr) \ + Tcl_DbIsShared(objPtr, __FILE__, __LINE__) +#else +# define Tcl_IncrRefCount(objPtr) \ + ++(objPtr)->refCount + /* + * Use do/while0 idiom for optimum correctness without compiler warnings. + * http://c2.com/cgi/wiki?TrivialDoWhileLoop + */ +# define Tcl_DecrRefCount(objPtr) \ + do { \ + Tcl_Obj *_objPtr = (objPtr); \ + if (--(_objPtr)->refCount <= 0) { \ + TclFreeObj(_objPtr); \ + } \ + } while(0) +# define Tcl_IsShared(objPtr) \ + ((objPtr)->refCount > 1) +#endif + +/* + * Macros and definitions that help to debug the use of Tcl objects. When + * TCL_MEM_DEBUG is defined, the Tcl_New declarations are overridden to call + * debugging versions of the object creation functions. + */ + +#ifdef TCL_MEM_DEBUG +# undef Tcl_NewBignumObj +# define Tcl_NewBignumObj(val) \ + Tcl_DbNewBignumObj(val, __FILE__, __LINE__) +# undef Tcl_NewBooleanObj +# define Tcl_NewBooleanObj(val) \ + Tcl_DbNewBooleanObj(val, __FILE__, __LINE__) +# undef Tcl_NewByteArrayObj +# define Tcl_NewByteArrayObj(bytes, len) \ + Tcl_DbNewByteArrayObj(bytes, len, __FILE__, __LINE__) +# undef Tcl_NewDoubleObj +# define Tcl_NewDoubleObj(val) \ + Tcl_DbNewDoubleObj(val, __FILE__, __LINE__) +# undef Tcl_NewIntObj +# define Tcl_NewIntObj(val) \ + Tcl_DbNewLongObj(val, __FILE__, __LINE__) +# undef Tcl_NewListObj +# define Tcl_NewListObj(objc, objv) \ + Tcl_DbNewListObj(objc, objv, __FILE__, __LINE__) +# undef Tcl_NewLongObj +# define Tcl_NewLongObj(val) \ + Tcl_DbNewLongObj(val, __FILE__, __LINE__) +# undef Tcl_NewObj +# define Tcl_NewObj() \ + Tcl_DbNewObj(__FILE__, __LINE__) +# undef Tcl_NewStringObj +# define Tcl_NewStringObj(bytes, len) \ + Tcl_DbNewStringObj(bytes, len, __FILE__, __LINE__) +# undef Tcl_NewWideIntObj +# define Tcl_NewWideIntObj(val) \ + Tcl_DbNewWideIntObj(val, __FILE__, __LINE__) +#endif /* TCL_MEM_DEBUG */ + +/* + *---------------------------------------------------------------------------- + * Macros for clients to use to access fields of hash entries: + */ + +#define Tcl_GetHashValue(h) ((h)->clientData) +#define Tcl_SetHashValue(h, value) ((h)->clientData = (ClientData) (value)) +#define Tcl_GetHashKey(tablePtr, h) \ + ((void *) (((tablePtr)->keyType == TCL_ONE_WORD_KEYS || \ + (tablePtr)->keyType == TCL_CUSTOM_PTR_KEYS) \ + ? (h)->key.oneWordValue \ + : (h)->key.string)) + +/* + * Macros to use for clients to use to invoke find and create functions for + * hash tables: + */ + +#undef Tcl_FindHashEntry +#define Tcl_FindHashEntry(tablePtr, key) \ + (*((tablePtr)->findProc))(tablePtr, (const char *)(key)) +#undef Tcl_CreateHashEntry +#define Tcl_CreateHashEntry(tablePtr, key, newPtr) \ + (*((tablePtr)->createProc))(tablePtr, (const char *)(key), newPtr) + +/* + *---------------------------------------------------------------------------- + * Macros that eliminate the overhead of the thread synchronization functions + * when compiling without thread support. + */ + +#ifndef TCL_THREADS +#undef Tcl_MutexLock +#define Tcl_MutexLock(mutexPtr) +#undef Tcl_MutexUnlock +#define Tcl_MutexUnlock(mutexPtr) +#undef Tcl_MutexFinalize +#define Tcl_MutexFinalize(mutexPtr) +#undef Tcl_ConditionNotify +#define Tcl_ConditionNotify(condPtr) +#undef Tcl_ConditionWait +#define Tcl_ConditionWait(condPtr, mutexPtr, timePtr) +#undef Tcl_ConditionFinalize +#define Tcl_ConditionFinalize(condPtr) +#endif /* TCL_THREADS */ + +/* + *---------------------------------------------------------------------------- + * Deprecated Tcl functions: + */ + +#ifndef TCL_NO_DEPRECATED +# undef Tcl_EvalObj +# define Tcl_EvalObj(interp,objPtr) \ + Tcl_EvalObjEx((interp),(objPtr),0) +# undef Tcl_GlobalEvalObj +# define Tcl_GlobalEvalObj(interp,objPtr) \ + Tcl_EvalObjEx((interp),(objPtr),TCL_EVAL_GLOBAL) + +/* + * These function have been renamed. The old names are deprecated, but we + * define these macros for backwards compatibilty. + */ + +# define Tcl_Ckalloc Tcl_Alloc +# define Tcl_Ckfree Tcl_Free +# define Tcl_Ckrealloc Tcl_Realloc +# define Tcl_Return Tcl_SetResult +# define Tcl_TildeSubst Tcl_TranslateFileName +# define panic Tcl_Panic +# define panicVA Tcl_PanicVA +#endif /* !TCL_NO_DEPRECATED */ + +/* + *---------------------------------------------------------------------------- + * Convenience declaration of Tcl_AppInit for backwards compatibility. This + * function is not *implemented* by the tcl library, so the storage class is + * neither DLLEXPORT nor DLLIMPORT. + */ + +extern Tcl_AppInitProc Tcl_AppInit; + +#endif /* RC_INVOKED */ + +/* + * end block for C++ + */ + +#ifdef __cplusplus +} +#endif + +#endif /* _TCL */ + +/* + * Local Variables: + * mode: c + * c-basic-offset: 4 + * fill-column: 78 + * End: + */ ADDED compat/tcl-8.6/generic/tclDecls.h Index: compat/tcl-8.6/generic/tclDecls.h ================================================================== --- /dev/null +++ compat/tcl-8.6/generic/tclDecls.h @@ -0,0 +1,3806 @@ +/* + * tclDecls.h -- + * + * Declarations of functions in the platform independent public Tcl API. + * + * Copyright (c) 1998-1999 by Scriptics Corporation. + * + * See the file "license.terms" for information on usage and redistribution + * of this file, and for a DISCLAIMER OF ALL WARRANTIES. + */ + +#ifndef _TCLDECLS +#define _TCLDECLS + +#undef TCL_STORAGE_CLASS +#ifdef BUILD_tcl +# define TCL_STORAGE_CLASS DLLEXPORT +#else +# ifdef USE_TCL_STUBS +# define TCL_STORAGE_CLASS +# else +# define TCL_STORAGE_CLASS DLLIMPORT +# endif +#endif + +/* + * WARNING: This file is automatically generated by the tools/genStubs.tcl + * script. Any modifications to the function declarations below should be made + * in the generic/tcl.decls script. + */ + +/* !BEGIN!: Do not edit below this line. */ + +/* + * Exported function declarations: + */ + +/* 0 */ +EXTERN int Tcl_PkgProvideEx(Tcl_Interp *interp, + const char *name, const char *version, + const void *clientData); +/* 1 */ +EXTERN CONST84_RETURN char * Tcl_PkgRequireEx(Tcl_Interp *interp, + const char *name, const char *version, + int exact, void *clientDataPtr); +/* 2 */ +EXTERN void Tcl_Panic(const char *format, ...) TCL_FORMAT_PRINTF(1, 2); +/* 3 */ +EXTERN char * Tcl_Alloc(unsigned int size); +/* 4 */ +EXTERN void Tcl_Free(char *ptr); +/* 5 */ +EXTERN char * Tcl_Realloc(char *ptr, unsigned int size); +/* 6 */ +EXTERN char * Tcl_DbCkalloc(unsigned int size, const char *file, + int line); +/* 7 */ +EXTERN void Tcl_DbCkfree(char *ptr, const char *file, int line); +/* 8 */ +EXTERN char * Tcl_DbCkrealloc(char *ptr, unsigned int size, + const char *file, int line); +#if !defined(__WIN32__) && !defined(MAC_OSX_TCL) /* UNIX */ +/* 9 */ +EXTERN void Tcl_CreateFileHandler(int fd, int mask, + Tcl_FileProc *proc, ClientData clientData); +#endif /* UNIX */ +#ifdef MAC_OSX_TCL /* MACOSX */ +/* 9 */ +EXTERN void Tcl_CreateFileHandler(int fd, int mask, + Tcl_FileProc *proc, ClientData clientData); +#endif /* MACOSX */ +#if !defined(__WIN32__) && !defined(MAC_OSX_TCL) /* UNIX */ +/* 10 */ +EXTERN void Tcl_DeleteFileHandler(int fd); +#endif /* UNIX */ +#ifdef MAC_OSX_TCL /* MACOSX */ +/* 10 */ +EXTERN void Tcl_DeleteFileHandler(int fd); +#endif /* MACOSX */ +/* 11 */ +EXTERN void Tcl_SetTimer(const Tcl_Time *timePtr); +/* 12 */ +EXTERN void Tcl_Sleep(int ms); +/* 13 */ +EXTERN int Tcl_WaitForEvent(const Tcl_Time *timePtr); +/* 14 */ +EXTERN int Tcl_AppendAllObjTypes(Tcl_Interp *interp, + Tcl_Obj *objPtr); +/* 15 */ +EXTERN void Tcl_AppendStringsToObj(Tcl_Obj *objPtr, ...); +/* 16 */ +EXTERN void Tcl_AppendToObj(Tcl_Obj *objPtr, const char *bytes, + int length); +/* 17 */ +EXTERN Tcl_Obj * Tcl_ConcatObj(int objc, Tcl_Obj *const objv[]); +/* 18 */ +EXTERN int Tcl_ConvertToType(Tcl_Interp *interp, + Tcl_Obj *objPtr, const Tcl_ObjType *typePtr); +/* 19 */ +EXTERN void Tcl_DbDecrRefCount(Tcl_Obj *objPtr, const char *file, + int line); +/* 20 */ +EXTERN void Tcl_DbIncrRefCount(Tcl_Obj *objPtr, const char *file, + int line); +/* 21 */ +EXTERN int Tcl_DbIsShared(Tcl_Obj *objPtr, const char *file, + int line); +/* 22 */ +EXTERN Tcl_Obj * Tcl_DbNewBooleanObj(int boolValue, const char *file, + int line); +/* 23 */ +EXTERN Tcl_Obj * Tcl_DbNewByteArrayObj(const unsigned char *bytes, + int length, const char *file, int line); +/* 24 */ +EXTERN Tcl_Obj * Tcl_DbNewDoubleObj(double doubleValue, + const char *file, int line); +/* 25 */ +EXTERN Tcl_Obj * Tcl_DbNewListObj(int objc, Tcl_Obj *const *objv, + const char *file, int line); +/* 26 */ +EXTERN Tcl_Obj * Tcl_DbNewLongObj(long longValue, const char *file, + int line); +/* 27 */ +EXTERN Tcl_Obj * Tcl_DbNewObj(const char *file, int line); +/* 28 */ +EXTERN Tcl_Obj * Tcl_DbNewStringObj(const char *bytes, int length, + const char *file, int line); +/* 29 */ +EXTERN Tcl_Obj * Tcl_DuplicateObj(Tcl_Obj *objPtr); +/* 30 */ +EXTERN void TclFreeObj(Tcl_Obj *objPtr); +/* 31 */ +EXTERN int Tcl_GetBoolean(Tcl_Interp *interp, const char *src, + int *boolPtr); +/* 32 */ +EXTERN int Tcl_GetBooleanFromObj(Tcl_Interp *interp, + Tcl_Obj *objPtr, int *boolPtr); +/* 33 */ +EXTERN unsigned char * Tcl_GetByteArrayFromObj(Tcl_Obj *objPtr, + int *lengthPtr); +/* 34 */ +EXTERN int Tcl_GetDouble(Tcl_Interp *interp, const char *src, + double *doublePtr); +/* 35 */ +EXTERN int Tcl_GetDoubleFromObj(Tcl_Interp *interp, + Tcl_Obj *objPtr, double *doublePtr); +/* 36 */ +EXTERN int Tcl_GetIndexFromObj(Tcl_Interp *interp, + Tcl_Obj *objPtr, + CONST84 char *const *tablePtr, + const char *msg, int flags, int *indexPtr); +/* 37 */ +EXTERN int Tcl_GetInt(Tcl_Interp *interp, const char *src, + int *intPtr); +/* 38 */ +EXTERN int Tcl_GetIntFromObj(Tcl_Interp *interp, + Tcl_Obj *objPtr, int *intPtr); +/* 39 */ +EXTERN int Tcl_GetLongFromObj(Tcl_Interp *interp, + Tcl_Obj *objPtr, long *longPtr); +/* 40 */ +EXTERN CONST86 Tcl_ObjType * Tcl_GetObjType(const char *typeName); +/* 41 */ +EXTERN char * Tcl_GetStringFromObj(Tcl_Obj *objPtr, int *lengthPtr); +/* 42 */ +EXTERN void Tcl_InvalidateStringRep(Tcl_Obj *objPtr); +/* 43 */ +EXTERN int Tcl_ListObjAppendList(Tcl_Interp *interp, + Tcl_Obj *listPtr, Tcl_Obj *elemListPtr); +/* 44 */ +EXTERN int Tcl_ListObjAppendElement(Tcl_Interp *interp, + Tcl_Obj *listPtr, Tcl_Obj *objPtr); +/* 45 */ +EXTERN int Tcl_ListObjGetElements(Tcl_Interp *interp, + Tcl_Obj *listPtr, int *objcPtr, + Tcl_Obj ***objvPtr); +/* 46 */ +EXTERN int Tcl_ListObjIndex(Tcl_Interp *interp, + Tcl_Obj *listPtr, int index, + Tcl_Obj **objPtrPtr); +/* 47 */ +EXTERN int Tcl_ListObjLength(Tcl_Interp *interp, + Tcl_Obj *listPtr, int *lengthPtr); +/* 48 */ +EXTERN int Tcl_ListObjReplace(Tcl_Interp *interp, + Tcl_Obj *listPtr, int first, int count, + int objc, Tcl_Obj *const objv[]); +/* 49 */ +EXTERN Tcl_Obj * Tcl_NewBooleanObj(int boolValue); +/* 50 */ +EXTERN Tcl_Obj * Tcl_NewByteArrayObj(const unsigned char *bytes, + int length); +/* 51 */ +EXTERN Tcl_Obj * Tcl_NewDoubleObj(double doubleValue); +/* 52 */ +EXTERN Tcl_Obj * Tcl_NewIntObj(int intValue); +/* 53 */ +EXTERN Tcl_Obj * Tcl_NewListObj(int objc, Tcl_Obj *const objv[]); +/* 54 */ +EXTERN Tcl_Obj * Tcl_NewLongObj(long longValue); +/* 55 */ +EXTERN Tcl_Obj * Tcl_NewObj(void); +/* 56 */ +EXTERN Tcl_Obj * Tcl_NewStringObj(const char *bytes, int length); +/* 57 */ +EXTERN void Tcl_SetBooleanObj(Tcl_Obj *objPtr, int boolValue); +/* 58 */ +EXTERN unsigned char * Tcl_SetByteArrayLength(Tcl_Obj *objPtr, int length); +/* 59 */ +EXTERN void Tcl_SetByteArrayObj(Tcl_Obj *objPtr, + const unsigned char *bytes, int length); +/* 60 */ +EXTERN void Tcl_SetDoubleObj(Tcl_Obj *objPtr, double doubleValue); +/* 61 */ +EXTERN void Tcl_SetIntObj(Tcl_Obj *objPtr, int intValue); +/* 62 */ +EXTERN void Tcl_SetListObj(Tcl_Obj *objPtr, int objc, + Tcl_Obj *const objv[]); +/* 63 */ +EXTERN void Tcl_SetLongObj(Tcl_Obj *objPtr, long longValue); +/* 64 */ +EXTERN void Tcl_SetObjLength(Tcl_Obj *objPtr, int length); +/* 65 */ +EXTERN void Tcl_SetStringObj(Tcl_Obj *objPtr, const char *bytes, + int length); +/* 66 */ +EXTERN void Tcl_AddErrorInfo(Tcl_Interp *interp, + const char *message); +/* 67 */ +EXTERN void Tcl_AddObjErrorInfo(Tcl_Interp *interp, + const char *message, int length); +/* 68 */ +EXTERN void Tcl_AllowExceptions(Tcl_Interp *interp); +/* 69 */ +EXTERN void Tcl_AppendElement(Tcl_Interp *interp, + const char *element); +/* 70 */ +EXTERN void Tcl_AppendResult(Tcl_Interp *interp, ...); +/* 71 */ +EXTERN Tcl_AsyncHandler Tcl_AsyncCreate(Tcl_AsyncProc *proc, + ClientData clientData); +/* 72 */ +EXTERN void Tcl_AsyncDelete(Tcl_AsyncHandler async); +/* 73 */ +EXTERN int Tcl_AsyncInvoke(Tcl_Interp *interp, int code); +/* 74 */ +EXTERN void Tcl_AsyncMark(Tcl_AsyncHandler async); +/* 75 */ +EXTERN int Tcl_AsyncReady(void); +/* 76 */ +EXTERN void Tcl_BackgroundError(Tcl_Interp *interp); +/* 77 */ +EXTERN char Tcl_Backslash(const char *src, int *readPtr); +/* 78 */ +EXTERN int Tcl_BadChannelOption(Tcl_Interp *interp, + const char *optionName, + const char *optionList); +/* 79 */ +EXTERN void Tcl_CallWhenDeleted(Tcl_Interp *interp, + Tcl_InterpDeleteProc *proc, + ClientData clientData); +/* 80 */ +EXTERN void Tcl_CancelIdleCall(Tcl_IdleProc *idleProc, + ClientData clientData); +/* 81 */ +EXTERN int Tcl_Close(Tcl_Interp *interp, Tcl_Channel chan); +/* 82 */ +EXTERN int Tcl_CommandComplete(const char *cmd); +/* 83 */ +EXTERN char * Tcl_Concat(int argc, CONST84 char *const *argv); +/* 84 */ +EXTERN int Tcl_ConvertElement(const char *src, char *dst, + int flags); +/* 85 */ +EXTERN int Tcl_ConvertCountedElement(const char *src, + int length, char *dst, int flags); +/* 86 */ +EXTERN int Tcl_CreateAlias(Tcl_Interp *slave, + const char *slaveCmd, Tcl_Interp *target, + const char *targetCmd, int argc, + CONST84 char *const *argv); +/* 87 */ +EXTERN int Tcl_CreateAliasObj(Tcl_Interp *slave, + const char *slaveCmd, Tcl_Interp *target, + const char *targetCmd, int objc, + Tcl_Obj *const objv[]); +/* 88 */ +EXTERN Tcl_Channel Tcl_CreateChannel(const Tcl_ChannelType *typePtr, + const char *chanName, + ClientData instanceData, int mask); +/* 89 */ +EXTERN void Tcl_CreateChannelHandler(Tcl_Channel chan, int mask, + Tcl_ChannelProc *proc, ClientData clientData); +/* 90 */ +EXTERN void Tcl_CreateCloseHandler(Tcl_Channel chan, + Tcl_CloseProc *proc, ClientData clientData); +/* 91 */ +EXTERN Tcl_Command Tcl_CreateCommand(Tcl_Interp *interp, + const char *cmdName, Tcl_CmdProc *proc, + ClientData clientData, + Tcl_CmdDeleteProc *deleteProc); +/* 92 */ +EXTERN void Tcl_CreateEventSource(Tcl_EventSetupProc *setupProc, + Tcl_EventCheckProc *checkProc, + ClientData clientData); +/* 93 */ +EXTERN void Tcl_CreateExitHandler(Tcl_ExitProc *proc, + ClientData clientData); +/* 94 */ +EXTERN Tcl_Interp * Tcl_CreateInterp(void); +/* 95 */ +EXTERN void Tcl_CreateMathFunc(Tcl_Interp *interp, + const char *name, int numArgs, + Tcl_ValueType *argTypes, Tcl_MathProc *proc, + ClientData clientData); +/* 96 */ +EXTERN Tcl_Command Tcl_CreateObjCommand(Tcl_Interp *interp, + const char *cmdName, Tcl_ObjCmdProc *proc, + ClientData clientData, + Tcl_CmdDeleteProc *deleteProc); +/* 97 */ +EXTERN Tcl_Interp * Tcl_CreateSlave(Tcl_Interp *interp, + const char *slaveName, int isSafe); +/* 98 */ +EXTERN Tcl_TimerToken Tcl_CreateTimerHandler(int milliseconds, + Tcl_TimerProc *proc, ClientData clientData); +/* 99 */ +EXTERN Tcl_Trace Tcl_CreateTrace(Tcl_Interp *interp, int level, + Tcl_CmdTraceProc *proc, + ClientData clientData); +/* 100 */ +EXTERN void Tcl_DeleteAssocData(Tcl_Interp *interp, + const char *name); +/* 101 */ +EXTERN void Tcl_DeleteChannelHandler(Tcl_Channel chan, + Tcl_ChannelProc *proc, ClientData clientData); +/* 102 */ +EXTERN void Tcl_DeleteCloseHandler(Tcl_Channel chan, + Tcl_CloseProc *proc, ClientData clientData); +/* 103 */ +EXTERN int Tcl_DeleteCommand(Tcl_Interp *interp, + const char *cmdName); +/* 104 */ +EXTERN int Tcl_DeleteCommandFromToken(Tcl_Interp *interp, + Tcl_Command command); +/* 105 */ +EXTERN void Tcl_DeleteEvents(Tcl_EventDeleteProc *proc, + ClientData clientData); +/* 106 */ +EXTERN void Tcl_DeleteEventSource(Tcl_EventSetupProc *setupProc, + Tcl_EventCheckProc *checkProc, + ClientData clientData); +/* 107 */ +EXTERN void Tcl_DeleteExitHandler(Tcl_ExitProc *proc, + ClientData clientData); +/* 108 */ +EXTERN void Tcl_DeleteHashEntry(Tcl_HashEntry *entryPtr); +/* 109 */ +EXTERN void Tcl_DeleteHashTable(Tcl_HashTable *tablePtr); +/* 110 */ +EXTERN void Tcl_DeleteInterp(Tcl_Interp *interp); +/* 111 */ +EXTERN void Tcl_DetachPids(int numPids, Tcl_Pid *pidPtr); +/* 112 */ +EXTERN void Tcl_DeleteTimerHandler(Tcl_TimerToken token); +/* 113 */ +EXTERN void Tcl_DeleteTrace(Tcl_Interp *interp, Tcl_Trace trace); +/* 114 */ +EXTERN void Tcl_DontCallWhenDeleted(Tcl_Interp *interp, + Tcl_InterpDeleteProc *proc, + ClientData clientData); +/* 115 */ +EXTERN int Tcl_DoOneEvent(int flags); +/* 116 */ +EXTERN void Tcl_DoWhenIdle(Tcl_IdleProc *proc, + ClientData clientData); +/* 117 */ +EXTERN char * Tcl_DStringAppend(Tcl_DString *dsPtr, + const char *bytes, int length); +/* 118 */ +EXTERN char * Tcl_DStringAppendElement(Tcl_DString *dsPtr, + const char *element); +/* 119 */ +EXTERN void Tcl_DStringEndSublist(Tcl_DString *dsPtr); +/* 120 */ +EXTERN void Tcl_DStringFree(Tcl_DString *dsPtr); +/* 121 */ +EXTERN void Tcl_DStringGetResult(Tcl_Interp *interp, + Tcl_DString *dsPtr); +/* 122 */ +EXTERN void Tcl_DStringInit(Tcl_DString *dsPtr); +/* 123 */ +EXTERN void Tcl_DStringResult(Tcl_Interp *interp, + Tcl_DString *dsPtr); +/* 124 */ +EXTERN void Tcl_DStringSetLength(Tcl_DString *dsPtr, int length); +/* 125 */ +EXTERN void Tcl_DStringStartSublist(Tcl_DString *dsPtr); +/* 126 */ +EXTERN int Tcl_Eof(Tcl_Channel chan); +/* 127 */ +EXTERN CONST84_RETURN char * Tcl_ErrnoId(void); +/* 128 */ +EXTERN CONST84_RETURN char * Tcl_ErrnoMsg(int err); +/* 129 */ +EXTERN int Tcl_Eval(Tcl_Interp *interp, const char *script); +/* 130 */ +EXTERN int Tcl_EvalFile(Tcl_Interp *interp, + const char *fileName); +/* 131 */ +EXTERN int Tcl_EvalObj(Tcl_Interp *interp, Tcl_Obj *objPtr); +/* 132 */ +EXTERN void Tcl_EventuallyFree(ClientData clientData, + Tcl_FreeProc *freeProc); +/* 133 */ +EXTERN void Tcl_Exit(int status); +/* 134 */ +EXTERN int Tcl_ExposeCommand(Tcl_Interp *interp, + const char *hiddenCmdToken, + const char *cmdName); +/* 135 */ +EXTERN int Tcl_ExprBoolean(Tcl_Interp *interp, const char *expr, + int *ptr); +/* 136 */ +EXTERN int Tcl_ExprBooleanObj(Tcl_Interp *interp, + Tcl_Obj *objPtr, int *ptr); +/* 137 */ +EXTERN int Tcl_ExprDouble(Tcl_Interp *interp, const char *expr, + double *ptr); +/* 138 */ +EXTERN int Tcl_ExprDoubleObj(Tcl_Interp *interp, + Tcl_Obj *objPtr, double *ptr); +/* 139 */ +EXTERN int Tcl_ExprLong(Tcl_Interp *interp, const char *expr, + long *ptr); +/* 140 */ +EXTERN int Tcl_ExprLongObj(Tcl_Interp *interp, Tcl_Obj *objPtr, + long *ptr); +/* 141 */ +EXTERN int Tcl_ExprObj(Tcl_Interp *interp, Tcl_Obj *objPtr, + Tcl_Obj **resultPtrPtr); +/* 142 */ +EXTERN int Tcl_ExprString(Tcl_Interp *interp, const char *expr); +/* 143 */ +EXTERN void Tcl_Finalize(void); +/* 144 */ +EXTERN void Tcl_FindExecutable(const char *argv0); +/* 145 */ +EXTERN Tcl_HashEntry * Tcl_FirstHashEntry(Tcl_HashTable *tablePtr, + Tcl_HashSearch *searchPtr); +/* 146 */ +EXTERN int Tcl_Flush(Tcl_Channel chan); +/* 147 */ +EXTERN void Tcl_FreeResult(Tcl_Interp *interp); +/* 148 */ +EXTERN int Tcl_GetAlias(Tcl_Interp *interp, + const char *slaveCmd, + Tcl_Interp **targetInterpPtr, + CONST84 char **targetCmdPtr, int *argcPtr, + CONST84 char ***argvPtr); +/* 149 */ +EXTERN int Tcl_GetAliasObj(Tcl_Interp *interp, + const char *slaveCmd, + Tcl_Interp **targetInterpPtr, + CONST84 char **targetCmdPtr, int *objcPtr, + Tcl_Obj ***objv); +/* 150 */ +EXTERN ClientData Tcl_GetAssocData(Tcl_Interp *interp, + const char *name, + Tcl_InterpDeleteProc **procPtr); +/* 151 */ +EXTERN Tcl_Channel Tcl_GetChannel(Tcl_Interp *interp, + const char *chanName, int *modePtr); +/* 152 */ +EXTERN int Tcl_GetChannelBufferSize(Tcl_Channel chan); +/* 153 */ +EXTERN int Tcl_GetChannelHandle(Tcl_Channel chan, int direction, + ClientData *handlePtr); +/* 154 */ +EXTERN ClientData Tcl_GetChannelInstanceData(Tcl_Channel chan); +/* 155 */ +EXTERN int Tcl_GetChannelMode(Tcl_Channel chan); +/* 156 */ +EXTERN CONST84_RETURN char * Tcl_GetChannelName(Tcl_Channel chan); +/* 157 */ +EXTERN int Tcl_GetChannelOption(Tcl_Interp *interp, + Tcl_Channel chan, const char *optionName, + Tcl_DString *dsPtr); +/* 158 */ +EXTERN CONST86 Tcl_ChannelType * Tcl_GetChannelType(Tcl_Channel chan); +/* 159 */ +EXTERN int Tcl_GetCommandInfo(Tcl_Interp *interp, + const char *cmdName, Tcl_CmdInfo *infoPtr); +/* 160 */ +EXTERN CONST84_RETURN char * Tcl_GetCommandName(Tcl_Interp *interp, + Tcl_Command command); +/* 161 */ +EXTERN int Tcl_GetErrno(void); +/* 162 */ +EXTERN CONST84_RETURN char * Tcl_GetHostName(void); +/* 163 */ +EXTERN int Tcl_GetInterpPath(Tcl_Interp *askInterp, + Tcl_Interp *slaveInterp); +/* 164 */ +EXTERN Tcl_Interp * Tcl_GetMaster(Tcl_Interp *interp); +/* 165 */ +EXTERN const char * Tcl_GetNameOfExecutable(void); +/* 166 */ +EXTERN Tcl_Obj * Tcl_GetObjResult(Tcl_Interp *interp); +#if !defined(__WIN32__) && !defined(MAC_OSX_TCL) /* UNIX */ +/* 167 */ +EXTERN int Tcl_GetOpenFile(Tcl_Interp *interp, + const char *chanID, int forWriting, + int checkUsage, ClientData *filePtr); +#endif /* UNIX */ +#ifdef MAC_OSX_TCL /* MACOSX */ +/* 167 */ +EXTERN int Tcl_GetOpenFile(Tcl_Interp *interp, + const char *chanID, int forWriting, + int checkUsage, ClientData *filePtr); +#endif /* MACOSX */ +/* 168 */ +EXTERN Tcl_PathType Tcl_GetPathType(const char *path); +/* 169 */ +EXTERN int Tcl_Gets(Tcl_Channel chan, Tcl_DString *dsPtr); +/* 170 */ +EXTERN int Tcl_GetsObj(Tcl_Channel chan, Tcl_Obj *objPtr); +/* 171 */ +EXTERN int Tcl_GetServiceMode(void); +/* 172 */ +EXTERN Tcl_Interp * Tcl_GetSlave(Tcl_Interp *interp, + const char *slaveName); +/* 173 */ +EXTERN Tcl_Channel Tcl_GetStdChannel(int type); +/* 174 */ +EXTERN CONST84_RETURN char * Tcl_GetStringResult(Tcl_Interp *interp); +/* 175 */ +EXTERN CONST84_RETURN char * Tcl_GetVar(Tcl_Interp *interp, + const char *varName, int flags); +/* 176 */ +EXTERN CONST84_RETURN char * Tcl_GetVar2(Tcl_Interp *interp, + const char *part1, const char *part2, + int flags); +/* 177 */ +EXTERN int Tcl_GlobalEval(Tcl_Interp *interp, + const char *command); +/* 178 */ +EXTERN int Tcl_GlobalEvalObj(Tcl_Interp *interp, + Tcl_Obj *objPtr); +/* 179 */ +EXTERN int Tcl_HideCommand(Tcl_Interp *interp, + const char *cmdName, + const char *hiddenCmdToken); +/* 180 */ +EXTERN int Tcl_Init(Tcl_Interp *interp); +/* 181 */ +EXTERN void Tcl_InitHashTable(Tcl_HashTable *tablePtr, + int keyType); +/* 182 */ +EXTERN int Tcl_InputBlocked(Tcl_Channel chan); +/* 183 */ +EXTERN int Tcl_InputBuffered(Tcl_Channel chan); +/* 184 */ +EXTERN int Tcl_InterpDeleted(Tcl_Interp *interp); +/* 185 */ +EXTERN int Tcl_IsSafe(Tcl_Interp *interp); +/* 186 */ +EXTERN char * Tcl_JoinPath(int argc, CONST84 char *const *argv, + Tcl_DString *resultPtr); +/* 187 */ +EXTERN int Tcl_LinkVar(Tcl_Interp *interp, const char *varName, + char *addr, int type); +/* Slot 188 is reserved */ +/* 189 */ +EXTERN Tcl_Channel Tcl_MakeFileChannel(ClientData handle, int mode); +/* 190 */ +EXTERN int Tcl_MakeSafe(Tcl_Interp *interp); +/* 191 */ +EXTERN Tcl_Channel Tcl_MakeTcpClientChannel(ClientData tcpSocket); +/* 192 */ +EXTERN char * Tcl_Merge(int argc, CONST84 char *const *argv); +/* 193 */ +EXTERN Tcl_HashEntry * Tcl_NextHashEntry(Tcl_HashSearch *searchPtr); +/* 194 */ +EXTERN void Tcl_NotifyChannel(Tcl_Channel channel, int mask); +/* 195 */ +EXTERN Tcl_Obj * Tcl_ObjGetVar2(Tcl_Interp *interp, Tcl_Obj *part1Ptr, + Tcl_Obj *part2Ptr, int flags); +/* 196 */ +EXTERN Tcl_Obj * Tcl_ObjSetVar2(Tcl_Interp *interp, Tcl_Obj *part1Ptr, + Tcl_Obj *part2Ptr, Tcl_Obj *newValuePtr, + int flags); +/* 197 */ +EXTERN Tcl_Channel Tcl_OpenCommandChannel(Tcl_Interp *interp, int argc, + CONST84 char **argv, int flags); +/* 198 */ +EXTERN Tcl_Channel Tcl_OpenFileChannel(Tcl_Interp *interp, + const char *fileName, const char *modeString, + int permissions); +/* 199 */ +EXTERN Tcl_Channel Tcl_OpenTcpClient(Tcl_Interp *interp, int port, + const char *address, const char *myaddr, + int myport, int async); +/* 200 */ +EXTERN Tcl_Channel Tcl_OpenTcpServer(Tcl_Interp *interp, int port, + const char *host, + Tcl_TcpAcceptProc *acceptProc, + ClientData callbackData); +/* 201 */ +EXTERN void Tcl_Preserve(ClientData data); +/* 202 */ +EXTERN void Tcl_PrintDouble(Tcl_Interp *interp, double value, + char *dst); +/* 203 */ +EXTERN int Tcl_PutEnv(const char *assignment); +/* 204 */ +EXTERN CONST84_RETURN char * Tcl_PosixError(Tcl_Interp *interp); +/* 205 */ +EXTERN void Tcl_QueueEvent(Tcl_Event *evPtr, + Tcl_QueuePosition position); +/* 206 */ +EXTERN int Tcl_Read(Tcl_Channel chan, char *bufPtr, int toRead); +/* 207 */ +EXTERN void Tcl_ReapDetachedProcs(void); +/* 208 */ +EXTERN int Tcl_RecordAndEval(Tcl_Interp *interp, + const char *cmd, int flags); +/* 209 */ +EXTERN int Tcl_RecordAndEvalObj(Tcl_Interp *interp, + Tcl_Obj *cmdPtr, int flags); +/* 210 */ +EXTERN void Tcl_RegisterChannel(Tcl_Interp *interp, + Tcl_Channel chan); +/* 211 */ +EXTERN void Tcl_RegisterObjType(const Tcl_ObjType *typePtr); +/* 212 */ +EXTERN Tcl_RegExp Tcl_RegExpCompile(Tcl_Interp *interp, + const char *pattern); +/* 213 */ +EXTERN int Tcl_RegExpExec(Tcl_Interp *interp, Tcl_RegExp regexp, + const char *text, const char *start); +/* 214 */ +EXTERN int Tcl_RegExpMatch(Tcl_Interp *interp, const char *text, + const char *pattern); +/* 215 */ +EXTERN void Tcl_RegExpRange(Tcl_RegExp regexp, int index, + CONST84 char **startPtr, + CONST84 char **endPtr); +/* 216 */ +EXTERN void Tcl_Release(ClientData clientData); +/* 217 */ +EXTERN void Tcl_ResetResult(Tcl_Interp *interp); +/* 218 */ +EXTERN int Tcl_ScanElement(const char *src, int *flagPtr); +/* 219 */ +EXTERN int Tcl_ScanCountedElement(const char *src, int length, + int *flagPtr); +/* 220 */ +EXTERN int Tcl_SeekOld(Tcl_Channel chan, int offset, int mode); +/* 221 */ +EXTERN int Tcl_ServiceAll(void); +/* 222 */ +EXTERN int Tcl_ServiceEvent(int flags); +/* 223 */ +EXTERN void Tcl_SetAssocData(Tcl_Interp *interp, + const char *name, Tcl_InterpDeleteProc *proc, + ClientData clientData); +/* 224 */ +EXTERN void Tcl_SetChannelBufferSize(Tcl_Channel chan, int sz); +/* 225 */ +EXTERN int Tcl_SetChannelOption(Tcl_Interp *interp, + Tcl_Channel chan, const char *optionName, + const char *newValue); +/* 226 */ +EXTERN int Tcl_SetCommandInfo(Tcl_Interp *interp, + const char *cmdName, + const Tcl_CmdInfo *infoPtr); +/* 227 */ +EXTERN void Tcl_SetErrno(int err); +/* 228 */ +EXTERN void Tcl_SetErrorCode(Tcl_Interp *interp, ...); +/* 229 */ +EXTERN void Tcl_SetMaxBlockTime(const Tcl_Time *timePtr); +/* 230 */ +EXTERN void Tcl_SetPanicProc(Tcl_PanicProc *panicProc); +/* 231 */ +EXTERN int Tcl_SetRecursionLimit(Tcl_Interp *interp, int depth); +/* 232 */ +EXTERN void Tcl_SetResult(Tcl_Interp *interp, char *result, + Tcl_FreeProc *freeProc); +/* 233 */ +EXTERN int Tcl_SetServiceMode(int mode); +/* 234 */ +EXTERN void Tcl_SetObjErrorCode(Tcl_Interp *interp, + Tcl_Obj *errorObjPtr); +/* 235 */ +EXTERN void Tcl_SetObjResult(Tcl_Interp *interp, + Tcl_Obj *resultObjPtr); +/* 236 */ +EXTERN void Tcl_SetStdChannel(Tcl_Channel channel, int type); +/* 237 */ +EXTERN CONST84_RETURN char * Tcl_SetVar(Tcl_Interp *interp, + const char *varName, const char *newValue, + int flags); +/* 238 */ +EXTERN CONST84_RETURN char * Tcl_SetVar2(Tcl_Interp *interp, + const char *part1, const char *part2, + const char *newValue, int flags); +/* 239 */ +EXTERN CONST84_RETURN char * Tcl_SignalId(int sig); +/* 240 */ +EXTERN CONST84_RETURN char * Tcl_SignalMsg(int sig); +/* 241 */ +EXTERN void Tcl_SourceRCFile(Tcl_Interp *interp); +/* 242 */ +EXTERN int Tcl_SplitList(Tcl_Interp *interp, + const char *listStr, int *argcPtr, + CONST84 char ***argvPtr); +/* 243 */ +EXTERN void Tcl_SplitPath(const char *path, int *argcPtr, + CONST84 char ***argvPtr); +/* 244 */ +EXTERN void Tcl_StaticPackage(Tcl_Interp *interp, + const char *pkgName, + Tcl_PackageInitProc *initProc, + Tcl_PackageInitProc *safeInitProc); +/* 245 */ +EXTERN int Tcl_StringMatch(const char *str, const char *pattern); +/* 246 */ +EXTERN int Tcl_TellOld(Tcl_Channel chan); +/* 247 */ +EXTERN int Tcl_TraceVar(Tcl_Interp *interp, const char *varName, + int flags, Tcl_VarTraceProc *proc, + ClientData clientData); +/* 248 */ +EXTERN int Tcl_TraceVar2(Tcl_Interp *interp, const char *part1, + const char *part2, int flags, + Tcl_VarTraceProc *proc, + ClientData clientData); +/* 249 */ +EXTERN char * Tcl_TranslateFileName(Tcl_Interp *interp, + const char *name, Tcl_DString *bufferPtr); +/* 250 */ +EXTERN int Tcl_Ungets(Tcl_Channel chan, const char *str, + int len, int atHead); +/* 251 */ +EXTERN void Tcl_UnlinkVar(Tcl_Interp *interp, + const char *varName); +/* 252 */ +EXTERN int Tcl_UnregisterChannel(Tcl_Interp *interp, + Tcl_Channel chan); +/* 253 */ +EXTERN int Tcl_UnsetVar(Tcl_Interp *interp, const char *varName, + int flags); +/* 254 */ +EXTERN int Tcl_UnsetVar2(Tcl_Interp *interp, const char *part1, + const char *part2, int flags); +/* 255 */ +EXTERN void Tcl_UntraceVar(Tcl_Interp *interp, + const char *varName, int flags, + Tcl_VarTraceProc *proc, + ClientData clientData); +/* 256 */ +EXTERN void Tcl_UntraceVar2(Tcl_Interp *interp, + const char *part1, const char *part2, + int flags, Tcl_VarTraceProc *proc, + ClientData clientData); +/* 257 */ +EXTERN void Tcl_UpdateLinkedVar(Tcl_Interp *interp, + const char *varName); +/* 258 */ +EXTERN int Tcl_UpVar(Tcl_Interp *interp, const char *frameName, + const char *varName, const char *localName, + int flags); +/* 259 */ +EXTERN int Tcl_UpVar2(Tcl_Interp *interp, const char *frameName, + const char *part1, const char *part2, + const char *localName, int flags); +/* 260 */ +EXTERN int Tcl_VarEval(Tcl_Interp *interp, ...); +/* 261 */ +EXTERN ClientData Tcl_VarTraceInfo(Tcl_Interp *interp, + const char *varName, int flags, + Tcl_VarTraceProc *procPtr, + ClientData prevClientData); +/* 262 */ +EXTERN ClientData Tcl_VarTraceInfo2(Tcl_Interp *interp, + const char *part1, const char *part2, + int flags, Tcl_VarTraceProc *procPtr, + ClientData prevClientData); +/* 263 */ +EXTERN int Tcl_Write(Tcl_Channel chan, const char *s, int slen); +/* 264 */ +EXTERN void Tcl_WrongNumArgs(Tcl_Interp *interp, int objc, + Tcl_Obj *const objv[], const char *message); +/* 265 */ +EXTERN int Tcl_DumpActiveMemory(const char *fileName); +/* 266 */ +EXTERN void Tcl_ValidateAllMemory(const char *file, int line); +/* 267 */ +EXTERN void Tcl_AppendResultVA(Tcl_Interp *interp, + va_list argList); +/* 268 */ +EXTERN void Tcl_AppendStringsToObjVA(Tcl_Obj *objPtr, + va_list argList); +/* 269 */ +EXTERN char * Tcl_HashStats(Tcl_HashTable *tablePtr); +/* 270 */ +EXTERN CONST84_RETURN char * Tcl_ParseVar(Tcl_Interp *interp, + const char *start, CONST84 char **termPtr); +/* 271 */ +EXTERN CONST84_RETURN char * Tcl_PkgPresent(Tcl_Interp *interp, + const char *name, const char *version, + int exact); +/* 272 */ +EXTERN CONST84_RETURN char * Tcl_PkgPresentEx(Tcl_Interp *interp, + const char *name, const char *version, + int exact, void *clientDataPtr); +/* 273 */ +EXTERN int Tcl_PkgProvide(Tcl_Interp *interp, const char *name, + const char *version); +/* 274 */ +EXTERN CONST84_RETURN char * Tcl_PkgRequire(Tcl_Interp *interp, + const char *name, const char *version, + int exact); +/* 275 */ +EXTERN void Tcl_SetErrorCodeVA(Tcl_Interp *interp, + va_list argList); +/* 276 */ +EXTERN int Tcl_VarEvalVA(Tcl_Interp *interp, va_list argList); +/* 277 */ +EXTERN Tcl_Pid Tcl_WaitPid(Tcl_Pid pid, int *statPtr, int options); +/* 278 */ +EXTERN void Tcl_PanicVA(const char *format, va_list argList); +/* 279 */ +EXTERN void Tcl_GetVersion(int *major, int *minor, + int *patchLevel, int *type); +/* 280 */ +EXTERN void Tcl_InitMemory(Tcl_Interp *interp); +/* 281 */ +EXTERN Tcl_Channel Tcl_StackChannel(Tcl_Interp *interp, + const Tcl_ChannelType *typePtr, + ClientData instanceData, int mask, + Tcl_Channel prevChan); +/* 282 */ +EXTERN int Tcl_UnstackChannel(Tcl_Interp *interp, + Tcl_Channel chan); +/* 283 */ +EXTERN Tcl_Channel Tcl_GetStackedChannel(Tcl_Channel chan); +/* 284 */ +EXTERN void Tcl_SetMainLoop(Tcl_MainLoopProc *proc); +/* Slot 285 is reserved */ +/* 286 */ +EXTERN void Tcl_AppendObjToObj(Tcl_Obj *objPtr, + Tcl_Obj *appendObjPtr); +/* 287 */ +EXTERN Tcl_Encoding Tcl_CreateEncoding(const Tcl_EncodingType *typePtr); +/* 288 */ +EXTERN void Tcl_CreateThreadExitHandler(Tcl_ExitProc *proc, + ClientData clientData); +/* 289 */ +EXTERN void Tcl_DeleteThreadExitHandler(Tcl_ExitProc *proc, + ClientData clientData); +/* 290 */ +EXTERN void Tcl_DiscardResult(Tcl_SavedResult *statePtr); +/* 291 */ +EXTERN int Tcl_EvalEx(Tcl_Interp *interp, const char *script, + int numBytes, int flags); +/* 292 */ +EXTERN int Tcl_EvalObjv(Tcl_Interp *interp, int objc, + Tcl_Obj *const objv[], int flags); +/* 293 */ +EXTERN int Tcl_EvalObjEx(Tcl_Interp *interp, Tcl_Obj *objPtr, + int flags); +/* 294 */ +EXTERN void Tcl_ExitThread(int status); +/* 295 */ +EXTERN int Tcl_ExternalToUtf(Tcl_Interp *interp, + Tcl_Encoding encoding, const char *src, + int srcLen, int flags, + Tcl_EncodingState *statePtr, char *dst, + int dstLen, int *srcReadPtr, + int *dstWrotePtr, int *dstCharsPtr); +/* 296 */ +EXTERN char * Tcl_ExternalToUtfDString(Tcl_Encoding encoding, + const char *src, int srcLen, + Tcl_DString *dsPtr); +/* 297 */ +EXTERN void Tcl_FinalizeThread(void); +/* 298 */ +EXTERN void Tcl_FinalizeNotifier(ClientData clientData); +/* 299 */ +EXTERN void Tcl_FreeEncoding(Tcl_Encoding encoding); +/* 300 */ +EXTERN Tcl_ThreadId Tcl_GetCurrentThread(void); +/* 301 */ +EXTERN Tcl_Encoding Tcl_GetEncoding(Tcl_Interp *interp, const char *name); +/* 302 */ +EXTERN CONST84_RETURN char * Tcl_GetEncodingName(Tcl_Encoding encoding); +/* 303 */ +EXTERN void Tcl_GetEncodingNames(Tcl_Interp *interp); +/* 304 */ +EXTERN int Tcl_GetIndexFromObjStruct(Tcl_Interp *interp, + Tcl_Obj *objPtr, const void *tablePtr, + int offset, const char *msg, int flags, + int *indexPtr); +/* 305 */ +EXTERN void * Tcl_GetThreadData(Tcl_ThreadDataKey *keyPtr, + int size); +/* 306 */ +EXTERN Tcl_Obj * Tcl_GetVar2Ex(Tcl_Interp *interp, const char *part1, + const char *part2, int flags); +/* 307 */ +EXTERN ClientData Tcl_InitNotifier(void); +/* 308 */ +EXTERN void Tcl_MutexLock(Tcl_Mutex *mutexPtr); +/* 309 */ +EXTERN void Tcl_MutexUnlock(Tcl_Mutex *mutexPtr); +/* 310 */ +EXTERN void Tcl_ConditionNotify(Tcl_Condition *condPtr); +/* 311 */ +EXTERN void Tcl_ConditionWait(Tcl_Condition *condPtr, + Tcl_Mutex *mutexPtr, const Tcl_Time *timePtr); +/* 312 */ +EXTERN int Tcl_NumUtfChars(const char *src, int length); +/* 313 */ +EXTERN int Tcl_ReadChars(Tcl_Channel channel, Tcl_Obj *objPtr, + int charsToRead, int appendFlag); +/* 314 */ +EXTERN void Tcl_RestoreResult(Tcl_Interp *interp, + Tcl_SavedResult *statePtr); +/* 315 */ +EXTERN void Tcl_SaveResult(Tcl_Interp *interp, + Tcl_SavedResult *statePtr); +/* 316 */ +EXTERN int Tcl_SetSystemEncoding(Tcl_Interp *interp, + const char *name); +/* 317 */ +EXTERN Tcl_Obj * Tcl_SetVar2Ex(Tcl_Interp *interp, const char *part1, + const char *part2, Tcl_Obj *newValuePtr, + int flags); +/* 318 */ +EXTERN void Tcl_ThreadAlert(Tcl_ThreadId threadId); +/* 319 */ +EXTERN void Tcl_ThreadQueueEvent(Tcl_ThreadId threadId, + Tcl_Event *evPtr, Tcl_QueuePosition position); +/* 320 */ +EXTERN Tcl_UniChar Tcl_UniCharAtIndex(const char *src, int index); +/* 321 */ +EXTERN Tcl_UniChar Tcl_UniCharToLower(int ch); +/* 322 */ +EXTERN Tcl_UniChar Tcl_UniCharToTitle(int ch); +/* 323 */ +EXTERN Tcl_UniChar Tcl_UniCharToUpper(int ch); +/* 324 */ +EXTERN int Tcl_UniCharToUtf(int ch, char *buf); +/* 325 */ +EXTERN CONST84_RETURN char * Tcl_UtfAtIndex(const char *src, int index); +/* 326 */ +EXTERN int Tcl_UtfCharComplete(const char *src, int length); +/* 327 */ +EXTERN int Tcl_UtfBackslash(const char *src, int *readPtr, + char *dst); +/* 328 */ +EXTERN CONST84_RETURN char * Tcl_UtfFindFirst(const char *src, int ch); +/* 329 */ +EXTERN CONST84_RETURN char * Tcl_UtfFindLast(const char *src, int ch); +/* 330 */ +EXTERN CONST84_RETURN char * Tcl_UtfNext(const char *src); +/* 331 */ +EXTERN CONST84_RETURN char * Tcl_UtfPrev(const char *src, const char *start); +/* 332 */ +EXTERN int Tcl_UtfToExternal(Tcl_Interp *interp, + Tcl_Encoding encoding, const char *src, + int srcLen, int flags, + Tcl_EncodingState *statePtr, char *dst, + int dstLen, int *srcReadPtr, + int *dstWrotePtr, int *dstCharsPtr); +/* 333 */ +EXTERN char * Tcl_UtfToExternalDString(Tcl_Encoding encoding, + const char *src, int srcLen, + Tcl_DString *dsPtr); +/* 334 */ +EXTERN int Tcl_UtfToLower(char *src); +/* 335 */ +EXTERN int Tcl_UtfToTitle(char *src); +/* 336 */ +EXTERN int Tcl_UtfToUniChar(const char *src, Tcl_UniChar *chPtr); +/* 337 */ +EXTERN int Tcl_UtfToUpper(char *src); +/* 338 */ +EXTERN int Tcl_WriteChars(Tcl_Channel chan, const char *src, + int srcLen); +/* 339 */ +EXTERN int Tcl_WriteObj(Tcl_Channel chan, Tcl_Obj *objPtr); +/* 340 */ +EXTERN char * Tcl_GetString(Tcl_Obj *objPtr); +/* 341 */ +EXTERN CONST84_RETURN char * Tcl_GetDefaultEncodingDir(void); +/* 342 */ +EXTERN void Tcl_SetDefaultEncodingDir(const char *path); +/* 343 */ +EXTERN void Tcl_AlertNotifier(ClientData clientData); +/* 344 */ +EXTERN void Tcl_ServiceModeHook(int mode); +/* 345 */ +EXTERN int Tcl_UniCharIsAlnum(int ch); +/* 346 */ +EXTERN int Tcl_UniCharIsAlpha(int ch); +/* 347 */ +EXTERN int Tcl_UniCharIsDigit(int ch); +/* 348 */ +EXTERN int Tcl_UniCharIsLower(int ch); +/* 349 */ +EXTERN int Tcl_UniCharIsSpace(int ch); +/* 350 */ +EXTERN int Tcl_UniCharIsUpper(int ch); +/* 351 */ +EXTERN int Tcl_UniCharIsWordChar(int ch); +/* 352 */ +EXTERN int Tcl_UniCharLen(const Tcl_UniChar *uniStr); +/* 353 */ +EXTERN int Tcl_UniCharNcmp(const Tcl_UniChar *ucs, + const Tcl_UniChar *uct, + unsigned long numChars); +/* 354 */ +EXTERN char * Tcl_UniCharToUtfDString(const Tcl_UniChar *uniStr, + int uniLength, Tcl_DString *dsPtr); +/* 355 */ +EXTERN Tcl_UniChar * Tcl_UtfToUniCharDString(const char *src, int length, + Tcl_DString *dsPtr); +/* 356 */ +EXTERN Tcl_RegExp Tcl_GetRegExpFromObj(Tcl_Interp *interp, + Tcl_Obj *patObj, int flags); +/* 357 */ +EXTERN Tcl_Obj * Tcl_EvalTokens(Tcl_Interp *interp, + Tcl_Token *tokenPtr, int count); +/* 358 */ +EXTERN void Tcl_FreeParse(Tcl_Parse *parsePtr); +/* 359 */ +EXTERN void Tcl_LogCommandInfo(Tcl_Interp *interp, + const char *script, const char *command, + int length); +/* 360 */ +EXTERN int Tcl_ParseBraces(Tcl_Interp *interp, + const char *start, int numBytes, + Tcl_Parse *parsePtr, int append, + CONST84 char **termPtr); +/* 361 */ +EXTERN int Tcl_ParseCommand(Tcl_Interp *interp, + const char *start, int numBytes, int nested, + Tcl_Parse *parsePtr); +/* 362 */ +EXTERN int Tcl_ParseExpr(Tcl_Interp *interp, const char *start, + int numBytes, Tcl_Parse *parsePtr); +/* 363 */ +EXTERN int Tcl_ParseQuotedString(Tcl_Interp *interp, + const char *start, int numBytes, + Tcl_Parse *parsePtr, int append, + CONST84 char **termPtr); +/* 364 */ +EXTERN int Tcl_ParseVarName(Tcl_Interp *interp, + const char *start, int numBytes, + Tcl_Parse *parsePtr, int append); +/* 365 */ +EXTERN char * Tcl_GetCwd(Tcl_Interp *interp, Tcl_DString *cwdPtr); +/* 366 */ +EXTERN int Tcl_Chdir(const char *dirName); +/* 367 */ +EXTERN int Tcl_Access(const char *path, int mode); +/* 368 */ +EXTERN int Tcl_Stat(const char *path, struct stat *bufPtr); +/* 369 */ +EXTERN int Tcl_UtfNcmp(const char *s1, const char *s2, + unsigned long n); +/* 370 */ +EXTERN int Tcl_UtfNcasecmp(const char *s1, const char *s2, + unsigned long n); +/* 371 */ +EXTERN int Tcl_StringCaseMatch(const char *str, + const char *pattern, int nocase); +/* 372 */ +EXTERN int Tcl_UniCharIsControl(int ch); +/* 373 */ +EXTERN int Tcl_UniCharIsGraph(int ch); +/* 374 */ +EXTERN int Tcl_UniCharIsPrint(int ch); +/* 375 */ +EXTERN int Tcl_UniCharIsPunct(int ch); +/* 376 */ +EXTERN int Tcl_RegExpExecObj(Tcl_Interp *interp, + Tcl_RegExp regexp, Tcl_Obj *textObj, + int offset, int nmatches, int flags); +/* 377 */ +EXTERN void Tcl_RegExpGetInfo(Tcl_RegExp regexp, + Tcl_RegExpInfo *infoPtr); +/* 378 */ +EXTERN Tcl_Obj * Tcl_NewUnicodeObj(const Tcl_UniChar *unicode, + int numChars); +/* 379 */ +EXTERN void Tcl_SetUnicodeObj(Tcl_Obj *objPtr, + const Tcl_UniChar *unicode, int numChars); +/* 380 */ +EXTERN int Tcl_GetCharLength(Tcl_Obj *objPtr); +/* 381 */ +EXTERN Tcl_UniChar Tcl_GetUniChar(Tcl_Obj *objPtr, int index); +/* 382 */ +EXTERN Tcl_UniChar * Tcl_GetUnicode(Tcl_Obj *objPtr); +/* 383 */ +EXTERN Tcl_Obj * Tcl_GetRange(Tcl_Obj *objPtr, int first, int last); +/* 384 */ +EXTERN void Tcl_AppendUnicodeToObj(Tcl_Obj *objPtr, + const Tcl_UniChar *unicode, int length); +/* 385 */ +EXTERN int Tcl_RegExpMatchObj(Tcl_Interp *interp, + Tcl_Obj *textObj, Tcl_Obj *patternObj); +/* 386 */ +EXTERN void Tcl_SetNotifier(Tcl_NotifierProcs *notifierProcPtr); +/* 387 */ +EXTERN Tcl_Mutex * Tcl_GetAllocMutex(void); +/* 388 */ +EXTERN int Tcl_GetChannelNames(Tcl_Interp *interp); +/* 389 */ +EXTERN int Tcl_GetChannelNamesEx(Tcl_Interp *interp, + const char *pattern); +/* 390 */ +EXTERN int Tcl_ProcObjCmd(ClientData clientData, + Tcl_Interp *interp, int objc, + Tcl_Obj *const objv[]); +/* 391 */ +EXTERN void Tcl_ConditionFinalize(Tcl_Condition *condPtr); +/* 392 */ +EXTERN void Tcl_MutexFinalize(Tcl_Mutex *mutex); +/* 393 */ +EXTERN int Tcl_CreateThread(Tcl_ThreadId *idPtr, + Tcl_ThreadCreateProc *proc, + ClientData clientData, int stackSize, + int flags); +/* 394 */ +EXTERN int Tcl_ReadRaw(Tcl_Channel chan, char *dst, + int bytesToRead); +/* 395 */ +EXTERN int Tcl_WriteRaw(Tcl_Channel chan, const char *src, + int srcLen); +/* 396 */ +EXTERN Tcl_Channel Tcl_GetTopChannel(Tcl_Channel chan); +/* 397 */ +EXTERN int Tcl_ChannelBuffered(Tcl_Channel chan); +/* 398 */ +EXTERN CONST84_RETURN char * Tcl_ChannelName( + const Tcl_ChannelType *chanTypePtr); +/* 399 */ +EXTERN Tcl_ChannelTypeVersion Tcl_ChannelVersion( + const Tcl_ChannelType *chanTypePtr); +/* 400 */ +EXTERN Tcl_DriverBlockModeProc * Tcl_ChannelBlockModeProc( + const Tcl_ChannelType *chanTypePtr); +/* 401 */ +EXTERN Tcl_DriverCloseProc * Tcl_ChannelCloseProc( + const Tcl_ChannelType *chanTypePtr); +/* 402 */ +EXTERN Tcl_DriverClose2Proc * Tcl_ChannelClose2Proc( + const Tcl_ChannelType *chanTypePtr); +/* 403 */ +EXTERN Tcl_DriverInputProc * Tcl_ChannelInputProc( + const Tcl_ChannelType *chanTypePtr); +/* 404 */ +EXTERN Tcl_DriverOutputProc * Tcl_ChannelOutputProc( + const Tcl_ChannelType *chanTypePtr); +/* 405 */ +EXTERN Tcl_DriverSeekProc * Tcl_ChannelSeekProc( + const Tcl_ChannelType *chanTypePtr); +/* 406 */ +EXTERN Tcl_DriverSetOptionProc * Tcl_ChannelSetOptionProc( + const Tcl_ChannelType *chanTypePtr); +/* 407 */ +EXTERN Tcl_DriverGetOptionProc * Tcl_ChannelGetOptionProc( + const Tcl_ChannelType *chanTypePtr); +/* 408 */ +EXTERN Tcl_DriverWatchProc * Tcl_ChannelWatchProc( + const Tcl_ChannelType *chanTypePtr); +/* 409 */ +EXTERN Tcl_DriverGetHandleProc * Tcl_ChannelGetHandleProc( + const Tcl_ChannelType *chanTypePtr); +/* 410 */ +EXTERN Tcl_DriverFlushProc * Tcl_ChannelFlushProc( + const Tcl_ChannelType *chanTypePtr); +/* 411 */ +EXTERN Tcl_DriverHandlerProc * Tcl_ChannelHandlerProc( + const Tcl_ChannelType *chanTypePtr); +/* 412 */ +EXTERN int Tcl_JoinThread(Tcl_ThreadId threadId, int *result); +/* 413 */ +EXTERN int Tcl_IsChannelShared(Tcl_Channel channel); +/* 414 */ +EXTERN int Tcl_IsChannelRegistered(Tcl_Interp *interp, + Tcl_Channel channel); +/* 415 */ +EXTERN void Tcl_CutChannel(Tcl_Channel channel); +/* 416 */ +EXTERN void Tcl_SpliceChannel(Tcl_Channel channel); +/* 417 */ +EXTERN void Tcl_ClearChannelHandlers(Tcl_Channel channel); +/* 418 */ +EXTERN int Tcl_IsChannelExisting(const char *channelName); +/* 419 */ +EXTERN int Tcl_UniCharNcasecmp(const Tcl_UniChar *ucs, + const Tcl_UniChar *uct, + unsigned long numChars); +/* 420 */ +EXTERN int Tcl_UniCharCaseMatch(const Tcl_UniChar *uniStr, + const Tcl_UniChar *uniPattern, int nocase); +/* 421 */ +EXTERN Tcl_HashEntry * Tcl_FindHashEntry(Tcl_HashTable *tablePtr, + const void *key); +/* 422 */ +EXTERN Tcl_HashEntry * Tcl_CreateHashEntry(Tcl_HashTable *tablePtr, + const void *key, int *newPtr); +/* 423 */ +EXTERN void Tcl_InitCustomHashTable(Tcl_HashTable *tablePtr, + int keyType, const Tcl_HashKeyType *typePtr); +/* 424 */ +EXTERN void Tcl_InitObjHashTable(Tcl_HashTable *tablePtr); +/* 425 */ +EXTERN ClientData Tcl_CommandTraceInfo(Tcl_Interp *interp, + const char *varName, int flags, + Tcl_CommandTraceProc *procPtr, + ClientData prevClientData); +/* 426 */ +EXTERN int Tcl_TraceCommand(Tcl_Interp *interp, + const char *varName, int flags, + Tcl_CommandTraceProc *proc, + ClientData clientData); +/* 427 */ +EXTERN void Tcl_UntraceCommand(Tcl_Interp *interp, + const char *varName, int flags, + Tcl_CommandTraceProc *proc, + ClientData clientData); +/* 428 */ +EXTERN char * Tcl_AttemptAlloc(unsigned int size); +/* 429 */ +EXTERN char * Tcl_AttemptDbCkalloc(unsigned int size, + const char *file, int line); +/* 430 */ +EXTERN char * Tcl_AttemptRealloc(char *ptr, unsigned int size); +/* 431 */ +EXTERN char * Tcl_AttemptDbCkrealloc(char *ptr, unsigned int size, + const char *file, int line); +/* 432 */ +EXTERN int Tcl_AttemptSetObjLength(Tcl_Obj *objPtr, int length); +/* 433 */ +EXTERN Tcl_ThreadId Tcl_GetChannelThread(Tcl_Channel channel); +/* 434 */ +EXTERN Tcl_UniChar * Tcl_GetUnicodeFromObj(Tcl_Obj *objPtr, + int *lengthPtr); +/* 435 */ +EXTERN int Tcl_GetMathFuncInfo(Tcl_Interp *interp, + const char *name, int *numArgsPtr, + Tcl_ValueType **argTypesPtr, + Tcl_MathProc **procPtr, + ClientData *clientDataPtr); +/* 436 */ +EXTERN Tcl_Obj * Tcl_ListMathFuncs(Tcl_Interp *interp, + const char *pattern); +/* 437 */ +EXTERN Tcl_Obj * Tcl_SubstObj(Tcl_Interp *interp, Tcl_Obj *objPtr, + int flags); +/* 438 */ +EXTERN int Tcl_DetachChannel(Tcl_Interp *interp, + Tcl_Channel channel); +/* 439 */ +EXTERN int Tcl_IsStandardChannel(Tcl_Channel channel); +/* 440 */ +EXTERN int Tcl_FSCopyFile(Tcl_Obj *srcPathPtr, + Tcl_Obj *destPathPtr); +/* 441 */ +EXTERN int Tcl_FSCopyDirectory(Tcl_Obj *srcPathPtr, + Tcl_Obj *destPathPtr, Tcl_Obj **errorPtr); +/* 442 */ +EXTERN int Tcl_FSCreateDirectory(Tcl_Obj *pathPtr); +/* 443 */ +EXTERN int Tcl_FSDeleteFile(Tcl_Obj *pathPtr); +/* 444 */ +EXTERN int Tcl_FSLoadFile(Tcl_Interp *interp, Tcl_Obj *pathPtr, + const char *sym1, const char *sym2, + Tcl_PackageInitProc **proc1Ptr, + Tcl_PackageInitProc **proc2Ptr, + Tcl_LoadHandle *handlePtr, + Tcl_FSUnloadFileProc **unloadProcPtr); +/* 445 */ +EXTERN int Tcl_FSMatchInDirectory(Tcl_Interp *interp, + Tcl_Obj *result, Tcl_Obj *pathPtr, + const char *pattern, Tcl_GlobTypeData *types); +/* 446 */ +EXTERN Tcl_Obj * Tcl_FSLink(Tcl_Obj *pathPtr, Tcl_Obj *toPtr, + int linkAction); +/* 447 */ +EXTERN int Tcl_FSRemoveDirectory(Tcl_Obj *pathPtr, + int recursive, Tcl_Obj **errorPtr); +/* 448 */ +EXTERN int Tcl_FSRenameFile(Tcl_Obj *srcPathPtr, + Tcl_Obj *destPathPtr); +/* 449 */ +EXTERN int Tcl_FSLstat(Tcl_Obj *pathPtr, Tcl_StatBuf *buf); +/* 450 */ +EXTERN int Tcl_FSUtime(Tcl_Obj *pathPtr, struct utimbuf *tval); +/* 451 */ +EXTERN int Tcl_FSFileAttrsGet(Tcl_Interp *interp, int index, + Tcl_Obj *pathPtr, Tcl_Obj **objPtrRef); +/* 452 */ +EXTERN int Tcl_FSFileAttrsSet(Tcl_Interp *interp, int index, + Tcl_Obj *pathPtr, Tcl_Obj *objPtr); +/* 453 */ +EXTERN const char *CONST86 * Tcl_FSFileAttrStrings(Tcl_Obj *pathPtr, + Tcl_Obj **objPtrRef); +/* 454 */ +EXTERN int Tcl_FSStat(Tcl_Obj *pathPtr, Tcl_StatBuf *buf); +/* 455 */ +EXTERN int Tcl_FSAccess(Tcl_Obj *pathPtr, int mode); +/* 456 */ +EXTERN Tcl_Channel Tcl_FSOpenFileChannel(Tcl_Interp *interp, + Tcl_Obj *pathPtr, const char *modeString, + int permissions); +/* 457 */ +EXTERN Tcl_Obj * Tcl_FSGetCwd(Tcl_Interp *interp); +/* 458 */ +EXTERN int Tcl_FSChdir(Tcl_Obj *pathPtr); +/* 459 */ +EXTERN int Tcl_FSConvertToPathType(Tcl_Interp *interp, + Tcl_Obj *pathPtr); +/* 460 */ +EXTERN Tcl_Obj * Tcl_FSJoinPath(Tcl_Obj *listObj, int elements); +/* 461 */ +EXTERN Tcl_Obj * Tcl_FSSplitPath(Tcl_Obj *pathPtr, int *lenPtr); +/* 462 */ +EXTERN int Tcl_FSEqualPaths(Tcl_Obj *firstPtr, + Tcl_Obj *secondPtr); +/* 463 */ +EXTERN Tcl_Obj * Tcl_FSGetNormalizedPath(Tcl_Interp *interp, + Tcl_Obj *pathPtr); +/* 464 */ +EXTERN Tcl_Obj * Tcl_FSJoinToPath(Tcl_Obj *pathPtr, int objc, + Tcl_Obj *const objv[]); +/* 465 */ +EXTERN ClientData Tcl_FSGetInternalRep(Tcl_Obj *pathPtr, + const Tcl_Filesystem *fsPtr); +/* 466 */ +EXTERN Tcl_Obj * Tcl_FSGetTranslatedPath(Tcl_Interp *interp, + Tcl_Obj *pathPtr); +/* 467 */ +EXTERN int Tcl_FSEvalFile(Tcl_Interp *interp, Tcl_Obj *fileName); +/* 468 */ +EXTERN Tcl_Obj * Tcl_FSNewNativePath( + const Tcl_Filesystem *fromFilesystem, + ClientData clientData); +/* 469 */ +EXTERN const void * Tcl_FSGetNativePath(Tcl_Obj *pathPtr); +/* 470 */ +EXTERN Tcl_Obj * Tcl_FSFileSystemInfo(Tcl_Obj *pathPtr); +/* 471 */ +EXTERN Tcl_Obj * Tcl_FSPathSeparator(Tcl_Obj *pathPtr); +/* 472 */ +EXTERN Tcl_Obj * Tcl_FSListVolumes(void); +/* 473 */ +EXTERN int Tcl_FSRegister(ClientData clientData, + const Tcl_Filesystem *fsPtr); +/* 474 */ +EXTERN int Tcl_FSUnregister(const Tcl_Filesystem *fsPtr); +/* 475 */ +EXTERN ClientData Tcl_FSData(const Tcl_Filesystem *fsPtr); +/* 476 */ +EXTERN const char * Tcl_FSGetTranslatedStringPath(Tcl_Interp *interp, + Tcl_Obj *pathPtr); +/* 477 */ +EXTERN CONST86 Tcl_Filesystem * Tcl_FSGetFileSystemForPath(Tcl_Obj *pathPtr); +/* 478 */ +EXTERN Tcl_PathType Tcl_FSGetPathType(Tcl_Obj *pathPtr); +/* 479 */ +EXTERN int Tcl_OutputBuffered(Tcl_Channel chan); +/* 480 */ +EXTERN void Tcl_FSMountsChanged(const Tcl_Filesystem *fsPtr); +/* 481 */ +EXTERN int Tcl_EvalTokensStandard(Tcl_Interp *interp, + Tcl_Token *tokenPtr, int count); +/* 482 */ +EXTERN void Tcl_GetTime(Tcl_Time *timeBuf); +/* 483 */ +EXTERN Tcl_Trace Tcl_CreateObjTrace(Tcl_Interp *interp, int level, + int flags, Tcl_CmdObjTraceProc *objProc, + ClientData clientData, + Tcl_CmdObjTraceDeleteProc *delProc); +/* 484 */ +EXTERN int Tcl_GetCommandInfoFromToken(Tcl_Command token, + Tcl_CmdInfo *infoPtr); +/* 485 */ +EXTERN int Tcl_SetCommandInfoFromToken(Tcl_Command token, + const Tcl_CmdInfo *infoPtr); +/* 486 */ +EXTERN Tcl_Obj * Tcl_DbNewWideIntObj(Tcl_WideInt wideValue, + const char *file, int line); +/* 487 */ +EXTERN int Tcl_GetWideIntFromObj(Tcl_Interp *interp, + Tcl_Obj *objPtr, Tcl_WideInt *widePtr); +/* 488 */ +EXTERN Tcl_Obj * Tcl_NewWideIntObj(Tcl_WideInt wideValue); +/* 489 */ +EXTERN void Tcl_SetWideIntObj(Tcl_Obj *objPtr, + Tcl_WideInt wideValue); +/* 490 */ +EXTERN Tcl_StatBuf * Tcl_AllocStatBuf(void); +/* 491 */ +EXTERN Tcl_WideInt Tcl_Seek(Tcl_Channel chan, Tcl_WideInt offset, + int mode); +/* 492 */ +EXTERN Tcl_WideInt Tcl_Tell(Tcl_Channel chan); +/* 493 */ +EXTERN Tcl_DriverWideSeekProc * Tcl_ChannelWideSeekProc( + const Tcl_ChannelType *chanTypePtr); +/* 494 */ +EXTERN int Tcl_DictObjPut(Tcl_Interp *interp, Tcl_Obj *dictPtr, + Tcl_Obj *keyPtr, Tcl_Obj *valuePtr); +/* 495 */ +EXTERN int Tcl_DictObjGet(Tcl_Interp *interp, Tcl_Obj *dictPtr, + Tcl_Obj *keyPtr, Tcl_Obj **valuePtrPtr); +/* 496 */ +EXTERN int Tcl_DictObjRemove(Tcl_Interp *interp, + Tcl_Obj *dictPtr, Tcl_Obj *keyPtr); +/* 497 */ +EXTERN int Tcl_DictObjSize(Tcl_Interp *interp, Tcl_Obj *dictPtr, + int *sizePtr); +/* 498 */ +EXTERN int Tcl_DictObjFirst(Tcl_Interp *interp, + Tcl_Obj *dictPtr, Tcl_DictSearch *searchPtr, + Tcl_Obj **keyPtrPtr, Tcl_Obj **valuePtrPtr, + int *donePtr); +/* 499 */ +EXTERN void Tcl_DictObjNext(Tcl_DictSearch *searchPtr, + Tcl_Obj **keyPtrPtr, Tcl_Obj **valuePtrPtr, + int *donePtr); +/* 500 */ +EXTERN void Tcl_DictObjDone(Tcl_DictSearch *searchPtr); +/* 501 */ +EXTERN int Tcl_DictObjPutKeyList(Tcl_Interp *interp, + Tcl_Obj *dictPtr, int keyc, + Tcl_Obj *const *keyv, Tcl_Obj *valuePtr); +/* 502 */ +EXTERN int Tcl_DictObjRemoveKeyList(Tcl_Interp *interp, + Tcl_Obj *dictPtr, int keyc, + Tcl_Obj *const *keyv); +/* 503 */ +EXTERN Tcl_Obj * Tcl_NewDictObj(void); +/* 504 */ +EXTERN Tcl_Obj * Tcl_DbNewDictObj(const char *file, int line); +/* 505 */ +EXTERN void Tcl_RegisterConfig(Tcl_Interp *interp, + const char *pkgName, + const Tcl_Config *configuration, + const char *valEncoding); +/* 506 */ +EXTERN Tcl_Namespace * Tcl_CreateNamespace(Tcl_Interp *interp, + const char *name, ClientData clientData, + Tcl_NamespaceDeleteProc *deleteProc); +/* 507 */ +EXTERN void Tcl_DeleteNamespace(Tcl_Namespace *nsPtr); +/* 508 */ +EXTERN int Tcl_AppendExportList(Tcl_Interp *interp, + Tcl_Namespace *nsPtr, Tcl_Obj *objPtr); +/* 509 */ +EXTERN int Tcl_Export(Tcl_Interp *interp, Tcl_Namespace *nsPtr, + const char *pattern, int resetListFirst); +/* 510 */ +EXTERN int Tcl_Import(Tcl_Interp *interp, Tcl_Namespace *nsPtr, + const char *pattern, int allowOverwrite); +/* 511 */ +EXTERN int Tcl_ForgetImport(Tcl_Interp *interp, + Tcl_Namespace *nsPtr, const char *pattern); +/* 512 */ +EXTERN Tcl_Namespace * Tcl_GetCurrentNamespace(Tcl_Interp *interp); +/* 513 */ +EXTERN Tcl_Namespace * Tcl_GetGlobalNamespace(Tcl_Interp *interp); +/* 514 */ +EXTERN Tcl_Namespace * Tcl_FindNamespace(Tcl_Interp *interp, + const char *name, + Tcl_Namespace *contextNsPtr, int flags); +/* 515 */ +EXTERN Tcl_Command Tcl_FindCommand(Tcl_Interp *interp, const char *name, + Tcl_Namespace *contextNsPtr, int flags); +/* 516 */ +EXTERN Tcl_Command Tcl_GetCommandFromObj(Tcl_Interp *interp, + Tcl_Obj *objPtr); +/* 517 */ +EXTERN void Tcl_GetCommandFullName(Tcl_Interp *interp, + Tcl_Command command, Tcl_Obj *objPtr); +/* 518 */ +EXTERN int Tcl_FSEvalFileEx(Tcl_Interp *interp, + Tcl_Obj *fileName, const char *encodingName); +/* 519 */ +EXTERN Tcl_ExitProc * Tcl_SetExitProc(Tcl_ExitProc *proc); +/* 520 */ +EXTERN void Tcl_LimitAddHandler(Tcl_Interp *interp, int type, + Tcl_LimitHandlerProc *handlerProc, + ClientData clientData, + Tcl_LimitHandlerDeleteProc *deleteProc); +/* 521 */ +EXTERN void Tcl_LimitRemoveHandler(Tcl_Interp *interp, int type, + Tcl_LimitHandlerProc *handlerProc, + ClientData clientData); +/* 522 */ +EXTERN int Tcl_LimitReady(Tcl_Interp *interp); +/* 523 */ +EXTERN int Tcl_LimitCheck(Tcl_Interp *interp); +/* 524 */ +EXTERN int Tcl_LimitExceeded(Tcl_Interp *interp); +/* 525 */ +EXTERN void Tcl_LimitSetCommands(Tcl_Interp *interp, + int commandLimit); +/* 526 */ +EXTERN void Tcl_LimitSetTime(Tcl_Interp *interp, + Tcl_Time *timeLimitPtr); +/* 527 */ +EXTERN void Tcl_LimitSetGranularity(Tcl_Interp *interp, int type, + int granularity); +/* 528 */ +EXTERN int Tcl_LimitTypeEnabled(Tcl_Interp *interp, int type); +/* 529 */ +EXTERN int Tcl_LimitTypeExceeded(Tcl_Interp *interp, int type); +/* 530 */ +EXTERN void Tcl_LimitTypeSet(Tcl_Interp *interp, int type); +/* 531 */ +EXTERN void Tcl_LimitTypeReset(Tcl_Interp *interp, int type); +/* 532 */ +EXTERN int Tcl_LimitGetCommands(Tcl_Interp *interp); +/* 533 */ +EXTERN void Tcl_LimitGetTime(Tcl_Interp *interp, + Tcl_Time *timeLimitPtr); +/* 534 */ +EXTERN int Tcl_LimitGetGranularity(Tcl_Interp *interp, int type); +/* 535 */ +EXTERN Tcl_InterpState Tcl_SaveInterpState(Tcl_Interp *interp, int status); +/* 536 */ +EXTERN int Tcl_RestoreInterpState(Tcl_Interp *interp, + Tcl_InterpState state); +/* 537 */ +EXTERN void Tcl_DiscardInterpState(Tcl_InterpState state); +/* 538 */ +EXTERN int Tcl_SetReturnOptions(Tcl_Interp *interp, + Tcl_Obj *options); +/* 539 */ +EXTERN Tcl_Obj * Tcl_GetReturnOptions(Tcl_Interp *interp, int result); +/* 540 */ +EXTERN int Tcl_IsEnsemble(Tcl_Command token); +/* 541 */ +EXTERN Tcl_Command Tcl_CreateEnsemble(Tcl_Interp *interp, + const char *name, + Tcl_Namespace *namespacePtr, int flags); +/* 542 */ +EXTERN Tcl_Command Tcl_FindEnsemble(Tcl_Interp *interp, + Tcl_Obj *cmdNameObj, int flags); +/* 543 */ +EXTERN int Tcl_SetEnsembleSubcommandList(Tcl_Interp *interp, + Tcl_Command token, Tcl_Obj *subcmdList); +/* 544 */ +EXTERN int Tcl_SetEnsembleMappingDict(Tcl_Interp *interp, + Tcl_Command token, Tcl_Obj *mapDict); +/* 545 */ +EXTERN int Tcl_SetEnsembleUnknownHandler(Tcl_Interp *interp, + Tcl_Command token, Tcl_Obj *unknownList); +/* 546 */ +EXTERN int Tcl_SetEnsembleFlags(Tcl_Interp *interp, + Tcl_Command token, int flags); +/* 547 */ +EXTERN int Tcl_GetEnsembleSubcommandList(Tcl_Interp *interp, + Tcl_Command token, Tcl_Obj **subcmdListPtr); +/* 548 */ +EXTERN int Tcl_GetEnsembleMappingDict(Tcl_Interp *interp, + Tcl_Command token, Tcl_Obj **mapDictPtr); +/* 549 */ +EXTERN int Tcl_GetEnsembleUnknownHandler(Tcl_Interp *interp, + Tcl_Command token, Tcl_Obj **unknownListPtr); +/* 550 */ +EXTERN int Tcl_GetEnsembleFlags(Tcl_Interp *interp, + Tcl_Command token, int *flagsPtr); +/* 551 */ +EXTERN int Tcl_GetEnsembleNamespace(Tcl_Interp *interp, + Tcl_Command token, + Tcl_Namespace **namespacePtrPtr); +/* 552 */ +EXTERN void Tcl_SetTimeProc(Tcl_GetTimeProc *getProc, + Tcl_ScaleTimeProc *scaleProc, + ClientData clientData); +/* 553 */ +EXTERN void Tcl_QueryTimeProc(Tcl_GetTimeProc **getProc, + Tcl_ScaleTimeProc **scaleProc, + ClientData *clientData); +/* 554 */ +EXTERN Tcl_DriverThreadActionProc * Tcl_ChannelThreadActionProc( + const Tcl_ChannelType *chanTypePtr); +/* 555 */ +EXTERN Tcl_Obj * Tcl_NewBignumObj(mp_int *value); +/* 556 */ +EXTERN Tcl_Obj * Tcl_DbNewBignumObj(mp_int *value, const char *file, + int line); +/* 557 */ +EXTERN void Tcl_SetBignumObj(Tcl_Obj *obj, mp_int *value); +/* 558 */ +EXTERN int Tcl_GetBignumFromObj(Tcl_Interp *interp, + Tcl_Obj *obj, mp_int *value); +/* 559 */ +EXTERN int Tcl_TakeBignumFromObj(Tcl_Interp *interp, + Tcl_Obj *obj, mp_int *value); +/* 560 */ +EXTERN int Tcl_TruncateChannel(Tcl_Channel chan, + Tcl_WideInt length); +/* 561 */ +EXTERN Tcl_DriverTruncateProc * Tcl_ChannelTruncateProc( + const Tcl_ChannelType *chanTypePtr); +/* 562 */ +EXTERN void Tcl_SetChannelErrorInterp(Tcl_Interp *interp, + Tcl_Obj *msg); +/* 563 */ +EXTERN void Tcl_GetChannelErrorInterp(Tcl_Interp *interp, + Tcl_Obj **msg); +/* 564 */ +EXTERN void Tcl_SetChannelError(Tcl_Channel chan, Tcl_Obj *msg); +/* 565 */ +EXTERN void Tcl_GetChannelError(Tcl_Channel chan, Tcl_Obj **msg); +/* 566 */ +EXTERN int Tcl_InitBignumFromDouble(Tcl_Interp *interp, + double initval, mp_int *toInit); +/* 567 */ +EXTERN Tcl_Obj * Tcl_GetNamespaceUnknownHandler(Tcl_Interp *interp, + Tcl_Namespace *nsPtr); +/* 568 */ +EXTERN int Tcl_SetNamespaceUnknownHandler(Tcl_Interp *interp, + Tcl_Namespace *nsPtr, Tcl_Obj *handlerPtr); +/* 569 */ +EXTERN int Tcl_GetEncodingFromObj(Tcl_Interp *interp, + Tcl_Obj *objPtr, Tcl_Encoding *encodingPtr); +/* 570 */ +EXTERN Tcl_Obj * Tcl_GetEncodingSearchPath(void); +/* 571 */ +EXTERN int Tcl_SetEncodingSearchPath(Tcl_Obj *searchPath); +/* 572 */ +EXTERN const char * Tcl_GetEncodingNameFromEnvironment( + Tcl_DString *bufPtr); +/* 573 */ +EXTERN int Tcl_PkgRequireProc(Tcl_Interp *interp, + const char *name, int objc, + Tcl_Obj *const objv[], void *clientDataPtr); +/* 574 */ +EXTERN void Tcl_AppendObjToErrorInfo(Tcl_Interp *interp, + Tcl_Obj *objPtr); +/* 575 */ +EXTERN void Tcl_AppendLimitedToObj(Tcl_Obj *objPtr, + const char *bytes, int length, int limit, + const char *ellipsis); +/* 576 */ +EXTERN Tcl_Obj * Tcl_Format(Tcl_Interp *interp, const char *format, + int objc, Tcl_Obj *const objv[]); +/* 577 */ +EXTERN int Tcl_AppendFormatToObj(Tcl_Interp *interp, + Tcl_Obj *objPtr, const char *format, + int objc, Tcl_Obj *const objv[]); +/* 578 */ +EXTERN Tcl_Obj * Tcl_ObjPrintf(const char *format, ...) TCL_FORMAT_PRINTF(1, 2); +/* 579 */ +EXTERN void Tcl_AppendPrintfToObj(Tcl_Obj *objPtr, + const char *format, ...) TCL_FORMAT_PRINTF(2, 3); +/* 580 */ +EXTERN int Tcl_CancelEval(Tcl_Interp *interp, + Tcl_Obj *resultObjPtr, ClientData clientData, + int flags); +/* 581 */ +EXTERN int Tcl_Canceled(Tcl_Interp *interp, int flags); +/* 582 */ +EXTERN int Tcl_CreatePipe(Tcl_Interp *interp, + Tcl_Channel *rchan, Tcl_Channel *wchan, + int flags); +/* 583 */ +EXTERN Tcl_Command Tcl_NRCreateCommand(Tcl_Interp *interp, + const char *cmdName, Tcl_ObjCmdProc *proc, + Tcl_ObjCmdProc *nreProc, + ClientData clientData, + Tcl_CmdDeleteProc *deleteProc); +/* 584 */ +EXTERN int Tcl_NREvalObj(Tcl_Interp *interp, Tcl_Obj *objPtr, + int flags); +/* 585 */ +EXTERN int Tcl_NREvalObjv(Tcl_Interp *interp, int objc, + Tcl_Obj *const objv[], int flags); +/* 586 */ +EXTERN int Tcl_NRCmdSwap(Tcl_Interp *interp, Tcl_Command cmd, + int objc, Tcl_Obj *const objv[], int flags); +/* 587 */ +EXTERN void Tcl_NRAddCallback(Tcl_Interp *interp, + Tcl_NRPostProc *postProcPtr, + ClientData data0, ClientData data1, + ClientData data2, ClientData data3); +/* 588 */ +EXTERN int Tcl_NRCallObjProc(Tcl_Interp *interp, + Tcl_ObjCmdProc *objProc, + ClientData clientData, int objc, + Tcl_Obj *const objv[]); +/* 589 */ +EXTERN unsigned Tcl_GetFSDeviceFromStat(const Tcl_StatBuf *statPtr); +/* 590 */ +EXTERN unsigned Tcl_GetFSInodeFromStat(const Tcl_StatBuf *statPtr); +/* 591 */ +EXTERN unsigned Tcl_GetModeFromStat(const Tcl_StatBuf *statPtr); +/* 592 */ +EXTERN int Tcl_GetLinkCountFromStat(const Tcl_StatBuf *statPtr); +/* 593 */ +EXTERN int Tcl_GetUserIdFromStat(const Tcl_StatBuf *statPtr); +/* 594 */ +EXTERN int Tcl_GetGroupIdFromStat(const Tcl_StatBuf *statPtr); +/* 595 */ +EXTERN int Tcl_GetDeviceTypeFromStat(const Tcl_StatBuf *statPtr); +/* 596 */ +EXTERN Tcl_WideInt Tcl_GetAccessTimeFromStat(const Tcl_StatBuf *statPtr); +/* 597 */ +EXTERN Tcl_WideInt Tcl_GetModificationTimeFromStat( + const Tcl_StatBuf *statPtr); +/* 598 */ +EXTERN Tcl_WideInt Tcl_GetChangeTimeFromStat(const Tcl_StatBuf *statPtr); +/* 599 */ +EXTERN Tcl_WideUInt Tcl_GetSizeFromStat(const Tcl_StatBuf *statPtr); +/* 600 */ +EXTERN Tcl_WideUInt Tcl_GetBlocksFromStat(const Tcl_StatBuf *statPtr); +/* 601 */ +EXTERN unsigned Tcl_GetBlockSizeFromStat(const Tcl_StatBuf *statPtr); +/* 602 */ +EXTERN int Tcl_SetEnsembleParameterList(Tcl_Interp *interp, + Tcl_Command token, Tcl_Obj *paramList); +/* 603 */ +EXTERN int Tcl_GetEnsembleParameterList(Tcl_Interp *interp, + Tcl_Command token, Tcl_Obj **paramListPtr); +/* 604 */ +EXTERN int Tcl_ParseArgsObjv(Tcl_Interp *interp, + const Tcl_ArgvInfo *argTable, int *objcPtr, + Tcl_Obj *const *objv, Tcl_Obj ***remObjv); +/* 605 */ +EXTERN int Tcl_GetErrorLine(Tcl_Interp *interp); +/* 606 */ +EXTERN void Tcl_SetErrorLine(Tcl_Interp *interp, int lineNum); +/* 607 */ +EXTERN void Tcl_TransferResult(Tcl_Interp *sourceInterp, + int result, Tcl_Interp *targetInterp); +/* 608 */ +EXTERN int Tcl_InterpActive(Tcl_Interp *interp); +/* 609 */ +EXTERN void Tcl_BackgroundException(Tcl_Interp *interp, int code); +/* 610 */ +EXTERN int Tcl_ZlibDeflate(Tcl_Interp *interp, int format, + Tcl_Obj *data, int level, + Tcl_Obj *gzipHeaderDictObj); +/* 611 */ +EXTERN int Tcl_ZlibInflate(Tcl_Interp *interp, int format, + Tcl_Obj *data, int buffersize, + Tcl_Obj *gzipHeaderDictObj); +/* 612 */ +EXTERN unsigned int Tcl_ZlibCRC32(unsigned int crc, + const unsigned char *buf, int len); +/* 613 */ +EXTERN unsigned int Tcl_ZlibAdler32(unsigned int adler, + const unsigned char *buf, int len); +/* 614 */ +EXTERN int Tcl_ZlibStreamInit(Tcl_Interp *interp, int mode, + int format, int level, Tcl_Obj *dictObj, + Tcl_ZlibStream *zshandle); +/* 615 */ +EXTERN Tcl_Obj * Tcl_ZlibStreamGetCommandName(Tcl_ZlibStream zshandle); +/* 616 */ +EXTERN int Tcl_ZlibStreamEof(Tcl_ZlibStream zshandle); +/* 617 */ +EXTERN int Tcl_ZlibStreamChecksum(Tcl_ZlibStream zshandle); +/* 618 */ +EXTERN int Tcl_ZlibStreamPut(Tcl_ZlibStream zshandle, + Tcl_Obj *data, int flush); +/* 619 */ +EXTERN int Tcl_ZlibStreamGet(Tcl_ZlibStream zshandle, + Tcl_Obj *data, int count); +/* 620 */ +EXTERN int Tcl_ZlibStreamClose(Tcl_ZlibStream zshandle); +/* 621 */ +EXTERN int Tcl_ZlibStreamReset(Tcl_ZlibStream zshandle); +/* 622 */ +EXTERN void Tcl_SetStartupScript(Tcl_Obj *path, + const char *encoding); +/* 623 */ +EXTERN Tcl_Obj * Tcl_GetStartupScript(const char **encodingPtr); +/* 624 */ +EXTERN int Tcl_CloseEx(Tcl_Interp *interp, Tcl_Channel chan, + int flags); +/* 625 */ +EXTERN int Tcl_NRExprObj(Tcl_Interp *interp, Tcl_Obj *objPtr, + Tcl_Obj *resultPtr); +/* 626 */ +EXTERN int Tcl_NRSubstObj(Tcl_Interp *interp, Tcl_Obj *objPtr, + int flags); +/* 627 */ +EXTERN int Tcl_LoadFile(Tcl_Interp *interp, Tcl_Obj *pathPtr, + const char *const symv[], int flags, + void *procPtrs, Tcl_LoadHandle *handlePtr); +/* 628 */ +EXTERN void * Tcl_FindSymbol(Tcl_Interp *interp, + Tcl_LoadHandle handle, const char *symbol); +/* 629 */ +EXTERN int Tcl_FSUnloadFile(Tcl_Interp *interp, + Tcl_LoadHandle handlePtr); +/* 630 */ +EXTERN void Tcl_ZlibStreamSetCompressionDictionary( + Tcl_ZlibStream zhandle, + Tcl_Obj *compressionDictionaryObj); + +typedef struct { + const struct TclPlatStubs *tclPlatStubs; + const struct TclIntStubs *tclIntStubs; + const struct TclIntPlatStubs *tclIntPlatStubs; +} TclStubHooks; + +typedef struct TclStubs { + int magic; + const TclStubHooks *hooks; + + int (*tcl_PkgProvideEx) (Tcl_Interp *interp, const char *name, const char *version, const void *clientData); /* 0 */ + CONST84_RETURN char * (*tcl_PkgRequireEx) (Tcl_Interp *interp, const char *name, const char *version, int exact, void *clientDataPtr); /* 1 */ + void (*tcl_Panic) (const char *format, ...) TCL_FORMAT_PRINTF(1, 2); /* 2 */ + char * (*tcl_Alloc) (unsigned int size); /* 3 */ + void (*tcl_Free) (char *ptr); /* 4 */ + char * (*tcl_Realloc) (char *ptr, unsigned int size); /* 5 */ + char * (*tcl_DbCkalloc) (unsigned int size, const char *file, int line); /* 6 */ + void (*tcl_DbCkfree) (char *ptr, const char *file, int line); /* 7 */ + char * (*tcl_DbCkrealloc) (char *ptr, unsigned int size, const char *file, int line); /* 8 */ +#if !defined(__WIN32__) && !defined(MAC_OSX_TCL) /* UNIX */ + void (*tcl_CreateFileHandler) (int fd, int mask, Tcl_FileProc *proc, ClientData clientData); /* 9 */ +#endif /* UNIX */ +#if defined(__WIN32__) /* WIN */ + void (*reserved9)(void); +#endif /* WIN */ +#ifdef MAC_OSX_TCL /* MACOSX */ + void (*tcl_CreateFileHandler) (int fd, int mask, Tcl_FileProc *proc, ClientData clientData); /* 9 */ +#endif /* MACOSX */ +#if !defined(__WIN32__) && !defined(MAC_OSX_TCL) /* UNIX */ + void (*tcl_DeleteFileHandler) (int fd); /* 10 */ +#endif /* UNIX */ +#if defined(__WIN32__) /* WIN */ + void (*reserved10)(void); +#endif /* WIN */ +#ifdef MAC_OSX_TCL /* MACOSX */ + void (*tcl_DeleteFileHandler) (int fd); /* 10 */ +#endif /* MACOSX */ + void (*tcl_SetTimer) (const Tcl_Time *timePtr); /* 11 */ + void (*tcl_Sleep) (int ms); /* 12 */ + int (*tcl_WaitForEvent) (const Tcl_Time *timePtr); /* 13 */ + int (*tcl_AppendAllObjTypes) (Tcl_Interp *interp, Tcl_Obj *objPtr); /* 14 */ + void (*tcl_AppendStringsToObj) (Tcl_Obj *objPtr, ...); /* 15 */ + void (*tcl_AppendToObj) (Tcl_Obj *objPtr, const char *bytes, int length); /* 16 */ + Tcl_Obj * (*tcl_ConcatObj) (int objc, Tcl_Obj *const objv[]); /* 17 */ + int (*tcl_ConvertToType) (Tcl_Interp *interp, Tcl_Obj *objPtr, const Tcl_ObjType *typePtr); /* 18 */ + void (*tcl_DbDecrRefCount) (Tcl_Obj *objPtr, const char *file, int line); /* 19 */ + void (*tcl_DbIncrRefCount) (Tcl_Obj *objPtr, const char *file, int line); /* 20 */ + int (*tcl_DbIsShared) (Tcl_Obj *objPtr, const char *file, int line); /* 21 */ + Tcl_Obj * (*tcl_DbNewBooleanObj) (int boolValue, const char *file, int line); /* 22 */ + Tcl_Obj * (*tcl_DbNewByteArrayObj) (const unsigned char *bytes, int length, const char *file, int line); /* 23 */ + Tcl_Obj * (*tcl_DbNewDoubleObj) (double doubleValue, const char *file, int line); /* 24 */ + Tcl_Obj * (*tcl_DbNewListObj) (int objc, Tcl_Obj *const *objv, const char *file, int line); /* 25 */ + Tcl_Obj * (*tcl_DbNewLongObj) (long longValue, const char *file, int line); /* 26 */ + Tcl_Obj * (*tcl_DbNewObj) (const char *file, int line); /* 27 */ + Tcl_Obj * (*tcl_DbNewStringObj) (const char *bytes, int length, const char *file, int line); /* 28 */ + Tcl_Obj * (*tcl_DuplicateObj) (Tcl_Obj *objPtr); /* 29 */ + void (*tclFreeObj) (Tcl_Obj *objPtr); /* 30 */ + int (*tcl_GetBoolean) (Tcl_Interp *interp, const char *src, int *boolPtr); /* 31 */ + int (*tcl_GetBooleanFromObj) (Tcl_Interp *interp, Tcl_Obj *objPtr, int *boolPtr); /* 32 */ + unsigned char * (*tcl_GetByteArrayFromObj) (Tcl_Obj *objPtr, int *lengthPtr); /* 33 */ + int (*tcl_GetDouble) (Tcl_Interp *interp, const char *src, double *doublePtr); /* 34 */ + int (*tcl_GetDoubleFromObj) (Tcl_Interp *interp, Tcl_Obj *objPtr, double *doublePtr); /* 35 */ + int (*tcl_GetIndexFromObj) (Tcl_Interp *interp, Tcl_Obj *objPtr, CONST84 char *const *tablePtr, const char *msg, int flags, int *indexPtr); /* 36 */ + int (*tcl_GetInt) (Tcl_Interp *interp, const char *src, int *intPtr); /* 37 */ + int (*tcl_GetIntFromObj) (Tcl_Interp *interp, Tcl_Obj *objPtr, int *intPtr); /* 38 */ + int (*tcl_GetLongFromObj) (Tcl_Interp *interp, Tcl_Obj *objPtr, long *longPtr); /* 39 */ + CONST86 Tcl_ObjType * (*tcl_GetObjType) (const char *typeName); /* 40 */ + char * (*tcl_GetStringFromObj) (Tcl_Obj *objPtr, int *lengthPtr); /* 41 */ + void (*tcl_InvalidateStringRep) (Tcl_Obj *objPtr); /* 42 */ + int (*tcl_ListObjAppendList) (Tcl_Interp *interp, Tcl_Obj *listPtr, Tcl_Obj *elemListPtr); /* 43 */ + int (*tcl_ListObjAppendElement) (Tcl_Interp *interp, Tcl_Obj *listPtr, Tcl_Obj *objPtr); /* 44 */ + int (*tcl_ListObjGetElements) (Tcl_Interp *interp, Tcl_Obj *listPtr, int *objcPtr, Tcl_Obj ***objvPtr); /* 45 */ + int (*tcl_ListObjIndex) (Tcl_Interp *interp, Tcl_Obj *listPtr, int index, Tcl_Obj **objPtrPtr); /* 46 */ + int (*tcl_ListObjLength) (Tcl_Interp *interp, Tcl_Obj *listPtr, int *lengthPtr); /* 47 */ + int (*tcl_ListObjReplace) (Tcl_Interp *interp, Tcl_Obj *listPtr, int first, int count, int objc, Tcl_Obj *const objv[]); /* 48 */ + Tcl_Obj * (*tcl_NewBooleanObj) (int boolValue); /* 49 */ + Tcl_Obj * (*tcl_NewByteArrayObj) (const unsigned char *bytes, int length); /* 50 */ + Tcl_Obj * (*tcl_NewDoubleObj) (double doubleValue); /* 51 */ + Tcl_Obj * (*tcl_NewIntObj) (int intValue); /* 52 */ + Tcl_Obj * (*tcl_NewListObj) (int objc, Tcl_Obj *const objv[]); /* 53 */ + Tcl_Obj * (*tcl_NewLongObj) (long longValue); /* 54 */ + Tcl_Obj * (*tcl_NewObj) (void); /* 55 */ + Tcl_Obj * (*tcl_NewStringObj) (const char *bytes, int length); /* 56 */ + void (*tcl_SetBooleanObj) (Tcl_Obj *objPtr, int boolValue); /* 57 */ + unsigned char * (*tcl_SetByteArrayLength) (Tcl_Obj *objPtr, int length); /* 58 */ + void (*tcl_SetByteArrayObj) (Tcl_Obj *objPtr, const unsigned char *bytes, int length); /* 59 */ + void (*tcl_SetDoubleObj) (Tcl_Obj *objPtr, double doubleValue); /* 60 */ + void (*tcl_SetIntObj) (Tcl_Obj *objPtr, int intValue); /* 61 */ + void (*tcl_SetListObj) (Tcl_Obj *objPtr, int objc, Tcl_Obj *const objv[]); /* 62 */ + void (*tcl_SetLongObj) (Tcl_Obj *objPtr, long longValue); /* 63 */ + void (*tcl_SetObjLength) (Tcl_Obj *objPtr, int length); /* 64 */ + void (*tcl_SetStringObj) (Tcl_Obj *objPtr, const char *bytes, int length); /* 65 */ + void (*tcl_AddErrorInfo) (Tcl_Interp *interp, const char *message); /* 66 */ + void (*tcl_AddObjErrorInfo) (Tcl_Interp *interp, const char *message, int length); /* 67 */ + void (*tcl_AllowExceptions) (Tcl_Interp *interp); /* 68 */ + void (*tcl_AppendElement) (Tcl_Interp *interp, const char *element); /* 69 */ + void (*tcl_AppendResult) (Tcl_Interp *interp, ...); /* 70 */ + Tcl_AsyncHandler (*tcl_AsyncCreate) (Tcl_AsyncProc *proc, ClientData clientData); /* 71 */ + void (*tcl_AsyncDelete) (Tcl_AsyncHandler async); /* 72 */ + int (*tcl_AsyncInvoke) (Tcl_Interp *interp, int code); /* 73 */ + void (*tcl_AsyncMark) (Tcl_AsyncHandler async); /* 74 */ + int (*tcl_AsyncReady) (void); /* 75 */ + void (*tcl_BackgroundError) (Tcl_Interp *interp); /* 76 */ + char (*tcl_Backslash) (const char *src, int *readPtr); /* 77 */ + int (*tcl_BadChannelOption) (Tcl_Interp *interp, const char *optionName, const char *optionList); /* 78 */ + void (*tcl_CallWhenDeleted) (Tcl_Interp *interp, Tcl_InterpDeleteProc *proc, ClientData clientData); /* 79 */ + void (*tcl_CancelIdleCall) (Tcl_IdleProc *idleProc, ClientData clientData); /* 80 */ + int (*tcl_Close) (Tcl_Interp *interp, Tcl_Channel chan); /* 81 */ + int (*tcl_CommandComplete) (const char *cmd); /* 82 */ + char * (*tcl_Concat) (int argc, CONST84 char *const *argv); /* 83 */ + int (*tcl_ConvertElement) (const char *src, char *dst, int flags); /* 84 */ + int (*tcl_ConvertCountedElement) (const char *src, int length, char *dst, int flags); /* 85 */ + int (*tcl_CreateAlias) (Tcl_Interp *slave, const char *slaveCmd, Tcl_Interp *target, const char *targetCmd, int argc, CONST84 char *const *argv); /* 86 */ + int (*tcl_CreateAliasObj) (Tcl_Interp *slave, const char *slaveCmd, Tcl_Interp *target, const char *targetCmd, int objc, Tcl_Obj *const objv[]); /* 87 */ + Tcl_Channel (*tcl_CreateChannel) (const Tcl_ChannelType *typePtr, const char *chanName, ClientData instanceData, int mask); /* 88 */ + void (*tcl_CreateChannelHandler) (Tcl_Channel chan, int mask, Tcl_ChannelProc *proc, ClientData clientData); /* 89 */ + void (*tcl_CreateCloseHandler) (Tcl_Channel chan, Tcl_CloseProc *proc, ClientData clientData); /* 90 */ + Tcl_Command (*tcl_CreateCommand) (Tcl_Interp *interp, const char *cmdName, Tcl_CmdProc *proc, ClientData clientData, Tcl_CmdDeleteProc *deleteProc); /* 91 */ + void (*tcl_CreateEventSource) (Tcl_EventSetupProc *setupProc, Tcl_EventCheckProc *checkProc, ClientData clientData); /* 92 */ + void (*tcl_CreateExitHandler) (Tcl_ExitProc *proc, ClientData clientData); /* 93 */ + Tcl_Interp * (*tcl_CreateInterp) (void); /* 94 */ + void (*tcl_CreateMathFunc) (Tcl_Interp *interp, const char *name, int numArgs, Tcl_ValueType *argTypes, Tcl_MathProc *proc, ClientData clientData); /* 95 */ + Tcl_Command (*tcl_CreateObjCommand) (Tcl_Interp *interp, const char *cmdName, Tcl_ObjCmdProc *proc, ClientData clientData, Tcl_CmdDeleteProc *deleteProc); /* 96 */ + Tcl_Interp * (*tcl_CreateSlave) (Tcl_Interp *interp, const char *slaveName, int isSafe); /* 97 */ + Tcl_TimerToken (*tcl_CreateTimerHandler) (int milliseconds, Tcl_TimerProc *proc, ClientData clientData); /* 98 */ + Tcl_Trace (*tcl_CreateTrace) (Tcl_Interp *interp, int level, Tcl_CmdTraceProc *proc, ClientData clientData); /* 99 */ + void (*tcl_DeleteAssocData) (Tcl_Interp *interp, const char *name); /* 100 */ + void (*tcl_DeleteChannelHandler) (Tcl_Channel chan, Tcl_ChannelProc *proc, ClientData clientData); /* 101 */ + void (*tcl_DeleteCloseHandler) (Tcl_Channel chan, Tcl_CloseProc *proc, ClientData clientData); /* 102 */ + int (*tcl_DeleteCommand) (Tcl_Interp *interp, const char *cmdName); /* 103 */ + int (*tcl_DeleteCommandFromToken) (Tcl_Interp *interp, Tcl_Command command); /* 104 */ + void (*tcl_DeleteEvents) (Tcl_EventDeleteProc *proc, ClientData clientData); /* 105 */ + void (*tcl_DeleteEventSource) (Tcl_EventSetupProc *setupProc, Tcl_EventCheckProc *checkProc, ClientData clientData); /* 106 */ + void (*tcl_DeleteExitHandler) (Tcl_ExitProc *proc, ClientData clientData); /* 107 */ + void (*tcl_DeleteHashEntry) (Tcl_HashEntry *entryPtr); /* 108 */ + void (*tcl_DeleteHashTable) (Tcl_HashTable *tablePtr); /* 109 */ + void (*tcl_DeleteInterp) (Tcl_Interp *interp); /* 110 */ + void (*tcl_DetachPids) (int numPids, Tcl_Pid *pidPtr); /* 111 */ + void (*tcl_DeleteTimerHandler) (Tcl_TimerToken token); /* 112 */ + void (*tcl_DeleteTrace) (Tcl_Interp *interp, Tcl_Trace trace); /* 113 */ + void (*tcl_DontCallWhenDeleted) (Tcl_Interp *interp, Tcl_InterpDeleteProc *proc, ClientData clientData); /* 114 */ + int (*tcl_DoOneEvent) (int flags); /* 115 */ + void (*tcl_DoWhenIdle) (Tcl_IdleProc *proc, ClientData clientData); /* 116 */ + char * (*tcl_DStringAppend) (Tcl_DString *dsPtr, const char *bytes, int length); /* 117 */ + char * (*tcl_DStringAppendElement) (Tcl_DString *dsPtr, const char *element); /* 118 */ + void (*tcl_DStringEndSublist) (Tcl_DString *dsPtr); /* 119 */ + void (*tcl_DStringFree) (Tcl_DString *dsPtr); /* 120 */ + void (*tcl_DStringGetResult) (Tcl_Interp *interp, Tcl_DString *dsPtr); /* 121 */ + void (*tcl_DStringInit) (Tcl_DString *dsPtr); /* 122 */ + void (*tcl_DStringResult) (Tcl_Interp *interp, Tcl_DString *dsPtr); /* 123 */ + void (*tcl_DStringSetLength) (Tcl_DString *dsPtr, int length); /* 124 */ + void (*tcl_DStringStartSublist) (Tcl_DString *dsPtr); /* 125 */ + int (*tcl_Eof) (Tcl_Channel chan); /* 126 */ + CONST84_RETURN char * (*tcl_ErrnoId) (void); /* 127 */ + CONST84_RETURN char * (*tcl_ErrnoMsg) (int err); /* 128 */ + int (*tcl_Eval) (Tcl_Interp *interp, const char *script); /* 129 */ + int (*tcl_EvalFile) (Tcl_Interp *interp, const char *fileName); /* 130 */ + int (*tcl_EvalObj) (Tcl_Interp *interp, Tcl_Obj *objPtr); /* 131 */ + void (*tcl_EventuallyFree) (ClientData clientData, Tcl_FreeProc *freeProc); /* 132 */ + void (*tcl_Exit) (int status); /* 133 */ + int (*tcl_ExposeCommand) (Tcl_Interp *interp, const char *hiddenCmdToken, const char *cmdName); /* 134 */ + int (*tcl_ExprBoolean) (Tcl_Interp *interp, const char *expr, int *ptr); /* 135 */ + int (*tcl_ExprBooleanObj) (Tcl_Interp *interp, Tcl_Obj *objPtr, int *ptr); /* 136 */ + int (*tcl_ExprDouble) (Tcl_Interp *interp, const char *expr, double *ptr); /* 137 */ + int (*tcl_ExprDoubleObj) (Tcl_Interp *interp, Tcl_Obj *objPtr, double *ptr); /* 138 */ + int (*tcl_ExprLong) (Tcl_Interp *interp, const char *expr, long *ptr); /* 139 */ + int (*tcl_ExprLongObj) (Tcl_Interp *interp, Tcl_Obj *objPtr, long *ptr); /* 140 */ + int (*tcl_ExprObj) (Tcl_Interp *interp, Tcl_Obj *objPtr, Tcl_Obj **resultPtrPtr); /* 141 */ + int (*tcl_ExprString) (Tcl_Interp *interp, const char *expr); /* 142 */ + void (*tcl_Finalize) (void); /* 143 */ + void (*tcl_FindExecutable) (const char *argv0); /* 144 */ + Tcl_HashEntry * (*tcl_FirstHashEntry) (Tcl_HashTable *tablePtr, Tcl_HashSearch *searchPtr); /* 145 */ + int (*tcl_Flush) (Tcl_Channel chan); /* 146 */ + void (*tcl_FreeResult) (Tcl_Interp *interp); /* 147 */ + int (*tcl_GetAlias) (Tcl_Interp *interp, const char *slaveCmd, Tcl_Interp **targetInterpPtr, CONST84 char **targetCmdPtr, int *argcPtr, CONST84 char ***argvPtr); /* 148 */ + int (*tcl_GetAliasObj) (Tcl_Interp *interp, const char *slaveCmd, Tcl_Interp **targetInterpPtr, CONST84 char **targetCmdPtr, int *objcPtr, Tcl_Obj ***objv); /* 149 */ + ClientData (*tcl_GetAssocData) (Tcl_Interp *interp, const char *name, Tcl_InterpDeleteProc **procPtr); /* 150 */ + Tcl_Channel (*tcl_GetChannel) (Tcl_Interp *interp, const char *chanName, int *modePtr); /* 151 */ + int (*tcl_GetChannelBufferSize) (Tcl_Channel chan); /* 152 */ + int (*tcl_GetChannelHandle) (Tcl_Channel chan, int direction, ClientData *handlePtr); /* 153 */ + ClientData (*tcl_GetChannelInstanceData) (Tcl_Channel chan); /* 154 */ + int (*tcl_GetChannelMode) (Tcl_Channel chan); /* 155 */ + CONST84_RETURN char * (*tcl_GetChannelName) (Tcl_Channel chan); /* 156 */ + int (*tcl_GetChannelOption) (Tcl_Interp *interp, Tcl_Channel chan, const char *optionName, Tcl_DString *dsPtr); /* 157 */ + CONST86 Tcl_ChannelType * (*tcl_GetChannelType) (Tcl_Channel chan); /* 158 */ + int (*tcl_GetCommandInfo) (Tcl_Interp *interp, const char *cmdName, Tcl_CmdInfo *infoPtr); /* 159 */ + CONST84_RETURN char * (*tcl_GetCommandName) (Tcl_Interp *interp, Tcl_Command command); /* 160 */ + int (*tcl_GetErrno) (void); /* 161 */ + CONST84_RETURN char * (*tcl_GetHostName) (void); /* 162 */ + int (*tcl_GetInterpPath) (Tcl_Interp *askInterp, Tcl_Interp *slaveInterp); /* 163 */ + Tcl_Interp * (*tcl_GetMaster) (Tcl_Interp *interp); /* 164 */ + const char * (*tcl_GetNameOfExecutable) (void); /* 165 */ + Tcl_Obj * (*tcl_GetObjResult) (Tcl_Interp *interp); /* 166 */ +#if !defined(__WIN32__) && !defined(MAC_OSX_TCL) /* UNIX */ + int (*tcl_GetOpenFile) (Tcl_Interp *interp, const char *chanID, int forWriting, int checkUsage, ClientData *filePtr); /* 167 */ +#endif /* UNIX */ +#if defined(__WIN32__) /* WIN */ + void (*reserved167)(void); +#endif /* WIN */ +#ifdef MAC_OSX_TCL /* MACOSX */ + int (*tcl_GetOpenFile) (Tcl_Interp *interp, const char *chanID, int forWriting, int checkUsage, ClientData *filePtr); /* 167 */ +#endif /* MACOSX */ + Tcl_PathType (*tcl_GetPathType) (const char *path); /* 168 */ + int (*tcl_Gets) (Tcl_Channel chan, Tcl_DString *dsPtr); /* 169 */ + int (*tcl_GetsObj) (Tcl_Channel chan, Tcl_Obj *objPtr); /* 170 */ + int (*tcl_GetServiceMode) (void); /* 171 */ + Tcl_Interp * (*tcl_GetSlave) (Tcl_Interp *interp, const char *slaveName); /* 172 */ + Tcl_Channel (*tcl_GetStdChannel) (int type); /* 173 */ + CONST84_RETURN char * (*tcl_GetStringResult) (Tcl_Interp *interp); /* 174 */ + CONST84_RETURN char * (*tcl_GetVar) (Tcl_Interp *interp, const char *varName, int flags); /* 175 */ + CONST84_RETURN char * (*tcl_GetVar2) (Tcl_Interp *interp, const char *part1, const char *part2, int flags); /* 176 */ + int (*tcl_GlobalEval) (Tcl_Interp *interp, const char *command); /* 177 */ + int (*tcl_GlobalEvalObj) (Tcl_Interp *interp, Tcl_Obj *objPtr); /* 178 */ + int (*tcl_HideCommand) (Tcl_Interp *interp, const char *cmdName, const char *hiddenCmdToken); /* 179 */ + int (*tcl_Init) (Tcl_Interp *interp); /* 180 */ + void (*tcl_InitHashTable) (Tcl_HashTable *tablePtr, int keyType); /* 181 */ + int (*tcl_InputBlocked) (Tcl_Channel chan); /* 182 */ + int (*tcl_InputBuffered) (Tcl_Channel chan); /* 183 */ + int (*tcl_InterpDeleted) (Tcl_Interp *interp); /* 184 */ + int (*tcl_IsSafe) (Tcl_Interp *interp); /* 185 */ + char * (*tcl_JoinPath) (int argc, CONST84 char *const *argv, Tcl_DString *resultPtr); /* 186 */ + int (*tcl_LinkVar) (Tcl_Interp *interp, const char *varName, char *addr, int type); /* 187 */ + void (*reserved188)(void); + Tcl_Channel (*tcl_MakeFileChannel) (ClientData handle, int mode); /* 189 */ + int (*tcl_MakeSafe) (Tcl_Interp *interp); /* 190 */ + Tcl_Channel (*tcl_MakeTcpClientChannel) (ClientData tcpSocket); /* 191 */ + char * (*tcl_Merge) (int argc, CONST84 char *const *argv); /* 192 */ + Tcl_HashEntry * (*tcl_NextHashEntry) (Tcl_HashSearch *searchPtr); /* 193 */ + void (*tcl_NotifyChannel) (Tcl_Channel channel, int mask); /* 194 */ + Tcl_Obj * (*tcl_ObjGetVar2) (Tcl_Interp *interp, Tcl_Obj *part1Ptr, Tcl_Obj *part2Ptr, int flags); /* 195 */ + Tcl_Obj * (*tcl_ObjSetVar2) (Tcl_Interp *interp, Tcl_Obj *part1Ptr, Tcl_Obj *part2Ptr, Tcl_Obj *newValuePtr, int flags); /* 196 */ + Tcl_Channel (*tcl_OpenCommandChannel) (Tcl_Interp *interp, int argc, CONST84 char **argv, int flags); /* 197 */ + Tcl_Channel (*tcl_OpenFileChannel) (Tcl_Interp *interp, const char *fileName, const char *modeString, int permissions); /* 198 */ + Tcl_Channel (*tcl_OpenTcpClient) (Tcl_Interp *interp, int port, const char *address, const char *myaddr, int myport, int async); /* 199 */ + Tcl_Channel (*tcl_OpenTcpServer) (Tcl_Interp *interp, int port, const char *host, Tcl_TcpAcceptProc *acceptProc, ClientData callbackData); /* 200 */ + void (*tcl_Preserve) (ClientData data); /* 201 */ + void (*tcl_PrintDouble) (Tcl_Interp *interp, double value, char *dst); /* 202 */ + int (*tcl_PutEnv) (const char *assignment); /* 203 */ + CONST84_RETURN char * (*tcl_PosixError) (Tcl_Interp *interp); /* 204 */ + void (*tcl_QueueEvent) (Tcl_Event *evPtr, Tcl_QueuePosition position); /* 205 */ + int (*tcl_Read) (Tcl_Channel chan, char *bufPtr, int toRead); /* 206 */ + void (*tcl_ReapDetachedProcs) (void); /* 207 */ + int (*tcl_RecordAndEval) (Tcl_Interp *interp, const char *cmd, int flags); /* 208 */ + int (*tcl_RecordAndEvalObj) (Tcl_Interp *interp, Tcl_Obj *cmdPtr, int flags); /* 209 */ + void (*tcl_RegisterChannel) (Tcl_Interp *interp, Tcl_Channel chan); /* 210 */ + void (*tcl_RegisterObjType) (const Tcl_ObjType *typePtr); /* 211 */ + Tcl_RegExp (*tcl_RegExpCompile) (Tcl_Interp *interp, const char *pattern); /* 212 */ + int (*tcl_RegExpExec) (Tcl_Interp *interp, Tcl_RegExp regexp, const char *text, const char *start); /* 213 */ + int (*tcl_RegExpMatch) (Tcl_Interp *interp, const char *text, const char *pattern); /* 214 */ + void (*tcl_RegExpRange) (Tcl_RegExp regexp, int index, CONST84 char **startPtr, CONST84 char **endPtr); /* 215 */ + void (*tcl_Release) (ClientData clientData); /* 216 */ + void (*tcl_ResetResult) (Tcl_Interp *interp); /* 217 */ + int (*tcl_ScanElement) (const char *src, int *flagPtr); /* 218 */ + int (*tcl_ScanCountedElement) (const char *src, int length, int *flagPtr); /* 219 */ + int (*tcl_SeekOld) (Tcl_Channel chan, int offset, int mode); /* 220 */ + int (*tcl_ServiceAll) (void); /* 221 */ + int (*tcl_ServiceEvent) (int flags); /* 222 */ + void (*tcl_SetAssocData) (Tcl_Interp *interp, const char *name, Tcl_InterpDeleteProc *proc, ClientData clientData); /* 223 */ + void (*tcl_SetChannelBufferSize) (Tcl_Channel chan, int sz); /* 224 */ + int (*tcl_SetChannelOption) (Tcl_Interp *interp, Tcl_Channel chan, const char *optionName, const char *newValue); /* 225 */ + int (*tcl_SetCommandInfo) (Tcl_Interp *interp, const char *cmdName, const Tcl_CmdInfo *infoPtr); /* 226 */ + void (*tcl_SetErrno) (int err); /* 227 */ + void (*tcl_SetErrorCode) (Tcl_Interp *interp, ...); /* 228 */ + void (*tcl_SetMaxBlockTime) (const Tcl_Time *timePtr); /* 229 */ + void (*tcl_SetPanicProc) (Tcl_PanicProc *panicProc); /* 230 */ + int (*tcl_SetRecursionLimit) (Tcl_Interp *interp, int depth); /* 231 */ + void (*tcl_SetResult) (Tcl_Interp *interp, char *result, Tcl_FreeProc *freeProc); /* 232 */ + int (*tcl_SetServiceMode) (int mode); /* 233 */ + void (*tcl_SetObjErrorCode) (Tcl_Interp *interp, Tcl_Obj *errorObjPtr); /* 234 */ + void (*tcl_SetObjResult) (Tcl_Interp *interp, Tcl_Obj *resultObjPtr); /* 235 */ + void (*tcl_SetStdChannel) (Tcl_Channel channel, int type); /* 236 */ + CONST84_RETURN char * (*tcl_SetVar) (Tcl_Interp *interp, const char *varName, const char *newValue, int flags); /* 237 */ + CONST84_RETURN char * (*tcl_SetVar2) (Tcl_Interp *interp, const char *part1, const char *part2, const char *newValue, int flags); /* 238 */ + CONST84_RETURN char * (*tcl_SignalId) (int sig); /* 239 */ + CONST84_RETURN char * (*tcl_SignalMsg) (int sig); /* 240 */ + void (*tcl_SourceRCFile) (Tcl_Interp *interp); /* 241 */ + int (*tcl_SplitList) (Tcl_Interp *interp, const char *listStr, int *argcPtr, CONST84 char ***argvPtr); /* 242 */ + void (*tcl_SplitPath) (const char *path, int *argcPtr, CONST84 char ***argvPtr); /* 243 */ + void (*tcl_StaticPackage) (Tcl_Interp *interp, const char *pkgName, Tcl_PackageInitProc *initProc, Tcl_PackageInitProc *safeInitProc); /* 244 */ + int (*tcl_StringMatch) (const char *str, const char *pattern); /* 245 */ + int (*tcl_TellOld) (Tcl_Channel chan); /* 246 */ + int (*tcl_TraceVar) (Tcl_Interp *interp, const char *varName, int flags, Tcl_VarTraceProc *proc, ClientData clientData); /* 247 */ + int (*tcl_TraceVar2) (Tcl_Interp *interp, const char *part1, const char *part2, int flags, Tcl_VarTraceProc *proc, ClientData clientData); /* 248 */ + char * (*tcl_TranslateFileName) (Tcl_Interp *interp, const char *name, Tcl_DString *bufferPtr); /* 249 */ + int (*tcl_Ungets) (Tcl_Channel chan, const char *str, int len, int atHead); /* 250 */ + void (*tcl_UnlinkVar) (Tcl_Interp *interp, const char *varName); /* 251 */ + int (*tcl_UnregisterChannel) (Tcl_Interp *interp, Tcl_Channel chan); /* 252 */ + int (*tcl_UnsetVar) (Tcl_Interp *interp, const char *varName, int flags); /* 253 */ + int (*tcl_UnsetVar2) (Tcl_Interp *interp, const char *part1, const char *part2, int flags); /* 254 */ + void (*tcl_UntraceVar) (Tcl_Interp *interp, const char *varName, int flags, Tcl_VarTraceProc *proc, ClientData clientData); /* 255 */ + void (*tcl_UntraceVar2) (Tcl_Interp *interp, const char *part1, const char *part2, int flags, Tcl_VarTraceProc *proc, ClientData clientData); /* 256 */ + void (*tcl_UpdateLinkedVar) (Tcl_Interp *interp, const char *varName); /* 257 */ + int (*tcl_UpVar) (Tcl_Interp *interp, const char *frameName, const char *varName, const char *localName, int flags); /* 258 */ + int (*tcl_UpVar2) (Tcl_Interp *interp, const char *frameName, const char *part1, const char *part2, const char *localName, int flags); /* 259 */ + int (*tcl_VarEval) (Tcl_Interp *interp, ...); /* 260 */ + ClientData (*tcl_VarTraceInfo) (Tcl_Interp *interp, const char *varName, int flags, Tcl_VarTraceProc *procPtr, ClientData prevClientData); /* 261 */ + ClientData (*tcl_VarTraceInfo2) (Tcl_Interp *interp, const char *part1, const char *part2, int flags, Tcl_VarTraceProc *procPtr, ClientData prevClientData); /* 262 */ + int (*tcl_Write) (Tcl_Channel chan, const char *s, int slen); /* 263 */ + void (*tcl_WrongNumArgs) (Tcl_Interp *interp, int objc, Tcl_Obj *const objv[], const char *message); /* 264 */ + int (*tcl_DumpActiveMemory) (const char *fileName); /* 265 */ + void (*tcl_ValidateAllMemory) (const char *file, int line); /* 266 */ + void (*tcl_AppendResultVA) (Tcl_Interp *interp, va_list argList); /* 267 */ + void (*tcl_AppendStringsToObjVA) (Tcl_Obj *objPtr, va_list argList); /* 268 */ + char * (*tcl_HashStats) (Tcl_HashTable *tablePtr); /* 269 */ + CONST84_RETURN char * (*tcl_ParseVar) (Tcl_Interp *interp, const char *start, CONST84 char **termPtr); /* 270 */ + CONST84_RETURN char * (*tcl_PkgPresent) (Tcl_Interp *interp, const char *name, const char *version, int exact); /* 271 */ + CONST84_RETURN char * (*tcl_PkgPresentEx) (Tcl_Interp *interp, const char *name, const char *version, int exact, void *clientDataPtr); /* 272 */ + int (*tcl_PkgProvide) (Tcl_Interp *interp, const char *name, const char *version); /* 273 */ + CONST84_RETURN char * (*tcl_PkgRequire) (Tcl_Interp *interp, const char *name, const char *version, int exact); /* 274 */ + void (*tcl_SetErrorCodeVA) (Tcl_Interp *interp, va_list argList); /* 275 */ + int (*tcl_VarEvalVA) (Tcl_Interp *interp, va_list argList); /* 276 */ + Tcl_Pid (*tcl_WaitPid) (Tcl_Pid pid, int *statPtr, int options); /* 277 */ + void (*tcl_PanicVA) (const char *format, va_list argList); /* 278 */ + void (*tcl_GetVersion) (int *major, int *minor, int *patchLevel, int *type); /* 279 */ + void (*tcl_InitMemory) (Tcl_Interp *interp); /* 280 */ + Tcl_Channel (*tcl_StackChannel) (Tcl_Interp *interp, const Tcl_ChannelType *typePtr, ClientData instanceData, int mask, Tcl_Channel prevChan); /* 281 */ + int (*tcl_UnstackChannel) (Tcl_Interp *interp, Tcl_Channel chan); /* 282 */ + Tcl_Channel (*tcl_GetStackedChannel) (Tcl_Channel chan); /* 283 */ + void (*tcl_SetMainLoop) (Tcl_MainLoopProc *proc); /* 284 */ + void (*reserved285)(void); + void (*tcl_AppendObjToObj) (Tcl_Obj *objPtr, Tcl_Obj *appendObjPtr); /* 286 */ + Tcl_Encoding (*tcl_CreateEncoding) (const Tcl_EncodingType *typePtr); /* 287 */ + void (*tcl_CreateThreadExitHandler) (Tcl_ExitProc *proc, ClientData clientData); /* 288 */ + void (*tcl_DeleteThreadExitHandler) (Tcl_ExitProc *proc, ClientData clientData); /* 289 */ + void (*tcl_DiscardResult) (Tcl_SavedResult *statePtr); /* 290 */ + int (*tcl_EvalEx) (Tcl_Interp *interp, const char *script, int numBytes, int flags); /* 291 */ + int (*tcl_EvalObjv) (Tcl_Interp *interp, int objc, Tcl_Obj *const objv[], int flags); /* 292 */ + int (*tcl_EvalObjEx) (Tcl_Interp *interp, Tcl_Obj *objPtr, int flags); /* 293 */ + void (*tcl_ExitThread) (int status); /* 294 */ + int (*tcl_ExternalToUtf) (Tcl_Interp *interp, Tcl_Encoding encoding, const char *src, int srcLen, int flags, Tcl_EncodingState *statePtr, char *dst, int dstLen, int *srcReadPtr, int *dstWrotePtr, int *dstCharsPtr); /* 295 */ + char * (*tcl_ExternalToUtfDString) (Tcl_Encoding encoding, const char *src, int srcLen, Tcl_DString *dsPtr); /* 296 */ + void (*tcl_FinalizeThread) (void); /* 297 */ + void (*tcl_FinalizeNotifier) (ClientData clientData); /* 298 */ + void (*tcl_FreeEncoding) (Tcl_Encoding encoding); /* 299 */ + Tcl_ThreadId (*tcl_GetCurrentThread) (void); /* 300 */ + Tcl_Encoding (*tcl_GetEncoding) (Tcl_Interp *interp, const char *name); /* 301 */ + CONST84_RETURN char * (*tcl_GetEncodingName) (Tcl_Encoding encoding); /* 302 */ + void (*tcl_GetEncodingNames) (Tcl_Interp *interp); /* 303 */ + int (*tcl_GetIndexFromObjStruct) (Tcl_Interp *interp, Tcl_Obj *objPtr, const void *tablePtr, int offset, const char *msg, int flags, int *indexPtr); /* 304 */ + void * (*tcl_GetThreadData) (Tcl_ThreadDataKey *keyPtr, int size); /* 305 */ + Tcl_Obj * (*tcl_GetVar2Ex) (Tcl_Interp *interp, const char *part1, const char *part2, int flags); /* 306 */ + ClientData (*tcl_InitNotifier) (void); /* 307 */ + void (*tcl_MutexLock) (Tcl_Mutex *mutexPtr); /* 308 */ + void (*tcl_MutexUnlock) (Tcl_Mutex *mutexPtr); /* 309 */ + void (*tcl_ConditionNotify) (Tcl_Condition *condPtr); /* 310 */ + void (*tcl_ConditionWait) (Tcl_Condition *condPtr, Tcl_Mutex *mutexPtr, const Tcl_Time *timePtr); /* 311 */ + int (*tcl_NumUtfChars) (const char *src, int length); /* 312 */ + int (*tcl_ReadChars) (Tcl_Channel channel, Tcl_Obj *objPtr, int charsToRead, int appendFlag); /* 313 */ + void (*tcl_RestoreResult) (Tcl_Interp *interp, Tcl_SavedResult *statePtr); /* 314 */ + void (*tcl_SaveResult) (Tcl_Interp *interp, Tcl_SavedResult *statePtr); /* 315 */ + int (*tcl_SetSystemEncoding) (Tcl_Interp *interp, const char *name); /* 316 */ + Tcl_Obj * (*tcl_SetVar2Ex) (Tcl_Interp *interp, const char *part1, const char *part2, Tcl_Obj *newValuePtr, int flags); /* 317 */ + void (*tcl_ThreadAlert) (Tcl_ThreadId threadId); /* 318 */ + void (*tcl_ThreadQueueEvent) (Tcl_ThreadId threadId, Tcl_Event *evPtr, Tcl_QueuePosition position); /* 319 */ + Tcl_UniChar (*tcl_UniCharAtIndex) (const char *src, int index); /* 320 */ + Tcl_UniChar (*tcl_UniCharToLower) (int ch); /* 321 */ + Tcl_UniChar (*tcl_UniCharToTitle) (int ch); /* 322 */ + Tcl_UniChar (*tcl_UniCharToUpper) (int ch); /* 323 */ + int (*tcl_UniCharToUtf) (int ch, char *buf); /* 324 */ + CONST84_RETURN char * (*tcl_UtfAtIndex) (const char *src, int index); /* 325 */ + int (*tcl_UtfCharComplete) (const char *src, int length); /* 326 */ + int (*tcl_UtfBackslash) (const char *src, int *readPtr, char *dst); /* 327 */ + CONST84_RETURN char * (*tcl_UtfFindFirst) (const char *src, int ch); /* 328 */ + CONST84_RETURN char * (*tcl_UtfFindLast) (const char *src, int ch); /* 329 */ + CONST84_RETURN char * (*tcl_UtfNext) (const char *src); /* 330 */ + CONST84_RETURN char * (*tcl_UtfPrev) (const char *src, const char *start); /* 331 */ + int (*tcl_UtfToExternal) (Tcl_Interp *interp, Tcl_Encoding encoding, const char *src, int srcLen, int flags, Tcl_EncodingState *statePtr, char *dst, int dstLen, int *srcReadPtr, int *dstWrotePtr, int *dstCharsPtr); /* 332 */ + char * (*tcl_UtfToExternalDString) (Tcl_Encoding encoding, const char *src, int srcLen, Tcl_DString *dsPtr); /* 333 */ + int (*tcl_UtfToLower) (char *src); /* 334 */ + int (*tcl_UtfToTitle) (char *src); /* 335 */ + int (*tcl_UtfToUniChar) (const char *src, Tcl_UniChar *chPtr); /* 336 */ + int (*tcl_UtfToUpper) (char *src); /* 337 */ + int (*tcl_WriteChars) (Tcl_Channel chan, const char *src, int srcLen); /* 338 */ + int (*tcl_WriteObj) (Tcl_Channel chan, Tcl_Obj *objPtr); /* 339 */ + char * (*tcl_GetString) (Tcl_Obj *objPtr); /* 340 */ + CONST84_RETURN char * (*tcl_GetDefaultEncodingDir) (void); /* 341 */ + void (*tcl_SetDefaultEncodingDir) (const char *path); /* 342 */ + void (*tcl_AlertNotifier) (ClientData clientData); /* 343 */ + void (*tcl_ServiceModeHook) (int mode); /* 344 */ + int (*tcl_UniCharIsAlnum) (int ch); /* 345 */ + int (*tcl_UniCharIsAlpha) (int ch); /* 346 */ + int (*tcl_UniCharIsDigit) (int ch); /* 347 */ + int (*tcl_UniCharIsLower) (int ch); /* 348 */ + int (*tcl_UniCharIsSpace) (int ch); /* 349 */ + int (*tcl_UniCharIsUpper) (int ch); /* 350 */ + int (*tcl_UniCharIsWordChar) (int ch); /* 351 */ + int (*tcl_UniCharLen) (const Tcl_UniChar *uniStr); /* 352 */ + int (*tcl_UniCharNcmp) (const Tcl_UniChar *ucs, const Tcl_UniChar *uct, unsigned long numChars); /* 353 */ + char * (*tcl_UniCharToUtfDString) (const Tcl_UniChar *uniStr, int uniLength, Tcl_DString *dsPtr); /* 354 */ + Tcl_UniChar * (*tcl_UtfToUniCharDString) (const char *src, int length, Tcl_DString *dsPtr); /* 355 */ + Tcl_RegExp (*tcl_GetRegExpFromObj) (Tcl_Interp *interp, Tcl_Obj *patObj, int flags); /* 356 */ + Tcl_Obj * (*tcl_EvalTokens) (Tcl_Interp *interp, Tcl_Token *tokenPtr, int count); /* 357 */ + void (*tcl_FreeParse) (Tcl_Parse *parsePtr); /* 358 */ + void (*tcl_LogCommandInfo) (Tcl_Interp *interp, const char *script, const char *command, int length); /* 359 */ + int (*tcl_ParseBraces) (Tcl_Interp *interp, const char *start, int numBytes, Tcl_Parse *parsePtr, int append, CONST84 char **termPtr); /* 360 */ + int (*tcl_ParseCommand) (Tcl_Interp *interp, const char *start, int numBytes, int nested, Tcl_Parse *parsePtr); /* 361 */ + int (*tcl_ParseExpr) (Tcl_Interp *interp, const char *start, int numBytes, Tcl_Parse *parsePtr); /* 362 */ + int (*tcl_ParseQuotedString) (Tcl_Interp *interp, const char *start, int numBytes, Tcl_Parse *parsePtr, int append, CONST84 char **termPtr); /* 363 */ + int (*tcl_ParseVarName) (Tcl_Interp *interp, const char *start, int numBytes, Tcl_Parse *parsePtr, int append); /* 364 */ + char * (*tcl_GetCwd) (Tcl_Interp *interp, Tcl_DString *cwdPtr); /* 365 */ + int (*tcl_Chdir) (const char *dirName); /* 366 */ + int (*tcl_Access) (const char *path, int mode); /* 367 */ + int (*tcl_Stat) (const char *path, struct stat *bufPtr); /* 368 */ + int (*tcl_UtfNcmp) (const char *s1, const char *s2, unsigned long n); /* 369 */ + int (*tcl_UtfNcasecmp) (const char *s1, const char *s2, unsigned long n); /* 370 */ + int (*tcl_StringCaseMatch) (const char *str, const char *pattern, int nocase); /* 371 */ + int (*tcl_UniCharIsControl) (int ch); /* 372 */ + int (*tcl_UniCharIsGraph) (int ch); /* 373 */ + int (*tcl_UniCharIsPrint) (int ch); /* 374 */ + int (*tcl_UniCharIsPunct) (int ch); /* 375 */ + int (*tcl_RegExpExecObj) (Tcl_Interp *interp, Tcl_RegExp regexp, Tcl_Obj *textObj, int offset, int nmatches, int flags); /* 376 */ + void (*tcl_RegExpGetInfo) (Tcl_RegExp regexp, Tcl_RegExpInfo *infoPtr); /* 377 */ + Tcl_Obj * (*tcl_NewUnicodeObj) (const Tcl_UniChar *unicode, int numChars); /* 378 */ + void (*tcl_SetUnicodeObj) (Tcl_Obj *objPtr, const Tcl_UniChar *unicode, int numChars); /* 379 */ + int (*tcl_GetCharLength) (Tcl_Obj *objPtr); /* 380 */ + Tcl_UniChar (*tcl_GetUniChar) (Tcl_Obj *objPtr, int index); /* 381 */ + Tcl_UniChar * (*tcl_GetUnicode) (Tcl_Obj *objPtr); /* 382 */ + Tcl_Obj * (*tcl_GetRange) (Tcl_Obj *objPtr, int first, int last); /* 383 */ + void (*tcl_AppendUnicodeToObj) (Tcl_Obj *objPtr, const Tcl_UniChar *unicode, int length); /* 384 */ + int (*tcl_RegExpMatchObj) (Tcl_Interp *interp, Tcl_Obj *textObj, Tcl_Obj *patternObj); /* 385 */ + void (*tcl_SetNotifier) (Tcl_NotifierProcs *notifierProcPtr); /* 386 */ + Tcl_Mutex * (*tcl_GetAllocMutex) (void); /* 387 */ + int (*tcl_GetChannelNames) (Tcl_Interp *interp); /* 388 */ + int (*tcl_GetChannelNamesEx) (Tcl_Interp *interp, const char *pattern); /* 389 */ + int (*tcl_ProcObjCmd) (ClientData clientData, Tcl_Interp *interp, int objc, Tcl_Obj *const objv[]); /* 390 */ + void (*tcl_ConditionFinalize) (Tcl_Condition *condPtr); /* 391 */ + void (*tcl_MutexFinalize) (Tcl_Mutex *mutex); /* 392 */ + int (*tcl_CreateThread) (Tcl_ThreadId *idPtr, Tcl_ThreadCreateProc *proc, ClientData clientData, int stackSize, int flags); /* 393 */ + int (*tcl_ReadRaw) (Tcl_Channel chan, char *dst, int bytesToRead); /* 394 */ + int (*tcl_WriteRaw) (Tcl_Channel chan, const char *src, int srcLen); /* 395 */ + Tcl_Channel (*tcl_GetTopChannel) (Tcl_Channel chan); /* 396 */ + int (*tcl_ChannelBuffered) (Tcl_Channel chan); /* 397 */ + CONST84_RETURN char * (*tcl_ChannelName) (const Tcl_ChannelType *chanTypePtr); /* 398 */ + Tcl_ChannelTypeVersion (*tcl_ChannelVersion) (const Tcl_ChannelType *chanTypePtr); /* 399 */ + Tcl_DriverBlockModeProc * (*tcl_ChannelBlockModeProc) (const Tcl_ChannelType *chanTypePtr); /* 400 */ + Tcl_DriverCloseProc * (*tcl_ChannelCloseProc) (const Tcl_ChannelType *chanTypePtr); /* 401 */ + Tcl_DriverClose2Proc * (*tcl_ChannelClose2Proc) (const Tcl_ChannelType *chanTypePtr); /* 402 */ + Tcl_DriverInputProc * (*tcl_ChannelInputProc) (const Tcl_ChannelType *chanTypePtr); /* 403 */ + Tcl_DriverOutputProc * (*tcl_ChannelOutputProc) (const Tcl_ChannelType *chanTypePtr); /* 404 */ + Tcl_DriverSeekProc * (*tcl_ChannelSeekProc) (const Tcl_ChannelType *chanTypePtr); /* 405 */ + Tcl_DriverSetOptionProc * (*tcl_ChannelSetOptionProc) (const Tcl_ChannelType *chanTypePtr); /* 406 */ + Tcl_DriverGetOptionProc * (*tcl_ChannelGetOptionProc) (const Tcl_ChannelType *chanTypePtr); /* 407 */ + Tcl_DriverWatchProc * (*tcl_ChannelWatchProc) (const Tcl_ChannelType *chanTypePtr); /* 408 */ + Tcl_DriverGetHandleProc * (*tcl_ChannelGetHandleProc) (const Tcl_ChannelType *chanTypePtr); /* 409 */ + Tcl_DriverFlushProc * (*tcl_ChannelFlushProc) (const Tcl_ChannelType *chanTypePtr); /* 410 */ + Tcl_DriverHandlerProc * (*tcl_ChannelHandlerProc) (const Tcl_ChannelType *chanTypePtr); /* 411 */ + int (*tcl_JoinThread) (Tcl_ThreadId threadId, int *result); /* 412 */ + int (*tcl_IsChannelShared) (Tcl_Channel channel); /* 413 */ + int (*tcl_IsChannelRegistered) (Tcl_Interp *interp, Tcl_Channel channel); /* 414 */ + void (*tcl_CutChannel) (Tcl_Channel channel); /* 415 */ + void (*tcl_SpliceChannel) (Tcl_Channel channel); /* 416 */ + void (*tcl_ClearChannelHandlers) (Tcl_Channel channel); /* 417 */ + int (*tcl_IsChannelExisting) (const char *channelName); /* 418 */ + int (*tcl_UniCharNcasecmp) (const Tcl_UniChar *ucs, const Tcl_UniChar *uct, unsigned long numChars); /* 419 */ + int (*tcl_UniCharCaseMatch) (const Tcl_UniChar *uniStr, const Tcl_UniChar *uniPattern, int nocase); /* 420 */ + Tcl_HashEntry * (*tcl_FindHashEntry) (Tcl_HashTable *tablePtr, const void *key); /* 421 */ + Tcl_HashEntry * (*tcl_CreateHashEntry) (Tcl_HashTable *tablePtr, const void *key, int *newPtr); /* 422 */ + void (*tcl_InitCustomHashTable) (Tcl_HashTable *tablePtr, int keyType, const Tcl_HashKeyType *typePtr); /* 423 */ + void (*tcl_InitObjHashTable) (Tcl_HashTable *tablePtr); /* 424 */ + ClientData (*tcl_CommandTraceInfo) (Tcl_Interp *interp, const char *varName, int flags, Tcl_CommandTraceProc *procPtr, ClientData prevClientData); /* 425 */ + int (*tcl_TraceCommand) (Tcl_Interp *interp, const char *varName, int flags, Tcl_CommandTraceProc *proc, ClientData clientData); /* 426 */ + void (*tcl_UntraceCommand) (Tcl_Interp *interp, const char *varName, int flags, Tcl_CommandTraceProc *proc, ClientData clientData); /* 427 */ + char * (*tcl_AttemptAlloc) (unsigned int size); /* 428 */ + char * (*tcl_AttemptDbCkalloc) (unsigned int size, const char *file, int line); /* 429 */ + char * (*tcl_AttemptRealloc) (char *ptr, unsigned int size); /* 430 */ + char * (*tcl_AttemptDbCkrealloc) (char *ptr, unsigned int size, const char *file, int line); /* 431 */ + int (*tcl_AttemptSetObjLength) (Tcl_Obj *objPtr, int length); /* 432 */ + Tcl_ThreadId (*tcl_GetChannelThread) (Tcl_Channel channel); /* 433 */ + Tcl_UniChar * (*tcl_GetUnicodeFromObj) (Tcl_Obj *objPtr, int *lengthPtr); /* 434 */ + int (*tcl_GetMathFuncInfo) (Tcl_Interp *interp, const char *name, int *numArgsPtr, Tcl_ValueType **argTypesPtr, Tcl_MathProc **procPtr, ClientData *clientDataPtr); /* 435 */ + Tcl_Obj * (*tcl_ListMathFuncs) (Tcl_Interp *interp, const char *pattern); /* 436 */ + Tcl_Obj * (*tcl_SubstObj) (Tcl_Interp *interp, Tcl_Obj *objPtr, int flags); /* 437 */ + int (*tcl_DetachChannel) (Tcl_Interp *interp, Tcl_Channel channel); /* 438 */ + int (*tcl_IsStandardChannel) (Tcl_Channel channel); /* 439 */ + int (*tcl_FSCopyFile) (Tcl_Obj *srcPathPtr, Tcl_Obj *destPathPtr); /* 440 */ + int (*tcl_FSCopyDirectory) (Tcl_Obj *srcPathPtr, Tcl_Obj *destPathPtr, Tcl_Obj **errorPtr); /* 441 */ + int (*tcl_FSCreateDirectory) (Tcl_Obj *pathPtr); /* 442 */ + int (*tcl_FSDeleteFile) (Tcl_Obj *pathPtr); /* 443 */ + int (*tcl_FSLoadFile) (Tcl_Interp *interp, Tcl_Obj *pathPtr, const char *sym1, const char *sym2, Tcl_PackageInitProc **proc1Ptr, Tcl_PackageInitProc **proc2Ptr, Tcl_LoadHandle *handlePtr, Tcl_FSUnloadFileProc **unloadProcPtr); /* 444 */ + int (*tcl_FSMatchInDirectory) (Tcl_Interp *interp, Tcl_Obj *result, Tcl_Obj *pathPtr, const char *pattern, Tcl_GlobTypeData *types); /* 445 */ + Tcl_Obj * (*tcl_FSLink) (Tcl_Obj *pathPtr, Tcl_Obj *toPtr, int linkAction); /* 446 */ + int (*tcl_FSRemoveDirectory) (Tcl_Obj *pathPtr, int recursive, Tcl_Obj **errorPtr); /* 447 */ + int (*tcl_FSRenameFile) (Tcl_Obj *srcPathPtr, Tcl_Obj *destPathPtr); /* 448 */ + int (*tcl_FSLstat) (Tcl_Obj *pathPtr, Tcl_StatBuf *buf); /* 449 */ + int (*tcl_FSUtime) (Tcl_Obj *pathPtr, struct utimbuf *tval); /* 450 */ + int (*tcl_FSFileAttrsGet) (Tcl_Interp *interp, int index, Tcl_Obj *pathPtr, Tcl_Obj **objPtrRef); /* 451 */ + int (*tcl_FSFileAttrsSet) (Tcl_Interp *interp, int index, Tcl_Obj *pathPtr, Tcl_Obj *objPtr); /* 452 */ + const char *CONST86 * (*tcl_FSFileAttrStrings) (Tcl_Obj *pathPtr, Tcl_Obj **objPtrRef); /* 453 */ + int (*tcl_FSStat) (Tcl_Obj *pathPtr, Tcl_StatBuf *buf); /* 454 */ + int (*tcl_FSAccess) (Tcl_Obj *pathPtr, int mode); /* 455 */ + Tcl_Channel (*tcl_FSOpenFileChannel) (Tcl_Interp *interp, Tcl_Obj *pathPtr, const char *modeString, int permissions); /* 456 */ + Tcl_Obj * (*tcl_FSGetCwd) (Tcl_Interp *interp); /* 457 */ + int (*tcl_FSChdir) (Tcl_Obj *pathPtr); /* 458 */ + int (*tcl_FSConvertToPathType) (Tcl_Interp *interp, Tcl_Obj *pathPtr); /* 459 */ + Tcl_Obj * (*tcl_FSJoinPath) (Tcl_Obj *listObj, int elements); /* 460 */ + Tcl_Obj * (*tcl_FSSplitPath) (Tcl_Obj *pathPtr, int *lenPtr); /* 461 */ + int (*tcl_FSEqualPaths) (Tcl_Obj *firstPtr, Tcl_Obj *secondPtr); /* 462 */ + Tcl_Obj * (*tcl_FSGetNormalizedPath) (Tcl_Interp *interp, Tcl_Obj *pathPtr); /* 463 */ + Tcl_Obj * (*tcl_FSJoinToPath) (Tcl_Obj *pathPtr, int objc, Tcl_Obj *const objv[]); /* 464 */ + ClientData (*tcl_FSGetInternalRep) (Tcl_Obj *pathPtr, const Tcl_Filesystem *fsPtr); /* 465 */ + Tcl_Obj * (*tcl_FSGetTranslatedPath) (Tcl_Interp *interp, Tcl_Obj *pathPtr); /* 466 */ + int (*tcl_FSEvalFile) (Tcl_Interp *interp, Tcl_Obj *fileName); /* 467 */ + Tcl_Obj * (*tcl_FSNewNativePath) (const Tcl_Filesystem *fromFilesystem, ClientData clientData); /* 468 */ + const void * (*tcl_FSGetNativePath) (Tcl_Obj *pathPtr); /* 469 */ + Tcl_Obj * (*tcl_FSFileSystemInfo) (Tcl_Obj *pathPtr); /* 470 */ + Tcl_Obj * (*tcl_FSPathSeparator) (Tcl_Obj *pathPtr); /* 471 */ + Tcl_Obj * (*tcl_FSListVolumes) (void); /* 472 */ + int (*tcl_FSRegister) (ClientData clientData, const Tcl_Filesystem *fsPtr); /* 473 */ + int (*tcl_FSUnregister) (const Tcl_Filesystem *fsPtr); /* 474 */ + ClientData (*tcl_FSData) (const Tcl_Filesystem *fsPtr); /* 475 */ + const char * (*tcl_FSGetTranslatedStringPath) (Tcl_Interp *interp, Tcl_Obj *pathPtr); /* 476 */ + CONST86 Tcl_Filesystem * (*tcl_FSGetFileSystemForPath) (Tcl_Obj *pathPtr); /* 477 */ + Tcl_PathType (*tcl_FSGetPathType) (Tcl_Obj *pathPtr); /* 478 */ + int (*tcl_OutputBuffered) (Tcl_Channel chan); /* 479 */ + void (*tcl_FSMountsChanged) (const Tcl_Filesystem *fsPtr); /* 480 */ + int (*tcl_EvalTokensStandard) (Tcl_Interp *interp, Tcl_Token *tokenPtr, int count); /* 481 */ + void (*tcl_GetTime) (Tcl_Time *timeBuf); /* 482 */ + Tcl_Trace (*tcl_CreateObjTrace) (Tcl_Interp *interp, int level, int flags, Tcl_CmdObjTraceProc *objProc, ClientData clientData, Tcl_CmdObjTraceDeleteProc *delProc); /* 483 */ + int (*tcl_GetCommandInfoFromToken) (Tcl_Command token, Tcl_CmdInfo *infoPtr); /* 484 */ + int (*tcl_SetCommandInfoFromToken) (Tcl_Command token, const Tcl_CmdInfo *infoPtr); /* 485 */ + Tcl_Obj * (*tcl_DbNewWideIntObj) (Tcl_WideInt wideValue, const char *file, int line); /* 486 */ + int (*tcl_GetWideIntFromObj) (Tcl_Interp *interp, Tcl_Obj *objPtr, Tcl_WideInt *widePtr); /* 487 */ + Tcl_Obj * (*tcl_NewWideIntObj) (Tcl_WideInt wideValue); /* 488 */ + void (*tcl_SetWideIntObj) (Tcl_Obj *objPtr, Tcl_WideInt wideValue); /* 489 */ + Tcl_StatBuf * (*tcl_AllocStatBuf) (void); /* 490 */ + Tcl_WideInt (*tcl_Seek) (Tcl_Channel chan, Tcl_WideInt offset, int mode); /* 491 */ + Tcl_WideInt (*tcl_Tell) (Tcl_Channel chan); /* 492 */ + Tcl_DriverWideSeekProc * (*tcl_ChannelWideSeekProc) (const Tcl_ChannelType *chanTypePtr); /* 493 */ + int (*tcl_DictObjPut) (Tcl_Interp *interp, Tcl_Obj *dictPtr, Tcl_Obj *keyPtr, Tcl_Obj *valuePtr); /* 494 */ + int (*tcl_DictObjGet) (Tcl_Interp *interp, Tcl_Obj *dictPtr, Tcl_Obj *keyPtr, Tcl_Obj **valuePtrPtr); /* 495 */ + int (*tcl_DictObjRemove) (Tcl_Interp *interp, Tcl_Obj *dictPtr, Tcl_Obj *keyPtr); /* 496 */ + int (*tcl_DictObjSize) (Tcl_Interp *interp, Tcl_Obj *dictPtr, int *sizePtr); /* 497 */ + int (*tcl_DictObjFirst) (Tcl_Interp *interp, Tcl_Obj *dictPtr, Tcl_DictSearch *searchPtr, Tcl_Obj **keyPtrPtr, Tcl_Obj **valuePtrPtr, int *donePtr); /* 498 */ + void (*tcl_DictObjNext) (Tcl_DictSearch *searchPtr, Tcl_Obj **keyPtrPtr, Tcl_Obj **valuePtrPtr, int *donePtr); /* 499 */ + void (*tcl_DictObjDone) (Tcl_DictSearch *searchPtr); /* 500 */ + int (*tcl_DictObjPutKeyList) (Tcl_Interp *interp, Tcl_Obj *dictPtr, int keyc, Tcl_Obj *const *keyv, Tcl_Obj *valuePtr); /* 501 */ + int (*tcl_DictObjRemoveKeyList) (Tcl_Interp *interp, Tcl_Obj *dictPtr, int keyc, Tcl_Obj *const *keyv); /* 502 */ + Tcl_Obj * (*tcl_NewDictObj) (void); /* 503 */ + Tcl_Obj * (*tcl_DbNewDictObj) (const char *file, int line); /* 504 */ + void (*tcl_RegisterConfig) (Tcl_Interp *interp, const char *pkgName, const Tcl_Config *configuration, const char *valEncoding); /* 505 */ + Tcl_Namespace * (*tcl_CreateNamespace) (Tcl_Interp *interp, const char *name, ClientData clientData, Tcl_NamespaceDeleteProc *deleteProc); /* 506 */ + void (*tcl_DeleteNamespace) (Tcl_Namespace *nsPtr); /* 507 */ + int (*tcl_AppendExportList) (Tcl_Interp *interp, Tcl_Namespace *nsPtr, Tcl_Obj *objPtr); /* 508 */ + int (*tcl_Export) (Tcl_Interp *interp, Tcl_Namespace *nsPtr, const char *pattern, int resetListFirst); /* 509 */ + int (*tcl_Import) (Tcl_Interp *interp, Tcl_Namespace *nsPtr, const char *pattern, int allowOverwrite); /* 510 */ + int (*tcl_ForgetImport) (Tcl_Interp *interp, Tcl_Namespace *nsPtr, const char *pattern); /* 511 */ + Tcl_Namespace * (*tcl_GetCurrentNamespace) (Tcl_Interp *interp); /* 512 */ + Tcl_Namespace * (*tcl_GetGlobalNamespace) (Tcl_Interp *interp); /* 513 */ + Tcl_Namespace * (*tcl_FindNamespace) (Tcl_Interp *interp, const char *name, Tcl_Namespace *contextNsPtr, int flags); /* 514 */ + Tcl_Command (*tcl_FindCommand) (Tcl_Interp *interp, const char *name, Tcl_Namespace *contextNsPtr, int flags); /* 515 */ + Tcl_Command (*tcl_GetCommandFromObj) (Tcl_Interp *interp, Tcl_Obj *objPtr); /* 516 */ + void (*tcl_GetCommandFullName) (Tcl_Interp *interp, Tcl_Command command, Tcl_Obj *objPtr); /* 517 */ + int (*tcl_FSEvalFileEx) (Tcl_Interp *interp, Tcl_Obj *fileName, const char *encodingName); /* 518 */ + Tcl_ExitProc * (*tcl_SetExitProc) (Tcl_ExitProc *proc); /* 519 */ + void (*tcl_LimitAddHandler) (Tcl_Interp *interp, int type, Tcl_LimitHandlerProc *handlerProc, ClientData clientData, Tcl_LimitHandlerDeleteProc *deleteProc); /* 520 */ + void (*tcl_LimitRemoveHandler) (Tcl_Interp *interp, int type, Tcl_LimitHandlerProc *handlerProc, ClientData clientData); /* 521 */ + int (*tcl_LimitReady) (Tcl_Interp *interp); /* 522 */ + int (*tcl_LimitCheck) (Tcl_Interp *interp); /* 523 */ + int (*tcl_LimitExceeded) (Tcl_Interp *interp); /* 524 */ + void (*tcl_LimitSetCommands) (Tcl_Interp *interp, int commandLimit); /* 525 */ + void (*tcl_LimitSetTime) (Tcl_Interp *interp, Tcl_Time *timeLimitPtr); /* 526 */ + void (*tcl_LimitSetGranularity) (Tcl_Interp *interp, int type, int granularity); /* 527 */ + int (*tcl_LimitTypeEnabled) (Tcl_Interp *interp, int type); /* 528 */ + int (*tcl_LimitTypeExceeded) (Tcl_Interp *interp, int type); /* 529 */ + void (*tcl_LimitTypeSet) (Tcl_Interp *interp, int type); /* 530 */ + void (*tcl_LimitTypeReset) (Tcl_Interp *interp, int type); /* 531 */ + int (*tcl_LimitGetCommands) (Tcl_Interp *interp); /* 532 */ + void (*tcl_LimitGetTime) (Tcl_Interp *interp, Tcl_Time *timeLimitPtr); /* 533 */ + int (*tcl_LimitGetGranularity) (Tcl_Interp *interp, int type); /* 534 */ + Tcl_InterpState (*tcl_SaveInterpState) (Tcl_Interp *interp, int status); /* 535 */ + int (*tcl_RestoreInterpState) (Tcl_Interp *interp, Tcl_InterpState state); /* 536 */ + void (*tcl_DiscardInterpState) (Tcl_InterpState state); /* 537 */ + int (*tcl_SetReturnOptions) (Tcl_Interp *interp, Tcl_Obj *options); /* 538 */ + Tcl_Obj * (*tcl_GetReturnOptions) (Tcl_Interp *interp, int result); /* 539 */ + int (*tcl_IsEnsemble) (Tcl_Command token); /* 540 */ + Tcl_Command (*tcl_CreateEnsemble) (Tcl_Interp *interp, const char *name, Tcl_Namespace *namespacePtr, int flags); /* 541 */ + Tcl_Command (*tcl_FindEnsemble) (Tcl_Interp *interp, Tcl_Obj *cmdNameObj, int flags); /* 542 */ + int (*tcl_SetEnsembleSubcommandList) (Tcl_Interp *interp, Tcl_Command token, Tcl_Obj *subcmdList); /* 543 */ + int (*tcl_SetEnsembleMappingDict) (Tcl_Interp *interp, Tcl_Command token, Tcl_Obj *mapDict); /* 544 */ + int (*tcl_SetEnsembleUnknownHandler) (Tcl_Interp *interp, Tcl_Command token, Tcl_Obj *unknownList); /* 545 */ + int (*tcl_SetEnsembleFlags) (Tcl_Interp *interp, Tcl_Command token, int flags); /* 546 */ + int (*tcl_GetEnsembleSubcommandList) (Tcl_Interp *interp, Tcl_Command token, Tcl_Obj **subcmdListPtr); /* 547 */ + int (*tcl_GetEnsembleMappingDict) (Tcl_Interp *interp, Tcl_Command token, Tcl_Obj **mapDictPtr); /* 548 */ + int (*tcl_GetEnsembleUnknownHandler) (Tcl_Interp *interp, Tcl_Command token, Tcl_Obj **unknownListPtr); /* 549 */ + int (*tcl_GetEnsembleFlags) (Tcl_Interp *interp, Tcl_Command token, int *flagsPtr); /* 550 */ + int (*tcl_GetEnsembleNamespace) (Tcl_Interp *interp, Tcl_Command token, Tcl_Namespace **namespacePtrPtr); /* 551 */ + void (*tcl_SetTimeProc) (Tcl_GetTimeProc *getProc, Tcl_ScaleTimeProc *scaleProc, ClientData clientData); /* 552 */ + void (*tcl_QueryTimeProc) (Tcl_GetTimeProc **getProc, Tcl_ScaleTimeProc **scaleProc, ClientData *clientData); /* 553 */ + Tcl_DriverThreadActionProc * (*tcl_ChannelThreadActionProc) (const Tcl_ChannelType *chanTypePtr); /* 554 */ + Tcl_Obj * (*tcl_NewBignumObj) (mp_int *value); /* 555 */ + Tcl_Obj * (*tcl_DbNewBignumObj) (mp_int *value, const char *file, int line); /* 556 */ + void (*tcl_SetBignumObj) (Tcl_Obj *obj, mp_int *value); /* 557 */ + int (*tcl_GetBignumFromObj) (Tcl_Interp *interp, Tcl_Obj *obj, mp_int *value); /* 558 */ + int (*tcl_TakeBignumFromObj) (Tcl_Interp *interp, Tcl_Obj *obj, mp_int *value); /* 559 */ + int (*tcl_TruncateChannel) (Tcl_Channel chan, Tcl_WideInt length); /* 560 */ + Tcl_DriverTruncateProc * (*tcl_ChannelTruncateProc) (const Tcl_ChannelType *chanTypePtr); /* 561 */ + void (*tcl_SetChannelErrorInterp) (Tcl_Interp *interp, Tcl_Obj *msg); /* 562 */ + void (*tcl_GetChannelErrorInterp) (Tcl_Interp *interp, Tcl_Obj **msg); /* 563 */ + void (*tcl_SetChannelError) (Tcl_Channel chan, Tcl_Obj *msg); /* 564 */ + void (*tcl_GetChannelError) (Tcl_Channel chan, Tcl_Obj **msg); /* 565 */ + int (*tcl_InitBignumFromDouble) (Tcl_Interp *interp, double initval, mp_int *toInit); /* 566 */ + Tcl_Obj * (*tcl_GetNamespaceUnknownHandler) (Tcl_Interp *interp, Tcl_Namespace *nsPtr); /* 567 */ + int (*tcl_SetNamespaceUnknownHandler) (Tcl_Interp *interp, Tcl_Namespace *nsPtr, Tcl_Obj *handlerPtr); /* 568 */ + int (*tcl_GetEncodingFromObj) (Tcl_Interp *interp, Tcl_Obj *objPtr, Tcl_Encoding *encodingPtr); /* 569 */ + Tcl_Obj * (*tcl_GetEncodingSearchPath) (void); /* 570 */ + int (*tcl_SetEncodingSearchPath) (Tcl_Obj *searchPath); /* 571 */ + const char * (*tcl_GetEncodingNameFromEnvironment) (Tcl_DString *bufPtr); /* 572 */ + int (*tcl_PkgRequireProc) (Tcl_Interp *interp, const char *name, int objc, Tcl_Obj *const objv[], void *clientDataPtr); /* 573 */ + void (*tcl_AppendObjToErrorInfo) (Tcl_Interp *interp, Tcl_Obj *objPtr); /* 574 */ + void (*tcl_AppendLimitedToObj) (Tcl_Obj *objPtr, const char *bytes, int length, int limit, const char *ellipsis); /* 575 */ + Tcl_Obj * (*tcl_Format) (Tcl_Interp *interp, const char *format, int objc, Tcl_Obj *const objv[]); /* 576 */ + int (*tcl_AppendFormatToObj) (Tcl_Interp *interp, Tcl_Obj *objPtr, const char *format, int objc, Tcl_Obj *const objv[]); /* 577 */ + Tcl_Obj * (*tcl_ObjPrintf) (const char *format, ...) TCL_FORMAT_PRINTF(1, 2); /* 578 */ + void (*tcl_AppendPrintfToObj) (Tcl_Obj *objPtr, const char *format, ...) TCL_FORMAT_PRINTF(2, 3); /* 579 */ + int (*tcl_CancelEval) (Tcl_Interp *interp, Tcl_Obj *resultObjPtr, ClientData clientData, int flags); /* 580 */ + int (*tcl_Canceled) (Tcl_Interp *interp, int flags); /* 581 */ + int (*tcl_CreatePipe) (Tcl_Interp *interp, Tcl_Channel *rchan, Tcl_Channel *wchan, int flags); /* 582 */ + Tcl_Command (*tcl_NRCreateCommand) (Tcl_Interp *interp, const char *cmdName, Tcl_ObjCmdProc *proc, Tcl_ObjCmdProc *nreProc, ClientData clientData, Tcl_CmdDeleteProc *deleteProc); /* 583 */ + int (*tcl_NREvalObj) (Tcl_Interp *interp, Tcl_Obj *objPtr, int flags); /* 584 */ + int (*tcl_NREvalObjv) (Tcl_Interp *interp, int objc, Tcl_Obj *const objv[], int flags); /* 585 */ + int (*tcl_NRCmdSwap) (Tcl_Interp *interp, Tcl_Command cmd, int objc, Tcl_Obj *const objv[], int flags); /* 586 */ + void (*tcl_NRAddCallback) (Tcl_Interp *interp, Tcl_NRPostProc *postProcPtr, ClientData data0, ClientData data1, ClientData data2, ClientData data3); /* 587 */ + int (*tcl_NRCallObjProc) (Tcl_Interp *interp, Tcl_ObjCmdProc *objProc, ClientData clientData, int objc, Tcl_Obj *const objv[]); /* 588 */ + unsigned (*tcl_GetFSDeviceFromStat) (const Tcl_StatBuf *statPtr); /* 589 */ + unsigned (*tcl_GetFSInodeFromStat) (const Tcl_StatBuf *statPtr); /* 590 */ + unsigned (*tcl_GetModeFromStat) (const Tcl_StatBuf *statPtr); /* 591 */ + int (*tcl_GetLinkCountFromStat) (const Tcl_StatBuf *statPtr); /* 592 */ + int (*tcl_GetUserIdFromStat) (const Tcl_StatBuf *statPtr); /* 593 */ + int (*tcl_GetGroupIdFromStat) (const Tcl_StatBuf *statPtr); /* 594 */ + int (*tcl_GetDeviceTypeFromStat) (const Tcl_StatBuf *statPtr); /* 595 */ + Tcl_WideInt (*tcl_GetAccessTimeFromStat) (const Tcl_StatBuf *statPtr); /* 596 */ + Tcl_WideInt (*tcl_GetModificationTimeFromStat) (const Tcl_StatBuf *statPtr); /* 597 */ + Tcl_WideInt (*tcl_GetChangeTimeFromStat) (const Tcl_StatBuf *statPtr); /* 598 */ + Tcl_WideUInt (*tcl_GetSizeFromStat) (const Tcl_StatBuf *statPtr); /* 599 */ + Tcl_WideUInt (*tcl_GetBlocksFromStat) (const Tcl_StatBuf *statPtr); /* 600 */ + unsigned (*tcl_GetBlockSizeFromStat) (const Tcl_StatBuf *statPtr); /* 601 */ + int (*tcl_SetEnsembleParameterList) (Tcl_Interp *interp, Tcl_Command token, Tcl_Obj *paramList); /* 602 */ + int (*tcl_GetEnsembleParameterList) (Tcl_Interp *interp, Tcl_Command token, Tcl_Obj **paramListPtr); /* 603 */ + int (*tcl_ParseArgsObjv) (Tcl_Interp *interp, const Tcl_ArgvInfo *argTable, int *objcPtr, Tcl_Obj *const *objv, Tcl_Obj ***remObjv); /* 604 */ + int (*tcl_GetErrorLine) (Tcl_Interp *interp); /* 605 */ + void (*tcl_SetErrorLine) (Tcl_Interp *interp, int lineNum); /* 606 */ + void (*tcl_TransferResult) (Tcl_Interp *sourceInterp, int result, Tcl_Interp *targetInterp); /* 607 */ + int (*tcl_InterpActive) (Tcl_Interp *interp); /* 608 */ + void (*tcl_BackgroundException) (Tcl_Interp *interp, int code); /* 609 */ + int (*tcl_ZlibDeflate) (Tcl_Interp *interp, int format, Tcl_Obj *data, int level, Tcl_Obj *gzipHeaderDictObj); /* 610 */ + int (*tcl_ZlibInflate) (Tcl_Interp *interp, int format, Tcl_Obj *data, int buffersize, Tcl_Obj *gzipHeaderDictObj); /* 611 */ + unsigned int (*tcl_ZlibCRC32) (unsigned int crc, const unsigned char *buf, int len); /* 612 */ + unsigned int (*tcl_ZlibAdler32) (unsigned int adler, const unsigned char *buf, int len); /* 613 */ + int (*tcl_ZlibStreamInit) (Tcl_Interp *interp, int mode, int format, int level, Tcl_Obj *dictObj, Tcl_ZlibStream *zshandle); /* 614 */ + Tcl_Obj * (*tcl_ZlibStreamGetCommandName) (Tcl_ZlibStream zshandle); /* 615 */ + int (*tcl_ZlibStreamEof) (Tcl_ZlibStream zshandle); /* 616 */ + int (*tcl_ZlibStreamChecksum) (Tcl_ZlibStream zshandle); /* 617 */ + int (*tcl_ZlibStreamPut) (Tcl_ZlibStream zshandle, Tcl_Obj *data, int flush); /* 618 */ + int (*tcl_ZlibStreamGet) (Tcl_ZlibStream zshandle, Tcl_Obj *data, int count); /* 619 */ + int (*tcl_ZlibStreamClose) (Tcl_ZlibStream zshandle); /* 620 */ + int (*tcl_ZlibStreamReset) (Tcl_ZlibStream zshandle); /* 621 */ + void (*tcl_SetStartupScript) (Tcl_Obj *path, const char *encoding); /* 622 */ + Tcl_Obj * (*tcl_GetStartupScript) (const char **encodingPtr); /* 623 */ + int (*tcl_CloseEx) (Tcl_Interp *interp, Tcl_Channel chan, int flags); /* 624 */ + int (*tcl_NRExprObj) (Tcl_Interp *interp, Tcl_Obj *objPtr, Tcl_Obj *resultPtr); /* 625 */ + int (*tcl_NRSubstObj) (Tcl_Interp *interp, Tcl_Obj *objPtr, int flags); /* 626 */ + int (*tcl_LoadFile) (Tcl_Interp *interp, Tcl_Obj *pathPtr, const char *const symv[], int flags, void *procPtrs, Tcl_LoadHandle *handlePtr); /* 627 */ + void * (*tcl_FindSymbol) (Tcl_Interp *interp, Tcl_LoadHandle handle, const char *symbol); /* 628 */ + int (*tcl_FSUnloadFile) (Tcl_Interp *interp, Tcl_LoadHandle handlePtr); /* 629 */ + void (*tcl_ZlibStreamSetCompressionDictionary) (Tcl_ZlibStream zhandle, Tcl_Obj *compressionDictionaryObj); /* 630 */ +} TclStubs; + +#ifdef __cplusplus +extern "C" { +#endif +extern const TclStubs *tclStubsPtr; +#ifdef __cplusplus +} +#endif + +#if defined(USE_TCL_STUBS) + +/* + * Inline function declarations: + */ + +#define Tcl_PkgProvideEx \ + (tclStubsPtr->tcl_PkgProvideEx) /* 0 */ +#define Tcl_PkgRequireEx \ + (tclStubsPtr->tcl_PkgRequireEx) /* 1 */ +#define Tcl_Panic \ + (tclStubsPtr->tcl_Panic) /* 2 */ +#define Tcl_Alloc \ + (tclStubsPtr->tcl_Alloc) /* 3 */ +#define Tcl_Free \ + (tclStubsPtr->tcl_Free) /* 4 */ +#define Tcl_Realloc \ + (tclStubsPtr->tcl_Realloc) /* 5 */ +#define Tcl_DbCkalloc \ + (tclStubsPtr->tcl_DbCkalloc) /* 6 */ +#define Tcl_DbCkfree \ + (tclStubsPtr->tcl_DbCkfree) /* 7 */ +#define Tcl_DbCkrealloc \ + (tclStubsPtr->tcl_DbCkrealloc) /* 8 */ +#if !defined(__WIN32__) && !defined(MAC_OSX_TCL) /* UNIX */ +#define Tcl_CreateFileHandler \ + (tclStubsPtr->tcl_CreateFileHandler) /* 9 */ +#endif /* UNIX */ +#ifdef MAC_OSX_TCL /* MACOSX */ +#define Tcl_CreateFileHandler \ + (tclStubsPtr->tcl_CreateFileHandler) /* 9 */ +#endif /* MACOSX */ +#if !defined(__WIN32__) && !defined(MAC_OSX_TCL) /* UNIX */ +#define Tcl_DeleteFileHandler \ + (tclStubsPtr->tcl_DeleteFileHandler) /* 10 */ +#endif /* UNIX */ +#ifdef MAC_OSX_TCL /* MACOSX */ +#define Tcl_DeleteFileHandler \ + (tclStubsPtr->tcl_DeleteFileHandler) /* 10 */ +#endif /* MACOSX */ +#define Tcl_SetTimer \ + (tclStubsPtr->tcl_SetTimer) /* 11 */ +#define Tcl_Sleep \ + (tclStubsPtr->tcl_Sleep) /* 12 */ +#define Tcl_WaitForEvent \ + (tclStubsPtr->tcl_WaitForEvent) /* 13 */ +#define Tcl_AppendAllObjTypes \ + (tclStubsPtr->tcl_AppendAllObjTypes) /* 14 */ +#define Tcl_AppendStringsToObj \ + (tclStubsPtr->tcl_AppendStringsToObj) /* 15 */ +#define Tcl_AppendToObj \ + (tclStubsPtr->tcl_AppendToObj) /* 16 */ +#define Tcl_ConcatObj \ + (tclStubsPtr->tcl_ConcatObj) /* 17 */ +#define Tcl_ConvertToType \ + (tclStubsPtr->tcl_ConvertToType) /* 18 */ +#define Tcl_DbDecrRefCount \ + (tclStubsPtr->tcl_DbDecrRefCount) /* 19 */ +#define Tcl_DbIncrRefCount \ + (tclStubsPtr->tcl_DbIncrRefCount) /* 20 */ +#define Tcl_DbIsShared \ + (tclStubsPtr->tcl_DbIsShared) /* 21 */ +#define Tcl_DbNewBooleanObj \ + (tclStubsPtr->tcl_DbNewBooleanObj) /* 22 */ +#define Tcl_DbNewByteArrayObj \ + (tclStubsPtr->tcl_DbNewByteArrayObj) /* 23 */ +#define Tcl_DbNewDoubleObj \ + (tclStubsPtr->tcl_DbNewDoubleObj) /* 24 */ +#define Tcl_DbNewListObj \ + (tclStubsPtr->tcl_DbNewListObj) /* 25 */ +#define Tcl_DbNewLongObj \ + (tclStubsPtr->tcl_DbNewLongObj) /* 26 */ +#define Tcl_DbNewObj \ + (tclStubsPtr->tcl_DbNewObj) /* 27 */ +#define Tcl_DbNewStringObj \ + (tclStubsPtr->tcl_DbNewStringObj) /* 28 */ +#define Tcl_DuplicateObj \ + (tclStubsPtr->tcl_DuplicateObj) /* 29 */ +#define TclFreeObj \ + (tclStubsPtr->tclFreeObj) /* 30 */ +#define Tcl_GetBoolean \ + (tclStubsPtr->tcl_GetBoolean) /* 31 */ +#define Tcl_GetBooleanFromObj \ + (tclStubsPtr->tcl_GetBooleanFromObj) /* 32 */ +#define Tcl_GetByteArrayFromObj \ + (tclStubsPtr->tcl_GetByteArrayFromObj) /* 33 */ +#define Tcl_GetDouble \ + (tclStubsPtr->tcl_GetDouble) /* 34 */ +#define Tcl_GetDoubleFromObj \ + (tclStubsPtr->tcl_GetDoubleFromObj) /* 35 */ +#define Tcl_GetIndexFromObj \ + (tclStubsPtr->tcl_GetIndexFromObj) /* 36 */ +#define Tcl_GetInt \ + (tclStubsPtr->tcl_GetInt) /* 37 */ +#define Tcl_GetIntFromObj \ + (tclStubsPtr->tcl_GetIntFromObj) /* 38 */ +#define Tcl_GetLongFromObj \ + (tclStubsPtr->tcl_GetLongFromObj) /* 39 */ +#define Tcl_GetObjType \ + (tclStubsPtr->tcl_GetObjType) /* 40 */ +#define Tcl_GetStringFromObj \ + (tclStubsPtr->tcl_GetStringFromObj) /* 41 */ +#define Tcl_InvalidateStringRep \ + (tclStubsPtr->tcl_InvalidateStringRep) /* 42 */ +#define Tcl_ListObjAppendList \ + (tclStubsPtr->tcl_ListObjAppendList) /* 43 */ +#define Tcl_ListObjAppendElement \ + (tclStubsPtr->tcl_ListObjAppendElement) /* 44 */ +#define Tcl_ListObjGetElements \ + (tclStubsPtr->tcl_ListObjGetElements) /* 45 */ +#define Tcl_ListObjIndex \ + (tclStubsPtr->tcl_ListObjIndex) /* 46 */ +#define Tcl_ListObjLength \ + (tclStubsPtr->tcl_ListObjLength) /* 47 */ +#define Tcl_ListObjReplace \ + (tclStubsPtr->tcl_ListObjReplace) /* 48 */ +#define Tcl_NewBooleanObj \ + (tclStubsPtr->tcl_NewBooleanObj) /* 49 */ +#define Tcl_NewByteArrayObj \ + (tclStubsPtr->tcl_NewByteArrayObj) /* 50 */ +#define Tcl_NewDoubleObj \ + (tclStubsPtr->tcl_NewDoubleObj) /* 51 */ +#define Tcl_NewIntObj \ + (tclStubsPtr->tcl_NewIntObj) /* 52 */ +#define Tcl_NewListObj \ + (tclStubsPtr->tcl_NewListObj) /* 53 */ +#define Tcl_NewLongObj \ + (tclStubsPtr->tcl_NewLongObj) /* 54 */ +#define Tcl_NewObj \ + (tclStubsPtr->tcl_NewObj) /* 55 */ +#define Tcl_NewStringObj \ + (tclStubsPtr->tcl_NewStringObj) /* 56 */ +#define Tcl_SetBooleanObj \ + (tclStubsPtr->tcl_SetBooleanObj) /* 57 */ +#define Tcl_SetByteArrayLength \ + (tclStubsPtr->tcl_SetByteArrayLength) /* 58 */ +#define Tcl_SetByteArrayObj \ + (tclStubsPtr->tcl_SetByteArrayObj) /* 59 */ +#define Tcl_SetDoubleObj \ + (tclStubsPtr->tcl_SetDoubleObj) /* 60 */ +#define Tcl_SetIntObj \ + (tclStubsPtr->tcl_SetIntObj) /* 61 */ +#define Tcl_SetListObj \ + (tclStubsPtr->tcl_SetListObj) /* 62 */ +#define Tcl_SetLongObj \ + (tclStubsPtr->tcl_SetLongObj) /* 63 */ +#define Tcl_SetObjLength \ + (tclStubsPtr->tcl_SetObjLength) /* 64 */ +#define Tcl_SetStringObj \ + (tclStubsPtr->tcl_SetStringObj) /* 65 */ +#define Tcl_AddErrorInfo \ + (tclStubsPtr->tcl_AddErrorInfo) /* 66 */ +#define Tcl_AddObjErrorInfo \ + (tclStubsPtr->tcl_AddObjErrorInfo) /* 67 */ +#define Tcl_AllowExceptions \ + (tclStubsPtr->tcl_AllowExceptions) /* 68 */ +#define Tcl_AppendElement \ + (tclStubsPtr->tcl_AppendElement) /* 69 */ +#define Tcl_AppendResult \ + (tclStubsPtr->tcl_AppendResult) /* 70 */ +#define Tcl_AsyncCreate \ + (tclStubsPtr->tcl_AsyncCreate) /* 71 */ +#define Tcl_AsyncDelete \ + (tclStubsPtr->tcl_AsyncDelete) /* 72 */ +#define Tcl_AsyncInvoke \ + (tclStubsPtr->tcl_AsyncInvoke) /* 73 */ +#define Tcl_AsyncMark \ + (tclStubsPtr->tcl_AsyncMark) /* 74 */ +#define Tcl_AsyncReady \ + (tclStubsPtr->tcl_AsyncReady) /* 75 */ +#define Tcl_BackgroundError \ + (tclStubsPtr->tcl_BackgroundError) /* 76 */ +#define Tcl_Backslash \ + (tclStubsPtr->tcl_Backslash) /* 77 */ +#define Tcl_BadChannelOption \ + (tclStubsPtr->tcl_BadChannelOption) /* 78 */ +#define Tcl_CallWhenDeleted \ + (tclStubsPtr->tcl_CallWhenDeleted) /* 79 */ +#define Tcl_CancelIdleCall \ + (tclStubsPtr->tcl_CancelIdleCall) /* 80 */ +#define Tcl_Close \ + (tclStubsPtr->tcl_Close) /* 81 */ +#define Tcl_CommandComplete \ + (tclStubsPtr->tcl_CommandComplete) /* 82 */ +#define Tcl_Concat \ + (tclStubsPtr->tcl_Concat) /* 83 */ +#define Tcl_ConvertElement \ + (tclStubsPtr->tcl_ConvertElement) /* 84 */ +#define Tcl_ConvertCountedElement \ + (tclStubsPtr->tcl_ConvertCountedElement) /* 85 */ +#define Tcl_CreateAlias \ + (tclStubsPtr->tcl_CreateAlias) /* 86 */ +#define Tcl_CreateAliasObj \ + (tclStubsPtr->tcl_CreateAliasObj) /* 87 */ +#define Tcl_CreateChannel \ + (tclStubsPtr->tcl_CreateChannel) /* 88 */ +#define Tcl_CreateChannelHandler \ + (tclStubsPtr->tcl_CreateChannelHandler) /* 89 */ +#define Tcl_CreateCloseHandler \ + (tclStubsPtr->tcl_CreateCloseHandler) /* 90 */ +#define Tcl_CreateCommand \ + (tclStubsPtr->tcl_CreateCommand) /* 91 */ +#define Tcl_CreateEventSource \ + (tclStubsPtr->tcl_CreateEventSource) /* 92 */ +#define Tcl_CreateExitHandler \ + (tclStubsPtr->tcl_CreateExitHandler) /* 93 */ +#define Tcl_CreateInterp \ + (tclStubsPtr->tcl_CreateInterp) /* 94 */ +#define Tcl_CreateMathFunc \ + (tclStubsPtr->tcl_CreateMathFunc) /* 95 */ +#define Tcl_CreateObjCommand \ + (tclStubsPtr->tcl_CreateObjCommand) /* 96 */ +#define Tcl_CreateSlave \ + (tclStubsPtr->tcl_CreateSlave) /* 97 */ +#define Tcl_CreateTimerHandler \ + (tclStubsPtr->tcl_CreateTimerHandler) /* 98 */ +#define Tcl_CreateTrace \ + (tclStubsPtr->tcl_CreateTrace) /* 99 */ +#define Tcl_DeleteAssocData \ + (tclStubsPtr->tcl_DeleteAssocData) /* 100 */ +#define Tcl_DeleteChannelHandler \ + (tclStubsPtr->tcl_DeleteChannelHandler) /* 101 */ +#define Tcl_DeleteCloseHandler \ + (tclStubsPtr->tcl_DeleteCloseHandler) /* 102 */ +#define Tcl_DeleteCommand \ + (tclStubsPtr->tcl_DeleteCommand) /* 103 */ +#define Tcl_DeleteCommandFromToken \ + (tclStubsPtr->tcl_DeleteCommandFromToken) /* 104 */ +#define Tcl_DeleteEvents \ + (tclStubsPtr->tcl_DeleteEvents) /* 105 */ +#define Tcl_DeleteEventSource \ + (tclStubsPtr->tcl_DeleteEventSource) /* 106 */ +#define Tcl_DeleteExitHandler \ + (tclStubsPtr->tcl_DeleteExitHandler) /* 107 */ +#define Tcl_DeleteHashEntry \ + (tclStubsPtr->tcl_DeleteHashEntry) /* 108 */ +#define Tcl_DeleteHashTable \ + (tclStubsPtr->tcl_DeleteHashTable) /* 109 */ +#define Tcl_DeleteInterp \ + (tclStubsPtr->tcl_DeleteInterp) /* 110 */ +#define Tcl_DetachPids \ + (tclStubsPtr->tcl_DetachPids) /* 111 */ +#define Tcl_DeleteTimerHandler \ + (tclStubsPtr->tcl_DeleteTimerHandler) /* 112 */ +#define Tcl_DeleteTrace \ + (tclStubsPtr->tcl_DeleteTrace) /* 113 */ +#define Tcl_DontCallWhenDeleted \ + (tclStubsPtr->tcl_DontCallWhenDeleted) /* 114 */ +#define Tcl_DoOneEvent \ + (tclStubsPtr->tcl_DoOneEvent) /* 115 */ +#define Tcl_DoWhenIdle \ + (tclStubsPtr->tcl_DoWhenIdle) /* 116 */ +#define Tcl_DStringAppend \ + (tclStubsPtr->tcl_DStringAppend) /* 117 */ +#define Tcl_DStringAppendElement \ + (tclStubsPtr->tcl_DStringAppendElement) /* 118 */ +#define Tcl_DStringEndSublist \ + (tclStubsPtr->tcl_DStringEndSublist) /* 119 */ +#define Tcl_DStringFree \ + (tclStubsPtr->tcl_DStringFree) /* 120 */ +#define Tcl_DStringGetResult \ + (tclStubsPtr->tcl_DStringGetResult) /* 121 */ +#define Tcl_DStringInit \ + (tclStubsPtr->tcl_DStringInit) /* 122 */ +#define Tcl_DStringResult \ + (tclStubsPtr->tcl_DStringResult) /* 123 */ +#define Tcl_DStringSetLength \ + (tclStubsPtr->tcl_DStringSetLength) /* 124 */ +#define Tcl_DStringStartSublist \ + (tclStubsPtr->tcl_DStringStartSublist) /* 125 */ +#define Tcl_Eof \ + (tclStubsPtr->tcl_Eof) /* 126 */ +#define Tcl_ErrnoId \ + (tclStubsPtr->tcl_ErrnoId) /* 127 */ +#define Tcl_ErrnoMsg \ + (tclStubsPtr->tcl_ErrnoMsg) /* 128 */ +#define Tcl_Eval \ + (tclStubsPtr->tcl_Eval) /* 129 */ +#define Tcl_EvalFile \ + (tclStubsPtr->tcl_EvalFile) /* 130 */ +#define Tcl_EvalObj \ + (tclStubsPtr->tcl_EvalObj) /* 131 */ +#define Tcl_EventuallyFree \ + (tclStubsPtr->tcl_EventuallyFree) /* 132 */ +#define Tcl_Exit \ + (tclStubsPtr->tcl_Exit) /* 133 */ +#define Tcl_ExposeCommand \ + (tclStubsPtr->tcl_ExposeCommand) /* 134 */ +#define Tcl_ExprBoolean \ + (tclStubsPtr->tcl_ExprBoolean) /* 135 */ +#define Tcl_ExprBooleanObj \ + (tclStubsPtr->tcl_ExprBooleanObj) /* 136 */ +#define Tcl_ExprDouble \ + (tclStubsPtr->tcl_ExprDouble) /* 137 */ +#define Tcl_ExprDoubleObj \ + (tclStubsPtr->tcl_ExprDoubleObj) /* 138 */ +#define Tcl_ExprLong \ + (tclStubsPtr->tcl_ExprLong) /* 139 */ +#define Tcl_ExprLongObj \ + (tclStubsPtr->tcl_ExprLongObj) /* 140 */ +#define Tcl_ExprObj \ + (tclStubsPtr->tcl_ExprObj) /* 141 */ +#define Tcl_ExprString \ + (tclStubsPtr->tcl_ExprString) /* 142 */ +#define Tcl_Finalize \ + (tclStubsPtr->tcl_Finalize) /* 143 */ +#define Tcl_FindExecutable \ + (tclStubsPtr->tcl_FindExecutable) /* 144 */ +#define Tcl_FirstHashEntry \ + (tclStubsPtr->tcl_FirstHashEntry) /* 145 */ +#define Tcl_Flush \ + (tclStubsPtr->tcl_Flush) /* 146 */ +#define Tcl_FreeResult \ + (tclStubsPtr->tcl_FreeResult) /* 147 */ +#define Tcl_GetAlias \ + (tclStubsPtr->tcl_GetAlias) /* 148 */ +#define Tcl_GetAliasObj \ + (tclStubsPtr->tcl_GetAliasObj) /* 149 */ +#define Tcl_GetAssocData \ + (tclStubsPtr->tcl_GetAssocData) /* 150 */ +#define Tcl_GetChannel \ + (tclStubsPtr->tcl_GetChannel) /* 151 */ +#define Tcl_GetChannelBufferSize \ + (tclStubsPtr->tcl_GetChannelBufferSize) /* 152 */ +#define Tcl_GetChannelHandle \ + (tclStubsPtr->tcl_GetChannelHandle) /* 153 */ +#define Tcl_GetChannelInstanceData \ + (tclStubsPtr->tcl_GetChannelInstanceData) /* 154 */ +#define Tcl_GetChannelMode \ + (tclStubsPtr->tcl_GetChannelMode) /* 155 */ +#define Tcl_GetChannelName \ + (tclStubsPtr->tcl_GetChannelName) /* 156 */ +#define Tcl_GetChannelOption \ + (tclStubsPtr->tcl_GetChannelOption) /* 157 */ +#define Tcl_GetChannelType \ + (tclStubsPtr->tcl_GetChannelType) /* 158 */ +#define Tcl_GetCommandInfo \ + (tclStubsPtr->tcl_GetCommandInfo) /* 159 */ +#define Tcl_GetCommandName \ + (tclStubsPtr->tcl_GetCommandName) /* 160 */ +#define Tcl_GetErrno \ + (tclStubsPtr->tcl_GetErrno) /* 161 */ +#define Tcl_GetHostName \ + (tclStubsPtr->tcl_GetHostName) /* 162 */ +#define Tcl_GetInterpPath \ + (tclStubsPtr->tcl_GetInterpPath) /* 163 */ +#define Tcl_GetMaster \ + (tclStubsPtr->tcl_GetMaster) /* 164 */ +#define Tcl_GetNameOfExecutable \ + (tclStubsPtr->tcl_GetNameOfExecutable) /* 165 */ +#define Tcl_GetObjResult \ + (tclStubsPtr->tcl_GetObjResult) /* 166 */ +#if !defined(__WIN32__) && !defined(MAC_OSX_TCL) /* UNIX */ +#define Tcl_GetOpenFile \ + (tclStubsPtr->tcl_GetOpenFile) /* 167 */ +#endif /* UNIX */ +#ifdef MAC_OSX_TCL /* MACOSX */ +#define Tcl_GetOpenFile \ + (tclStubsPtr->tcl_GetOpenFile) /* 167 */ +#endif /* MACOSX */ +#define Tcl_GetPathType \ + (tclStubsPtr->tcl_GetPathType) /* 168 */ +#define Tcl_Gets \ + (tclStubsPtr->tcl_Gets) /* 169 */ +#define Tcl_GetsObj \ + (tclStubsPtr->tcl_GetsObj) /* 170 */ +#define Tcl_GetServiceMode \ + (tclStubsPtr->tcl_GetServiceMode) /* 171 */ +#define Tcl_GetSlave \ + (tclStubsPtr->tcl_GetSlave) /* 172 */ +#define Tcl_GetStdChannel \ + (tclStubsPtr->tcl_GetStdChannel) /* 173 */ +#define Tcl_GetStringResult \ + (tclStubsPtr->tcl_GetStringResult) /* 174 */ +#define Tcl_GetVar \ + (tclStubsPtr->tcl_GetVar) /* 175 */ +#define Tcl_GetVar2 \ + (tclStubsPtr->tcl_GetVar2) /* 176 */ +#define Tcl_GlobalEval \ + (tclStubsPtr->tcl_GlobalEval) /* 177 */ +#define Tcl_GlobalEvalObj \ + (tclStubsPtr->tcl_GlobalEvalObj) /* 178 */ +#define Tcl_HideCommand \ + (tclStubsPtr->tcl_HideCommand) /* 179 */ +#define Tcl_Init \ + (tclStubsPtr->tcl_Init) /* 180 */ +#define Tcl_InitHashTable \ + (tclStubsPtr->tcl_InitHashTable) /* 181 */ +#define Tcl_InputBlocked \ + (tclStubsPtr->tcl_InputBlocked) /* 182 */ +#define Tcl_InputBuffered \ + (tclStubsPtr->tcl_InputBuffered) /* 183 */ +#define Tcl_InterpDeleted \ + (tclStubsPtr->tcl_InterpDeleted) /* 184 */ +#define Tcl_IsSafe \ + (tclStubsPtr->tcl_IsSafe) /* 185 */ +#define Tcl_JoinPath \ + (tclStubsPtr->tcl_JoinPath) /* 186 */ +#define Tcl_LinkVar \ + (tclStubsPtr->tcl_LinkVar) /* 187 */ +/* Slot 188 is reserved */ +#define Tcl_MakeFileChannel \ + (tclStubsPtr->tcl_MakeFileChannel) /* 189 */ +#define Tcl_MakeSafe \ + (tclStubsPtr->tcl_MakeSafe) /* 190 */ +#define Tcl_MakeTcpClientChannel \ + (tclStubsPtr->tcl_MakeTcpClientChannel) /* 191 */ +#define Tcl_Merge \ + (tclStubsPtr->tcl_Merge) /* 192 */ +#define Tcl_NextHashEntry \ + (tclStubsPtr->tcl_NextHashEntry) /* 193 */ +#define Tcl_NotifyChannel \ + (tclStubsPtr->tcl_NotifyChannel) /* 194 */ +#define Tcl_ObjGetVar2 \ + (tclStubsPtr->tcl_ObjGetVar2) /* 195 */ +#define Tcl_ObjSetVar2 \ + (tclStubsPtr->tcl_ObjSetVar2) /* 196 */ +#define Tcl_OpenCommandChannel \ + (tclStubsPtr->tcl_OpenCommandChannel) /* 197 */ +#define Tcl_OpenFileChannel \ + (tclStubsPtr->tcl_OpenFileChannel) /* 198 */ +#define Tcl_OpenTcpClient \ + (tclStubsPtr->tcl_OpenTcpClient) /* 199 */ +#define Tcl_OpenTcpServer \ + (tclStubsPtr->tcl_OpenTcpServer) /* 200 */ +#define Tcl_Preserve \ + (tclStubsPtr->tcl_Preserve) /* 201 */ +#define Tcl_PrintDouble \ + (tclStubsPtr->tcl_PrintDouble) /* 202 */ +#define Tcl_PutEnv \ + (tclStubsPtr->tcl_PutEnv) /* 203 */ +#define Tcl_PosixError \ + (tclStubsPtr->tcl_PosixError) /* 204 */ +#define Tcl_QueueEvent \ + (tclStubsPtr->tcl_QueueEvent) /* 205 */ +#define Tcl_Read \ + (tclStubsPtr->tcl_Read) /* 206 */ +#define Tcl_ReapDetachedProcs \ + (tclStubsPtr->tcl_ReapDetachedProcs) /* 207 */ +#define Tcl_RecordAndEval \ + (tclStubsPtr->tcl_RecordAndEval) /* 208 */ +#define Tcl_RecordAndEvalObj \ + (tclStubsPtr->tcl_RecordAndEvalObj) /* 209 */ +#define Tcl_RegisterChannel \ + (tclStubsPtr->tcl_RegisterChannel) /* 210 */ +#define Tcl_RegisterObjType \ + (tclStubsPtr->tcl_RegisterObjType) /* 211 */ +#define Tcl_RegExpCompile \ + (tclStubsPtr->tcl_RegExpCompile) /* 212 */ +#define Tcl_RegExpExec \ + (tclStubsPtr->tcl_RegExpExec) /* 213 */ +#define Tcl_RegExpMatch \ + (tclStubsPtr->tcl_RegExpMatch) /* 214 */ +#define Tcl_RegExpRange \ + (tclStubsPtr->tcl_RegExpRange) /* 215 */ +#define Tcl_Release \ + (tclStubsPtr->tcl_Release) /* 216 */ +#define Tcl_ResetResult \ + (tclStubsPtr->tcl_ResetResult) /* 217 */ +#define Tcl_ScanElement \ + (tclStubsPtr->tcl_ScanElement) /* 218 */ +#define Tcl_ScanCountedElement \ + (tclStubsPtr->tcl_ScanCountedElement) /* 219 */ +#define Tcl_SeekOld \ + (tclStubsPtr->tcl_SeekOld) /* 220 */ +#define Tcl_ServiceAll \ + (tclStubsPtr->tcl_ServiceAll) /* 221 */ +#define Tcl_ServiceEvent \ + (tclStubsPtr->tcl_ServiceEvent) /* 222 */ +#define Tcl_SetAssocData \ + (tclStubsPtr->tcl_SetAssocData) /* 223 */ +#define Tcl_SetChannelBufferSize \ + (tclStubsPtr->tcl_SetChannelBufferSize) /* 224 */ +#define Tcl_SetChannelOption \ + (tclStubsPtr->tcl_SetChannelOption) /* 225 */ +#define Tcl_SetCommandInfo \ + (tclStubsPtr->tcl_SetCommandInfo) /* 226 */ +#define Tcl_SetErrno \ + (tclStubsPtr->tcl_SetErrno) /* 227 */ +#define Tcl_SetErrorCode \ + (tclStubsPtr->tcl_SetErrorCode) /* 228 */ +#define Tcl_SetMaxBlockTime \ + (tclStubsPtr->tcl_SetMaxBlockTime) /* 229 */ +#define Tcl_SetPanicProc \ + (tclStubsPtr->tcl_SetPanicProc) /* 230 */ +#define Tcl_SetRecursionLimit \ + (tclStubsPtr->tcl_SetRecursionLimit) /* 231 */ +#define Tcl_SetResult \ + (tclStubsPtr->tcl_SetResult) /* 232 */ +#define Tcl_SetServiceMode \ + (tclStubsPtr->tcl_SetServiceMode) /* 233 */ +#define Tcl_SetObjErrorCode \ + (tclStubsPtr->tcl_SetObjErrorCode) /* 234 */ +#define Tcl_SetObjResult \ + (tclStubsPtr->tcl_SetObjResult) /* 235 */ +#define Tcl_SetStdChannel \ + (tclStubsPtr->tcl_SetStdChannel) /* 236 */ +#define Tcl_SetVar \ + (tclStubsPtr->tcl_SetVar) /* 237 */ +#define Tcl_SetVar2 \ + (tclStubsPtr->tcl_SetVar2) /* 238 */ +#define Tcl_SignalId \ + (tclStubsPtr->tcl_SignalId) /* 239 */ +#define Tcl_SignalMsg \ + (tclStubsPtr->tcl_SignalMsg) /* 240 */ +#define Tcl_SourceRCFile \ + (tclStubsPtr->tcl_SourceRCFile) /* 241 */ +#define Tcl_SplitList \ + (tclStubsPtr->tcl_SplitList) /* 242 */ +#define Tcl_SplitPath \ + (tclStubsPtr->tcl_SplitPath) /* 243 */ +#define Tcl_StaticPackage \ + (tclStubsPtr->tcl_StaticPackage) /* 244 */ +#define Tcl_StringMatch \ + (tclStubsPtr->tcl_StringMatch) /* 245 */ +#define Tcl_TellOld \ + (tclStubsPtr->tcl_TellOld) /* 246 */ +#define Tcl_TraceVar \ + (tclStubsPtr->tcl_TraceVar) /* 247 */ +#define Tcl_TraceVar2 \ + (tclStubsPtr->tcl_TraceVar2) /* 248 */ +#define Tcl_TranslateFileName \ + (tclStubsPtr->tcl_TranslateFileName) /* 249 */ +#define Tcl_Ungets \ + (tclStubsPtr->tcl_Ungets) /* 250 */ +#define Tcl_UnlinkVar \ + (tclStubsPtr->tcl_UnlinkVar) /* 251 */ +#define Tcl_UnregisterChannel \ + (tclStubsPtr->tcl_UnregisterChannel) /* 252 */ +#define Tcl_UnsetVar \ + (tclStubsPtr->tcl_UnsetVar) /* 253 */ +#define Tcl_UnsetVar2 \ + (tclStubsPtr->tcl_UnsetVar2) /* 254 */ +#define Tcl_UntraceVar \ + (tclStubsPtr->tcl_UntraceVar) /* 255 */ +#define Tcl_UntraceVar2 \ + (tclStubsPtr->tcl_UntraceVar2) /* 256 */ +#define Tcl_UpdateLinkedVar \ + (tclStubsPtr->tcl_UpdateLinkedVar) /* 257 */ +#define Tcl_UpVar \ + (tclStubsPtr->tcl_UpVar) /* 258 */ +#define Tcl_UpVar2 \ + (tclStubsPtr->tcl_UpVar2) /* 259 */ +#define Tcl_VarEval \ + (tclStubsPtr->tcl_VarEval) /* 260 */ +#define Tcl_VarTraceInfo \ + (tclStubsPtr->tcl_VarTraceInfo) /* 261 */ +#define Tcl_VarTraceInfo2 \ + (tclStubsPtr->tcl_VarTraceInfo2) /* 262 */ +#define Tcl_Write \ + (tclStubsPtr->tcl_Write) /* 263 */ +#define Tcl_WrongNumArgs \ + (tclStubsPtr->tcl_WrongNumArgs) /* 264 */ +#define Tcl_DumpActiveMemory \ + (tclStubsPtr->tcl_DumpActiveMemory) /* 265 */ +#define Tcl_ValidateAllMemory \ + (tclStubsPtr->tcl_ValidateAllMemory) /* 266 */ +#define Tcl_AppendResultVA \ + (tclStubsPtr->tcl_AppendResultVA) /* 267 */ +#define Tcl_AppendStringsToObjVA \ + (tclStubsPtr->tcl_AppendStringsToObjVA) /* 268 */ +#define Tcl_HashStats \ + (tclStubsPtr->tcl_HashStats) /* 269 */ +#define Tcl_ParseVar \ + (tclStubsPtr->tcl_ParseVar) /* 270 */ +#define Tcl_PkgPresent \ + (tclStubsPtr->tcl_PkgPresent) /* 271 */ +#define Tcl_PkgPresentEx \ + (tclStubsPtr->tcl_PkgPresentEx) /* 272 */ +#define Tcl_PkgProvide \ + (tclStubsPtr->tcl_PkgProvide) /* 273 */ +#define Tcl_PkgRequire \ + (tclStubsPtr->tcl_PkgRequire) /* 274 */ +#define Tcl_SetErrorCodeVA \ + (tclStubsPtr->tcl_SetErrorCodeVA) /* 275 */ +#define Tcl_VarEvalVA \ + (tclStubsPtr->tcl_VarEvalVA) /* 276 */ +#define Tcl_WaitPid \ + (tclStubsPtr->tcl_WaitPid) /* 277 */ +#define Tcl_PanicVA \ + (tclStubsPtr->tcl_PanicVA) /* 278 */ +#define Tcl_GetVersion \ + (tclStubsPtr->tcl_GetVersion) /* 279 */ +#define Tcl_InitMemory \ + (tclStubsPtr->tcl_InitMemory) /* 280 */ +#define Tcl_StackChannel \ + (tclStubsPtr->tcl_StackChannel) /* 281 */ +#define Tcl_UnstackChannel \ + (tclStubsPtr->tcl_UnstackChannel) /* 282 */ +#define Tcl_GetStackedChannel \ + (tclStubsPtr->tcl_GetStackedChannel) /* 283 */ +#define Tcl_SetMainLoop \ + (tclStubsPtr->tcl_SetMainLoop) /* 284 */ +/* Slot 285 is reserved */ +#define Tcl_AppendObjToObj \ + (tclStubsPtr->tcl_AppendObjToObj) /* 286 */ +#define Tcl_CreateEncoding \ + (tclStubsPtr->tcl_CreateEncoding) /* 287 */ +#define Tcl_CreateThreadExitHandler \ + (tclStubsPtr->tcl_CreateThreadExitHandler) /* 288 */ +#define Tcl_DeleteThreadExitHandler \ + (tclStubsPtr->tcl_DeleteThreadExitHandler) /* 289 */ +#define Tcl_DiscardResult \ + (tclStubsPtr->tcl_DiscardResult) /* 290 */ +#define Tcl_EvalEx \ + (tclStubsPtr->tcl_EvalEx) /* 291 */ +#define Tcl_EvalObjv \ + (tclStubsPtr->tcl_EvalObjv) /* 292 */ +#define Tcl_EvalObjEx \ + (tclStubsPtr->tcl_EvalObjEx) /* 293 */ +#define Tcl_ExitThread \ + (tclStubsPtr->tcl_ExitThread) /* 294 */ +#define Tcl_ExternalToUtf \ + (tclStubsPtr->tcl_ExternalToUtf) /* 295 */ +#define Tcl_ExternalToUtfDString \ + (tclStubsPtr->tcl_ExternalToUtfDString) /* 296 */ +#define Tcl_FinalizeThread \ + (tclStubsPtr->tcl_FinalizeThread) /* 297 */ +#define Tcl_FinalizeNotifier \ + (tclStubsPtr->tcl_FinalizeNotifier) /* 298 */ +#define Tcl_FreeEncoding \ + (tclStubsPtr->tcl_FreeEncoding) /* 299 */ +#define Tcl_GetCurrentThread \ + (tclStubsPtr->tcl_GetCurrentThread) /* 300 */ +#define Tcl_GetEncoding \ + (tclStubsPtr->tcl_GetEncoding) /* 301 */ +#define Tcl_GetEncodingName \ + (tclStubsPtr->tcl_GetEncodingName) /* 302 */ +#define Tcl_GetEncodingNames \ + (tclStubsPtr->tcl_GetEncodingNames) /* 303 */ +#define Tcl_GetIndexFromObjStruct \ + (tclStubsPtr->tcl_GetIndexFromObjStruct) /* 304 */ +#define Tcl_GetThreadData \ + (tclStubsPtr->tcl_GetThreadData) /* 305 */ +#define Tcl_GetVar2Ex \ + (tclStubsPtr->tcl_GetVar2Ex) /* 306 */ +#define Tcl_InitNotifier \ + (tclStubsPtr->tcl_InitNotifier) /* 307 */ +#define Tcl_MutexLock \ + (tclStubsPtr->tcl_MutexLock) /* 308 */ +#define Tcl_MutexUnlock \ + (tclStubsPtr->tcl_MutexUnlock) /* 309 */ +#define Tcl_ConditionNotify \ + (tclStubsPtr->tcl_ConditionNotify) /* 310 */ +#define Tcl_ConditionWait \ + (tclStubsPtr->tcl_ConditionWait) /* 311 */ +#define Tcl_NumUtfChars \ + (tclStubsPtr->tcl_NumUtfChars) /* 312 */ +#define Tcl_ReadChars \ + (tclStubsPtr->tcl_ReadChars) /* 313 */ +#define Tcl_RestoreResult \ + (tclStubsPtr->tcl_RestoreResult) /* 314 */ +#define Tcl_SaveResult \ + (tclStubsPtr->tcl_SaveResult) /* 315 */ +#define Tcl_SetSystemEncoding \ + (tclStubsPtr->tcl_SetSystemEncoding) /* 316 */ +#define Tcl_SetVar2Ex \ + (tclStubsPtr->tcl_SetVar2Ex) /* 317 */ +#define Tcl_ThreadAlert \ + (tclStubsPtr->tcl_ThreadAlert) /* 318 */ +#define Tcl_ThreadQueueEvent \ + (tclStubsPtr->tcl_ThreadQueueEvent) /* 319 */ +#define Tcl_UniCharAtIndex \ + (tclStubsPtr->tcl_UniCharAtIndex) /* 320 */ +#define Tcl_UniCharToLower \ + (tclStubsPtr->tcl_UniCharToLower) /* 321 */ +#define Tcl_UniCharToTitle \ + (tclStubsPtr->tcl_UniCharToTitle) /* 322 */ +#define Tcl_UniCharToUpper \ + (tclStubsPtr->tcl_UniCharToUpper) /* 323 */ +#define Tcl_UniCharToUtf \ + (tclStubsPtr->tcl_UniCharToUtf) /* 324 */ +#define Tcl_UtfAtIndex \ + (tclStubsPtr->tcl_UtfAtIndex) /* 325 */ +#define Tcl_UtfCharComplete \ + (tclStubsPtr->tcl_UtfCharComplete) /* 326 */ +#define Tcl_UtfBackslash \ + (tclStubsPtr->tcl_UtfBackslash) /* 327 */ +#define Tcl_UtfFindFirst \ + (tclStubsPtr->tcl_UtfFindFirst) /* 328 */ +#define Tcl_UtfFindLast \ + (tclStubsPtr->tcl_UtfFindLast) /* 329 */ +#define Tcl_UtfNext \ + (tclStubsPtr->tcl_UtfNext) /* 330 */ +#define Tcl_UtfPrev \ + (tclStubsPtr->tcl_UtfPrev) /* 331 */ +#define Tcl_UtfToExternal \ + (tclStubsPtr->tcl_UtfToExternal) /* 332 */ +#define Tcl_UtfToExternalDString \ + (tclStubsPtr->tcl_UtfToExternalDString) /* 333 */ +#define Tcl_UtfToLower \ + (tclStubsPtr->tcl_UtfToLower) /* 334 */ +#define Tcl_UtfToTitle \ + (tclStubsPtr->tcl_UtfToTitle) /* 335 */ +#define Tcl_UtfToUniChar \ + (tclStubsPtr->tcl_UtfToUniChar) /* 336 */ +#define Tcl_UtfToUpper \ + (tclStubsPtr->tcl_UtfToUpper) /* 337 */ +#define Tcl_WriteChars \ + (tclStubsPtr->tcl_WriteChars) /* 338 */ +#define Tcl_WriteObj \ + (tclStubsPtr->tcl_WriteObj) /* 339 */ +#define Tcl_GetString \ + (tclStubsPtr->tcl_GetString) /* 340 */ +#define Tcl_GetDefaultEncodingDir \ + (tclStubsPtr->tcl_GetDefaultEncodingDir) /* 341 */ +#define Tcl_SetDefaultEncodingDir \ + (tclStubsPtr->tcl_SetDefaultEncodingDir) /* 342 */ +#define Tcl_AlertNotifier \ + (tclStubsPtr->tcl_AlertNotifier) /* 343 */ +#define Tcl_ServiceModeHook \ + (tclStubsPtr->tcl_ServiceModeHook) /* 344 */ +#define Tcl_UniCharIsAlnum \ + (tclStubsPtr->tcl_UniCharIsAlnum) /* 345 */ +#define Tcl_UniCharIsAlpha \ + (tclStubsPtr->tcl_UniCharIsAlpha) /* 346 */ +#define Tcl_UniCharIsDigit \ + (tclStubsPtr->tcl_UniCharIsDigit) /* 347 */ +#define Tcl_UniCharIsLower \ + (tclStubsPtr->tcl_UniCharIsLower) /* 348 */ +#define Tcl_UniCharIsSpace \ + (tclStubsPtr->tcl_UniCharIsSpace) /* 349 */ +#define Tcl_UniCharIsUpper \ + (tclStubsPtr->tcl_UniCharIsUpper) /* 350 */ +#define Tcl_UniCharIsWordChar \ + (tclStubsPtr->tcl_UniCharIsWordChar) /* 351 */ +#define Tcl_UniCharLen \ + (tclStubsPtr->tcl_UniCharLen) /* 352 */ +#define Tcl_UniCharNcmp \ + (tclStubsPtr->tcl_UniCharNcmp) /* 353 */ +#define Tcl_UniCharToUtfDString \ + (tclStubsPtr->tcl_UniCharToUtfDString) /* 354 */ +#define Tcl_UtfToUniCharDString \ + (tclStubsPtr->tcl_UtfToUniCharDString) /* 355 */ +#define Tcl_GetRegExpFromObj \ + (tclStubsPtr->tcl_GetRegExpFromObj) /* 356 */ +#define Tcl_EvalTokens \ + (tclStubsPtr->tcl_EvalTokens) /* 357 */ +#define Tcl_FreeParse \ + (tclStubsPtr->tcl_FreeParse) /* 358 */ +#define Tcl_LogCommandInfo \ + (tclStubsPtr->tcl_LogCommandInfo) /* 359 */ +#define Tcl_ParseBraces \ + (tclStubsPtr->tcl_ParseBraces) /* 360 */ +#define Tcl_ParseCommand \ + (tclStubsPtr->tcl_ParseCommand) /* 361 */ +#define Tcl_ParseExpr \ + (tclStubsPtr->tcl_ParseExpr) /* 362 */ +#define Tcl_ParseQuotedString \ + (tclStubsPtr->tcl_ParseQuotedString) /* 363 */ +#define Tcl_ParseVarName \ + (tclStubsPtr->tcl_ParseVarName) /* 364 */ +#define Tcl_GetCwd \ + (tclStubsPtr->tcl_GetCwd) /* 365 */ +#define Tcl_Chdir \ + (tclStubsPtr->tcl_Chdir) /* 366 */ +#define Tcl_Access \ + (tclStubsPtr->tcl_Access) /* 367 */ +#define Tcl_Stat \ + (tclStubsPtr->tcl_Stat) /* 368 */ +#define Tcl_UtfNcmp \ + (tclStubsPtr->tcl_UtfNcmp) /* 369 */ +#define Tcl_UtfNcasecmp \ + (tclStubsPtr->tcl_UtfNcasecmp) /* 370 */ +#define Tcl_StringCaseMatch \ + (tclStubsPtr->tcl_StringCaseMatch) /* 371 */ +#define Tcl_UniCharIsControl \ + (tclStubsPtr->tcl_UniCharIsControl) /* 372 */ +#define Tcl_UniCharIsGraph \ + (tclStubsPtr->tcl_UniCharIsGraph) /* 373 */ +#define Tcl_UniCharIsPrint \ + (tclStubsPtr->tcl_UniCharIsPrint) /* 374 */ +#define Tcl_UniCharIsPunct \ + (tclStubsPtr->tcl_UniCharIsPunct) /* 375 */ +#define Tcl_RegExpExecObj \ + (tclStubsPtr->tcl_RegExpExecObj) /* 376 */ +#define Tcl_RegExpGetInfo \ + (tclStubsPtr->tcl_RegExpGetInfo) /* 377 */ +#define Tcl_NewUnicodeObj \ + (tclStubsPtr->tcl_NewUnicodeObj) /* 378 */ +#define Tcl_SetUnicodeObj \ + (tclStubsPtr->tcl_SetUnicodeObj) /* 379 */ +#define Tcl_GetCharLength \ + (tclStubsPtr->tcl_GetCharLength) /* 380 */ +#define Tcl_GetUniChar \ + (tclStubsPtr->tcl_GetUniChar) /* 381 */ +#define Tcl_GetUnicode \ + (tclStubsPtr->tcl_GetUnicode) /* 382 */ +#define Tcl_GetRange \ + (tclStubsPtr->tcl_GetRange) /* 383 */ +#define Tcl_AppendUnicodeToObj \ + (tclStubsPtr->tcl_AppendUnicodeToObj) /* 384 */ +#define Tcl_RegExpMatchObj \ + (tclStubsPtr->tcl_RegExpMatchObj) /* 385 */ +#define Tcl_SetNotifier \ + (tclStubsPtr->tcl_SetNotifier) /* 386 */ +#define Tcl_GetAllocMutex \ + (tclStubsPtr->tcl_GetAllocMutex) /* 387 */ +#define Tcl_GetChannelNames \ + (tclStubsPtr->tcl_GetChannelNames) /* 388 */ +#define Tcl_GetChannelNamesEx \ + (tclStubsPtr->tcl_GetChannelNamesEx) /* 389 */ +#define Tcl_ProcObjCmd \ + (tclStubsPtr->tcl_ProcObjCmd) /* 390 */ +#define Tcl_ConditionFinalize \ + (tclStubsPtr->tcl_ConditionFinalize) /* 391 */ +#define Tcl_MutexFinalize \ + (tclStubsPtr->tcl_MutexFinalize) /* 392 */ +#define Tcl_CreateThread \ + (tclStubsPtr->tcl_CreateThread) /* 393 */ +#define Tcl_ReadRaw \ + (tclStubsPtr->tcl_ReadRaw) /* 394 */ +#define Tcl_WriteRaw \ + (tclStubsPtr->tcl_WriteRaw) /* 395 */ +#define Tcl_GetTopChannel \ + (tclStubsPtr->tcl_GetTopChannel) /* 396 */ +#define Tcl_ChannelBuffered \ + (tclStubsPtr->tcl_ChannelBuffered) /* 397 */ +#define Tcl_ChannelName \ + (tclStubsPtr->tcl_ChannelName) /* 398 */ +#define Tcl_ChannelVersion \ + (tclStubsPtr->tcl_ChannelVersion) /* 399 */ +#define Tcl_ChannelBlockModeProc \ + (tclStubsPtr->tcl_ChannelBlockModeProc) /* 400 */ +#define Tcl_ChannelCloseProc \ + (tclStubsPtr->tcl_ChannelCloseProc) /* 401 */ +#define Tcl_ChannelClose2Proc \ + (tclStubsPtr->tcl_ChannelClose2Proc) /* 402 */ +#define Tcl_ChannelInputProc \ + (tclStubsPtr->tcl_ChannelInputProc) /* 403 */ +#define Tcl_ChannelOutputProc \ + (tclStubsPtr->tcl_ChannelOutputProc) /* 404 */ +#define Tcl_ChannelSeekProc \ + (tclStubsPtr->tcl_ChannelSeekProc) /* 405 */ +#define Tcl_ChannelSetOptionProc \ + (tclStubsPtr->tcl_ChannelSetOptionProc) /* 406 */ +#define Tcl_ChannelGetOptionProc \ + (tclStubsPtr->tcl_ChannelGetOptionProc) /* 407 */ +#define Tcl_ChannelWatchProc \ + (tclStubsPtr->tcl_ChannelWatchProc) /* 408 */ +#define Tcl_ChannelGetHandleProc \ + (tclStubsPtr->tcl_ChannelGetHandleProc) /* 409 */ +#define Tcl_ChannelFlushProc \ + (tclStubsPtr->tcl_ChannelFlushProc) /* 410 */ +#define Tcl_ChannelHandlerProc \ + (tclStubsPtr->tcl_ChannelHandlerProc) /* 411 */ +#define Tcl_JoinThread \ + (tclStubsPtr->tcl_JoinThread) /* 412 */ +#define Tcl_IsChannelShared \ + (tclStubsPtr->tcl_IsChannelShared) /* 413 */ +#define Tcl_IsChannelRegistered \ + (tclStubsPtr->tcl_IsChannelRegistered) /* 414 */ +#define Tcl_CutChannel \ + (tclStubsPtr->tcl_CutChannel) /* 415 */ +#define Tcl_SpliceChannel \ + (tclStubsPtr->tcl_SpliceChannel) /* 416 */ +#define Tcl_ClearChannelHandlers \ + (tclStubsPtr->tcl_ClearChannelHandlers) /* 417 */ +#define Tcl_IsChannelExisting \ + (tclStubsPtr->tcl_IsChannelExisting) /* 418 */ +#define Tcl_UniCharNcasecmp \ + (tclStubsPtr->tcl_UniCharNcasecmp) /* 419 */ +#define Tcl_UniCharCaseMatch \ + (tclStubsPtr->tcl_UniCharCaseMatch) /* 420 */ +#define Tcl_FindHashEntry \ + (tclStubsPtr->tcl_FindHashEntry) /* 421 */ +#define Tcl_CreateHashEntry \ + (tclStubsPtr->tcl_CreateHashEntry) /* 422 */ +#define Tcl_InitCustomHashTable \ + (tclStubsPtr->tcl_InitCustomHashTable) /* 423 */ +#define Tcl_InitObjHashTable \ + (tclStubsPtr->tcl_InitObjHashTable) /* 424 */ +#define Tcl_CommandTraceInfo \ + (tclStubsPtr->tcl_CommandTraceInfo) /* 425 */ +#define Tcl_TraceCommand \ + (tclStubsPtr->tcl_TraceCommand) /* 426 */ +#define Tcl_UntraceCommand \ + (tclStubsPtr->tcl_UntraceCommand) /* 427 */ +#define Tcl_AttemptAlloc \ + (tclStubsPtr->tcl_AttemptAlloc) /* 428 */ +#define Tcl_AttemptDbCkalloc \ + (tclStubsPtr->tcl_AttemptDbCkalloc) /* 429 */ +#define Tcl_AttemptRealloc \ + (tclStubsPtr->tcl_AttemptRealloc) /* 430 */ +#define Tcl_AttemptDbCkrealloc \ + (tclStubsPtr->tcl_AttemptDbCkrealloc) /* 431 */ +#define Tcl_AttemptSetObjLength \ + (tclStubsPtr->tcl_AttemptSetObjLength) /* 432 */ +#define Tcl_GetChannelThread \ + (tclStubsPtr->tcl_GetChannelThread) /* 433 */ +#define Tcl_GetUnicodeFromObj \ + (tclStubsPtr->tcl_GetUnicodeFromObj) /* 434 */ +#define Tcl_GetMathFuncInfo \ + (tclStubsPtr->tcl_GetMathFuncInfo) /* 435 */ +#define Tcl_ListMathFuncs \ + (tclStubsPtr->tcl_ListMathFuncs) /* 436 */ +#define Tcl_SubstObj \ + (tclStubsPtr->tcl_SubstObj) /* 437 */ +#define Tcl_DetachChannel \ + (tclStubsPtr->tcl_DetachChannel) /* 438 */ +#define Tcl_IsStandardChannel \ + (tclStubsPtr->tcl_IsStandardChannel) /* 439 */ +#define Tcl_FSCopyFile \ + (tclStubsPtr->tcl_FSCopyFile) /* 440 */ +#define Tcl_FSCopyDirectory \ + (tclStubsPtr->tcl_FSCopyDirectory) /* 441 */ +#define Tcl_FSCreateDirectory \ + (tclStubsPtr->tcl_FSCreateDirectory) /* 442 */ +#define Tcl_FSDeleteFile \ + (tclStubsPtr->tcl_FSDeleteFile) /* 443 */ +#define Tcl_FSLoadFile \ + (tclStubsPtr->tcl_FSLoadFile) /* 444 */ +#define Tcl_FSMatchInDirectory \ + (tclStubsPtr->tcl_FSMatchInDirectory) /* 445 */ +#define Tcl_FSLink \ + (tclStubsPtr->tcl_FSLink) /* 446 */ +#define Tcl_FSRemoveDirectory \ + (tclStubsPtr->tcl_FSRemoveDirectory) /* 447 */ +#define Tcl_FSRenameFile \ + (tclStubsPtr->tcl_FSRenameFile) /* 448 */ +#define Tcl_FSLstat \ + (tclStubsPtr->tcl_FSLstat) /* 449 */ +#define Tcl_FSUtime \ + (tclStubsPtr->tcl_FSUtime) /* 450 */ +#define Tcl_FSFileAttrsGet \ + (tclStubsPtr->tcl_FSFileAttrsGet) /* 451 */ +#define Tcl_FSFileAttrsSet \ + (tclStubsPtr->tcl_FSFileAttrsSet) /* 452 */ +#define Tcl_FSFileAttrStrings \ + (tclStubsPtr->tcl_FSFileAttrStrings) /* 453 */ +#define Tcl_FSStat \ + (tclStubsPtr->tcl_FSStat) /* 454 */ +#define Tcl_FSAccess \ + (tclStubsPtr->tcl_FSAccess) /* 455 */ +#define Tcl_FSOpenFileChannel \ + (tclStubsPtr->tcl_FSOpenFileChannel) /* 456 */ +#define Tcl_FSGetCwd \ + (tclStubsPtr->tcl_FSGetCwd) /* 457 */ +#define Tcl_FSChdir \ + (tclStubsPtr->tcl_FSChdir) /* 458 */ +#define Tcl_FSConvertToPathType \ + (tclStubsPtr->tcl_FSConvertToPathType) /* 459 */ +#define Tcl_FSJoinPath \ + (tclStubsPtr->tcl_FSJoinPath) /* 460 */ +#define Tcl_FSSplitPath \ + (tclStubsPtr->tcl_FSSplitPath) /* 461 */ +#define Tcl_FSEqualPaths \ + (tclStubsPtr->tcl_FSEqualPaths) /* 462 */ +#define Tcl_FSGetNormalizedPath \ + (tclStubsPtr->tcl_FSGetNormalizedPath) /* 463 */ +#define Tcl_FSJoinToPath \ + (tclStubsPtr->tcl_FSJoinToPath) /* 464 */ +#define Tcl_FSGetInternalRep \ + (tclStubsPtr->tcl_FSGetInternalRep) /* 465 */ +#define Tcl_FSGetTranslatedPath \ + (tclStubsPtr->tcl_FSGetTranslatedPath) /* 466 */ +#define Tcl_FSEvalFile \ + (tclStubsPtr->tcl_FSEvalFile) /* 467 */ +#define Tcl_FSNewNativePath \ + (tclStubsPtr->tcl_FSNewNativePath) /* 468 */ +#define Tcl_FSGetNativePath \ + (tclStubsPtr->tcl_FSGetNativePath) /* 469 */ +#define Tcl_FSFileSystemInfo \ + (tclStubsPtr->tcl_FSFileSystemInfo) /* 470 */ +#define Tcl_FSPathSeparator \ + (tclStubsPtr->tcl_FSPathSeparator) /* 471 */ +#define Tcl_FSListVolumes \ + (tclStubsPtr->tcl_FSListVolumes) /* 472 */ +#define Tcl_FSRegister \ + (tclStubsPtr->tcl_FSRegister) /* 473 */ +#define Tcl_FSUnregister \ + (tclStubsPtr->tcl_FSUnregister) /* 474 */ +#define Tcl_FSData \ + (tclStubsPtr->tcl_FSData) /* 475 */ +#define Tcl_FSGetTranslatedStringPath \ + (tclStubsPtr->tcl_FSGetTranslatedStringPath) /* 476 */ +#define Tcl_FSGetFileSystemForPath \ + (tclStubsPtr->tcl_FSGetFileSystemForPath) /* 477 */ +#define Tcl_FSGetPathType \ + (tclStubsPtr->tcl_FSGetPathType) /* 478 */ +#define Tcl_OutputBuffered \ + (tclStubsPtr->tcl_OutputBuffered) /* 479 */ +#define Tcl_FSMountsChanged \ + (tclStubsPtr->tcl_FSMountsChanged) /* 480 */ +#define Tcl_EvalTokensStandard \ + (tclStubsPtr->tcl_EvalTokensStandard) /* 481 */ +#define Tcl_GetTime \ + (tclStubsPtr->tcl_GetTime) /* 482 */ +#define Tcl_CreateObjTrace \ + (tclStubsPtr->tcl_CreateObjTrace) /* 483 */ +#define Tcl_GetCommandInfoFromToken \ + (tclStubsPtr->tcl_GetCommandInfoFromToken) /* 484 */ +#define Tcl_SetCommandInfoFromToken \ + (tclStubsPtr->tcl_SetCommandInfoFromToken) /* 485 */ +#define Tcl_DbNewWideIntObj \ + (tclStubsPtr->tcl_DbNewWideIntObj) /* 486 */ +#define Tcl_GetWideIntFromObj \ + (tclStubsPtr->tcl_GetWideIntFromObj) /* 487 */ +#define Tcl_NewWideIntObj \ + (tclStubsPtr->tcl_NewWideIntObj) /* 488 */ +#define Tcl_SetWideIntObj \ + (tclStubsPtr->tcl_SetWideIntObj) /* 489 */ +#define Tcl_AllocStatBuf \ + (tclStubsPtr->tcl_AllocStatBuf) /* 490 */ +#define Tcl_Seek \ + (tclStubsPtr->tcl_Seek) /* 491 */ +#define Tcl_Tell \ + (tclStubsPtr->tcl_Tell) /* 492 */ +#define Tcl_ChannelWideSeekProc \ + (tclStubsPtr->tcl_ChannelWideSeekProc) /* 493 */ +#define Tcl_DictObjPut \ + (tclStubsPtr->tcl_DictObjPut) /* 494 */ +#define Tcl_DictObjGet \ + (tclStubsPtr->tcl_DictObjGet) /* 495 */ +#define Tcl_DictObjRemove \ + (tclStubsPtr->tcl_DictObjRemove) /* 496 */ +#define Tcl_DictObjSize \ + (tclStubsPtr->tcl_DictObjSize) /* 497 */ +#define Tcl_DictObjFirst \ + (tclStubsPtr->tcl_DictObjFirst) /* 498 */ +#define Tcl_DictObjNext \ + (tclStubsPtr->tcl_DictObjNext) /* 499 */ +#define Tcl_DictObjDone \ + (tclStubsPtr->tcl_DictObjDone) /* 500 */ +#define Tcl_DictObjPutKeyList \ + (tclStubsPtr->tcl_DictObjPutKeyList) /* 501 */ +#define Tcl_DictObjRemoveKeyList \ + (tclStubsPtr->tcl_DictObjRemoveKeyList) /* 502 */ +#define Tcl_NewDictObj \ + (tclStubsPtr->tcl_NewDictObj) /* 503 */ +#define Tcl_DbNewDictObj \ + (tclStubsPtr->tcl_DbNewDictObj) /* 504 */ +#define Tcl_RegisterConfig \ + (tclStubsPtr->tcl_RegisterConfig) /* 505 */ +#define Tcl_CreateNamespace \ + (tclStubsPtr->tcl_CreateNamespace) /* 506 */ +#define Tcl_DeleteNamespace \ + (tclStubsPtr->tcl_DeleteNamespace) /* 507 */ +#define Tcl_AppendExportList \ + (tclStubsPtr->tcl_AppendExportList) /* 508 */ +#define Tcl_Export \ + (tclStubsPtr->tcl_Export) /* 509 */ +#define Tcl_Import \ + (tclStubsPtr->tcl_Import) /* 510 */ +#define Tcl_ForgetImport \ + (tclStubsPtr->tcl_ForgetImport) /* 511 */ +#define Tcl_GetCurrentNamespace \ + (tclStubsPtr->tcl_GetCurrentNamespace) /* 512 */ +#define Tcl_GetGlobalNamespace \ + (tclStubsPtr->tcl_GetGlobalNamespace) /* 513 */ +#define Tcl_FindNamespace \ + (tclStubsPtr->tcl_FindNamespace) /* 514 */ +#define Tcl_FindCommand \ + (tclStubsPtr->tcl_FindCommand) /* 515 */ +#define Tcl_GetCommandFromObj \ + (tclStubsPtr->tcl_GetCommandFromObj) /* 516 */ +#define Tcl_GetCommandFullName \ + (tclStubsPtr->tcl_GetCommandFullName) /* 517 */ +#define Tcl_FSEvalFileEx \ + (tclStubsPtr->tcl_FSEvalFileEx) /* 518 */ +#define Tcl_SetExitProc \ + (tclStubsPtr->tcl_SetExitProc) /* 519 */ +#define Tcl_LimitAddHandler \ + (tclStubsPtr->tcl_LimitAddHandler) /* 520 */ +#define Tcl_LimitRemoveHandler \ + (tclStubsPtr->tcl_LimitRemoveHandler) /* 521 */ +#define Tcl_LimitReady \ + (tclStubsPtr->tcl_LimitReady) /* 522 */ +#define Tcl_LimitCheck \ + (tclStubsPtr->tcl_LimitCheck) /* 523 */ +#define Tcl_LimitExceeded \ + (tclStubsPtr->tcl_LimitExceeded) /* 524 */ +#define Tcl_LimitSetCommands \ + (tclStubsPtr->tcl_LimitSetCommands) /* 525 */ +#define Tcl_LimitSetTime \ + (tclStubsPtr->tcl_LimitSetTime) /* 526 */ +#define Tcl_LimitSetGranularity \ + (tclStubsPtr->tcl_LimitSetGranularity) /* 527 */ +#define Tcl_LimitTypeEnabled \ + (tclStubsPtr->tcl_LimitTypeEnabled) /* 528 */ +#define Tcl_LimitTypeExceeded \ + (tclStubsPtr->tcl_LimitTypeExceeded) /* 529 */ +#define Tcl_LimitTypeSet \ + (tclStubsPtr->tcl_LimitTypeSet) /* 530 */ +#define Tcl_LimitTypeReset \ + (tclStubsPtr->tcl_LimitTypeReset) /* 531 */ +#define Tcl_LimitGetCommands \ + (tclStubsPtr->tcl_LimitGetCommands) /* 532 */ +#define Tcl_LimitGetTime \ + (tclStubsPtr->tcl_LimitGetTime) /* 533 */ +#define Tcl_LimitGetGranularity \ + (tclStubsPtr->tcl_LimitGetGranularity) /* 534 */ +#define Tcl_SaveInterpState \ + (tclStubsPtr->tcl_SaveInterpState) /* 535 */ +#define Tcl_RestoreInterpState \ + (tclStubsPtr->tcl_RestoreInterpState) /* 536 */ +#define Tcl_DiscardInterpState \ + (tclStubsPtr->tcl_DiscardInterpState) /* 537 */ +#define Tcl_SetReturnOptions \ + (tclStubsPtr->tcl_SetReturnOptions) /* 538 */ +#define Tcl_GetReturnOptions \ + (tclStubsPtr->tcl_GetReturnOptions) /* 539 */ +#define Tcl_IsEnsemble \ + (tclStubsPtr->tcl_IsEnsemble) /* 540 */ +#define Tcl_CreateEnsemble \ + (tclStubsPtr->tcl_CreateEnsemble) /* 541 */ +#define Tcl_FindEnsemble \ + (tclStubsPtr->tcl_FindEnsemble) /* 542 */ +#define Tcl_SetEnsembleSubcommandList \ + (tclStubsPtr->tcl_SetEnsembleSubcommandList) /* 543 */ +#define Tcl_SetEnsembleMappingDict \ + (tclStubsPtr->tcl_SetEnsembleMappingDict) /* 544 */ +#define Tcl_SetEnsembleUnknownHandler \ + (tclStubsPtr->tcl_SetEnsembleUnknownHandler) /* 545 */ +#define Tcl_SetEnsembleFlags \ + (tclStubsPtr->tcl_SetEnsembleFlags) /* 546 */ +#define Tcl_GetEnsembleSubcommandList \ + (tclStubsPtr->tcl_GetEnsembleSubcommandList) /* 547 */ +#define Tcl_GetEnsembleMappingDict \ + (tclStubsPtr->tcl_GetEnsembleMappingDict) /* 548 */ +#define Tcl_GetEnsembleUnknownHandler \ + (tclStubsPtr->tcl_GetEnsembleUnknownHandler) /* 549 */ +#define Tcl_GetEnsembleFlags \ + (tclStubsPtr->tcl_GetEnsembleFlags) /* 550 */ +#define Tcl_GetEnsembleNamespace \ + (tclStubsPtr->tcl_GetEnsembleNamespace) /* 551 */ +#define Tcl_SetTimeProc \ + (tclStubsPtr->tcl_SetTimeProc) /* 552 */ +#define Tcl_QueryTimeProc \ + (tclStubsPtr->tcl_QueryTimeProc) /* 553 */ +#define Tcl_ChannelThreadActionProc \ + (tclStubsPtr->tcl_ChannelThreadActionProc) /* 554 */ +#define Tcl_NewBignumObj \ + (tclStubsPtr->tcl_NewBignumObj) /* 555 */ +#define Tcl_DbNewBignumObj \ + (tclStubsPtr->tcl_DbNewBignumObj) /* 556 */ +#define Tcl_SetBignumObj \ + (tclStubsPtr->tcl_SetBignumObj) /* 557 */ +#define Tcl_GetBignumFromObj \ + (tclStubsPtr->tcl_GetBignumFromObj) /* 558 */ +#define Tcl_TakeBignumFromObj \ + (tclStubsPtr->tcl_TakeBignumFromObj) /* 559 */ +#define Tcl_TruncateChannel \ + (tclStubsPtr->tcl_TruncateChannel) /* 560 */ +#define Tcl_ChannelTruncateProc \ + (tclStubsPtr->tcl_ChannelTruncateProc) /* 561 */ +#define Tcl_SetChannelErrorInterp \ + (tclStubsPtr->tcl_SetChannelErrorInterp) /* 562 */ +#define Tcl_GetChannelErrorInterp \ + (tclStubsPtr->tcl_GetChannelErrorInterp) /* 563 */ +#define Tcl_SetChannelError \ + (tclStubsPtr->tcl_SetChannelError) /* 564 */ +#define Tcl_GetChannelError \ + (tclStubsPtr->tcl_GetChannelError) /* 565 */ +#define Tcl_InitBignumFromDouble \ + (tclStubsPtr->tcl_InitBignumFromDouble) /* 566 */ +#define Tcl_GetNamespaceUnknownHandler \ + (tclStubsPtr->tcl_GetNamespaceUnknownHandler) /* 567 */ +#define Tcl_SetNamespaceUnknownHandler \ + (tclStubsPtr->tcl_SetNamespaceUnknownHandler) /* 568 */ +#define Tcl_GetEncodingFromObj \ + (tclStubsPtr->tcl_GetEncodingFromObj) /* 569 */ +#define Tcl_GetEncodingSearchPath \ + (tclStubsPtr->tcl_GetEncodingSearchPath) /* 570 */ +#define Tcl_SetEncodingSearchPath \ + (tclStubsPtr->tcl_SetEncodingSearchPath) /* 571 */ +#define Tcl_GetEncodingNameFromEnvironment \ + (tclStubsPtr->tcl_GetEncodingNameFromEnvironment) /* 572 */ +#define Tcl_PkgRequireProc \ + (tclStubsPtr->tcl_PkgRequireProc) /* 573 */ +#define Tcl_AppendObjToErrorInfo \ + (tclStubsPtr->tcl_AppendObjToErrorInfo) /* 574 */ +#define Tcl_AppendLimitedToObj \ + (tclStubsPtr->tcl_AppendLimitedToObj) /* 575 */ +#define Tcl_Format \ + (tclStubsPtr->tcl_Format) /* 576 */ +#define Tcl_AppendFormatToObj \ + (tclStubsPtr->tcl_AppendFormatToObj) /* 577 */ +#define Tcl_ObjPrintf \ + (tclStubsPtr->tcl_ObjPrintf) /* 578 */ +#define Tcl_AppendPrintfToObj \ + (tclStubsPtr->tcl_AppendPrintfToObj) /* 579 */ +#define Tcl_CancelEval \ + (tclStubsPtr->tcl_CancelEval) /* 580 */ +#define Tcl_Canceled \ + (tclStubsPtr->tcl_Canceled) /* 581 */ +#define Tcl_CreatePipe \ + (tclStubsPtr->tcl_CreatePipe) /* 582 */ +#define Tcl_NRCreateCommand \ + (tclStubsPtr->tcl_NRCreateCommand) /* 583 */ +#define Tcl_NREvalObj \ + (tclStubsPtr->tcl_NREvalObj) /* 584 */ +#define Tcl_NREvalObjv \ + (tclStubsPtr->tcl_NREvalObjv) /* 585 */ +#define Tcl_NRCmdSwap \ + (tclStubsPtr->tcl_NRCmdSwap) /* 586 */ +#define Tcl_NRAddCallback \ + (tclStubsPtr->tcl_NRAddCallback) /* 587 */ +#define Tcl_NRCallObjProc \ + (tclStubsPtr->tcl_NRCallObjProc) /* 588 */ +#define Tcl_GetFSDeviceFromStat \ + (tclStubsPtr->tcl_GetFSDeviceFromStat) /* 589 */ +#define Tcl_GetFSInodeFromStat \ + (tclStubsPtr->tcl_GetFSInodeFromStat) /* 590 */ +#define Tcl_GetModeFromStat \ + (tclStubsPtr->tcl_GetModeFromStat) /* 591 */ +#define Tcl_GetLinkCountFromStat \ + (tclStubsPtr->tcl_GetLinkCountFromStat) /* 592 */ +#define Tcl_GetUserIdFromStat \ + (tclStubsPtr->tcl_GetUserIdFromStat) /* 593 */ +#define Tcl_GetGroupIdFromStat \ + (tclStubsPtr->tcl_GetGroupIdFromStat) /* 594 */ +#define Tcl_GetDeviceTypeFromStat \ + (tclStubsPtr->tcl_GetDeviceTypeFromStat) /* 595 */ +#define Tcl_GetAccessTimeFromStat \ + (tclStubsPtr->tcl_GetAccessTimeFromStat) /* 596 */ +#define Tcl_GetModificationTimeFromStat \ + (tclStubsPtr->tcl_GetModificationTimeFromStat) /* 597 */ +#define Tcl_GetChangeTimeFromStat \ + (tclStubsPtr->tcl_GetChangeTimeFromStat) /* 598 */ +#define Tcl_GetSizeFromStat \ + (tclStubsPtr->tcl_GetSizeFromStat) /* 599 */ +#define Tcl_GetBlocksFromStat \ + (tclStubsPtr->tcl_GetBlocksFromStat) /* 600 */ +#define Tcl_GetBlockSizeFromStat \ + (tclStubsPtr->tcl_GetBlockSizeFromStat) /* 601 */ +#define Tcl_SetEnsembleParameterList \ + (tclStubsPtr->tcl_SetEnsembleParameterList) /* 602 */ +#define Tcl_GetEnsembleParameterList \ + (tclStubsPtr->tcl_GetEnsembleParameterList) /* 603 */ +#define Tcl_ParseArgsObjv \ + (tclStubsPtr->tcl_ParseArgsObjv) /* 604 */ +#define Tcl_GetErrorLine \ + (tclStubsPtr->tcl_GetErrorLine) /* 605 */ +#define Tcl_SetErrorLine \ + (tclStubsPtr->tcl_SetErrorLine) /* 606 */ +#define Tcl_TransferResult \ + (tclStubsPtr->tcl_TransferResult) /* 607 */ +#define Tcl_InterpActive \ + (tclStubsPtr->tcl_InterpActive) /* 608 */ +#define Tcl_BackgroundException \ + (tclStubsPtr->tcl_BackgroundException) /* 609 */ +#define Tcl_ZlibDeflate \ + (tclStubsPtr->tcl_ZlibDeflate) /* 610 */ +#define Tcl_ZlibInflate \ + (tclStubsPtr->tcl_ZlibInflate) /* 611 */ +#define Tcl_ZlibCRC32 \ + (tclStubsPtr->tcl_ZlibCRC32) /* 612 */ +#define Tcl_ZlibAdler32 \ + (tclStubsPtr->tcl_ZlibAdler32) /* 613 */ +#define Tcl_ZlibStreamInit \ + (tclStubsPtr->tcl_ZlibStreamInit) /* 614 */ +#define Tcl_ZlibStreamGetCommandName \ + (tclStubsPtr->tcl_ZlibStreamGetCommandName) /* 615 */ +#define Tcl_ZlibStreamEof \ + (tclStubsPtr->tcl_ZlibStreamEof) /* 616 */ +#define Tcl_ZlibStreamChecksum \ + (tclStubsPtr->tcl_ZlibStreamChecksum) /* 617 */ +#define Tcl_ZlibStreamPut \ + (tclStubsPtr->tcl_ZlibStreamPut) /* 618 */ +#define Tcl_ZlibStreamGet \ + (tclStubsPtr->tcl_ZlibStreamGet) /* 619 */ +#define Tcl_ZlibStreamClose \ + (tclStubsPtr->tcl_ZlibStreamClose) /* 620 */ +#define Tcl_ZlibStreamReset \ + (tclStubsPtr->tcl_ZlibStreamReset) /* 621 */ +#define Tcl_SetStartupScript \ + (tclStubsPtr->tcl_SetStartupScript) /* 622 */ +#define Tcl_GetStartupScript \ + (tclStubsPtr->tcl_GetStartupScript) /* 623 */ +#define Tcl_CloseEx \ + (tclStubsPtr->tcl_CloseEx) /* 624 */ +#define Tcl_NRExprObj \ + (tclStubsPtr->tcl_NRExprObj) /* 625 */ +#define Tcl_NRSubstObj \ + (tclStubsPtr->tcl_NRSubstObj) /* 626 */ +#define Tcl_LoadFile \ + (tclStubsPtr->tcl_LoadFile) /* 627 */ +#define Tcl_FindSymbol \ + (tclStubsPtr->tcl_FindSymbol) /* 628 */ +#define Tcl_FSUnloadFile \ + (tclStubsPtr->tcl_FSUnloadFile) /* 629 */ +#define Tcl_ZlibStreamSetCompressionDictionary \ + (tclStubsPtr->tcl_ZlibStreamSetCompressionDictionary) /* 630 */ + +#endif /* defined(USE_TCL_STUBS) */ + +/* !END!: Do not edit above this line. */ + +#if defined(USE_TCL_STUBS) +# undef Tcl_CreateInterp +# undef Tcl_FindExecutable +# undef Tcl_GetStringResult +# undef Tcl_Init +# undef Tcl_SetPanicProc +# undef Tcl_SetVar +# undef Tcl_StaticPackage +# undef TclFSGetNativePath +# define Tcl_CreateInterp() (tclStubsPtr->tcl_CreateInterp()) +# define Tcl_GetStringResult(interp) (tclStubsPtr->tcl_GetStringResult(interp)) +# define Tcl_Init(interp) (tclStubsPtr->tcl_Init(interp)) +# define Tcl_SetPanicProc(proc) (tclStubsPtr->tcl_SetPanicProc(proc)) +# define Tcl_SetVar(interp, varName, newValue, flags) \ + (tclStubsPtr->tcl_SetVar(interp, varName, newValue, flags)) +#endif + +#if defined(_WIN32) && defined(UNICODE) +# define Tcl_FindExecutable(arg) ((Tcl_FindExecutable)((const char *)(arg))) +# define Tcl_MainEx Tcl_MainExW + EXTERN void Tcl_MainExW(int argc, wchar_t **argv, + Tcl_AppInitProc *appInitProc, Tcl_Interp *interp); +#endif + +#undef TCL_STORAGE_CLASS +#define TCL_STORAGE_CLASS DLLIMPORT + +#endif /* _TCLDECLS */ ADDED compat/tcl-8.6/generic/tclPlatDecls.h Index: compat/tcl-8.6/generic/tclPlatDecls.h ================================================================== --- /dev/null +++ compat/tcl-8.6/generic/tclPlatDecls.h @@ -0,0 +1,120 @@ +/* + * tclPlatDecls.h -- + * + * Declarations of platform specific Tcl APIs. + * + * Copyright (c) 1998-1999 by Scriptics Corporation. + * All rights reserved. + */ + +#ifndef _TCLPLATDECLS +#define _TCLPLATDECLS + +#undef TCL_STORAGE_CLASS +#ifdef BUILD_tcl +# define TCL_STORAGE_CLASS DLLEXPORT +#else +# ifdef USE_TCL_STUBS +# define TCL_STORAGE_CLASS +# else +# define TCL_STORAGE_CLASS DLLIMPORT +# endif +#endif + +/* + * WARNING: This file is automatically generated by the tools/genStubs.tcl + * script. Any modifications to the function declarations below should be made + * in the generic/tcl.decls script. + */ + +/* + * TCHAR is needed here for win32, so if it is not defined yet do it here. + * This way, we don't need to include just for one define. + */ +#if (defined(_WIN32) || defined(__CYGWIN__)) && !defined(_TCHAR_DEFINED) +# if defined(_UNICODE) + typedef wchar_t TCHAR; +# else + typedef char TCHAR; +# endif +# define _TCHAR_DEFINED +#endif + +/* !BEGIN!: Do not edit below this line. */ + +/* + * Exported function declarations: + */ + +#if defined(__WIN32__) || defined(__CYGWIN__) /* WIN */ +/* 0 */ +EXTERN TCHAR * Tcl_WinUtfToTChar(const char *str, int len, + Tcl_DString *dsPtr); +/* 1 */ +EXTERN char * Tcl_WinTCharToUtf(const TCHAR *str, int len, + Tcl_DString *dsPtr); +#endif /* WIN */ +#ifdef MAC_OSX_TCL /* MACOSX */ +/* 0 */ +EXTERN int Tcl_MacOSXOpenBundleResources(Tcl_Interp *interp, + const char *bundleName, int hasResourceFile, + int maxPathLen, char *libraryPath); +/* 1 */ +EXTERN int Tcl_MacOSXOpenVersionedBundleResources( + Tcl_Interp *interp, const char *bundleName, + const char *bundleVersion, + int hasResourceFile, int maxPathLen, + char *libraryPath); +#endif /* MACOSX */ + +typedef struct TclPlatStubs { + int magic; + void *hooks; + +#if defined(__WIN32__) || defined(__CYGWIN__) /* WIN */ + TCHAR * (*tcl_WinUtfToTChar) (const char *str, int len, Tcl_DString *dsPtr); /* 0 */ + char * (*tcl_WinTCharToUtf) (const TCHAR *str, int len, Tcl_DString *dsPtr); /* 1 */ +#endif /* WIN */ +#ifdef MAC_OSX_TCL /* MACOSX */ + int (*tcl_MacOSXOpenBundleResources) (Tcl_Interp *interp, const char *bundleName, int hasResourceFile, int maxPathLen, char *libraryPath); /* 0 */ + int (*tcl_MacOSXOpenVersionedBundleResources) (Tcl_Interp *interp, const char *bundleName, const char *bundleVersion, int hasResourceFile, int maxPathLen, char *libraryPath); /* 1 */ +#endif /* MACOSX */ +} TclPlatStubs; + +#ifdef __cplusplus +extern "C" { +#endif +extern const TclPlatStubs *tclPlatStubsPtr; +#ifdef __cplusplus +} +#endif + +#if defined(USE_TCL_STUBS) + +/* + * Inline function declarations: + */ + +#if defined(__WIN32__) || defined(__CYGWIN__) /* WIN */ +#define Tcl_WinUtfToTChar \ + (tclPlatStubsPtr->tcl_WinUtfToTChar) /* 0 */ +#define Tcl_WinTCharToUtf \ + (tclPlatStubsPtr->tcl_WinTCharToUtf) /* 1 */ +#endif /* WIN */ +#ifdef MAC_OSX_TCL /* MACOSX */ +#define Tcl_MacOSXOpenBundleResources \ + (tclPlatStubsPtr->tcl_MacOSXOpenBundleResources) /* 0 */ +#define Tcl_MacOSXOpenVersionedBundleResources \ + (tclPlatStubsPtr->tcl_MacOSXOpenVersionedBundleResources) /* 1 */ +#endif /* MACOSX */ + +#endif /* defined(USE_TCL_STUBS) */ + +/* !END!: Do not edit above this line. */ + +#undef TCL_STORAGE_CLASS +#define TCL_STORAGE_CLASS DLLIMPORT + +#endif /* _TCLPLATDECLS */ + + ADDED compat/zlib/CMakeLists.txt Index: compat/zlib/CMakeLists.txt ================================================================== --- /dev/null +++ compat/zlib/CMakeLists.txt @@ -0,0 +1,249 @@ +cmake_minimum_required(VERSION 2.4.4) +set(CMAKE_ALLOW_LOOSE_LOOP_CONSTRUCTS ON) + +project(zlib C) + +set(VERSION "1.2.11") + +option(ASM686 "Enable building i686 assembly implementation") +option(AMD64 "Enable building amd64 assembly implementation") + +set(INSTALL_BIN_DIR "${CMAKE_INSTALL_PREFIX}/bin" CACHE PATH "Installation directory for executables") +set(INSTALL_LIB_DIR "${CMAKE_INSTALL_PREFIX}/lib" CACHE PATH "Installation directory for libraries") +set(INSTALL_INC_DIR "${CMAKE_INSTALL_PREFIX}/include" CACHE PATH "Installation directory for headers") +set(INSTALL_MAN_DIR "${CMAKE_INSTALL_PREFIX}/share/man" CACHE PATH "Installation directory for manual pages") +set(INSTALL_PKGCONFIG_DIR "${CMAKE_INSTALL_PREFIX}/share/pkgconfig" CACHE PATH "Installation directory for pkgconfig (.pc) files") + +include(CheckTypeSize) +include(CheckFunctionExists) +include(CheckIncludeFile) +include(CheckCSourceCompiles) +enable_testing() + +check_include_file(sys/types.h HAVE_SYS_TYPES_H) +check_include_file(stdint.h HAVE_STDINT_H) +check_include_file(stddef.h HAVE_STDDEF_H) + +# +# Check to see if we have large file support +# +set(CMAKE_REQUIRED_DEFINITIONS -D_LARGEFILE64_SOURCE=1) +# We add these other definitions here because CheckTypeSize.cmake +# in CMake 2.4.x does not automatically do so and we want +# compatibility with CMake 2.4.x. +if(HAVE_SYS_TYPES_H) + list(APPEND CMAKE_REQUIRED_DEFINITIONS -DHAVE_SYS_TYPES_H) +endif() +if(HAVE_STDINT_H) + list(APPEND CMAKE_REQUIRED_DEFINITIONS -DHAVE_STDINT_H) +endif() +if(HAVE_STDDEF_H) + list(APPEND CMAKE_REQUIRED_DEFINITIONS -DHAVE_STDDEF_H) +endif() +check_type_size(off64_t OFF64_T) +if(HAVE_OFF64_T) + add_definitions(-D_LARGEFILE64_SOURCE=1) +endif() +set(CMAKE_REQUIRED_DEFINITIONS) # clear variable + +# +# Check for fseeko +# +check_function_exists(fseeko HAVE_FSEEKO) +if(NOT HAVE_FSEEKO) + add_definitions(-DNO_FSEEKO) +endif() + +# +# Check for unistd.h +# +check_include_file(unistd.h Z_HAVE_UNISTD_H) + +if(MSVC) + set(CMAKE_DEBUG_POSTFIX "d") + add_definitions(-D_CRT_SECURE_NO_DEPRECATE) + add_definitions(-D_CRT_NONSTDC_NO_DEPRECATE) + include_directories(${CMAKE_CURRENT_SOURCE_DIR}) +endif() + +if(NOT CMAKE_CURRENT_SOURCE_DIR STREQUAL CMAKE_CURRENT_BINARY_DIR) + # If we're doing an out of source build and the user has a zconf.h + # in their source tree... + if(EXISTS ${CMAKE_CURRENT_SOURCE_DIR}/zconf.h) + message(STATUS "Renaming") + message(STATUS " ${CMAKE_CURRENT_SOURCE_DIR}/zconf.h") + message(STATUS "to 'zconf.h.included' because this file is included with zlib") + message(STATUS "but CMake generates it automatically in the build directory.") + file(RENAME ${CMAKE_CURRENT_SOURCE_DIR}/zconf.h ${CMAKE_CURRENT_SOURCE_DIR}/zconf.h.included) + endif() +endif() + +set(ZLIB_PC ${CMAKE_CURRENT_BINARY_DIR}/zlib.pc) +configure_file( ${CMAKE_CURRENT_SOURCE_DIR}/zlib.pc.cmakein + ${ZLIB_PC} @ONLY) +configure_file( ${CMAKE_CURRENT_SOURCE_DIR}/zconf.h.cmakein + ${CMAKE_CURRENT_BINARY_DIR}/zconf.h @ONLY) +include_directories(${CMAKE_CURRENT_BINARY_DIR} ${CMAKE_SOURCE_DIR}) + + +#============================================================================ +# zlib +#============================================================================ + +set(ZLIB_PUBLIC_HDRS + ${CMAKE_CURRENT_BINARY_DIR}/zconf.h + zlib.h +) +set(ZLIB_PRIVATE_HDRS + crc32.h + deflate.h + gzguts.h + inffast.h + inffixed.h + inflate.h + inftrees.h + trees.h + zutil.h +) +set(ZLIB_SRCS + adler32.c + compress.c + crc32.c + deflate.c + gzclose.c + gzlib.c + gzread.c + gzwrite.c + inflate.c + infback.c + inftrees.c + inffast.c + trees.c + uncompr.c + zutil.c +) + +if(NOT MINGW) + set(ZLIB_DLL_SRCS + win32/zlib1.rc # If present will override custom build rule below. + ) +endif() + +if(CMAKE_COMPILER_IS_GNUCC) + if(ASM686) + set(ZLIB_ASMS contrib/asm686/match.S) + elseif (AMD64) + set(ZLIB_ASMS contrib/amd64/amd64-match.S) + endif () + + if(ZLIB_ASMS) + add_definitions(-DASMV) + set_source_files_properties(${ZLIB_ASMS} PROPERTIES LANGUAGE C COMPILE_FLAGS -DNO_UNDERLINE) + endif() +endif() + +if(MSVC) + if(ASM686) + ENABLE_LANGUAGE(ASM_MASM) + set(ZLIB_ASMS + contrib/masmx86/inffas32.asm + contrib/masmx86/match686.asm + ) + elseif (AMD64) + ENABLE_LANGUAGE(ASM_MASM) + set(ZLIB_ASMS + contrib/masmx64/gvmat64.asm + contrib/masmx64/inffasx64.asm + ) + endif() + + if(ZLIB_ASMS) + add_definitions(-DASMV -DASMINF) + endif() +endif() + +# parse the full version number from zlib.h and include in ZLIB_FULL_VERSION +file(READ ${CMAKE_CURRENT_SOURCE_DIR}/zlib.h _zlib_h_contents) +string(REGEX REPLACE ".*#define[ \t]+ZLIB_VERSION[ \t]+\"([-0-9A-Za-z.]+)\".*" + "\\1" ZLIB_FULL_VERSION ${_zlib_h_contents}) + +if(MINGW) + # This gets us DLL resource information when compiling on MinGW. + if(NOT CMAKE_RC_COMPILER) + set(CMAKE_RC_COMPILER windres.exe) + endif() + + add_custom_command(OUTPUT ${CMAKE_CURRENT_BINARY_DIR}/zlib1rc.obj + COMMAND ${CMAKE_RC_COMPILER} + -D GCC_WINDRES + -I ${CMAKE_CURRENT_SOURCE_DIR} + -I ${CMAKE_CURRENT_BINARY_DIR} + -o ${CMAKE_CURRENT_BINARY_DIR}/zlib1rc.obj + -i ${CMAKE_CURRENT_SOURCE_DIR}/win32/zlib1.rc) + set(ZLIB_DLL_SRCS ${CMAKE_CURRENT_BINARY_DIR}/zlib1rc.obj) +endif(MINGW) + +add_library(zlib SHARED ${ZLIB_SRCS} ${ZLIB_ASMS} ${ZLIB_DLL_SRCS} ${ZLIB_PUBLIC_HDRS} ${ZLIB_PRIVATE_HDRS}) +add_library(zlibstatic STATIC ${ZLIB_SRCS} ${ZLIB_ASMS} ${ZLIB_PUBLIC_HDRS} ${ZLIB_PRIVATE_HDRS}) +set_target_properties(zlib PROPERTIES DEFINE_SYMBOL ZLIB_DLL) +set_target_properties(zlib PROPERTIES SOVERSION 1) + +if(NOT CYGWIN) + # This property causes shared libraries on Linux to have the full version + # encoded into their final filename. We disable this on Cygwin because + # it causes cygz-${ZLIB_FULL_VERSION}.dll to be created when cygz.dll + # seems to be the default. + # + # This has no effect with MSVC, on that platform the version info for + # the DLL comes from the resource file win32/zlib1.rc + set_target_properties(zlib PROPERTIES VERSION ${ZLIB_FULL_VERSION}) +endif() + +if(UNIX) + # On unix-like platforms the library is almost always called libz + set_target_properties(zlib zlibstatic PROPERTIES OUTPUT_NAME z) + if(NOT APPLE) + set_target_properties(zlib PROPERTIES LINK_FLAGS "-Wl,--version-script,\"${CMAKE_CURRENT_SOURCE_DIR}/zlib.map\"") + endif() +elseif(BUILD_SHARED_LIBS AND WIN32) + # Creates zlib1.dll when building shared library version + set_target_properties(zlib PROPERTIES SUFFIX "1.dll") +endif() + +if(NOT SKIP_INSTALL_LIBRARIES AND NOT SKIP_INSTALL_ALL ) + install(TARGETS zlib zlibstatic + RUNTIME DESTINATION "${INSTALL_BIN_DIR}" + ARCHIVE DESTINATION "${INSTALL_LIB_DIR}" + LIBRARY DESTINATION "${INSTALL_LIB_DIR}" ) +endif() +if(NOT SKIP_INSTALL_HEADERS AND NOT SKIP_INSTALL_ALL ) + install(FILES ${ZLIB_PUBLIC_HDRS} DESTINATION "${INSTALL_INC_DIR}") +endif() +if(NOT SKIP_INSTALL_FILES AND NOT SKIP_INSTALL_ALL ) + install(FILES zlib.3 DESTINATION "${INSTALL_MAN_DIR}/man3") +endif() +if(NOT SKIP_INSTALL_FILES AND NOT SKIP_INSTALL_ALL ) + install(FILES ${ZLIB_PC} DESTINATION "${INSTALL_PKGCONFIG_DIR}") +endif() + +#============================================================================ +# Example binaries +#============================================================================ + +add_executable(example test/example.c) +target_link_libraries(example zlib) +add_test(example example) + +add_executable(minigzip test/minigzip.c) +target_link_libraries(minigzip zlib) + +if(HAVE_OFF64_T) + add_executable(example64 test/example.c) + target_link_libraries(example64 zlib) + set_target_properties(example64 PROPERTIES COMPILE_FLAGS "-D_FILE_OFFSET_BITS=64") + add_test(example64 example64) + + add_executable(minigzip64 test/minigzip.c) + target_link_libraries(minigzip64 zlib) + set_target_properties(minigzip64 PROPERTIES COMPILE_FLAGS "-D_FILE_OFFSET_BITS=64") +endif() ADDED compat/zlib/ChangeLog Index: compat/zlib/ChangeLog ================================================================== --- /dev/null +++ compat/zlib/ChangeLog @@ -0,0 +1,1515 @@ + + ChangeLog file for zlib + +Changes in 1.2.11 (15 Jan 2017) +- Fix deflate stored bug when pulling last block from window +- Permit immediate deflateParams changes before any deflate input + +Changes in 1.2.10 (2 Jan 2017) +- Avoid warnings on snprintf() return value +- Fix bug in deflate_stored() for zero-length input +- Fix bug in gzwrite.c that produced corrupt gzip files +- Remove files to be installed before copying them in Makefile.in +- Add warnings when compiling with assembler code + +Changes in 1.2.9 (31 Dec 2016) +- Fix contrib/minizip to permit unzipping with desktop API [Zouzou] +- Improve contrib/blast to return unused bytes +- Assure that gzoffset() is correct when appending +- Improve compress() and uncompress() to support large lengths +- Fix bug in test/example.c where error code not saved +- Remedy Coverity warning [Randers-Pehrson] +- Improve speed of gzprintf() in transparent mode +- Fix inflateInit2() bug when windowBits is 16 or 32 +- Change DEBUG macro to ZLIB_DEBUG +- Avoid uninitialized access by gzclose_w() +- Allow building zlib outside of the source directory +- Fix bug that accepted invalid zlib header when windowBits is zero +- Fix gzseek() problem on MinGW due to buggy _lseeki64 there +- Loop on write() calls in gzwrite.c in case of non-blocking I/O +- Add --warn (-w) option to ./configure for more compiler warnings +- Reject a window size of 256 bytes if not using the zlib wrapper +- Fix bug when level 0 used with Z_HUFFMAN or Z_RLE +- Add --debug (-d) option to ./configure to define ZLIB_DEBUG +- Fix bugs in creating a very large gzip header +- Add uncompress2() function, which returns the input size used +- Assure that deflateParams() will not switch functions mid-block +- Dramatically speed up deflation for level 0 (storing) +- Add gzfread(), duplicating the interface of fread() +- Add gzfwrite(), duplicating the interface of fwrite() +- Add deflateGetDictionary() function +- Use snprintf() for later versions of Microsoft C +- Fix *Init macros to use z_ prefix when requested +- Replace as400 with os400 for OS/400 support [Monnerat] +- Add crc32_z() and adler32_z() functions with size_t lengths +- Update Visual Studio project files [AraHaan] + +Changes in 1.2.8 (28 Apr 2013) +- Update contrib/minizip/iowin32.c for Windows RT [Vollant] +- Do not force Z_CONST for C++ +- Clean up contrib/vstudio [Roß] +- Correct spelling error in zlib.h +- Fix mixed line endings in contrib/vstudio + +Changes in 1.2.7.3 (13 Apr 2013) +- Fix version numbers and DLL names in contrib/vstudio/*/zlib.rc + +Changes in 1.2.7.2 (13 Apr 2013) +- Change check for a four-byte type back to hexadecimal +- Fix typo in win32/Makefile.msc +- Add casts in gzwrite.c for pointer differences + +Changes in 1.2.7.1 (24 Mar 2013) +- Replace use of unsafe string functions with snprintf if available +- Avoid including stddef.h on Windows for Z_SOLO compile [Niessink] +- Fix gzgetc undefine when Z_PREFIX set [Turk] +- Eliminate use of mktemp in Makefile (not always available) +- Fix bug in 'F' mode for gzopen() +- Add inflateGetDictionary() function +- Correct comment in deflate.h +- Use _snprintf for snprintf in Microsoft C +- On Darwin, only use /usr/bin/libtool if libtool is not Apple +- Delete "--version" file if created by "ar --version" [Richard G.] +- Fix configure check for veracity of compiler error return codes +- Fix CMake compilation of static lib for MSVC2010 x64 +- Remove unused variable in infback9.c +- Fix argument checks in gzlog_compress() and gzlog_write() +- Clean up the usage of z_const and respect const usage within zlib +- Clean up examples/gzlog.[ch] comparisons of different types +- Avoid shift equal to bits in type (caused endless loop) +- Fix uninitialized value bug in gzputc() introduced by const patches +- Fix memory allocation error in examples/zran.c [Nor] +- Fix bug where gzopen(), gzclose() would write an empty file +- Fix bug in gzclose() when gzwrite() runs out of memory +- Check for input buffer malloc failure in examples/gzappend.c +- Add note to contrib/blast to use binary mode in stdio +- Fix comparisons of differently signed integers in contrib/blast +- Check for invalid code length codes in contrib/puff +- Fix serious but very rare decompression bug in inftrees.c +- Update inflateBack() comments, since inflate() can be faster +- Use underscored I/O function names for WINAPI_FAMILY +- Add _tr_flush_bits to the external symbols prefixed by --zprefix +- Add contrib/vstudio/vc10 pre-build step for static only +- Quote --version-script argument in CMakeLists.txt +- Don't specify --version-script on Apple platforms in CMakeLists.txt +- Fix casting error in contrib/testzlib/testzlib.c +- Fix types in contrib/minizip to match result of get_crc_table() +- Simplify contrib/vstudio/vc10 with 'd' suffix +- Add TOP support to win32/Makefile.msc +- Suport i686 and amd64 assembler builds in CMakeLists.txt +- Fix typos in the use of _LARGEFILE64_SOURCE in zconf.h +- Add vc11 and vc12 build files to contrib/vstudio +- Add gzvprintf() as an undocumented function in zlib +- Fix configure for Sun shell +- Remove runtime check in configure for four-byte integer type +- Add casts and consts to ease user conversion to C++ +- Add man pages for minizip and miniunzip +- In Makefile uninstall, don't rm if preceding cd fails +- Do not return Z_BUF_ERROR if deflateParam() has nothing to write + +Changes in 1.2.7 (2 May 2012) +- Replace use of memmove() with a simple copy for portability +- Test for existence of strerror +- Restore gzgetc_ for backward compatibility with 1.2.6 +- Fix build with non-GNU make on Solaris +- Require gcc 4.0 or later on Mac OS X to use the hidden attribute +- Include unistd.h for Watcom C +- Use __WATCOMC__ instead of __WATCOM__ +- Do not use the visibility attribute if NO_VIZ defined +- Improve the detection of no hidden visibility attribute +- Avoid using __int64 for gcc or solo compilation +- Cast to char * in gzprintf to avoid warnings [Zinser] +- Fix make_vms.com for VAX [Zinser] +- Don't use library or built-in byte swaps +- Simplify test and use of gcc hidden attribute +- Fix bug in gzclose_w() when gzwrite() fails to allocate memory +- Add "x" (O_EXCL) and "e" (O_CLOEXEC) modes support to gzopen() +- Fix bug in test/minigzip.c for configure --solo +- Fix contrib/vstudio project link errors [Mohanathas] +- Add ability to choose the builder in make_vms.com [Schweda] +- Add DESTDIR support to mingw32 win32/Makefile.gcc +- Fix comments in win32/Makefile.gcc for proper usage +- Allow overriding the default install locations for cmake +- Generate and install the pkg-config file with cmake +- Build both a static and a shared version of zlib with cmake +- Include version symbols for cmake builds +- If using cmake with MSVC, add the source directory to the includes +- Remove unneeded EXTRA_CFLAGS from win32/Makefile.gcc [Truta] +- Move obsolete emx makefile to old [Truta] +- Allow the use of -Wundef when compiling or using zlib +- Avoid the use of the -u option with mktemp +- Improve inflate() documentation on the use of Z_FINISH +- Recognize clang as gcc +- Add gzopen_w() in Windows for wide character path names +- Rename zconf.h in CMakeLists.txt to move it out of the way +- Add source directory in CMakeLists.txt for building examples +- Look in build directory for zlib.pc in CMakeLists.txt +- Remove gzflags from zlibvc.def in vc9 and vc10 +- Fix contrib/minizip compilation in the MinGW environment +- Update ./configure for Solaris, support --64 [Mooney] +- Remove -R. from Solaris shared build (possible security issue) +- Avoid race condition for parallel make (-j) running example +- Fix type mismatch between get_crc_table() and crc_table +- Fix parsing of version with "-" in CMakeLists.txt [Snider, Ziegler] +- Fix the path to zlib.map in CMakeLists.txt +- Force the native libtool in Mac OS X to avoid GNU libtool [Beebe] +- Add instructions to win32/Makefile.gcc for shared install [Torri] + +Changes in 1.2.6.1 (12 Feb 2012) +- Avoid the use of the Objective-C reserved name "id" +- Include io.h in gzguts.h for Microsoft compilers +- Fix problem with ./configure --prefix and gzgetc macro +- Include gz_header definition when compiling zlib solo +- Put gzflags() functionality back in zutil.c +- Avoid library header include in crc32.c for Z_SOLO +- Use name in GCC_CLASSIC as C compiler for coverage testing, if set +- Minor cleanup in contrib/minizip/zip.c [Vollant] +- Update make_vms.com [Zinser] +- Remove unnecessary gzgetc_ function +- Use optimized byte swap operations for Microsoft and GNU [Snyder] +- Fix minor typo in zlib.h comments [Rzesniowiecki] + +Changes in 1.2.6 (29 Jan 2012) +- Update the Pascal interface in contrib/pascal +- Fix function numbers for gzgetc_ in zlibvc.def files +- Fix configure.ac for contrib/minizip [Schiffer] +- Fix large-entry detection in minizip on 64-bit systems [Schiffer] +- Have ./configure use the compiler return code for error indication +- Fix CMakeLists.txt for cross compilation [McClure] +- Fix contrib/minizip/zip.c for 64-bit architectures [Dalsnes] +- Fix compilation of contrib/minizip on FreeBSD [Marquez] +- Correct suggested usages in win32/Makefile.msc [Shachar, Horvath] +- Include io.h for Turbo C / Borland C on all platforms [Truta] +- Make version explicit in contrib/minizip/configure.ac [Bosmans] +- Avoid warning for no encryption in contrib/minizip/zip.c [Vollant] +- Minor cleanup up contrib/minizip/unzip.c [Vollant] +- Fix bug when compiling minizip with C++ [Vollant] +- Protect for long name and extra fields in contrib/minizip [Vollant] +- Avoid some warnings in contrib/minizip [Vollant] +- Add -I../.. -L../.. to CFLAGS for minizip and miniunzip +- Add missing libs to minizip linker command +- Add support for VPATH builds in contrib/minizip +- Add an --enable-demos option to contrib/minizip/configure +- Add the generation of configure.log by ./configure +- Exit when required parameters not provided to win32/Makefile.gcc +- Have gzputc return the character written instead of the argument +- Use the -m option on ldconfig for BSD systems [Tobias] +- Correct in zlib.map when deflateResetKeep was added + +Changes in 1.2.5.3 (15 Jan 2012) +- Restore gzgetc function for binary compatibility +- Do not use _lseeki64 under Borland C++ [Truta] +- Update win32/Makefile.msc to build test/*.c [Truta] +- Remove old/visualc6 given CMakefile and other alternatives +- Update AS400 build files and documentation [Monnerat] +- Update win32/Makefile.gcc to build test/*.c [Truta] +- Permit stronger flushes after Z_BLOCK flushes +- Avoid extraneous empty blocks when doing empty flushes +- Permit Z_NULL arguments to deflatePending +- Allow deflatePrime() to insert bits in the middle of a stream +- Remove second empty static block for Z_PARTIAL_FLUSH +- Write out all of the available bits when using Z_BLOCK +- Insert the first two strings in the hash table after a flush + +Changes in 1.2.5.2 (17 Dec 2011) +- fix ld error: unable to find version dependency 'ZLIB_1.2.5' +- use relative symlinks for shared libs +- Avoid searching past window for Z_RLE strategy +- Assure that high-water mark initialization is always applied in deflate +- Add assertions to fill_window() in deflate.c to match comments +- Update python link in README +- Correct spelling error in gzread.c +- Fix bug in gzgets() for a concatenated empty gzip stream +- Correct error in comment for gz_make() +- Change gzread() and related to ignore junk after gzip streams +- Allow gzread() and related to continue after gzclearerr() +- Allow gzrewind() and gzseek() after a premature end-of-file +- Simplify gzseek() now that raw after gzip is ignored +- Change gzgetc() to a macro for speed (~40% speedup in testing) +- Fix gzclose() to return the actual error last encountered +- Always add large file support for windows +- Include zconf.h for windows large file support +- Include zconf.h.cmakein for windows large file support +- Update zconf.h.cmakein on make distclean +- Merge vestigial vsnprintf determination from zutil.h to gzguts.h +- Clarify how gzopen() appends in zlib.h comments +- Correct documentation of gzdirect() since junk at end now ignored +- Add a transparent write mode to gzopen() when 'T' is in the mode +- Update python link in zlib man page +- Get inffixed.h and MAKEFIXED result to match +- Add a ./config --solo option to make zlib subset with no library use +- Add undocumented inflateResetKeep() function for CAB file decoding +- Add --cover option to ./configure for gcc coverage testing +- Add #define ZLIB_CONST option to use const in the z_stream interface +- Add comment to gzdopen() in zlib.h to use dup() when using fileno() +- Note behavior of uncompress() to provide as much data as it can +- Add files in contrib/minizip to aid in building libminizip +- Split off AR options in Makefile.in and configure +- Change ON macro to Z_ARG to avoid application conflicts +- Facilitate compilation with Borland C++ for pragmas and vsnprintf +- Include io.h for Turbo C / Borland C++ +- Move example.c and minigzip.c to test/ +- Simplify incomplete code table filling in inflate_table() +- Remove code from inflate.c and infback.c that is impossible to execute +- Test the inflate code with full coverage +- Allow deflateSetDictionary, inflateSetDictionary at any time (in raw) +- Add deflateResetKeep and fix inflateResetKeep to retain dictionary +- Fix gzwrite.c to accommodate reduced memory zlib compilation +- Have inflate() with Z_FINISH avoid the allocation of a window +- Do not set strm->adler when doing raw inflate +- Fix gzeof() to behave just like feof() when read is not past end of file +- Fix bug in gzread.c when end-of-file is reached +- Avoid use of Z_BUF_ERROR in gz* functions except for premature EOF +- Document gzread() capability to read concurrently written files +- Remove hard-coding of resource compiler in CMakeLists.txt [Blammo] + +Changes in 1.2.5.1 (10 Sep 2011) +- Update FAQ entry on shared builds (#13) +- Avoid symbolic argument to chmod in Makefile.in +- Fix bug and add consts in contrib/puff [Oberhumer] +- Update contrib/puff/zeros.raw test file to have all block types +- Add full coverage test for puff in contrib/puff/Makefile +- Fix static-only-build install in Makefile.in +- Fix bug in unzGetCurrentFileInfo() in contrib/minizip [Kuno] +- Add libz.a dependency to shared in Makefile.in for parallel builds +- Spell out "number" (instead of "nb") in zlib.h for total_in, total_out +- Replace $(...) with `...` in configure for non-bash sh [Bowler] +- Add darwin* to Darwin* and solaris* to SunOS\ 5* in configure [Groffen] +- Add solaris* to Linux* in configure to allow gcc use [Groffen] +- Add *bsd* to Linux* case in configure [Bar-Lev] +- Add inffast.obj to dependencies in win32/Makefile.msc +- Correct spelling error in deflate.h [Kohler] +- Change libzdll.a again to libz.dll.a (!) in win32/Makefile.gcc +- Add test to configure for GNU C looking for gcc in output of $cc -v +- Add zlib.pc generation to win32/Makefile.gcc [Weigelt] +- Fix bug in zlib.h for _FILE_OFFSET_BITS set and _LARGEFILE64_SOURCE not +- Add comment in zlib.h that adler32_combine with len2 < 0 makes no sense +- Make NO_DIVIDE option in adler32.c much faster (thanks to John Reiser) +- Make stronger test in zconf.h to include unistd.h for LFS +- Apply Darwin patches for 64-bit file offsets to contrib/minizip [Slack] +- Fix zlib.h LFS support when Z_PREFIX used +- Add updated as400 support (removed from old) [Monnerat] +- Avoid deflate sensitivity to volatile input data +- Avoid division in adler32_combine for NO_DIVIDE +- Clarify the use of Z_FINISH with deflateBound() amount of space +- Set binary for output file in puff.c +- Use u4 type for crc_table to avoid conversion warnings +- Apply casts in zlib.h to avoid conversion warnings +- Add OF to prototypes for adler32_combine_ and crc32_combine_ [Miller] +- Improve inflateSync() documentation to note indeterminancy +- Add deflatePending() function to return the amount of pending output +- Correct the spelling of "specification" in FAQ [Randers-Pehrson] +- Add a check in configure for stdarg.h, use for gzprintf() +- Check that pointers fit in ints when gzprint() compiled old style +- Add dummy name before $(SHAREDLIBV) in Makefile [Bar-Lev, Bowler] +- Delete line in configure that adds -L. libz.a to LDFLAGS [Weigelt] +- Add debug records in assmebler code [Londer] +- Update RFC references to use http://tools.ietf.org/html/... [Li] +- Add --archs option, use of libtool to configure for Mac OS X [Borstel] + +Changes in 1.2.5 (19 Apr 2010) +- Disable visibility attribute in win32/Makefile.gcc [Bar-Lev] +- Default to libdir as sharedlibdir in configure [Nieder] +- Update copyright dates on modified source files +- Update trees.c to be able to generate modified trees.h +- Exit configure for MinGW, suggesting win32/Makefile.gcc +- Check for NULL path in gz_open [Homurlu] + +Changes in 1.2.4.5 (18 Apr 2010) +- Set sharedlibdir in configure [Torok] +- Set LDFLAGS in Makefile.in [Bar-Lev] +- Avoid mkdir objs race condition in Makefile.in [Bowler] +- Add ZLIB_INTERNAL in front of internal inter-module functions and arrays +- Define ZLIB_INTERNAL to hide internal functions and arrays for GNU C +- Don't use hidden attribute when it is a warning generator (e.g. Solaris) + +Changes in 1.2.4.4 (18 Apr 2010) +- Fix CROSS_PREFIX executable testing, CHOST extract, mingw* [Torok] +- Undefine _LARGEFILE64_SOURCE in zconf.h if it is zero, but not if empty +- Try to use bash or ksh regardless of functionality of /bin/sh +- Fix configure incompatibility with NetBSD sh +- Remove attempt to run under bash or ksh since have better NetBSD fix +- Fix win32/Makefile.gcc for MinGW [Bar-Lev] +- Add diagnostic messages when using CROSS_PREFIX in configure +- Added --sharedlibdir option to configure [Weigelt] +- Use hidden visibility attribute when available [Frysinger] + +Changes in 1.2.4.3 (10 Apr 2010) +- Only use CROSS_PREFIX in configure for ar and ranlib if they exist +- Use CROSS_PREFIX for nm [Bar-Lev] +- Assume _LARGEFILE64_SOURCE defined is equivalent to true +- Avoid use of undefined symbols in #if with && and || +- Make *64 prototypes in gzguts.h consistent with functions +- Add -shared load option for MinGW in configure [Bowler] +- Move z_off64_t to public interface, use instead of off64_t +- Remove ! from shell test in configure (not portable to Solaris) +- Change +0 macro tests to -0 for possibly increased portability + +Changes in 1.2.4.2 (9 Apr 2010) +- Add consistent carriage returns to readme.txt's in masmx86 and masmx64 +- Really provide prototypes for *64 functions when building without LFS +- Only define unlink() in minigzip.c if unistd.h not included +- Update README to point to contrib/vstudio project files +- Move projects/vc6 to old/ and remove projects/ +- Include stdlib.h in minigzip.c for setmode() definition under WinCE +- Clean up assembler builds in win32/Makefile.msc [Rowe] +- Include sys/types.h for Microsoft for off_t definition +- Fix memory leak on error in gz_open() +- Symbolize nm as $NM in configure [Weigelt] +- Use TEST_LDSHARED instead of LDSHARED to link test programs [Weigelt] +- Add +0 to _FILE_OFFSET_BITS and _LFS64_LARGEFILE in case not defined +- Fix bug in gzeof() to take into account unused input data +- Avoid initialization of structures with variables in puff.c +- Updated win32/README-WIN32.txt [Rowe] + +Changes in 1.2.4.1 (28 Mar 2010) +- Remove the use of [a-z] constructs for sed in configure [gentoo 310225] +- Remove $(SHAREDLIB) from LIBS in Makefile.in [Creech] +- Restore "for debugging" comment on sprintf() in gzlib.c +- Remove fdopen for MVS from gzguts.h +- Put new README-WIN32.txt in win32 [Rowe] +- Add check for shell to configure and invoke another shell if needed +- Fix big fat stinking bug in gzseek() on uncompressed files +- Remove vestigial F_OPEN64 define in zutil.h +- Set and check the value of _LARGEFILE_SOURCE and _LARGEFILE64_SOURCE +- Avoid errors on non-LFS systems when applications define LFS macros +- Set EXE to ".exe" in configure for MINGW [Kahle] +- Match crc32() in crc32.c exactly to the prototype in zlib.h [Sherrill] +- Add prefix for cross-compilation in win32/makefile.gcc [Bar-Lev] +- Add DLL install in win32/makefile.gcc [Bar-Lev] +- Allow Linux* or linux* from uname in configure [Bar-Lev] +- Allow ldconfig to be redefined in configure and Makefile.in [Bar-Lev] +- Add cross-compilation prefixes to configure [Bar-Lev] +- Match type exactly in gz_load() invocation in gzread.c +- Match type exactly of zcalloc() in zutil.c to zlib.h alloc_func +- Provide prototypes for *64 functions when building zlib without LFS +- Don't use -lc when linking shared library on MinGW +- Remove errno.h check in configure and vestigial errno code in zutil.h + +Changes in 1.2.4 (14 Mar 2010) +- Fix VER3 extraction in configure for no fourth subversion +- Update zlib.3, add docs to Makefile.in to make .pdf out of it +- Add zlib.3.pdf to distribution +- Don't set error code in gzerror() if passed pointer is NULL +- Apply destination directory fixes to CMakeLists.txt [Lowman] +- Move #cmakedefine's to a new zconf.in.cmakein +- Restore zconf.h for builds that don't use configure or cmake +- Add distclean to dummy Makefile for convenience +- Update and improve INDEX, README, and FAQ +- Update CMakeLists.txt for the return of zconf.h [Lowman] +- Update contrib/vstudio/vc9 and vc10 [Vollant] +- Change libz.dll.a back to libzdll.a in win32/Makefile.gcc +- Apply license and readme changes to contrib/asm686 [Raiter] +- Check file name lengths and add -c option in minigzip.c [Li] +- Update contrib/amd64 and contrib/masmx86/ [Vollant] +- Avoid use of "eof" parameter in trees.c to not shadow library variable +- Update make_vms.com for removal of zlibdefs.h [Zinser] +- Update assembler code and vstudio projects in contrib [Vollant] +- Remove outdated assembler code contrib/masm686 and contrib/asm586 +- Remove old vc7 and vc8 from contrib/vstudio +- Update win32/Makefile.msc, add ZLIB_VER_SUBREVISION [Rowe] +- Fix memory leaks in gzclose_r() and gzclose_w(), file leak in gz_open() +- Add contrib/gcc_gvmat64 for longest_match and inflate_fast [Vollant] +- Remove *64 functions from win32/zlib.def (they're not 64-bit yet) +- Fix bug in void-returning vsprintf() case in gzwrite.c +- Fix name change from inflate.h in contrib/inflate86/inffas86.c +- Check if temporary file exists before removing in make_vms.com [Zinser] +- Fix make install and uninstall for --static option +- Fix usage of _MSC_VER in gzguts.h and zutil.h [Truta] +- Update readme.txt in contrib/masmx64 and masmx86 to assemble + +Changes in 1.2.3.9 (21 Feb 2010) +- Expunge gzio.c +- Move as400 build information to old +- Fix updates in contrib/minizip and contrib/vstudio +- Add const to vsnprintf test in configure to avoid warnings [Weigelt] +- Delete zconf.h (made by configure) [Weigelt] +- Change zconf.in.h to zconf.h.in per convention [Weigelt] +- Check for NULL buf in gzgets() +- Return empty string for gzgets() with len == 1 (like fgets()) +- Fix description of gzgets() in zlib.h for end-of-file, NULL return +- Update minizip to 1.1 [Vollant] +- Avoid MSVC loss of data warnings in gzread.c, gzwrite.c +- Note in zlib.h that gzerror() should be used to distinguish from EOF +- Remove use of snprintf() from gzlib.c +- Fix bug in gzseek() +- Update contrib/vstudio, adding vc9 and vc10 [Kuno, Vollant] +- Fix zconf.h generation in CMakeLists.txt [Lowman] +- Improve comments in zconf.h where modified by configure + +Changes in 1.2.3.8 (13 Feb 2010) +- Clean up text files (tabs, trailing whitespace, etc.) [Oberhumer] +- Use z_off64_t in gz_zero() and gz_skip() to match state->skip +- Avoid comparison problem when sizeof(int) == sizeof(z_off64_t) +- Revert to Makefile.in from 1.2.3.6 (live with the clutter) +- Fix missing error return in gzflush(), add zlib.h note +- Add *64 functions to zlib.map [Levin] +- Fix signed/unsigned comparison in gz_comp() +- Use SFLAGS when testing shared linking in configure +- Add --64 option to ./configure to use -m64 with gcc +- Fix ./configure --help to correctly name options +- Have make fail if a test fails [Levin] +- Avoid buffer overrun in contrib/masmx64/gvmat64.asm [Simpson] +- Remove assembler object files from contrib + +Changes in 1.2.3.7 (24 Jan 2010) +- Always gzopen() with O_LARGEFILE if available +- Fix gzdirect() to work immediately after gzopen() or gzdopen() +- Make gzdirect() more precise when the state changes while reading +- Improve zlib.h documentation in many places +- Catch memory allocation failure in gz_open() +- Complete close operation if seek forward in gzclose_w() fails +- Return Z_ERRNO from gzclose_r() if close() fails +- Return Z_STREAM_ERROR instead of EOF for gzclose() being passed NULL +- Return zero for gzwrite() errors to match zlib.h description +- Return -1 on gzputs() error to match zlib.h description +- Add zconf.in.h to allow recovery from configure modification [Weigelt] +- Fix static library permissions in Makefile.in [Weigelt] +- Avoid warnings in configure tests that hide functionality [Weigelt] +- Add *BSD and DragonFly to Linux case in configure [gentoo 123571] +- Change libzdll.a to libz.dll.a in win32/Makefile.gcc [gentoo 288212] +- Avoid access of uninitialized data for first inflateReset2 call [Gomes] +- Keep object files in subdirectories to reduce the clutter somewhat +- Remove default Makefile and zlibdefs.h, add dummy Makefile +- Add new external functions to Z_PREFIX, remove duplicates, z_z_ -> z_ +- Remove zlibdefs.h completely -- modify zconf.h instead + +Changes in 1.2.3.6 (17 Jan 2010) +- Avoid void * arithmetic in gzread.c and gzwrite.c +- Make compilers happier with const char * for gz_error message +- Avoid unused parameter warning in inflate.c +- Avoid signed-unsigned comparison warning in inflate.c +- Indent #pragma's for traditional C +- Fix usage of strwinerror() in glib.c, change to gz_strwinerror() +- Correct email address in configure for system options +- Update make_vms.com and add make_vms.com to contrib/minizip [Zinser] +- Update zlib.map [Brown] +- Fix Makefile.in for Solaris 10 make of example64 and minizip64 [Torok] +- Apply various fixes to CMakeLists.txt [Lowman] +- Add checks on len in gzread() and gzwrite() +- Add error message for no more room for gzungetc() +- Remove zlib version check in gzwrite() +- Defer compression of gzprintf() result until need to +- Use snprintf() in gzdopen() if available +- Remove USE_MMAP configuration determination (only used by minigzip) +- Remove examples/pigz.c (available separately) +- Update examples/gun.c to 1.6 + +Changes in 1.2.3.5 (8 Jan 2010) +- Add space after #if in zutil.h for some compilers +- Fix relatively harmless bug in deflate_fast() [Exarevsky] +- Fix same problem in deflate_slow() +- Add $(SHAREDLIBV) to LIBS in Makefile.in [Brown] +- Add deflate_rle() for faster Z_RLE strategy run-length encoding +- Add deflate_huff() for faster Z_HUFFMAN_ONLY encoding +- Change name of "write" variable in inffast.c to avoid library collisions +- Fix premature EOF from gzread() in gzio.c [Brown] +- Use zlib header window size if windowBits is 0 in inflateInit2() +- Remove compressBound() call in deflate.c to avoid linking compress.o +- Replace use of errno in gz* with functions, support WinCE [Alves] +- Provide alternative to perror() in minigzip.c for WinCE [Alves] +- Don't use _vsnprintf on later versions of MSVC [Lowman] +- Add CMake build script and input file [Lowman] +- Update contrib/minizip to 1.1 [Svensson, Vollant] +- Moved nintendods directory from contrib to . +- Replace gzio.c with a new set of routines with the same functionality +- Add gzbuffer(), gzoffset(), gzclose_r(), gzclose_w() as part of above +- Update contrib/minizip to 1.1b +- Change gzeof() to return 0 on error instead of -1 to agree with zlib.h + +Changes in 1.2.3.4 (21 Dec 2009) +- Use old school .SUFFIXES in Makefile.in for FreeBSD compatibility +- Update comments in configure and Makefile.in for default --shared +- Fix test -z's in configure [Marquess] +- Build examplesh and minigzipsh when not testing +- Change NULL's to Z_NULL's in deflate.c and in comments in zlib.h +- Import LDFLAGS from the environment in configure +- Fix configure to populate SFLAGS with discovered CFLAGS options +- Adapt make_vms.com to the new Makefile.in [Zinser] +- Add zlib2ansi script for C++ compilation [Marquess] +- Add _FILE_OFFSET_BITS=64 test to make test (when applicable) +- Add AMD64 assembler code for longest match to contrib [Teterin] +- Include options from $SFLAGS when doing $LDSHARED +- Simplify 64-bit file support by introducing z_off64_t type +- Make shared object files in objs directory to work around old Sun cc +- Use only three-part version number for Darwin shared compiles +- Add rc option to ar in Makefile.in for when ./configure not run +- Add -WI,-rpath,. to LDFLAGS for OSF 1 V4* +- Set LD_LIBRARYN32_PATH for SGI IRIX shared compile +- Protect against _FILE_OFFSET_BITS being defined when compiling zlib +- Rename Makefile.in targets allstatic to static and allshared to shared +- Fix static and shared Makefile.in targets to be independent +- Correct error return bug in gz_open() by setting state [Brown] +- Put spaces before ;;'s in configure for better sh compatibility +- Add pigz.c (parallel implementation of gzip) to examples/ +- Correct constant in crc32.c to UL [Leventhal] +- Reject negative lengths in crc32_combine() +- Add inflateReset2() function to work like inflateEnd()/inflateInit2() +- Include sys/types.h for _LARGEFILE64_SOURCE [Brown] +- Correct typo in doc/algorithm.txt [Janik] +- Fix bug in adler32_combine() [Zhu] +- Catch missing-end-of-block-code error in all inflates and in puff + Assures that random input to inflate eventually results in an error +- Added enough.c (calculation of ENOUGH for inftrees.h) to examples/ +- Update ENOUGH and its usage to reflect discovered bounds +- Fix gzerror() error report on empty input file [Brown] +- Add ush casts in trees.c to avoid pedantic runtime errors +- Fix typo in zlib.h uncompress() description [Reiss] +- Correct inflate() comments with regard to automatic header detection +- Remove deprecation comment on Z_PARTIAL_FLUSH (it stays) +- Put new version of gzlog (2.0) in examples with interruption recovery +- Add puff compile option to permit invalid distance-too-far streams +- Add puff TEST command options, ability to read piped input +- Prototype the *64 functions in zlib.h when _FILE_OFFSET_BITS == 64, but + _LARGEFILE64_SOURCE not defined +- Fix Z_FULL_FLUSH to truly erase the past by resetting s->strstart +- Fix deflateSetDictionary() to use all 32K for output consistency +- Remove extraneous #define MIN_LOOKAHEAD in deflate.c (in deflate.h) +- Clear bytes after deflate lookahead to avoid use of uninitialized data +- Change a limit in inftrees.c to be more transparent to Coverity Prevent +- Update win32/zlib.def with exported symbols from zlib.h +- Correct spelling errors in zlib.h [Willem, Sobrado] +- Allow Z_BLOCK for deflate() to force a new block +- Allow negative bits in inflatePrime() to delete existing bit buffer +- Add Z_TREES flush option to inflate() to return at end of trees +- Add inflateMark() to return current state information for random access +- Add Makefile for NintendoDS to contrib [Costa] +- Add -w in configure compile tests to avoid spurious warnings [Beucler] +- Fix typos in zlib.h comments for deflateSetDictionary() +- Fix EOF detection in transparent gzread() [Maier] + +Changes in 1.2.3.3 (2 October 2006) +- Make --shared the default for configure, add a --static option +- Add compile option to permit invalid distance-too-far streams +- Add inflateUndermine() function which is required to enable above +- Remove use of "this" variable name for C++ compatibility [Marquess] +- Add testing of shared library in make test, if shared library built +- Use ftello() and fseeko() if available instead of ftell() and fseek() +- Provide two versions of all functions that use the z_off_t type for + binary compatibility -- a normal version and a 64-bit offset version, + per the Large File Support Extension when _LARGEFILE64_SOURCE is + defined; use the 64-bit versions by default when _FILE_OFFSET_BITS + is defined to be 64 +- Add a --uname= option to configure to perhaps help with cross-compiling + +Changes in 1.2.3.2 (3 September 2006) +- Turn off silly Borland warnings [Hay] +- Use off64_t and define _LARGEFILE64_SOURCE when present +- Fix missing dependency on inffixed.h in Makefile.in +- Rig configure --shared to build both shared and static [Teredesai, Truta] +- Remove zconf.in.h and instead create a new zlibdefs.h file +- Fix contrib/minizip/unzip.c non-encrypted after encrypted [Vollant] +- Add treebuild.xml (see http://treebuild.metux.de/) [Weigelt] + +Changes in 1.2.3.1 (16 August 2006) +- Add watcom directory with OpenWatcom make files [Daniel] +- Remove #undef of FAR in zconf.in.h for MVS [Fedtke] +- Update make_vms.com [Zinser] +- Use -fPIC for shared build in configure [Teredesai, Nicholson] +- Use only major version number for libz.so on IRIX and OSF1 [Reinholdtsen] +- Use fdopen() (not _fdopen()) for Interix in zutil.h [Bäck] +- Add some FAQ entries about the contrib directory +- Update the MVS question in the FAQ +- Avoid extraneous reads after EOF in gzio.c [Brown] +- Correct spelling of "successfully" in gzio.c [Randers-Pehrson] +- Add comments to zlib.h about gzerror() usage [Brown] +- Set extra flags in gzip header in gzopen() like deflate() does +- Make configure options more compatible with double-dash conventions + [Weigelt] +- Clean up compilation under Solaris SunStudio cc [Rowe, Reinholdtsen] +- Fix uninstall target in Makefile.in [Truta] +- Add pkgconfig support [Weigelt] +- Use $(DESTDIR) macro in Makefile.in [Reinholdtsen, Weigelt] +- Replace set_data_type() with a more accurate detect_data_type() in + trees.c, according to the txtvsbin.txt document [Truta] +- Swap the order of #include and #include "zlib.h" in + gzio.c, example.c and minigzip.c [Truta] +- Shut up annoying VS2005 warnings about standard C deprecation [Rowe, + Truta] (where?) +- Fix target "clean" from win32/Makefile.bor [Truta] +- Create .pdb and .manifest files in win32/makefile.msc [Ziegler, Rowe] +- Update zlib www home address in win32/DLL_FAQ.txt [Truta] +- Update contrib/masmx86/inffas32.asm for VS2005 [Vollant, Van Wassenhove] +- Enable browse info in the "Debug" and "ASM Debug" configurations in + the Visual C++ 6 project, and set (non-ASM) "Debug" as default [Truta] +- Add pkgconfig support [Weigelt] +- Add ZLIB_VER_MAJOR, ZLIB_VER_MINOR and ZLIB_VER_REVISION in zlib.h, + for use in win32/zlib1.rc [Polushin, Rowe, Truta] +- Add a document that explains the new text detection scheme to + doc/txtvsbin.txt [Truta] +- Add rfc1950.txt, rfc1951.txt and rfc1952.txt to doc/ [Truta] +- Move algorithm.txt into doc/ [Truta] +- Synchronize FAQ with website +- Fix compressBound(), was low for some pathological cases [Fearnley] +- Take into account wrapper variations in deflateBound() +- Set examples/zpipe.c input and output to binary mode for Windows +- Update examples/zlib_how.html with new zpipe.c (also web site) +- Fix some warnings in examples/gzlog.c and examples/zran.c (it seems + that gcc became pickier in 4.0) +- Add zlib.map for Linux: "All symbols from zlib-1.1.4 remain + un-versioned, the patch adds versioning only for symbols introduced in + zlib-1.2.0 or later. It also declares as local those symbols which are + not designed to be exported." [Levin] +- Update Z_PREFIX list in zconf.in.h, add --zprefix option to configure +- Do not initialize global static by default in trees.c, add a response + NO_INIT_GLOBAL_POINTERS to initialize them if needed [Marquess] +- Don't use strerror() in gzio.c under WinCE [Yakimov] +- Don't use errno.h in zutil.h under WinCE [Yakimov] +- Move arguments for AR to its usage to allow replacing ar [Marot] +- Add HAVE_VISIBILITY_PRAGMA in zconf.in.h for Mozilla [Randers-Pehrson] +- Improve inflateInit() and inflateInit2() documentation +- Fix structure size comment in inflate.h +- Change configure help option from --h* to --help [Santos] + +Changes in 1.2.3 (18 July 2005) +- Apply security vulnerability fixes to contrib/infback9 as well +- Clean up some text files (carriage returns, trailing space) +- Update testzlib, vstudio, masmx64, and masmx86 in contrib [Vollant] + +Changes in 1.2.2.4 (11 July 2005) +- Add inflatePrime() function for starting inflation at bit boundary +- Avoid some Visual C warnings in deflate.c +- Avoid more silly Visual C warnings in inflate.c and inftrees.c for 64-bit + compile +- Fix some spelling errors in comments [Betts] +- Correct inflateInit2() error return documentation in zlib.h +- Add zran.c example of compressed data random access to examples + directory, shows use of inflatePrime() +- Fix cast for assignments to strm->state in inflate.c and infback.c +- Fix zlibCompileFlags() in zutil.c to use 1L for long shifts [Oberhumer] +- Move declarations of gf2 functions to right place in crc32.c [Oberhumer] +- Add cast in trees.c t avoid a warning [Oberhumer] +- Avoid some warnings in fitblk.c, gun.c, gzjoin.c in examples [Oberhumer] +- Update make_vms.com [Zinser] +- Initialize state->write in inflateReset() since copied in inflate_fast() +- Be more strict on incomplete code sets in inflate_table() and increase + ENOUGH and MAXD -- this repairs a possible security vulnerability for + invalid inflate input. Thanks to Tavis Ormandy and Markus Oberhumer for + discovering the vulnerability and providing test cases. +- Add ia64 support to configure for HP-UX [Smith] +- Add error return to gzread() for format or i/o error [Levin] +- Use malloc.h for OS/2 [Necasek] + +Changes in 1.2.2.3 (27 May 2005) +- Replace 1U constants in inflate.c and inftrees.c for 64-bit compile +- Typecast fread() return values in gzio.c [Vollant] +- Remove trailing space in minigzip.c outmode (VC++ can't deal with it) +- Fix crc check bug in gzread() after gzungetc() [Heiner] +- Add the deflateTune() function to adjust internal compression parameters +- Add a fast gzip decompressor, gun.c, to examples (use of inflateBack) +- Remove an incorrect assertion in examples/zpipe.c +- Add C++ wrapper in infback9.h [Donais] +- Fix bug in inflateCopy() when decoding fixed codes +- Note in zlib.h how much deflateSetDictionary() actually uses +- Remove USE_DICT_HEAD in deflate.c (would mess up inflate if used) +- Add _WIN32_WCE to define WIN32 in zconf.in.h [Spencer] +- Don't include stderr.h or errno.h for _WIN32_WCE in zutil.h [Spencer] +- Add gzdirect() function to indicate transparent reads +- Update contrib/minizip [Vollant] +- Fix compilation of deflate.c when both ASMV and FASTEST [Oberhumer] +- Add casts in crc32.c to avoid warnings [Oberhumer] +- Add contrib/masmx64 [Vollant] +- Update contrib/asm586, asm686, masmx86, testzlib, vstudio [Vollant] + +Changes in 1.2.2.2 (30 December 2004) +- Replace structure assignments in deflate.c and inflate.c with zmemcpy to + avoid implicit memcpy calls (portability for no-library compilation) +- Increase sprintf() buffer size in gzdopen() to allow for large numbers +- Add INFLATE_STRICT to check distances against zlib header +- Improve WinCE errno handling and comments [Chang] +- Remove comment about no gzip header processing in FAQ +- Add Z_FIXED strategy option to deflateInit2() to force fixed trees +- Add updated make_vms.com [Coghlan], update README +- Create a new "examples" directory, move gzappend.c there, add zpipe.c, + fitblk.c, gzlog.[ch], gzjoin.c, and zlib_how.html. +- Add FAQ entry and comments in deflate.c on uninitialized memory access +- Add Solaris 9 make options in configure [Gilbert] +- Allow strerror() usage in gzio.c for STDC +- Fix DecompressBuf in contrib/delphi/ZLib.pas [ManChesTer] +- Update contrib/masmx86/inffas32.asm and gvmat32.asm [Vollant] +- Use z_off_t for adler32_combine() and crc32_combine() lengths +- Make adler32() much faster for small len +- Use OS_CODE in deflate() default gzip header + +Changes in 1.2.2.1 (31 October 2004) +- Allow inflateSetDictionary() call for raw inflate +- Fix inflate header crc check bug for file names and comments +- Add deflateSetHeader() and gz_header structure for custom gzip headers +- Add inflateGetheader() to retrieve gzip headers +- Add crc32_combine() and adler32_combine() functions +- Add alloc_func, free_func, in_func, out_func to Z_PREFIX list +- Use zstreamp consistently in zlib.h (inflate_back functions) +- Remove GUNZIP condition from definition of inflate_mode in inflate.h + and in contrib/inflate86/inffast.S [Truta, Anderson] +- Add support for AMD64 in contrib/inflate86/inffas86.c [Anderson] +- Update projects/README.projects and projects/visualc6 [Truta] +- Update win32/DLL_FAQ.txt [Truta] +- Avoid warning under NO_GZCOMPRESS in gzio.c; fix typo [Truta] +- Deprecate Z_ASCII; use Z_TEXT instead [Truta] +- Use a new algorithm for setting strm->data_type in trees.c [Truta] +- Do not define an exit() prototype in zutil.c unless DEBUG defined +- Remove prototype of exit() from zutil.c, example.c, minigzip.c [Truta] +- Add comment in zlib.h for Z_NO_FLUSH parameter to deflate() +- Fix Darwin build version identification [Peterson] + +Changes in 1.2.2 (3 October 2004) +- Update zlib.h comments on gzip in-memory processing +- Set adler to 1 in inflateReset() to support Java test suite [Walles] +- Add contrib/dotzlib [Ravn] +- Update win32/DLL_FAQ.txt [Truta] +- Update contrib/minizip [Vollant] +- Move contrib/visual-basic.txt to old/ [Truta] +- Fix assembler builds in projects/visualc6/ [Truta] + +Changes in 1.2.1.2 (9 September 2004) +- Update INDEX file +- Fix trees.c to update strm->data_type (no one ever noticed!) +- Fix bug in error case in inflate.c, infback.c, and infback9.c [Brown] +- Add "volatile" to crc table flag declaration (for DYNAMIC_CRC_TABLE) +- Add limited multitasking protection to DYNAMIC_CRC_TABLE +- Add NO_vsnprintf for VMS in zutil.h [Mozilla] +- Don't declare strerror() under VMS [Mozilla] +- Add comment to DYNAMIC_CRC_TABLE to use get_crc_table() to initialize +- Update contrib/ada [Anisimkov] +- Update contrib/minizip [Vollant] +- Fix configure to not hardcode directories for Darwin [Peterson] +- Fix gzio.c to not return error on empty files [Brown] +- Fix indentation; update version in contrib/delphi/ZLib.pas and + contrib/pascal/zlibpas.pas [Truta] +- Update mkasm.bat in contrib/masmx86 [Truta] +- Update contrib/untgz [Truta] +- Add projects/README.projects [Truta] +- Add project for MS Visual C++ 6.0 in projects/visualc6 [Cadieux, Truta] +- Update win32/DLL_FAQ.txt [Truta] +- Update list of Z_PREFIX symbols in zconf.h [Randers-Pehrson, Truta] +- Remove an unnecessary assignment to curr in inftrees.c [Truta] +- Add OS/2 to exe builds in configure [Poltorak] +- Remove err dummy parameter in zlib.h [Kientzle] + +Changes in 1.2.1.1 (9 January 2004) +- Update email address in README +- Several FAQ updates +- Fix a big fat bug in inftrees.c that prevented decoding valid + dynamic blocks with only literals and no distance codes -- + Thanks to "Hot Emu" for the bug report and sample file +- Add a note to puff.c on no distance codes case. + +Changes in 1.2.1 (17 November 2003) +- Remove a tab in contrib/gzappend/gzappend.c +- Update some interfaces in contrib for new zlib functions +- Update zlib version number in some contrib entries +- Add Windows CE definition for ptrdiff_t in zutil.h [Mai, Truta] +- Support shared libraries on Hurd and KFreeBSD [Brown] +- Fix error in NO_DIVIDE option of adler32.c + +Changes in 1.2.0.8 (4 November 2003) +- Update version in contrib/delphi/ZLib.pas and contrib/pascal/zlibpas.pas +- Add experimental NO_DIVIDE #define in adler32.c + - Possibly faster on some processors (let me know if it is) +- Correct Z_BLOCK to not return on first inflate call if no wrap +- Fix strm->data_type on inflate() return to correctly indicate EOB +- Add deflatePrime() function for appending in the middle of a byte +- Add contrib/gzappend for an example of appending to a stream +- Update win32/DLL_FAQ.txt [Truta] +- Delete Turbo C comment in README [Truta] +- Improve some indentation in zconf.h [Truta] +- Fix infinite loop on bad input in configure script [Church] +- Fix gzeof() for concatenated gzip files [Johnson] +- Add example to contrib/visual-basic.txt [Michael B.] +- Add -p to mkdir's in Makefile.in [vda] +- Fix configure to properly detect presence or lack of printf functions +- Add AS400 support [Monnerat] +- Add a little Cygwin support [Wilson] + +Changes in 1.2.0.7 (21 September 2003) +- Correct some debug formats in contrib/infback9 +- Cast a type in a debug statement in trees.c +- Change search and replace delimiter in configure from % to # [Beebe] +- Update contrib/untgz to 0.2 with various fixes [Truta] +- Add build support for Amiga [Nikl] +- Remove some directories in old that have been updated to 1.2 +- Add dylib building for Mac OS X in configure and Makefile.in +- Remove old distribution stuff from Makefile +- Update README to point to DLL_FAQ.txt, and add comment on Mac OS X +- Update links in README + +Changes in 1.2.0.6 (13 September 2003) +- Minor FAQ updates +- Update contrib/minizip to 1.00 [Vollant] +- Remove test of gz functions in example.c when GZ_COMPRESS defined [Truta] +- Update POSTINC comment for 68060 [Nikl] +- Add contrib/infback9 with deflate64 decoding (unsupported) +- For MVS define NO_vsnprintf and undefine FAR [van Burik] +- Add pragma for fdopen on MVS [van Burik] + +Changes in 1.2.0.5 (8 September 2003) +- Add OF to inflateBackEnd() declaration in zlib.h +- Remember start when using gzdopen in the middle of a file +- Use internal off_t counters in gz* functions to properly handle seeks +- Perform more rigorous check for distance-too-far in inffast.c +- Add Z_BLOCK flush option to return from inflate at block boundary +- Set strm->data_type on return from inflate + - Indicate bits unused, if at block boundary, and if in last block +- Replace size_t with ptrdiff_t in crc32.c, and check for correct size +- Add condition so old NO_DEFLATE define still works for compatibility +- FAQ update regarding the Windows DLL [Truta] +- INDEX update: add qnx entry, remove aix entry [Truta] +- Install zlib.3 into mandir [Wilson] +- Move contrib/zlib_dll_FAQ.txt to win32/DLL_FAQ.txt; update [Truta] +- Adapt the zlib interface to the new DLL convention guidelines [Truta] +- Introduce ZLIB_WINAPI macro to allow the export of functions using + the WINAPI calling convention, for Visual Basic [Vollant, Truta] +- Update msdos and win32 scripts and makefiles [Truta] +- Export symbols by name, not by ordinal, in win32/zlib.def [Truta] +- Add contrib/ada [Anisimkov] +- Move asm files from contrib/vstudio/vc70_32 to contrib/asm386 [Truta] +- Rename contrib/asm386 to contrib/masmx86 [Truta, Vollant] +- Add contrib/masm686 [Truta] +- Fix offsets in contrib/inflate86 and contrib/masmx86/inffas32.asm + [Truta, Vollant] +- Update contrib/delphi; rename to contrib/pascal; add example [Truta] +- Remove contrib/delphi2; add a new contrib/delphi [Truta] +- Avoid inclusion of the nonstandard in contrib/iostream, + and fix some method prototypes [Truta] +- Fix the ZCR_SEED2 constant to avoid warnings in contrib/minizip + [Truta] +- Avoid the use of backslash (\) in contrib/minizip [Vollant] +- Fix file time handling in contrib/untgz; update makefiles [Truta] +- Update contrib/vstudio/vc70_32 to comply with the new DLL guidelines + [Vollant] +- Remove contrib/vstudio/vc15_16 [Vollant] +- Rename contrib/vstudio/vc70_32 to contrib/vstudio/vc7 [Truta] +- Update README.contrib [Truta] +- Invert the assignment order of match_head and s->prev[...] in + INSERT_STRING [Truta] +- Compare TOO_FAR with 32767 instead of 32768, to avoid 16-bit warnings + [Truta] +- Compare function pointers with 0, not with NULL or Z_NULL [Truta] +- Fix prototype of syncsearch in inflate.c [Truta] +- Introduce ASMINF macro to be enabled when using an ASM implementation + of inflate_fast [Truta] +- Change NO_DEFLATE to NO_GZCOMPRESS [Truta] +- Modify test_gzio in example.c to take a single file name as a + parameter [Truta] +- Exit the example.c program if gzopen fails [Truta] +- Add type casts around strlen in example.c [Truta] +- Remove casting to sizeof in minigzip.c; give a proper type + to the variable compared with SUFFIX_LEN [Truta] +- Update definitions of STDC and STDC99 in zconf.h [Truta] +- Synchronize zconf.h with the new Windows DLL interface [Truta] +- Use SYS16BIT instead of __32BIT__ to distinguish between + 16- and 32-bit platforms [Truta] +- Use far memory allocators in small 16-bit memory models for + Turbo C [Truta] +- Add info about the use of ASMV, ASMINF and ZLIB_WINAPI in + zlibCompileFlags [Truta] +- Cygwin has vsnprintf [Wilson] +- In Windows16, OS_CODE is 0, as in MSDOS [Truta] +- In Cygwin, OS_CODE is 3 (Unix), not 11 (Windows32) [Wilson] + +Changes in 1.2.0.4 (10 August 2003) +- Minor FAQ updates +- Be more strict when checking inflateInit2's windowBits parameter +- Change NO_GUNZIP compile option to NO_GZIP to cover deflate as well +- Add gzip wrapper option to deflateInit2 using windowBits +- Add updated QNX rule in configure and qnx directory [Bonnefoy] +- Make inflate distance-too-far checks more rigorous +- Clean up FAR usage in inflate +- Add casting to sizeof() in gzio.c and minigzip.c + +Changes in 1.2.0.3 (19 July 2003) +- Fix silly error in gzungetc() implementation [Vollant] +- Update contrib/minizip and contrib/vstudio [Vollant] +- Fix printf format in example.c +- Correct cdecl support in zconf.in.h [Anisimkov] +- Minor FAQ updates + +Changes in 1.2.0.2 (13 July 2003) +- Add ZLIB_VERNUM in zlib.h for numerical preprocessor comparisons +- Attempt to avoid warnings in crc32.c for pointer-int conversion +- Add AIX to configure, remove aix directory [Bakker] +- Add some casts to minigzip.c +- Improve checking after insecure sprintf() or vsprintf() calls +- Remove #elif's from crc32.c +- Change leave label to inf_leave in inflate.c and infback.c to avoid + library conflicts +- Remove inflate gzip decoding by default--only enable gzip decoding by + special request for stricter backward compatibility +- Add zlibCompileFlags() function to return compilation information +- More typecasting in deflate.c to avoid warnings +- Remove leading underscore from _Capital #defines [Truta] +- Fix configure to link shared library when testing +- Add some Windows CE target adjustments [Mai] +- Remove #define ZLIB_DLL in zconf.h [Vollant] +- Add zlib.3 [Rodgers] +- Update RFC URL in deflate.c and algorithm.txt [Mai] +- Add zlib_dll_FAQ.txt to contrib [Truta] +- Add UL to some constants [Truta] +- Update minizip and vstudio [Vollant] +- Remove vestigial NEED_DUMMY_RETURN from zconf.in.h +- Expand use of NO_DUMMY_DECL to avoid all dummy structures +- Added iostream3 to contrib [Schwardt] +- Replace rewind() with fseek() for WinCE [Truta] +- Improve setting of zlib format compression level flags + - Report 0 for huffman and rle strategies and for level == 0 or 1 + - Report 2 only for level == 6 +- Only deal with 64K limit when necessary at compile time [Truta] +- Allow TOO_FAR check to be turned off at compile time [Truta] +- Add gzclearerr() function [Souza] +- Add gzungetc() function + +Changes in 1.2.0.1 (17 March 2003) +- Add Z_RLE strategy for run-length encoding [Truta] + - When Z_RLE requested, restrict matches to distance one + - Update zlib.h, minigzip.c, gzopen(), gzdopen() for Z_RLE +- Correct FASTEST compilation to allow level == 0 +- Clean up what gets compiled for FASTEST +- Incorporate changes to zconf.in.h [Vollant] + - Refine detection of Turbo C need for dummy returns + - Refine ZLIB_DLL compilation + - Include additional header file on VMS for off_t typedef +- Try to use _vsnprintf where it supplants vsprintf [Vollant] +- Add some casts in inffast.c +- Enchance comments in zlib.h on what happens if gzprintf() tries to + write more than 4095 bytes before compression +- Remove unused state from inflateBackEnd() +- Remove exit(0) from minigzip.c, example.c +- Get rid of all those darn tabs +- Add "check" target to Makefile.in that does the same thing as "test" +- Add "mostlyclean" and "maintainer-clean" targets to Makefile.in +- Update contrib/inflate86 [Anderson] +- Update contrib/testzlib, contrib/vstudio, contrib/minizip [Vollant] +- Add msdos and win32 directories with makefiles [Truta] +- More additions and improvements to the FAQ + +Changes in 1.2.0 (9 March 2003) +- New and improved inflate code + - About 20% faster + - Does not allocate 32K window unless and until needed + - Automatically detects and decompresses gzip streams + - Raw inflate no longer needs an extra dummy byte at end + - Added inflateBack functions using a callback interface--even faster + than inflate, useful for file utilities (gzip, zip) + - Added inflateCopy() function to record state for random access on + externally generated deflate streams (e.g. in gzip files) + - More readable code (I hope) +- New and improved crc32() + - About 50% faster, thanks to suggestions from Rodney Brown +- Add deflateBound() and compressBound() functions +- Fix memory leak in deflateInit2() +- Permit setting dictionary for raw deflate (for parallel deflate) +- Fix const declaration for gzwrite() +- Check for some malloc() failures in gzio.c +- Fix bug in gzopen() on single-byte file 0x1f +- Fix bug in gzread() on concatenated file with 0x1f at end of buffer + and next buffer doesn't start with 0x8b +- Fix uncompress() to return Z_DATA_ERROR on truncated input +- Free memory at end of example.c +- Remove MAX #define in trees.c (conflicted with some libraries) +- Fix static const's in deflate.c, gzio.c, and zutil.[ch] +- Declare malloc() and free() in gzio.c if STDC not defined +- Use malloc() instead of calloc() in zutil.c if int big enough +- Define STDC for AIX +- Add aix/ with approach for compiling shared library on AIX +- Add HP-UX support for shared libraries in configure +- Add OpenUNIX support for shared libraries in configure +- Use $cc instead of gcc to build shared library +- Make prefix directory if needed when installing +- Correct Macintosh avoidance of typedef Byte in zconf.h +- Correct Turbo C memory allocation when under Linux +- Use libz.a instead of -lz in Makefile (assure use of compiled library) +- Update configure to check for snprintf or vsnprintf functions and their + return value, warn during make if using an insecure function +- Fix configure problem with compile-time knowledge of HAVE_UNISTD_H that + is lost when library is used--resolution is to build new zconf.h +- Documentation improvements (in zlib.h): + - Document raw deflate and inflate + - Update RFCs URL + - Point out that zlib and gzip formats are different + - Note that Z_BUF_ERROR is not fatal + - Document string limit for gzprintf() and possible buffer overflow + - Note requirement on avail_out when flushing + - Note permitted values of flush parameter of inflate() +- Add some FAQs (and even answers) to the FAQ +- Add contrib/inflate86/ for x86 faster inflate +- Add contrib/blast/ for PKWare Data Compression Library decompression +- Add contrib/puff/ simple inflate for deflate format description + +Changes in 1.1.4 (11 March 2002) +- ZFREE was repeated on same allocation on some error conditions. + This creates a security problem described in + http://www.zlib.org/advisory-2002-03-11.txt +- Returned incorrect error (Z_MEM_ERROR) on some invalid data +- Avoid accesses before window for invalid distances with inflate window + less than 32K. +- force windowBits > 8 to avoid a bug in the encoder for a window size + of 256 bytes. (A complete fix will be available in 1.1.5). + +Changes in 1.1.3 (9 July 1998) +- fix "an inflate input buffer bug that shows up on rare but persistent + occasions" (Mark) +- fix gzread and gztell for concatenated .gz files (Didier Le Botlan) +- fix gzseek(..., SEEK_SET) in write mode +- fix crc check after a gzeek (Frank Faubert) +- fix miniunzip when the last entry in a zip file is itself a zip file + (J Lillge) +- add contrib/asm586 and contrib/asm686 (Brian Raiter) + See http://www.muppetlabs.com/~breadbox/software/assembly.html +- add support for Delphi 3 in contrib/delphi (Bob Dellaca) +- add support for C++Builder 3 and Delphi 3 in contrib/delphi2 (Davide Moretti) +- do not exit prematurely in untgz if 0 at start of block (Magnus Holmgren) +- use macro EXTERN instead of extern to support DLL for BeOS (Sander Stoks) +- added a FAQ file + +- Support gzdopen on Mac with Metrowerks (Jason Linhart) +- Do not redefine Byte on Mac (Brad Pettit & Jason Linhart) +- define SEEK_END too if SEEK_SET is not defined (Albert Chin-A-Young) +- avoid some warnings with Borland C (Tom Tanner) +- fix a problem in contrib/minizip/zip.c for 16-bit MSDOS (Gilles Vollant) +- emulate utime() for WIN32 in contrib/untgz (Gilles Vollant) +- allow several arguments to configure (Tim Mooney, Frodo Looijaard) +- use libdir and includedir in Makefile.in (Tim Mooney) +- support shared libraries on OSF1 V4 (Tim Mooney) +- remove so_locations in "make clean" (Tim Mooney) +- fix maketree.c compilation error (Glenn, Mark) +- Python interface to zlib now in Python 1.5 (Jeremy Hylton) +- new Makefile.riscos (Rich Walker) +- initialize static descriptors in trees.c for embedded targets (Nick Smith) +- use "foo-gz" in example.c for RISCOS and VMS (Nick Smith) +- add the OS/2 files in Makefile.in too (Andrew Zabolotny) +- fix fdopen and halloc macros for Microsoft C 6.0 (Tom Lane) +- fix maketree.c to allow clean compilation of inffixed.h (Mark) +- fix parameter check in deflateCopy (Gunther Nikl) +- cleanup trees.c, use compressed_len only in debug mode (Christian Spieler) +- Many portability patches by Christian Spieler: + . zutil.c, zutil.h: added "const" for zmem* + . Make_vms.com: fixed some typos + . Make_vms.com: msdos/Makefile.*: removed zutil.h from some dependency lists + . msdos/Makefile.msc: remove "default rtl link library" info from obj files + . msdos/Makefile.*: use model-dependent name for the built zlib library + . msdos/Makefile.emx, nt/Makefile.emx, nt/Makefile.gcc: + new makefiles, for emx (DOS/OS2), emx&rsxnt and mingw32 (Windows 9x / NT) +- use define instead of typedef for Bytef also for MSC small/medium (Tom Lane) +- replace __far with _far for better portability (Christian Spieler, Tom Lane) +- fix test for errno.h in configure (Tim Newsham) + +Changes in 1.1.2 (19 March 98) +- added contrib/minzip, mini zip and unzip based on zlib (Gilles Vollant) + See http://www.winimage.com/zLibDll/unzip.html +- preinitialize the inflate tables for fixed codes, to make the code + completely thread safe (Mark) +- some simplifications and slight speed-up to the inflate code (Mark) +- fix gzeof on non-compressed files (Allan Schrum) +- add -std1 option in configure for OSF1 to fix gzprintf (Martin Mokrejs) +- use default value of 4K for Z_BUFSIZE for 16-bit MSDOS (Tim Wegner + Glenn) +- added os2/Makefile.def and os2/zlib.def (Andrew Zabolotny) +- add shared lib support for UNIX_SV4.2MP (MATSUURA Takanori) +- do not wrap extern "C" around system includes (Tom Lane) +- mention zlib binding for TCL in README (Andreas Kupries) +- added amiga/Makefile.pup for Amiga powerUP SAS/C PPC (Andreas Kleinert) +- allow "make install prefix=..." even after configure (Glenn Randers-Pehrson) +- allow "configure --prefix $HOME" (Tim Mooney) +- remove warnings in example.c and gzio.c (Glenn Randers-Pehrson) +- move Makefile.sas to amiga/Makefile.sas + +Changes in 1.1.1 (27 Feb 98) +- fix macros _tr_tally_* in deflate.h for debug mode (Glenn Randers-Pehrson) +- remove block truncation heuristic which had very marginal effect for zlib + (smaller lit_bufsize than in gzip 1.2.4) and degraded a little the + compression ratio on some files. This also allows inlining _tr_tally for + matches in deflate_slow. +- added msdos/Makefile.w32 for WIN32 Microsoft Visual C++ (Bob Frazier) + +Changes in 1.1.0 (24 Feb 98) +- do not return STREAM_END prematurely in inflate (John Bowler) +- revert to the zlib 1.0.8 inflate to avoid the gcc 2.8.0 bug (Jeremy Buhler) +- compile with -DFASTEST to get compression code optimized for speed only +- in minigzip, try mmap'ing the input file first (Miguel Albrecht) +- increase size of I/O buffers in minigzip.c and gzio.c (not a big gain + on Sun but significant on HP) + +- add a pointer to experimental unzip library in README (Gilles Vollant) +- initialize variable gcc in configure (Chris Herborth) + +Changes in 1.0.9 (17 Feb 1998) +- added gzputs and gzgets functions +- do not clear eof flag in gzseek (Mark Diekhans) +- fix gzseek for files in transparent mode (Mark Diekhans) +- do not assume that vsprintf returns the number of bytes written (Jens Krinke) +- replace EXPORT with ZEXPORT to avoid conflict with other programs +- added compress2 in zconf.h, zlib.def, zlib.dnt +- new asm code from Gilles Vollant in contrib/asm386 +- simplify the inflate code (Mark): + . Replace ZALLOC's in huft_build() with single ZALLOC in inflate_blocks_new() + . ZALLOC the length list in inflate_trees_fixed() instead of using stack + . ZALLOC the value area for huft_build() instead of using stack + . Simplify Z_FINISH check in inflate() + +- Avoid gcc 2.8.0 comparison bug a little differently than zlib 1.0.8 +- in inftrees.c, avoid cc -O bug on HP (Farshid Elahi) +- in zconf.h move the ZLIB_DLL stuff earlier to avoid problems with + the declaration of FAR (Gilles VOllant) +- install libz.so* with mode 755 (executable) instead of 644 (Marc Lehmann) +- read_buf buf parameter of type Bytef* instead of charf* +- zmemcpy parameters are of type Bytef*, not charf* (Joseph Strout) +- do not redeclare unlink in minigzip.c for WIN32 (John Bowler) +- fix check for presence of directories in "make install" (Ian Willis) + +Changes in 1.0.8 (27 Jan 1998) +- fixed offsets in contrib/asm386/gvmat32.asm (Gilles Vollant) +- fix gzgetc and gzputc for big endian systems (Markus Oberhumer) +- added compress2() to allow setting the compression level +- include sys/types.h to get off_t on some systems (Marc Lehmann & QingLong) +- use constant arrays for the static trees in trees.c instead of computing + them at run time (thanks to Ken Raeburn for this suggestion). To create + trees.h, compile with GEN_TREES_H and run "make test". +- check return code of example in "make test" and display result +- pass minigzip command line options to file_compress +- simplifying code of inflateSync to avoid gcc 2.8 bug + +- support CC="gcc -Wall" in configure -s (QingLong) +- avoid a flush caused by ftell in gzopen for write mode (Ken Raeburn) +- fix test for shared library support to avoid compiler warnings +- zlib.lib -> zlib.dll in msdos/zlib.rc (Gilles Vollant) +- check for TARGET_OS_MAC in addition to MACOS (Brad Pettit) +- do not use fdopen for Metrowerks on Mac (Brad Pettit)) +- add checks for gzputc and gzputc in example.c +- avoid warnings in gzio.c and deflate.c (Andreas Kleinert) +- use const for the CRC table (Ken Raeburn) +- fixed "make uninstall" for shared libraries +- use Tracev instead of Trace in infblock.c +- in example.c use correct compressed length for test_sync +- suppress +vnocompatwarnings in configure for HPUX (not always supported) + +Changes in 1.0.7 (20 Jan 1998) +- fix gzseek which was broken in write mode +- return error for gzseek to negative absolute position +- fix configure for Linux (Chun-Chung Chen) +- increase stack space for MSC (Tim Wegner) +- get_crc_table and inflateSyncPoint are EXPORTed (Gilles Vollant) +- define EXPORTVA for gzprintf (Gilles Vollant) +- added man page zlib.3 (Rick Rodgers) +- for contrib/untgz, fix makedir() and improve Makefile + +- check gzseek in write mode in example.c +- allocate extra buffer for seeks only if gzseek is actually called +- avoid signed/unsigned comparisons (Tim Wegner, Gilles Vollant) +- add inflateSyncPoint in zconf.h +- fix list of exported functions in nt/zlib.dnt and mdsos/zlib.def + +Changes in 1.0.6 (19 Jan 1998) +- add functions gzprintf, gzputc, gzgetc, gztell, gzeof, gzseek, gzrewind and + gzsetparams (thanks to Roland Giersig and Kevin Ruland for some of this code) +- Fix a deflate bug occurring only with compression level 0 (thanks to + Andy Buckler for finding this one). +- In minigzip, pass transparently also the first byte for .Z files. +- return Z_BUF_ERROR instead of Z_OK if output buffer full in uncompress() +- check Z_FINISH in inflate (thanks to Marc Schluper) +- Implement deflateCopy (thanks to Adam Costello) +- make static libraries by default in configure, add --shared option. +- move MSDOS or Windows specific files to directory msdos +- suppress the notion of partial flush to simplify the interface + (but the symbol Z_PARTIAL_FLUSH is kept for compatibility with 1.0.4) +- suppress history buffer provided by application to simplify the interface + (this feature was not implemented anyway in 1.0.4) +- next_in and avail_in must be initialized before calling inflateInit or + inflateInit2 +- add EXPORT in all exported functions (for Windows DLL) +- added Makefile.nt (thanks to Stephen Williams) +- added the unsupported "contrib" directory: + contrib/asm386/ by Gilles Vollant + 386 asm code replacing longest_match(). + contrib/iostream/ by Kevin Ruland + A C++ I/O streams interface to the zlib gz* functions + contrib/iostream2/ by Tyge Løvset + Another C++ I/O streams interface + contrib/untgz/ by "Pedro A. Aranda Guti\irrez" + A very simple tar.gz file extractor using zlib + contrib/visual-basic.txt by Carlos Rios + How to use compress(), uncompress() and the gz* functions from VB. +- pass params -f (filtered data), -h (huffman only), -1 to -9 (compression + level) in minigzip (thanks to Tom Lane) + +- use const for rommable constants in deflate +- added test for gzseek and gztell in example.c +- add undocumented function inflateSyncPoint() (hack for Paul Mackerras) +- add undocumented function zError to convert error code to string + (for Tim Smithers) +- Allow compilation of gzio with -DNO_DEFLATE to avoid the compression code. +- Use default memcpy for Symantec MSDOS compiler. +- Add EXPORT keyword for check_func (needed for Windows DLL) +- add current directory to LD_LIBRARY_PATH for "make test" +- create also a link for libz.so.1 +- added support for FUJITSU UXP/DS (thanks to Toshiaki Nomura) +- use $(SHAREDLIB) instead of libz.so in Makefile.in (for HPUX) +- added -soname for Linux in configure (Chun-Chung Chen, +- assign numbers to the exported functions in zlib.def (for Windows DLL) +- add advice in zlib.h for best usage of deflateSetDictionary +- work around compiler bug on Atari (cast Z_NULL in call of s->checkfn) +- allow compilation with ANSI keywords only enabled for TurboC in large model +- avoid "versionString"[0] (Borland bug) +- add NEED_DUMMY_RETURN for Borland +- use variable z_verbose for tracing in debug mode (L. Peter Deutsch). +- allow compilation with CC +- defined STDC for OS/2 (David Charlap) +- limit external names to 8 chars for MVS (Thomas Lund) +- in minigzip.c, use static buffers only for 16-bit systems +- fix suffix check for "minigzip -d foo.gz" +- do not return an error for the 2nd of two consecutive gzflush() (Felix Lee) +- use _fdopen instead of fdopen for MSC >= 6.0 (Thomas Fanslau) +- added makelcc.bat for lcc-win32 (Tom St Denis) +- in Makefile.dj2, use copy and del instead of install and rm (Frank Donahoe) +- Avoid expanded $Id$. Use "rcs -kb" or "cvs admin -kb" to avoid Id expansion. +- check for unistd.h in configure (for off_t) +- remove useless check parameter in inflate_blocks_free +- avoid useless assignment of s->check to itself in inflate_blocks_new +- do not flush twice in gzclose (thanks to Ken Raeburn) +- rename FOPEN as F_OPEN to avoid clash with /usr/include/sys/file.h +- use NO_ERRNO_H instead of enumeration of operating systems with errno.h +- work around buggy fclose on pipes for HP/UX +- support zlib DLL with BORLAND C++ 5.0 (thanks to Glenn Randers-Pehrson) +- fix configure if CC is already equal to gcc + +Changes in 1.0.5 (3 Jan 98) +- Fix inflate to terminate gracefully when fed corrupted or invalid data +- Use const for rommable constants in inflate +- Eliminate memory leaks on error conditions in inflate +- Removed some vestigial code in inflate +- Update web address in README + +Changes in 1.0.4 (24 Jul 96) +- In very rare conditions, deflate(s, Z_FINISH) could fail to produce an EOF + bit, so the decompressor could decompress all the correct data but went + on to attempt decompressing extra garbage data. This affected minigzip too. +- zlibVersion and gzerror return const char* (needed for DLL) +- port to RISCOS (no fdopen, no multiple dots, no unlink, no fileno) +- use z_error only for DEBUG (avoid problem with DLLs) + +Changes in 1.0.3 (2 Jul 96) +- use z_streamp instead of z_stream *, which is now a far pointer in MSDOS + small and medium models; this makes the library incompatible with previous + versions for these models. (No effect in large model or on other systems.) +- return OK instead of BUF_ERROR if previous deflate call returned with + avail_out as zero but there is nothing to do +- added memcmp for non STDC compilers +- define NO_DUMMY_DECL for more Mac compilers (.h files merged incorrectly) +- define __32BIT__ if __386__ or i386 is defined (pb. with Watcom and SCO) +- better check for 16-bit mode MSC (avoids problem with Symantec) + +Changes in 1.0.2 (23 May 96) +- added Windows DLL support +- added a function zlibVersion (for the DLL support) +- fixed declarations using Bytef in infutil.c (pb with MSDOS medium model) +- Bytef is define's instead of typedef'd only for Borland C +- avoid reading uninitialized memory in example.c +- mention in README that the zlib format is now RFC1950 +- updated Makefile.dj2 +- added algorithm.doc + +Changes in 1.0.1 (20 May 96) [1.0 skipped to avoid confusion] +- fix array overlay in deflate.c which sometimes caused bad compressed data +- fix inflate bug with empty stored block +- fix MSDOS medium model which was broken in 0.99 +- fix deflateParams() which could generate bad compressed data. +- Bytef is define'd instead of typedef'ed (work around Borland bug) +- added an INDEX file +- new makefiles for DJGPP (Makefile.dj2), 32-bit Borland (Makefile.b32), + Watcom (Makefile.wat), Amiga SAS/C (Makefile.sas) +- speed up adler32 for modern machines without auto-increment +- added -ansi for IRIX in configure +- static_init_done in trees.c is an int +- define unlink as delete for VMS +- fix configure for QNX +- add configure branch for SCO and HPUX +- avoid many warnings (unused variables, dead assignments, etc...) +- no fdopen for BeOS +- fix the Watcom fix for 32 bit mode (define FAR as empty) +- removed redefinition of Byte for MKWERKS +- work around an MWKERKS bug (incorrect merge of all .h files) + +Changes in 0.99 (27 Jan 96) +- allow preset dictionary shared between compressor and decompressor +- allow compression level 0 (no compression) +- add deflateParams in zlib.h: allow dynamic change of compression level + and compression strategy. +- test large buffers and deflateParams in example.c +- add optional "configure" to build zlib as a shared library +- suppress Makefile.qnx, use configure instead +- fixed deflate for 64-bit systems (detected on Cray) +- fixed inflate_blocks for 64-bit systems (detected on Alpha) +- declare Z_DEFLATED in zlib.h (possible parameter for deflateInit2) +- always return Z_BUF_ERROR when deflate() has nothing to do +- deflateInit and inflateInit are now macros to allow version checking +- prefix all global functions and types with z_ with -DZ_PREFIX +- make falloc completely reentrant (inftrees.c) +- fixed very unlikely race condition in ct_static_init +- free in reverse order of allocation to help memory manager +- use zlib-1.0/* instead of zlib/* inside the tar.gz +- make zlib warning-free with "gcc -O3 -Wall -Wwrite-strings -Wpointer-arith + -Wconversion -Wstrict-prototypes -Wmissing-prototypes" +- allow gzread on concatenated .gz files +- deflateEnd now returns Z_DATA_ERROR if it was premature +- deflate is finally (?) fully deterministic (no matches beyond end of input) +- Document Z_SYNC_FLUSH +- add uninstall in Makefile +- Check for __cpluplus in zlib.h +- Better test in ct_align for partial flush +- avoid harmless warnings for Borland C++ +- initialize hash_head in deflate.c +- avoid warning on fdopen (gzio.c) for HP cc -Aa +- include stdlib.h for STDC compilers +- include errno.h for Cray +- ignore error if ranlib doesn't exist +- call ranlib twice for NeXTSTEP +- use exec_prefix instead of prefix for libz.a +- renamed ct_* as _tr_* to avoid conflict with applications +- clear z->msg in inflateInit2 before any error return +- initialize opaque in example.c, gzio.c, deflate.c and inflate.c +- fixed typo in zconf.h (_GNUC__ => __GNUC__) +- check for WIN32 in zconf.h and zutil.c (avoid farmalloc in 32-bit mode) +- fix typo in Make_vms.com (f$trnlnm -> f$getsyi) +- in fcalloc, normalize pointer if size > 65520 bytes +- don't use special fcalloc for 32 bit Borland C++ +- use STDC instead of __GO32__ to avoid redeclaring exit, calloc, etc... +- use Z_BINARY instead of BINARY +- document that gzclose after gzdopen will close the file +- allow "a" as mode in gzopen. +- fix error checking in gzread +- allow skipping .gz extra-field on pipes +- added reference to Perl interface in README +- put the crc table in FAR data (I dislike more and more the medium model :) +- added get_crc_table +- added a dimension to all arrays (Borland C can't count). +- workaround Borland C bug in declaration of inflate_codes_new & inflate_fast +- guard against multiple inclusion of *.h (for precompiled header on Mac) +- Watcom C pretends to be Microsoft C small model even in 32 bit mode. +- don't use unsized arrays to avoid silly warnings by Visual C++: + warning C4746: 'inflate_mask' : unsized array treated as '__far' + (what's wrong with far data in far model?). +- define enum out of inflate_blocks_state to allow compilation with C++ + +Changes in 0.95 (16 Aug 95) +- fix MSDOS small and medium model (now easier to adapt to any compiler) +- inlined send_bits +- fix the final (:-) bug for deflate with flush (output was correct but + not completely flushed in rare occasions). +- default window size is same for compression and decompression + (it's now sufficient to set MAX_WBITS in zconf.h). +- voidp -> voidpf and voidnp -> voidp (for consistency with other + typedefs and because voidnp was not near in large model). + +Changes in 0.94 (13 Aug 95) +- support MSDOS medium model +- fix deflate with flush (could sometimes generate bad output) +- fix deflateReset (zlib header was incorrectly suppressed) +- added support for VMS +- allow a compression level in gzopen() +- gzflush now calls fflush +- For deflate with flush, flush even if no more input is provided. +- rename libgz.a as libz.a +- avoid complex expression in infcodes.c triggering Turbo C bug +- work around a problem with gcc on Alpha (in INSERT_STRING) +- don't use inline functions (problem with some gcc versions) +- allow renaming of Byte, uInt, etc... with #define. +- avoid warning about (unused) pointer before start of array in deflate.c +- avoid various warnings in gzio.c, example.c, infblock.c, adler32.c, zutil.c +- avoid reserved word 'new' in trees.c + +Changes in 0.93 (25 June 95) +- temporarily disable inline functions +- make deflate deterministic +- give enough lookahead for PARTIAL_FLUSH +- Set binary mode for stdin/stdout in minigzip.c for OS/2 +- don't even use signed char in inflate (not portable enough) +- fix inflate memory leak for segmented architectures + +Changes in 0.92 (3 May 95) +- don't assume that char is signed (problem on SGI) +- Clear bit buffer when starting a stored block +- no memcpy on Pyramid +- suppressed inftest.c +- optimized fill_window, put longest_match inline for gcc +- optimized inflate on stored blocks. +- untabify all sources to simplify patches + +Changes in 0.91 (2 May 95) +- Default MEM_LEVEL is 8 (not 9 for Unix) as documented in zlib.h +- Document the memory requirements in zconf.h +- added "make install" +- fix sync search logic in inflateSync +- deflate(Z_FULL_FLUSH) now works even if output buffer too short +- after inflateSync, don't scare people with just "lo world" +- added support for DJGPP + +Changes in 0.9 (1 May 95) +- don't assume that zalloc clears the allocated memory (the TurboC bug + was Mark's bug after all :) +- let again gzread copy uncompressed data unchanged (was working in 0.71) +- deflate(Z_FULL_FLUSH), inflateReset and inflateSync are now fully implemented +- added a test of inflateSync in example.c +- moved MAX_WBITS to zconf.h because users might want to change that. +- document explicitly that zalloc(64K) on MSDOS must return a normalized + pointer (zero offset) +- added Makefiles for Microsoft C, Turbo C, Borland C++ +- faster crc32() + +Changes in 0.8 (29 April 95) +- added fast inflate (inffast.c) +- deflate(Z_FINISH) now returns Z_STREAM_END when done. Warning: this + is incompatible with previous versions of zlib which returned Z_OK. +- work around a TurboC compiler bug (bad code for b << 0, see infutil.h) + (actually that was not a compiler bug, see 0.81 above) +- gzread no longer reads one extra byte in certain cases +- In gzio destroy(), don't reference a freed structure +- avoid many warnings for MSDOS +- avoid the ERROR symbol which is used by MS Windows + +Changes in 0.71 (14 April 95) +- Fixed more MSDOS compilation problems :( There is still a bug with + TurboC large model. + +Changes in 0.7 (14 April 95) +- Added full inflate support. +- Simplified the crc32() interface. The pre- and post-conditioning + (one's complement) is now done inside crc32(). WARNING: this is + incompatible with previous versions; see zlib.h for the new usage. + +Changes in 0.61 (12 April 95) +- workaround for a bug in TurboC. example and minigzip now work on MSDOS. + +Changes in 0.6 (11 April 95) +- added minigzip.c +- added gzdopen to reopen a file descriptor as gzFile +- added transparent reading of non-gziped files in gzread. +- fixed bug in gzread (don't read crc as data) +- fixed bug in destroy (gzio.c) (don't return Z_STREAM_END for gzclose). +- don't allocate big arrays in the stack (for MSDOS) +- fix some MSDOS compilation problems + +Changes in 0.5: +- do real compression in deflate.c. Z_PARTIAL_FLUSH is supported but + not yet Z_FULL_FLUSH. +- support decompression but only in a single step (forced Z_FINISH) +- added opaque object for zalloc and zfree. +- added deflateReset and inflateReset +- added a variable zlib_version for consistency checking. +- renamed the 'filter' parameter of deflateInit2 as 'strategy'. + Added Z_FILTERED and Z_HUFFMAN_ONLY constants. + +Changes in 0.4: +- avoid "zip" everywhere, use zlib instead of ziplib. +- suppress Z_BLOCK_FLUSH, interpret Z_PARTIAL_FLUSH as block flush + if compression method == 8. +- added adler32 and crc32 +- renamed deflateOptions as deflateInit2, call one or the other but not both +- added the method parameter for deflateInit2. +- added inflateInit2 +- simplied considerably deflateInit and inflateInit by not supporting + user-provided history buffer. This is supported only in deflateInit2 + and inflateInit2. + +Changes in 0.3: +- prefix all macro names with Z_ +- use Z_FINISH instead of deflateEnd to finish compression. +- added Z_HUFFMAN_ONLY +- added gzerror() ADDED compat/zlib/FAQ Index: compat/zlib/FAQ ================================================================== --- /dev/null +++ compat/zlib/FAQ @@ -0,0 +1,368 @@ + + Frequently Asked Questions about zlib + + +If your question is not there, please check the zlib home page +http://zlib.net/ which may have more recent information. +The lastest zlib FAQ is at http://zlib.net/zlib_faq.html + + + 1. Is zlib Y2K-compliant? + + Yes. zlib doesn't handle dates. + + 2. Where can I get a Windows DLL version? + + The zlib sources can be compiled without change to produce a DLL. See the + file win32/DLL_FAQ.txt in the zlib distribution. Pointers to the + precompiled DLL are found in the zlib web site at http://zlib.net/ . + + 3. Where can I get a Visual Basic interface to zlib? + + See + * http://marknelson.us/1997/01/01/zlib-engine/ + * win32/DLL_FAQ.txt in the zlib distribution + + 4. compress() returns Z_BUF_ERROR. + + Make sure that before the call of compress(), the length of the compressed + buffer is equal to the available size of the compressed buffer and not + zero. For Visual Basic, check that this parameter is passed by reference + ("as any"), not by value ("as long"). + + 5. deflate() or inflate() returns Z_BUF_ERROR. + + Before making the call, make sure that avail_in and avail_out are not zero. + When setting the parameter flush equal to Z_FINISH, also make sure that + avail_out is big enough to allow processing all pending input. Note that a + Z_BUF_ERROR is not fatal--another call to deflate() or inflate() can be + made with more input or output space. A Z_BUF_ERROR may in fact be + unavoidable depending on how the functions are used, since it is not + possible to tell whether or not there is more output pending when + strm.avail_out returns with zero. See http://zlib.net/zlib_how.html for a + heavily annotated example. + + 6. Where's the zlib documentation (man pages, etc.)? + + It's in zlib.h . Examples of zlib usage are in the files test/example.c + and test/minigzip.c, with more in examples/ . + + 7. Why don't you use GNU autoconf or libtool or ...? + + Because we would like to keep zlib as a very small and simple package. + zlib is rather portable and doesn't need much configuration. + + 8. I found a bug in zlib. + + Most of the time, such problems are due to an incorrect usage of zlib. + Please try to reproduce the problem with a small program and send the + corresponding source to us at zlib@gzip.org . Do not send multi-megabyte + data files without prior agreement. + + 9. Why do I get "undefined reference to gzputc"? + + If "make test" produces something like + + example.o(.text+0x154): undefined reference to `gzputc' + + check that you don't have old files libz.* in /usr/lib, /usr/local/lib or + /usr/X11R6/lib. Remove any old versions, then do "make install". + +10. I need a Delphi interface to zlib. + + See the contrib/delphi directory in the zlib distribution. + +11. Can zlib handle .zip archives? + + Not by itself, no. See the directory contrib/minizip in the zlib + distribution. + +12. Can zlib handle .Z files? + + No, sorry. You have to spawn an uncompress or gunzip subprocess, or adapt + the code of uncompress on your own. + +13. How can I make a Unix shared library? + + By default a shared (and a static) library is built for Unix. So: + + make distclean + ./configure + make + +14. How do I install a shared zlib library on Unix? + + After the above, then: + + make install + + However, many flavors of Unix come with a shared zlib already installed. + Before going to the trouble of compiling a shared version of zlib and + trying to install it, you may want to check if it's already there! If you + can #include , it's there. The -lz option will probably link to + it. You can check the version at the top of zlib.h or with the + ZLIB_VERSION symbol defined in zlib.h . + +15. I have a question about OttoPDF. + + We are not the authors of OttoPDF. The real author is on the OttoPDF web + site: Joel Hainley, jhainley@myndkryme.com. + +16. Can zlib decode Flate data in an Adobe PDF file? + + Yes. See http://www.pdflib.com/ . To modify PDF forms, see + http://sourceforge.net/projects/acroformtool/ . + +17. Why am I getting this "register_frame_info not found" error on Solaris? + + After installing zlib 1.1.4 on Solaris 2.6, running applications using zlib + generates an error such as: + + ld.so.1: rpm: fatal: relocation error: file /usr/local/lib/libz.so: + symbol __register_frame_info: referenced symbol not found + + The symbol __register_frame_info is not part of zlib, it is generated by + the C compiler (cc or gcc). You must recompile applications using zlib + which have this problem. This problem is specific to Solaris. See + http://www.sunfreeware.com for Solaris versions of zlib and applications + using zlib. + +18. Why does gzip give an error on a file I make with compress/deflate? + + The compress and deflate functions produce data in the zlib format, which + is different and incompatible with the gzip format. The gz* functions in + zlib on the other hand use the gzip format. Both the zlib and gzip formats + use the same compressed data format internally, but have different headers + and trailers around the compressed data. + +19. Ok, so why are there two different formats? + + The gzip format was designed to retain the directory information about a + single file, such as the name and last modification date. The zlib format + on the other hand was designed for in-memory and communication channel + applications, and has a much more compact header and trailer and uses a + faster integrity check than gzip. + +20. Well that's nice, but how do I make a gzip file in memory? + + You can request that deflate write the gzip format instead of the zlib + format using deflateInit2(). You can also request that inflate decode the + gzip format using inflateInit2(). Read zlib.h for more details. + +21. Is zlib thread-safe? + + Yes. However any library routines that zlib uses and any application- + provided memory allocation routines must also be thread-safe. zlib's gz* + functions use stdio library routines, and most of zlib's functions use the + library memory allocation routines by default. zlib's *Init* functions + allow for the application to provide custom memory allocation routines. + + Of course, you should only operate on any given zlib or gzip stream from a + single thread at a time. + +22. Can I use zlib in my commercial application? + + Yes. Please read the license in zlib.h. + +23. Is zlib under the GNU license? + + No. Please read the license in zlib.h. + +24. The license says that altered source versions must be "plainly marked". So + what exactly do I need to do to meet that requirement? + + You need to change the ZLIB_VERSION and ZLIB_VERNUM #defines in zlib.h. In + particular, the final version number needs to be changed to "f", and an + identification string should be appended to ZLIB_VERSION. Version numbers + x.x.x.f are reserved for modifications to zlib by others than the zlib + maintainers. For example, if the version of the base zlib you are altering + is "1.2.3.4", then in zlib.h you should change ZLIB_VERNUM to 0x123f, and + ZLIB_VERSION to something like "1.2.3.f-zachary-mods-v3". You can also + update the version strings in deflate.c and inftrees.c. + + For altered source distributions, you should also note the origin and + nature of the changes in zlib.h, as well as in ChangeLog and README, along + with the dates of the alterations. The origin should include at least your + name (or your company's name), and an email address to contact for help or + issues with the library. + + Note that distributing a compiled zlib library along with zlib.h and + zconf.h is also a source distribution, and so you should change + ZLIB_VERSION and ZLIB_VERNUM and note the origin and nature of the changes + in zlib.h as you would for a full source distribution. + +25. Will zlib work on a big-endian or little-endian architecture, and can I + exchange compressed data between them? + + Yes and yes. + +26. Will zlib work on a 64-bit machine? + + Yes. It has been tested on 64-bit machines, and has no dependence on any + data types being limited to 32-bits in length. If you have any + difficulties, please provide a complete problem report to zlib@gzip.org + +27. Will zlib decompress data from the PKWare Data Compression Library? + + No. The PKWare DCL uses a completely different compressed data format than + does PKZIP and zlib. However, you can look in zlib's contrib/blast + directory for a possible solution to your problem. + +28. Can I access data randomly in a compressed stream? + + No, not without some preparation. If when compressing you periodically use + Z_FULL_FLUSH, carefully write all the pending data at those points, and + keep an index of those locations, then you can start decompression at those + points. You have to be careful to not use Z_FULL_FLUSH too often, since it + can significantly degrade compression. Alternatively, you can scan a + deflate stream once to generate an index, and then use that index for + random access. See examples/zran.c . + +29. Does zlib work on MVS, OS/390, CICS, etc.? + + It has in the past, but we have not heard of any recent evidence. There + were working ports of zlib 1.1.4 to MVS, but those links no longer work. + If you know of recent, successful applications of zlib on these operating + systems, please let us know. Thanks. + +30. Is there some simpler, easier to read version of inflate I can look at to + understand the deflate format? + + First off, you should read RFC 1951. Second, yes. Look in zlib's + contrib/puff directory. + +31. Does zlib infringe on any patents? + + As far as we know, no. In fact, that was originally the whole point behind + zlib. Look here for some more information: + + http://www.gzip.org/#faq11 + +32. Can zlib work with greater than 4 GB of data? + + Yes. inflate() and deflate() will process any amount of data correctly. + Each call of inflate() or deflate() is limited to input and output chunks + of the maximum value that can be stored in the compiler's "unsigned int" + type, but there is no limit to the number of chunks. Note however that the + strm.total_in and strm_total_out counters may be limited to 4 GB. These + counters are provided as a convenience and are not used internally by + inflate() or deflate(). The application can easily set up its own counters + updated after each call of inflate() or deflate() to count beyond 4 GB. + compress() and uncompress() may be limited to 4 GB, since they operate in a + single call. gzseek() and gztell() may be limited to 4 GB depending on how + zlib is compiled. See the zlibCompileFlags() function in zlib.h. + + The word "may" appears several times above since there is a 4 GB limit only + if the compiler's "long" type is 32 bits. If the compiler's "long" type is + 64 bits, then the limit is 16 exabytes. + +33. Does zlib have any security vulnerabilities? + + The only one that we are aware of is potentially in gzprintf(). If zlib is + compiled to use sprintf() or vsprintf(), then there is no protection + against a buffer overflow of an 8K string space (or other value as set by + gzbuffer()), other than the caller of gzprintf() assuring that the output + will not exceed 8K. On the other hand, if zlib is compiled to use + snprintf() or vsnprintf(), which should normally be the case, then there is + no vulnerability. The ./configure script will display warnings if an + insecure variation of sprintf() will be used by gzprintf(). Also the + zlibCompileFlags() function will return information on what variant of + sprintf() is used by gzprintf(). + + If you don't have snprintf() or vsnprintf() and would like one, you can + find a portable implementation here: + + http://www.ijs.si/software/snprintf/ + + Note that you should be using the most recent version of zlib. Versions + 1.1.3 and before were subject to a double-free vulnerability, and versions + 1.2.1 and 1.2.2 were subject to an access exception when decompressing + invalid compressed data. + +34. Is there a Java version of zlib? + + Probably what you want is to use zlib in Java. zlib is already included + as part of the Java SDK in the java.util.zip package. If you really want + a version of zlib written in the Java language, look on the zlib home + page for links: http://zlib.net/ . + +35. I get this or that compiler or source-code scanner warning when I crank it + up to maximally-pedantic. Can't you guys write proper code? + + Many years ago, we gave up attempting to avoid warnings on every compiler + in the universe. It just got to be a waste of time, and some compilers + were downright silly as well as contradicted each other. So now, we simply + make sure that the code always works. + +36. Valgrind (or some similar memory access checker) says that deflate is + performing a conditional jump that depends on an uninitialized value. + Isn't that a bug? + + No. That is intentional for performance reasons, and the output of deflate + is not affected. This only started showing up recently since zlib 1.2.x + uses malloc() by default for allocations, whereas earlier versions used + calloc(), which zeros out the allocated memory. Even though the code was + correct, versions 1.2.4 and later was changed to not stimulate these + checkers. + +37. Will zlib read the (insert any ancient or arcane format here) compressed + data format? + + Probably not. Look in the comp.compression FAQ for pointers to various + formats and associated software. + +38. How can I encrypt/decrypt zip files with zlib? + + zlib doesn't support encryption. The original PKZIP encryption is very + weak and can be broken with freely available programs. To get strong + encryption, use GnuPG, http://www.gnupg.org/ , which already includes zlib + compression. For PKZIP compatible "encryption", look at + http://www.info-zip.org/ + +39. What's the difference between the "gzip" and "deflate" HTTP 1.1 encodings? + + "gzip" is the gzip format, and "deflate" is the zlib format. They should + probably have called the second one "zlib" instead to avoid confusion with + the raw deflate compressed data format. While the HTTP 1.1 RFC 2616 + correctly points to the zlib specification in RFC 1950 for the "deflate" + transfer encoding, there have been reports of servers and browsers that + incorrectly produce or expect raw deflate data per the deflate + specification in RFC 1951, most notably Microsoft. So even though the + "deflate" transfer encoding using the zlib format would be the more + efficient approach (and in fact exactly what the zlib format was designed + for), using the "gzip" transfer encoding is probably more reliable due to + an unfortunate choice of name on the part of the HTTP 1.1 authors. + + Bottom line: use the gzip format for HTTP 1.1 encoding. + +40. Does zlib support the new "Deflate64" format introduced by PKWare? + + No. PKWare has apparently decided to keep that format proprietary, since + they have not documented it as they have previous compression formats. In + any case, the compression improvements are so modest compared to other more + modern approaches, that it's not worth the effort to implement. + +41. I'm having a problem with the zip functions in zlib, can you help? + + There are no zip functions in zlib. You are probably using minizip by + Giles Vollant, which is found in the contrib directory of zlib. It is not + part of zlib. In fact none of the stuff in contrib is part of zlib. The + files in there are not supported by the zlib authors. You need to contact + the authors of the respective contribution for help. + +42. The match.asm code in contrib is under the GNU General Public License. + Since it's part of zlib, doesn't that mean that all of zlib falls under the + GNU GPL? + + No. The files in contrib are not part of zlib. They were contributed by + other authors and are provided as a convenience to the user within the zlib + distribution. Each item in contrib has its own license. + +43. Is zlib subject to export controls? What is its ECCN? + + zlib is not subject to export controls, and so is classified as EAR99. + +44. Can you please sign these lengthy legal documents and fax them back to us + so that we can use your software in our product? + + No. Go away. Shoo. ADDED compat/zlib/INDEX Index: compat/zlib/INDEX ================================================================== --- /dev/null +++ compat/zlib/INDEX @@ -0,0 +1,68 @@ +CMakeLists.txt cmake build file +ChangeLog history of changes +FAQ Frequently Asked Questions about zlib +INDEX this file +Makefile dummy Makefile that tells you to ./configure +Makefile.in template for Unix Makefile +README guess what +configure configure script for Unix +make_vms.com makefile for VMS +test/example.c zlib usages examples for build testing +test/minigzip.c minimal gzip-like functionality for build testing +test/infcover.c inf*.c code coverage for build coverage testing +treebuild.xml XML description of source file dependencies +zconf.h.cmakein zconf.h template for cmake +zconf.h.in zconf.h template for configure +zlib.3 Man page for zlib +zlib.3.pdf Man page in PDF format +zlib.map Linux symbol information +zlib.pc.in Template for pkg-config descriptor +zlib.pc.cmakein zlib.pc template for cmake +zlib2ansi perl script to convert source files for C++ compilation + +amiga/ makefiles for Amiga SAS C +as400/ makefiles for AS/400 +doc/ documentation for formats and algorithms +msdos/ makefiles for MSDOS +nintendods/ makefile for Nintendo DS +old/ makefiles for various architectures and zlib documentation + files that have not yet been updated for zlib 1.2.x +qnx/ makefiles for QNX +watcom/ makefiles for OpenWatcom +win32/ makefiles for Windows + + zlib public header files (required for library use): +zconf.h +zlib.h + + private source files used to build the zlib library: +adler32.c +compress.c +crc32.c +crc32.h +deflate.c +deflate.h +gzclose.c +gzguts.h +gzlib.c +gzread.c +gzwrite.c +infback.c +inffast.c +inffast.h +inffixed.h +inflate.c +inflate.h +inftrees.c +inftrees.h +trees.c +trees.h +uncompr.c +zutil.c +zutil.h + + source files for sample programs +See examples/README.examples + + unsupported contributions by third parties +See contrib/README.contrib ADDED compat/zlib/Makefile Index: compat/zlib/Makefile ================================================================== --- /dev/null +++ compat/zlib/Makefile @@ -0,0 +1,5 @@ +all: + -@echo "Please use ./configure first. Thank you." + +distclean: + make -f Makefile.in distclean ADDED compat/zlib/Makefile.in Index: compat/zlib/Makefile.in ================================================================== --- /dev/null +++ compat/zlib/Makefile.in @@ -0,0 +1,410 @@ +# Makefile for zlib +# Copyright (C) 1995-2017 Jean-loup Gailly, Mark Adler +# For conditions of distribution and use, see copyright notice in zlib.h + +# To compile and test, type: +# ./configure; make test +# Normally configure builds both a static and a shared library. +# If you want to build just a static library, use: ./configure --static + +# To use the asm code, type: +# cp contrib/asm?86/match.S ./match.S +# make LOC=-DASMV OBJA=match.o + +# To install /usr/local/lib/libz.* and /usr/local/include/zlib.h, type: +# make install +# To install in $HOME instead of /usr/local, use: +# make install prefix=$HOME + +CC=cc + +CFLAGS=-O +#CFLAGS=-O -DMAX_WBITS=14 -DMAX_MEM_LEVEL=7 +#CFLAGS=-g -DZLIB_DEBUG +#CFLAGS=-O3 -Wall -Wwrite-strings -Wpointer-arith -Wconversion \ +# -Wstrict-prototypes -Wmissing-prototypes + +SFLAGS=-O +LDFLAGS= +TEST_LDFLAGS=-L. libz.a +LDSHARED=$(CC) +CPP=$(CC) -E + +STATICLIB=libz.a +SHAREDLIB=libz.so +SHAREDLIBV=libz.so.1.2.11 +SHAREDLIBM=libz.so.1 +LIBS=$(STATICLIB) $(SHAREDLIBV) + +AR=ar +ARFLAGS=rc +RANLIB=ranlib +LDCONFIG=ldconfig +LDSHAREDLIBC=-lc +TAR=tar +SHELL=/bin/sh +EXE= + +prefix = /usr/local +exec_prefix = ${prefix} +libdir = ${exec_prefix}/lib +sharedlibdir = ${libdir} +includedir = ${prefix}/include +mandir = ${prefix}/share/man +man3dir = ${mandir}/man3 +pkgconfigdir = ${libdir}/pkgconfig +SRCDIR= +ZINC= +ZINCOUT=-I. + +OBJZ = adler32.o crc32.o deflate.o infback.o inffast.o inflate.o inftrees.o trees.o zutil.o +OBJG = compress.o uncompr.o gzclose.o gzlib.o gzread.o gzwrite.o +OBJC = $(OBJZ) $(OBJG) + +PIC_OBJZ = adler32.lo crc32.lo deflate.lo infback.lo inffast.lo inflate.lo inftrees.lo trees.lo zutil.lo +PIC_OBJG = compress.lo uncompr.lo gzclose.lo gzlib.lo gzread.lo gzwrite.lo +PIC_OBJC = $(PIC_OBJZ) $(PIC_OBJG) + +# to use the asm code: make OBJA=match.o, PIC_OBJA=match.lo +OBJA = +PIC_OBJA = + +OBJS = $(OBJC) $(OBJA) + +PIC_OBJS = $(PIC_OBJC) $(PIC_OBJA) + +all: static shared + +static: example$(EXE) minigzip$(EXE) + +shared: examplesh$(EXE) minigzipsh$(EXE) + +all64: example64$(EXE) minigzip64$(EXE) + +check: test + +test: all teststatic testshared + +teststatic: static + @TMPST=tmpst_$$; \ + if echo hello world | ./minigzip | ./minigzip -d && ./example $$TMPST ; then \ + echo ' *** zlib test OK ***'; \ + else \ + echo ' *** zlib test FAILED ***'; false; \ + fi; \ + rm -f $$TMPST + +testshared: shared + @LD_LIBRARY_PATH=`pwd`:$(LD_LIBRARY_PATH) ; export LD_LIBRARY_PATH; \ + LD_LIBRARYN32_PATH=`pwd`:$(LD_LIBRARYN32_PATH) ; export LD_LIBRARYN32_PATH; \ + DYLD_LIBRARY_PATH=`pwd`:$(DYLD_LIBRARY_PATH) ; export DYLD_LIBRARY_PATH; \ + SHLIB_PATH=`pwd`:$(SHLIB_PATH) ; export SHLIB_PATH; \ + TMPSH=tmpsh_$$; \ + if echo hello world | ./minigzipsh | ./minigzipsh -d && ./examplesh $$TMPSH; then \ + echo ' *** zlib shared test OK ***'; \ + else \ + echo ' *** zlib shared test FAILED ***'; false; \ + fi; \ + rm -f $$TMPSH + +test64: all64 + @TMP64=tmp64_$$; \ + if echo hello world | ./minigzip64 | ./minigzip64 -d && ./example64 $$TMP64; then \ + echo ' *** zlib 64-bit test OK ***'; \ + else \ + echo ' *** zlib 64-bit test FAILED ***'; false; \ + fi; \ + rm -f $$TMP64 + +infcover.o: $(SRCDIR)test/infcover.c $(SRCDIR)zlib.h zconf.h + $(CC) $(CFLAGS) $(ZINCOUT) -c -o $@ $(SRCDIR)test/infcover.c + +infcover: infcover.o libz.a + $(CC) $(CFLAGS) -o $@ infcover.o libz.a + +cover: infcover + rm -f *.gcda + ./infcover + gcov inf*.c + +libz.a: $(OBJS) + $(AR) $(ARFLAGS) $@ $(OBJS) + -@ ($(RANLIB) $@ || true) >/dev/null 2>&1 + +match.o: match.S + $(CPP) match.S > _match.s + $(CC) -c _match.s + mv _match.o match.o + rm -f _match.s + +match.lo: match.S + $(CPP) match.S > _match.s + $(CC) -c -fPIC _match.s + mv _match.o match.lo + rm -f _match.s + +example.o: $(SRCDIR)test/example.c $(SRCDIR)zlib.h zconf.h + $(CC) $(CFLAGS) $(ZINCOUT) -c -o $@ $(SRCDIR)test/example.c + +minigzip.o: $(SRCDIR)test/minigzip.c $(SRCDIR)zlib.h zconf.h + $(CC) $(CFLAGS) $(ZINCOUT) -c -o $@ $(SRCDIR)test/minigzip.c + +example64.o: $(SRCDIR)test/example.c $(SRCDIR)zlib.h zconf.h + $(CC) $(CFLAGS) $(ZINCOUT) -D_FILE_OFFSET_BITS=64 -c -o $@ $(SRCDIR)test/example.c + +minigzip64.o: $(SRCDIR)test/minigzip.c $(SRCDIR)zlib.h zconf.h + $(CC) $(CFLAGS) $(ZINCOUT) -D_FILE_OFFSET_BITS=64 -c -o $@ $(SRCDIR)test/minigzip.c + + +adler32.o: $(SRCDIR)adler32.c + $(CC) $(CFLAGS) $(ZINC) -c -o $@ $(SRCDIR)adler32.c + +crc32.o: $(SRCDIR)crc32.c + $(CC) $(CFLAGS) $(ZINC) -c -o $@ $(SRCDIR)crc32.c + +deflate.o: $(SRCDIR)deflate.c + $(CC) $(CFLAGS) $(ZINC) -c -o $@ $(SRCDIR)deflate.c + +infback.o: $(SRCDIR)infback.c + $(CC) $(CFLAGS) $(ZINC) -c -o $@ $(SRCDIR)infback.c + +inffast.o: $(SRCDIR)inffast.c + $(CC) $(CFLAGS) $(ZINC) -c -o $@ $(SRCDIR)inffast.c + +inflate.o: $(SRCDIR)inflate.c + $(CC) $(CFLAGS) $(ZINC) -c -o $@ $(SRCDIR)inflate.c + +inftrees.o: $(SRCDIR)inftrees.c + $(CC) $(CFLAGS) $(ZINC) -c -o $@ $(SRCDIR)inftrees.c + +trees.o: $(SRCDIR)trees.c + $(CC) $(CFLAGS) $(ZINC) -c -o $@ $(SRCDIR)trees.c + +zutil.o: $(SRCDIR)zutil.c + $(CC) $(CFLAGS) $(ZINC) -c -o $@ $(SRCDIR)zutil.c + +compress.o: $(SRCDIR)compress.c + $(CC) $(CFLAGS) $(ZINC) -c -o $@ $(SRCDIR)compress.c + +uncompr.o: $(SRCDIR)uncompr.c + $(CC) $(CFLAGS) $(ZINC) -c -o $@ $(SRCDIR)uncompr.c + +gzclose.o: $(SRCDIR)gzclose.c + $(CC) $(CFLAGS) $(ZINC) -c -o $@ $(SRCDIR)gzclose.c + +gzlib.o: $(SRCDIR)gzlib.c + $(CC) $(CFLAGS) $(ZINC) -c -o $@ $(SRCDIR)gzlib.c + +gzread.o: $(SRCDIR)gzread.c + $(CC) $(CFLAGS) $(ZINC) -c -o $@ $(SRCDIR)gzread.c + +gzwrite.o: $(SRCDIR)gzwrite.c + $(CC) $(CFLAGS) $(ZINC) -c -o $@ $(SRCDIR)gzwrite.c + + +adler32.lo: $(SRCDIR)adler32.c + -@mkdir objs 2>/dev/null || test -d objs + $(CC) $(SFLAGS) $(ZINC) -DPIC -c -o objs/adler32.o $(SRCDIR)adler32.c + -@mv objs/adler32.o $@ + +crc32.lo: $(SRCDIR)crc32.c + -@mkdir objs 2>/dev/null || test -d objs + $(CC) $(SFLAGS) $(ZINC) -DPIC -c -o objs/crc32.o $(SRCDIR)crc32.c + -@mv objs/crc32.o $@ + +deflate.lo: $(SRCDIR)deflate.c + -@mkdir objs 2>/dev/null || test -d objs + $(CC) $(SFLAGS) $(ZINC) -DPIC -c -o objs/deflate.o $(SRCDIR)deflate.c + -@mv objs/deflate.o $@ + +infback.lo: $(SRCDIR)infback.c + -@mkdir objs 2>/dev/null || test -d objs + $(CC) $(SFLAGS) $(ZINC) -DPIC -c -o objs/infback.o $(SRCDIR)infback.c + -@mv objs/infback.o $@ + +inffast.lo: $(SRCDIR)inffast.c + -@mkdir objs 2>/dev/null || test -d objs + $(CC) $(SFLAGS) $(ZINC) -DPIC -c -o objs/inffast.o $(SRCDIR)inffast.c + -@mv objs/inffast.o $@ + +inflate.lo: $(SRCDIR)inflate.c + -@mkdir objs 2>/dev/null || test -d objs + $(CC) $(SFLAGS) $(ZINC) -DPIC -c -o objs/inflate.o $(SRCDIR)inflate.c + -@mv objs/inflate.o $@ + +inftrees.lo: $(SRCDIR)inftrees.c + -@mkdir objs 2>/dev/null || test -d objs + $(CC) $(SFLAGS) $(ZINC) -DPIC -c -o objs/inftrees.o $(SRCDIR)inftrees.c + -@mv objs/inftrees.o $@ + +trees.lo: $(SRCDIR)trees.c + -@mkdir objs 2>/dev/null || test -d objs + $(CC) $(SFLAGS) $(ZINC) -DPIC -c -o objs/trees.o $(SRCDIR)trees.c + -@mv objs/trees.o $@ + +zutil.lo: $(SRCDIR)zutil.c + -@mkdir objs 2>/dev/null || test -d objs + $(CC) $(SFLAGS) $(ZINC) -DPIC -c -o objs/zutil.o $(SRCDIR)zutil.c + -@mv objs/zutil.o $@ + +compress.lo: $(SRCDIR)compress.c + -@mkdir objs 2>/dev/null || test -d objs + $(CC) $(SFLAGS) $(ZINC) -DPIC -c -o objs/compress.o $(SRCDIR)compress.c + -@mv objs/compress.o $@ + +uncompr.lo: $(SRCDIR)uncompr.c + -@mkdir objs 2>/dev/null || test -d objs + $(CC) $(SFLAGS) $(ZINC) -DPIC -c -o objs/uncompr.o $(SRCDIR)uncompr.c + -@mv objs/uncompr.o $@ + +gzclose.lo: $(SRCDIR)gzclose.c + -@mkdir objs 2>/dev/null || test -d objs + $(CC) $(SFLAGS) $(ZINC) -DPIC -c -o objs/gzclose.o $(SRCDIR)gzclose.c + -@mv objs/gzclose.o $@ + +gzlib.lo: $(SRCDIR)gzlib.c + -@mkdir objs 2>/dev/null || test -d objs + $(CC) $(SFLAGS) $(ZINC) -DPIC -c -o objs/gzlib.o $(SRCDIR)gzlib.c + -@mv objs/gzlib.o $@ + +gzread.lo: $(SRCDIR)gzread.c + -@mkdir objs 2>/dev/null || test -d objs + $(CC) $(SFLAGS) $(ZINC) -DPIC -c -o objs/gzread.o $(SRCDIR)gzread.c + -@mv objs/gzread.o $@ + +gzwrite.lo: $(SRCDIR)gzwrite.c + -@mkdir objs 2>/dev/null || test -d objs + $(CC) $(SFLAGS) $(ZINC) -DPIC -c -o objs/gzwrite.o $(SRCDIR)gzwrite.c + -@mv objs/gzwrite.o $@ + + +placebo $(SHAREDLIBV): $(PIC_OBJS) libz.a + $(LDSHARED) $(SFLAGS) -o $@ $(PIC_OBJS) $(LDSHAREDLIBC) $(LDFLAGS) + rm -f $(SHAREDLIB) $(SHAREDLIBM) + ln -s $@ $(SHAREDLIB) + ln -s $@ $(SHAREDLIBM) + -@rmdir objs + +example$(EXE): example.o $(STATICLIB) + $(CC) $(CFLAGS) -o $@ example.o $(TEST_LDFLAGS) + +minigzip$(EXE): minigzip.o $(STATICLIB) + $(CC) $(CFLAGS) -o $@ minigzip.o $(TEST_LDFLAGS) + +examplesh$(EXE): example.o $(SHAREDLIBV) + $(CC) $(CFLAGS) -o $@ example.o -L. $(SHAREDLIBV) + +minigzipsh$(EXE): minigzip.o $(SHAREDLIBV) + $(CC) $(CFLAGS) -o $@ minigzip.o -L. $(SHAREDLIBV) + +example64$(EXE): example64.o $(STATICLIB) + $(CC) $(CFLAGS) -o $@ example64.o $(TEST_LDFLAGS) + +minigzip64$(EXE): minigzip64.o $(STATICLIB) + $(CC) $(CFLAGS) -o $@ minigzip64.o $(TEST_LDFLAGS) + +install-libs: $(LIBS) + -@if [ ! -d $(DESTDIR)$(exec_prefix) ]; then mkdir -p $(DESTDIR)$(exec_prefix); fi + -@if [ ! -d $(DESTDIR)$(libdir) ]; then mkdir -p $(DESTDIR)$(libdir); fi + -@if [ ! -d $(DESTDIR)$(sharedlibdir) ]; then mkdir -p $(DESTDIR)$(sharedlibdir); fi + -@if [ ! -d $(DESTDIR)$(man3dir) ]; then mkdir -p $(DESTDIR)$(man3dir); fi + -@if [ ! -d $(DESTDIR)$(pkgconfigdir) ]; then mkdir -p $(DESTDIR)$(pkgconfigdir); fi + rm -f $(DESTDIR)$(libdir)/$(STATICLIB) + cp $(STATICLIB) $(DESTDIR)$(libdir) + chmod 644 $(DESTDIR)$(libdir)/$(STATICLIB) + -@($(RANLIB) $(DESTDIR)$(libdir)/libz.a || true) >/dev/null 2>&1 + -@if test -n "$(SHAREDLIBV)"; then \ + rm -f $(DESTDIR)$(sharedlibdir)/$(SHAREDLIBV); \ + cp $(SHAREDLIBV) $(DESTDIR)$(sharedlibdir); \ + echo "cp $(SHAREDLIBV) $(DESTDIR)$(sharedlibdir)"; \ + chmod 755 $(DESTDIR)$(sharedlibdir)/$(SHAREDLIBV); \ + echo "chmod 755 $(DESTDIR)$(sharedlibdir)/$(SHAREDLIBV)"; \ + rm -f $(DESTDIR)$(sharedlibdir)/$(SHAREDLIB) $(DESTDIR)$(sharedlibdir)/$(SHAREDLIBM); \ + ln -s $(SHAREDLIBV) $(DESTDIR)$(sharedlibdir)/$(SHAREDLIB); \ + ln -s $(SHAREDLIBV) $(DESTDIR)$(sharedlibdir)/$(SHAREDLIBM); \ + ($(LDCONFIG) || true) >/dev/null 2>&1; \ + fi + rm -f $(DESTDIR)$(man3dir)/zlib.3 + cp $(SRCDIR)zlib.3 $(DESTDIR)$(man3dir) + chmod 644 $(DESTDIR)$(man3dir)/zlib.3 + rm -f $(DESTDIR)$(pkgconfigdir)/zlib.pc + cp zlib.pc $(DESTDIR)$(pkgconfigdir) + chmod 644 $(DESTDIR)$(pkgconfigdir)/zlib.pc +# The ranlib in install is needed on NeXTSTEP which checks file times +# ldconfig is for Linux + +install: install-libs + -@if [ ! -d $(DESTDIR)$(includedir) ]; then mkdir -p $(DESTDIR)$(includedir); fi + rm -f $(DESTDIR)$(includedir)/zlib.h $(DESTDIR)$(includedir)/zconf.h + cp $(SRCDIR)zlib.h zconf.h $(DESTDIR)$(includedir) + chmod 644 $(DESTDIR)$(includedir)/zlib.h $(DESTDIR)$(includedir)/zconf.h + +uninstall: + cd $(DESTDIR)$(includedir) && rm -f zlib.h zconf.h + cd $(DESTDIR)$(libdir) && rm -f libz.a; \ + if test -n "$(SHAREDLIBV)" -a -f $(SHAREDLIBV); then \ + rm -f $(SHAREDLIBV) $(SHAREDLIB) $(SHAREDLIBM); \ + fi + cd $(DESTDIR)$(man3dir) && rm -f zlib.3 + cd $(DESTDIR)$(pkgconfigdir) && rm -f zlib.pc + +docs: zlib.3.pdf + +zlib.3.pdf: $(SRCDIR)zlib.3 + groff -mandoc -f H -T ps $(SRCDIR)zlib.3 | ps2pdf - $@ + +zconf.h.cmakein: $(SRCDIR)zconf.h.in + -@ TEMPFILE=zconfh_$$; \ + echo "/#define ZCONF_H/ a\\\\\n#cmakedefine Z_PREFIX\\\\\n#cmakedefine Z_HAVE_UNISTD_H\n" >> $$TEMPFILE &&\ + sed -f $$TEMPFILE $(SRCDIR)zconf.h.in > $@ &&\ + touch -r $(SRCDIR)zconf.h.in $@ &&\ + rm $$TEMPFILE + +zconf: $(SRCDIR)zconf.h.in + cp -p $(SRCDIR)zconf.h.in zconf.h + +mostlyclean: clean +clean: + rm -f *.o *.lo *~ \ + example$(EXE) minigzip$(EXE) examplesh$(EXE) minigzipsh$(EXE) \ + example64$(EXE) minigzip64$(EXE) \ + infcover \ + libz.* foo.gz so_locations \ + _match.s maketree contrib/infback9/*.o + rm -rf objs + rm -f *.gcda *.gcno *.gcov + rm -f contrib/infback9/*.gcda contrib/infback9/*.gcno contrib/infback9/*.gcov + +maintainer-clean: distclean +distclean: clean zconf zconf.h.cmakein docs + rm -f Makefile zlib.pc configure.log + -@rm -f .DS_Store + @if [ -f Makefile.in ]; then \ + printf 'all:\n\t-@echo "Please use ./configure first. Thank you."\n' > Makefile ; \ + printf '\ndistclean:\n\tmake -f Makefile.in distclean\n' >> Makefile ; \ + touch -r $(SRCDIR)Makefile.in Makefile ; fi + @if [ ! -f zconf.h.in ]; then rm -f zconf.h zconf.h.cmakein ; fi + @if [ ! -f zlib.3 ]; then rm -f zlib.3.pdf ; fi + +tags: + etags $(SRCDIR)*.[ch] + +adler32.o zutil.o: $(SRCDIR)zutil.h $(SRCDIR)zlib.h zconf.h +gzclose.o gzlib.o gzread.o gzwrite.o: $(SRCDIR)zlib.h zconf.h $(SRCDIR)gzguts.h +compress.o example.o minigzip.o uncompr.o: $(SRCDIR)zlib.h zconf.h +crc32.o: $(SRCDIR)zutil.h $(SRCDIR)zlib.h zconf.h $(SRCDIR)crc32.h +deflate.o: $(SRCDIR)deflate.h $(SRCDIR)zutil.h $(SRCDIR)zlib.h zconf.h +infback.o inflate.o: $(SRCDIR)zutil.h $(SRCDIR)zlib.h zconf.h $(SRCDIR)inftrees.h $(SRCDIR)inflate.h $(SRCDIR)inffast.h $(SRCDIR)inffixed.h +inffast.o: $(SRCDIR)zutil.h $(SRCDIR)zlib.h zconf.h $(SRCDIR)inftrees.h $(SRCDIR)inflate.h $(SRCDIR)inffast.h +inftrees.o: $(SRCDIR)zutil.h $(SRCDIR)zlib.h zconf.h $(SRCDIR)inftrees.h +trees.o: $(SRCDIR)deflate.h $(SRCDIR)zutil.h $(SRCDIR)zlib.h zconf.h $(SRCDIR)trees.h + +adler32.lo zutil.lo: $(SRCDIR)zutil.h $(SRCDIR)zlib.h zconf.h +gzclose.lo gzlib.lo gzread.lo gzwrite.lo: $(SRCDIR)zlib.h zconf.h $(SRCDIR)gzguts.h +compress.lo example.lo minigzip.lo uncompr.lo: $(SRCDIR)zlib.h zconf.h +crc32.lo: $(SRCDIR)zutil.h $(SRCDIR)zlib.h zconf.h $(SRCDIR)crc32.h +deflate.lo: $(SRCDIR)deflate.h $(SRCDIR)zutil.h $(SRCDIR)zlib.h zconf.h +infback.lo inflate.lo: $(SRCDIR)zutil.h $(SRCDIR)zlib.h zconf.h $(SRCDIR)inftrees.h $(SRCDIR)inflate.h $(SRCDIR)inffast.h $(SRCDIR)inffixed.h +inffast.lo: $(SRCDIR)zutil.h $(SRCDIR)zlib.h zconf.h $(SRCDIR)inftrees.h $(SRCDIR)inflate.h $(SRCDIR)inffast.h +inftrees.lo: $(SRCDIR)zutil.h $(SRCDIR)zlib.h zconf.h $(SRCDIR)inftrees.h +trees.lo: $(SRCDIR)deflate.h $(SRCDIR)zutil.h $(SRCDIR)zlib.h zconf.h $(SRCDIR)trees.h ADDED compat/zlib/README Index: compat/zlib/README ================================================================== --- /dev/null +++ compat/zlib/README @@ -0,0 +1,115 @@ +ZLIB DATA COMPRESSION LIBRARY + +zlib 1.2.11 is a general purpose data compression library. All the code is +thread safe. The data format used by the zlib library is described by RFCs +(Request for Comments) 1950 to 1952 in the files +http://tools.ietf.org/html/rfc1950 (zlib format), rfc1951 (deflate format) and +rfc1952 (gzip format). + +All functions of the compression library are documented in the file zlib.h +(volunteer to write man pages welcome, contact zlib@gzip.org). A usage example +of the library is given in the file test/example.c which also tests that +the library is working correctly. Another example is given in the file +test/minigzip.c. The compression library itself is composed of all source +files in the root directory. + +To compile all files and run the test program, follow the instructions given at +the top of Makefile.in. In short "./configure; make test", and if that goes +well, "make install" should work for most flavors of Unix. For Windows, use +one of the special makefiles in win32/ or contrib/vstudio/ . For VMS, use +make_vms.com. + +Questions about zlib should be sent to , or to Gilles Vollant + for the Windows DLL version. The zlib home page is +http://zlib.net/ . Before reporting a problem, please check this site to +verify that you have the latest version of zlib; otherwise get the latest +version and check whether the problem still exists or not. + +PLEASE read the zlib FAQ http://zlib.net/zlib_faq.html before asking for help. + +Mark Nelson wrote an article about zlib for the Jan. 1997 +issue of Dr. Dobb's Journal; a copy of the article is available at +http://marknelson.us/1997/01/01/zlib-engine/ . + +The changes made in version 1.2.11 are documented in the file ChangeLog. + +Unsupported third party contributions are provided in directory contrib/ . + +zlib is available in Java using the java.util.zip package, documented at +http://java.sun.com/developer/technicalArticles/Programming/compression/ . + +A Perl interface to zlib written by Paul Marquess is available +at CPAN (Comprehensive Perl Archive Network) sites, including +http://search.cpan.org/~pmqs/IO-Compress-Zlib/ . + +A Python interface to zlib written by A.M. Kuchling is +available in Python 1.5 and later versions, see +http://docs.python.org/library/zlib.html . + +zlib is built into tcl: http://wiki.tcl.tk/4610 . + +An experimental package to read and write files in .zip format, written on top +of zlib by Gilles Vollant , is available in the +contrib/minizip directory of zlib. + + +Notes for some targets: + +- For Windows DLL versions, please see win32/DLL_FAQ.txt + +- For 64-bit Irix, deflate.c must be compiled without any optimization. With + -O, one libpng test fails. The test works in 32 bit mode (with the -n32 + compiler flag). The compiler bug has been reported to SGI. + +- zlib doesn't work with gcc 2.6.3 on a DEC 3000/300LX under OSF/1 2.1 it works + when compiled with cc. + +- On Digital Unix 4.0D (formely OSF/1) on AlphaServer, the cc option -std1 is + necessary to get gzprintf working correctly. This is done by configure. + +- zlib doesn't work on HP-UX 9.05 with some versions of /bin/cc. It works with + other compilers. Use "make test" to check your compiler. + +- gzdopen is not supported on RISCOS or BEOS. + +- For PalmOs, see http://palmzlib.sourceforge.net/ + + +Acknowledgments: + + The deflate format used by zlib was defined by Phil Katz. The deflate and + zlib specifications were written by L. Peter Deutsch. Thanks to all the + people who reported problems and suggested various improvements in zlib; they + are too numerous to cite here. + +Copyright notice: + + (C) 1995-2017 Jean-loup Gailly and Mark Adler + + This software is provided 'as-is', without any express or implied + warranty. In no event will the authors be held liable for any damages + arising from the use of this software. + + Permission is granted to anyone to use this software for any purpose, + including commercial applications, and to alter it and redistribute it + freely, subject to the following restrictions: + + 1. The origin of this software must not be misrepresented; you must not + claim that you wrote the original software. If you use this software + in a product, an acknowledgment in the product documentation would be + appreciated but is not required. + 2. Altered source versions must be plainly marked as such, and must not be + misrepresented as being the original software. + 3. This notice may not be removed or altered from any source distribution. + + Jean-loup Gailly Mark Adler + jloup@gzip.org madler@alumni.caltech.edu + +If you use the zlib library in a product, we would appreciate *not* receiving +lengthy legal documents to sign. The sources are provided for free but without +warranty of any kind. The library has been entirely written by Jean-loup +Gailly and Mark Adler; it does not include third-party code. + +If you redistribute modified sources, we would appreciate that you include in +the file ChangeLog history information documenting your changes. Please read +the FAQ for more information on the distribution of modified source versions. ADDED compat/zlib/adler32.c Index: compat/zlib/adler32.c ================================================================== --- /dev/null +++ compat/zlib/adler32.c @@ -0,0 +1,186 @@ +/* adler32.c -- compute the Adler-32 checksum of a data stream + * Copyright (C) 1995-2011, 2016 Mark Adler + * For conditions of distribution and use, see copyright notice in zlib.h + */ + +/* @(#) $Id$ */ + +#include "zutil.h" + +local uLong adler32_combine_ OF((uLong adler1, uLong adler2, z_off64_t len2)); + +#define BASE 65521U /* largest prime smaller than 65536 */ +#define NMAX 5552 +/* NMAX is the largest n such that 255n(n+1)/2 + (n+1)(BASE-1) <= 2^32-1 */ + +#define DO1(buf,i) {adler += (buf)[i]; sum2 += adler;} +#define DO2(buf,i) DO1(buf,i); DO1(buf,i+1); +#define DO4(buf,i) DO2(buf,i); DO2(buf,i+2); +#define DO8(buf,i) DO4(buf,i); DO4(buf,i+4); +#define DO16(buf) DO8(buf,0); DO8(buf,8); + +/* use NO_DIVIDE if your processor does not do division in hardware -- + try it both ways to see which is faster */ +#ifdef NO_DIVIDE +/* note that this assumes BASE is 65521, where 65536 % 65521 == 15 + (thank you to John Reiser for pointing this out) */ +# define CHOP(a) \ + do { \ + unsigned long tmp = a >> 16; \ + a &= 0xffffUL; \ + a += (tmp << 4) - tmp; \ + } while (0) +# define MOD28(a) \ + do { \ + CHOP(a); \ + if (a >= BASE) a -= BASE; \ + } while (0) +# define MOD(a) \ + do { \ + CHOP(a); \ + MOD28(a); \ + } while (0) +# define MOD63(a) \ + do { /* this assumes a is not negative */ \ + z_off64_t tmp = a >> 32; \ + a &= 0xffffffffL; \ + a += (tmp << 8) - (tmp << 5) + tmp; \ + tmp = a >> 16; \ + a &= 0xffffL; \ + a += (tmp << 4) - tmp; \ + tmp = a >> 16; \ + a &= 0xffffL; \ + a += (tmp << 4) - tmp; \ + if (a >= BASE) a -= BASE; \ + } while (0) +#else +# define MOD(a) a %= BASE +# define MOD28(a) a %= BASE +# define MOD63(a) a %= BASE +#endif + +/* ========================================================================= */ +uLong ZEXPORT adler32_z(adler, buf, len) + uLong adler; + const Bytef *buf; + z_size_t len; +{ + unsigned long sum2; + unsigned n; + + /* split Adler-32 into component sums */ + sum2 = (adler >> 16) & 0xffff; + adler &= 0xffff; + + /* in case user likes doing a byte at a time, keep it fast */ + if (len == 1) { + adler += buf[0]; + if (adler >= BASE) + adler -= BASE; + sum2 += adler; + if (sum2 >= BASE) + sum2 -= BASE; + return adler | (sum2 << 16); + } + + /* initial Adler-32 value (deferred check for len == 1 speed) */ + if (buf == Z_NULL) + return 1L; + + /* in case short lengths are provided, keep it somewhat fast */ + if (len < 16) { + while (len--) { + adler += *buf++; + sum2 += adler; + } + if (adler >= BASE) + adler -= BASE; + MOD28(sum2); /* only added so many BASE's */ + return adler | (sum2 << 16); + } + + /* do length NMAX blocks -- requires just one modulo operation */ + while (len >= NMAX) { + len -= NMAX; + n = NMAX / 16; /* NMAX is divisible by 16 */ + do { + DO16(buf); /* 16 sums unrolled */ + buf += 16; + } while (--n); + MOD(adler); + MOD(sum2); + } + + /* do remaining bytes (less than NMAX, still just one modulo) */ + if (len) { /* avoid modulos if none remaining */ + while (len >= 16) { + len -= 16; + DO16(buf); + buf += 16; + } + while (len--) { + adler += *buf++; + sum2 += adler; + } + MOD(adler); + MOD(sum2); + } + + /* return recombined sums */ + return adler | (sum2 << 16); +} + +/* ========================================================================= */ +uLong ZEXPORT adler32(adler, buf, len) + uLong adler; + const Bytef *buf; + uInt len; +{ + return adler32_z(adler, buf, len); +} + +/* ========================================================================= */ +local uLong adler32_combine_(adler1, adler2, len2) + uLong adler1; + uLong adler2; + z_off64_t len2; +{ + unsigned long sum1; + unsigned long sum2; + unsigned rem; + + /* for negative len, return invalid adler32 as a clue for debugging */ + if (len2 < 0) + return 0xffffffffUL; + + /* the derivation of this formula is left as an exercise for the reader */ + MOD63(len2); /* assumes len2 >= 0 */ + rem = (unsigned)len2; + sum1 = adler1 & 0xffff; + sum2 = rem * sum1; + MOD(sum2); + sum1 += (adler2 & 0xffff) + BASE - 1; + sum2 += ((adler1 >> 16) & 0xffff) + ((adler2 >> 16) & 0xffff) + BASE - rem; + if (sum1 >= BASE) sum1 -= BASE; + if (sum1 >= BASE) sum1 -= BASE; + if (sum2 >= ((unsigned long)BASE << 1)) sum2 -= ((unsigned long)BASE << 1); + if (sum2 >= BASE) sum2 -= BASE; + return sum1 | (sum2 << 16); +} + +/* ========================================================================= */ +uLong ZEXPORT adler32_combine(adler1, adler2, len2) + uLong adler1; + uLong adler2; + z_off_t len2; +{ + return adler32_combine_(adler1, adler2, len2); +} + +uLong ZEXPORT adler32_combine64(adler1, adler2, len2) + uLong adler1; + uLong adler2; + z_off64_t len2; +{ + return adler32_combine_(adler1, adler2, len2); +} ADDED compat/zlib/amiga/Makefile.pup Index: compat/zlib/amiga/Makefile.pup ================================================================== --- /dev/null +++ compat/zlib/amiga/Makefile.pup @@ -0,0 +1,69 @@ +# Amiga powerUP (TM) Makefile +# makefile for libpng and SAS C V6.58/7.00 PPC compiler +# Copyright (C) 1998 by Andreas R. Kleinert + +LIBNAME = libzip.a + +CC = scppc +CFLAGS = NOSTKCHK NOSINT OPTIMIZE OPTGO OPTPEEP OPTINLOCAL OPTINL \ + OPTLOOP OPTRDEP=8 OPTDEP=8 OPTCOMP=8 NOVER +AR = ppc-amigaos-ar cr +RANLIB = ppc-amigaos-ranlib +LD = ppc-amigaos-ld -r +LDFLAGS = -o +LDLIBS = LIB:scppc.a LIB:end.o +RM = delete quiet + +OBJS = adler32.o compress.o crc32.o gzclose.o gzlib.o gzread.o gzwrite.o \ + uncompr.o deflate.o trees.o zutil.o inflate.o infback.o inftrees.o inffast.o + +TEST_OBJS = example.o minigzip.o + +all: example minigzip + +check: test +test: all + example + echo hello world | minigzip | minigzip -d + +$(LIBNAME): $(OBJS) + $(AR) $@ $(OBJS) + -$(RANLIB) $@ + +example: example.o $(LIBNAME) + $(LD) $(LDFLAGS) $@ LIB:c_ppc.o $@.o $(LIBNAME) $(LDLIBS) + +minigzip: minigzip.o $(LIBNAME) + $(LD) $(LDFLAGS) $@ LIB:c_ppc.o $@.o $(LIBNAME) $(LDLIBS) + +mostlyclean: clean +clean: + $(RM) *.o example minigzip $(LIBNAME) foo.gz + +zip: + zip -ul9 zlib README ChangeLog Makefile Make????.??? Makefile.?? \ + descrip.mms *.[ch] + +tgz: + cd ..; tar cfz zlib/zlib.tgz zlib/README zlib/ChangeLog zlib/Makefile \ + zlib/Make????.??? zlib/Makefile.?? zlib/descrip.mms zlib/*.[ch] + +# DO NOT DELETE THIS LINE -- make depend depends on it. + +adler32.o: zlib.h zconf.h +compress.o: zlib.h zconf.h +crc32.o: crc32.h zlib.h zconf.h +deflate.o: deflate.h zutil.h zlib.h zconf.h +example.o: zlib.h zconf.h +gzclose.o: zlib.h zconf.h gzguts.h +gzlib.o: zlib.h zconf.h gzguts.h +gzread.o: zlib.h zconf.h gzguts.h +gzwrite.o: zlib.h zconf.h gzguts.h +inffast.o: zutil.h zlib.h zconf.h inftrees.h inflate.h inffast.h +inflate.o: zutil.h zlib.h zconf.h inftrees.h inflate.h inffast.h +infback.o: zutil.h zlib.h zconf.h inftrees.h inflate.h inffast.h +inftrees.o: zutil.h zlib.h zconf.h inftrees.h +minigzip.o: zlib.h zconf.h +trees.o: deflate.h zutil.h zlib.h zconf.h trees.h +uncompr.o: zlib.h zconf.h +zutil.o: zutil.h zlib.h zconf.h ADDED compat/zlib/amiga/Makefile.sas Index: compat/zlib/amiga/Makefile.sas ================================================================== --- /dev/null +++ compat/zlib/amiga/Makefile.sas @@ -0,0 +1,68 @@ +# SMakefile for zlib +# Modified from the standard UNIX Makefile Copyright Jean-loup Gailly +# Osma Ahvenlampi +# Amiga, SAS/C 6.56 & Smake + +CC=sc +CFLAGS=OPT +#CFLAGS=OPT CPU=68030 +#CFLAGS=DEBUG=LINE +LDFLAGS=LIB z.lib + +SCOPTIONS=OPTSCHED OPTINLINE OPTALIAS OPTTIME OPTINLOCAL STRMERGE \ + NOICONS PARMS=BOTH NOSTACKCHECK UTILLIB NOVERSION ERRORREXX \ + DEF=POSTINC + +OBJS = adler32.o compress.o crc32.o gzclose.o gzlib.o gzread.o gzwrite.o \ + uncompr.o deflate.o trees.o zutil.o inflate.o infback.o inftrees.o inffast.o + +TEST_OBJS = example.o minigzip.o + +all: SCOPTIONS example minigzip + +check: test +test: all + example + echo hello world | minigzip | minigzip -d + +install: z.lib + copy clone zlib.h zconf.h INCLUDE: + copy clone z.lib LIB: + +z.lib: $(OBJS) + oml z.lib r $(OBJS) + +example: example.o z.lib + $(CC) $(CFLAGS) LINK TO $@ example.o $(LDFLAGS) + +minigzip: minigzip.o z.lib + $(CC) $(CFLAGS) LINK TO $@ minigzip.o $(LDFLAGS) + +mostlyclean: clean +clean: + -delete force quiet example minigzip *.o z.lib foo.gz *.lnk SCOPTIONS + +SCOPTIONS: Makefile.sas + copy to $@ (uLong)max ? max : (uInt)left; + left -= stream.avail_out; + } + if (stream.avail_in == 0) { + stream.avail_in = sourceLen > (uLong)max ? max : (uInt)sourceLen; + sourceLen -= stream.avail_in; + } + err = deflate(&stream, sourceLen ? Z_NO_FLUSH : Z_FINISH); + } while (err == Z_OK); + + *destLen = stream.total_out; + deflateEnd(&stream); + return err == Z_STREAM_END ? Z_OK : err; +} + +/* =========================================================================== + */ +int ZEXPORT compress (dest, destLen, source, sourceLen) + Bytef *dest; + uLongf *destLen; + const Bytef *source; + uLong sourceLen; +{ + return compress2(dest, destLen, source, sourceLen, Z_DEFAULT_COMPRESSION); +} + +/* =========================================================================== + If the default memLevel or windowBits for deflateInit() is changed, then + this function needs to be updated. + */ +uLong ZEXPORT compressBound (sourceLen) + uLong sourceLen; +{ + return sourceLen + (sourceLen >> 12) + (sourceLen >> 14) + + (sourceLen >> 25) + 13; +} ADDED compat/zlib/configure Index: compat/zlib/configure ================================================================== --- /dev/null +++ compat/zlib/configure @@ -0,0 +1,921 @@ +#!/bin/sh +# configure script for zlib. +# +# Normally configure builds both a static and a shared library. +# If you want to build just a static library, use: ./configure --static +# +# To impose specific compiler or flags or install directory, use for example: +# prefix=$HOME CC=cc CFLAGS="-O4" ./configure +# or for csh/tcsh users: +# (setenv prefix $HOME; setenv CC cc; setenv CFLAGS "-O4"; ./configure) + +# Incorrect settings of CC or CFLAGS may prevent creating a shared library. +# If you have problems, try without defining CC and CFLAGS before reporting +# an error. + +# start off configure.log +echo -------------------- >> configure.log +echo $0 $* >> configure.log +date >> configure.log + +# get source directory +SRCDIR=`dirname $0` +if test $SRCDIR = "."; then + ZINC="" + ZINCOUT="-I." + SRCDIR="" +else + ZINC='-include zconf.h' + ZINCOUT='-I. -I$(SRCDIR)' + SRCDIR="$SRCDIR/" +fi + +# set command prefix for cross-compilation +if [ -n "${CHOST}" ]; then + uname="`echo "${CHOST}" | sed -e 's/^[^-]*-\([^-]*\)$/\1/' -e 's/^[^-]*-[^-]*-\([^-]*\)$/\1/' -e 's/^[^-]*-[^-]*-\([^-]*\)-.*$/\1/'`" + CROSS_PREFIX="${CHOST}-" +fi + +# destination name for static library +STATICLIB=libz.a + +# extract zlib version numbers from zlib.h +VER=`sed -n -e '/VERSION "/s/.*"\(.*\)".*/\1/p' < ${SRCDIR}zlib.h` +VER3=`sed -n -e '/VERSION "/s/.*"\([0-9]*\\.[0-9]*\\.[0-9]*\).*/\1/p' < ${SRCDIR}zlib.h` +VER2=`sed -n -e '/VERSION "/s/.*"\([0-9]*\\.[0-9]*\)\\..*/\1/p' < ${SRCDIR}zlib.h` +VER1=`sed -n -e '/VERSION "/s/.*"\([0-9]*\)\\..*/\1/p' < ${SRCDIR}zlib.h` + +# establish commands for library building +if "${CROSS_PREFIX}ar" --version >/dev/null 2>/dev/null || test $? -lt 126; then + AR=${AR-"${CROSS_PREFIX}ar"} + test -n "${CROSS_PREFIX}" && echo Using ${AR} | tee -a configure.log +else + AR=${AR-"ar"} + test -n "${CROSS_PREFIX}" && echo Using ${AR} | tee -a configure.log +fi +ARFLAGS=${ARFLAGS-"rc"} +if "${CROSS_PREFIX}ranlib" --version >/dev/null 2>/dev/null || test $? -lt 126; then + RANLIB=${RANLIB-"${CROSS_PREFIX}ranlib"} + test -n "${CROSS_PREFIX}" && echo Using ${RANLIB} | tee -a configure.log +else + RANLIB=${RANLIB-"ranlib"} +fi +if "${CROSS_PREFIX}nm" --version >/dev/null 2>/dev/null || test $? -lt 126; then + NM=${NM-"${CROSS_PREFIX}nm"} + test -n "${CROSS_PREFIX}" && echo Using ${NM} | tee -a configure.log +else + NM=${NM-"nm"} +fi + +# set defaults before processing command line options +LDCONFIG=${LDCONFIG-"ldconfig"} +LDSHAREDLIBC="${LDSHAREDLIBC--lc}" +ARCHS= +prefix=${prefix-/usr/local} +exec_prefix=${exec_prefix-'${prefix}'} +libdir=${libdir-'${exec_prefix}/lib'} +sharedlibdir=${sharedlibdir-'${libdir}'} +includedir=${includedir-'${prefix}/include'} +mandir=${mandir-'${prefix}/share/man'} +shared_ext='.so' +shared=1 +solo=0 +cover=0 +zprefix=0 +zconst=0 +build64=0 +gcc=0 +warn=0 +debug=0 +old_cc="$CC" +old_cflags="$CFLAGS" +OBJC='$(OBJZ) $(OBJG)' +PIC_OBJC='$(PIC_OBJZ) $(PIC_OBJG)' + +# leave this script, optionally in a bad way +leave() +{ + if test "$*" != "0"; then + echo "** $0 aborting." | tee -a configure.log + fi + rm -f $test.[co] $test $test$shared_ext $test.gcno ./--version + echo -------------------- >> configure.log + echo >> configure.log + echo >> configure.log + exit $1 +} + +# process command line options +while test $# -ge 1 +do +case "$1" in + -h* | --help) + echo 'usage:' | tee -a configure.log + echo ' configure [--const] [--zprefix] [--prefix=PREFIX] [--eprefix=EXPREFIX]' | tee -a configure.log + echo ' [--static] [--64] [--libdir=LIBDIR] [--sharedlibdir=LIBDIR]' | tee -a configure.log + echo ' [--includedir=INCLUDEDIR] [--archs="-arch i386 -arch x86_64"]' | tee -a configure.log + exit 0 ;; + -p*=* | --prefix=*) prefix=`echo $1 | sed 's/.*=//'`; shift ;; + -e*=* | --eprefix=*) exec_prefix=`echo $1 | sed 's/.*=//'`; shift ;; + -l*=* | --libdir=*) libdir=`echo $1 | sed 's/.*=//'`; shift ;; + --sharedlibdir=*) sharedlibdir=`echo $1 | sed 's/.*=//'`; shift ;; + -i*=* | --includedir=*) includedir=`echo $1 | sed 's/.*=//'`;shift ;; + -u*=* | --uname=*) uname=`echo $1 | sed 's/.*=//'`;shift ;; + -p* | --prefix) prefix="$2"; shift; shift ;; + -e* | --eprefix) exec_prefix="$2"; shift; shift ;; + -l* | --libdir) libdir="$2"; shift; shift ;; + -i* | --includedir) includedir="$2"; shift; shift ;; + -s* | --shared | --enable-shared) shared=1; shift ;; + -t | --static) shared=0; shift ;; + --solo) solo=1; shift ;; + --cover) cover=1; shift ;; + -z* | --zprefix) zprefix=1; shift ;; + -6* | --64) build64=1; shift ;; + -a*=* | --archs=*) ARCHS=`echo $1 | sed 's/.*=//'`; shift ;; + --sysconfdir=*) echo "ignored option: --sysconfdir" | tee -a configure.log; shift ;; + --localstatedir=*) echo "ignored option: --localstatedir" | tee -a configure.log; shift ;; + -c* | --const) zconst=1; shift ;; + -w* | --warn) warn=1; shift ;; + -d* | --debug) debug=1; shift ;; + *) + echo "unknown option: $1" | tee -a configure.log + echo "$0 --help for help" | tee -a configure.log + leave 1;; + esac +done + +# temporary file name +test=ztest$$ + +# put arguments in log, also put test file in log if used in arguments +show() +{ + case "$*" in + *$test.c*) + echo === $test.c === >> configure.log + cat $test.c >> configure.log + echo === >> configure.log;; + esac + echo $* >> configure.log +} + +# check for gcc vs. cc and set compile and link flags based on the system identified by uname +cat > $test.c <&1` in + *gcc*) gcc=1 ;; + *clang*) gcc=1 ;; +esac + +show $cc -c $test.c +if test "$gcc" -eq 1 && ($cc -c $test.c) >> configure.log 2>&1; then + echo ... using gcc >> configure.log + CC="$cc" + CFLAGS="${CFLAGS--O3}" + SFLAGS="${CFLAGS--O3} -fPIC" + if test "$ARCHS"; then + CFLAGS="${CFLAGS} ${ARCHS}" + LDFLAGS="${LDFLAGS} ${ARCHS}" + fi + if test $build64 -eq 1; then + CFLAGS="${CFLAGS} -m64" + SFLAGS="${SFLAGS} -m64" + fi + if test "$warn" -eq 1; then + if test "$zconst" -eq 1; then + CFLAGS="${CFLAGS} -Wall -Wextra -Wcast-qual -pedantic -DZLIB_CONST" + else + CFLAGS="${CFLAGS} -Wall -Wextra -pedantic" + fi + fi + if test $debug -eq 1; then + CFLAGS="${CFLAGS} -DZLIB_DEBUG" + SFLAGS="${SFLAGS} -DZLIB_DEBUG" + fi + if test -z "$uname"; then + uname=`(uname -s || echo unknown) 2>/dev/null` + fi + case "$uname" in + Linux* | linux* | GNU | GNU/* | solaris*) + LDSHARED=${LDSHARED-"$cc -shared -Wl,-soname,libz.so.1,--version-script,${SRCDIR}zlib.map"} ;; + *BSD | *bsd* | DragonFly) + LDSHARED=${LDSHARED-"$cc -shared -Wl,-soname,libz.so.1,--version-script,${SRCDIR}zlib.map"} + LDCONFIG="ldconfig -m" ;; + CYGWIN* | Cygwin* | cygwin* | OS/2*) + EXE='.exe' ;; + MINGW* | mingw*) +# temporary bypass + rm -f $test.[co] $test $test$shared_ext + echo "Please use win32/Makefile.gcc instead." | tee -a configure.log + leave 1 + LDSHARED=${LDSHARED-"$cc -shared"} + LDSHAREDLIBC="" + EXE='.exe' ;; + QNX*) # This is for QNX6. I suppose that the QNX rule below is for QNX2,QNX4 + # (alain.bonnefoy@icbt.com) + LDSHARED=${LDSHARED-"$cc -shared -Wl,-hlibz.so.1"} ;; + HP-UX*) + LDSHARED=${LDSHARED-"$cc -shared $SFLAGS"} + case `(uname -m || echo unknown) 2>/dev/null` in + ia64) + shared_ext='.so' + SHAREDLIB='libz.so' ;; + *) + shared_ext='.sl' + SHAREDLIB='libz.sl' ;; + esac ;; + Darwin* | darwin*) + shared_ext='.dylib' + SHAREDLIB=libz$shared_ext + SHAREDLIBV=libz.$VER$shared_ext + SHAREDLIBM=libz.$VER1$shared_ext + LDSHARED=${LDSHARED-"$cc -dynamiclib -install_name $libdir/$SHAREDLIBM -compatibility_version $VER1 -current_version $VER3"} + if libtool -V 2>&1 | grep Apple > /dev/null; then + AR="libtool" + else + AR="/usr/bin/libtool" + fi + ARFLAGS="-o" ;; + *) LDSHARED=${LDSHARED-"$cc -shared"} ;; + esac +else + # find system name and corresponding cc options + CC=${CC-cc} + gcc=0 + echo ... using $CC >> configure.log + if test -z "$uname"; then + uname=`(uname -sr || echo unknown) 2>/dev/null` + fi + case "$uname" in + HP-UX*) SFLAGS=${CFLAGS-"-O +z"} + CFLAGS=${CFLAGS-"-O"} +# LDSHARED=${LDSHARED-"ld -b +vnocompatwarnings"} + LDSHARED=${LDSHARED-"ld -b"} + case `(uname -m || echo unknown) 2>/dev/null` in + ia64) + shared_ext='.so' + SHAREDLIB='libz.so' ;; + *) + shared_ext='.sl' + SHAREDLIB='libz.sl' ;; + esac ;; + IRIX*) SFLAGS=${CFLAGS-"-ansi -O2 -rpath ."} + CFLAGS=${CFLAGS-"-ansi -O2"} + LDSHARED=${LDSHARED-"cc -shared -Wl,-soname,libz.so.1"} ;; + OSF1\ V4*) SFLAGS=${CFLAGS-"-O -std1"} + CFLAGS=${CFLAGS-"-O -std1"} + LDFLAGS="${LDFLAGS} -Wl,-rpath,." + LDSHARED=${LDSHARED-"cc -shared -Wl,-soname,libz.so -Wl,-msym -Wl,-rpath,$(libdir) -Wl,-set_version,${VER}:1.0"} ;; + OSF1*) SFLAGS=${CFLAGS-"-O -std1"} + CFLAGS=${CFLAGS-"-O -std1"} + LDSHARED=${LDSHARED-"cc -shared -Wl,-soname,libz.so.1"} ;; + QNX*) SFLAGS=${CFLAGS-"-4 -O"} + CFLAGS=${CFLAGS-"-4 -O"} + LDSHARED=${LDSHARED-"cc"} + RANLIB=${RANLIB-"true"} + AR="cc" + ARFLAGS="-A" ;; + SCO_SV\ 3.2*) SFLAGS=${CFLAGS-"-O3 -dy -KPIC "} + CFLAGS=${CFLAGS-"-O3"} + LDSHARED=${LDSHARED-"cc -dy -KPIC -G"} ;; + SunOS\ 5* | solaris*) + LDSHARED=${LDSHARED-"cc -G -h libz$shared_ext.$VER1"} + SFLAGS=${CFLAGS-"-fast -KPIC"} + CFLAGS=${CFLAGS-"-fast"} + if test $build64 -eq 1; then + # old versions of SunPRO/Workshop/Studio don't support -m64, + # but newer ones do. Check for it. + flag64=`$CC -flags | egrep -- '^-m64'` + if test x"$flag64" != x"" ; then + CFLAGS="${CFLAGS} -m64" + SFLAGS="${SFLAGS} -m64" + else + case `(uname -m || echo unknown) 2>/dev/null` in + i86*) + SFLAGS="$SFLAGS -xarch=amd64" + CFLAGS="$CFLAGS -xarch=amd64" ;; + *) + SFLAGS="$SFLAGS -xarch=v9" + CFLAGS="$CFLAGS -xarch=v9" ;; + esac + fi + fi + if test -n "$ZINC"; then + ZINC='-I- -I. -I$(SRCDIR)' + fi + ;; + SunOS\ 4*) SFLAGS=${CFLAGS-"-O2 -PIC"} + CFLAGS=${CFLAGS-"-O2"} + LDSHARED=${LDSHARED-"ld"} ;; + SunStudio\ 9*) SFLAGS=${CFLAGS-"-fast -xcode=pic32 -xtarget=ultra3 -xarch=v9b"} + CFLAGS=${CFLAGS-"-fast -xtarget=ultra3 -xarch=v9b"} + LDSHARED=${LDSHARED-"cc -xarch=v9b"} ;; + UNIX_System_V\ 4.2.0) + SFLAGS=${CFLAGS-"-KPIC -O"} + CFLAGS=${CFLAGS-"-O"} + LDSHARED=${LDSHARED-"cc -G"} ;; + UNIX_SV\ 4.2MP) + SFLAGS=${CFLAGS-"-Kconform_pic -O"} + CFLAGS=${CFLAGS-"-O"} + LDSHARED=${LDSHARED-"cc -G"} ;; + OpenUNIX\ 5) + SFLAGS=${CFLAGS-"-KPIC -O"} + CFLAGS=${CFLAGS-"-O"} + LDSHARED=${LDSHARED-"cc -G"} ;; + AIX*) # Courtesy of dbakker@arrayasolutions.com + SFLAGS=${CFLAGS-"-O -qmaxmem=8192"} + CFLAGS=${CFLAGS-"-O -qmaxmem=8192"} + LDSHARED=${LDSHARED-"xlc -G"} ;; + # send working options for other systems to zlib@gzip.org + *) SFLAGS=${CFLAGS-"-O"} + CFLAGS=${CFLAGS-"-O"} + LDSHARED=${LDSHARED-"cc -shared"} ;; + esac +fi + +# destination names for shared library if not defined above +SHAREDLIB=${SHAREDLIB-"libz$shared_ext"} +SHAREDLIBV=${SHAREDLIBV-"libz$shared_ext.$VER"} +SHAREDLIBM=${SHAREDLIBM-"libz$shared_ext.$VER1"} + +echo >> configure.log + +# define functions for testing compiler and library characteristics and logging the results + +cat > $test.c </dev/null; then + try() + { + show $* + test "`( $* ) 2>&1 | tee -a configure.log`" = "" + } + echo - using any output from compiler to indicate an error >> configure.log +else + try() + { + show $* + ( $* ) >> configure.log 2>&1 + ret=$? + if test $ret -ne 0; then + echo "(exit code "$ret")" >> configure.log + fi + return $ret + } +fi + +tryboth() +{ + show $* + got=`( $* ) 2>&1` + ret=$? + printf %s "$got" >> configure.log + if test $ret -ne 0; then + return $ret + fi + test "$got" = "" +} + +cat > $test.c << EOF +int foo() { return 0; } +EOF +echo "Checking for obsessive-compulsive compiler options..." >> configure.log +if try $CC -c $CFLAGS $test.c; then + : +else + echo "Compiler error reporting is too harsh for $0 (perhaps remove -Werror)." | tee -a configure.log + leave 1 +fi + +echo >> configure.log + +# see if shared library build supported +cat > $test.c <> configure.log + show "$NM $test.o | grep _hello" + if test "`$NM $test.o | grep _hello | tee -a configure.log`" = ""; then + CPP="$CPP -DNO_UNDERLINE" + echo Checking for underline in external names... No. | tee -a configure.log + else + echo Checking for underline in external names... Yes. | tee -a configure.log + fi ;; +esac + +echo >> configure.log + +# check for size_t +cat > $test.c < +#include +size_t dummy = 0; +EOF +if try $CC -c $CFLAGS $test.c; then + echo "Checking for size_t... Yes." | tee -a configure.log + need_sizet=0 +else + echo "Checking for size_t... No." | tee -a configure.log + need_sizet=1 +fi + +echo >> configure.log + +# find the size_t integer type, if needed +if test $need_sizet -eq 1; then + cat > $test.c < $test.c < +int main(void) { + if (sizeof(void *) <= sizeof(int)) puts("int"); + else if (sizeof(void *) <= sizeof(long)) puts("long"); + else puts("z_longlong"); + return 0; +} +EOF + else + echo "Checking for long long... No." | tee -a configure.log + cat > $test.c < +int main(void) { + if (sizeof(void *) <= sizeof(int)) puts("int"); + else puts("long"); + return 0; +} +EOF + fi + if try $CC $CFLAGS -o $test $test.c; then + sizet=`./$test` + echo "Checking for a pointer-size integer type..." $sizet"." | tee -a configure.log + else + echo "Failed to find a pointer-size integer type." | tee -a configure.log + leave 1 + fi +fi + +if test $need_sizet -eq 1; then + CFLAGS="${CFLAGS} -DNO_SIZE_T=${sizet}" + SFLAGS="${SFLAGS} -DNO_SIZE_T=${sizet}" +fi + +echo >> configure.log + +# check for large file support, and if none, check for fseeko() +cat > $test.c < +off64_t dummy = 0; +EOF +if try $CC -c $CFLAGS -D_LARGEFILE64_SOURCE=1 $test.c; then + CFLAGS="${CFLAGS} -D_LARGEFILE64_SOURCE=1" + SFLAGS="${SFLAGS} -D_LARGEFILE64_SOURCE=1" + ALL="${ALL} all64" + TEST="${TEST} test64" + echo "Checking for off64_t... Yes." | tee -a configure.log + echo "Checking for fseeko... Yes." | tee -a configure.log +else + echo "Checking for off64_t... No." | tee -a configure.log + echo >> configure.log + cat > $test.c < +int main(void) { + fseeko(NULL, 0, 0); + return 0; +} +EOF + if try $CC $CFLAGS -o $test $test.c; then + echo "Checking for fseeko... Yes." | tee -a configure.log + else + CFLAGS="${CFLAGS} -DNO_FSEEKO" + SFLAGS="${SFLAGS} -DNO_FSEEKO" + echo "Checking for fseeko... No." | tee -a configure.log + fi +fi + +echo >> configure.log + +# check for strerror() for use by gz* functions +cat > $test.c < +#include +int main() { return strlen(strerror(errno)); } +EOF +if try $CC $CFLAGS -o $test $test.c; then + echo "Checking for strerror... Yes." | tee -a configure.log +else + CFLAGS="${CFLAGS} -DNO_STRERROR" + SFLAGS="${SFLAGS} -DNO_STRERROR" + echo "Checking for strerror... No." | tee -a configure.log +fi + +# copy clean zconf.h for subsequent edits +cp -p ${SRCDIR}zconf.h.in zconf.h + +echo >> configure.log + +# check for unistd.h and save result in zconf.h +cat > $test.c < +int main() { return 0; } +EOF +if try $CC -c $CFLAGS $test.c; then + sed < zconf.h "/^#ifdef HAVE_UNISTD_H.* may be/s/def HAVE_UNISTD_H\(.*\) may be/ 1\1 was/" > zconf.temp.h + mv zconf.temp.h zconf.h + echo "Checking for unistd.h... Yes." | tee -a configure.log +else + echo "Checking for unistd.h... No." | tee -a configure.log +fi + +echo >> configure.log + +# check for stdarg.h and save result in zconf.h +cat > $test.c < +int main() { return 0; } +EOF +if try $CC -c $CFLAGS $test.c; then + sed < zconf.h "/^#ifdef HAVE_STDARG_H.* may be/s/def HAVE_STDARG_H\(.*\) may be/ 1\1 was/" > zconf.temp.h + mv zconf.temp.h zconf.h + echo "Checking for stdarg.h... Yes." | tee -a configure.log +else + echo "Checking for stdarg.h... No." | tee -a configure.log +fi + +# if the z_ prefix was requested, save that in zconf.h +if test $zprefix -eq 1; then + sed < zconf.h "/#ifdef Z_PREFIX.* may be/s/def Z_PREFIX\(.*\) may be/ 1\1 was/" > zconf.temp.h + mv zconf.temp.h zconf.h + echo >> configure.log + echo "Using z_ prefix on all symbols." | tee -a configure.log +fi + +# if --solo compilation was requested, save that in zconf.h and remove gz stuff from object lists +if test $solo -eq 1; then + sed '/#define ZCONF_H/a\ +#define Z_SOLO + +' < zconf.h > zconf.temp.h + mv zconf.temp.h zconf.h +OBJC='$(OBJZ)' +PIC_OBJC='$(PIC_OBJZ)' +fi + +# if code coverage testing was requested, use older gcc if defined, e.g. "gcc-4.2" on Mac OS X +if test $cover -eq 1; then + CFLAGS="${CFLAGS} -fprofile-arcs -ftest-coverage" + if test -n "$GCC_CLASSIC"; then + CC=$GCC_CLASSIC + fi +fi + +echo >> configure.log + +# conduct a series of tests to resolve eight possible cases of using "vs" or "s" printf functions +# (using stdarg or not), with or without "n" (proving size of buffer), and with or without a +# return value. The most secure result is vsnprintf() with a return value. snprintf() with a +# return value is secure as well, but then gzprintf() will be limited to 20 arguments. +cat > $test.c < +#include +#include "zconf.h" +int main() +{ +#ifndef STDC + choke me +#endif + return 0; +} +EOF +if try $CC -c $CFLAGS $test.c; then + echo "Checking whether to use vs[n]printf() or s[n]printf()... using vs[n]printf()." | tee -a configure.log + + echo >> configure.log + cat > $test.c < +#include +int mytest(const char *fmt, ...) +{ + char buf[20]; + va_list ap; + va_start(ap, fmt); + vsnprintf(buf, sizeof(buf), fmt, ap); + va_end(ap); + return 0; +} +int main() +{ + return (mytest("Hello%d\n", 1)); +} +EOF + if try $CC $CFLAGS -o $test $test.c; then + echo "Checking for vsnprintf() in stdio.h... Yes." | tee -a configure.log + + echo >> configure.log + cat >$test.c < +#include +int mytest(const char *fmt, ...) +{ + int n; + char buf[20]; + va_list ap; + va_start(ap, fmt); + n = vsnprintf(buf, sizeof(buf), fmt, ap); + va_end(ap); + return n; +} +int main() +{ + return (mytest("Hello%d\n", 1)); +} +EOF + + if try $CC -c $CFLAGS $test.c; then + echo "Checking for return value of vsnprintf()... Yes." | tee -a configure.log + else + CFLAGS="$CFLAGS -DHAS_vsnprintf_void" + SFLAGS="$SFLAGS -DHAS_vsnprintf_void" + echo "Checking for return value of vsnprintf()... No." | tee -a configure.log + echo " WARNING: apparently vsnprintf() does not return a value. zlib" | tee -a configure.log + echo " can build but will be open to possible string-format security" | tee -a configure.log + echo " vulnerabilities." | tee -a configure.log + fi + else + CFLAGS="$CFLAGS -DNO_vsnprintf" + SFLAGS="$SFLAGS -DNO_vsnprintf" + echo "Checking for vsnprintf() in stdio.h... No." | tee -a configure.log + echo " WARNING: vsnprintf() not found, falling back to vsprintf(). zlib" | tee -a configure.log + echo " can build but will be open to possible buffer-overflow security" | tee -a configure.log + echo " vulnerabilities." | tee -a configure.log + + echo >> configure.log + cat >$test.c < +#include +int mytest(const char *fmt, ...) +{ + int n; + char buf[20]; + va_list ap; + va_start(ap, fmt); + n = vsprintf(buf, fmt, ap); + va_end(ap); + return n; +} +int main() +{ + return (mytest("Hello%d\n", 1)); +} +EOF + + if try $CC -c $CFLAGS $test.c; then + echo "Checking for return value of vsprintf()... Yes." | tee -a configure.log + else + CFLAGS="$CFLAGS -DHAS_vsprintf_void" + SFLAGS="$SFLAGS -DHAS_vsprintf_void" + echo "Checking for return value of vsprintf()... No." | tee -a configure.log + echo " WARNING: apparently vsprintf() does not return a value. zlib" | tee -a configure.log + echo " can build but will be open to possible string-format security" | tee -a configure.log + echo " vulnerabilities." | tee -a configure.log + fi + fi +else + echo "Checking whether to use vs[n]printf() or s[n]printf()... using s[n]printf()." | tee -a configure.log + + echo >> configure.log + cat >$test.c < +int mytest() +{ + char buf[20]; + snprintf(buf, sizeof(buf), "%s", "foo"); + return 0; +} +int main() +{ + return (mytest()); +} +EOF + + if try $CC $CFLAGS -o $test $test.c; then + echo "Checking for snprintf() in stdio.h... Yes." | tee -a configure.log + + echo >> configure.log + cat >$test.c < +int mytest() +{ + char buf[20]; + return snprintf(buf, sizeof(buf), "%s", "foo"); +} +int main() +{ + return (mytest()); +} +EOF + + if try $CC -c $CFLAGS $test.c; then + echo "Checking for return value of snprintf()... Yes." | tee -a configure.log + else + CFLAGS="$CFLAGS -DHAS_snprintf_void" + SFLAGS="$SFLAGS -DHAS_snprintf_void" + echo "Checking for return value of snprintf()... No." | tee -a configure.log + echo " WARNING: apparently snprintf() does not return a value. zlib" | tee -a configure.log + echo " can build but will be open to possible string-format security" | tee -a configure.log + echo " vulnerabilities." | tee -a configure.log + fi + else + CFLAGS="$CFLAGS -DNO_snprintf" + SFLAGS="$SFLAGS -DNO_snprintf" + echo "Checking for snprintf() in stdio.h... No." | tee -a configure.log + echo " WARNING: snprintf() not found, falling back to sprintf(). zlib" | tee -a configure.log + echo " can build but will be open to possible buffer-overflow security" | tee -a configure.log + echo " vulnerabilities." | tee -a configure.log + + echo >> configure.log + cat >$test.c < +int mytest() +{ + char buf[20]; + return sprintf(buf, "%s", "foo"); +} +int main() +{ + return (mytest()); +} +EOF + + if try $CC -c $CFLAGS $test.c; then + echo "Checking for return value of sprintf()... Yes." | tee -a configure.log + else + CFLAGS="$CFLAGS -DHAS_sprintf_void" + SFLAGS="$SFLAGS -DHAS_sprintf_void" + echo "Checking for return value of sprintf()... No." | tee -a configure.log + echo " WARNING: apparently sprintf() does not return a value. zlib" | tee -a configure.log + echo " can build but will be open to possible string-format security" | tee -a configure.log + echo " vulnerabilities." | tee -a configure.log + fi + fi +fi + +# see if we can hide zlib internal symbols that are linked between separate source files +if test "$gcc" -eq 1; then + echo >> configure.log + cat > $test.c <> configure.log +echo ALL = $ALL >> configure.log +echo AR = $AR >> configure.log +echo ARFLAGS = $ARFLAGS >> configure.log +echo CC = $CC >> configure.log +echo CFLAGS = $CFLAGS >> configure.log +echo CPP = $CPP >> configure.log +echo EXE = $EXE >> configure.log +echo LDCONFIG = $LDCONFIG >> configure.log +echo LDFLAGS = $LDFLAGS >> configure.log +echo LDSHARED = $LDSHARED >> configure.log +echo LDSHAREDLIBC = $LDSHAREDLIBC >> configure.log +echo OBJC = $OBJC >> configure.log +echo PIC_OBJC = $PIC_OBJC >> configure.log +echo RANLIB = $RANLIB >> configure.log +echo SFLAGS = $SFLAGS >> configure.log +echo SHAREDLIB = $SHAREDLIB >> configure.log +echo SHAREDLIBM = $SHAREDLIBM >> configure.log +echo SHAREDLIBV = $SHAREDLIBV >> configure.log +echo STATICLIB = $STATICLIB >> configure.log +echo TEST = $TEST >> configure.log +echo VER = $VER >> configure.log +echo Z_U4 = $Z_U4 >> configure.log +echo SRCDIR = $SRCDIR >> configure.log +echo exec_prefix = $exec_prefix >> configure.log +echo includedir = $includedir >> configure.log +echo libdir = $libdir >> configure.log +echo mandir = $mandir >> configure.log +echo prefix = $prefix >> configure.log +echo sharedlibdir = $sharedlibdir >> configure.log +echo uname = $uname >> configure.log + +# udpate Makefile with the configure results +sed < ${SRCDIR}Makefile.in " +/^CC *=/s#=.*#=$CC# +/^CFLAGS *=/s#=.*#=$CFLAGS# +/^SFLAGS *=/s#=.*#=$SFLAGS# +/^LDFLAGS *=/s#=.*#=$LDFLAGS# +/^LDSHARED *=/s#=.*#=$LDSHARED# +/^CPP *=/s#=.*#=$CPP# +/^STATICLIB *=/s#=.*#=$STATICLIB# +/^SHAREDLIB *=/s#=.*#=$SHAREDLIB# +/^SHAREDLIBV *=/s#=.*#=$SHAREDLIBV# +/^SHAREDLIBM *=/s#=.*#=$SHAREDLIBM# +/^AR *=/s#=.*#=$AR# +/^ARFLAGS *=/s#=.*#=$ARFLAGS# +/^RANLIB *=/s#=.*#=$RANLIB# +/^LDCONFIG *=/s#=.*#=$LDCONFIG# +/^LDSHAREDLIBC *=/s#=.*#=$LDSHAREDLIBC# +/^EXE *=/s#=.*#=$EXE# +/^SRCDIR *=/s#=.*#=$SRCDIR# +/^ZINC *=/s#=.*#=$ZINC# +/^ZINCOUT *=/s#=.*#=$ZINCOUT# +/^prefix *=/s#=.*#=$prefix# +/^exec_prefix *=/s#=.*#=$exec_prefix# +/^libdir *=/s#=.*#=$libdir# +/^sharedlibdir *=/s#=.*#=$sharedlibdir# +/^includedir *=/s#=.*#=$includedir# +/^mandir *=/s#=.*#=$mandir# +/^OBJC *=/s#=.*#= $OBJC# +/^PIC_OBJC *=/s#=.*#= $PIC_OBJC# +/^all: */s#:.*#: $ALL# +/^test: */s#:.*#: $TEST# +" > Makefile + +# create zlib.pc with the configure results +sed < ${SRCDIR}zlib.pc.in " +/^CC *=/s#=.*#=$CC# +/^CFLAGS *=/s#=.*#=$CFLAGS# +/^CPP *=/s#=.*#=$CPP# +/^LDSHARED *=/s#=.*#=$LDSHARED# +/^STATICLIB *=/s#=.*#=$STATICLIB# +/^SHAREDLIB *=/s#=.*#=$SHAREDLIB# +/^SHAREDLIBV *=/s#=.*#=$SHAREDLIBV# +/^SHAREDLIBM *=/s#=.*#=$SHAREDLIBM# +/^AR *=/s#=.*#=$AR# +/^ARFLAGS *=/s#=.*#=$ARFLAGS# +/^RANLIB *=/s#=.*#=$RANLIB# +/^EXE *=/s#=.*#=$EXE# +/^prefix *=/s#=.*#=$prefix# +/^exec_prefix *=/s#=.*#=$exec_prefix# +/^libdir *=/s#=.*#=$libdir# +/^sharedlibdir *=/s#=.*#=$sharedlibdir# +/^includedir *=/s#=.*#=$includedir# +/^mandir *=/s#=.*#=$mandir# +/^LDFLAGS *=/s#=.*#=$LDFLAGS# +" | sed -e " +s/\@VERSION\@/$VER/g; +" > zlib.pc + +# done +leave 0 ADDED compat/zlib/contrib/README.contrib Index: compat/zlib/contrib/README.contrib ================================================================== --- /dev/null +++ compat/zlib/contrib/README.contrib @@ -0,0 +1,78 @@ +All files under this contrib directory are UNSUPPORTED. There were +provided by users of zlib and were not tested by the authors of zlib. +Use at your own risk. Please contact the authors of the contributions +for help about these, not the zlib authors. Thanks. + + +ada/ by Dmitriy Anisimkov + Support for Ada + See http://zlib-ada.sourceforge.net/ + +amd64/ by Mikhail Teterin + asm code for AMD64 + See patch at http://www.freebsd.org/cgi/query-pr.cgi?pr=bin/96393 + +asm686/ by Brian Raiter + asm code for Pentium and PPro/PII, using the AT&T (GNU as) syntax + See http://www.muppetlabs.com/~breadbox/software/assembly.html + +blast/ by Mark Adler + Decompressor for output of PKWare Data Compression Library (DCL) + +delphi/ by Cosmin Truta + Support for Delphi and C++ Builder + +dotzlib/ by Henrik Ravn + Support for Microsoft .Net and Visual C++ .Net + +gcc_gvmat64/by Gilles Vollant + GCC Version of x86 64-bit (AMD64 and Intel EM64t) code for x64 + assembler to replace longest_match() and inflate_fast() + +infback9/ by Mark Adler + Unsupported diffs to infback to decode the deflate64 format + +inflate86/ by Chris Anderson + Tuned x86 gcc asm code to replace inflate_fast() + +iostream/ by Kevin Ruland + A C++ I/O streams interface to the zlib gz* functions + +iostream2/ by Tyge Løvset + Another C++ I/O streams interface + +iostream3/ by Ludwig Schwardt + and Kevin Ruland + Yet another C++ I/O streams interface + +masmx64/ by Gilles Vollant + x86 64-bit (AMD64 and Intel EM64t) code for x64 assembler to + replace longest_match() and inflate_fast(), also masm x86 + 64-bits translation of Chris Anderson inflate_fast() + +masmx86/ by Gilles Vollant + x86 asm code to replace longest_match() and inflate_fast(), + for Visual C++ and MASM (32 bits). + Based on Brian Raiter (asm686) and Chris Anderson (inflate86) + +minizip/ by Gilles Vollant + Mini zip and unzip based on zlib + Includes Zip64 support by Mathias Svensson + See http://www.winimage.com/zLibDll/minizip.html + +pascal/ by Bob Dellaca et al. + Support for Pascal + +puff/ by Mark Adler + Small, low memory usage inflate. Also serves to provide an + unambiguous description of the deflate format. + +testzlib/ by Gilles Vollant + Example of the use of zlib + +untgz/ by Pedro A. Aranda Gutierrez + A very simple tar.gz file extractor using zlib + +vstudio/ by Gilles Vollant + Building a minizip-enhanced zlib with Microsoft Visual Studio + Includes vc11 from kreuzerkrieg and vc12 from davispuh ADDED compat/zlib/contrib/ada/buffer_demo.adb Index: compat/zlib/contrib/ada/buffer_demo.adb ================================================================== --- /dev/null +++ compat/zlib/contrib/ada/buffer_demo.adb @@ -0,0 +1,106 @@ +---------------------------------------------------------------- +-- ZLib for Ada thick binding. -- +-- -- +-- Copyright (C) 2002-2004 Dmitriy Anisimkov -- +-- -- +-- Open source license information is in the zlib.ads file. -- +---------------------------------------------------------------- +-- +-- $Id: buffer_demo.adb,v 1.3 2004/09/06 06:55:35 vagul Exp $ + +-- This demo program provided by Dr Steve Sangwine +-- +-- Demonstration of a problem with Zlib-Ada (already fixed) when a buffer +-- of exactly the correct size is used for decompressed data, and the last +-- few bytes passed in to Zlib are checksum bytes. + +-- This program compresses a string of text, and then decompresses the +-- compressed text into a buffer of the same size as the original text. + +with Ada.Streams; use Ada.Streams; +with Ada.Text_IO; + +with ZLib; use ZLib; + +procedure Buffer_Demo is + EOL : Character renames ASCII.LF; + Text : constant String + := "Four score and seven years ago our fathers brought forth," & EOL & + "upon this continent, a new nation, conceived in liberty," & EOL & + "and dedicated to the proposition that `all men are created equal'."; + + Source : Stream_Element_Array (1 .. Text'Length); + for Source'Address use Text'Address; + +begin + Ada.Text_IO.Put (Text); + Ada.Text_IO.New_Line; + Ada.Text_IO.Put_Line + ("Uncompressed size : " & Positive'Image (Text'Length) & " bytes"); + + declare + Compressed_Data : Stream_Element_Array (1 .. Text'Length); + L : Stream_Element_Offset; + begin + Compress : declare + Compressor : Filter_Type; + I : Stream_Element_Offset; + begin + Deflate_Init (Compressor); + + -- Compress the whole of T at once. + + Translate (Compressor, Source, I, Compressed_Data, L, Finish); + pragma Assert (I = Source'Last); + + Close (Compressor); + + Ada.Text_IO.Put_Line + ("Compressed size : " + & Stream_Element_Offset'Image (L) & " bytes"); + end Compress; + + -- Now we decompress the data, passing short blocks of data to Zlib + -- (because this demonstrates the problem - the last block passed will + -- contain checksum information and there will be no output, only a + -- check inside Zlib that the checksum is correct). + + Decompress : declare + Decompressor : Filter_Type; + + Uncompressed_Data : Stream_Element_Array (1 .. Text'Length); + + Block_Size : constant := 4; + -- This makes sure that the last block contains + -- only Adler checksum data. + + P : Stream_Element_Offset := Compressed_Data'First - 1; + O : Stream_Element_Offset; + begin + Inflate_Init (Decompressor); + + loop + Translate + (Decompressor, + Compressed_Data + (P + 1 .. Stream_Element_Offset'Min (P + Block_Size, L)), + P, + Uncompressed_Data + (Total_Out (Decompressor) + 1 .. Uncompressed_Data'Last), + O, + No_Flush); + + Ada.Text_IO.Put_Line + ("Total in : " & Count'Image (Total_In (Decompressor)) & + ", out : " & Count'Image (Total_Out (Decompressor))); + + exit when P = L; + end loop; + + Ada.Text_IO.New_Line; + Ada.Text_IO.Put_Line + ("Decompressed text matches original text : " + & Boolean'Image (Uncompressed_Data = Source)); + end Decompress; + end; +end Buffer_Demo; ADDED compat/zlib/contrib/ada/mtest.adb Index: compat/zlib/contrib/ada/mtest.adb ================================================================== --- /dev/null +++ compat/zlib/contrib/ada/mtest.adb @@ -0,0 +1,156 @@ +---------------------------------------------------------------- +-- ZLib for Ada thick binding. -- +-- -- +-- Copyright (C) 2002-2003 Dmitriy Anisimkov -- +-- -- +-- Open source license information is in the zlib.ads file. -- +---------------------------------------------------------------- +-- Continuous test for ZLib multithreading. If the test would fail +-- we should provide thread safe allocation routines for the Z_Stream. +-- +-- $Id: mtest.adb,v 1.4 2004/07/23 07:49:54 vagul Exp $ + +with ZLib; +with Ada.Streams; +with Ada.Numerics.Discrete_Random; +with Ada.Text_IO; +with Ada.Exceptions; +with Ada.Task_Identification; + +procedure MTest is + use Ada.Streams; + use ZLib; + + Stop : Boolean := False; + + pragma Atomic (Stop); + + subtype Visible_Symbols is Stream_Element range 16#20# .. 16#7E#; + + package Random_Elements is + new Ada.Numerics.Discrete_Random (Visible_Symbols); + + task type Test_Task; + + task body Test_Task is + Buffer : Stream_Element_Array (1 .. 100_000); + Gen : Random_Elements.Generator; + + Buffer_First : Stream_Element_Offset; + Compare_First : Stream_Element_Offset; + + Deflate : Filter_Type; + Inflate : Filter_Type; + + procedure Further (Item : in Stream_Element_Array); + + procedure Read_Buffer + (Item : out Ada.Streams.Stream_Element_Array; + Last : out Ada.Streams.Stream_Element_Offset); + + ------------- + -- Further -- + ------------- + + procedure Further (Item : in Stream_Element_Array) is + + procedure Compare (Item : in Stream_Element_Array); + + ------------- + -- Compare -- + ------------- + + procedure Compare (Item : in Stream_Element_Array) is + Next_First : Stream_Element_Offset := Compare_First + Item'Length; + begin + if Buffer (Compare_First .. Next_First - 1) /= Item then + raise Program_Error; + end if; + + Compare_First := Next_First; + end Compare; + + procedure Compare_Write is new ZLib.Write (Write => Compare); + begin + Compare_Write (Inflate, Item, No_Flush); + end Further; + + ----------------- + -- Read_Buffer -- + ----------------- + + procedure Read_Buffer + (Item : out Ada.Streams.Stream_Element_Array; + Last : out Ada.Streams.Stream_Element_Offset) + is + Buff_Diff : Stream_Element_Offset := Buffer'Last - Buffer_First; + Next_First : Stream_Element_Offset; + begin + if Item'Length <= Buff_Diff then + Last := Item'Last; + + Next_First := Buffer_First + Item'Length; + + Item := Buffer (Buffer_First .. Next_First - 1); + + Buffer_First := Next_First; + else + Last := Item'First + Buff_Diff; + Item (Item'First .. Last) := Buffer (Buffer_First .. Buffer'Last); + Buffer_First := Buffer'Last + 1; + end if; + end Read_Buffer; + + procedure Translate is new Generic_Translate + (Data_In => Read_Buffer, + Data_Out => Further); + + begin + Random_Elements.Reset (Gen); + + Buffer := (others => 20); + + Main : loop + for J in Buffer'Range loop + Buffer (J) := Random_Elements.Random (Gen); + + Deflate_Init (Deflate); + Inflate_Init (Inflate); + + Buffer_First := Buffer'First; + Compare_First := Buffer'First; + + Translate (Deflate); + + if Compare_First /= Buffer'Last + 1 then + raise Program_Error; + end if; + + Ada.Text_IO.Put_Line + (Ada.Task_Identification.Image + (Ada.Task_Identification.Current_Task) + & Stream_Element_Offset'Image (J) + & ZLib.Count'Image (Total_Out (Deflate))); + + Close (Deflate); + Close (Inflate); + + exit Main when Stop; + end loop; + end loop Main; + exception + when E : others => + Ada.Text_IO.Put_Line (Ada.Exceptions.Exception_Information (E)); + Stop := True; + end Test_Task; + + Test : array (1 .. 4) of Test_Task; + + pragma Unreferenced (Test); + + Dummy : Character; + +begin + Ada.Text_IO.Get_Immediate (Dummy); + Stop := True; +end MTest; ADDED compat/zlib/contrib/ada/read.adb Index: compat/zlib/contrib/ada/read.adb ================================================================== --- /dev/null +++ compat/zlib/contrib/ada/read.adb @@ -0,0 +1,156 @@ +---------------------------------------------------------------- +-- ZLib for Ada thick binding. -- +-- -- +-- Copyright (C) 2002-2003 Dmitriy Anisimkov -- +-- -- +-- Open source license information is in the zlib.ads file. -- +---------------------------------------------------------------- + +-- $Id: read.adb,v 1.8 2004/05/31 10:53:40 vagul Exp $ + +-- Test/demo program for the generic read interface. + +with Ada.Numerics.Discrete_Random; +with Ada.Streams; +with Ada.Text_IO; + +with ZLib; + +procedure Read is + + use Ada.Streams; + + ------------------------------------ + -- Test configuration parameters -- + ------------------------------------ + + File_Size : Stream_Element_Offset := 100_000; + + Continuous : constant Boolean := False; + -- If this constant is True, the test would be repeated again and again, + -- with increment File_Size for every iteration. + + Header : constant ZLib.Header_Type := ZLib.Default; + -- Do not use Header other than Default in ZLib versions 1.1.4 and older. + + Init_Random : constant := 8; + -- We are using the same random sequence, in case of we catch bug, + -- so we would be able to reproduce it. + + -- End -- + + Pack_Size : Stream_Element_Offset; + Offset : Stream_Element_Offset; + + Filter : ZLib.Filter_Type; + + subtype Visible_Symbols + is Stream_Element range 16#20# .. 16#7E#; + + package Random_Elements is new + Ada.Numerics.Discrete_Random (Visible_Symbols); + + Gen : Random_Elements.Generator; + Period : constant Stream_Element_Offset := 200; + -- Period constant variable for random generator not to be very random. + -- Bigger period, harder random. + + Read_Buffer : Stream_Element_Array (1 .. 2048); + Read_First : Stream_Element_Offset; + Read_Last : Stream_Element_Offset; + + procedure Reset; + + procedure Read + (Item : out Stream_Element_Array; + Last : out Stream_Element_Offset); + -- this procedure is for generic instantiation of + -- ZLib.Read + -- reading data from the File_In. + + procedure Read is new ZLib.Read + (Read, + Read_Buffer, + Rest_First => Read_First, + Rest_Last => Read_Last); + + ---------- + -- Read -- + ---------- + + procedure Read + (Item : out Stream_Element_Array; + Last : out Stream_Element_Offset) is + begin + Last := Stream_Element_Offset'Min + (Item'Last, + Item'First + File_Size - Offset); + + for J in Item'First .. Last loop + if J < Item'First + Period then + Item (J) := Random_Elements.Random (Gen); + else + Item (J) := Item (J - Period); + end if; + + Offset := Offset + 1; + end loop; + end Read; + + ----------- + -- Reset -- + ----------- + + procedure Reset is + begin + Random_Elements.Reset (Gen, Init_Random); + Pack_Size := 0; + Offset := 1; + Read_First := Read_Buffer'Last + 1; + Read_Last := Read_Buffer'Last; + end Reset; + +begin + Ada.Text_IO.Put_Line ("ZLib " & ZLib.Version); + + loop + for Level in ZLib.Compression_Level'Range loop + + Ada.Text_IO.Put ("Level =" + & ZLib.Compression_Level'Image (Level)); + + -- Deflate using generic instantiation. + + ZLib.Deflate_Init + (Filter, + Level, + Header => Header); + + Reset; + + Ada.Text_IO.Put + (Stream_Element_Offset'Image (File_Size) & " ->"); + + loop + declare + Buffer : Stream_Element_Array (1 .. 1024); + Last : Stream_Element_Offset; + begin + Read (Filter, Buffer, Last); + + Pack_Size := Pack_Size + Last - Buffer'First + 1; + + exit when Last < Buffer'Last; + end; + end loop; + + Ada.Text_IO.Put_Line (Stream_Element_Offset'Image (Pack_Size)); + + ZLib.Close (Filter); + end loop; + + exit when not Continuous; + + File_Size := File_Size + 1; + end loop; +end Read; ADDED compat/zlib/contrib/ada/readme.txt Index: compat/zlib/contrib/ada/readme.txt ================================================================== --- /dev/null +++ compat/zlib/contrib/ada/readme.txt @@ -0,0 +1,65 @@ + ZLib for Ada thick binding (ZLib.Ada) + Release 1.3 + +ZLib.Ada is a thick binding interface to the popular ZLib data +compression library, available at http://www.gzip.org/zlib/. +It provides Ada-style access to the ZLib C library. + + + Here are the main changes since ZLib.Ada 1.2: + +- Attension: ZLib.Read generic routine have a initialization requirement + for Read_Last parameter now. It is a bit incompartible with previous version, + but extends functionality, we could use new parameters Allow_Read_Some and + Flush now. + +- Added Is_Open routines to ZLib and ZLib.Streams packages. + +- Add pragma Assert to check Stream_Element is 8 bit. + +- Fix extraction to buffer with exact known decompressed size. Error reported by + Steve Sangwine. + +- Fix definition of ULong (changed to unsigned_long), fix regression on 64 bits + computers. Patch provided by Pascal Obry. + +- Add Status_Error exception definition. + +- Add pragma Assertion that Ada.Streams.Stream_Element size is 8 bit. + + + How to build ZLib.Ada under GNAT + +You should have the ZLib library already build on your computer, before +building ZLib.Ada. Make the directory of ZLib.Ada sources current and +issue the command: + + gnatmake test -largs -L -lz + +Or use the GNAT project file build for GNAT 3.15 or later: + + gnatmake -Pzlib.gpr -L + + + How to build ZLib.Ada under Aonix ObjectAda for Win32 7.2.2 + +1. Make a project with all *.ads and *.adb files from the distribution. +2. Build the libz.a library from the ZLib C sources. +3. Rename libz.a to z.lib. +4. Add the library z.lib to the project. +5. Add the libc.lib library from the ObjectAda distribution to the project. +6. Build the executable using test.adb as a main procedure. + + + How to use ZLib.Ada + +The source files test.adb and read.adb are small demo programs that show +the main functionality of ZLib.Ada. + +The routines from the package specifications are commented. + + +Homepage: http://zlib-ada.sourceforge.net/ +Author: Dmitriy Anisimkov + +Contributors: Pascal Obry , Steve Sangwine ADDED compat/zlib/contrib/ada/test.adb Index: compat/zlib/contrib/ada/test.adb ================================================================== --- /dev/null +++ compat/zlib/contrib/ada/test.adb @@ -0,0 +1,463 @@ +---------------------------------------------------------------- +-- ZLib for Ada thick binding. -- +-- -- +-- Copyright (C) 2002-2003 Dmitriy Anisimkov -- +-- -- +-- Open source license information is in the zlib.ads file. -- +---------------------------------------------------------------- + +-- $Id: test.adb,v 1.17 2003/08/12 12:13:30 vagul Exp $ + +-- The program has a few aims. +-- 1. Test ZLib.Ada95 thick binding functionality. +-- 2. Show the example of use main functionality of the ZLib.Ada95 binding. +-- 3. Build this program automatically compile all ZLib.Ada95 packages under +-- GNAT Ada95 compiler. + +with ZLib.Streams; +with Ada.Streams.Stream_IO; +with Ada.Numerics.Discrete_Random; + +with Ada.Text_IO; + +with Ada.Calendar; + +procedure Test is + + use Ada.Streams; + use Stream_IO; + + ------------------------------------ + -- Test configuration parameters -- + ------------------------------------ + + File_Size : Count := 100_000; + Continuous : constant Boolean := False; + + Header : constant ZLib.Header_Type := ZLib.Default; + -- ZLib.None; + -- ZLib.Auto; + -- ZLib.GZip; + -- Do not use Header other then Default in ZLib versions 1.1.4 + -- and older. + + Strategy : constant ZLib.Strategy_Type := ZLib.Default_Strategy; + Init_Random : constant := 10; + + -- End -- + + In_File_Name : constant String := "testzlib.in"; + -- Name of the input file + + Z_File_Name : constant String := "testzlib.zlb"; + -- Name of the compressed file. + + Out_File_Name : constant String := "testzlib.out"; + -- Name of the decompressed file. + + File_In : File_Type; + File_Out : File_Type; + File_Back : File_Type; + File_Z : ZLib.Streams.Stream_Type; + + Filter : ZLib.Filter_Type; + + Time_Stamp : Ada.Calendar.Time; + + procedure Generate_File; + -- Generate file of spetsified size with some random data. + -- The random data is repeatable, for the good compression. + + procedure Compare_Streams + (Left, Right : in out Root_Stream_Type'Class); + -- The procedure compearing data in 2 streams. + -- It is for compare data before and after compression/decompression. + + procedure Compare_Files (Left, Right : String); + -- Compare files. Based on the Compare_Streams. + + procedure Copy_Streams + (Source, Target : in out Root_Stream_Type'Class; + Buffer_Size : in Stream_Element_Offset := 1024); + -- Copying data from one stream to another. It is for test stream + -- interface of the library. + + procedure Data_In + (Item : out Stream_Element_Array; + Last : out Stream_Element_Offset); + -- this procedure is for generic instantiation of + -- ZLib.Generic_Translate. + -- reading data from the File_In. + + procedure Data_Out (Item : in Stream_Element_Array); + -- this procedure is for generic instantiation of + -- ZLib.Generic_Translate. + -- writing data to the File_Out. + + procedure Stamp; + -- Store the timestamp to the local variable. + + procedure Print_Statistic (Msg : String; Data_Size : ZLib.Count); + -- Print the time statistic with the message. + + procedure Translate is new ZLib.Generic_Translate + (Data_In => Data_In, + Data_Out => Data_Out); + -- This procedure is moving data from File_In to File_Out + -- with compression or decompression, depend on initialization of + -- Filter parameter. + + ------------------- + -- Compare_Files -- + ------------------- + + procedure Compare_Files (Left, Right : String) is + Left_File, Right_File : File_Type; + begin + Open (Left_File, In_File, Left); + Open (Right_File, In_File, Right); + Compare_Streams (Stream (Left_File).all, Stream (Right_File).all); + Close (Left_File); + Close (Right_File); + end Compare_Files; + + --------------------- + -- Compare_Streams -- + --------------------- + + procedure Compare_Streams + (Left, Right : in out Ada.Streams.Root_Stream_Type'Class) + is + Left_Buffer, Right_Buffer : Stream_Element_Array (0 .. 16#FFF#); + Left_Last, Right_Last : Stream_Element_Offset; + begin + loop + Read (Left, Left_Buffer, Left_Last); + Read (Right, Right_Buffer, Right_Last); + + if Left_Last /= Right_Last then + Ada.Text_IO.Put_Line ("Compare error :" + & Stream_Element_Offset'Image (Left_Last) + & " /= " + & Stream_Element_Offset'Image (Right_Last)); + + raise Constraint_Error; + + elsif Left_Buffer (0 .. Left_Last) + /= Right_Buffer (0 .. Right_Last) + then + Ada.Text_IO.Put_Line ("ERROR: IN and OUT files is not equal."); + raise Constraint_Error; + + end if; + + exit when Left_Last < Left_Buffer'Last; + end loop; + end Compare_Streams; + + ------------------ + -- Copy_Streams -- + ------------------ + + procedure Copy_Streams + (Source, Target : in out Ada.Streams.Root_Stream_Type'Class; + Buffer_Size : in Stream_Element_Offset := 1024) + is + Buffer : Stream_Element_Array (1 .. Buffer_Size); + Last : Stream_Element_Offset; + begin + loop + Read (Source, Buffer, Last); + Write (Target, Buffer (1 .. Last)); + + exit when Last < Buffer'Last; + end loop; + end Copy_Streams; + + ------------- + -- Data_In -- + ------------- + + procedure Data_In + (Item : out Stream_Element_Array; + Last : out Stream_Element_Offset) is + begin + Read (File_In, Item, Last); + end Data_In; + + -------------- + -- Data_Out -- + -------------- + + procedure Data_Out (Item : in Stream_Element_Array) is + begin + Write (File_Out, Item); + end Data_Out; + + ------------------- + -- Generate_File -- + ------------------- + + procedure Generate_File is + subtype Visible_Symbols is Stream_Element range 16#20# .. 16#7E#; + + package Random_Elements is + new Ada.Numerics.Discrete_Random (Visible_Symbols); + + Gen : Random_Elements.Generator; + Buffer : Stream_Element_Array := (1 .. 77 => 16#20#) & 10; + + Buffer_Count : constant Count := File_Size / Buffer'Length; + -- Number of same buffers in the packet. + + Density : constant Count := 30; -- from 0 to Buffer'Length - 2; + + procedure Fill_Buffer (J, D : in Count); + -- Change the part of the buffer. + + ----------------- + -- Fill_Buffer -- + ----------------- + + procedure Fill_Buffer (J, D : in Count) is + begin + for K in 0 .. D loop + Buffer + (Stream_Element_Offset ((J + K) mod (Buffer'Length - 1) + 1)) + := Random_Elements.Random (Gen); + + end loop; + end Fill_Buffer; + + begin + Random_Elements.Reset (Gen, Init_Random); + + Create (File_In, Out_File, In_File_Name); + + Fill_Buffer (1, Buffer'Length - 2); + + for J in 1 .. Buffer_Count loop + Write (File_In, Buffer); + + Fill_Buffer (J, Density); + end loop; + + -- fill remain size. + + Write + (File_In, + Buffer + (1 .. Stream_Element_Offset + (File_Size - Buffer'Length * Buffer_Count))); + + Flush (File_In); + Close (File_In); + end Generate_File; + + --------------------- + -- Print_Statistic -- + --------------------- + + procedure Print_Statistic (Msg : String; Data_Size : ZLib.Count) is + use Ada.Calendar; + use Ada.Text_IO; + + package Count_IO is new Integer_IO (ZLib.Count); + + Curr_Dur : Duration := Clock - Time_Stamp; + begin + Put (Msg); + + Set_Col (20); + Ada.Text_IO.Put ("size ="); + + Count_IO.Put + (Data_Size, + Width => Stream_IO.Count'Image (File_Size)'Length); + + Put_Line (" duration =" & Duration'Image (Curr_Dur)); + end Print_Statistic; + + ----------- + -- Stamp -- + ----------- + + procedure Stamp is + begin + Time_Stamp := Ada.Calendar.Clock; + end Stamp; + +begin + Ada.Text_IO.Put_Line ("ZLib " & ZLib.Version); + + loop + Generate_File; + + for Level in ZLib.Compression_Level'Range loop + + Ada.Text_IO.Put_Line ("Level =" + & ZLib.Compression_Level'Image (Level)); + + -- Test generic interface. + Open (File_In, In_File, In_File_Name); + Create (File_Out, Out_File, Z_File_Name); + + Stamp; + + -- Deflate using generic instantiation. + + ZLib.Deflate_Init + (Filter => Filter, + Level => Level, + Strategy => Strategy, + Header => Header); + + Translate (Filter); + Print_Statistic ("Generic compress", ZLib.Total_Out (Filter)); + ZLib.Close (Filter); + + Close (File_In); + Close (File_Out); + + Open (File_In, In_File, Z_File_Name); + Create (File_Out, Out_File, Out_File_Name); + + Stamp; + + -- Inflate using generic instantiation. + + ZLib.Inflate_Init (Filter, Header => Header); + + Translate (Filter); + Print_Statistic ("Generic decompress", ZLib.Total_Out (Filter)); + + ZLib.Close (Filter); + + Close (File_In); + Close (File_Out); + + Compare_Files (In_File_Name, Out_File_Name); + + -- Test stream interface. + + -- Compress to the back stream. + + Open (File_In, In_File, In_File_Name); + Create (File_Back, Out_File, Z_File_Name); + + Stamp; + + ZLib.Streams.Create + (Stream => File_Z, + Mode => ZLib.Streams.Out_Stream, + Back => ZLib.Streams.Stream_Access + (Stream (File_Back)), + Back_Compressed => True, + Level => Level, + Strategy => Strategy, + Header => Header); + + Copy_Streams + (Source => Stream (File_In).all, + Target => File_Z); + + -- Flushing internal buffers to the back stream. + + ZLib.Streams.Flush (File_Z, ZLib.Finish); + + Print_Statistic ("Write compress", + ZLib.Streams.Write_Total_Out (File_Z)); + + ZLib.Streams.Close (File_Z); + + Close (File_In); + Close (File_Back); + + -- Compare reading from original file and from + -- decompression stream. + + Open (File_In, In_File, In_File_Name); + Open (File_Back, In_File, Z_File_Name); + + ZLib.Streams.Create + (Stream => File_Z, + Mode => ZLib.Streams.In_Stream, + Back => ZLib.Streams.Stream_Access + (Stream (File_Back)), + Back_Compressed => True, + Header => Header); + + Stamp; + Compare_Streams (Stream (File_In).all, File_Z); + + Print_Statistic ("Read decompress", + ZLib.Streams.Read_Total_Out (File_Z)); + + ZLib.Streams.Close (File_Z); + Close (File_In); + Close (File_Back); + + -- Compress by reading from compression stream. + + Open (File_Back, In_File, In_File_Name); + Create (File_Out, Out_File, Z_File_Name); + + ZLib.Streams.Create + (Stream => File_Z, + Mode => ZLib.Streams.In_Stream, + Back => ZLib.Streams.Stream_Access + (Stream (File_Back)), + Back_Compressed => False, + Level => Level, + Strategy => Strategy, + Header => Header); + + Stamp; + Copy_Streams + (Source => File_Z, + Target => Stream (File_Out).all); + + Print_Statistic ("Read compress", + ZLib.Streams.Read_Total_Out (File_Z)); + + ZLib.Streams.Close (File_Z); + + Close (File_Out); + Close (File_Back); + + -- Decompress to decompression stream. + + Open (File_In, In_File, Z_File_Name); + Create (File_Back, Out_File, Out_File_Name); + + ZLib.Streams.Create + (Stream => File_Z, + Mode => ZLib.Streams.Out_Stream, + Back => ZLib.Streams.Stream_Access + (Stream (File_Back)), + Back_Compressed => False, + Header => Header); + + Stamp; + + Copy_Streams + (Source => Stream (File_In).all, + Target => File_Z); + + Print_Statistic ("Write decompress", + ZLib.Streams.Write_Total_Out (File_Z)); + + ZLib.Streams.Close (File_Z); + Close (File_In); + Close (File_Back); + + Compare_Files (In_File_Name, Out_File_Name); + end loop; + + Ada.Text_IO.Put_Line (Count'Image (File_Size) & " Ok."); + + exit when not Continuous; + + File_Size := File_Size + 1; + end loop; +end Test; ADDED compat/zlib/contrib/ada/zlib-streams.adb Index: compat/zlib/contrib/ada/zlib-streams.adb ================================================================== --- /dev/null +++ compat/zlib/contrib/ada/zlib-streams.adb @@ -0,0 +1,225 @@ +---------------------------------------------------------------- +-- ZLib for Ada thick binding. -- +-- -- +-- Copyright (C) 2002-2003 Dmitriy Anisimkov -- +-- -- +-- Open source license information is in the zlib.ads file. -- +---------------------------------------------------------------- + +-- $Id: zlib-streams.adb,v 1.10 2004/05/31 10:53:40 vagul Exp $ + +with Ada.Unchecked_Deallocation; + +package body ZLib.Streams is + + ----------- + -- Close -- + ----------- + + procedure Close (Stream : in out Stream_Type) is + procedure Free is new Ada.Unchecked_Deallocation + (Stream_Element_Array, Buffer_Access); + begin + if Stream.Mode = Out_Stream or Stream.Mode = Duplex then + -- We should flush the data written by the writer. + + Flush (Stream, Finish); + + Close (Stream.Writer); + end if; + + if Stream.Mode = In_Stream or Stream.Mode = Duplex then + Close (Stream.Reader); + Free (Stream.Buffer); + end if; + end Close; + + ------------ + -- Create -- + ------------ + + procedure Create + (Stream : out Stream_Type; + Mode : in Stream_Mode; + Back : in Stream_Access; + Back_Compressed : in Boolean; + Level : in Compression_Level := Default_Compression; + Strategy : in Strategy_Type := Default_Strategy; + Header : in Header_Type := Default; + Read_Buffer_Size : in Ada.Streams.Stream_Element_Offset + := Default_Buffer_Size; + Write_Buffer_Size : in Ada.Streams.Stream_Element_Offset + := Default_Buffer_Size) + is + + subtype Buffer_Subtype is Stream_Element_Array (1 .. Read_Buffer_Size); + + procedure Init_Filter + (Filter : in out Filter_Type; + Compress : in Boolean); + + ----------------- + -- Init_Filter -- + ----------------- + + procedure Init_Filter + (Filter : in out Filter_Type; + Compress : in Boolean) is + begin + if Compress then + Deflate_Init + (Filter, Level, Strategy, Header => Header); + else + Inflate_Init (Filter, Header => Header); + end if; + end Init_Filter; + + begin + Stream.Back := Back; + Stream.Mode := Mode; + + if Mode = Out_Stream or Mode = Duplex then + Init_Filter (Stream.Writer, Back_Compressed); + Stream.Buffer_Size := Write_Buffer_Size; + else + Stream.Buffer_Size := 0; + end if; + + if Mode = In_Stream or Mode = Duplex then + Init_Filter (Stream.Reader, not Back_Compressed); + + Stream.Buffer := new Buffer_Subtype; + Stream.Rest_First := Stream.Buffer'Last + 1; + Stream.Rest_Last := Stream.Buffer'Last; + end if; + end Create; + + ----------- + -- Flush -- + ----------- + + procedure Flush + (Stream : in out Stream_Type; + Mode : in Flush_Mode := Sync_Flush) + is + Buffer : Stream_Element_Array (1 .. Stream.Buffer_Size); + Last : Stream_Element_Offset; + begin + loop + Flush (Stream.Writer, Buffer, Last, Mode); + + Ada.Streams.Write (Stream.Back.all, Buffer (1 .. Last)); + + exit when Last < Buffer'Last; + end loop; + end Flush; + + ------------- + -- Is_Open -- + ------------- + + function Is_Open (Stream : Stream_Type) return Boolean is + begin + return Is_Open (Stream.Reader) or else Is_Open (Stream.Writer); + end Is_Open; + + ---------- + -- Read -- + ---------- + + procedure Read + (Stream : in out Stream_Type; + Item : out Stream_Element_Array; + Last : out Stream_Element_Offset) + is + + procedure Read + (Item : out Stream_Element_Array; + Last : out Stream_Element_Offset); + + ---------- + -- Read -- + ---------- + + procedure Read + (Item : out Stream_Element_Array; + Last : out Stream_Element_Offset) is + begin + Ada.Streams.Read (Stream.Back.all, Item, Last); + end Read; + + procedure Read is new ZLib.Read + (Read => Read, + Buffer => Stream.Buffer.all, + Rest_First => Stream.Rest_First, + Rest_Last => Stream.Rest_Last); + + begin + Read (Stream.Reader, Item, Last); + end Read; + + ------------------- + -- Read_Total_In -- + ------------------- + + function Read_Total_In (Stream : in Stream_Type) return Count is + begin + return Total_In (Stream.Reader); + end Read_Total_In; + + -------------------- + -- Read_Total_Out -- + -------------------- + + function Read_Total_Out (Stream : in Stream_Type) return Count is + begin + return Total_Out (Stream.Reader); + end Read_Total_Out; + + ----------- + -- Write -- + ----------- + + procedure Write + (Stream : in out Stream_Type; + Item : in Stream_Element_Array) + is + + procedure Write (Item : in Stream_Element_Array); + + ----------- + -- Write -- + ----------- + + procedure Write (Item : in Stream_Element_Array) is + begin + Ada.Streams.Write (Stream.Back.all, Item); + end Write; + + procedure Write is new ZLib.Write + (Write => Write, + Buffer_Size => Stream.Buffer_Size); + + begin + Write (Stream.Writer, Item, No_Flush); + end Write; + + -------------------- + -- Write_Total_In -- + -------------------- + + function Write_Total_In (Stream : in Stream_Type) return Count is + begin + return Total_In (Stream.Writer); + end Write_Total_In; + + --------------------- + -- Write_Total_Out -- + --------------------- + + function Write_Total_Out (Stream : in Stream_Type) return Count is + begin + return Total_Out (Stream.Writer); + end Write_Total_Out; + +end ZLib.Streams; ADDED compat/zlib/contrib/ada/zlib-streams.ads Index: compat/zlib/contrib/ada/zlib-streams.ads ================================================================== --- /dev/null +++ compat/zlib/contrib/ada/zlib-streams.ads @@ -0,0 +1,114 @@ +---------------------------------------------------------------- +-- ZLib for Ada thick binding. -- +-- -- +-- Copyright (C) 2002-2003 Dmitriy Anisimkov -- +-- -- +-- Open source license information is in the zlib.ads file. -- +---------------------------------------------------------------- + +-- $Id: zlib-streams.ads,v 1.12 2004/05/31 10:53:40 vagul Exp $ + +package ZLib.Streams is + + type Stream_Mode is (In_Stream, Out_Stream, Duplex); + + type Stream_Access is access all Ada.Streams.Root_Stream_Type'Class; + + type Stream_Type is + new Ada.Streams.Root_Stream_Type with private; + + procedure Read + (Stream : in out Stream_Type; + Item : out Ada.Streams.Stream_Element_Array; + Last : out Ada.Streams.Stream_Element_Offset); + + procedure Write + (Stream : in out Stream_Type; + Item : in Ada.Streams.Stream_Element_Array); + + procedure Flush + (Stream : in out Stream_Type; + Mode : in Flush_Mode := Sync_Flush); + -- Flush the written data to the back stream, + -- all data placed to the compressor is flushing to the Back stream. + -- Should not be used until necessary, because it is decreasing + -- compression. + + function Read_Total_In (Stream : in Stream_Type) return Count; + pragma Inline (Read_Total_In); + -- Return total number of bytes read from back stream so far. + + function Read_Total_Out (Stream : in Stream_Type) return Count; + pragma Inline (Read_Total_Out); + -- Return total number of bytes read so far. + + function Write_Total_In (Stream : in Stream_Type) return Count; + pragma Inline (Write_Total_In); + -- Return total number of bytes written so far. + + function Write_Total_Out (Stream : in Stream_Type) return Count; + pragma Inline (Write_Total_Out); + -- Return total number of bytes written to the back stream. + + procedure Create + (Stream : out Stream_Type; + Mode : in Stream_Mode; + Back : in Stream_Access; + Back_Compressed : in Boolean; + Level : in Compression_Level := Default_Compression; + Strategy : in Strategy_Type := Default_Strategy; + Header : in Header_Type := Default; + Read_Buffer_Size : in Ada.Streams.Stream_Element_Offset + := Default_Buffer_Size; + Write_Buffer_Size : in Ada.Streams.Stream_Element_Offset + := Default_Buffer_Size); + -- Create the Comression/Decompression stream. + -- If mode is In_Stream then Write operation is disabled. + -- If mode is Out_Stream then Read operation is disabled. + + -- If Back_Compressed is true then + -- Data written to the Stream is compressing to the Back stream + -- and data read from the Stream is decompressed data from the Back stream. + + -- If Back_Compressed is false then + -- Data written to the Stream is decompressing to the Back stream + -- and data read from the Stream is compressed data from the Back stream. + + -- !!! When the Need_Header is False ZLib-Ada is using undocumented + -- ZLib 1.1.4 functionality to do not create/wait for ZLib headers. + + function Is_Open (Stream : Stream_Type) return Boolean; + + procedure Close (Stream : in out Stream_Type); + +private + + use Ada.Streams; + + type Buffer_Access is access all Stream_Element_Array; + + type Stream_Type + is new Root_Stream_Type with + record + Mode : Stream_Mode; + + Buffer : Buffer_Access; + Rest_First : Stream_Element_Offset; + Rest_Last : Stream_Element_Offset; + -- Buffer for Read operation. + -- We need to have this buffer in the record + -- because not all read data from back stream + -- could be processed during the read operation. + + Buffer_Size : Stream_Element_Offset; + -- Buffer size for write operation. + -- We do not need to have this buffer + -- in the record because all data could be + -- processed in the write operation. + + Back : Stream_Access; + Reader : Filter_Type; + Writer : Filter_Type; + end record; + +end ZLib.Streams; ADDED compat/zlib/contrib/ada/zlib-thin.adb Index: compat/zlib/contrib/ada/zlib-thin.adb ================================================================== --- /dev/null +++ compat/zlib/contrib/ada/zlib-thin.adb @@ -0,0 +1,141 @@ +---------------------------------------------------------------- +-- ZLib for Ada thick binding. -- +-- -- +-- Copyright (C) 2002-2003 Dmitriy Anisimkov -- +-- -- +-- Open source license information is in the zlib.ads file. -- +---------------------------------------------------------------- + +-- $Id: zlib-thin.adb,v 1.8 2003/12/14 18:27:31 vagul Exp $ + +package body ZLib.Thin is + + ZLIB_VERSION : constant Chars_Ptr := zlibVersion; + + Z_Stream_Size : constant Int := Z_Stream'Size / System.Storage_Unit; + + -------------- + -- Avail_In -- + -------------- + + function Avail_In (Strm : in Z_Stream) return UInt is + begin + return Strm.Avail_In; + end Avail_In; + + --------------- + -- Avail_Out -- + --------------- + + function Avail_Out (Strm : in Z_Stream) return UInt is + begin + return Strm.Avail_Out; + end Avail_Out; + + ------------------ + -- Deflate_Init -- + ------------------ + + function Deflate_Init + (strm : Z_Streamp; + level : Int; + method : Int; + windowBits : Int; + memLevel : Int; + strategy : Int) + return Int is + begin + return deflateInit2 + (strm, + level, + method, + windowBits, + memLevel, + strategy, + ZLIB_VERSION, + Z_Stream_Size); + end Deflate_Init; + + ------------------ + -- Inflate_Init -- + ------------------ + + function Inflate_Init (strm : Z_Streamp; windowBits : Int) return Int is + begin + return inflateInit2 (strm, windowBits, ZLIB_VERSION, Z_Stream_Size); + end Inflate_Init; + + ------------------------ + -- Last_Error_Message -- + ------------------------ + + function Last_Error_Message (Strm : in Z_Stream) return String is + use Interfaces.C.Strings; + begin + if Strm.msg = Null_Ptr then + return ""; + else + return Value (Strm.msg); + end if; + end Last_Error_Message; + + ------------ + -- Set_In -- + ------------ + + procedure Set_In + (Strm : in out Z_Stream; + Buffer : in Voidp; + Size : in UInt) is + begin + Strm.Next_In := Buffer; + Strm.Avail_In := Size; + end Set_In; + + ------------------ + -- Set_Mem_Func -- + ------------------ + + procedure Set_Mem_Func + (Strm : in out Z_Stream; + Opaque : in Voidp; + Alloc : in alloc_func; + Free : in free_func) is + begin + Strm.opaque := Opaque; + Strm.zalloc := Alloc; + Strm.zfree := Free; + end Set_Mem_Func; + + ------------- + -- Set_Out -- + ------------- + + procedure Set_Out + (Strm : in out Z_Stream; + Buffer : in Voidp; + Size : in UInt) is + begin + Strm.Next_Out := Buffer; + Strm.Avail_Out := Size; + end Set_Out; + + -------------- + -- Total_In -- + -------------- + + function Total_In (Strm : in Z_Stream) return ULong is + begin + return Strm.Total_In; + end Total_In; + + --------------- + -- Total_Out -- + --------------- + + function Total_Out (Strm : in Z_Stream) return ULong is + begin + return Strm.Total_Out; + end Total_Out; + +end ZLib.Thin; ADDED compat/zlib/contrib/ada/zlib-thin.ads Index: compat/zlib/contrib/ada/zlib-thin.ads ================================================================== --- /dev/null +++ compat/zlib/contrib/ada/zlib-thin.ads @@ -0,0 +1,450 @@ +---------------------------------------------------------------- +-- ZLib for Ada thick binding. -- +-- -- +-- Copyright (C) 2002-2003 Dmitriy Anisimkov -- +-- -- +-- Open source license information is in the zlib.ads file. -- +---------------------------------------------------------------- + +-- $Id: zlib-thin.ads,v 1.11 2004/07/23 06:33:11 vagul Exp $ + +with Interfaces.C.Strings; + +with System; + +private package ZLib.Thin is + + -- From zconf.h + + MAX_MEM_LEVEL : constant := 9; -- zconf.h:105 + -- zconf.h:105 + MAX_WBITS : constant := 15; -- zconf.h:115 + -- 32K LZ77 window + -- zconf.h:115 + SEEK_SET : constant := 8#0000#; -- zconf.h:244 + -- Seek from beginning of file. + -- zconf.h:244 + SEEK_CUR : constant := 1; -- zconf.h:245 + -- Seek from current position. + -- zconf.h:245 + SEEK_END : constant := 2; -- zconf.h:246 + -- Set file pointer to EOF plus "offset" + -- zconf.h:246 + + type Byte is new Interfaces.C.unsigned_char; -- 8 bits + -- zconf.h:214 + type UInt is new Interfaces.C.unsigned; -- 16 bits or more + -- zconf.h:216 + type Int is new Interfaces.C.int; + + type ULong is new Interfaces.C.unsigned_long; -- 32 bits or more + -- zconf.h:217 + subtype Chars_Ptr is Interfaces.C.Strings.chars_ptr; + + type ULong_Access is access ULong; + type Int_Access is access Int; + + subtype Voidp is System.Address; -- zconf.h:232 + + subtype Byte_Access is Voidp; + + Nul : constant Voidp := System.Null_Address; + -- end from zconf + + Z_NO_FLUSH : constant := 8#0000#; -- zlib.h:125 + -- zlib.h:125 + Z_PARTIAL_FLUSH : constant := 1; -- zlib.h:126 + -- will be removed, use + -- Z_SYNC_FLUSH instead + -- zlib.h:126 + Z_SYNC_FLUSH : constant := 2; -- zlib.h:127 + -- zlib.h:127 + Z_FULL_FLUSH : constant := 3; -- zlib.h:128 + -- zlib.h:128 + Z_FINISH : constant := 4; -- zlib.h:129 + -- zlib.h:129 + Z_OK : constant := 8#0000#; -- zlib.h:132 + -- zlib.h:132 + Z_STREAM_END : constant := 1; -- zlib.h:133 + -- zlib.h:133 + Z_NEED_DICT : constant := 2; -- zlib.h:134 + -- zlib.h:134 + Z_ERRNO : constant := -1; -- zlib.h:135 + -- zlib.h:135 + Z_STREAM_ERROR : constant := -2; -- zlib.h:136 + -- zlib.h:136 + Z_DATA_ERROR : constant := -3; -- zlib.h:137 + -- zlib.h:137 + Z_MEM_ERROR : constant := -4; -- zlib.h:138 + -- zlib.h:138 + Z_BUF_ERROR : constant := -5; -- zlib.h:139 + -- zlib.h:139 + Z_VERSION_ERROR : constant := -6; -- zlib.h:140 + -- zlib.h:140 + Z_NO_COMPRESSION : constant := 8#0000#; -- zlib.h:145 + -- zlib.h:145 + Z_BEST_SPEED : constant := 1; -- zlib.h:146 + -- zlib.h:146 + Z_BEST_COMPRESSION : constant := 9; -- zlib.h:147 + -- zlib.h:147 + Z_DEFAULT_COMPRESSION : constant := -1; -- zlib.h:148 + -- zlib.h:148 + Z_FILTERED : constant := 1; -- zlib.h:151 + -- zlib.h:151 + Z_HUFFMAN_ONLY : constant := 2; -- zlib.h:152 + -- zlib.h:152 + Z_DEFAULT_STRATEGY : constant := 8#0000#; -- zlib.h:153 + -- zlib.h:153 + Z_BINARY : constant := 8#0000#; -- zlib.h:156 + -- zlib.h:156 + Z_ASCII : constant := 1; -- zlib.h:157 + -- zlib.h:157 + Z_UNKNOWN : constant := 2; -- zlib.h:158 + -- zlib.h:158 + Z_DEFLATED : constant := 8; -- zlib.h:161 + -- zlib.h:161 + Z_NULL : constant := 8#0000#; -- zlib.h:164 + -- for initializing zalloc, zfree, opaque + -- zlib.h:164 + type gzFile is new Voidp; -- zlib.h:646 + + type Z_Stream is private; + + type Z_Streamp is access all Z_Stream; -- zlib.h:89 + + type alloc_func is access function + (Opaque : Voidp; + Items : UInt; + Size : UInt) + return Voidp; -- zlib.h:63 + + type free_func is access procedure (opaque : Voidp; address : Voidp); + + function zlibVersion return Chars_Ptr; + + function Deflate (strm : Z_Streamp; flush : Int) return Int; + + function DeflateEnd (strm : Z_Streamp) return Int; + + function Inflate (strm : Z_Streamp; flush : Int) return Int; + + function InflateEnd (strm : Z_Streamp) return Int; + + function deflateSetDictionary + (strm : Z_Streamp; + dictionary : Byte_Access; + dictLength : UInt) + return Int; + + function deflateCopy (dest : Z_Streamp; source : Z_Streamp) return Int; + -- zlib.h:478 + + function deflateReset (strm : Z_Streamp) return Int; -- zlib.h:495 + + function deflateParams + (strm : Z_Streamp; + level : Int; + strategy : Int) + return Int; -- zlib.h:506 + + function inflateSetDictionary + (strm : Z_Streamp; + dictionary : Byte_Access; + dictLength : UInt) + return Int; -- zlib.h:548 + + function inflateSync (strm : Z_Streamp) return Int; -- zlib.h:565 + + function inflateReset (strm : Z_Streamp) return Int; -- zlib.h:580 + + function compress + (dest : Byte_Access; + destLen : ULong_Access; + source : Byte_Access; + sourceLen : ULong) + return Int; -- zlib.h:601 + + function compress2 + (dest : Byte_Access; + destLen : ULong_Access; + source : Byte_Access; + sourceLen : ULong; + level : Int) + return Int; -- zlib.h:615 + + function uncompress + (dest : Byte_Access; + destLen : ULong_Access; + source : Byte_Access; + sourceLen : ULong) + return Int; + + function gzopen (path : Chars_Ptr; mode : Chars_Ptr) return gzFile; + + function gzdopen (fd : Int; mode : Chars_Ptr) return gzFile; + + function gzsetparams + (file : gzFile; + level : Int; + strategy : Int) + return Int; + + function gzread + (file : gzFile; + buf : Voidp; + len : UInt) + return Int; + + function gzwrite + (file : in gzFile; + buf : in Voidp; + len : in UInt) + return Int; + + function gzprintf (file : in gzFile; format : in Chars_Ptr) return Int; + + function gzputs (file : in gzFile; s : in Chars_Ptr) return Int; + + function gzgets + (file : gzFile; + buf : Chars_Ptr; + len : Int) + return Chars_Ptr; + + function gzputc (file : gzFile; char : Int) return Int; + + function gzgetc (file : gzFile) return Int; + + function gzflush (file : gzFile; flush : Int) return Int; + + function gzseek + (file : gzFile; + offset : Int; + whence : Int) + return Int; + + function gzrewind (file : gzFile) return Int; + + function gztell (file : gzFile) return Int; + + function gzeof (file : gzFile) return Int; + + function gzclose (file : gzFile) return Int; + + function gzerror (file : gzFile; errnum : Int_Access) return Chars_Ptr; + + function adler32 + (adler : ULong; + buf : Byte_Access; + len : UInt) + return ULong; + + function crc32 + (crc : ULong; + buf : Byte_Access; + len : UInt) + return ULong; + + function deflateInit + (strm : Z_Streamp; + level : Int; + version : Chars_Ptr; + stream_size : Int) + return Int; + + function deflateInit2 + (strm : Z_Streamp; + level : Int; + method : Int; + windowBits : Int; + memLevel : Int; + strategy : Int; + version : Chars_Ptr; + stream_size : Int) + return Int; + + function Deflate_Init + (strm : Z_Streamp; + level : Int; + method : Int; + windowBits : Int; + memLevel : Int; + strategy : Int) + return Int; + pragma Inline (Deflate_Init); + + function inflateInit + (strm : Z_Streamp; + version : Chars_Ptr; + stream_size : Int) + return Int; + + function inflateInit2 + (strm : in Z_Streamp; + windowBits : in Int; + version : in Chars_Ptr; + stream_size : in Int) + return Int; + + function inflateBackInit + (strm : in Z_Streamp; + windowBits : in Int; + window : in Byte_Access; + version : in Chars_Ptr; + stream_size : in Int) + return Int; + -- Size of window have to be 2**windowBits. + + function Inflate_Init (strm : Z_Streamp; windowBits : Int) return Int; + pragma Inline (Inflate_Init); + + function zError (err : Int) return Chars_Ptr; + + function inflateSyncPoint (z : Z_Streamp) return Int; + + function get_crc_table return ULong_Access; + + -- Interface to the available fields of the z_stream structure. + -- The application must update next_in and avail_in when avail_in has + -- dropped to zero. It must update next_out and avail_out when avail_out + -- has dropped to zero. The application must initialize zalloc, zfree and + -- opaque before calling the init function. + + procedure Set_In + (Strm : in out Z_Stream; + Buffer : in Voidp; + Size : in UInt); + pragma Inline (Set_In); + + procedure Set_Out + (Strm : in out Z_Stream; + Buffer : in Voidp; + Size : in UInt); + pragma Inline (Set_Out); + + procedure Set_Mem_Func + (Strm : in out Z_Stream; + Opaque : in Voidp; + Alloc : in alloc_func; + Free : in free_func); + pragma Inline (Set_Mem_Func); + + function Last_Error_Message (Strm : in Z_Stream) return String; + pragma Inline (Last_Error_Message); + + function Avail_Out (Strm : in Z_Stream) return UInt; + pragma Inline (Avail_Out); + + function Avail_In (Strm : in Z_Stream) return UInt; + pragma Inline (Avail_In); + + function Total_In (Strm : in Z_Stream) return ULong; + pragma Inline (Total_In); + + function Total_Out (Strm : in Z_Stream) return ULong; + pragma Inline (Total_Out); + + function inflateCopy + (dest : in Z_Streamp; + Source : in Z_Streamp) + return Int; + + function compressBound (Source_Len : in ULong) return ULong; + + function deflateBound + (Strm : in Z_Streamp; + Source_Len : in ULong) + return ULong; + + function gzungetc (C : in Int; File : in gzFile) return Int; + + function zlibCompileFlags return ULong; + +private + + type Z_Stream is record -- zlib.h:68 + Next_In : Voidp := Nul; -- next input byte + Avail_In : UInt := 0; -- number of bytes available at next_in + Total_In : ULong := 0; -- total nb of input bytes read so far + Next_Out : Voidp := Nul; -- next output byte should be put there + Avail_Out : UInt := 0; -- remaining free space at next_out + Total_Out : ULong := 0; -- total nb of bytes output so far + msg : Chars_Ptr; -- last error message, NULL if no error + state : Voidp; -- not visible by applications + zalloc : alloc_func := null; -- used to allocate the internal state + zfree : free_func := null; -- used to free the internal state + opaque : Voidp; -- private data object passed to + -- zalloc and zfree + data_type : Int; -- best guess about the data type: + -- ascii or binary + adler : ULong; -- adler32 value of the uncompressed + -- data + reserved : ULong; -- reserved for future use + end record; + + pragma Convention (C, Z_Stream); + + pragma Import (C, zlibVersion, "zlibVersion"); + pragma Import (C, Deflate, "deflate"); + pragma Import (C, DeflateEnd, "deflateEnd"); + pragma Import (C, Inflate, "inflate"); + pragma Import (C, InflateEnd, "inflateEnd"); + pragma Import (C, deflateSetDictionary, "deflateSetDictionary"); + pragma Import (C, deflateCopy, "deflateCopy"); + pragma Import (C, deflateReset, "deflateReset"); + pragma Import (C, deflateParams, "deflateParams"); + pragma Import (C, inflateSetDictionary, "inflateSetDictionary"); + pragma Import (C, inflateSync, "inflateSync"); + pragma Import (C, inflateReset, "inflateReset"); + pragma Import (C, compress, "compress"); + pragma Import (C, compress2, "compress2"); + pragma Import (C, uncompress, "uncompress"); + pragma Import (C, gzopen, "gzopen"); + pragma Import (C, gzdopen, "gzdopen"); + pragma Import (C, gzsetparams, "gzsetparams"); + pragma Import (C, gzread, "gzread"); + pragma Import (C, gzwrite, "gzwrite"); + pragma Import (C, gzprintf, "gzprintf"); + pragma Import (C, gzputs, "gzputs"); + pragma Import (C, gzgets, "gzgets"); + pragma Import (C, gzputc, "gzputc"); + pragma Import (C, gzgetc, "gzgetc"); + pragma Import (C, gzflush, "gzflush"); + pragma Import (C, gzseek, "gzseek"); + pragma Import (C, gzrewind, "gzrewind"); + pragma Import (C, gztell, "gztell"); + pragma Import (C, gzeof, "gzeof"); + pragma Import (C, gzclose, "gzclose"); + pragma Import (C, gzerror, "gzerror"); + pragma Import (C, adler32, "adler32"); + pragma Import (C, crc32, "crc32"); + pragma Import (C, deflateInit, "deflateInit_"); + pragma Import (C, inflateInit, "inflateInit_"); + pragma Import (C, deflateInit2, "deflateInit2_"); + pragma Import (C, inflateInit2, "inflateInit2_"); + pragma Import (C, zError, "zError"); + pragma Import (C, inflateSyncPoint, "inflateSyncPoint"); + pragma Import (C, get_crc_table, "get_crc_table"); + + -- since zlib 1.2.0: + + pragma Import (C, inflateCopy, "inflateCopy"); + pragma Import (C, compressBound, "compressBound"); + pragma Import (C, deflateBound, "deflateBound"); + pragma Import (C, gzungetc, "gzungetc"); + pragma Import (C, zlibCompileFlags, "zlibCompileFlags"); + + pragma Import (C, inflateBackInit, "inflateBackInit_"); + + -- I stopped binding the inflateBack routines, because realize that + -- it does not support zlib and gzip headers for now, and have no + -- symmetric deflateBack routines. + -- ZLib-Ada is symmetric regarding deflate/inflate data transformation + -- and has a similar generic callback interface for the + -- deflate/inflate transformation based on the regular Deflate/Inflate + -- routines. + + -- pragma Import (C, inflateBack, "inflateBack"); + -- pragma Import (C, inflateBackEnd, "inflateBackEnd"); + +end ZLib.Thin; ADDED compat/zlib/contrib/ada/zlib.adb Index: compat/zlib/contrib/ada/zlib.adb ================================================================== --- /dev/null +++ compat/zlib/contrib/ada/zlib.adb @@ -0,0 +1,701 @@ +---------------------------------------------------------------- +-- ZLib for Ada thick binding. -- +-- -- +-- Copyright (C) 2002-2004 Dmitriy Anisimkov -- +-- -- +-- Open source license information is in the zlib.ads file. -- +---------------------------------------------------------------- + +-- $Id: zlib.adb,v 1.31 2004/09/06 06:53:19 vagul Exp $ + +with Ada.Exceptions; +with Ada.Unchecked_Conversion; +with Ada.Unchecked_Deallocation; + +with Interfaces.C.Strings; + +with ZLib.Thin; + +package body ZLib is + + use type Thin.Int; + + type Z_Stream is new Thin.Z_Stream; + + type Return_Code_Enum is + (OK, + STREAM_END, + NEED_DICT, + ERRNO, + STREAM_ERROR, + DATA_ERROR, + MEM_ERROR, + BUF_ERROR, + VERSION_ERROR); + + type Flate_Step_Function is access + function (Strm : in Thin.Z_Streamp; Flush : in Thin.Int) return Thin.Int; + pragma Convention (C, Flate_Step_Function); + + type Flate_End_Function is access + function (Ctrm : in Thin.Z_Streamp) return Thin.Int; + pragma Convention (C, Flate_End_Function); + + type Flate_Type is record + Step : Flate_Step_Function; + Done : Flate_End_Function; + end record; + + subtype Footer_Array is Stream_Element_Array (1 .. 8); + + Simple_GZip_Header : constant Stream_Element_Array (1 .. 10) + := (16#1f#, 16#8b#, -- Magic header + 16#08#, -- Z_DEFLATED + 16#00#, -- Flags + 16#00#, 16#00#, 16#00#, 16#00#, -- Time + 16#00#, -- XFlags + 16#03# -- OS code + ); + -- The simplest gzip header is not for informational, but just for + -- gzip format compatibility. + -- Note that some code below is using assumption + -- Simple_GZip_Header'Last > Footer_Array'Last, so do not make + -- Simple_GZip_Header'Last <= Footer_Array'Last. + + Return_Code : constant array (Thin.Int range <>) of Return_Code_Enum + := (0 => OK, + 1 => STREAM_END, + 2 => NEED_DICT, + -1 => ERRNO, + -2 => STREAM_ERROR, + -3 => DATA_ERROR, + -4 => MEM_ERROR, + -5 => BUF_ERROR, + -6 => VERSION_ERROR); + + Flate : constant array (Boolean) of Flate_Type + := (True => (Step => Thin.Deflate'Access, + Done => Thin.DeflateEnd'Access), + False => (Step => Thin.Inflate'Access, + Done => Thin.InflateEnd'Access)); + + Flush_Finish : constant array (Boolean) of Flush_Mode + := (True => Finish, False => No_Flush); + + procedure Raise_Error (Stream : in Z_Stream); + pragma Inline (Raise_Error); + + procedure Raise_Error (Message : in String); + pragma Inline (Raise_Error); + + procedure Check_Error (Stream : in Z_Stream; Code : in Thin.Int); + + procedure Free is new Ada.Unchecked_Deallocation + (Z_Stream, Z_Stream_Access); + + function To_Thin_Access is new Ada.Unchecked_Conversion + (Z_Stream_Access, Thin.Z_Streamp); + + procedure Translate_GZip + (Filter : in out Filter_Type; + In_Data : in Ada.Streams.Stream_Element_Array; + In_Last : out Ada.Streams.Stream_Element_Offset; + Out_Data : out Ada.Streams.Stream_Element_Array; + Out_Last : out Ada.Streams.Stream_Element_Offset; + Flush : in Flush_Mode); + -- Separate translate routine for make gzip header. + + procedure Translate_Auto + (Filter : in out Filter_Type; + In_Data : in Ada.Streams.Stream_Element_Array; + In_Last : out Ada.Streams.Stream_Element_Offset; + Out_Data : out Ada.Streams.Stream_Element_Array; + Out_Last : out Ada.Streams.Stream_Element_Offset; + Flush : in Flush_Mode); + -- translate routine without additional headers. + + ----------------- + -- Check_Error -- + ----------------- + + procedure Check_Error (Stream : in Z_Stream; Code : in Thin.Int) is + use type Thin.Int; + begin + if Code /= Thin.Z_OK then + Raise_Error + (Return_Code_Enum'Image (Return_Code (Code)) + & ": " & Last_Error_Message (Stream)); + end if; + end Check_Error; + + ----------- + -- Close -- + ----------- + + procedure Close + (Filter : in out Filter_Type; + Ignore_Error : in Boolean := False) + is + Code : Thin.Int; + begin + if not Ignore_Error and then not Is_Open (Filter) then + raise Status_Error; + end if; + + Code := Flate (Filter.Compression).Done (To_Thin_Access (Filter.Strm)); + + if Ignore_Error or else Code = Thin.Z_OK then + Free (Filter.Strm); + else + declare + Error_Message : constant String + := Last_Error_Message (Filter.Strm.all); + begin + Free (Filter.Strm); + Ada.Exceptions.Raise_Exception + (ZLib_Error'Identity, + Return_Code_Enum'Image (Return_Code (Code)) + & ": " & Error_Message); + end; + end if; + end Close; + + ----------- + -- CRC32 -- + ----------- + + function CRC32 + (CRC : in Unsigned_32; + Data : in Ada.Streams.Stream_Element_Array) + return Unsigned_32 + is + use Thin; + begin + return Unsigned_32 (crc32 (ULong (CRC), + Data'Address, + Data'Length)); + end CRC32; + + procedure CRC32 + (CRC : in out Unsigned_32; + Data : in Ada.Streams.Stream_Element_Array) is + begin + CRC := CRC32 (CRC, Data); + end CRC32; + + ------------------ + -- Deflate_Init -- + ------------------ + + procedure Deflate_Init + (Filter : in out Filter_Type; + Level : in Compression_Level := Default_Compression; + Strategy : in Strategy_Type := Default_Strategy; + Method : in Compression_Method := Deflated; + Window_Bits : in Window_Bits_Type := Default_Window_Bits; + Memory_Level : in Memory_Level_Type := Default_Memory_Level; + Header : in Header_Type := Default) + is + use type Thin.Int; + Win_Bits : Thin.Int := Thin.Int (Window_Bits); + begin + if Is_Open (Filter) then + raise Status_Error; + end if; + + -- We allow ZLib to make header only in case of default header type. + -- Otherwise we would either do header by ourselfs, or do not do + -- header at all. + + if Header = None or else Header = GZip then + Win_Bits := -Win_Bits; + end if; + + -- For the GZip CRC calculation and make headers. + + if Header = GZip then + Filter.CRC := 0; + Filter.Offset := Simple_GZip_Header'First; + else + Filter.Offset := Simple_GZip_Header'Last + 1; + end if; + + Filter.Strm := new Z_Stream; + Filter.Compression := True; + Filter.Stream_End := False; + Filter.Header := Header; + + if Thin.Deflate_Init + (To_Thin_Access (Filter.Strm), + Level => Thin.Int (Level), + method => Thin.Int (Method), + windowBits => Win_Bits, + memLevel => Thin.Int (Memory_Level), + strategy => Thin.Int (Strategy)) /= Thin.Z_OK + then + Raise_Error (Filter.Strm.all); + end if; + end Deflate_Init; + + ----------- + -- Flush -- + ----------- + + procedure Flush + (Filter : in out Filter_Type; + Out_Data : out Ada.Streams.Stream_Element_Array; + Out_Last : out Ada.Streams.Stream_Element_Offset; + Flush : in Flush_Mode) + is + No_Data : Stream_Element_Array := (1 .. 0 => 0); + Last : Stream_Element_Offset; + begin + Translate (Filter, No_Data, Last, Out_Data, Out_Last, Flush); + end Flush; + + ----------------------- + -- Generic_Translate -- + ----------------------- + + procedure Generic_Translate + (Filter : in out ZLib.Filter_Type; + In_Buffer_Size : in Integer := Default_Buffer_Size; + Out_Buffer_Size : in Integer := Default_Buffer_Size) + is + In_Buffer : Stream_Element_Array + (1 .. Stream_Element_Offset (In_Buffer_Size)); + Out_Buffer : Stream_Element_Array + (1 .. Stream_Element_Offset (Out_Buffer_Size)); + Last : Stream_Element_Offset; + In_Last : Stream_Element_Offset; + In_First : Stream_Element_Offset; + Out_Last : Stream_Element_Offset; + begin + Main : loop + Data_In (In_Buffer, Last); + + In_First := In_Buffer'First; + + loop + Translate + (Filter => Filter, + In_Data => In_Buffer (In_First .. Last), + In_Last => In_Last, + Out_Data => Out_Buffer, + Out_Last => Out_Last, + Flush => Flush_Finish (Last < In_Buffer'First)); + + if Out_Buffer'First <= Out_Last then + Data_Out (Out_Buffer (Out_Buffer'First .. Out_Last)); + end if; + + exit Main when Stream_End (Filter); + + -- The end of in buffer. + + exit when In_Last = Last; + + In_First := In_Last + 1; + end loop; + end loop Main; + + end Generic_Translate; + + ------------------ + -- Inflate_Init -- + ------------------ + + procedure Inflate_Init + (Filter : in out Filter_Type; + Window_Bits : in Window_Bits_Type := Default_Window_Bits; + Header : in Header_Type := Default) + is + use type Thin.Int; + Win_Bits : Thin.Int := Thin.Int (Window_Bits); + + procedure Check_Version; + -- Check the latest header types compatibility. + + procedure Check_Version is + begin + if Version <= "1.1.4" then + Raise_Error + ("Inflate header type " & Header_Type'Image (Header) + & " incompatible with ZLib version " & Version); + end if; + end Check_Version; + + begin + if Is_Open (Filter) then + raise Status_Error; + end if; + + case Header is + when None => + Check_Version; + + -- Inflate data without headers determined + -- by negative Win_Bits. + + Win_Bits := -Win_Bits; + when GZip => + Check_Version; + + -- Inflate gzip data defined by flag 16. + + Win_Bits := Win_Bits + 16; + when Auto => + Check_Version; + + -- Inflate with automatic detection + -- of gzip or native header defined by flag 32. + + Win_Bits := Win_Bits + 32; + when Default => null; + end case; + + Filter.Strm := new Z_Stream; + Filter.Compression := False; + Filter.Stream_End := False; + Filter.Header := Header; + + if Thin.Inflate_Init + (To_Thin_Access (Filter.Strm), Win_Bits) /= Thin.Z_OK + then + Raise_Error (Filter.Strm.all); + end if; + end Inflate_Init; + + ------------- + -- Is_Open -- + ------------- + + function Is_Open (Filter : in Filter_Type) return Boolean is + begin + return Filter.Strm /= null; + end Is_Open; + + ----------------- + -- Raise_Error -- + ----------------- + + procedure Raise_Error (Message : in String) is + begin + Ada.Exceptions.Raise_Exception (ZLib_Error'Identity, Message); + end Raise_Error; + + procedure Raise_Error (Stream : in Z_Stream) is + begin + Raise_Error (Last_Error_Message (Stream)); + end Raise_Error; + + ---------- + -- Read -- + ---------- + + procedure Read + (Filter : in out Filter_Type; + Item : out Ada.Streams.Stream_Element_Array; + Last : out Ada.Streams.Stream_Element_Offset; + Flush : in Flush_Mode := No_Flush) + is + In_Last : Stream_Element_Offset; + Item_First : Ada.Streams.Stream_Element_Offset := Item'First; + V_Flush : Flush_Mode := Flush; + + begin + pragma Assert (Rest_First in Buffer'First .. Buffer'Last + 1); + pragma Assert (Rest_Last in Buffer'First - 1 .. Buffer'Last); + + loop + if Rest_Last = Buffer'First - 1 then + V_Flush := Finish; + + elsif Rest_First > Rest_Last then + Read (Buffer, Rest_Last); + Rest_First := Buffer'First; + + if Rest_Last < Buffer'First then + V_Flush := Finish; + end if; + end if; + + Translate + (Filter => Filter, + In_Data => Buffer (Rest_First .. Rest_Last), + In_Last => In_Last, + Out_Data => Item (Item_First .. Item'Last), + Out_Last => Last, + Flush => V_Flush); + + Rest_First := In_Last + 1; + + exit when Stream_End (Filter) + or else Last = Item'Last + or else (Last >= Item'First and then Allow_Read_Some); + + Item_First := Last + 1; + end loop; + end Read; + + ---------------- + -- Stream_End -- + ---------------- + + function Stream_End (Filter : in Filter_Type) return Boolean is + begin + if Filter.Header = GZip and Filter.Compression then + return Filter.Stream_End + and then Filter.Offset = Footer_Array'Last + 1; + else + return Filter.Stream_End; + end if; + end Stream_End; + + -------------- + -- Total_In -- + -------------- + + function Total_In (Filter : in Filter_Type) return Count is + begin + return Count (Thin.Total_In (To_Thin_Access (Filter.Strm).all)); + end Total_In; + + --------------- + -- Total_Out -- + --------------- + + function Total_Out (Filter : in Filter_Type) return Count is + begin + return Count (Thin.Total_Out (To_Thin_Access (Filter.Strm).all)); + end Total_Out; + + --------------- + -- Translate -- + --------------- + + procedure Translate + (Filter : in out Filter_Type; + In_Data : in Ada.Streams.Stream_Element_Array; + In_Last : out Ada.Streams.Stream_Element_Offset; + Out_Data : out Ada.Streams.Stream_Element_Array; + Out_Last : out Ada.Streams.Stream_Element_Offset; + Flush : in Flush_Mode) is + begin + if Filter.Header = GZip and then Filter.Compression then + Translate_GZip + (Filter => Filter, + In_Data => In_Data, + In_Last => In_Last, + Out_Data => Out_Data, + Out_Last => Out_Last, + Flush => Flush); + else + Translate_Auto + (Filter => Filter, + In_Data => In_Data, + In_Last => In_Last, + Out_Data => Out_Data, + Out_Last => Out_Last, + Flush => Flush); + end if; + end Translate; + + -------------------- + -- Translate_Auto -- + -------------------- + + procedure Translate_Auto + (Filter : in out Filter_Type; + In_Data : in Ada.Streams.Stream_Element_Array; + In_Last : out Ada.Streams.Stream_Element_Offset; + Out_Data : out Ada.Streams.Stream_Element_Array; + Out_Last : out Ada.Streams.Stream_Element_Offset; + Flush : in Flush_Mode) + is + use type Thin.Int; + Code : Thin.Int; + + begin + if not Is_Open (Filter) then + raise Status_Error; + end if; + + if Out_Data'Length = 0 and then In_Data'Length = 0 then + raise Constraint_Error; + end if; + + Set_Out (Filter.Strm.all, Out_Data'Address, Out_Data'Length); + Set_In (Filter.Strm.all, In_Data'Address, In_Data'Length); + + Code := Flate (Filter.Compression).Step + (To_Thin_Access (Filter.Strm), + Thin.Int (Flush)); + + if Code = Thin.Z_STREAM_END then + Filter.Stream_End := True; + else + Check_Error (Filter.Strm.all, Code); + end if; + + In_Last := In_Data'Last + - Stream_Element_Offset (Avail_In (Filter.Strm.all)); + Out_Last := Out_Data'Last + - Stream_Element_Offset (Avail_Out (Filter.Strm.all)); + end Translate_Auto; + + -------------------- + -- Translate_GZip -- + -------------------- + + procedure Translate_GZip + (Filter : in out Filter_Type; + In_Data : in Ada.Streams.Stream_Element_Array; + In_Last : out Ada.Streams.Stream_Element_Offset; + Out_Data : out Ada.Streams.Stream_Element_Array; + Out_Last : out Ada.Streams.Stream_Element_Offset; + Flush : in Flush_Mode) + is + Out_First : Stream_Element_Offset; + + procedure Add_Data (Data : in Stream_Element_Array); + -- Add data to stream from the Filter.Offset till necessary, + -- used for add gzip headr/footer. + + procedure Put_32 + (Item : in out Stream_Element_Array; + Data : in Unsigned_32); + pragma Inline (Put_32); + + -------------- + -- Add_Data -- + -------------- + + procedure Add_Data (Data : in Stream_Element_Array) is + Data_First : Stream_Element_Offset renames Filter.Offset; + Data_Last : Stream_Element_Offset; + Data_Len : Stream_Element_Offset; -- -1 + Out_Len : Stream_Element_Offset; -- -1 + begin + Out_First := Out_Last + 1; + + if Data_First > Data'Last then + return; + end if; + + Data_Len := Data'Last - Data_First; + Out_Len := Out_Data'Last - Out_First; + + if Data_Len <= Out_Len then + Out_Last := Out_First + Data_Len; + Data_Last := Data'Last; + else + Out_Last := Out_Data'Last; + Data_Last := Data_First + Out_Len; + end if; + + Out_Data (Out_First .. Out_Last) := Data (Data_First .. Data_Last); + + Data_First := Data_Last + 1; + Out_First := Out_Last + 1; + end Add_Data; + + ------------ + -- Put_32 -- + ------------ + + procedure Put_32 + (Item : in out Stream_Element_Array; + Data : in Unsigned_32) + is + D : Unsigned_32 := Data; + begin + for J in Item'First .. Item'First + 3 loop + Item (J) := Stream_Element (D and 16#FF#); + D := Shift_Right (D, 8); + end loop; + end Put_32; + + begin + Out_Last := Out_Data'First - 1; + + if not Filter.Stream_End then + Add_Data (Simple_GZip_Header); + + Translate_Auto + (Filter => Filter, + In_Data => In_Data, + In_Last => In_Last, + Out_Data => Out_Data (Out_First .. Out_Data'Last), + Out_Last => Out_Last, + Flush => Flush); + + CRC32 (Filter.CRC, In_Data (In_Data'First .. In_Last)); + end if; + + if Filter.Stream_End and then Out_Last <= Out_Data'Last then + -- This detection method would work only when + -- Simple_GZip_Header'Last > Footer_Array'Last + + if Filter.Offset = Simple_GZip_Header'Last + 1 then + Filter.Offset := Footer_Array'First; + end if; + + declare + Footer : Footer_Array; + begin + Put_32 (Footer, Filter.CRC); + Put_32 (Footer (Footer'First + 4 .. Footer'Last), + Unsigned_32 (Total_In (Filter))); + Add_Data (Footer); + end; + end if; + end Translate_GZip; + + ------------- + -- Version -- + ------------- + + function Version return String is + begin + return Interfaces.C.Strings.Value (Thin.zlibVersion); + end Version; + + ----------- + -- Write -- + ----------- + + procedure Write + (Filter : in out Filter_Type; + Item : in Ada.Streams.Stream_Element_Array; + Flush : in Flush_Mode := No_Flush) + is + Buffer : Stream_Element_Array (1 .. Buffer_Size); + In_Last : Stream_Element_Offset; + Out_Last : Stream_Element_Offset; + In_First : Stream_Element_Offset := Item'First; + begin + if Item'Length = 0 and Flush = No_Flush then + return; + end if; + + loop + Translate + (Filter => Filter, + In_Data => Item (In_First .. Item'Last), + In_Last => In_Last, + Out_Data => Buffer, + Out_Last => Out_Last, + Flush => Flush); + + if Out_Last >= Buffer'First then + Write (Buffer (1 .. Out_Last)); + end if; + + exit when In_Last = Item'Last or Stream_End (Filter); + + In_First := In_Last + 1; + end loop; + end Write; + +end ZLib; ADDED compat/zlib/contrib/ada/zlib.ads Index: compat/zlib/contrib/ada/zlib.ads ================================================================== --- /dev/null +++ compat/zlib/contrib/ada/zlib.ads @@ -0,0 +1,328 @@ +------------------------------------------------------------------------------ +-- ZLib for Ada thick binding. -- +-- -- +-- Copyright (C) 2002-2004 Dmitriy Anisimkov -- +-- -- +-- This library is free software; you can redistribute it and/or modify -- +-- it under the terms of the GNU General Public License as published by -- +-- the Free Software Foundation; either version 2 of the License, or (at -- +-- your option) any later version. -- +-- -- +-- This library 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. See the GNU -- +-- General Public License for more details. -- +-- -- +-- You should have received a copy of the GNU General Public License -- +-- along with this library; if not, write to the Free Software Foundation, -- +-- Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. -- +-- -- +-- As a special exception, if other files instantiate generics from this -- +-- unit, or you link this unit with other files to produce an executable, -- +-- this unit does not by itself cause the resulting executable to be -- +-- covered by the GNU General Public License. This exception does not -- +-- however invalidate any other reasons why the executable file might be -- +-- covered by the GNU Public License. -- +------------------------------------------------------------------------------ + +-- $Id: zlib.ads,v 1.26 2004/09/06 06:53:19 vagul Exp $ + +with Ada.Streams; + +with Interfaces; + +package ZLib is + + ZLib_Error : exception; + Status_Error : exception; + + type Compression_Level is new Integer range -1 .. 9; + + type Flush_Mode is private; + + type Compression_Method is private; + + type Window_Bits_Type is new Integer range 8 .. 15; + + type Memory_Level_Type is new Integer range 1 .. 9; + + type Unsigned_32 is new Interfaces.Unsigned_32; + + type Strategy_Type is private; + + type Header_Type is (None, Auto, Default, GZip); + -- Header type usage have a some limitation for inflate. + -- See comment for Inflate_Init. + + subtype Count is Ada.Streams.Stream_Element_Count; + + Default_Memory_Level : constant Memory_Level_Type := 8; + Default_Window_Bits : constant Window_Bits_Type := 15; + + ---------------------------------- + -- Compression method constants -- + ---------------------------------- + + Deflated : constant Compression_Method; + -- Only one method allowed in this ZLib version + + --------------------------------- + -- Compression level constants -- + --------------------------------- + + No_Compression : constant Compression_Level := 0; + Best_Speed : constant Compression_Level := 1; + Best_Compression : constant Compression_Level := 9; + Default_Compression : constant Compression_Level := -1; + + -------------------------- + -- Flush mode constants -- + -------------------------- + + No_Flush : constant Flush_Mode; + -- Regular way for compression, no flush + + Partial_Flush : constant Flush_Mode; + -- Will be removed, use Z_SYNC_FLUSH instead + + Sync_Flush : constant Flush_Mode; + -- All pending output is flushed to the output buffer and the output + -- is aligned on a byte boundary, so that the decompressor can get all + -- input data available so far. (In particular avail_in is zero after the + -- call if enough output space has been provided before the call.) + -- Flushing may degrade compression for some compression algorithms and so + -- it should be used only when necessary. + + Block_Flush : constant Flush_Mode; + -- Z_BLOCK requests that inflate() stop + -- if and when it get to the next deflate block boundary. When decoding the + -- zlib or gzip format, this will cause inflate() to return immediately + -- after the header and before the first block. When doing a raw inflate, + -- inflate() will go ahead and process the first block, and will return + -- when it gets to the end of that block, or when it runs out of data. + + Full_Flush : constant Flush_Mode; + -- All output is flushed as with SYNC_FLUSH, and the compression state + -- is reset so that decompression can restart from this point if previous + -- compressed data has been damaged or if random access is desired. Using + -- Full_Flush too often can seriously degrade the compression. + + Finish : constant Flush_Mode; + -- Just for tell the compressor that input data is complete. + + ------------------------------------ + -- Compression strategy constants -- + ------------------------------------ + + -- RLE stategy could be used only in version 1.2.0 and later. + + Filtered : constant Strategy_Type; + Huffman_Only : constant Strategy_Type; + RLE : constant Strategy_Type; + Default_Strategy : constant Strategy_Type; + + Default_Buffer_Size : constant := 4096; + + type Filter_Type is tagged limited private; + -- The filter is for compression and for decompression. + -- The usage of the type is depend of its initialization. + + function Version return String; + pragma Inline (Version); + -- Return string representation of the ZLib version. + + procedure Deflate_Init + (Filter : in out Filter_Type; + Level : in Compression_Level := Default_Compression; + Strategy : in Strategy_Type := Default_Strategy; + Method : in Compression_Method := Deflated; + Window_Bits : in Window_Bits_Type := Default_Window_Bits; + Memory_Level : in Memory_Level_Type := Default_Memory_Level; + Header : in Header_Type := Default); + -- Compressor initialization. + -- When Header parameter is Auto or Default, then default zlib header + -- would be provided for compressed data. + -- When Header is GZip, then gzip header would be set instead of + -- default header. + -- When Header is None, no header would be set for compressed data. + + procedure Inflate_Init + (Filter : in out Filter_Type; + Window_Bits : in Window_Bits_Type := Default_Window_Bits; + Header : in Header_Type := Default); + -- Decompressor initialization. + -- Default header type mean that ZLib default header is expecting in the + -- input compressed stream. + -- Header type None mean that no header is expecting in the input stream. + -- GZip header type mean that GZip header is expecting in the + -- input compressed stream. + -- Auto header type mean that header type (GZip or Native) would be + -- detected automatically in the input stream. + -- Note that header types parameter values None, GZip and Auto are + -- supported for inflate routine only in ZLib versions 1.2.0.2 and later. + -- Deflate_Init is supporting all header types. + + function Is_Open (Filter : in Filter_Type) return Boolean; + pragma Inline (Is_Open); + -- Is the filter opened for compression or decompression. + + procedure Close + (Filter : in out Filter_Type; + Ignore_Error : in Boolean := False); + -- Closing the compression or decompressor. + -- If stream is closing before the complete and Ignore_Error is False, + -- The exception would be raised. + + generic + with procedure Data_In + (Item : out Ada.Streams.Stream_Element_Array; + Last : out Ada.Streams.Stream_Element_Offset); + with procedure Data_Out + (Item : in Ada.Streams.Stream_Element_Array); + procedure Generic_Translate + (Filter : in out Filter_Type; + In_Buffer_Size : in Integer := Default_Buffer_Size; + Out_Buffer_Size : in Integer := Default_Buffer_Size); + -- Compress/decompress data fetch from Data_In routine and pass the result + -- to the Data_Out routine. User should provide Data_In and Data_Out + -- for compression/decompression data flow. + -- Compression or decompression depend on Filter initialization. + + function Total_In (Filter : in Filter_Type) return Count; + pragma Inline (Total_In); + -- Returns total number of input bytes read so far + + function Total_Out (Filter : in Filter_Type) return Count; + pragma Inline (Total_Out); + -- Returns total number of bytes output so far + + function CRC32 + (CRC : in Unsigned_32; + Data : in Ada.Streams.Stream_Element_Array) + return Unsigned_32; + pragma Inline (CRC32); + -- Compute CRC32, it could be necessary for make gzip format + + procedure CRC32 + (CRC : in out Unsigned_32; + Data : in Ada.Streams.Stream_Element_Array); + pragma Inline (CRC32); + -- Compute CRC32, it could be necessary for make gzip format + + ------------------------------------------------- + -- Below is more complex low level routines. -- + ------------------------------------------------- + + procedure Translate + (Filter : in out Filter_Type; + In_Data : in Ada.Streams.Stream_Element_Array; + In_Last : out Ada.Streams.Stream_Element_Offset; + Out_Data : out Ada.Streams.Stream_Element_Array; + Out_Last : out Ada.Streams.Stream_Element_Offset; + Flush : in Flush_Mode); + -- Compress/decompress the In_Data buffer and place the result into + -- Out_Data. In_Last is the index of last element from In_Data accepted by + -- the Filter. Out_Last is the last element of the received data from + -- Filter. To tell the filter that incoming data are complete put the + -- Flush parameter to Finish. + + function Stream_End (Filter : in Filter_Type) return Boolean; + pragma Inline (Stream_End); + -- Return the true when the stream is complete. + + procedure Flush + (Filter : in out Filter_Type; + Out_Data : out Ada.Streams.Stream_Element_Array; + Out_Last : out Ada.Streams.Stream_Element_Offset; + Flush : in Flush_Mode); + pragma Inline (Flush); + -- Flushing the data from the compressor. + + generic + with procedure Write + (Item : in Ada.Streams.Stream_Element_Array); + -- User should provide this routine for accept + -- compressed/decompressed data. + + Buffer_Size : in Ada.Streams.Stream_Element_Offset + := Default_Buffer_Size; + -- Buffer size for Write user routine. + + procedure Write + (Filter : in out Filter_Type; + Item : in Ada.Streams.Stream_Element_Array; + Flush : in Flush_Mode := No_Flush); + -- Compress/Decompress data from Item to the generic parameter procedure + -- Write. Output buffer size could be set in Buffer_Size generic parameter. + + generic + with procedure Read + (Item : out Ada.Streams.Stream_Element_Array; + Last : out Ada.Streams.Stream_Element_Offset); + -- User should provide data for compression/decompression + -- thru this routine. + + Buffer : in out Ada.Streams.Stream_Element_Array; + -- Buffer for keep remaining data from the previous + -- back read. + + Rest_First, Rest_Last : in out Ada.Streams.Stream_Element_Offset; + -- Rest_First have to be initialized to Buffer'Last + 1 + -- Rest_Last have to be initialized to Buffer'Last + -- before usage. + + Allow_Read_Some : in Boolean := False; + -- Is it allowed to return Last < Item'Last before end of data. + + procedure Read + (Filter : in out Filter_Type; + Item : out Ada.Streams.Stream_Element_Array; + Last : out Ada.Streams.Stream_Element_Offset; + Flush : in Flush_Mode := No_Flush); + -- Compress/Decompress data from generic parameter procedure Read to the + -- Item. User should provide Buffer and initialized Rest_First, Rest_Last + -- indicators. If Allow_Read_Some is True, Read routines could return + -- Last < Item'Last only at end of stream. + +private + + use Ada.Streams; + + pragma Assert (Ada.Streams.Stream_Element'Size = 8); + pragma Assert (Ada.Streams.Stream_Element'Modulus = 2**8); + + type Flush_Mode is new Integer range 0 .. 5; + + type Compression_Method is new Integer range 8 .. 8; + + type Strategy_Type is new Integer range 0 .. 3; + + No_Flush : constant Flush_Mode := 0; + Partial_Flush : constant Flush_Mode := 1; + Sync_Flush : constant Flush_Mode := 2; + Full_Flush : constant Flush_Mode := 3; + Finish : constant Flush_Mode := 4; + Block_Flush : constant Flush_Mode := 5; + + Filtered : constant Strategy_Type := 1; + Huffman_Only : constant Strategy_Type := 2; + RLE : constant Strategy_Type := 3; + Default_Strategy : constant Strategy_Type := 0; + + Deflated : constant Compression_Method := 8; + + type Z_Stream; + + type Z_Stream_Access is access all Z_Stream; + + type Filter_Type is tagged limited record + Strm : Z_Stream_Access; + Compression : Boolean; + Stream_End : Boolean; + Header : Header_Type; + CRC : Unsigned_32; + Offset : Stream_Element_Offset; + -- Offset for gzip header/footer output. + end record; + +end ZLib; ADDED compat/zlib/contrib/ada/zlib.gpr Index: compat/zlib/contrib/ada/zlib.gpr ================================================================== --- /dev/null +++ compat/zlib/contrib/ada/zlib.gpr @@ -0,0 +1,20 @@ +project Zlib is + + for Languages use ("Ada"); + for Source_Dirs use ("."); + for Object_Dir use "."; + for Main use ("test.adb", "mtest.adb", "read.adb", "buffer_demo"); + + package Compiler is + for Default_Switches ("ada") use ("-gnatwcfilopru", "-gnatVcdfimorst", "-gnatyabcefhiklmnoprst"); + end Compiler; + + package Linker is + for Default_Switches ("ada") use ("-lz"); + end Linker; + + package Builder is + for Default_Switches ("ada") use ("-s", "-gnatQ"); + end Builder; + +end Zlib; ADDED compat/zlib/contrib/amd64/amd64-match.S Index: compat/zlib/contrib/amd64/amd64-match.S ================================================================== --- /dev/null +++ compat/zlib/contrib/amd64/amd64-match.S @@ -0,0 +1,452 @@ +/* + * match.S -- optimized version of longest_match() + * based on the similar work by Gilles Vollant, and Brian Raiter, written 1998 + * + * This is free software; you can redistribute it and/or modify it + * under the terms of the BSD License. Use by owners of Che Guevarra + * parafernalia is prohibited, where possible, and highly discouraged + * elsewhere. + */ + +#ifndef NO_UNDERLINE +# define match_init _match_init +# define longest_match _longest_match +#endif + +#define scanend ebx +#define scanendw bx +#define chainlenwmask edx /* high word: current chain len low word: s->wmask */ +#define curmatch rsi +#define curmatchd esi +#define windowbestlen r8 +#define scanalign r9 +#define scanalignd r9d +#define window r10 +#define bestlen r11 +#define bestlend r11d +#define scanstart r12d +#define scanstartw r12w +#define scan r13 +#define nicematch r14d +#define limit r15 +#define limitd r15d +#define prev rcx + +/* + * The 258 is a "magic number, not a parameter -- changing it + * breaks the hell loose + */ +#define MAX_MATCH (258) +#define MIN_MATCH (3) +#define MIN_LOOKAHEAD (MAX_MATCH + MIN_MATCH + 1) +#define MAX_MATCH_8 ((MAX_MATCH + 7) & ~7) + +/* stack frame offsets */ +#define LocalVarsSize (112) +#define _chainlenwmask ( 8-LocalVarsSize)(%rsp) +#define _windowbestlen (16-LocalVarsSize)(%rsp) +#define save_r14 (24-LocalVarsSize)(%rsp) +#define save_rsi (32-LocalVarsSize)(%rsp) +#define save_rbx (40-LocalVarsSize)(%rsp) +#define save_r12 (56-LocalVarsSize)(%rsp) +#define save_r13 (64-LocalVarsSize)(%rsp) +#define save_r15 (80-LocalVarsSize)(%rsp) + + +.globl match_init, longest_match + +/* + * On AMD64 the first argument of a function (in our case -- the pointer to + * deflate_state structure) is passed in %rdi, hence our offsets below are + * all off of that. + */ + +/* you can check the structure offset by running + +#include +#include +#include "deflate.h" + +void print_depl() +{ +deflate_state ds; +deflate_state *s=&ds; +printf("size pointer=%u\n",(int)sizeof(void*)); + +printf("#define dsWSize (%3u)(%%rdi)\n",(int)(((char*)&(s->w_size))-((char*)s))); +printf("#define dsWMask (%3u)(%%rdi)\n",(int)(((char*)&(s->w_mask))-((char*)s))); +printf("#define dsWindow (%3u)(%%rdi)\n",(int)(((char*)&(s->window))-((char*)s))); +printf("#define dsPrev (%3u)(%%rdi)\n",(int)(((char*)&(s->prev))-((char*)s))); +printf("#define dsMatchLen (%3u)(%%rdi)\n",(int)(((char*)&(s->match_length))-((char*)s))); +printf("#define dsPrevMatch (%3u)(%%rdi)\n",(int)(((char*)&(s->prev_match))-((char*)s))); +printf("#define dsStrStart (%3u)(%%rdi)\n",(int)(((char*)&(s->strstart))-((char*)s))); +printf("#define dsMatchStart (%3u)(%%rdi)\n",(int)(((char*)&(s->match_start))-((char*)s))); +printf("#define dsLookahead (%3u)(%%rdi)\n",(int)(((char*)&(s->lookahead))-((char*)s))); +printf("#define dsPrevLen (%3u)(%%rdi)\n",(int)(((char*)&(s->prev_length))-((char*)s))); +printf("#define dsMaxChainLen (%3u)(%%rdi)\n",(int)(((char*)&(s->max_chain_length))-((char*)s))); +printf("#define dsGoodMatch (%3u)(%%rdi)\n",(int)(((char*)&(s->good_match))-((char*)s))); +printf("#define dsNiceMatch (%3u)(%%rdi)\n",(int)(((char*)&(s->nice_match))-((char*)s))); +} + +*/ + + +/* + to compile for XCode 3.2 on MacOSX x86_64 + - run "gcc -g -c -DXCODE_MAC_X64_STRUCTURE amd64-match.S" + */ + + +#ifndef CURRENT_LINX_XCODE_MAC_X64_STRUCTURE +#define dsWSize ( 68)(%rdi) +#define dsWMask ( 76)(%rdi) +#define dsWindow ( 80)(%rdi) +#define dsPrev ( 96)(%rdi) +#define dsMatchLen (144)(%rdi) +#define dsPrevMatch (148)(%rdi) +#define dsStrStart (156)(%rdi) +#define dsMatchStart (160)(%rdi) +#define dsLookahead (164)(%rdi) +#define dsPrevLen (168)(%rdi) +#define dsMaxChainLen (172)(%rdi) +#define dsGoodMatch (188)(%rdi) +#define dsNiceMatch (192)(%rdi) + +#else + +#ifndef STRUCT_OFFSET +# define STRUCT_OFFSET (0) +#endif + + +#define dsWSize ( 56 + STRUCT_OFFSET)(%rdi) +#define dsWMask ( 64 + STRUCT_OFFSET)(%rdi) +#define dsWindow ( 72 + STRUCT_OFFSET)(%rdi) +#define dsPrev ( 88 + STRUCT_OFFSET)(%rdi) +#define dsMatchLen (136 + STRUCT_OFFSET)(%rdi) +#define dsPrevMatch (140 + STRUCT_OFFSET)(%rdi) +#define dsStrStart (148 + STRUCT_OFFSET)(%rdi) +#define dsMatchStart (152 + STRUCT_OFFSET)(%rdi) +#define dsLookahead (156 + STRUCT_OFFSET)(%rdi) +#define dsPrevLen (160 + STRUCT_OFFSET)(%rdi) +#define dsMaxChainLen (164 + STRUCT_OFFSET)(%rdi) +#define dsGoodMatch (180 + STRUCT_OFFSET)(%rdi) +#define dsNiceMatch (184 + STRUCT_OFFSET)(%rdi) + +#endif + + + + +.text + +/* uInt longest_match(deflate_state *deflatestate, IPos curmatch) */ + +longest_match: +/* + * Retrieve the function arguments. %curmatch will hold cur_match + * throughout the entire function (passed via rsi on amd64). + * rdi will hold the pointer to the deflate_state (first arg on amd64) + */ + mov %rsi, save_rsi + mov %rbx, save_rbx + mov %r12, save_r12 + mov %r13, save_r13 + mov %r14, save_r14 + mov %r15, save_r15 + +/* uInt wmask = s->w_mask; */ +/* unsigned chain_length = s->max_chain_length; */ +/* if (s->prev_length >= s->good_match) { */ +/* chain_length >>= 2; */ +/* } */ + + movl dsPrevLen, %eax + movl dsGoodMatch, %ebx + cmpl %ebx, %eax + movl dsWMask, %eax + movl dsMaxChainLen, %chainlenwmask + jl LastMatchGood + shrl $2, %chainlenwmask +LastMatchGood: + +/* chainlen is decremented once beforehand so that the function can */ +/* use the sign flag instead of the zero flag for the exit test. */ +/* It is then shifted into the high word, to make room for the wmask */ +/* value, which it will always accompany. */ + + decl %chainlenwmask + shll $16, %chainlenwmask + orl %eax, %chainlenwmask + +/* if ((uInt)nice_match > s->lookahead) nice_match = s->lookahead; */ + + movl dsNiceMatch, %eax + movl dsLookahead, %ebx + cmpl %eax, %ebx + jl LookaheadLess + movl %eax, %ebx +LookaheadLess: movl %ebx, %nicematch + +/* register Bytef *scan = s->window + s->strstart; */ + + mov dsWindow, %window + movl dsStrStart, %limitd + lea (%limit, %window), %scan + +/* Determine how many bytes the scan ptr is off from being */ +/* dword-aligned. */ + + mov %scan, %scanalign + negl %scanalignd + andl $3, %scanalignd + +/* IPos limit = s->strstart > (IPos)MAX_DIST(s) ? */ +/* s->strstart - (IPos)MAX_DIST(s) : NIL; */ + + movl dsWSize, %eax + subl $MIN_LOOKAHEAD, %eax + xorl %ecx, %ecx + subl %eax, %limitd + cmovng %ecx, %limitd + +/* int best_len = s->prev_length; */ + + movl dsPrevLen, %bestlend + +/* Store the sum of s->window + best_len in %windowbestlen locally, and in memory. */ + + lea (%window, %bestlen), %windowbestlen + mov %windowbestlen, _windowbestlen + +/* register ush scan_start = *(ushf*)scan; */ +/* register ush scan_end = *(ushf*)(scan+best_len-1); */ +/* Posf *prev = s->prev; */ + + movzwl (%scan), %scanstart + movzwl -1(%scan, %bestlen), %scanend + mov dsPrev, %prev + +/* Jump into the main loop. */ + + movl %chainlenwmask, _chainlenwmask + jmp LoopEntry + +.balign 16 + +/* do { + * match = s->window + cur_match; + * if (*(ushf*)(match+best_len-1) != scan_end || + * *(ushf*)match != scan_start) continue; + * [...] + * } while ((cur_match = prev[cur_match & wmask]) > limit + * && --chain_length != 0); + * + * Here is the inner loop of the function. The function will spend the + * majority of its time in this loop, and majority of that time will + * be spent in the first ten instructions. + */ +LookupLoop: + andl %chainlenwmask, %curmatchd + movzwl (%prev, %curmatch, 2), %curmatchd + cmpl %limitd, %curmatchd + jbe LeaveNow + subl $0x00010000, %chainlenwmask + js LeaveNow +LoopEntry: cmpw -1(%windowbestlen, %curmatch), %scanendw + jne LookupLoop + cmpw %scanstartw, (%window, %curmatch) + jne LookupLoop + +/* Store the current value of chainlen. */ + movl %chainlenwmask, _chainlenwmask + +/* %scan is the string under scrutiny, and %prev to the string we */ +/* are hoping to match it up with. In actuality, %esi and %edi are */ +/* both pointed (MAX_MATCH_8 - scanalign) bytes ahead, and %edx is */ +/* initialized to -(MAX_MATCH_8 - scanalign). */ + + mov $(-MAX_MATCH_8), %rdx + lea (%curmatch, %window), %windowbestlen + lea MAX_MATCH_8(%windowbestlen, %scanalign), %windowbestlen + lea MAX_MATCH_8(%scan, %scanalign), %prev + +/* the prefetching below makes very little difference... */ + prefetcht1 (%windowbestlen, %rdx) + prefetcht1 (%prev, %rdx) + +/* + * Test the strings for equality, 8 bytes at a time. At the end, + * adjust %rdx so that it is offset to the exact byte that mismatched. + * + * It should be confessed that this loop usually does not represent + * much of the total running time. Replacing it with a more + * straightforward "rep cmpsb" would not drastically degrade + * performance -- unrolling it, for example, makes no difference. + */ + +#undef USE_SSE /* works, but is 6-7% slower, than non-SSE... */ + +LoopCmps: +#ifdef USE_SSE + /* Preload the SSE registers */ + movdqu (%windowbestlen, %rdx), %xmm1 + movdqu (%prev, %rdx), %xmm2 + pcmpeqb %xmm2, %xmm1 + movdqu 16(%windowbestlen, %rdx), %xmm3 + movdqu 16(%prev, %rdx), %xmm4 + pcmpeqb %xmm4, %xmm3 + movdqu 32(%windowbestlen, %rdx), %xmm5 + movdqu 32(%prev, %rdx), %xmm6 + pcmpeqb %xmm6, %xmm5 + movdqu 48(%windowbestlen, %rdx), %xmm7 + movdqu 48(%prev, %rdx), %xmm8 + pcmpeqb %xmm8, %xmm7 + + /* Check the comparisions' results */ + pmovmskb %xmm1, %rax + notw %ax + bsfw %ax, %ax + jnz LeaveLoopCmps + + /* this is the only iteration of the loop with a possibility of having + incremented rdx by 0x108 (each loop iteration add 16*4 = 0x40 + and (0x40*4)+8=0x108 */ + add $8, %rdx + jz LenMaximum + add $8, %rdx + + + pmovmskb %xmm3, %rax + notw %ax + bsfw %ax, %ax + jnz LeaveLoopCmps + + + add $16, %rdx + + + pmovmskb %xmm5, %rax + notw %ax + bsfw %ax, %ax + jnz LeaveLoopCmps + + add $16, %rdx + + + pmovmskb %xmm7, %rax + notw %ax + bsfw %ax, %ax + jnz LeaveLoopCmps + + add $16, %rdx + + jmp LoopCmps +LeaveLoopCmps: add %rax, %rdx +#else + mov (%windowbestlen, %rdx), %rax + xor (%prev, %rdx), %rax + jnz LeaveLoopCmps + + mov 8(%windowbestlen, %rdx), %rax + xor 8(%prev, %rdx), %rax + jnz LeaveLoopCmps8 + + mov 16(%windowbestlen, %rdx), %rax + xor 16(%prev, %rdx), %rax + jnz LeaveLoopCmps16 + + add $24, %rdx + jnz LoopCmps + jmp LenMaximum +# if 0 +/* + * This three-liner is tantalizingly simple, but bsf is a slow instruction, + * and the complicated alternative down below is quite a bit faster. Sad... + */ + +LeaveLoopCmps: bsf %rax, %rax /* find the first non-zero bit */ + shrl $3, %eax /* divide by 8 to get the byte */ + add %rax, %rdx +# else +LeaveLoopCmps16: + add $8, %rdx +LeaveLoopCmps8: + add $8, %rdx +LeaveLoopCmps: testl $0xFFFFFFFF, %eax /* Check the first 4 bytes */ + jnz Check16 + add $4, %rdx + shr $32, %rax +Check16: testw $0xFFFF, %ax + jnz LenLower + add $2, %rdx + shrl $16, %eax +LenLower: subb $1, %al + adc $0, %rdx +# endif +#endif + +/* Calculate the length of the match. If it is longer than MAX_MATCH, */ +/* then automatically accept it as the best possible match and leave. */ + + lea (%prev, %rdx), %rax + sub %scan, %rax + cmpl $MAX_MATCH, %eax + jge LenMaximum + +/* If the length of the match is not longer than the best match we */ +/* have so far, then forget it and return to the lookup loop. */ + + cmpl %bestlend, %eax + jg LongerMatch + mov _windowbestlen, %windowbestlen + mov dsPrev, %prev + movl _chainlenwmask, %edx + jmp LookupLoop + +/* s->match_start = cur_match; */ +/* best_len = len; */ +/* if (len >= nice_match) break; */ +/* scan_end = *(ushf*)(scan+best_len-1); */ + +LongerMatch: + movl %eax, %bestlend + movl %curmatchd, dsMatchStart + cmpl %nicematch, %eax + jge LeaveNow + + lea (%window, %bestlen), %windowbestlen + mov %windowbestlen, _windowbestlen + + movzwl -1(%scan, %rax), %scanend + mov dsPrev, %prev + movl _chainlenwmask, %chainlenwmask + jmp LookupLoop + +/* Accept the current string, with the maximum possible length. */ + +LenMaximum: + movl $MAX_MATCH, %bestlend + movl %curmatchd, dsMatchStart + +/* if ((uInt)best_len <= s->lookahead) return (uInt)best_len; */ +/* return s->lookahead; */ + +LeaveNow: + movl dsLookahead, %eax + cmpl %eax, %bestlend + cmovngl %bestlend, %eax +LookaheadRet: + +/* Restore the registers and return from whence we came. */ + + mov save_rsi, %rsi + mov save_rbx, %rbx + mov save_r12, %r12 + mov save_r13, %r13 + mov save_r14, %r14 + mov save_r15, %r15 + + ret + +match_init: ret ADDED compat/zlib/contrib/asm686/README.686 Index: compat/zlib/contrib/asm686/README.686 ================================================================== --- /dev/null +++ compat/zlib/contrib/asm686/README.686 @@ -0,0 +1,51 @@ +This is a patched version of zlib, modified to use +Pentium-Pro-optimized assembly code in the deflation algorithm. The +files changed/added by this patch are: + +README.686 +match.S + +The speedup that this patch provides varies, depending on whether the +compiler used to build the original version of zlib falls afoul of the +PPro's speed traps. My own tests show a speedup of around 10-20% at +the default compression level, and 20-30% using -9, against a version +compiled using gcc 2.7.2.3. Your mileage may vary. + +Note that this code has been tailored for the PPro/PII in particular, +and will not perform particuarly well on a Pentium. + +If you are using an assembler other than GNU as, you will have to +translate match.S to use your assembler's syntax. (Have fun.) + +Brian Raiter +breadbox@muppetlabs.com +April, 1998 + + +Added for zlib 1.1.3: + +The patches come from +http://www.muppetlabs.com/~breadbox/software/assembly.html + +To compile zlib with this asm file, copy match.S to the zlib directory +then do: + +CFLAGS="-O3 -DASMV" ./configure +make OBJA=match.o + + +Update: + +I've been ignoring these assembly routines for years, believing that +gcc's generated code had caught up with it sometime around gcc 2.95 +and the major rearchitecting of the Pentium 4. However, I recently +learned that, despite what I believed, this code still has some life +in it. On the Pentium 4 and AMD64 chips, it continues to run about 8% +faster than the code produced by gcc 4.1. + +In acknowledgement of its continuing usefulness, I've altered the +license to match that of the rest of zlib. Share and Enjoy! + +Brian Raiter +breadbox@muppetlabs.com +April, 2007 ADDED compat/zlib/contrib/asm686/match.S Index: compat/zlib/contrib/asm686/match.S ================================================================== --- /dev/null +++ compat/zlib/contrib/asm686/match.S @@ -0,0 +1,357 @@ +/* match.S -- x86 assembly version of the zlib longest_match() function. + * Optimized for the Intel 686 chips (PPro and later). + * + * Copyright (C) 1998, 2007 Brian Raiter + * + * This software is provided 'as-is', without any express or implied + * warranty. In no event will the author be held liable for any damages + * arising from the use of this software. + * + * Permission is granted to anyone to use this software for any purpose, + * including commercial applications, and to alter it and redistribute it + * freely, subject to the following restrictions: + * + * 1. The origin of this software must not be misrepresented; you must not + * claim that you wrote the original software. If you use this software + * in a product, an acknowledgment in the product documentation would be + * appreciated but is not required. + * 2. Altered source versions must be plainly marked as such, and must not be + * misrepresented as being the original software. + * 3. This notice may not be removed or altered from any source distribution. + */ + +#ifndef NO_UNDERLINE +#define match_init _match_init +#define longest_match _longest_match +#endif + +#define MAX_MATCH (258) +#define MIN_MATCH (3) +#define MIN_LOOKAHEAD (MAX_MATCH + MIN_MATCH + 1) +#define MAX_MATCH_8 ((MAX_MATCH + 7) & ~7) + +/* stack frame offsets */ + +#define chainlenwmask 0 /* high word: current chain len */ + /* low word: s->wmask */ +#define window 4 /* local copy of s->window */ +#define windowbestlen 8 /* s->window + bestlen */ +#define scanstart 16 /* first two bytes of string */ +#define scanend 12 /* last two bytes of string */ +#define scanalign 20 /* dword-misalignment of string */ +#define nicematch 24 /* a good enough match size */ +#define bestlen 28 /* size of best match so far */ +#define scan 32 /* ptr to string wanting match */ + +#define LocalVarsSize (36) +/* saved ebx 36 */ +/* saved edi 40 */ +/* saved esi 44 */ +/* saved ebp 48 */ +/* return address 52 */ +#define deflatestate 56 /* the function arguments */ +#define curmatch 60 + +/* All the +zlib1222add offsets are due to the addition of fields + * in zlib in the deflate_state structure since the asm code was first written + * (if you compile with zlib 1.0.4 or older, use "zlib1222add equ (-4)"). + * (if you compile with zlib between 1.0.5 and 1.2.2.1, use "zlib1222add equ 0"). + * if you compile with zlib 1.2.2.2 or later , use "zlib1222add equ 8"). + */ + +#define zlib1222add (8) + +#define dsWSize (36+zlib1222add) +#define dsWMask (44+zlib1222add) +#define dsWindow (48+zlib1222add) +#define dsPrev (56+zlib1222add) +#define dsMatchLen (88+zlib1222add) +#define dsPrevMatch (92+zlib1222add) +#define dsStrStart (100+zlib1222add) +#define dsMatchStart (104+zlib1222add) +#define dsLookahead (108+zlib1222add) +#define dsPrevLen (112+zlib1222add) +#define dsMaxChainLen (116+zlib1222add) +#define dsGoodMatch (132+zlib1222add) +#define dsNiceMatch (136+zlib1222add) + + +.file "match.S" + +.globl match_init, longest_match + +.text + +/* uInt longest_match(deflate_state *deflatestate, IPos curmatch) */ +.cfi_sections .debug_frame + +longest_match: + +.cfi_startproc +/* Save registers that the compiler may be using, and adjust %esp to */ +/* make room for our stack frame. */ + + pushl %ebp + .cfi_def_cfa_offset 8 + .cfi_offset ebp, -8 + pushl %edi + .cfi_def_cfa_offset 12 + pushl %esi + .cfi_def_cfa_offset 16 + pushl %ebx + .cfi_def_cfa_offset 20 + subl $LocalVarsSize, %esp + .cfi_def_cfa_offset LocalVarsSize+20 + +/* Retrieve the function arguments. %ecx will hold cur_match */ +/* throughout the entire function. %edx will hold the pointer to the */ +/* deflate_state structure during the function's setup (before */ +/* entering the main loop). */ + + movl deflatestate(%esp), %edx + movl curmatch(%esp), %ecx + +/* uInt wmask = s->w_mask; */ +/* unsigned chain_length = s->max_chain_length; */ +/* if (s->prev_length >= s->good_match) { */ +/* chain_length >>= 2; */ +/* } */ + + movl dsPrevLen(%edx), %eax + movl dsGoodMatch(%edx), %ebx + cmpl %ebx, %eax + movl dsWMask(%edx), %eax + movl dsMaxChainLen(%edx), %ebx + jl LastMatchGood + shrl $2, %ebx +LastMatchGood: + +/* chainlen is decremented once beforehand so that the function can */ +/* use the sign flag instead of the zero flag for the exit test. */ +/* It is then shifted into the high word, to make room for the wmask */ +/* value, which it will always accompany. */ + + decl %ebx + shll $16, %ebx + orl %eax, %ebx + movl %ebx, chainlenwmask(%esp) + +/* if ((uInt)nice_match > s->lookahead) nice_match = s->lookahead; */ + + movl dsNiceMatch(%edx), %eax + movl dsLookahead(%edx), %ebx + cmpl %eax, %ebx + jl LookaheadLess + movl %eax, %ebx +LookaheadLess: movl %ebx, nicematch(%esp) + +/* register Bytef *scan = s->window + s->strstart; */ + + movl dsWindow(%edx), %esi + movl %esi, window(%esp) + movl dsStrStart(%edx), %ebp + lea (%esi,%ebp), %edi + movl %edi, scan(%esp) + +/* Determine how many bytes the scan ptr is off from being */ +/* dword-aligned. */ + + movl %edi, %eax + negl %eax + andl $3, %eax + movl %eax, scanalign(%esp) + +/* IPos limit = s->strstart > (IPos)MAX_DIST(s) ? */ +/* s->strstart - (IPos)MAX_DIST(s) : NIL; */ + + movl dsWSize(%edx), %eax + subl $MIN_LOOKAHEAD, %eax + subl %eax, %ebp + jg LimitPositive + xorl %ebp, %ebp +LimitPositive: + +/* int best_len = s->prev_length; */ + + movl dsPrevLen(%edx), %eax + movl %eax, bestlen(%esp) + +/* Store the sum of s->window + best_len in %esi locally, and in %esi. */ + + addl %eax, %esi + movl %esi, windowbestlen(%esp) + +/* register ush scan_start = *(ushf*)scan; */ +/* register ush scan_end = *(ushf*)(scan+best_len-1); */ +/* Posf *prev = s->prev; */ + + movzwl (%edi), %ebx + movl %ebx, scanstart(%esp) + movzwl -1(%edi,%eax), %ebx + movl %ebx, scanend(%esp) + movl dsPrev(%edx), %edi + +/* Jump into the main loop. */ + + movl chainlenwmask(%esp), %edx + jmp LoopEntry + +.balign 16 + +/* do { + * match = s->window + cur_match; + * if (*(ushf*)(match+best_len-1) != scan_end || + * *(ushf*)match != scan_start) continue; + * [...] + * } while ((cur_match = prev[cur_match & wmask]) > limit + * && --chain_length != 0); + * + * Here is the inner loop of the function. The function will spend the + * majority of its time in this loop, and majority of that time will + * be spent in the first ten instructions. + * + * Within this loop: + * %ebx = scanend + * %ecx = curmatch + * %edx = chainlenwmask - i.e., ((chainlen << 16) | wmask) + * %esi = windowbestlen - i.e., (window + bestlen) + * %edi = prev + * %ebp = limit + */ +LookupLoop: + andl %edx, %ecx + movzwl (%edi,%ecx,2), %ecx + cmpl %ebp, %ecx + jbe LeaveNow + subl $0x00010000, %edx + js LeaveNow +LoopEntry: movzwl -1(%esi,%ecx), %eax + cmpl %ebx, %eax + jnz LookupLoop + movl window(%esp), %eax + movzwl (%eax,%ecx), %eax + cmpl scanstart(%esp), %eax + jnz LookupLoop + +/* Store the current value of chainlen. */ + + movl %edx, chainlenwmask(%esp) + +/* Point %edi to the string under scrutiny, and %esi to the string we */ +/* are hoping to match it up with. In actuality, %esi and %edi are */ +/* both pointed (MAX_MATCH_8 - scanalign) bytes ahead, and %edx is */ +/* initialized to -(MAX_MATCH_8 - scanalign). */ + + movl window(%esp), %esi + movl scan(%esp), %edi + addl %ecx, %esi + movl scanalign(%esp), %eax + movl $(-MAX_MATCH_8), %edx + lea MAX_MATCH_8(%edi,%eax), %edi + lea MAX_MATCH_8(%esi,%eax), %esi + +/* Test the strings for equality, 8 bytes at a time. At the end, + * adjust %edx so that it is offset to the exact byte that mismatched. + * + * We already know at this point that the first three bytes of the + * strings match each other, and they can be safely passed over before + * starting the compare loop. So what this code does is skip over 0-3 + * bytes, as much as necessary in order to dword-align the %edi + * pointer. (%esi will still be misaligned three times out of four.) + * + * It should be confessed that this loop usually does not represent + * much of the total running time. Replacing it with a more + * straightforward "rep cmpsb" would not drastically degrade + * performance. + */ +LoopCmps: + movl (%esi,%edx), %eax + xorl (%edi,%edx), %eax + jnz LeaveLoopCmps + movl 4(%esi,%edx), %eax + xorl 4(%edi,%edx), %eax + jnz LeaveLoopCmps4 + addl $8, %edx + jnz LoopCmps + jmp LenMaximum +LeaveLoopCmps4: addl $4, %edx +LeaveLoopCmps: testl $0x0000FFFF, %eax + jnz LenLower + addl $2, %edx + shrl $16, %eax +LenLower: subb $1, %al + adcl $0, %edx + +/* Calculate the length of the match. If it is longer than MAX_MATCH, */ +/* then automatically accept it as the best possible match and leave. */ + + lea (%edi,%edx), %eax + movl scan(%esp), %edi + subl %edi, %eax + cmpl $MAX_MATCH, %eax + jge LenMaximum + +/* If the length of the match is not longer than the best match we */ +/* have so far, then forget it and return to the lookup loop. */ + + movl deflatestate(%esp), %edx + movl bestlen(%esp), %ebx + cmpl %ebx, %eax + jg LongerMatch + movl windowbestlen(%esp), %esi + movl dsPrev(%edx), %edi + movl scanend(%esp), %ebx + movl chainlenwmask(%esp), %edx + jmp LookupLoop + +/* s->match_start = cur_match; */ +/* best_len = len; */ +/* if (len >= nice_match) break; */ +/* scan_end = *(ushf*)(scan+best_len-1); */ + +LongerMatch: movl nicematch(%esp), %ebx + movl %eax, bestlen(%esp) + movl %ecx, dsMatchStart(%edx) + cmpl %ebx, %eax + jge LeaveNow + movl window(%esp), %esi + addl %eax, %esi + movl %esi, windowbestlen(%esp) + movzwl -1(%edi,%eax), %ebx + movl dsPrev(%edx), %edi + movl %ebx, scanend(%esp) + movl chainlenwmask(%esp), %edx + jmp LookupLoop + +/* Accept the current string, with the maximum possible length. */ + +LenMaximum: movl deflatestate(%esp), %edx + movl $MAX_MATCH, bestlen(%esp) + movl %ecx, dsMatchStart(%edx) + +/* if ((uInt)best_len <= s->lookahead) return (uInt)best_len; */ +/* return s->lookahead; */ + +LeaveNow: + movl deflatestate(%esp), %edx + movl bestlen(%esp), %ebx + movl dsLookahead(%edx), %eax + cmpl %eax, %ebx + jg LookaheadRet + movl %ebx, %eax +LookaheadRet: + +/* Restore the stack and return from whence we came. */ + + addl $LocalVarsSize, %esp + .cfi_def_cfa_offset 20 + popl %ebx + .cfi_def_cfa_offset 16 + popl %esi + .cfi_def_cfa_offset 12 + popl %edi + .cfi_def_cfa_offset 8 + popl %ebp + .cfi_def_cfa_offset 4 +.cfi_endproc +match_init: ret ADDED compat/zlib/contrib/blast/Makefile Index: compat/zlib/contrib/blast/Makefile ================================================================== --- /dev/null +++ compat/zlib/contrib/blast/Makefile @@ -0,0 +1,8 @@ +blast: blast.c blast.h + cc -DTEST -o blast blast.c + +test: blast + blast < test.pk | cmp - test.txt + +clean: + rm -f blast blast.o ADDED compat/zlib/contrib/blast/README Index: compat/zlib/contrib/blast/README ================================================================== --- /dev/null +++ compat/zlib/contrib/blast/README @@ -0,0 +1,4 @@ +Read blast.h for purpose and usage. + +Mark Adler +madler@alumni.caltech.edu ADDED compat/zlib/contrib/blast/blast.c Index: compat/zlib/contrib/blast/blast.c ================================================================== --- /dev/null +++ compat/zlib/contrib/blast/blast.c @@ -0,0 +1,466 @@ +/* blast.c + * Copyright (C) 2003, 2012, 2013 Mark Adler + * For conditions of distribution and use, see copyright notice in blast.h + * version 1.3, 24 Aug 2013 + * + * blast.c decompresses data compressed by the PKWare Compression Library. + * This function provides functionality similar to the explode() function of + * the PKWare library, hence the name "blast". + * + * This decompressor is based on the excellent format description provided by + * Ben Rudiak-Gould in comp.compression on August 13, 2001. Interestingly, the + * example Ben provided in the post is incorrect. The distance 110001 should + * instead be 111000. When corrected, the example byte stream becomes: + * + * 00 04 82 24 25 8f 80 7f + * + * which decompresses to "AIAIAIAIAIAIA" (without the quotes). + */ + +/* + * Change history: + * + * 1.0 12 Feb 2003 - First version + * 1.1 16 Feb 2003 - Fixed distance check for > 4 GB uncompressed data + * 1.2 24 Oct 2012 - Add note about using binary mode in stdio + * - Fix comparisons of differently signed integers + * 1.3 24 Aug 2013 - Return unused input from blast() + * - Fix test code to correctly report unused input + * - Enable the provision of initial input to blast() + */ + +#include /* for NULL */ +#include /* for setjmp(), longjmp(), and jmp_buf */ +#include "blast.h" /* prototype for blast() */ + +#define local static /* for local function definitions */ +#define MAXBITS 13 /* maximum code length */ +#define MAXWIN 4096 /* maximum window size */ + +/* input and output state */ +struct state { + /* input state */ + blast_in infun; /* input function provided by user */ + void *inhow; /* opaque information passed to infun() */ + unsigned char *in; /* next input location */ + unsigned left; /* available input at in */ + int bitbuf; /* bit buffer */ + int bitcnt; /* number of bits in bit buffer */ + + /* input limit error return state for bits() and decode() */ + jmp_buf env; + + /* output state */ + blast_out outfun; /* output function provided by user */ + void *outhow; /* opaque information passed to outfun() */ + unsigned next; /* index of next write location in out[] */ + int first; /* true to check distances (for first 4K) */ + unsigned char out[MAXWIN]; /* output buffer and sliding window */ +}; + +/* + * Return need bits from the input stream. This always leaves less than + * eight bits in the buffer. bits() works properly for need == 0. + * + * Format notes: + * + * - Bits are stored in bytes from the least significant bit to the most + * significant bit. Therefore bits are dropped from the bottom of the bit + * buffer, using shift right, and new bytes are appended to the top of the + * bit buffer, using shift left. + */ +local int bits(struct state *s, int need) +{ + int val; /* bit accumulator */ + + /* load at least need bits into val */ + val = s->bitbuf; + while (s->bitcnt < need) { + if (s->left == 0) { + s->left = s->infun(s->inhow, &(s->in)); + if (s->left == 0) longjmp(s->env, 1); /* out of input */ + } + val |= (int)(*(s->in)++) << s->bitcnt; /* load eight bits */ + s->left--; + s->bitcnt += 8; + } + + /* drop need bits and update buffer, always zero to seven bits left */ + s->bitbuf = val >> need; + s->bitcnt -= need; + + /* return need bits, zeroing the bits above that */ + return val & ((1 << need) - 1); +} + +/* + * Huffman code decoding tables. count[1..MAXBITS] is the number of symbols of + * each length, which for a canonical code are stepped through in order. + * symbol[] are the symbol values in canonical order, where the number of + * entries is the sum of the counts in count[]. The decoding process can be + * seen in the function decode() below. + */ +struct huffman { + short *count; /* number of symbols of each length */ + short *symbol; /* canonically ordered symbols */ +}; + +/* + * Decode a code from the stream s using huffman table h. Return the symbol or + * a negative value if there is an error. If all of the lengths are zero, i.e. + * an empty code, or if the code is incomplete and an invalid code is received, + * then -9 is returned after reading MAXBITS bits. + * + * Format notes: + * + * - The codes as stored in the compressed data are bit-reversed relative to + * a simple integer ordering of codes of the same lengths. Hence below the + * bits are pulled from the compressed data one at a time and used to + * build the code value reversed from what is in the stream in order to + * permit simple integer comparisons for decoding. + * + * - The first code for the shortest length is all ones. Subsequent codes of + * the same length are simply integer decrements of the previous code. When + * moving up a length, a one bit is appended to the code. For a complete + * code, the last code of the longest length will be all zeros. To support + * this ordering, the bits pulled during decoding are inverted to apply the + * more "natural" ordering starting with all zeros and incrementing. + */ +local int decode(struct state *s, struct huffman *h) +{ + int len; /* current number of bits in code */ + int code; /* len bits being decoded */ + int first; /* first code of length len */ + int count; /* number of codes of length len */ + int index; /* index of first code of length len in symbol table */ + int bitbuf; /* bits from stream */ + int left; /* bits left in next or left to process */ + short *next; /* next number of codes */ + + bitbuf = s->bitbuf; + left = s->bitcnt; + code = first = index = 0; + len = 1; + next = h->count + 1; + while (1) { + while (left--) { + code |= (bitbuf & 1) ^ 1; /* invert code */ + bitbuf >>= 1; + count = *next++; + if (code < first + count) { /* if length len, return symbol */ + s->bitbuf = bitbuf; + s->bitcnt = (s->bitcnt - len) & 7; + return h->symbol[index + (code - first)]; + } + index += count; /* else update for next length */ + first += count; + first <<= 1; + code <<= 1; + len++; + } + left = (MAXBITS+1) - len; + if (left == 0) break; + if (s->left == 0) { + s->left = s->infun(s->inhow, &(s->in)); + if (s->left == 0) longjmp(s->env, 1); /* out of input */ + } + bitbuf = *(s->in)++; + s->left--; + if (left > 8) left = 8; + } + return -9; /* ran out of codes */ +} + +/* + * Given a list of repeated code lengths rep[0..n-1], where each byte is a + * count (high four bits + 1) and a code length (low four bits), generate the + * list of code lengths. This compaction reduces the size of the object code. + * Then given the list of code lengths length[0..n-1] representing a canonical + * Huffman code for n symbols, construct the tables required to decode those + * codes. Those tables are the number of codes of each length, and the symbols + * sorted by length, retaining their original order within each length. The + * return value is zero for a complete code set, negative for an over- + * subscribed code set, and positive for an incomplete code set. The tables + * can be used if the return value is zero or positive, but they cannot be used + * if the return value is negative. If the return value is zero, it is not + * possible for decode() using that table to return an error--any stream of + * enough bits will resolve to a symbol. If the return value is positive, then + * it is possible for decode() using that table to return an error for received + * codes past the end of the incomplete lengths. + */ +local int construct(struct huffman *h, const unsigned char *rep, int n) +{ + int symbol; /* current symbol when stepping through length[] */ + int len; /* current length when stepping through h->count[] */ + int left; /* number of possible codes left of current length */ + short offs[MAXBITS+1]; /* offsets in symbol table for each length */ + short length[256]; /* code lengths */ + + /* convert compact repeat counts into symbol bit length list */ + symbol = 0; + do { + len = *rep++; + left = (len >> 4) + 1; + len &= 15; + do { + length[symbol++] = len; + } while (--left); + } while (--n); + n = symbol; + + /* count number of codes of each length */ + for (len = 0; len <= MAXBITS; len++) + h->count[len] = 0; + for (symbol = 0; symbol < n; symbol++) + (h->count[length[symbol]])++; /* assumes lengths are within bounds */ + if (h->count[0] == n) /* no codes! */ + return 0; /* complete, but decode() will fail */ + + /* check for an over-subscribed or incomplete set of lengths */ + left = 1; /* one possible code of zero length */ + for (len = 1; len <= MAXBITS; len++) { + left <<= 1; /* one more bit, double codes left */ + left -= h->count[len]; /* deduct count from possible codes */ + if (left < 0) return left; /* over-subscribed--return negative */ + } /* left > 0 means incomplete */ + + /* generate offsets into symbol table for each length for sorting */ + offs[1] = 0; + for (len = 1; len < MAXBITS; len++) + offs[len + 1] = offs[len] + h->count[len]; + + /* + * put symbols in table sorted by length, by symbol order within each + * length + */ + for (symbol = 0; symbol < n; symbol++) + if (length[symbol] != 0) + h->symbol[offs[length[symbol]]++] = symbol; + + /* return zero for complete set, positive for incomplete set */ + return left; +} + +/* + * Decode PKWare Compression Library stream. + * + * Format notes: + * + * - First byte is 0 if literals are uncoded or 1 if they are coded. Second + * byte is 4, 5, or 6 for the number of extra bits in the distance code. + * This is the base-2 logarithm of the dictionary size minus six. + * + * - Compressed data is a combination of literals and length/distance pairs + * terminated by an end code. Literals are either Huffman coded or + * uncoded bytes. A length/distance pair is a coded length followed by a + * coded distance to represent a string that occurs earlier in the + * uncompressed data that occurs again at the current location. + * + * - A bit preceding a literal or length/distance pair indicates which comes + * next, 0 for literals, 1 for length/distance. + * + * - If literals are uncoded, then the next eight bits are the literal, in the + * normal bit order in the stream, i.e. no bit-reversal is needed. Similarly, + * no bit reversal is needed for either the length extra bits or the distance + * extra bits. + * + * - Literal bytes are simply written to the output. A length/distance pair is + * an instruction to copy previously uncompressed bytes to the output. The + * copy is from distance bytes back in the output stream, copying for length + * bytes. + * + * - Distances pointing before the beginning of the output data are not + * permitted. + * + * - Overlapped copies, where the length is greater than the distance, are + * allowed and common. For example, a distance of one and a length of 518 + * simply copies the last byte 518 times. A distance of four and a length of + * twelve copies the last four bytes three times. A simple forward copy + * ignoring whether the length is greater than the distance or not implements + * this correctly. + */ +local int decomp(struct state *s) +{ + int lit; /* true if literals are coded */ + int dict; /* log2(dictionary size) - 6 */ + int symbol; /* decoded symbol, extra bits for distance */ + int len; /* length for copy */ + unsigned dist; /* distance for copy */ + int copy; /* copy counter */ + unsigned char *from, *to; /* copy pointers */ + static int virgin = 1; /* build tables once */ + static short litcnt[MAXBITS+1], litsym[256]; /* litcode memory */ + static short lencnt[MAXBITS+1], lensym[16]; /* lencode memory */ + static short distcnt[MAXBITS+1], distsym[64]; /* distcode memory */ + static struct huffman litcode = {litcnt, litsym}; /* length code */ + static struct huffman lencode = {lencnt, lensym}; /* length code */ + static struct huffman distcode = {distcnt, distsym};/* distance code */ + /* bit lengths of literal codes */ + static const unsigned char litlen[] = { + 11, 124, 8, 7, 28, 7, 188, 13, 76, 4, 10, 8, 12, 10, 12, 10, 8, 23, 8, + 9, 7, 6, 7, 8, 7, 6, 55, 8, 23, 24, 12, 11, 7, 9, 11, 12, 6, 7, 22, 5, + 7, 24, 6, 11, 9, 6, 7, 22, 7, 11, 38, 7, 9, 8, 25, 11, 8, 11, 9, 12, + 8, 12, 5, 38, 5, 38, 5, 11, 7, 5, 6, 21, 6, 10, 53, 8, 7, 24, 10, 27, + 44, 253, 253, 253, 252, 252, 252, 13, 12, 45, 12, 45, 12, 61, 12, 45, + 44, 173}; + /* bit lengths of length codes 0..15 */ + static const unsigned char lenlen[] = {2, 35, 36, 53, 38, 23}; + /* bit lengths of distance codes 0..63 */ + static const unsigned char distlen[] = {2, 20, 53, 230, 247, 151, 248}; + static const short base[16] = { /* base for length codes */ + 3, 2, 4, 5, 6, 7, 8, 9, 10, 12, 16, 24, 40, 72, 136, 264}; + static const char extra[16] = { /* extra bits for length codes */ + 0, 0, 0, 0, 0, 0, 0, 0, 1, 2, 3, 4, 5, 6, 7, 8}; + + /* set up decoding tables (once--might not be thread-safe) */ + if (virgin) { + construct(&litcode, litlen, sizeof(litlen)); + construct(&lencode, lenlen, sizeof(lenlen)); + construct(&distcode, distlen, sizeof(distlen)); + virgin = 0; + } + + /* read header */ + lit = bits(s, 8); + if (lit > 1) return -1; + dict = bits(s, 8); + if (dict < 4 || dict > 6) return -2; + + /* decode literals and length/distance pairs */ + do { + if (bits(s, 1)) { + /* get length */ + symbol = decode(s, &lencode); + len = base[symbol] + bits(s, extra[symbol]); + if (len == 519) break; /* end code */ + + /* get distance */ + symbol = len == 2 ? 2 : dict; + dist = decode(s, &distcode) << symbol; + dist += bits(s, symbol); + dist++; + if (s->first && dist > s->next) + return -3; /* distance too far back */ + + /* copy length bytes from distance bytes back */ + do { + to = s->out + s->next; + from = to - dist; + copy = MAXWIN; + if (s->next < dist) { + from += copy; + copy = dist; + } + copy -= s->next; + if (copy > len) copy = len; + len -= copy; + s->next += copy; + do { + *to++ = *from++; + } while (--copy); + if (s->next == MAXWIN) { + if (s->outfun(s->outhow, s->out, s->next)) return 1; + s->next = 0; + s->first = 0; + } + } while (len != 0); + } + else { + /* get literal and write it */ + symbol = lit ? decode(s, &litcode) : bits(s, 8); + s->out[s->next++] = symbol; + if (s->next == MAXWIN) { + if (s->outfun(s->outhow, s->out, s->next)) return 1; + s->next = 0; + s->first = 0; + } + } + } while (1); + return 0; +} + +/* See comments in blast.h */ +int blast(blast_in infun, void *inhow, blast_out outfun, void *outhow, + unsigned *left, unsigned char **in) +{ + struct state s; /* input/output state */ + int err; /* return value */ + + /* initialize input state */ + s.infun = infun; + s.inhow = inhow; + if (left != NULL && *left) { + s.left = *left; + s.in = *in; + } + else + s.left = 0; + s.bitbuf = 0; + s.bitcnt = 0; + + /* initialize output state */ + s.outfun = outfun; + s.outhow = outhow; + s.next = 0; + s.first = 1; + + /* return if bits() or decode() tries to read past available input */ + if (setjmp(s.env) != 0) /* if came back here via longjmp(), */ + err = 2; /* then skip decomp(), return error */ + else + err = decomp(&s); /* decompress */ + + /* return unused input */ + if (left != NULL) + *left = s.left; + if (in != NULL) + *in = s.left ? s.in : NULL; + + /* write any leftover output and update the error code if needed */ + if (err != 1 && s.next && s.outfun(s.outhow, s.out, s.next) && err == 0) + err = 1; + return err; +} + +#ifdef TEST +/* Example of how to use blast() */ +#include +#include + +#define CHUNK 16384 + +local unsigned inf(void *how, unsigned char **buf) +{ + static unsigned char hold[CHUNK]; + + *buf = hold; + return fread(hold, 1, CHUNK, (FILE *)how); +} + +local int outf(void *how, unsigned char *buf, unsigned len) +{ + return fwrite(buf, 1, len, (FILE *)how) != len; +} + +/* Decompress a PKWare Compression Library stream from stdin to stdout */ +int main(void) +{ + int ret; + unsigned left; + + /* decompress to stdout */ + left = 0; + ret = blast(inf, stdin, outf, stdout, &left, NULL); + if (ret != 0) + fprintf(stderr, "blast error: %d\n", ret); + + /* count any leftover bytes */ + while (getchar() != EOF) + left++; + if (left) + fprintf(stderr, "blast warning: %u unused bytes of input\n", left); + + /* return blast() error code */ + return ret; +} +#endif ADDED compat/zlib/contrib/blast/blast.h Index: compat/zlib/contrib/blast/blast.h ================================================================== --- /dev/null +++ compat/zlib/contrib/blast/blast.h @@ -0,0 +1,83 @@ +/* blast.h -- interface for blast.c + Copyright (C) 2003, 2012, 2013 Mark Adler + version 1.3, 24 Aug 2013 + + This software is provided 'as-is', without any express or implied + warranty. In no event will the author be held liable for any damages + arising from the use of this software. + + Permission is granted to anyone to use this software for any purpose, + including commercial applications, and to alter it and redistribute it + freely, subject to the following restrictions: + + 1. The origin of this software must not be misrepresented; you must not + claim that you wrote the original software. If you use this software + in a product, an acknowledgment in the product documentation would be + appreciated but is not required. + 2. Altered source versions must be plainly marked as such, and must not be + misrepresented as being the original software. + 3. This notice may not be removed or altered from any source distribution. + + Mark Adler madler@alumni.caltech.edu + */ + + +/* + * blast() decompresses the PKWare Data Compression Library (DCL) compressed + * format. It provides the same functionality as the explode() function in + * that library. (Note: PKWare overused the "implode" verb, and the format + * used by their library implode() function is completely different and + * incompatible with the implode compression method supported by PKZIP.) + * + * The binary mode for stdio functions should be used to assure that the + * compressed data is not corrupted when read or written. For example: + * fopen(..., "rb") and fopen(..., "wb"). + */ + + +typedef unsigned (*blast_in)(void *how, unsigned char **buf); +typedef int (*blast_out)(void *how, unsigned char *buf, unsigned len); +/* Definitions for input/output functions passed to blast(). See below for + * what the provided functions need to do. + */ + + +int blast(blast_in infun, void *inhow, blast_out outfun, void *outhow, + unsigned *left, unsigned char **in); +/* Decompress input to output using the provided infun() and outfun() calls. + * On success, the return value of blast() is zero. If there is an error in + * the source data, i.e. it is not in the proper format, then a negative value + * is returned. If there is not enough input available or there is not enough + * output space, then a positive error is returned. + * + * The input function is invoked: len = infun(how, &buf), where buf is set by + * infun() to point to the input buffer, and infun() returns the number of + * available bytes there. If infun() returns zero, then blast() returns with + * an input error. (blast() only asks for input if it needs it.) inhow is for + * use by the application to pass an input descriptor to infun(), if desired. + * + * If left and in are not NULL and *left is not zero when blast() is called, + * then the *left bytes are *in are consumed for input before infun() is used. + * + * The output function is invoked: err = outfun(how, buf, len), where the bytes + * to be written are buf[0..len-1]. If err is not zero, then blast() returns + * with an output error. outfun() is always called with len <= 4096. outhow + * is for use by the application to pass an output descriptor to outfun(), if + * desired. + * + * If there is any unused input, *left is set to the number of bytes that were + * read and *in points to them. Otherwise *left is set to zero and *in is set + * to NULL. If left or in are NULL, then they are not set. + * + * The return codes are: + * + * 2: ran out of input before completing decompression + * 1: output error before completing decompression + * 0: successful decompression + * -1: literal flag not zero or one + * -2: dictionary size not in 4..6 + * -3: distance is too far back + * + * At the bottom of blast.c is an example program that uses blast() that can be + * compiled to produce a command-line decompression filter by defining TEST. + */ ADDED compat/zlib/contrib/blast/test.pk Index: compat/zlib/contrib/blast/test.pk ================================================================== --- /dev/null +++ compat/zlib/contrib/blast/test.pk cannot compute difference between binary files ADDED compat/zlib/contrib/blast/test.txt Index: compat/zlib/contrib/blast/test.txt ================================================================== --- /dev/null +++ compat/zlib/contrib/blast/test.txt @@ -0,0 +1,1 @@ +AIAIAIAIAIAIA ADDED compat/zlib/contrib/delphi/ZLib.pas Index: compat/zlib/contrib/delphi/ZLib.pas ================================================================== --- /dev/null +++ compat/zlib/contrib/delphi/ZLib.pas @@ -0,0 +1,557 @@ +{*******************************************************} +{ } +{ Borland Delphi Supplemental Components } +{ ZLIB Data Compression Interface Unit } +{ } +{ Copyright (c) 1997,99 Borland Corporation } +{ } +{*******************************************************} + +{ Updated for zlib 1.2.x by Cosmin Truta } + +unit ZLib; + +interface + +uses SysUtils, Classes; + +type + TAlloc = function (AppData: Pointer; Items, Size: Integer): Pointer; cdecl; + TFree = procedure (AppData, Block: Pointer); cdecl; + + // Internal structure. Ignore. + TZStreamRec = packed record + next_in: PChar; // next input byte + avail_in: Integer; // number of bytes available at next_in + total_in: Longint; // total nb of input bytes read so far + + next_out: PChar; // next output byte should be put here + avail_out: Integer; // remaining free space at next_out + total_out: Longint; // total nb of bytes output so far + + msg: PChar; // last error message, NULL if no error + internal: Pointer; // not visible by applications + + zalloc: TAlloc; // used to allocate the internal state + zfree: TFree; // used to free the internal state + AppData: Pointer; // private data object passed to zalloc and zfree + + data_type: Integer; // best guess about the data type: ascii or binary + adler: Longint; // adler32 value of the uncompressed data + reserved: Longint; // reserved for future use + end; + + // Abstract ancestor class + TCustomZlibStream = class(TStream) + private + FStrm: TStream; + FStrmPos: Integer; + FOnProgress: TNotifyEvent; + FZRec: TZStreamRec; + FBuffer: array [Word] of Char; + protected + procedure Progress(Sender: TObject); dynamic; + property OnProgress: TNotifyEvent read FOnProgress write FOnProgress; + constructor Create(Strm: TStream); + end; + +{ TCompressionStream compresses data on the fly as data is written to it, and + stores the compressed data to another stream. + + TCompressionStream is write-only and strictly sequential. Reading from the + stream will raise an exception. Using Seek to move the stream pointer + will raise an exception. + + Output data is cached internally, written to the output stream only when + the internal output buffer is full. All pending output data is flushed + when the stream is destroyed. + + The Position property returns the number of uncompressed bytes of + data that have been written to the stream so far. + + CompressionRate returns the on-the-fly percentage by which the original + data has been compressed: (1 - (CompressedBytes / UncompressedBytes)) * 100 + If raw data size = 100 and compressed data size = 25, the CompressionRate + is 75% + + The OnProgress event is called each time the output buffer is filled and + written to the output stream. This is useful for updating a progress + indicator when you are writing a large chunk of data to the compression + stream in a single call.} + + + TCompressionLevel = (clNone, clFastest, clDefault, clMax); + + TCompressionStream = class(TCustomZlibStream) + private + function GetCompressionRate: Single; + public + constructor Create(CompressionLevel: TCompressionLevel; Dest: TStream); + destructor Destroy; override; + function Read(var Buffer; Count: Longint): Longint; override; + function Write(const Buffer; Count: Longint): Longint; override; + function Seek(Offset: Longint; Origin: Word): Longint; override; + property CompressionRate: Single read GetCompressionRate; + property OnProgress; + end; + +{ TDecompressionStream decompresses data on the fly as data is read from it. + + Compressed data comes from a separate source stream. TDecompressionStream + is read-only and unidirectional; you can seek forward in the stream, but not + backwards. The special case of setting the stream position to zero is + allowed. Seeking forward decompresses data until the requested position in + the uncompressed data has been reached. Seeking backwards, seeking relative + to the end of the stream, requesting the size of the stream, and writing to + the stream will raise an exception. + + The Position property returns the number of bytes of uncompressed data that + have been read from the stream so far. + + The OnProgress event is called each time the internal input buffer of + compressed data is exhausted and the next block is read from the input stream. + This is useful for updating a progress indicator when you are reading a + large chunk of data from the decompression stream in a single call.} + + TDecompressionStream = class(TCustomZlibStream) + public + constructor Create(Source: TStream); + destructor Destroy; override; + function Read(var Buffer; Count: Longint): Longint; override; + function Write(const Buffer; Count: Longint): Longint; override; + function Seek(Offset: Longint; Origin: Word): Longint; override; + property OnProgress; + end; + + + +{ CompressBuf compresses data, buffer to buffer, in one call. + In: InBuf = ptr to compressed data + InBytes = number of bytes in InBuf + Out: OutBuf = ptr to newly allocated buffer containing decompressed data + OutBytes = number of bytes in OutBuf } +procedure CompressBuf(const InBuf: Pointer; InBytes: Integer; + out OutBuf: Pointer; out OutBytes: Integer); + + +{ DecompressBuf decompresses data, buffer to buffer, in one call. + In: InBuf = ptr to compressed data + InBytes = number of bytes in InBuf + OutEstimate = zero, or est. size of the decompressed data + Out: OutBuf = ptr to newly allocated buffer containing decompressed data + OutBytes = number of bytes in OutBuf } +procedure DecompressBuf(const InBuf: Pointer; InBytes: Integer; + OutEstimate: Integer; out OutBuf: Pointer; out OutBytes: Integer); + +{ DecompressToUserBuf decompresses data, buffer to buffer, in one call. + In: InBuf = ptr to compressed data + InBytes = number of bytes in InBuf + Out: OutBuf = ptr to user-allocated buffer to contain decompressed data + BufSize = number of bytes in OutBuf } +procedure DecompressToUserBuf(const InBuf: Pointer; InBytes: Integer; + const OutBuf: Pointer; BufSize: Integer); + +const + zlib_version = '1.2.11'; + +type + EZlibError = class(Exception); + ECompressionError = class(EZlibError); + EDecompressionError = class(EZlibError); + +implementation + +uses ZLibConst; + +const + Z_NO_FLUSH = 0; + Z_PARTIAL_FLUSH = 1; + Z_SYNC_FLUSH = 2; + Z_FULL_FLUSH = 3; + Z_FINISH = 4; + + Z_OK = 0; + Z_STREAM_END = 1; + Z_NEED_DICT = 2; + Z_ERRNO = (-1); + Z_STREAM_ERROR = (-2); + Z_DATA_ERROR = (-3); + Z_MEM_ERROR = (-4); + Z_BUF_ERROR = (-5); + Z_VERSION_ERROR = (-6); + + Z_NO_COMPRESSION = 0; + Z_BEST_SPEED = 1; + Z_BEST_COMPRESSION = 9; + Z_DEFAULT_COMPRESSION = (-1); + + Z_FILTERED = 1; + Z_HUFFMAN_ONLY = 2; + Z_RLE = 3; + Z_DEFAULT_STRATEGY = 0; + + Z_BINARY = 0; + Z_ASCII = 1; + Z_UNKNOWN = 2; + + Z_DEFLATED = 8; + + +{$L adler32.obj} +{$L compress.obj} +{$L crc32.obj} +{$L deflate.obj} +{$L infback.obj} +{$L inffast.obj} +{$L inflate.obj} +{$L inftrees.obj} +{$L trees.obj} +{$L uncompr.obj} +{$L zutil.obj} + +procedure adler32; external; +procedure compressBound; external; +procedure crc32; external; +procedure deflateInit2_; external; +procedure deflateParams; external; + +function _malloc(Size: Integer): Pointer; cdecl; +begin + Result := AllocMem(Size); +end; + +procedure _free(Block: Pointer); cdecl; +begin + FreeMem(Block); +end; + +procedure _memset(P: Pointer; B: Byte; count: Integer); cdecl; +begin + FillChar(P^, count, B); +end; + +procedure _memcpy(dest, source: Pointer; count: Integer); cdecl; +begin + Move(source^, dest^, count); +end; + + + +// deflate compresses data +function deflateInit_(var strm: TZStreamRec; level: Integer; version: PChar; + recsize: Integer): Integer; external; +function deflate(var strm: TZStreamRec; flush: Integer): Integer; external; +function deflateEnd(var strm: TZStreamRec): Integer; external; + +// inflate decompresses data +function inflateInit_(var strm: TZStreamRec; version: PChar; + recsize: Integer): Integer; external; +function inflate(var strm: TZStreamRec; flush: Integer): Integer; external; +function inflateEnd(var strm: TZStreamRec): Integer; external; +function inflateReset(var strm: TZStreamRec): Integer; external; + + +function zlibAllocMem(AppData: Pointer; Items, Size: Integer): Pointer; cdecl; +begin +// GetMem(Result, Items*Size); + Result := AllocMem(Items * Size); +end; + +procedure zlibFreeMem(AppData, Block: Pointer); cdecl; +begin + FreeMem(Block); +end; + +{function zlibCheck(code: Integer): Integer; +begin + Result := code; + if code < 0 then + raise EZlibError.Create('error'); //!! +end;} + +function CCheck(code: Integer): Integer; +begin + Result := code; + if code < 0 then + raise ECompressionError.Create('error'); //!! +end; + +function DCheck(code: Integer): Integer; +begin + Result := code; + if code < 0 then + raise EDecompressionError.Create('error'); //!! +end; + +procedure CompressBuf(const InBuf: Pointer; InBytes: Integer; + out OutBuf: Pointer; out OutBytes: Integer); +var + strm: TZStreamRec; + P: Pointer; +begin + FillChar(strm, sizeof(strm), 0); + strm.zalloc := zlibAllocMem; + strm.zfree := zlibFreeMem; + OutBytes := ((InBytes + (InBytes div 10) + 12) + 255) and not 255; + GetMem(OutBuf, OutBytes); + try + strm.next_in := InBuf; + strm.avail_in := InBytes; + strm.next_out := OutBuf; + strm.avail_out := OutBytes; + CCheck(deflateInit_(strm, Z_BEST_COMPRESSION, zlib_version, sizeof(strm))); + try + while CCheck(deflate(strm, Z_FINISH)) <> Z_STREAM_END do + begin + P := OutBuf; + Inc(OutBytes, 256); + ReallocMem(OutBuf, OutBytes); + strm.next_out := PChar(Integer(OutBuf) + (Integer(strm.next_out) - Integer(P))); + strm.avail_out := 256; + end; + finally + CCheck(deflateEnd(strm)); + end; + ReallocMem(OutBuf, strm.total_out); + OutBytes := strm.total_out; + except + FreeMem(OutBuf); + raise + end; +end; + + +procedure DecompressBuf(const InBuf: Pointer; InBytes: Integer; + OutEstimate: Integer; out OutBuf: Pointer; out OutBytes: Integer); +var + strm: TZStreamRec; + P: Pointer; + BufInc: Integer; +begin + FillChar(strm, sizeof(strm), 0); + strm.zalloc := zlibAllocMem; + strm.zfree := zlibFreeMem; + BufInc := (InBytes + 255) and not 255; + if OutEstimate = 0 then + OutBytes := BufInc + else + OutBytes := OutEstimate; + GetMem(OutBuf, OutBytes); + try + strm.next_in := InBuf; + strm.avail_in := InBytes; + strm.next_out := OutBuf; + strm.avail_out := OutBytes; + DCheck(inflateInit_(strm, zlib_version, sizeof(strm))); + try + while DCheck(inflate(strm, Z_NO_FLUSH)) <> Z_STREAM_END do + begin + P := OutBuf; + Inc(OutBytes, BufInc); + ReallocMem(OutBuf, OutBytes); + strm.next_out := PChar(Integer(OutBuf) + (Integer(strm.next_out) - Integer(P))); + strm.avail_out := BufInc; + end; + finally + DCheck(inflateEnd(strm)); + end; + ReallocMem(OutBuf, strm.total_out); + OutBytes := strm.total_out; + except + FreeMem(OutBuf); + raise + end; +end; + +procedure DecompressToUserBuf(const InBuf: Pointer; InBytes: Integer; + const OutBuf: Pointer; BufSize: Integer); +var + strm: TZStreamRec; +begin + FillChar(strm, sizeof(strm), 0); + strm.zalloc := zlibAllocMem; + strm.zfree := zlibFreeMem; + strm.next_in := InBuf; + strm.avail_in := InBytes; + strm.next_out := OutBuf; + strm.avail_out := BufSize; + DCheck(inflateInit_(strm, zlib_version, sizeof(strm))); + try + if DCheck(inflate(strm, Z_FINISH)) <> Z_STREAM_END then + raise EZlibError.CreateRes(@sTargetBufferTooSmall); + finally + DCheck(inflateEnd(strm)); + end; +end; + +// TCustomZlibStream + +constructor TCustomZLibStream.Create(Strm: TStream); +begin + inherited Create; + FStrm := Strm; + FStrmPos := Strm.Position; + FZRec.zalloc := zlibAllocMem; + FZRec.zfree := zlibFreeMem; +end; + +procedure TCustomZLibStream.Progress(Sender: TObject); +begin + if Assigned(FOnProgress) then FOnProgress(Sender); +end; + + +// TCompressionStream + +constructor TCompressionStream.Create(CompressionLevel: TCompressionLevel; + Dest: TStream); +const + Levels: array [TCompressionLevel] of ShortInt = + (Z_NO_COMPRESSION, Z_BEST_SPEED, Z_DEFAULT_COMPRESSION, Z_BEST_COMPRESSION); +begin + inherited Create(Dest); + FZRec.next_out := FBuffer; + FZRec.avail_out := sizeof(FBuffer); + CCheck(deflateInit_(FZRec, Levels[CompressionLevel], zlib_version, sizeof(FZRec))); +end; + +destructor TCompressionStream.Destroy; +begin + FZRec.next_in := nil; + FZRec.avail_in := 0; + try + if FStrm.Position <> FStrmPos then FStrm.Position := FStrmPos; + while (CCheck(deflate(FZRec, Z_FINISH)) <> Z_STREAM_END) + and (FZRec.avail_out = 0) do + begin + FStrm.WriteBuffer(FBuffer, sizeof(FBuffer)); + FZRec.next_out := FBuffer; + FZRec.avail_out := sizeof(FBuffer); + end; + if FZRec.avail_out < sizeof(FBuffer) then + FStrm.WriteBuffer(FBuffer, sizeof(FBuffer) - FZRec.avail_out); + finally + deflateEnd(FZRec); + end; + inherited Destroy; +end; + +function TCompressionStream.Read(var Buffer; Count: Longint): Longint; +begin + raise ECompressionError.CreateRes(@sInvalidStreamOp); +end; + +function TCompressionStream.Write(const Buffer; Count: Longint): Longint; +begin + FZRec.next_in := @Buffer; + FZRec.avail_in := Count; + if FStrm.Position <> FStrmPos then FStrm.Position := FStrmPos; + while (FZRec.avail_in > 0) do + begin + CCheck(deflate(FZRec, 0)); + if FZRec.avail_out = 0 then + begin + FStrm.WriteBuffer(FBuffer, sizeof(FBuffer)); + FZRec.next_out := FBuffer; + FZRec.avail_out := sizeof(FBuffer); + FStrmPos := FStrm.Position; + Progress(Self); + end; + end; + Result := Count; +end; + +function TCompressionStream.Seek(Offset: Longint; Origin: Word): Longint; +begin + if (Offset = 0) and (Origin = soFromCurrent) then + Result := FZRec.total_in + else + raise ECompressionError.CreateRes(@sInvalidStreamOp); +end; + +function TCompressionStream.GetCompressionRate: Single; +begin + if FZRec.total_in = 0 then + Result := 0 + else + Result := (1.0 - (FZRec.total_out / FZRec.total_in)) * 100.0; +end; + + +// TDecompressionStream + +constructor TDecompressionStream.Create(Source: TStream); +begin + inherited Create(Source); + FZRec.next_in := FBuffer; + FZRec.avail_in := 0; + DCheck(inflateInit_(FZRec, zlib_version, sizeof(FZRec))); +end; + +destructor TDecompressionStream.Destroy; +begin + FStrm.Seek(-FZRec.avail_in, 1); + inflateEnd(FZRec); + inherited Destroy; +end; + +function TDecompressionStream.Read(var Buffer; Count: Longint): Longint; +begin + FZRec.next_out := @Buffer; + FZRec.avail_out := Count; + if FStrm.Position <> FStrmPos then FStrm.Position := FStrmPos; + while (FZRec.avail_out > 0) do + begin + if FZRec.avail_in = 0 then + begin + FZRec.avail_in := FStrm.Read(FBuffer, sizeof(FBuffer)); + if FZRec.avail_in = 0 then + begin + Result := Count - FZRec.avail_out; + Exit; + end; + FZRec.next_in := FBuffer; + FStrmPos := FStrm.Position; + Progress(Self); + end; + CCheck(inflate(FZRec, 0)); + end; + Result := Count; +end; + +function TDecompressionStream.Write(const Buffer; Count: Longint): Longint; +begin + raise EDecompressionError.CreateRes(@sInvalidStreamOp); +end; + +function TDecompressionStream.Seek(Offset: Longint; Origin: Word): Longint; +var + I: Integer; + Buf: array [0..4095] of Char; +begin + if (Offset = 0) and (Origin = soFromBeginning) then + begin + DCheck(inflateReset(FZRec)); + FZRec.next_in := FBuffer; + FZRec.avail_in := 0; + FStrm.Position := 0; + FStrmPos := 0; + end + else if ( (Offset >= 0) and (Origin = soFromCurrent)) or + ( ((Offset - FZRec.total_out) > 0) and (Origin = soFromBeginning)) then + begin + if Origin = soFromBeginning then Dec(Offset, FZRec.total_out); + if Offset > 0 then + begin + for I := 1 to Offset div sizeof(Buf) do + ReadBuffer(Buf, sizeof(Buf)); + ReadBuffer(Buf, Offset mod sizeof(Buf)); + end; + end + else + raise EDecompressionError.CreateRes(@sInvalidStreamOp); + Result := FZRec.total_out; +end; + + +end. ADDED compat/zlib/contrib/delphi/ZLibConst.pas Index: compat/zlib/contrib/delphi/ZLibConst.pas ================================================================== --- /dev/null +++ compat/zlib/contrib/delphi/ZLibConst.pas @@ -0,0 +1,11 @@ +unit ZLibConst; + +interface + +resourcestring + sTargetBufferTooSmall = 'ZLib error: target buffer may be too small'; + sInvalidStreamOp = 'Invalid stream operation'; + +implementation + +end. ADDED compat/zlib/contrib/delphi/readme.txt Index: compat/zlib/contrib/delphi/readme.txt ================================================================== --- /dev/null +++ compat/zlib/contrib/delphi/readme.txt @@ -0,0 +1,76 @@ + +Overview +======== + +This directory contains an update to the ZLib interface unit, +distributed by Borland as a Delphi supplemental component. + +The original ZLib unit is Copyright (c) 1997,99 Borland Corp., +and is based on zlib version 1.0.4. There are a series of bugs +and security problems associated with that old zlib version, and +we recommend the users to update their ZLib unit. + + +Summary of modifications +======================== + +- Improved makefile, adapted to zlib version 1.2.1. + +- Some field types from TZStreamRec are changed from Integer to + Longint, for consistency with the zlib.h header, and for 64-bit + readiness. + +- The zlib_version constant is updated. + +- The new Z_RLE strategy has its corresponding symbolic constant. + +- The allocation and deallocation functions and function types + (TAlloc, TFree, zlibAllocMem and zlibFreeMem) are now cdecl, + and _malloc and _free are added as C RTL stubs. As a result, + the original C sources of zlib can be compiled out of the box, + and linked to the ZLib unit. + + +Suggestions for improvements +============================ + +Currently, the ZLib unit provides only a limited wrapper around +the zlib library, and much of the original zlib functionality is +missing. Handling compressed file formats like ZIP/GZIP or PNG +cannot be implemented without having this functionality. +Applications that handle these formats are either using their own, +duplicated code, or not using the ZLib unit at all. + +Here are a few suggestions: + +- Checksum class wrappers around adler32() and crc32(), similar + to the Java classes that implement the java.util.zip.Checksum + interface. + +- The ability to read and write raw deflate streams, without the + zlib stream header and trailer. Raw deflate streams are used + in the ZIP file format. + +- The ability to read and write gzip streams, used in the GZIP + file format, and normally produced by the gzip program. + +- The ability to select a different compression strategy, useful + to PNG and MNG image compression, and to multimedia compression + in general. Besides the compression level + + TCompressionLevel = (clNone, clFastest, clDefault, clMax); + + which, in fact, could have used the 'z' prefix and avoided + TColor-like symbols + + TCompressionLevel = (zcNone, zcFastest, zcDefault, zcMax); + + there could be a compression strategy + + TCompressionStrategy = (zsDefault, zsFiltered, zsHuffmanOnly, zsRle); + +- ZIP and GZIP stream handling via TStreams. + + +-- +Cosmin Truta ADDED compat/zlib/contrib/delphi/zlibd32.mak Index: compat/zlib/contrib/delphi/zlibd32.mak ================================================================== --- /dev/null +++ compat/zlib/contrib/delphi/zlibd32.mak @@ -0,0 +1,99 @@ +# Makefile for zlib +# For use with Delphi and C++ Builder under Win32 +# Updated for zlib 1.2.x by Cosmin Truta + +# ------------ Borland C++ ------------ + +# This project uses the Delphi (fastcall/register) calling convention: +LOC = -DZEXPORT=__fastcall -DZEXPORTVA=__cdecl + +CC = bcc32 +LD = bcc32 +AR = tlib +# do not use "-pr" in CFLAGS +CFLAGS = -a -d -k- -O2 $(LOC) +LDFLAGS = + + +# variables +ZLIB_LIB = zlib.lib + +OBJ1 = adler32.obj compress.obj crc32.obj deflate.obj gzclose.obj gzlib.obj gzread.obj +OBJ2 = gzwrite.obj infback.obj inffast.obj inflate.obj inftrees.obj trees.obj uncompr.obj zutil.obj +OBJP1 = +adler32.obj+compress.obj+crc32.obj+deflate.obj+gzclose.obj+gzlib.obj+gzread.obj +OBJP2 = +gzwrite.obj+infback.obj+inffast.obj+inflate.obj+inftrees.obj+trees.obj+uncompr.obj+zutil.obj + + +# targets +all: $(ZLIB_LIB) example.exe minigzip.exe + +.c.obj: + $(CC) -c $(CFLAGS) $*.c + +adler32.obj: adler32.c zlib.h zconf.h + +compress.obj: compress.c zlib.h zconf.h + +crc32.obj: crc32.c zlib.h zconf.h crc32.h + +deflate.obj: deflate.c deflate.h zutil.h zlib.h zconf.h + +gzclose.obj: gzclose.c zlib.h zconf.h gzguts.h + +gzlib.obj: gzlib.c zlib.h zconf.h gzguts.h + +gzread.obj: gzread.c zlib.h zconf.h gzguts.h + +gzwrite.obj: gzwrite.c zlib.h zconf.h gzguts.h + +infback.obj: infback.c zutil.h zlib.h zconf.h inftrees.h inflate.h \ + inffast.h inffixed.h + +inffast.obj: inffast.c zutil.h zlib.h zconf.h inftrees.h inflate.h \ + inffast.h + +inflate.obj: inflate.c zutil.h zlib.h zconf.h inftrees.h inflate.h \ + inffast.h inffixed.h + +inftrees.obj: inftrees.c zutil.h zlib.h zconf.h inftrees.h + +trees.obj: trees.c zutil.h zlib.h zconf.h deflate.h trees.h + +uncompr.obj: uncompr.c zlib.h zconf.h + +zutil.obj: zutil.c zutil.h zlib.h zconf.h + +example.obj: test/example.c zlib.h zconf.h + +minigzip.obj: test/minigzip.c zlib.h zconf.h + + +# For the sake of the old Borland make, +# the command line is cut to fit in the MS-DOS 128 byte limit: +$(ZLIB_LIB): $(OBJ1) $(OBJ2) + -del $(ZLIB_LIB) + $(AR) $(ZLIB_LIB) $(OBJP1) + $(AR) $(ZLIB_LIB) $(OBJP2) + + +# testing +test: example.exe minigzip.exe + example + echo hello world | minigzip | minigzip -d + +example.exe: example.obj $(ZLIB_LIB) + $(LD) $(LDFLAGS) example.obj $(ZLIB_LIB) + +minigzip.exe: minigzip.obj $(ZLIB_LIB) + $(LD) $(LDFLAGS) minigzip.obj $(ZLIB_LIB) + + +# cleanup +clean: + -del *.obj + -del *.exe + -del *.lib + -del *.tds + -del zlib.bak + -del foo.gz + ADDED compat/zlib/contrib/dotzlib/DotZLib.build Index: compat/zlib/contrib/dotzlib/DotZLib.build ================================================================== --- /dev/null +++ compat/zlib/contrib/dotzlib/DotZLib.build @@ -0,0 +1,33 @@ + + + A .Net wrapper library around ZLib1.dll + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + ADDED compat/zlib/contrib/dotzlib/DotZLib.chm Index: compat/zlib/contrib/dotzlib/DotZLib.chm ================================================================== --- /dev/null +++ compat/zlib/contrib/dotzlib/DotZLib.chm cannot compute difference between binary files ADDED compat/zlib/contrib/dotzlib/DotZLib.sln Index: compat/zlib/contrib/dotzlib/DotZLib.sln ================================================================== --- /dev/null +++ compat/zlib/contrib/dotzlib/DotZLib.sln @@ -0,0 +1,21 @@ +Microsoft Visual Studio Solution File, Format Version 8.00 +Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "DotZLib", "DotZLib\DotZLib.csproj", "{BB1EE0B1-1808-46CB-B786-949D91117FC5}" + ProjectSection(ProjectDependencies) = postProject + EndProjectSection +EndProject +Global + GlobalSection(SolutionConfiguration) = preSolution + Debug = Debug + Release = Release + EndGlobalSection + GlobalSection(ProjectConfiguration) = postSolution + {BB1EE0B1-1808-46CB-B786-949D91117FC5}.Debug.ActiveCfg = Debug|.NET + {BB1EE0B1-1808-46CB-B786-949D91117FC5}.Debug.Build.0 = Debug|.NET + {BB1EE0B1-1808-46CB-B786-949D91117FC5}.Release.ActiveCfg = Release|.NET + {BB1EE0B1-1808-46CB-B786-949D91117FC5}.Release.Build.0 = Release|.NET + EndGlobalSection + GlobalSection(ExtensibilityGlobals) = postSolution + EndGlobalSection + GlobalSection(ExtensibilityAddIns) = postSolution + EndGlobalSection +EndGlobal ADDED compat/zlib/contrib/dotzlib/DotZLib/AssemblyInfo.cs Index: compat/zlib/contrib/dotzlib/DotZLib/AssemblyInfo.cs ================================================================== --- /dev/null +++ compat/zlib/contrib/dotzlib/DotZLib/AssemblyInfo.cs @@ -0,0 +1,58 @@ +using System.Reflection; +using System.Runtime.CompilerServices; + +// +// General Information about an assembly is controlled through the following +// set of attributes. Change these attribute values to modify the information +// associated with an assembly. +// +[assembly: AssemblyTitle("DotZLib")] +[assembly: AssemblyDescription(".Net bindings for ZLib compression dll 1.2.x")] +[assembly: AssemblyConfiguration("")] +[assembly: AssemblyCompany("Henrik Ravn")] +[assembly: AssemblyProduct("")] +[assembly: AssemblyCopyright("(c) 2004 by Henrik Ravn")] +[assembly: AssemblyTrademark("")] +[assembly: AssemblyCulture("")] + +// +// Version information for an assembly consists of the following four values: +// +// Major Version +// Minor Version +// Build Number +// Revision +// +// You can specify all the values or you can default the Revision and Build Numbers +// by using the '*' as shown below: + +[assembly: AssemblyVersion("1.0.*")] + +// +// In order to sign your assembly you must specify a key to use. Refer to the +// Microsoft .NET Framework documentation for more information on assembly signing. +// +// Use the attributes below to control which key is used for signing. +// +// Notes: +// (*) If no key is specified, the assembly is not signed. +// (*) KeyName refers to a key that has been installed in the Crypto Service +// Provider (CSP) on your machine. KeyFile refers to a file which contains +// a key. +// (*) If the KeyFile and the KeyName values are both specified, the +// following processing occurs: +// (1) If the KeyName can be found in the CSP, that key is used. +// (2) If the KeyName does not exist and the KeyFile does exist, the key +// in the KeyFile is installed into the CSP and used. +// (*) In order to create a KeyFile, you can use the sn.exe (Strong Name) utility. +// When specifying the KeyFile, the location of the KeyFile should be +// relative to the project output directory which is +// %Project Directory%\obj\. For example, if your KeyFile is +// located in the project directory, you would specify the AssemblyKeyFile +// attribute as [assembly: AssemblyKeyFile("..\\..\\mykey.snk")] +// (*) Delay Signing is an advanced option - see the Microsoft .NET Framework +// documentation for more information on this. +// +[assembly: AssemblyDelaySign(false)] +[assembly: AssemblyKeyFile("")] +[assembly: AssemblyKeyName("")] ADDED compat/zlib/contrib/dotzlib/DotZLib/ChecksumImpl.cs Index: compat/zlib/contrib/dotzlib/DotZLib/ChecksumImpl.cs ================================================================== --- /dev/null +++ compat/zlib/contrib/dotzlib/DotZLib/ChecksumImpl.cs @@ -0,0 +1,202 @@ +// +// Copyright Henrik Ravn 2004 +// +// Use, modification and distribution are subject to the Boost Software License, Version 1.0. +// (See accompanying file LICENSE_1_0.txt or copy at http://www.boost.org/LICENSE_1_0.txt) +// + +using System; +using System.Runtime.InteropServices; +using System.Text; + + +namespace DotZLib +{ + #region ChecksumGeneratorBase + /// + /// Implements the common functionality needed for all s + /// + /// + public abstract class ChecksumGeneratorBase : ChecksumGenerator + { + /// + /// The value of the current checksum + /// + protected uint _current; + + /// + /// Initializes a new instance of the checksum generator base - the current checksum is + /// set to zero + /// + public ChecksumGeneratorBase() + { + _current = 0; + } + + /// + /// Initializes a new instance of the checksum generator basewith a specified value + /// + /// The value to set the current checksum to + public ChecksumGeneratorBase(uint initialValue) + { + _current = initialValue; + } + + /// + /// Resets the current checksum to zero + /// + public void Reset() { _current = 0; } + + /// + /// Gets the current checksum value + /// + public uint Value { get { return _current; } } + + /// + /// Updates the current checksum with part of an array of bytes + /// + /// The data to update the checksum with + /// Where in data to start updating + /// The number of bytes from data to use + /// The sum of offset and count is larger than the length of data + /// data is a null reference + /// Offset or count is negative. + /// All the other Update methods are implmeneted in terms of this one. + /// This is therefore the only method a derived class has to implement + public abstract void Update(byte[] data, int offset, int count); + + /// + /// Updates the current checksum with an array of bytes. + /// + /// The data to update the checksum with + public void Update(byte[] data) + { + Update(data, 0, data.Length); + } + + /// + /// Updates the current checksum with the data from a string + /// + /// The string to update the checksum with + /// The characters in the string are converted by the UTF-8 encoding + public void Update(string data) + { + Update(Encoding.UTF8.GetBytes(data)); + } + + /// + /// Updates the current checksum with the data from a string, using a specific encoding + /// + /// The string to update the checksum with + /// The encoding to use + public void Update(string data, Encoding encoding) + { + Update(encoding.GetBytes(data)); + } + + } + #endregion + + #region CRC32 + /// + /// Implements a CRC32 checksum generator + /// + public sealed class CRC32Checksum : ChecksumGeneratorBase + { + #region DLL imports + + [DllImport("ZLIB1.dll", CallingConvention=CallingConvention.Cdecl)] + private static extern uint crc32(uint crc, int data, uint length); + + #endregion + + /// + /// Initializes a new instance of the CRC32 checksum generator + /// + public CRC32Checksum() : base() {} + + /// + /// Initializes a new instance of the CRC32 checksum generator with a specified value + /// + /// The value to set the current checksum to + public CRC32Checksum(uint initialValue) : base(initialValue) {} + + /// + /// Updates the current checksum with part of an array of bytes + /// + /// The data to update the checksum with + /// Where in data to start updating + /// The number of bytes from data to use + /// The sum of offset and count is larger than the length of data + /// data is a null reference + /// Offset or count is negative. + public override void Update(byte[] data, int offset, int count) + { + if (offset < 0 || count < 0) throw new ArgumentOutOfRangeException(); + if ((offset+count) > data.Length) throw new ArgumentException(); + GCHandle hData = GCHandle.Alloc(data, GCHandleType.Pinned); + try + { + _current = crc32(_current, hData.AddrOfPinnedObject().ToInt32()+offset, (uint)count); + } + finally + { + hData.Free(); + } + } + + } + #endregion + + #region Adler + /// + /// Implements a checksum generator that computes the Adler checksum on data + /// + public sealed class AdlerChecksum : ChecksumGeneratorBase + { + #region DLL imports + + [DllImport("ZLIB1.dll", CallingConvention=CallingConvention.Cdecl)] + private static extern uint adler32(uint adler, int data, uint length); + + #endregion + + /// + /// Initializes a new instance of the Adler checksum generator + /// + public AdlerChecksum() : base() {} + + /// + /// Initializes a new instance of the Adler checksum generator with a specified value + /// + /// The value to set the current checksum to + public AdlerChecksum(uint initialValue) : base(initialValue) {} + + /// + /// Updates the current checksum with part of an array of bytes + /// + /// The data to update the checksum with + /// Where in data to start updating + /// The number of bytes from data to use + /// The sum of offset and count is larger than the length of data + /// data is a null reference + /// Offset or count is negative. + public override void Update(byte[] data, int offset, int count) + { + if (offset < 0 || count < 0) throw new ArgumentOutOfRangeException(); + if ((offset+count) > data.Length) throw new ArgumentException(); + GCHandle hData = GCHandle.Alloc(data, GCHandleType.Pinned); + try + { + _current = adler32(_current, hData.AddrOfPinnedObject().ToInt32()+offset, (uint)count); + } + finally + { + hData.Free(); + } + } + + } + #endregion + +} ADDED compat/zlib/contrib/dotzlib/DotZLib/CircularBuffer.cs Index: compat/zlib/contrib/dotzlib/DotZLib/CircularBuffer.cs ================================================================== --- /dev/null +++ compat/zlib/contrib/dotzlib/DotZLib/CircularBuffer.cs @@ -0,0 +1,83 @@ +// +// Copyright Henrik Ravn 2004 +// +// Use, modification and distribution are subject to the Boost Software License, Version 1.0. +// (See accompanying file LICENSE_1_0.txt or copy at http://www.boost.org/LICENSE_1_0.txt) +// + +using System; +using System.Diagnostics; + +namespace DotZLib +{ + + /// + /// This class implements a circular buffer + /// + internal class CircularBuffer + { + #region Private data + private int _capacity; + private int _head; + private int _tail; + private int _size; + private byte[] _buffer; + #endregion + + public CircularBuffer(int capacity) + { + Debug.Assert( capacity > 0 ); + _buffer = new byte[capacity]; + _capacity = capacity; + _head = 0; + _tail = 0; + _size = 0; + } + + public int Size { get { return _size; } } + + public int Put(byte[] source, int offset, int count) + { + Debug.Assert( count > 0 ); + int trueCount = Math.Min(count, _capacity - Size); + for (int i = 0; i < trueCount; ++i) + _buffer[(_tail+i) % _capacity] = source[offset+i]; + _tail += trueCount; + _tail %= _capacity; + _size += trueCount; + return trueCount; + } + + public bool Put(byte b) + { + if (Size == _capacity) // no room + return false; + _buffer[_tail++] = b; + _tail %= _capacity; + ++_size; + return true; + } + + public int Get(byte[] destination, int offset, int count) + { + int trueCount = Math.Min(count,Size); + for (int i = 0; i < trueCount; ++i) + destination[offset + i] = _buffer[(_head+i) % _capacity]; + _head += trueCount; + _head %= _capacity; + _size -= trueCount; + return trueCount; + } + + public int Get() + { + if (Size == 0) + return -1; + + int result = (int)_buffer[_head++ % _capacity]; + --_size; + return result; + } + + } +} ADDED compat/zlib/contrib/dotzlib/DotZLib/CodecBase.cs Index: compat/zlib/contrib/dotzlib/DotZLib/CodecBase.cs ================================================================== --- /dev/null +++ compat/zlib/contrib/dotzlib/DotZLib/CodecBase.cs @@ -0,0 +1,198 @@ +// +// Copyright Henrik Ravn 2004 +// +// Use, modification and distribution are subject to the Boost Software License, Version 1.0. +// (See accompanying file LICENSE_1_0.txt or copy at http://www.boost.org/LICENSE_1_0.txt) +// + +using System; +using System.Runtime.InteropServices; + +namespace DotZLib +{ + /// + /// Implements the common functionality needed for all s + /// + public abstract class CodecBase : Codec, IDisposable + { + + #region Data members + + /// + /// Instance of the internal zlib buffer structure that is + /// passed to all functions in the zlib dll + /// + internal ZStream _ztream = new ZStream(); + + /// + /// True if the object instance has been disposed, false otherwise + /// + protected bool _isDisposed = false; + + /// + /// The size of the internal buffers + /// + protected const int kBufferSize = 16384; + + private byte[] _outBuffer = new byte[kBufferSize]; + private byte[] _inBuffer = new byte[kBufferSize]; + + private GCHandle _hInput; + private GCHandle _hOutput; + + private uint _checksum = 0; + + #endregion + + /// + /// Initializes a new instance of the CodeBase class. + /// + public CodecBase() + { + try + { + _hInput = GCHandle.Alloc(_inBuffer, GCHandleType.Pinned); + _hOutput = GCHandle.Alloc(_outBuffer, GCHandleType.Pinned); + } + catch (Exception) + { + CleanUp(false); + throw; + } + } + + + #region Codec Members + + /// + /// Occurs when more processed data are available. + /// + public event DataAvailableHandler DataAvailable; + + /// + /// Fires the event + /// + protected void OnDataAvailable() + { + if (_ztream.total_out > 0) + { + if (DataAvailable != null) + DataAvailable( _outBuffer, 0, (int)_ztream.total_out); + resetOutput(); + } + } + + /// + /// Adds more data to the codec to be processed. + /// + /// Byte array containing the data to be added to the codec + /// Adding data may, or may not, raise the DataAvailable event + public void Add(byte[] data) + { + Add(data,0,data.Length); + } + + /// + /// Adds more data to the codec to be processed. + /// + /// Byte array containing the data to be added to the codec + /// The index of the first byte to add from data + /// The number of bytes to add + /// Adding data may, or may not, raise the DataAvailable event + /// This must be implemented by a derived class + public abstract void Add(byte[] data, int offset, int count); + + /// + /// Finishes up any pending data that needs to be processed and handled. + /// + /// This must be implemented by a derived class + public abstract void Finish(); + + /// + /// Gets the checksum of the data that has been added so far + /// + public uint Checksum { get { return _checksum; } } + + #endregion + + #region Destructor & IDisposable stuff + + /// + /// Destroys this instance + /// + ~CodecBase() + { + CleanUp(false); + } + + /// + /// Releases any unmanaged resources and calls the method of the derived class + /// + public void Dispose() + { + CleanUp(true); + } + + /// + /// Performs any codec specific cleanup + /// + /// This must be implemented by a derived class + protected abstract void CleanUp(); + + // performs the release of the handles and calls the dereived CleanUp() + private void CleanUp(bool isDisposing) + { + if (!_isDisposed) + { + CleanUp(); + if (_hInput.IsAllocated) + _hInput.Free(); + if (_hOutput.IsAllocated) + _hOutput.Free(); + + _isDisposed = true; + } + } + + + #endregion + + #region Helper methods + + /// + /// Copies a number of bytes to the internal codec buffer - ready for proccesing + /// + /// The byte array that contains the data to copy + /// The index of the first byte to copy + /// The number of bytes to copy from data + protected void copyInput(byte[] data, int startIndex, int count) + { + Array.Copy(data, startIndex, _inBuffer,0, count); + _ztream.next_in = _hInput.AddrOfPinnedObject(); + _ztream.total_in = 0; + _ztream.avail_in = (uint)count; + + } + + /// + /// Resets the internal output buffers to a known state - ready for processing + /// + protected void resetOutput() + { + _ztream.total_out = 0; + _ztream.avail_out = kBufferSize; + _ztream.next_out = _hOutput.AddrOfPinnedObject(); + } + + /// + /// Updates the running checksum property + /// + /// The new checksum value + protected void setChecksum(uint newSum) + { + _checksum = newSum; + } + #endregion + + } +} ADDED compat/zlib/contrib/dotzlib/DotZLib/Deflater.cs Index: compat/zlib/contrib/dotzlib/DotZLib/Deflater.cs ================================================================== --- /dev/null +++ compat/zlib/contrib/dotzlib/DotZLib/Deflater.cs @@ -0,0 +1,106 @@ +// +// Copyright Henrik Ravn 2004 +// +// Use, modification and distribution are subject to the Boost Software License, Version 1.0. +// (See accompanying file LICENSE_1_0.txt or copy at http://www.boost.org/LICENSE_1_0.txt) +// + +using System; +using System.Diagnostics; +using System.Runtime.InteropServices; + +namespace DotZLib +{ + + /// + /// Implements a data compressor, using the deflate algorithm in the ZLib dll + /// + public sealed class Deflater : CodecBase + { + #region Dll imports + [DllImport("ZLIB1.dll", CallingConvention=CallingConvention.Cdecl, CharSet=CharSet.Ansi)] + private static extern int deflateInit_(ref ZStream sz, int level, string vs, int size); + + [DllImport("ZLIB1.dll", CallingConvention=CallingConvention.Cdecl)] + private static extern int deflate(ref ZStream sz, int flush); + + [DllImport("ZLIB1.dll", CallingConvention=CallingConvention.Cdecl)] + private static extern int deflateReset(ref ZStream sz); + + [DllImport("ZLIB1.dll", CallingConvention=CallingConvention.Cdecl)] + private static extern int deflateEnd(ref ZStream sz); + #endregion + + /// + /// Constructs an new instance of the Deflater + /// + /// The compression level to use for this Deflater + public Deflater(CompressLevel level) : base() + { + int retval = deflateInit_(ref _ztream, (int)level, Info.Version, Marshal.SizeOf(_ztream)); + if (retval != 0) + throw new ZLibException(retval, "Could not initialize deflater"); + + resetOutput(); + } + + /// + /// Adds more data to the codec to be processed. + /// + /// Byte array containing the data to be added to the codec + /// The index of the first byte to add from data + /// The number of bytes to add + /// Adding data may, or may not, raise the DataAvailable event + public override void Add(byte[] data, int offset, int count) + { + if (data == null) throw new ArgumentNullException(); + if (offset < 0 || count < 0) throw new ArgumentOutOfRangeException(); + if ((offset+count) > data.Length) throw new ArgumentException(); + + int total = count; + int inputIndex = offset; + int err = 0; + + while (err >= 0 && inputIndex < total) + { + copyInput(data, inputIndex, Math.Min(total - inputIndex, kBufferSize)); + while (err >= 0 && _ztream.avail_in > 0) + { + err = deflate(ref _ztream, (int)FlushTypes.None); + if (err == 0) + while (_ztream.avail_out == 0) + { + OnDataAvailable(); + err = deflate(ref _ztream, (int)FlushTypes.None); + } + inputIndex += (int)_ztream.total_in; + } + } + setChecksum( _ztream.adler ); + } + + + /// + /// Finishes up any pending data that needs to be processed and handled. + /// + public override void Finish() + { + int err; + do + { + err = deflate(ref _ztream, (int)FlushTypes.Finish); + OnDataAvailable(); + } + while (err == 0); + setChecksum( _ztream.adler ); + deflateReset(ref _ztream); + resetOutput(); + } + + /// + /// Closes the internal zlib deflate stream + /// + protected override void CleanUp() { deflateEnd(ref _ztream); } + + } +} ADDED compat/zlib/contrib/dotzlib/DotZLib/DotZLib.cs Index: compat/zlib/contrib/dotzlib/DotZLib/DotZLib.cs ================================================================== --- /dev/null +++ compat/zlib/contrib/dotzlib/DotZLib/DotZLib.cs @@ -0,0 +1,288 @@ +// +// Copyright Henrik Ravn 2004 +// +// Use, modification and distribution are subject to the Boost Software License, Version 1.0. +// (See accompanying file LICENSE_1_0.txt or copy at http://www.boost.org/LICENSE_1_0.txt) +// + +using System; +using System.IO; +using System.Runtime.InteropServices; +using System.Text; + + +namespace DotZLib +{ + + #region Internal types + + /// + /// Defines constants for the various flush types used with zlib + /// + internal enum FlushTypes + { + None, Partial, Sync, Full, Finish, Block + } + + #region ZStream structure + // internal mapping of the zlib zstream structure for marshalling + [StructLayoutAttribute(LayoutKind.Sequential, Pack=4, Size=0, CharSet=CharSet.Ansi)] + internal struct ZStream + { + public IntPtr next_in; + public uint avail_in; + public uint total_in; + + public IntPtr next_out; + public uint avail_out; + public uint total_out; + + [MarshalAs(UnmanagedType.LPStr)] + string msg; + uint state; + + uint zalloc; + uint zfree; + uint opaque; + + int data_type; + public uint adler; + uint reserved; + } + + #endregion + + #endregion + + #region Public enums + /// + /// Defines constants for the available compression levels in zlib + /// + public enum CompressLevel : int + { + /// + /// The default compression level with a reasonable compromise between compression and speed + /// + Default = -1, + /// + /// No compression at all. The data are passed straight through. + /// + None = 0, + /// + /// The maximum compression rate available. + /// + Best = 9, + /// + /// The fastest available compression level. + /// + Fastest = 1 + } + #endregion + + #region Exception classes + /// + /// The exception that is thrown when an error occurs on the zlib dll + /// + public class ZLibException : ApplicationException + { + /// + /// Initializes a new instance of the class with a specified + /// error message and error code + /// + /// The zlib error code that caused the exception + /// A message that (hopefully) describes the error + public ZLibException(int errorCode, string msg) : base(String.Format("ZLib error {0} {1}", errorCode, msg)) + { + } + + /// + /// Initializes a new instance of the class with a specified + /// error code + /// + /// The zlib error code that caused the exception + public ZLibException(int errorCode) : base(String.Format("ZLib error {0}", errorCode)) + { + } + } + #endregion + + #region Interfaces + + /// + /// Declares methods and properties that enables a running checksum to be calculated + /// + public interface ChecksumGenerator + { + /// + /// Gets the current value of the checksum + /// + uint Value { get; } + + /// + /// Clears the current checksum to 0 + /// + void Reset(); + + /// + /// Updates the current checksum with an array of bytes + /// + /// The data to update the checksum with + void Update(byte[] data); + + /// + /// Updates the current checksum with part of an array of bytes + /// + /// The data to update the checksum with + /// Where in data to start updating + /// The number of bytes from data to use + /// The sum of offset and count is larger than the length of data + /// data is a null reference + /// Offset or count is negative. + void Update(byte[] data, int offset, int count); + + /// + /// Updates the current checksum with the data from a string + /// + /// The string to update the checksum with + /// The characters in the string are converted by the UTF-8 encoding + void Update(string data); + + /// + /// Updates the current checksum with the data from a string, using a specific encoding + /// + /// The string to update the checksum with + /// The encoding to use + void Update(string data, Encoding encoding); + } + + + /// + /// Represents the method that will be called from a codec when new data + /// are available. + /// + /// The byte array containing the processed data + /// The index of the first processed byte in data + /// The number of processed bytes available + /// On return from this method, the data may be overwritten, so grab it while you can. + /// You cannot assume that startIndex will be zero. + /// + public delegate void DataAvailableHandler(byte[] data, int startIndex, int count); + + /// + /// Declares methods and events for implementing compressors/decompressors + /// + public interface Codec + { + /// + /// Occurs when more processed data are available. + /// + event DataAvailableHandler DataAvailable; + + /// + /// Adds more data to the codec to be processed. + /// + /// Byte array containing the data to be added to the codec + /// Adding data may, or may not, raise the DataAvailable event + void Add(byte[] data); + + /// + /// Adds more data to the codec to be processed. + /// + /// Byte array containing the data to be added to the codec + /// The index of the first byte to add from data + /// The number of bytes to add + /// Adding data may, or may not, raise the DataAvailable event + void Add(byte[] data, int offset, int count); + + /// + /// Finishes up any pending data that needs to be processed and handled. + /// + void Finish(); + + /// + /// Gets the checksum of the data that has been added so far + /// + uint Checksum { get; } + + + } + + #endregion + + #region Classes + /// + /// Encapsulates general information about the ZLib library + /// + public class Info + { + #region DLL imports + [DllImport("ZLIB1.dll", CallingConvention=CallingConvention.Cdecl)] + private static extern uint zlibCompileFlags(); + + [DllImport("ZLIB1.dll", CallingConvention=CallingConvention.Cdecl)] + private static extern string zlibVersion(); + #endregion + + #region Private stuff + private uint _flags; + + // helper function that unpacks a bitsize mask + private static int bitSize(uint bits) + { + switch (bits) + { + case 0: return 16; + case 1: return 32; + case 2: return 64; + } + return -1; + } + #endregion + + /// + /// Constructs an instance of the Info class. + /// + public Info() + { + _flags = zlibCompileFlags(); + } + + /// + /// True if the library is compiled with debug info + /// + public bool HasDebugInfo { get { return 0 != (_flags & 0x100); } } + + /// + /// True if the library is compiled with assembly optimizations + /// + public bool UsesAssemblyCode { get { return 0 != (_flags & 0x200); } } + + /// + /// Gets the size of the unsigned int that was compiled into Zlib + /// + public int SizeOfUInt { get { return bitSize(_flags & 3); } } + + /// + /// Gets the size of the unsigned long that was compiled into Zlib + /// + public int SizeOfULong { get { return bitSize((_flags >> 2) & 3); } } + + /// + /// Gets the size of the pointers that were compiled into Zlib + /// + public int SizeOfPointer { get { return bitSize((_flags >> 4) & 3); } } + + /// + /// Gets the size of the z_off_t type that was compiled into Zlib + /// + public int SizeOfOffset { get { return bitSize((_flags >> 6) & 3); } } + + /// + /// Gets the version of ZLib as a string, e.g. "1.2.1" + /// + public static string Version { get { return zlibVersion(); } } + } + + #endregion + +} ADDED compat/zlib/contrib/dotzlib/DotZLib/DotZLib.csproj Index: compat/zlib/contrib/dotzlib/DotZLib/DotZLib.csproj ================================================================== --- /dev/null +++ compat/zlib/contrib/dotzlib/DotZLib/DotZLib.csproj @@ -0,0 +1,141 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + ADDED compat/zlib/contrib/dotzlib/DotZLib/GZipStream.cs Index: compat/zlib/contrib/dotzlib/DotZLib/GZipStream.cs ================================================================== --- /dev/null +++ compat/zlib/contrib/dotzlib/DotZLib/GZipStream.cs @@ -0,0 +1,301 @@ +// +// Copyright Henrik Ravn 2004 +// +// Use, modification and distribution are subject to the Boost Software License, Version 1.0. +// (See accompanying file LICENSE_1_0.txt or copy at http://www.boost.org/LICENSE_1_0.txt) +// + +using System; +using System.IO; +using System.Runtime.InteropServices; + +namespace DotZLib +{ + /// + /// Implements a compressed , in GZip (.gz) format. + /// + public class GZipStream : Stream, IDisposable + { + #region Dll Imports + [DllImport("ZLIB1.dll", CallingConvention=CallingConvention.Cdecl, CharSet=CharSet.Ansi)] + private static extern IntPtr gzopen(string name, string mode); + + [DllImport("ZLIB1.dll", CallingConvention=CallingConvention.Cdecl)] + private static extern int gzclose(IntPtr gzFile); + + [DllImport("ZLIB1.dll", CallingConvention=CallingConvention.Cdecl)] + private static extern int gzwrite(IntPtr gzFile, int data, int length); + + [DllImport("ZLIB1.dll", CallingConvention=CallingConvention.Cdecl)] + private static extern int gzread(IntPtr gzFile, int data, int length); + + [DllImport("ZLIB1.dll", CallingConvention=CallingConvention.Cdecl)] + private static extern int gzgetc(IntPtr gzFile); + + [DllImport("ZLIB1.dll", CallingConvention=CallingConvention.Cdecl)] + private static extern int gzputc(IntPtr gzFile, int c); + + #endregion + + #region Private data + private IntPtr _gzFile; + private bool _isDisposed = false; + private bool _isWriting; + #endregion + + #region Constructors + /// + /// Creates a new file as a writeable GZipStream + /// + /// The name of the compressed file to create + /// The compression level to use when adding data + /// If an error occurred in the internal zlib function + public GZipStream(string fileName, CompressLevel level) + { + _isWriting = true; + _gzFile = gzopen(fileName, String.Format("wb{0}", (int)level)); + if (_gzFile == IntPtr.Zero) + throw new ZLibException(-1, "Could not open " + fileName); + } + + /// + /// Opens an existing file as a readable GZipStream + /// + /// The name of the file to open + /// If an error occurred in the internal zlib function + public GZipStream(string fileName) + { + _isWriting = false; + _gzFile = gzopen(fileName, "rb"); + if (_gzFile == IntPtr.Zero) + throw new ZLibException(-1, "Could not open " + fileName); + + } + #endregion + + #region Access properties + /// + /// Returns true of this stream can be read from, false otherwise + /// + public override bool CanRead + { + get + { + return !_isWriting; + } + } + + + /// + /// Returns false. + /// + public override bool CanSeek + { + get + { + return false; + } + } + + /// + /// Returns true if this tsream is writeable, false otherwise + /// + public override bool CanWrite + { + get + { + return _isWriting; + } + } + #endregion + + #region Destructor & IDispose stuff + + /// + /// Destroys this instance + /// + ~GZipStream() + { + cleanUp(false); + } + + /// + /// Closes the external file handle + /// + public void Dispose() + { + cleanUp(true); + } + + // Does the actual closing of the file handle. + private void cleanUp(bool isDisposing) + { + if (!_isDisposed) + { + gzclose(_gzFile); + _isDisposed = true; + } + } + #endregion + + #region Basic reading and writing + /// + /// Attempts to read a number of bytes from the stream. + /// + /// The destination data buffer + /// The index of the first destination byte in buffer + /// The number of bytes requested + /// The number of bytes read + /// If buffer is null + /// If count or offset are negative + /// If offset + count is > buffer.Length + /// If this stream is not readable. + /// If this stream has been disposed. + public override int Read(byte[] buffer, int offset, int count) + { + if (!CanRead) throw new NotSupportedException(); + if (buffer == null) throw new ArgumentNullException(); + if (offset < 0 || count < 0) throw new ArgumentOutOfRangeException(); + if ((offset+count) > buffer.Length) throw new ArgumentException(); + if (_isDisposed) throw new ObjectDisposedException("GZipStream"); + + GCHandle h = GCHandle.Alloc(buffer, GCHandleType.Pinned); + int result; + try + { + result = gzread(_gzFile, h.AddrOfPinnedObject().ToInt32() + offset, count); + if (result < 0) + throw new IOException(); + } + finally + { + h.Free(); + } + return result; + } + + /// + /// Attempts to read a single byte from the stream. + /// + /// The byte that was read, or -1 in case of error or End-Of-File + public override int ReadByte() + { + if (!CanRead) throw new NotSupportedException(); + if (_isDisposed) throw new ObjectDisposedException("GZipStream"); + return gzgetc(_gzFile); + } + + /// + /// Writes a number of bytes to the stream + /// + /// + /// + /// + /// If buffer is null + /// If count or offset are negative + /// If offset + count is > buffer.Length + /// If this stream is not writeable. + /// If this stream has been disposed. + public override void Write(byte[] buffer, int offset, int count) + { + if (!CanWrite) throw new NotSupportedException(); + if (buffer == null) throw new ArgumentNullException(); + if (offset < 0 || count < 0) throw new ArgumentOutOfRangeException(); + if ((offset+count) > buffer.Length) throw new ArgumentException(); + if (_isDisposed) throw new ObjectDisposedException("GZipStream"); + + GCHandle h = GCHandle.Alloc(buffer, GCHandleType.Pinned); + try + { + int result = gzwrite(_gzFile, h.AddrOfPinnedObject().ToInt32() + offset, count); + if (result < 0) + throw new IOException(); + } + finally + { + h.Free(); + } + } + + /// + /// Writes a single byte to the stream + /// + /// The byte to add to the stream. + /// If this stream is not writeable. + /// If this stream has been disposed. + public override void WriteByte(byte value) + { + if (!CanWrite) throw new NotSupportedException(); + if (_isDisposed) throw new ObjectDisposedException("GZipStream"); + + int result = gzputc(_gzFile, (int)value); + if (result < 0) + throw new IOException(); + } + #endregion + + #region Position & length stuff + /// + /// Not supported. + /// + /// + /// Always thrown + public override void SetLength(long value) + { + throw new NotSupportedException(); + } + + /// + /// Not suppported. + /// + /// + /// + /// + /// Always thrown + public override long Seek(long offset, SeekOrigin origin) + { + throw new NotSupportedException(); + } + + /// + /// Flushes the GZipStream. + /// + /// In this implementation, this method does nothing. This is because excessive + /// flushing may degrade the achievable compression rates. + public override void Flush() + { + // left empty on purpose + } + + /// + /// Gets/sets the current position in the GZipStream. Not suppported. + /// + /// In this implementation this property is not supported + /// Always thrown + public override long Position + { + get + { + throw new NotSupportedException(); + } + set + { + throw new NotSupportedException(); + } + } + + /// + /// Gets the size of the stream. Not suppported. + /// + /// In this implementation this property is not supported + /// Always thrown + public override long Length + { + get + { + throw new NotSupportedException(); + } + } + #endregion + } +} ADDED compat/zlib/contrib/dotzlib/DotZLib/Inflater.cs Index: compat/zlib/contrib/dotzlib/DotZLib/Inflater.cs ================================================================== --- /dev/null +++ compat/zlib/contrib/dotzlib/DotZLib/Inflater.cs @@ -0,0 +1,105 @@ +// +// Copyright Henrik Ravn 2004 +// +// Use, modification and distribution are subject to the Boost Software License, Version 1.0. +// (See accompanying file LICENSE_1_0.txt or copy at http://www.boost.org/LICENSE_1_0.txt) +// + +using System; +using System.Diagnostics; +using System.Runtime.InteropServices; + +namespace DotZLib +{ + + /// + /// Implements a data decompressor, using the inflate algorithm in the ZLib dll + /// + public class Inflater : CodecBase + { + #region Dll imports + [DllImport("ZLIB1.dll", CallingConvention=CallingConvention.Cdecl, CharSet=CharSet.Ansi)] + private static extern int inflateInit_(ref ZStream sz, string vs, int size); + + [DllImport("ZLIB1.dll", CallingConvention=CallingConvention.Cdecl)] + private static extern int inflate(ref ZStream sz, int flush); + + [DllImport("ZLIB1.dll", CallingConvention=CallingConvention.Cdecl)] + private static extern int inflateReset(ref ZStream sz); + + [DllImport("ZLIB1.dll", CallingConvention=CallingConvention.Cdecl)] + private static extern int inflateEnd(ref ZStream sz); + #endregion + + /// + /// Constructs an new instance of the Inflater + /// + public Inflater() : base() + { + int retval = inflateInit_(ref _ztream, Info.Version, Marshal.SizeOf(_ztream)); + if (retval != 0) + throw new ZLibException(retval, "Could not initialize inflater"); + + resetOutput(); + } + + + /// + /// Adds more data to the codec to be processed. + /// + /// Byte array containing the data to be added to the codec + /// The index of the first byte to add from data + /// The number of bytes to add + /// Adding data may, or may not, raise the DataAvailable event + public override void Add(byte[] data, int offset, int count) + { + if (data == null) throw new ArgumentNullException(); + if (offset < 0 || count < 0) throw new ArgumentOutOfRangeException(); + if ((offset+count) > data.Length) throw new ArgumentException(); + + int total = count; + int inputIndex = offset; + int err = 0; + + while (err >= 0 && inputIndex < total) + { + copyInput(data, inputIndex, Math.Min(total - inputIndex, kBufferSize)); + err = inflate(ref _ztream, (int)FlushTypes.None); + if (err == 0) + while (_ztream.avail_out == 0) + { + OnDataAvailable(); + err = inflate(ref _ztream, (int)FlushTypes.None); + } + + inputIndex += (int)_ztream.total_in; + } + setChecksum( _ztream.adler ); + } + + + /// + /// Finishes up any pending data that needs to be processed and handled. + /// + public override void Finish() + { + int err; + do + { + err = inflate(ref _ztream, (int)FlushTypes.Finish); + OnDataAvailable(); + } + while (err == 0); + setChecksum( _ztream.adler ); + inflateReset(ref _ztream); + resetOutput(); + } + + /// + /// Closes the internal zlib inflate stream + /// + protected override void CleanUp() { inflateEnd(ref _ztream); } + + + } +} ADDED compat/zlib/contrib/dotzlib/DotZLib/UnitTests.cs Index: compat/zlib/contrib/dotzlib/DotZLib/UnitTests.cs ================================================================== --- /dev/null +++ compat/zlib/contrib/dotzlib/DotZLib/UnitTests.cs @@ -0,0 +1,274 @@ +// +// © Copyright Henrik Ravn 2004 +// +// Use, modification and distribution are subject to the Boost Software License, Version 1.0. +// (See accompanying file LICENSE_1_0.txt or copy at http://www.boost.org/LICENSE_1_0.txt) +// + +using System; +using System.Collections; +using System.IO; + +// uncomment the define below to include unit tests +//#define nunit +#if nunit +using NUnit.Framework; + +// Unit tests for the DotZLib class library +// ---------------------------------------- +// +// Use this with NUnit 2 from http://www.nunit.org +// + +namespace DotZLibTests +{ + using DotZLib; + + // helper methods + internal class Utils + { + public static bool byteArrEqual( byte[] lhs, byte[] rhs ) + { + if (lhs.Length != rhs.Length) + return false; + for (int i = lhs.Length-1; i >= 0; --i) + if (lhs[i] != rhs[i]) + return false; + return true; + } + + } + + + [TestFixture] + public class CircBufferTests + { + #region Circular buffer tests + [Test] + public void SinglePutGet() + { + CircularBuffer buf = new CircularBuffer(10); + Assert.AreEqual( 0, buf.Size ); + Assert.AreEqual( -1, buf.Get() ); + + Assert.IsTrue(buf.Put( 1 )); + Assert.AreEqual( 1, buf.Size ); + Assert.AreEqual( 1, buf.Get() ); + Assert.AreEqual( 0, buf.Size ); + Assert.AreEqual( -1, buf.Get() ); + } + + [Test] + public void BlockPutGet() + { + CircularBuffer buf = new CircularBuffer(10); + byte[] arr = {1,2,3,4,5,6,7,8,9,10}; + Assert.AreEqual( 10, buf.Put(arr,0,10) ); + Assert.AreEqual( 10, buf.Size ); + Assert.IsFalse( buf.Put(11) ); + Assert.AreEqual( 1, buf.Get() ); + Assert.IsTrue( buf.Put(11) ); + + byte[] arr2 = (byte[])arr.Clone(); + Assert.AreEqual( 9, buf.Get(arr2,1,9) ); + Assert.IsTrue( Utils.byteArrEqual(arr,arr2) ); + } + + #endregion + } + + [TestFixture] + public class ChecksumTests + { + #region CRC32 Tests + [Test] + public void CRC32_Null() + { + CRC32Checksum crc32 = new CRC32Checksum(); + Assert.AreEqual( 0, crc32.Value ); + + crc32 = new CRC32Checksum(1); + Assert.AreEqual( 1, crc32.Value ); + + crc32 = new CRC32Checksum(556); + Assert.AreEqual( 556, crc32.Value ); + } + + [Test] + public void CRC32_Data() + { + CRC32Checksum crc32 = new CRC32Checksum(); + byte[] data = { 1,2,3,4,5,6,7 }; + crc32.Update(data); + Assert.AreEqual( 0x70e46888, crc32.Value ); + + crc32 = new CRC32Checksum(); + crc32.Update("penguin"); + Assert.AreEqual( 0x0e5c1a120, crc32.Value ); + + crc32 = new CRC32Checksum(1); + crc32.Update("penguin"); + Assert.AreEqual(0x43b6aa94, crc32.Value); + + } + #endregion + + #region Adler tests + + [Test] + public void Adler_Null() + { + AdlerChecksum adler = new AdlerChecksum(); + Assert.AreEqual(0, adler.Value); + + adler = new AdlerChecksum(1); + Assert.AreEqual( 1, adler.Value ); + + adler = new AdlerChecksum(556); + Assert.AreEqual( 556, adler.Value ); + } + + [Test] + public void Adler_Data() + { + AdlerChecksum adler = new AdlerChecksum(1); + byte[] data = { 1,2,3,4,5,6,7 }; + adler.Update(data); + Assert.AreEqual( 0x5b001d, adler.Value ); + + adler = new AdlerChecksum(); + adler.Update("penguin"); + Assert.AreEqual(0x0bcf02f6, adler.Value ); + + adler = new AdlerChecksum(1); + adler.Update("penguin"); + Assert.AreEqual(0x0bd602f7, adler.Value); + + } + #endregion + } + + [TestFixture] + public class InfoTests + { + #region Info tests + [Test] + public void Info_Version() + { + Info info = new Info(); + Assert.AreEqual("1.2.11", Info.Version); + Assert.AreEqual(32, info.SizeOfUInt); + Assert.AreEqual(32, info.SizeOfULong); + Assert.AreEqual(32, info.SizeOfPointer); + Assert.AreEqual(32, info.SizeOfOffset); + } + #endregion + } + + [TestFixture] + public class DeflateInflateTests + { + #region Deflate tests + [Test] + public void Deflate_Init() + { + using (Deflater def = new Deflater(CompressLevel.Default)) + { + } + } + + private ArrayList compressedData = new ArrayList(); + private uint adler1; + + private ArrayList uncompressedData = new ArrayList(); + private uint adler2; + + public void CDataAvail(byte[] data, int startIndex, int count) + { + for (int i = 0; i < count; ++i) + compressedData.Add(data[i+startIndex]); + } + + [Test] + public void Deflate_Compress() + { + compressedData.Clear(); + + byte[] testData = new byte[35000]; + for (int i = 0; i < testData.Length; ++i) + testData[i] = 5; + + using (Deflater def = new Deflater((CompressLevel)5)) + { + def.DataAvailable += new DataAvailableHandler(CDataAvail); + def.Add(testData); + def.Finish(); + adler1 = def.Checksum; + } + } + #endregion + + #region Inflate tests + [Test] + public void Inflate_Init() + { + using (Inflater inf = new Inflater()) + { + } + } + + private void DDataAvail(byte[] data, int startIndex, int count) + { + for (int i = 0; i < count; ++i) + uncompressedData.Add(data[i+startIndex]); + } + + [Test] + public void Inflate_Expand() + { + uncompressedData.Clear(); + + using (Inflater inf = new Inflater()) + { + inf.DataAvailable += new DataAvailableHandler(DDataAvail); + inf.Add((byte[])compressedData.ToArray(typeof(byte))); + inf.Finish(); + adler2 = inf.Checksum; + } + Assert.AreEqual( adler1, adler2 ); + } + #endregion + } + + [TestFixture] + public class GZipStreamTests + { + #region GZipStream test + [Test] + public void GZipStream_WriteRead() + { + using (GZipStream gzOut = new GZipStream("gzstream.gz", CompressLevel.Best)) + { + BinaryWriter writer = new BinaryWriter(gzOut); + writer.Write("hi there"); + writer.Write(Math.PI); + writer.Write(42); + } + + using (GZipStream gzIn = new GZipStream("gzstream.gz")) + { + BinaryReader reader = new BinaryReader(gzIn); + string s = reader.ReadString(); + Assert.AreEqual("hi there",s); + double d = reader.ReadDouble(); + Assert.AreEqual(Math.PI, d); + int i = reader.ReadInt32(); + Assert.AreEqual(42,i); + } + + } + #endregion + } +} + +#endif ADDED compat/zlib/contrib/dotzlib/LICENSE_1_0.txt Index: compat/zlib/contrib/dotzlib/LICENSE_1_0.txt ================================================================== --- /dev/null +++ compat/zlib/contrib/dotzlib/LICENSE_1_0.txt @@ -0,0 +1,23 @@ +Boost Software License - Version 1.0 - August 17th, 2003 + +Permission is hereby granted, free of charge, to any person or organization +obtaining a copy of the software and accompanying documentation covered by +this license (the "Software") to use, reproduce, display, distribute, +execute, and transmit the Software, and to prepare derivative works of the +Software, and to permit third-parties to whom the Software is furnished to +do so, all subject to the following: + +The copyright notices in the Software and this entire statement, including +the above license grant, this restriction and the following disclaimer, +must be included in all copies of the Software, in whole or in part, and +all derivative works of the Software, unless such copies or derivative +works are solely in the form of machine-executable object code generated by +a source language processor. + +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, TITLE AND NON-INFRINGEMENT. IN NO EVENT +SHALL THE COPYRIGHT HOLDERS OR ANYONE DISTRIBUTING THE SOFTWARE BE LIABLE +FOR ANY DAMAGES OR OTHER LIABILITY, WHETHER IN CONTRACT, TORT OR OTHERWISE, +ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER +DEALINGS IN THE SOFTWARE. ADDED compat/zlib/contrib/dotzlib/readme.txt Index: compat/zlib/contrib/dotzlib/readme.txt ================================================================== --- /dev/null +++ compat/zlib/contrib/dotzlib/readme.txt @@ -0,0 +1,58 @@ +This directory contains a .Net wrapper class library for the ZLib1.dll + +The wrapper includes support for inflating/deflating memory buffers, +.Net streaming wrappers for the gz streams part of zlib, and wrappers +for the checksum parts of zlib. See DotZLib/UnitTests.cs for examples. + +Directory structure: +-------------------- + +LICENSE_1_0.txt - License file. +readme.txt - This file. +DotZLib.chm - Class library documentation +DotZLib.build - NAnt build file +DotZLib.sln - Microsoft Visual Studio 2003 solution file + +DotZLib\*.cs - Source files for the class library + +Unit tests: +----------- +The file DotZLib/UnitTests.cs contains unit tests for use with NUnit 2.1 or higher. +To include unit tests in the build, define nunit before building. + + +Build instructions: +------------------- + +1. Using Visual Studio.Net 2003: + Open DotZLib.sln in VS.Net and build from there. Output file (DotZLib.dll) + will be found ./DotZLib/bin/release or ./DotZLib/bin/debug, depending on + you are building the release or debug version of the library. Check + DotZLib/UnitTests.cs for instructions on how to include unit tests in the + build. + +2. Using NAnt: + Open a command prompt with access to the build environment and run nant + in the same directory as the DotZLib.build file. + You can define 2 properties on the nant command-line to control the build: + debug={true|false} to toggle between release/debug builds (default=true). + nunit={true|false} to include or esclude unit tests (default=true). + Also the target clean will remove binaries. + Output file (DotZLib.dll) will be found in either ./DotZLib/bin/release + or ./DotZLib/bin/debug, depending on whether you are building the release + or debug version of the library. + + Examples: + nant -D:debug=false -D:nunit=false + will build a release mode version of the library without unit tests. + nant + will build a debug version of the library with unit tests + nant clean + will remove all previously built files. + + +--------------------------------- +Copyright (c) Henrik Ravn 2004 + +Use, modification and distribution are subject to the Boost Software License, Version 1.0. +(See accompanying file LICENSE_1_0.txt or copy at http://www.boost.org/LICENSE_1_0.txt) ADDED compat/zlib/contrib/gcc_gvmat64/gvmat64.S Index: compat/zlib/contrib/gcc_gvmat64/gvmat64.S ================================================================== --- /dev/null +++ compat/zlib/contrib/gcc_gvmat64/gvmat64.S @@ -0,0 +1,574 @@ +/* +;uInt longest_match_x64( +; deflate_state *s, +; IPos cur_match); // current match + +; gvmat64.S -- Asm portion of the optimized longest_match for 32 bits x86_64 +; (AMD64 on Athlon 64, Opteron, Phenom +; and Intel EM64T on Pentium 4 with EM64T, Pentium D, Core 2 Duo, Core I5/I7) +; this file is translation from gvmat64.asm to GCC 4.x (for Linux, Mac XCode) +; Copyright (C) 1995-2010 Jean-loup Gailly, Brian Raiter and Gilles Vollant. +; +; File written by Gilles Vollant, by converting to assembly the longest_match +; from Jean-loup Gailly in deflate.c of zLib and infoZip zip. +; and by taking inspiration on asm686 with masm, optimised assembly code +; from Brian Raiter, written 1998 +; +; This software is provided 'as-is', without any express or implied +; warranty. In no event will the authors be held liable for any damages +; arising from the use of this software. +; +; Permission is granted to anyone to use this software for any purpose, +; including commercial applications, and to alter it and redistribute it +; freely, subject to the following restrictions: +; +; 1. The origin of this software must not be misrepresented; you must not +; claim that you wrote the original software. If you use this software +; in a product, an acknowledgment in the product documentation would be +; appreciated but is not required. +; 2. Altered source versions must be plainly marked as such, and must not be +; misrepresented as being the original software +; 3. This notice may not be removed or altered from any source distribution. +; +; http://www.zlib.net +; http://www.winimage.com/zLibDll +; http://www.muppetlabs.com/~breadbox/software/assembly.html +; +; to compile this file for zLib, I use option: +; gcc -c -arch x86_64 gvmat64.S + + +;uInt longest_match(s, cur_match) +; deflate_state *s; +; IPos cur_match; // current match / +; +; with XCode for Mac, I had strange error with some jump on intel syntax +; this is why BEFORE_JMP and AFTER_JMP are used + */ + + +#define BEFORE_JMP .att_syntax +#define AFTER_JMP .intel_syntax noprefix + +#ifndef NO_UNDERLINE +# define match_init _match_init +# define longest_match _longest_match +#endif + +.intel_syntax noprefix + +.globl match_init, longest_match +.text +longest_match: + + + +#define LocalVarsSize 96 +/* +; register used : rax,rbx,rcx,rdx,rsi,rdi,r8,r9,r10,r11,r12 +; free register : r14,r15 +; register can be saved : rsp +*/ + +#define chainlenwmask (rsp + 8 - LocalVarsSize) +#define nicematch (rsp + 16 - LocalVarsSize) + +#define save_rdi (rsp + 24 - LocalVarsSize) +#define save_rsi (rsp + 32 - LocalVarsSize) +#define save_rbx (rsp + 40 - LocalVarsSize) +#define save_rbp (rsp + 48 - LocalVarsSize) +#define save_r12 (rsp + 56 - LocalVarsSize) +#define save_r13 (rsp + 64 - LocalVarsSize) +#define save_r14 (rsp + 72 - LocalVarsSize) +#define save_r15 (rsp + 80 - LocalVarsSize) + + +/* +; all the +4 offsets are due to the addition of pending_buf_size (in zlib +; in the deflate_state structure since the asm code was first written +; (if you compile with zlib 1.0.4 or older, remove the +4). +; Note : these value are good with a 8 bytes boundary pack structure +*/ + +#define MAX_MATCH 258 +#define MIN_MATCH 3 +#define MIN_LOOKAHEAD (MAX_MATCH+MIN_MATCH+1) + +/* +;;; Offsets for fields in the deflate_state structure. These numbers +;;; are calculated from the definition of deflate_state, with the +;;; assumption that the compiler will dword-align the fields. (Thus, +;;; changing the definition of deflate_state could easily cause this +;;; program to crash horribly, without so much as a warning at +;;; compile time. Sigh.) + +; all the +zlib1222add offsets are due to the addition of fields +; in zlib in the deflate_state structure since the asm code was first written +; (if you compile with zlib 1.0.4 or older, use "zlib1222add equ (-4)"). +; (if you compile with zlib between 1.0.5 and 1.2.2.1, use "zlib1222add equ 0"). +; if you compile with zlib 1.2.2.2 or later , use "zlib1222add equ 8"). +*/ + + + +/* you can check the structure offset by running + +#include +#include +#include "deflate.h" + +void print_depl() +{ +deflate_state ds; +deflate_state *s=&ds; +printf("size pointer=%u\n",(int)sizeof(void*)); + +printf("#define dsWSize %u\n",(int)(((char*)&(s->w_size))-((char*)s))); +printf("#define dsWMask %u\n",(int)(((char*)&(s->w_mask))-((char*)s))); +printf("#define dsWindow %u\n",(int)(((char*)&(s->window))-((char*)s))); +printf("#define dsPrev %u\n",(int)(((char*)&(s->prev))-((char*)s))); +printf("#define dsMatchLen %u\n",(int)(((char*)&(s->match_length))-((char*)s))); +printf("#define dsPrevMatch %u\n",(int)(((char*)&(s->prev_match))-((char*)s))); +printf("#define dsStrStart %u\n",(int)(((char*)&(s->strstart))-((char*)s))); +printf("#define dsMatchStart %u\n",(int)(((char*)&(s->match_start))-((char*)s))); +printf("#define dsLookahead %u\n",(int)(((char*)&(s->lookahead))-((char*)s))); +printf("#define dsPrevLen %u\n",(int)(((char*)&(s->prev_length))-((char*)s))); +printf("#define dsMaxChainLen %u\n",(int)(((char*)&(s->max_chain_length))-((char*)s))); +printf("#define dsGoodMatch %u\n",(int)(((char*)&(s->good_match))-((char*)s))); +printf("#define dsNiceMatch %u\n",(int)(((char*)&(s->nice_match))-((char*)s))); +} +*/ + +#define dsWSize 68 +#define dsWMask 76 +#define dsWindow 80 +#define dsPrev 96 +#define dsMatchLen 144 +#define dsPrevMatch 148 +#define dsStrStart 156 +#define dsMatchStart 160 +#define dsLookahead 164 +#define dsPrevLen 168 +#define dsMaxChainLen 172 +#define dsGoodMatch 188 +#define dsNiceMatch 192 + +#define window_size [ rcx + dsWSize] +#define WMask [ rcx + dsWMask] +#define window_ad [ rcx + dsWindow] +#define prev_ad [ rcx + dsPrev] +#define strstart [ rcx + dsStrStart] +#define match_start [ rcx + dsMatchStart] +#define Lookahead [ rcx + dsLookahead] //; 0ffffffffh on infozip +#define prev_length [ rcx + dsPrevLen] +#define max_chain_length [ rcx + dsMaxChainLen] +#define good_match [ rcx + dsGoodMatch] +#define nice_match [ rcx + dsNiceMatch] + +/* +; windows: +; parameter 1 in rcx(deflate state s), param 2 in rdx (cur match) + +; see http://weblogs.asp.net/oldnewthing/archive/2004/01/14/58579.aspx and +; http://msdn.microsoft.com/library/en-us/kmarch/hh/kmarch/64bitAMD_8e951dd2-ee77-4728-8702-55ce4b5dd24a.xml.asp +; +; All registers must be preserved across the call, except for +; rax, rcx, rdx, r8, r9, r10, and r11, which are scratch. + +; +; gcc on macosx-linux: +; see http://www.x86-64.org/documentation/abi-0.99.pdf +; param 1 in rdi, param 2 in rsi +; rbx, rsp, rbp, r12 to r15 must be preserved + +;;; Save registers that the compiler may be using, and adjust esp to +;;; make room for our stack frame. + + +;;; Retrieve the function arguments. r8d will hold cur_match +;;; throughout the entire function. edx will hold the pointer to the +;;; deflate_state structure during the function's setup (before +;;; entering the main loop. + +; ms: parameter 1 in rcx (deflate_state* s), param 2 in edx -> r8 (cur match) +; mac: param 1 in rdi, param 2 rsi +; this clear high 32 bits of r8, which can be garbage in both r8 and rdx +*/ + mov [save_rbx],rbx + mov [save_rbp],rbp + + + mov rcx,rdi + + mov r8d,esi + + + mov [save_r12],r12 + mov [save_r13],r13 + mov [save_r14],r14 + mov [save_r15],r15 + + +//;;; uInt wmask = s->w_mask; +//;;; unsigned chain_length = s->max_chain_length; +//;;; if (s->prev_length >= s->good_match) { +//;;; chain_length >>= 2; +//;;; } + + + mov edi, prev_length + mov esi, good_match + mov eax, WMask + mov ebx, max_chain_length + cmp edi, esi + jl LastMatchGood + shr ebx, 2 +LastMatchGood: + +//;;; chainlen is decremented once beforehand so that the function can +//;;; use the sign flag instead of the zero flag for the exit test. +//;;; It is then shifted into the high word, to make room for the wmask +//;;; value, which it will always accompany. + + dec ebx + shl ebx, 16 + or ebx, eax + +//;;; on zlib only +//;;; if ((uInt)nice_match > s->lookahead) nice_match = s->lookahead; + + + + mov eax, nice_match + mov [chainlenwmask], ebx + mov r10d, Lookahead + cmp r10d, eax + cmovnl r10d, eax + mov [nicematch],r10d + + + +//;;; register Bytef *scan = s->window + s->strstart; + mov r10, window_ad + mov ebp, strstart + lea r13, [r10 + rbp] + +//;;; Determine how many bytes the scan ptr is off from being +//;;; dword-aligned. + + mov r9,r13 + neg r13 + and r13,3 + +//;;; IPos limit = s->strstart > (IPos)MAX_DIST(s) ? +//;;; s->strstart - (IPos)MAX_DIST(s) : NIL; + + + mov eax, window_size + sub eax, MIN_LOOKAHEAD + + + xor edi,edi + sub ebp, eax + + mov r11d, prev_length + + cmovng ebp,edi + +//;;; int best_len = s->prev_length; + + +//;;; Store the sum of s->window + best_len in esi locally, and in esi. + + lea rsi,[r10+r11] + +//;;; register ush scan_start = *(ushf*)scan; +//;;; register ush scan_end = *(ushf*)(scan+best_len-1); +//;;; Posf *prev = s->prev; + + movzx r12d,word ptr [r9] + movzx ebx, word ptr [r9 + r11 - 1] + + mov rdi, prev_ad + +//;;; Jump into the main loop. + + mov edx, [chainlenwmask] + + cmp bx,word ptr [rsi + r8 - 1] + jz LookupLoopIsZero + + + +LookupLoop1: + and r8d, edx + + movzx r8d, word ptr [rdi + r8*2] + cmp r8d, ebp + jbe LeaveNow + + + + sub edx, 0x00010000 + BEFORE_JMP + js LeaveNow + AFTER_JMP + +LoopEntry1: + cmp bx,word ptr [rsi + r8 - 1] + BEFORE_JMP + jz LookupLoopIsZero + AFTER_JMP + +LookupLoop2: + and r8d, edx + + movzx r8d, word ptr [rdi + r8*2] + cmp r8d, ebp + BEFORE_JMP + jbe LeaveNow + AFTER_JMP + sub edx, 0x00010000 + BEFORE_JMP + js LeaveNow + AFTER_JMP + +LoopEntry2: + cmp bx,word ptr [rsi + r8 - 1] + BEFORE_JMP + jz LookupLoopIsZero + AFTER_JMP + +LookupLoop4: + and r8d, edx + + movzx r8d, word ptr [rdi + r8*2] + cmp r8d, ebp + BEFORE_JMP + jbe LeaveNow + AFTER_JMP + sub edx, 0x00010000 + BEFORE_JMP + js LeaveNow + AFTER_JMP + +LoopEntry4: + + cmp bx,word ptr [rsi + r8 - 1] + BEFORE_JMP + jnz LookupLoop1 + jmp LookupLoopIsZero + AFTER_JMP +/* +;;; do { +;;; match = s->window + cur_match; +;;; if (*(ushf*)(match+best_len-1) != scan_end || +;;; *(ushf*)match != scan_start) continue; +;;; [...] +;;; } while ((cur_match = prev[cur_match & wmask]) > limit +;;; && --chain_length != 0); +;;; +;;; Here is the inner loop of the function. The function will spend the +;;; majority of its time in this loop, and majority of that time will +;;; be spent in the first ten instructions. +;;; +;;; Within this loop: +;;; ebx = scanend +;;; r8d = curmatch +;;; edx = chainlenwmask - i.e., ((chainlen << 16) | wmask) +;;; esi = windowbestlen - i.e., (window + bestlen) +;;; edi = prev +;;; ebp = limit +*/ +.balign 16 +LookupLoop: + and r8d, edx + + movzx r8d, word ptr [rdi + r8*2] + cmp r8d, ebp + BEFORE_JMP + jbe LeaveNow + AFTER_JMP + sub edx, 0x00010000 + BEFORE_JMP + js LeaveNow + AFTER_JMP + +LoopEntry: + + cmp bx,word ptr [rsi + r8 - 1] + BEFORE_JMP + jnz LookupLoop1 + AFTER_JMP +LookupLoopIsZero: + cmp r12w, word ptr [r10 + r8] + BEFORE_JMP + jnz LookupLoop1 + AFTER_JMP + + +//;;; Store the current value of chainlen. + mov [chainlenwmask], edx +/* +;;; Point edi to the string under scrutiny, and esi to the string we +;;; are hoping to match it up with. In actuality, esi and edi are +;;; both pointed (MAX_MATCH_8 - scanalign) bytes ahead, and edx is +;;; initialized to -(MAX_MATCH_8 - scanalign). +*/ + lea rsi,[r8+r10] + mov rdx, 0xfffffffffffffef8 //; -(MAX_MATCH_8) + lea rsi, [rsi + r13 + 0x0108] //;MAX_MATCH_8] + lea rdi, [r9 + r13 + 0x0108] //;MAX_MATCH_8] + + prefetcht1 [rsi+rdx] + prefetcht1 [rdi+rdx] + +/* +;;; Test the strings for equality, 8 bytes at a time. At the end, +;;; adjust rdx so that it is offset to the exact byte that mismatched. +;;; +;;; We already know at this point that the first three bytes of the +;;; strings match each other, and they can be safely passed over before +;;; starting the compare loop. So what this code does is skip over 0-3 +;;; bytes, as much as necessary in order to dword-align the edi +;;; pointer. (rsi will still be misaligned three times out of four.) +;;; +;;; It should be confessed that this loop usually does not represent +;;; much of the total running time. Replacing it with a more +;;; straightforward "rep cmpsb" would not drastically degrade +;;; performance. +*/ + +LoopCmps: + mov rax, [rsi + rdx] + xor rax, [rdi + rdx] + jnz LeaveLoopCmps + + mov rax, [rsi + rdx + 8] + xor rax, [rdi + rdx + 8] + jnz LeaveLoopCmps8 + + + mov rax, [rsi + rdx + 8+8] + xor rax, [rdi + rdx + 8+8] + jnz LeaveLoopCmps16 + + add rdx,8+8+8 + + BEFORE_JMP + jnz LoopCmps + jmp LenMaximum + AFTER_JMP + +LeaveLoopCmps16: add rdx,8 +LeaveLoopCmps8: add rdx,8 +LeaveLoopCmps: + + test eax, 0x0000FFFF + jnz LenLower + + test eax,0xffffffff + + jnz LenLower32 + + add rdx,4 + shr rax,32 + or ax,ax + BEFORE_JMP + jnz LenLower + AFTER_JMP + +LenLower32: + shr eax,16 + add rdx,2 + +LenLower: + sub al, 1 + adc rdx, 0 +//;;; Calculate the length of the match. If it is longer than MAX_MATCH, +//;;; then automatically accept it as the best possible match and leave. + + lea rax, [rdi + rdx] + sub rax, r9 + cmp eax, MAX_MATCH + BEFORE_JMP + jge LenMaximum + AFTER_JMP +/* +;;; If the length of the match is not longer than the best match we +;;; have so far, then forget it and return to the lookup loop. +;/////////////////////////////////// +*/ + cmp eax, r11d + jg LongerMatch + + lea rsi,[r10+r11] + + mov rdi, prev_ad + mov edx, [chainlenwmask] + BEFORE_JMP + jmp LookupLoop + AFTER_JMP +/* +;;; s->match_start = cur_match; +;;; best_len = len; +;;; if (len >= nice_match) break; +;;; scan_end = *(ushf*)(scan+best_len-1); +*/ +LongerMatch: + mov r11d, eax + mov match_start, r8d + cmp eax, [nicematch] + BEFORE_JMP + jge LeaveNow + AFTER_JMP + + lea rsi,[r10+rax] + + movzx ebx, word ptr [r9 + rax - 1] + mov rdi, prev_ad + mov edx, [chainlenwmask] + BEFORE_JMP + jmp LookupLoop + AFTER_JMP + +//;;; Accept the current string, with the maximum possible length. + +LenMaximum: + mov r11d,MAX_MATCH + mov match_start, r8d + +//;;; if ((uInt)best_len <= s->lookahead) return (uInt)best_len; +//;;; return s->lookahead; + +LeaveNow: + mov eax, Lookahead + cmp r11d, eax + cmovng eax, r11d + + + +//;;; Restore the stack and return from whence we came. + + +// mov rsi,[save_rsi] +// mov rdi,[save_rdi] + mov rbx,[save_rbx] + mov rbp,[save_rbp] + mov r12,[save_r12] + mov r13,[save_r13] + mov r14,[save_r14] + mov r15,[save_r15] + + + ret 0 +//; please don't remove this string ! +//; Your can freely use gvmat64 in any free or commercial app +//; but it is far better don't remove the string in the binary! + // db 0dh,0ah,"asm686 with masm, optimised assembly code from Brian Raiter, written 1998, converted to amd 64 by Gilles Vollant 2005",0dh,0ah,0 + + +match_init: + ret 0 + + ADDED compat/zlib/contrib/infback9/README Index: compat/zlib/contrib/infback9/README ================================================================== --- /dev/null +++ compat/zlib/contrib/infback9/README @@ -0,0 +1,1 @@ +See infback9.h for what this is and how to use it. ADDED compat/zlib/contrib/infback9/infback9.c Index: compat/zlib/contrib/infback9/infback9.c ================================================================== --- /dev/null +++ compat/zlib/contrib/infback9/infback9.c @@ -0,0 +1,615 @@ +/* infback9.c -- inflate deflate64 data using a call-back interface + * Copyright (C) 1995-2008 Mark Adler + * For conditions of distribution and use, see copyright notice in zlib.h + */ + +#include "zutil.h" +#include "infback9.h" +#include "inftree9.h" +#include "inflate9.h" + +#define WSIZE 65536UL + +/* + strm provides memory allocation functions in zalloc and zfree, or + Z_NULL to use the library memory allocation functions. + + window is a user-supplied window and output buffer that is 64K bytes. + */ +int ZEXPORT inflateBack9Init_(strm, window, version, stream_size) +z_stream FAR *strm; +unsigned char FAR *window; +const char *version; +int stream_size; +{ + struct inflate_state FAR *state; + + if (version == Z_NULL || version[0] != ZLIB_VERSION[0] || + stream_size != (int)(sizeof(z_stream))) + return Z_VERSION_ERROR; + if (strm == Z_NULL || window == Z_NULL) + return Z_STREAM_ERROR; + strm->msg = Z_NULL; /* in case we return an error */ + if (strm->zalloc == (alloc_func)0) { + strm->zalloc = zcalloc; + strm->opaque = (voidpf)0; + } + if (strm->zfree == (free_func)0) strm->zfree = zcfree; + state = (struct inflate_state FAR *)ZALLOC(strm, 1, + sizeof(struct inflate_state)); + if (state == Z_NULL) return Z_MEM_ERROR; + Tracev((stderr, "inflate: allocated\n")); + strm->state = (voidpf)state; + state->window = window; + return Z_OK; +} + +/* + Build and output length and distance decoding tables for fixed code + decoding. + */ +#ifdef MAKEFIXED +#include + +void makefixed9(void) +{ + unsigned sym, bits, low, size; + code *next, *lenfix, *distfix; + struct inflate_state state; + code fixed[544]; + + /* literal/length table */ + sym = 0; + while (sym < 144) state.lens[sym++] = 8; + while (sym < 256) state.lens[sym++] = 9; + while (sym < 280) state.lens[sym++] = 7; + while (sym < 288) state.lens[sym++] = 8; + next = fixed; + lenfix = next; + bits = 9; + inflate_table9(LENS, state.lens, 288, &(next), &(bits), state.work); + + /* distance table */ + sym = 0; + while (sym < 32) state.lens[sym++] = 5; + distfix = next; + bits = 5; + inflate_table9(DISTS, state.lens, 32, &(next), &(bits), state.work); + + /* write tables */ + puts(" /* inffix9.h -- table for decoding deflate64 fixed codes"); + puts(" * Generated automatically by makefixed9()."); + puts(" */"); + puts(""); + puts(" /* WARNING: this file should *not* be used by applications."); + puts(" It is part of the implementation of this library and is"); + puts(" subject to change. Applications should only use zlib.h."); + puts(" */"); + puts(""); + size = 1U << 9; + printf(" static const code lenfix[%u] = {", size); + low = 0; + for (;;) { + if ((low % 6) == 0) printf("\n "); + printf("{%u,%u,%d}", lenfix[low].op, lenfix[low].bits, + lenfix[low].val); + if (++low == size) break; + putchar(','); + } + puts("\n };"); + size = 1U << 5; + printf("\n static const code distfix[%u] = {", size); + low = 0; + for (;;) { + if ((low % 5) == 0) printf("\n "); + printf("{%u,%u,%d}", distfix[low].op, distfix[low].bits, + distfix[low].val); + if (++low == size) break; + putchar(','); + } + puts("\n };"); +} +#endif /* MAKEFIXED */ + +/* Macros for inflateBack(): */ + +/* Clear the input bit accumulator */ +#define INITBITS() \ + do { \ + hold = 0; \ + bits = 0; \ + } while (0) + +/* Assure that some input is available. If input is requested, but denied, + then return a Z_BUF_ERROR from inflateBack(). */ +#define PULL() \ + do { \ + if (have == 0) { \ + have = in(in_desc, &next); \ + if (have == 0) { \ + next = Z_NULL; \ + ret = Z_BUF_ERROR; \ + goto inf_leave; \ + } \ + } \ + } while (0) + +/* Get a byte of input into the bit accumulator, or return from inflateBack() + with an error if there is no input available. */ +#define PULLBYTE() \ + do { \ + PULL(); \ + have--; \ + hold += (unsigned long)(*next++) << bits; \ + bits += 8; \ + } while (0) + +/* Assure that there are at least n bits in the bit accumulator. If there is + not enough available input to do that, then return from inflateBack() with + an error. */ +#define NEEDBITS(n) \ + do { \ + while (bits < (unsigned)(n)) \ + PULLBYTE(); \ + } while (0) + +/* Return the low n bits of the bit accumulator (n <= 16) */ +#define BITS(n) \ + ((unsigned)hold & ((1U << (n)) - 1)) + +/* Remove n bits from the bit accumulator */ +#define DROPBITS(n) \ + do { \ + hold >>= (n); \ + bits -= (unsigned)(n); \ + } while (0) + +/* Remove zero to seven bits as needed to go to a byte boundary */ +#define BYTEBITS() \ + do { \ + hold >>= bits & 7; \ + bits -= bits & 7; \ + } while (0) + +/* Assure that some output space is available, by writing out the window + if it's full. If the write fails, return from inflateBack() with a + Z_BUF_ERROR. */ +#define ROOM() \ + do { \ + if (left == 0) { \ + put = window; \ + left = WSIZE; \ + wrap = 1; \ + if (out(out_desc, put, (unsigned)left)) { \ + ret = Z_BUF_ERROR; \ + goto inf_leave; \ + } \ + } \ + } while (0) + +/* + strm provides the memory allocation functions and window buffer on input, + and provides information on the unused input on return. For Z_DATA_ERROR + returns, strm will also provide an error message. + + in() and out() are the call-back input and output functions. When + inflateBack() needs more input, it calls in(). When inflateBack() has + filled the window with output, or when it completes with data in the + window, it calls out() to write out the data. The application must not + change the provided input until in() is called again or inflateBack() + returns. The application must not change the window/output buffer until + inflateBack() returns. + + in() and out() are called with a descriptor parameter provided in the + inflateBack() call. This parameter can be a structure that provides the + information required to do the read or write, as well as accumulated + information on the input and output such as totals and check values. + + in() should return zero on failure. out() should return non-zero on + failure. If either in() or out() fails, than inflateBack() returns a + Z_BUF_ERROR. strm->next_in can be checked for Z_NULL to see whether it + was in() or out() that caused in the error. Otherwise, inflateBack() + returns Z_STREAM_END on success, Z_DATA_ERROR for an deflate format + error, or Z_MEM_ERROR if it could not allocate memory for the state. + inflateBack() can also return Z_STREAM_ERROR if the input parameters + are not correct, i.e. strm is Z_NULL or the state was not initialized. + */ +int ZEXPORT inflateBack9(strm, in, in_desc, out, out_desc) +z_stream FAR *strm; +in_func in; +void FAR *in_desc; +out_func out; +void FAR *out_desc; +{ + struct inflate_state FAR *state; + z_const unsigned char FAR *next; /* next input */ + unsigned char FAR *put; /* next output */ + unsigned have; /* available input */ + unsigned long left; /* available output */ + inflate_mode mode; /* current inflate mode */ + int lastblock; /* true if processing last block */ + int wrap; /* true if the window has wrapped */ + unsigned char FAR *window; /* allocated sliding window, if needed */ + unsigned long hold; /* bit buffer */ + unsigned bits; /* bits in bit buffer */ + unsigned extra; /* extra bits needed */ + unsigned long length; /* literal or length of data to copy */ + unsigned long offset; /* distance back to copy string from */ + unsigned long copy; /* number of stored or match bytes to copy */ + unsigned char FAR *from; /* where to copy match bytes from */ + code const FAR *lencode; /* starting table for length/literal codes */ + code const FAR *distcode; /* starting table for distance codes */ + unsigned lenbits; /* index bits for lencode */ + unsigned distbits; /* index bits for distcode */ + code here; /* current decoding table entry */ + code last; /* parent table entry */ + unsigned len; /* length to copy for repeats, bits to drop */ + int ret; /* return code */ + static const unsigned short order[19] = /* permutation of code lengths */ + {16, 17, 18, 0, 8, 7, 9, 6, 10, 5, 11, 4, 12, 3, 13, 2, 14, 1, 15}; +#include "inffix9.h" + + /* Check that the strm exists and that the state was initialized */ + if (strm == Z_NULL || strm->state == Z_NULL) + return Z_STREAM_ERROR; + state = (struct inflate_state FAR *)strm->state; + + /* Reset the state */ + strm->msg = Z_NULL; + mode = TYPE; + lastblock = 0; + wrap = 0; + window = state->window; + next = strm->next_in; + have = next != Z_NULL ? strm->avail_in : 0; + hold = 0; + bits = 0; + put = window; + left = WSIZE; + lencode = Z_NULL; + distcode = Z_NULL; + + /* Inflate until end of block marked as last */ + for (;;) + switch (mode) { + case TYPE: + /* determine and dispatch block type */ + if (lastblock) { + BYTEBITS(); + mode = DONE; + break; + } + NEEDBITS(3); + lastblock = BITS(1); + DROPBITS(1); + switch (BITS(2)) { + case 0: /* stored block */ + Tracev((stderr, "inflate: stored block%s\n", + lastblock ? " (last)" : "")); + mode = STORED; + break; + case 1: /* fixed block */ + lencode = lenfix; + lenbits = 9; + distcode = distfix; + distbits = 5; + Tracev((stderr, "inflate: fixed codes block%s\n", + lastblock ? " (last)" : "")); + mode = LEN; /* decode codes */ + break; + case 2: /* dynamic block */ + Tracev((stderr, "inflate: dynamic codes block%s\n", + lastblock ? " (last)" : "")); + mode = TABLE; + break; + case 3: + strm->msg = (char *)"invalid block type"; + mode = BAD; + } + DROPBITS(2); + break; + + case STORED: + /* get and verify stored block length */ + BYTEBITS(); /* go to byte boundary */ + NEEDBITS(32); + if ((hold & 0xffff) != ((hold >> 16) ^ 0xffff)) { + strm->msg = (char *)"invalid stored block lengths"; + mode = BAD; + break; + } + length = (unsigned)hold & 0xffff; + Tracev((stderr, "inflate: stored length %lu\n", + length)); + INITBITS(); + + /* copy stored block from input to output */ + while (length != 0) { + copy = length; + PULL(); + ROOM(); + if (copy > have) copy = have; + if (copy > left) copy = left; + zmemcpy(put, next, copy); + have -= copy; + next += copy; + left -= copy; + put += copy; + length -= copy; + } + Tracev((stderr, "inflate: stored end\n")); + mode = TYPE; + break; + + case TABLE: + /* get dynamic table entries descriptor */ + NEEDBITS(14); + state->nlen = BITS(5) + 257; + DROPBITS(5); + state->ndist = BITS(5) + 1; + DROPBITS(5); + state->ncode = BITS(4) + 4; + DROPBITS(4); + if (state->nlen > 286) { + strm->msg = (char *)"too many length symbols"; + mode = BAD; + break; + } + Tracev((stderr, "inflate: table sizes ok\n")); + + /* get code length code lengths (not a typo) */ + state->have = 0; + while (state->have < state->ncode) { + NEEDBITS(3); + state->lens[order[state->have++]] = (unsigned short)BITS(3); + DROPBITS(3); + } + while (state->have < 19) + state->lens[order[state->have++]] = 0; + state->next = state->codes; + lencode = (code const FAR *)(state->next); + lenbits = 7; + ret = inflate_table9(CODES, state->lens, 19, &(state->next), + &(lenbits), state->work); + if (ret) { + strm->msg = (char *)"invalid code lengths set"; + mode = BAD; + break; + } + Tracev((stderr, "inflate: code lengths ok\n")); + + /* get length and distance code code lengths */ + state->have = 0; + while (state->have < state->nlen + state->ndist) { + for (;;) { + here = lencode[BITS(lenbits)]; + if ((unsigned)(here.bits) <= bits) break; + PULLBYTE(); + } + if (here.val < 16) { + NEEDBITS(here.bits); + DROPBITS(here.bits); + state->lens[state->have++] = here.val; + } + else { + if (here.val == 16) { + NEEDBITS(here.bits + 2); + DROPBITS(here.bits); + if (state->have == 0) { + strm->msg = (char *)"invalid bit length repeat"; + mode = BAD; + break; + } + len = (unsigned)(state->lens[state->have - 1]); + copy = 3 + BITS(2); + DROPBITS(2); + } + else if (here.val == 17) { + NEEDBITS(here.bits + 3); + DROPBITS(here.bits); + len = 0; + copy = 3 + BITS(3); + DROPBITS(3); + } + else { + NEEDBITS(here.bits + 7); + DROPBITS(here.bits); + len = 0; + copy = 11 + BITS(7); + DROPBITS(7); + } + if (state->have + copy > state->nlen + state->ndist) { + strm->msg = (char *)"invalid bit length repeat"; + mode = BAD; + break; + } + while (copy--) + state->lens[state->have++] = (unsigned short)len; + } + } + + /* handle error breaks in while */ + if (mode == BAD) break; + + /* check for end-of-block code (better have one) */ + if (state->lens[256] == 0) { + strm->msg = (char *)"invalid code -- missing end-of-block"; + mode = BAD; + break; + } + + /* build code tables -- note: do not change the lenbits or distbits + values here (9 and 6) without reading the comments in inftree9.h + concerning the ENOUGH constants, which depend on those values */ + state->next = state->codes; + lencode = (code const FAR *)(state->next); + lenbits = 9; + ret = inflate_table9(LENS, state->lens, state->nlen, + &(state->next), &(lenbits), state->work); + if (ret) { + strm->msg = (char *)"invalid literal/lengths set"; + mode = BAD; + break; + } + distcode = (code const FAR *)(state->next); + distbits = 6; + ret = inflate_table9(DISTS, state->lens + state->nlen, + state->ndist, &(state->next), &(distbits), + state->work); + if (ret) { + strm->msg = (char *)"invalid distances set"; + mode = BAD; + break; + } + Tracev((stderr, "inflate: codes ok\n")); + mode = LEN; + + case LEN: + /* get a literal, length, or end-of-block code */ + for (;;) { + here = lencode[BITS(lenbits)]; + if ((unsigned)(here.bits) <= bits) break; + PULLBYTE(); + } + if (here.op && (here.op & 0xf0) == 0) { + last = here; + for (;;) { + here = lencode[last.val + + (BITS(last.bits + last.op) >> last.bits)]; + if ((unsigned)(last.bits + here.bits) <= bits) break; + PULLBYTE(); + } + DROPBITS(last.bits); + } + DROPBITS(here.bits); + length = (unsigned)here.val; + + /* process literal */ + if (here.op == 0) { + Tracevv((stderr, here.val >= 0x20 && here.val < 0x7f ? + "inflate: literal '%c'\n" : + "inflate: literal 0x%02x\n", here.val)); + ROOM(); + *put++ = (unsigned char)(length); + left--; + mode = LEN; + break; + } + + /* process end of block */ + if (here.op & 32) { + Tracevv((stderr, "inflate: end of block\n")); + mode = TYPE; + break; + } + + /* invalid code */ + if (here.op & 64) { + strm->msg = (char *)"invalid literal/length code"; + mode = BAD; + break; + } + + /* length code -- get extra bits, if any */ + extra = (unsigned)(here.op) & 31; + if (extra != 0) { + NEEDBITS(extra); + length += BITS(extra); + DROPBITS(extra); + } + Tracevv((stderr, "inflate: length %lu\n", length)); + + /* get distance code */ + for (;;) { + here = distcode[BITS(distbits)]; + if ((unsigned)(here.bits) <= bits) break; + PULLBYTE(); + } + if ((here.op & 0xf0) == 0) { + last = here; + for (;;) { + here = distcode[last.val + + (BITS(last.bits + last.op) >> last.bits)]; + if ((unsigned)(last.bits + here.bits) <= bits) break; + PULLBYTE(); + } + DROPBITS(last.bits); + } + DROPBITS(here.bits); + if (here.op & 64) { + strm->msg = (char *)"invalid distance code"; + mode = BAD; + break; + } + offset = (unsigned)here.val; + + /* get distance extra bits, if any */ + extra = (unsigned)(here.op) & 15; + if (extra != 0) { + NEEDBITS(extra); + offset += BITS(extra); + DROPBITS(extra); + } + if (offset > WSIZE - (wrap ? 0: left)) { + strm->msg = (char *)"invalid distance too far back"; + mode = BAD; + break; + } + Tracevv((stderr, "inflate: distance %lu\n", offset)); + + /* copy match from window to output */ + do { + ROOM(); + copy = WSIZE - offset; + if (copy < left) { + from = put + copy; + copy = left - copy; + } + else { + from = put - offset; + copy = left; + } + if (copy > length) copy = length; + length -= copy; + left -= copy; + do { + *put++ = *from++; + } while (--copy); + } while (length != 0); + break; + + case DONE: + /* inflate stream terminated properly -- write leftover output */ + ret = Z_STREAM_END; + if (left < WSIZE) { + if (out(out_desc, window, (unsigned)(WSIZE - left))) + ret = Z_BUF_ERROR; + } + goto inf_leave; + + case BAD: + ret = Z_DATA_ERROR; + goto inf_leave; + + default: /* can't happen, but makes compilers happy */ + ret = Z_STREAM_ERROR; + goto inf_leave; + } + + /* Return unused input */ + inf_leave: + strm->next_in = next; + strm->avail_in = have; + return ret; +} + +int ZEXPORT inflateBack9End(strm) +z_stream FAR *strm; +{ + if (strm == Z_NULL || strm->state == Z_NULL || strm->zfree == (free_func)0) + return Z_STREAM_ERROR; + ZFREE(strm, strm->state); + strm->state = Z_NULL; + Tracev((stderr, "inflate: end\n")); + return Z_OK; +} ADDED compat/zlib/contrib/infback9/infback9.h Index: compat/zlib/contrib/infback9/infback9.h ================================================================== --- /dev/null +++ compat/zlib/contrib/infback9/infback9.h @@ -0,0 +1,37 @@ +/* infback9.h -- header for using inflateBack9 functions + * Copyright (C) 2003 Mark Adler + * For conditions of distribution and use, see copyright notice in zlib.h + */ + +/* + * This header file and associated patches provide a decoder for PKWare's + * undocumented deflate64 compression method (method 9). Use with infback9.c, + * inftree9.h, inftree9.c, and inffix9.h. These patches are not supported. + * This should be compiled with zlib, since it uses zutil.h and zutil.o. + * This code has not yet been tested on 16-bit architectures. See the + * comments in zlib.h for inflateBack() usage. These functions are used + * identically, except that there is no windowBits parameter, and a 64K + * window must be provided. Also if int's are 16 bits, then a zero for + * the third parameter of the "out" function actually means 65536UL. + * zlib.h must be included before this header file. + */ + +#ifdef __cplusplus +extern "C" { +#endif + +ZEXTERN int ZEXPORT inflateBack9 OF((z_stream FAR *strm, + in_func in, void FAR *in_desc, + out_func out, void FAR *out_desc)); +ZEXTERN int ZEXPORT inflateBack9End OF((z_stream FAR *strm)); +ZEXTERN int ZEXPORT inflateBack9Init_ OF((z_stream FAR *strm, + unsigned char FAR *window, + const char *version, + int stream_size)); +#define inflateBack9Init(strm, window) \ + inflateBack9Init_((strm), (window), \ + ZLIB_VERSION, sizeof(z_stream)) + +#ifdef __cplusplus +} +#endif ADDED compat/zlib/contrib/infback9/inffix9.h Index: compat/zlib/contrib/infback9/inffix9.h ================================================================== --- /dev/null +++ compat/zlib/contrib/infback9/inffix9.h @@ -0,0 +1,107 @@ + /* inffix9.h -- table for decoding deflate64 fixed codes + * Generated automatically by makefixed9(). + */ + + /* WARNING: this file should *not* be used by applications. + It is part of the implementation of this library and is + subject to change. Applications should only use zlib.h. + */ + + static const code lenfix[512] = { + {96,7,0},{0,8,80},{0,8,16},{132,8,115},{130,7,31},{0,8,112}, + {0,8,48},{0,9,192},{128,7,10},{0,8,96},{0,8,32},{0,9,160}, + {0,8,0},{0,8,128},{0,8,64},{0,9,224},{128,7,6},{0,8,88}, + {0,8,24},{0,9,144},{131,7,59},{0,8,120},{0,8,56},{0,9,208}, + {129,7,17},{0,8,104},{0,8,40},{0,9,176},{0,8,8},{0,8,136}, + {0,8,72},{0,9,240},{128,7,4},{0,8,84},{0,8,20},{133,8,227}, + {131,7,43},{0,8,116},{0,8,52},{0,9,200},{129,7,13},{0,8,100}, + {0,8,36},{0,9,168},{0,8,4},{0,8,132},{0,8,68},{0,9,232}, + {128,7,8},{0,8,92},{0,8,28},{0,9,152},{132,7,83},{0,8,124}, + {0,8,60},{0,9,216},{130,7,23},{0,8,108},{0,8,44},{0,9,184}, + {0,8,12},{0,8,140},{0,8,76},{0,9,248},{128,7,3},{0,8,82}, + {0,8,18},{133,8,163},{131,7,35},{0,8,114},{0,8,50},{0,9,196}, + {129,7,11},{0,8,98},{0,8,34},{0,9,164},{0,8,2},{0,8,130}, + {0,8,66},{0,9,228},{128,7,7},{0,8,90},{0,8,26},{0,9,148}, + {132,7,67},{0,8,122},{0,8,58},{0,9,212},{130,7,19},{0,8,106}, + {0,8,42},{0,9,180},{0,8,10},{0,8,138},{0,8,74},{0,9,244}, + {128,7,5},{0,8,86},{0,8,22},{65,8,0},{131,7,51},{0,8,118}, + {0,8,54},{0,9,204},{129,7,15},{0,8,102},{0,8,38},{0,9,172}, + {0,8,6},{0,8,134},{0,8,70},{0,9,236},{128,7,9},{0,8,94}, + {0,8,30},{0,9,156},{132,7,99},{0,8,126},{0,8,62},{0,9,220}, + {130,7,27},{0,8,110},{0,8,46},{0,9,188},{0,8,14},{0,8,142}, + {0,8,78},{0,9,252},{96,7,0},{0,8,81},{0,8,17},{133,8,131}, + {130,7,31},{0,8,113},{0,8,49},{0,9,194},{128,7,10},{0,8,97}, + {0,8,33},{0,9,162},{0,8,1},{0,8,129},{0,8,65},{0,9,226}, + {128,7,6},{0,8,89},{0,8,25},{0,9,146},{131,7,59},{0,8,121}, + {0,8,57},{0,9,210},{129,7,17},{0,8,105},{0,8,41},{0,9,178}, + {0,8,9},{0,8,137},{0,8,73},{0,9,242},{128,7,4},{0,8,85}, + {0,8,21},{144,8,3},{131,7,43},{0,8,117},{0,8,53},{0,9,202}, + {129,7,13},{0,8,101},{0,8,37},{0,9,170},{0,8,5},{0,8,133}, + {0,8,69},{0,9,234},{128,7,8},{0,8,93},{0,8,29},{0,9,154}, + {132,7,83},{0,8,125},{0,8,61},{0,9,218},{130,7,23},{0,8,109}, + {0,8,45},{0,9,186},{0,8,13},{0,8,141},{0,8,77},{0,9,250}, + {128,7,3},{0,8,83},{0,8,19},{133,8,195},{131,7,35},{0,8,115}, + {0,8,51},{0,9,198},{129,7,11},{0,8,99},{0,8,35},{0,9,166}, + {0,8,3},{0,8,131},{0,8,67},{0,9,230},{128,7,7},{0,8,91}, + {0,8,27},{0,9,150},{132,7,67},{0,8,123},{0,8,59},{0,9,214}, + {130,7,19},{0,8,107},{0,8,43},{0,9,182},{0,8,11},{0,8,139}, + {0,8,75},{0,9,246},{128,7,5},{0,8,87},{0,8,23},{77,8,0}, + {131,7,51},{0,8,119},{0,8,55},{0,9,206},{129,7,15},{0,8,103}, + {0,8,39},{0,9,174},{0,8,7},{0,8,135},{0,8,71},{0,9,238}, + {128,7,9},{0,8,95},{0,8,31},{0,9,158},{132,7,99},{0,8,127}, + {0,8,63},{0,9,222},{130,7,27},{0,8,111},{0,8,47},{0,9,190}, + {0,8,15},{0,8,143},{0,8,79},{0,9,254},{96,7,0},{0,8,80}, + {0,8,16},{132,8,115},{130,7,31},{0,8,112},{0,8,48},{0,9,193}, + {128,7,10},{0,8,96},{0,8,32},{0,9,161},{0,8,0},{0,8,128}, + {0,8,64},{0,9,225},{128,7,6},{0,8,88},{0,8,24},{0,9,145}, + {131,7,59},{0,8,120},{0,8,56},{0,9,209},{129,7,17},{0,8,104}, + {0,8,40},{0,9,177},{0,8,8},{0,8,136},{0,8,72},{0,9,241}, + {128,7,4},{0,8,84},{0,8,20},{133,8,227},{131,7,43},{0,8,116}, + {0,8,52},{0,9,201},{129,7,13},{0,8,100},{0,8,36},{0,9,169}, + {0,8,4},{0,8,132},{0,8,68},{0,9,233},{128,7,8},{0,8,92}, + {0,8,28},{0,9,153},{132,7,83},{0,8,124},{0,8,60},{0,9,217}, + {130,7,23},{0,8,108},{0,8,44},{0,9,185},{0,8,12},{0,8,140}, + {0,8,76},{0,9,249},{128,7,3},{0,8,82},{0,8,18},{133,8,163}, + {131,7,35},{0,8,114},{0,8,50},{0,9,197},{129,7,11},{0,8,98}, + {0,8,34},{0,9,165},{0,8,2},{0,8,130},{0,8,66},{0,9,229}, + {128,7,7},{0,8,90},{0,8,26},{0,9,149},{132,7,67},{0,8,122}, + {0,8,58},{0,9,213},{130,7,19},{0,8,106},{0,8,42},{0,9,181}, + {0,8,10},{0,8,138},{0,8,74},{0,9,245},{128,7,5},{0,8,86}, + {0,8,22},{65,8,0},{131,7,51},{0,8,118},{0,8,54},{0,9,205}, + {129,7,15},{0,8,102},{0,8,38},{0,9,173},{0,8,6},{0,8,134}, + {0,8,70},{0,9,237},{128,7,9},{0,8,94},{0,8,30},{0,9,157}, + {132,7,99},{0,8,126},{0,8,62},{0,9,221},{130,7,27},{0,8,110}, + {0,8,46},{0,9,189},{0,8,14},{0,8,142},{0,8,78},{0,9,253}, + {96,7,0},{0,8,81},{0,8,17},{133,8,131},{130,7,31},{0,8,113}, + {0,8,49},{0,9,195},{128,7,10},{0,8,97},{0,8,33},{0,9,163}, + {0,8,1},{0,8,129},{0,8,65},{0,9,227},{128,7,6},{0,8,89}, + {0,8,25},{0,9,147},{131,7,59},{0,8,121},{0,8,57},{0,9,211}, + {129,7,17},{0,8,105},{0,8,41},{0,9,179},{0,8,9},{0,8,137}, + {0,8,73},{0,9,243},{128,7,4},{0,8,85},{0,8,21},{144,8,3}, + {131,7,43},{0,8,117},{0,8,53},{0,9,203},{129,7,13},{0,8,101}, + {0,8,37},{0,9,171},{0,8,5},{0,8,133},{0,8,69},{0,9,235}, + {128,7,8},{0,8,93},{0,8,29},{0,9,155},{132,7,83},{0,8,125}, + {0,8,61},{0,9,219},{130,7,23},{0,8,109},{0,8,45},{0,9,187}, + {0,8,13},{0,8,141},{0,8,77},{0,9,251},{128,7,3},{0,8,83}, + {0,8,19},{133,8,195},{131,7,35},{0,8,115},{0,8,51},{0,9,199}, + {129,7,11},{0,8,99},{0,8,35},{0,9,167},{0,8,3},{0,8,131}, + {0,8,67},{0,9,231},{128,7,7},{0,8,91},{0,8,27},{0,9,151}, + {132,7,67},{0,8,123},{0,8,59},{0,9,215},{130,7,19},{0,8,107}, + {0,8,43},{0,9,183},{0,8,11},{0,8,139},{0,8,75},{0,9,247}, + {128,7,5},{0,8,87},{0,8,23},{77,8,0},{131,7,51},{0,8,119}, + {0,8,55},{0,9,207},{129,7,15},{0,8,103},{0,8,39},{0,9,175}, + {0,8,7},{0,8,135},{0,8,71},{0,9,239},{128,7,9},{0,8,95}, + {0,8,31},{0,9,159},{132,7,99},{0,8,127},{0,8,63},{0,9,223}, + {130,7,27},{0,8,111},{0,8,47},{0,9,191},{0,8,15},{0,8,143}, + {0,8,79},{0,9,255} + }; + + static const code distfix[32] = { + {128,5,1},{135,5,257},{131,5,17},{139,5,4097},{129,5,5}, + {137,5,1025},{133,5,65},{141,5,16385},{128,5,3},{136,5,513}, + {132,5,33},{140,5,8193},{130,5,9},{138,5,2049},{134,5,129}, + {142,5,32769},{128,5,2},{135,5,385},{131,5,25},{139,5,6145}, + {129,5,7},{137,5,1537},{133,5,97},{141,5,24577},{128,5,4}, + {136,5,769},{132,5,49},{140,5,12289},{130,5,13},{138,5,3073}, + {134,5,193},{142,5,49153} + }; ADDED compat/zlib/contrib/infback9/inflate9.h Index: compat/zlib/contrib/infback9/inflate9.h ================================================================== --- /dev/null +++ compat/zlib/contrib/infback9/inflate9.h @@ -0,0 +1,47 @@ +/* inflate9.h -- internal inflate state definition + * Copyright (C) 1995-2003 Mark Adler + * For conditions of distribution and use, see copyright notice in zlib.h + */ + +/* WARNING: this file should *not* be used by applications. It is + part of the implementation of the compression library and is + subject to change. Applications should only use zlib.h. + */ + +/* Possible inflate modes between inflate() calls */ +typedef enum { + TYPE, /* i: waiting for type bits, including last-flag bit */ + STORED, /* i: waiting for stored size (length and complement) */ + TABLE, /* i: waiting for dynamic block table lengths */ + LEN, /* i: waiting for length/lit code */ + DONE, /* finished check, done -- remain here until reset */ + BAD /* got a data error -- remain here until reset */ +} inflate_mode; + +/* + State transitions between above modes - + + (most modes can go to the BAD mode -- not shown for clarity) + + Read deflate blocks: + TYPE -> STORED or TABLE or LEN or DONE + STORED -> TYPE + TABLE -> LENLENS -> CODELENS -> LEN + Read deflate codes: + LEN -> LEN or TYPE + */ + +/* state maintained between inflate() calls. Approximately 7K bytes. */ +struct inflate_state { + /* sliding window */ + unsigned char FAR *window; /* allocated sliding window, if needed */ + /* dynamic table building */ + unsigned ncode; /* number of code length code lengths */ + unsigned nlen; /* number of length code lengths */ + unsigned ndist; /* number of distance code lengths */ + unsigned have; /* number of code lengths in lens[] */ + code FAR *next; /* next available space in codes[] */ + unsigned short lens[320]; /* temporary storage for code lengths */ + unsigned short work[288]; /* work area for code table building */ + code codes[ENOUGH]; /* space for code tables */ +}; ADDED compat/zlib/contrib/infback9/inftree9.c Index: compat/zlib/contrib/infback9/inftree9.c ================================================================== --- /dev/null +++ compat/zlib/contrib/infback9/inftree9.c @@ -0,0 +1,324 @@ +/* inftree9.c -- generate Huffman trees for efficient decoding + * Copyright (C) 1995-2017 Mark Adler + * For conditions of distribution and use, see copyright notice in zlib.h + */ + +#include "zutil.h" +#include "inftree9.h" + +#define MAXBITS 15 + +const char inflate9_copyright[] = + " inflate9 1.2.11 Copyright 1995-2017 Mark Adler "; +/* + If you use the zlib library in a product, an acknowledgment is welcome + in the documentation of your product. If for some reason you cannot + include such an acknowledgment, I would appreciate that you keep this + copyright string in the executable of your product. + */ + +/* + Build a set of tables to decode the provided canonical Huffman code. + The code lengths are lens[0..codes-1]. The result starts at *table, + whose indices are 0..2^bits-1. work is a writable array of at least + lens shorts, which is used as a work area. type is the type of code + to be generated, CODES, LENS, or DISTS. On return, zero is success, + -1 is an invalid code, and +1 means that ENOUGH isn't enough. table + on return points to the next available entry's address. bits is the + requested root table index bits, and on return it is the actual root + table index bits. It will differ if the request is greater than the + longest code or if it is less than the shortest code. + */ +int inflate_table9(type, lens, codes, table, bits, work) +codetype type; +unsigned short FAR *lens; +unsigned codes; +code FAR * FAR *table; +unsigned FAR *bits; +unsigned short FAR *work; +{ + unsigned len; /* a code's length in bits */ + unsigned sym; /* index of code symbols */ + unsigned min, max; /* minimum and maximum code lengths */ + unsigned root; /* number of index bits for root table */ + unsigned curr; /* number of index bits for current table */ + unsigned drop; /* code bits to drop for sub-table */ + int left; /* number of prefix codes available */ + unsigned used; /* code entries in table used */ + unsigned huff; /* Huffman code */ + unsigned incr; /* for incrementing code, index */ + unsigned fill; /* index for replicating entries */ + unsigned low; /* low bits for current root entry */ + unsigned mask; /* mask for low root bits */ + code this; /* table entry for duplication */ + code FAR *next; /* next available space in table */ + const unsigned short FAR *base; /* base value table to use */ + const unsigned short FAR *extra; /* extra bits table to use */ + int end; /* use base and extra for symbol > end */ + unsigned short count[MAXBITS+1]; /* number of codes of each length */ + unsigned short offs[MAXBITS+1]; /* offsets in table for each length */ + static const unsigned short lbase[31] = { /* Length codes 257..285 base */ + 3, 4, 5, 6, 7, 8, 9, 10, 11, 13, 15, 17, + 19, 23, 27, 31, 35, 43, 51, 59, 67, 83, 99, 115, + 131, 163, 195, 227, 3, 0, 0}; + static const unsigned short lext[31] = { /* Length codes 257..285 extra */ + 128, 128, 128, 128, 128, 128, 128, 128, 129, 129, 129, 129, + 130, 130, 130, 130, 131, 131, 131, 131, 132, 132, 132, 132, + 133, 133, 133, 133, 144, 77, 202}; + static const unsigned short dbase[32] = { /* Distance codes 0..31 base */ + 1, 2, 3, 4, 5, 7, 9, 13, 17, 25, 33, 49, + 65, 97, 129, 193, 257, 385, 513, 769, 1025, 1537, 2049, 3073, + 4097, 6145, 8193, 12289, 16385, 24577, 32769, 49153}; + static const unsigned short dext[32] = { /* Distance codes 0..31 extra */ + 128, 128, 128, 128, 129, 129, 130, 130, 131, 131, 132, 132, + 133, 133, 134, 134, 135, 135, 136, 136, 137, 137, 138, 138, + 139, 139, 140, 140, 141, 141, 142, 142}; + + /* + Process a set of code lengths to create a canonical Huffman code. The + code lengths are lens[0..codes-1]. Each length corresponds to the + symbols 0..codes-1. The Huffman code is generated by first sorting the + symbols by length from short to long, and retaining the symbol order + for codes with equal lengths. Then the code starts with all zero bits + for the first code of the shortest length, and the codes are integer + increments for the same length, and zeros are appended as the length + increases. For the deflate format, these bits are stored backwards + from their more natural integer increment ordering, and so when the + decoding tables are built in the large loop below, the integer codes + are incremented backwards. + + This routine assumes, but does not check, that all of the entries in + lens[] are in the range 0..MAXBITS. The caller must assure this. + 1..MAXBITS is interpreted as that code length. zero means that that + symbol does not occur in this code. + + The codes are sorted by computing a count of codes for each length, + creating from that a table of starting indices for each length in the + sorted table, and then entering the symbols in order in the sorted + table. The sorted table is work[], with that space being provided by + the caller. + + The length counts are used for other purposes as well, i.e. finding + the minimum and maximum length codes, determining if there are any + codes at all, checking for a valid set of lengths, and looking ahead + at length counts to determine sub-table sizes when building the + decoding tables. + */ + + /* accumulate lengths for codes (assumes lens[] all in 0..MAXBITS) */ + for (len = 0; len <= MAXBITS; len++) + count[len] = 0; + for (sym = 0; sym < codes; sym++) + count[lens[sym]]++; + + /* bound code lengths, force root to be within code lengths */ + root = *bits; + for (max = MAXBITS; max >= 1; max--) + if (count[max] != 0) break; + if (root > max) root = max; + if (max == 0) return -1; /* no codes! */ + for (min = 1; min <= MAXBITS; min++) + if (count[min] != 0) break; + if (root < min) root = min; + + /* check for an over-subscribed or incomplete set of lengths */ + left = 1; + for (len = 1; len <= MAXBITS; len++) { + left <<= 1; + left -= count[len]; + if (left < 0) return -1; /* over-subscribed */ + } + if (left > 0 && (type == CODES || max != 1)) + return -1; /* incomplete set */ + + /* generate offsets into symbol table for each length for sorting */ + offs[1] = 0; + for (len = 1; len < MAXBITS; len++) + offs[len + 1] = offs[len] + count[len]; + + /* sort symbols by length, by symbol order within each length */ + for (sym = 0; sym < codes; sym++) + if (lens[sym] != 0) work[offs[lens[sym]]++] = (unsigned short)sym; + + /* + Create and fill in decoding tables. In this loop, the table being + filled is at next and has curr index bits. The code being used is huff + with length len. That code is converted to an index by dropping drop + bits off of the bottom. For codes where len is less than drop + curr, + those top drop + curr - len bits are incremented through all values to + fill the table with replicated entries. + + root is the number of index bits for the root table. When len exceeds + root, sub-tables are created pointed to by the root entry with an index + of the low root bits of huff. This is saved in low to check for when a + new sub-table should be started. drop is zero when the root table is + being filled, and drop is root when sub-tables are being filled. + + When a new sub-table is needed, it is necessary to look ahead in the + code lengths to determine what size sub-table is needed. The length + counts are used for this, and so count[] is decremented as codes are + entered in the tables. + + used keeps track of how many table entries have been allocated from the + provided *table space. It is checked for LENS and DIST tables against + the constants ENOUGH_LENS and ENOUGH_DISTS to guard against changes in + the initial root table size constants. See the comments in inftree9.h + for more information. + + sym increments through all symbols, and the loop terminates when + all codes of length max, i.e. all codes, have been processed. This + routine permits incomplete codes, so another loop after this one fills + in the rest of the decoding tables with invalid code markers. + */ + + /* set up for code type */ + switch (type) { + case CODES: + base = extra = work; /* dummy value--not used */ + end = 19; + break; + case LENS: + base = lbase; + base -= 257; + extra = lext; + extra -= 257; + end = 256; + break; + default: /* DISTS */ + base = dbase; + extra = dext; + end = -1; + } + + /* initialize state for loop */ + huff = 0; /* starting code */ + sym = 0; /* starting code symbol */ + len = min; /* starting code length */ + next = *table; /* current table to fill in */ + curr = root; /* current table index bits */ + drop = 0; /* current bits to drop from code for index */ + low = (unsigned)(-1); /* trigger new sub-table when len > root */ + used = 1U << root; /* use root table entries */ + mask = used - 1; /* mask for comparing low */ + + /* check available table space */ + if ((type == LENS && used >= ENOUGH_LENS) || + (type == DISTS && used >= ENOUGH_DISTS)) + return 1; + + /* process all codes and make table entries */ + for (;;) { + /* create table entry */ + this.bits = (unsigned char)(len - drop); + if ((int)(work[sym]) < end) { + this.op = (unsigned char)0; + this.val = work[sym]; + } + else if ((int)(work[sym]) > end) { + this.op = (unsigned char)(extra[work[sym]]); + this.val = base[work[sym]]; + } + else { + this.op = (unsigned char)(32 + 64); /* end of block */ + this.val = 0; + } + + /* replicate for those indices with low len bits equal to huff */ + incr = 1U << (len - drop); + fill = 1U << curr; + do { + fill -= incr; + next[(huff >> drop) + fill] = this; + } while (fill != 0); + + /* backwards increment the len-bit code huff */ + incr = 1U << (len - 1); + while (huff & incr) + incr >>= 1; + if (incr != 0) { + huff &= incr - 1; + huff += incr; + } + else + huff = 0; + + /* go to next symbol, update count, len */ + sym++; + if (--(count[len]) == 0) { + if (len == max) break; + len = lens[work[sym]]; + } + + /* create new sub-table if needed */ + if (len > root && (huff & mask) != low) { + /* if first time, transition to sub-tables */ + if (drop == 0) + drop = root; + + /* increment past last table */ + next += 1U << curr; + + /* determine length of next table */ + curr = len - drop; + left = (int)(1 << curr); + while (curr + drop < max) { + left -= count[curr + drop]; + if (left <= 0) break; + curr++; + left <<= 1; + } + + /* check for enough space */ + used += 1U << curr; + if ((type == LENS && used >= ENOUGH_LENS) || + (type == DISTS && used >= ENOUGH_DISTS)) + return 1; + + /* point entry in root table to sub-table */ + low = huff & mask; + (*table)[low].op = (unsigned char)curr; + (*table)[low].bits = (unsigned char)root; + (*table)[low].val = (unsigned short)(next - *table); + } + } + + /* + Fill in rest of table for incomplete codes. This loop is similar to the + loop above in incrementing huff for table indices. It is assumed that + len is equal to curr + drop, so there is no loop needed to increment + through high index bits. When the current sub-table is filled, the loop + drops back to the root table to fill in any remaining entries there. + */ + this.op = (unsigned char)64; /* invalid code marker */ + this.bits = (unsigned char)(len - drop); + this.val = (unsigned short)0; + while (huff != 0) { + /* when done with sub-table, drop back to root table */ + if (drop != 0 && (huff & mask) != low) { + drop = 0; + len = root; + next = *table; + curr = root; + this.bits = (unsigned char)len; + } + + /* put invalid code marker in table */ + next[huff >> drop] = this; + + /* backwards increment the len-bit code huff */ + incr = 1U << (len - 1); + while (huff & incr) + incr >>= 1; + if (incr != 0) { + huff &= incr - 1; + huff += incr; + } + else + huff = 0; + } + + /* set return parameters */ + *table += used; + *bits = root; + return 0; +} ADDED compat/zlib/contrib/infback9/inftree9.h Index: compat/zlib/contrib/infback9/inftree9.h ================================================================== --- /dev/null +++ compat/zlib/contrib/infback9/inftree9.h @@ -0,0 +1,61 @@ +/* inftree9.h -- header to use inftree9.c + * Copyright (C) 1995-2008 Mark Adler + * For conditions of distribution and use, see copyright notice in zlib.h + */ + +/* WARNING: this file should *not* be used by applications. It is + part of the implementation of the compression library and is + subject to change. Applications should only use zlib.h. + */ + +/* Structure for decoding tables. Each entry provides either the + information needed to do the operation requested by the code that + indexed that table entry, or it provides a pointer to another + table that indexes more bits of the code. op indicates whether + the entry is a pointer to another table, a literal, a length or + distance, an end-of-block, or an invalid code. For a table + pointer, the low four bits of op is the number of index bits of + that table. For a length or distance, the low four bits of op + is the number of extra bits to get after the code. bits is + the number of bits in this code or part of the code to drop off + of the bit buffer. val is the actual byte to output in the case + of a literal, the base length or distance, or the offset from + the current table to the next table. Each entry is four bytes. */ +typedef struct { + unsigned char op; /* operation, extra bits, table bits */ + unsigned char bits; /* bits in this part of the code */ + unsigned short val; /* offset in table or code value */ +} code; + +/* op values as set by inflate_table(): + 00000000 - literal + 0000tttt - table link, tttt != 0 is the number of table index bits + 100eeeee - length or distance, eeee is the number of extra bits + 01100000 - end of block + 01000000 - invalid code + */ + +/* Maximum size of the dynamic table. The maximum number of code structures is + 1446, which is the sum of 852 for literal/length codes and 594 for distance + codes. These values were found by exhaustive searches using the program + examples/enough.c found in the zlib distribtution. The arguments to that + program are the number of symbols, the initial root table size, and the + maximum bit length of a code. "enough 286 9 15" for literal/length codes + returns returns 852, and "enough 32 6 15" for distance codes returns 594. + The initial root table size (9 or 6) is found in the fifth argument of the + inflate_table() calls in infback9.c. If the root table size is changed, + then these maximum sizes would be need to be recalculated and updated. */ +#define ENOUGH_LENS 852 +#define ENOUGH_DISTS 594 +#define ENOUGH (ENOUGH_LENS+ENOUGH_DISTS) + +/* Type of code to build for inflate_table9() */ +typedef enum { + CODES, + LENS, + DISTS +} codetype; + +extern int inflate_table9 OF((codetype type, unsigned short FAR *lens, + unsigned codes, code FAR * FAR *table, + unsigned FAR *bits, unsigned short FAR *work)); ADDED compat/zlib/contrib/inflate86/inffas86.c Index: compat/zlib/contrib/inflate86/inffas86.c ================================================================== --- /dev/null +++ compat/zlib/contrib/inflate86/inffas86.c @@ -0,0 +1,1157 @@ +/* inffas86.c is a hand tuned assembler version of + * + * inffast.c -- fast decoding + * Copyright (C) 1995-2003 Mark Adler + * For conditions of distribution and use, see copyright notice in zlib.h + * + * Copyright (C) 2003 Chris Anderson + * Please use the copyright conditions above. + * + * Dec-29-2003 -- I added AMD64 inflate asm support. This version is also + * slightly quicker on x86 systems because, instead of using rep movsb to copy + * data, it uses rep movsw, which moves data in 2-byte chunks instead of single + * bytes. I've tested the AMD64 code on a Fedora Core 1 + the x86_64 updates + * from http://fedora.linux.duke.edu/fc1_x86_64 + * which is running on an Athlon 64 3000+ / Gigabyte GA-K8VT800M system with + * 1GB ram. The 64-bit version is about 4% faster than the 32-bit version, + * when decompressing mozilla-source-1.3.tar.gz. + * + * Mar-13-2003 -- Most of this is derived from inffast.S which is derived from + * the gcc -S output of zlib-1.2.0/inffast.c. Zlib-1.2.0 is in beta release at + * the moment. I have successfully compiled and tested this code with gcc2.96, + * gcc3.2, icc5.0, msvc6.0. It is very close to the speed of inffast.S + * compiled with gcc -DNO_MMX, but inffast.S is still faster on the P3 with MMX + * enabled. I will attempt to merge the MMX code into this version. Newer + * versions of this and inffast.S can be found at + * http://www.eetbeetee.com/zlib/ and http://www.charm.net/~christop/zlib/ + */ + +#include "zutil.h" +#include "inftrees.h" +#include "inflate.h" +#include "inffast.h" + +/* Mark Adler's comments from inffast.c: */ + +/* + Decode literal, length, and distance codes and write out the resulting + literal and match bytes until either not enough input or output is + available, an end-of-block is encountered, or a data error is encountered. + When large enough input and output buffers are supplied to inflate(), for + example, a 16K input buffer and a 64K output buffer, more than 95% of the + inflate execution time is spent in this routine. + + Entry assumptions: + + state->mode == LEN + strm->avail_in >= 6 + strm->avail_out >= 258 + start >= strm->avail_out + state->bits < 8 + + On return, state->mode is one of: + + LEN -- ran out of enough output space or enough available input + TYPE -- reached end of block code, inflate() to interpret next block + BAD -- error in block data + + Notes: + + - The maximum input bits used by a length/distance pair is 15 bits for the + length code, 5 bits for the length extra, 15 bits for the distance code, + and 13 bits for the distance extra. This totals 48 bits, or six bytes. + Therefore if strm->avail_in >= 6, then there is enough input to avoid + checking for available input while decoding. + + - The maximum bytes that a single length/distance pair can output is 258 + bytes, which is the maximum length that can be coded. inflate_fast() + requires strm->avail_out >= 258 for each loop to avoid checking for + output space. + */ +void inflate_fast(strm, start) +z_streamp strm; +unsigned start; /* inflate()'s starting value for strm->avail_out */ +{ + struct inflate_state FAR *state; + struct inffast_ar { +/* 64 32 x86 x86_64 */ +/* ar offset register */ +/* 0 0 */ void *esp; /* esp save */ +/* 8 4 */ void *ebp; /* ebp save */ +/* 16 8 */ unsigned char FAR *in; /* esi rsi local strm->next_in */ +/* 24 12 */ unsigned char FAR *last; /* r9 while in < last */ +/* 32 16 */ unsigned char FAR *out; /* edi rdi local strm->next_out */ +/* 40 20 */ unsigned char FAR *beg; /* inflate()'s init next_out */ +/* 48 24 */ unsigned char FAR *end; /* r10 while out < end */ +/* 56 28 */ unsigned char FAR *window;/* size of window, wsize!=0 */ +/* 64 32 */ code const FAR *lcode; /* ebp rbp local strm->lencode */ +/* 72 36 */ code const FAR *dcode; /* r11 local strm->distcode */ +/* 80 40 */ unsigned long hold; /* edx rdx local strm->hold */ +/* 88 44 */ unsigned bits; /* ebx rbx local strm->bits */ +/* 92 48 */ unsigned wsize; /* window size */ +/* 96 52 */ unsigned write; /* window write index */ +/*100 56 */ unsigned lmask; /* r12 mask for lcode */ +/*104 60 */ unsigned dmask; /* r13 mask for dcode */ +/*108 64 */ unsigned len; /* r14 match length */ +/*112 68 */ unsigned dist; /* r15 match distance */ +/*116 72 */ unsigned status; /* set when state chng*/ + } ar; + +#if defined( __GNUC__ ) && defined( __amd64__ ) && ! defined( __i386 ) +#define PAD_AVAIL_IN 6 +#define PAD_AVAIL_OUT 258 +#else +#define PAD_AVAIL_IN 5 +#define PAD_AVAIL_OUT 257 +#endif + + /* copy state to local variables */ + state = (struct inflate_state FAR *)strm->state; + ar.in = strm->next_in; + ar.last = ar.in + (strm->avail_in - PAD_AVAIL_IN); + ar.out = strm->next_out; + ar.beg = ar.out - (start - strm->avail_out); + ar.end = ar.out + (strm->avail_out - PAD_AVAIL_OUT); + ar.wsize = state->wsize; + ar.write = state->wnext; + ar.window = state->window; + ar.hold = state->hold; + ar.bits = state->bits; + ar.lcode = state->lencode; + ar.dcode = state->distcode; + ar.lmask = (1U << state->lenbits) - 1; + ar.dmask = (1U << state->distbits) - 1; + + /* decode literals and length/distances until end-of-block or not enough + input data or output space */ + + /* align in on 1/2 hold size boundary */ + while (((unsigned long)(void *)ar.in & (sizeof(ar.hold) / 2 - 1)) != 0) { + ar.hold += (unsigned long)*ar.in++ << ar.bits; + ar.bits += 8; + } + +#if defined( __GNUC__ ) && defined( __amd64__ ) && ! defined( __i386 ) + __asm__ __volatile__ ( +" leaq %0, %%rax\n" +" movq %%rbp, 8(%%rax)\n" /* save regs rbp and rsp */ +" movq %%rsp, (%%rax)\n" +" movq %%rax, %%rsp\n" /* make rsp point to &ar */ +" movq 16(%%rsp), %%rsi\n" /* rsi = in */ +" movq 32(%%rsp), %%rdi\n" /* rdi = out */ +" movq 24(%%rsp), %%r9\n" /* r9 = last */ +" movq 48(%%rsp), %%r10\n" /* r10 = end */ +" movq 64(%%rsp), %%rbp\n" /* rbp = lcode */ +" movq 72(%%rsp), %%r11\n" /* r11 = dcode */ +" movq 80(%%rsp), %%rdx\n" /* rdx = hold */ +" movl 88(%%rsp), %%ebx\n" /* ebx = bits */ +" movl 100(%%rsp), %%r12d\n" /* r12d = lmask */ +" movl 104(%%rsp), %%r13d\n" /* r13d = dmask */ + /* r14d = len */ + /* r15d = dist */ +" cld\n" +" cmpq %%rdi, %%r10\n" +" je .L_one_time\n" /* if only one decode left */ +" cmpq %%rsi, %%r9\n" +" je .L_one_time\n" +" jmp .L_do_loop\n" + +".L_one_time:\n" +" movq %%r12, %%r8\n" /* r8 = lmask */ +" cmpb $32, %%bl\n" +" ja .L_get_length_code_one_time\n" + +" lodsl\n" /* eax = *(uint *)in++ */ +" movb %%bl, %%cl\n" /* cl = bits, needs it for shifting */ +" addb $32, %%bl\n" /* bits += 32 */ +" shlq %%cl, %%rax\n" +" orq %%rax, %%rdx\n" /* hold |= *((uint *)in)++ << bits */ +" jmp .L_get_length_code_one_time\n" + +".align 32,0x90\n" +".L_while_test:\n" +" cmpq %%rdi, %%r10\n" +" jbe .L_break_loop\n" +" cmpq %%rsi, %%r9\n" +" jbe .L_break_loop\n" + +".L_do_loop:\n" +" movq %%r12, %%r8\n" /* r8 = lmask */ +" cmpb $32, %%bl\n" +" ja .L_get_length_code\n" /* if (32 < bits) */ + +" lodsl\n" /* eax = *(uint *)in++ */ +" movb %%bl, %%cl\n" /* cl = bits, needs it for shifting */ +" addb $32, %%bl\n" /* bits += 32 */ +" shlq %%cl, %%rax\n" +" orq %%rax, %%rdx\n" /* hold |= *((uint *)in)++ << bits */ + +".L_get_length_code:\n" +" andq %%rdx, %%r8\n" /* r8 &= hold */ +" movl (%%rbp,%%r8,4), %%eax\n" /* eax = lcode[hold & lmask] */ + +" movb %%ah, %%cl\n" /* cl = this.bits */ +" subb %%ah, %%bl\n" /* bits -= this.bits */ +" shrq %%cl, %%rdx\n" /* hold >>= this.bits */ + +" testb %%al, %%al\n" +" jnz .L_test_for_length_base\n" /* if (op != 0) 45.7% */ + +" movq %%r12, %%r8\n" /* r8 = lmask */ +" shrl $16, %%eax\n" /* output this.val char */ +" stosb\n" + +".L_get_length_code_one_time:\n" +" andq %%rdx, %%r8\n" /* r8 &= hold */ +" movl (%%rbp,%%r8,4), %%eax\n" /* eax = lcode[hold & lmask] */ + +".L_dolen:\n" +" movb %%ah, %%cl\n" /* cl = this.bits */ +" subb %%ah, %%bl\n" /* bits -= this.bits */ +" shrq %%cl, %%rdx\n" /* hold >>= this.bits */ + +" testb %%al, %%al\n" +" jnz .L_test_for_length_base\n" /* if (op != 0) 45.7% */ + +" shrl $16, %%eax\n" /* output this.val char */ +" stosb\n" +" jmp .L_while_test\n" + +".align 32,0x90\n" +".L_test_for_length_base:\n" +" movl %%eax, %%r14d\n" /* len = this */ +" shrl $16, %%r14d\n" /* len = this.val */ +" movb %%al, %%cl\n" + +" testb $16, %%al\n" +" jz .L_test_for_second_level_length\n" /* if ((op & 16) == 0) 8% */ +" andb $15, %%cl\n" /* op &= 15 */ +" jz .L_decode_distance\n" /* if (!op) */ + +".L_add_bits_to_len:\n" +" subb %%cl, %%bl\n" +" xorl %%eax, %%eax\n" +" incl %%eax\n" +" shll %%cl, %%eax\n" +" decl %%eax\n" +" andl %%edx, %%eax\n" /* eax &= hold */ +" shrq %%cl, %%rdx\n" +" addl %%eax, %%r14d\n" /* len += hold & mask[op] */ + +".L_decode_distance:\n" +" movq %%r13, %%r8\n" /* r8 = dmask */ +" cmpb $32, %%bl\n" +" ja .L_get_distance_code\n" /* if (32 < bits) */ + +" lodsl\n" /* eax = *(uint *)in++ */ +" movb %%bl, %%cl\n" /* cl = bits, needs it for shifting */ +" addb $32, %%bl\n" /* bits += 32 */ +" shlq %%cl, %%rax\n" +" orq %%rax, %%rdx\n" /* hold |= *((uint *)in)++ << bits */ + +".L_get_distance_code:\n" +" andq %%rdx, %%r8\n" /* r8 &= hold */ +" movl (%%r11,%%r8,4), %%eax\n" /* eax = dcode[hold & dmask] */ + +".L_dodist:\n" +" movl %%eax, %%r15d\n" /* dist = this */ +" shrl $16, %%r15d\n" /* dist = this.val */ +" movb %%ah, %%cl\n" +" subb %%ah, %%bl\n" /* bits -= this.bits */ +" shrq %%cl, %%rdx\n" /* hold >>= this.bits */ +" movb %%al, %%cl\n" /* cl = this.op */ + +" testb $16, %%al\n" /* if ((op & 16) == 0) */ +" jz .L_test_for_second_level_dist\n" +" andb $15, %%cl\n" /* op &= 15 */ +" jz .L_check_dist_one\n" + +".L_add_bits_to_dist:\n" +" subb %%cl, %%bl\n" +" xorl %%eax, %%eax\n" +" incl %%eax\n" +" shll %%cl, %%eax\n" +" decl %%eax\n" /* (1 << op) - 1 */ +" andl %%edx, %%eax\n" /* eax &= hold */ +" shrq %%cl, %%rdx\n" +" addl %%eax, %%r15d\n" /* dist += hold & ((1 << op) - 1) */ + +".L_check_window:\n" +" movq %%rsi, %%r8\n" /* save in so from can use it's reg */ +" movq %%rdi, %%rax\n" +" subq 40(%%rsp), %%rax\n" /* nbytes = out - beg */ + +" cmpl %%r15d, %%eax\n" +" jb .L_clip_window\n" /* if (dist > nbytes) 4.2% */ + +" movl %%r14d, %%ecx\n" /* ecx = len */ +" movq %%rdi, %%rsi\n" +" subq %%r15, %%rsi\n" /* from = out - dist */ + +" sarl %%ecx\n" +" jnc .L_copy_two\n" /* if len % 2 == 0 */ + +" rep movsw\n" +" movb (%%rsi), %%al\n" +" movb %%al, (%%rdi)\n" +" incq %%rdi\n" + +" movq %%r8, %%rsi\n" /* move in back to %rsi, toss from */ +" jmp .L_while_test\n" + +".L_copy_two:\n" +" rep movsw\n" +" movq %%r8, %%rsi\n" /* move in back to %rsi, toss from */ +" jmp .L_while_test\n" + +".align 32,0x90\n" +".L_check_dist_one:\n" +" cmpl $1, %%r15d\n" /* if dist 1, is a memset */ +" jne .L_check_window\n" +" cmpq %%rdi, 40(%%rsp)\n" /* if out == beg, outside window */ +" je .L_check_window\n" + +" movl %%r14d, %%ecx\n" /* ecx = len */ +" movb -1(%%rdi), %%al\n" +" movb %%al, %%ah\n" + +" sarl %%ecx\n" +" jnc .L_set_two\n" +" movb %%al, (%%rdi)\n" +" incq %%rdi\n" + +".L_set_two:\n" +" rep stosw\n" +" jmp .L_while_test\n" + +".align 32,0x90\n" +".L_test_for_second_level_length:\n" +" testb $64, %%al\n" +" jnz .L_test_for_end_of_block\n" /* if ((op & 64) != 0) */ + +" xorl %%eax, %%eax\n" +" incl %%eax\n" +" shll %%cl, %%eax\n" +" decl %%eax\n" +" andl %%edx, %%eax\n" /* eax &= hold */ +" addl %%r14d, %%eax\n" /* eax += len */ +" movl (%%rbp,%%rax,4), %%eax\n" /* eax = lcode[val+(hold&mask[op])]*/ +" jmp .L_dolen\n" + +".align 32,0x90\n" +".L_test_for_second_level_dist:\n" +" testb $64, %%al\n" +" jnz .L_invalid_distance_code\n" /* if ((op & 64) != 0) */ + +" xorl %%eax, %%eax\n" +" incl %%eax\n" +" shll %%cl, %%eax\n" +" decl %%eax\n" +" andl %%edx, %%eax\n" /* eax &= hold */ +" addl %%r15d, %%eax\n" /* eax += dist */ +" movl (%%r11,%%rax,4), %%eax\n" /* eax = dcode[val+(hold&mask[op])]*/ +" jmp .L_dodist\n" + +".align 32,0x90\n" +".L_clip_window:\n" +" movl %%eax, %%ecx\n" /* ecx = nbytes */ +" movl 92(%%rsp), %%eax\n" /* eax = wsize, prepare for dist cmp */ +" negl %%ecx\n" /* nbytes = -nbytes */ + +" cmpl %%r15d, %%eax\n" +" jb .L_invalid_distance_too_far\n" /* if (dist > wsize) */ + +" addl %%r15d, %%ecx\n" /* nbytes = dist - nbytes */ +" cmpl $0, 96(%%rsp)\n" +" jne .L_wrap_around_window\n" /* if (write != 0) */ + +" movq 56(%%rsp), %%rsi\n" /* from = window */ +" subl %%ecx, %%eax\n" /* eax -= nbytes */ +" addq %%rax, %%rsi\n" /* from += wsize - nbytes */ + +" movl %%r14d, %%eax\n" /* eax = len */ +" cmpl %%ecx, %%r14d\n" +" jbe .L_do_copy\n" /* if (nbytes >= len) */ + +" subl %%ecx, %%eax\n" /* eax -= nbytes */ +" rep movsb\n" +" movq %%rdi, %%rsi\n" +" subq %%r15, %%rsi\n" /* from = &out[ -dist ] */ +" jmp .L_do_copy\n" + +".align 32,0x90\n" +".L_wrap_around_window:\n" +" movl 96(%%rsp), %%eax\n" /* eax = write */ +" cmpl %%eax, %%ecx\n" +" jbe .L_contiguous_in_window\n" /* if (write >= nbytes) */ + +" movl 92(%%rsp), %%esi\n" /* from = wsize */ +" addq 56(%%rsp), %%rsi\n" /* from += window */ +" addq %%rax, %%rsi\n" /* from += write */ +" subq %%rcx, %%rsi\n" /* from -= nbytes */ +" subl %%eax, %%ecx\n" /* nbytes -= write */ + +" movl %%r14d, %%eax\n" /* eax = len */ +" cmpl %%ecx, %%eax\n" +" jbe .L_do_copy\n" /* if (nbytes >= len) */ + +" subl %%ecx, %%eax\n" /* len -= nbytes */ +" rep movsb\n" +" movq 56(%%rsp), %%rsi\n" /* from = window */ +" movl 96(%%rsp), %%ecx\n" /* nbytes = write */ +" cmpl %%ecx, %%eax\n" +" jbe .L_do_copy\n" /* if (nbytes >= len) */ + +" subl %%ecx, %%eax\n" /* len -= nbytes */ +" rep movsb\n" +" movq %%rdi, %%rsi\n" +" subq %%r15, %%rsi\n" /* from = out - dist */ +" jmp .L_do_copy\n" + +".align 32,0x90\n" +".L_contiguous_in_window:\n" +" movq 56(%%rsp), %%rsi\n" /* rsi = window */ +" addq %%rax, %%rsi\n" +" subq %%rcx, %%rsi\n" /* from += write - nbytes */ + +" movl %%r14d, %%eax\n" /* eax = len */ +" cmpl %%ecx, %%eax\n" +" jbe .L_do_copy\n" /* if (nbytes >= len) */ + +" subl %%ecx, %%eax\n" /* len -= nbytes */ +" rep movsb\n" +" movq %%rdi, %%rsi\n" +" subq %%r15, %%rsi\n" /* from = out - dist */ +" jmp .L_do_copy\n" /* if (nbytes >= len) */ + +".align 32,0x90\n" +".L_do_copy:\n" +" movl %%eax, %%ecx\n" /* ecx = len */ +" rep movsb\n" + +" movq %%r8, %%rsi\n" /* move in back to %esi, toss from */ +" jmp .L_while_test\n" + +".L_test_for_end_of_block:\n" +" testb $32, %%al\n" +" jz .L_invalid_literal_length_code\n" +" movl $1, 116(%%rsp)\n" +" jmp .L_break_loop_with_status\n" + +".L_invalid_literal_length_code:\n" +" movl $2, 116(%%rsp)\n" +" jmp .L_break_loop_with_status\n" + +".L_invalid_distance_code:\n" +" movl $3, 116(%%rsp)\n" +" jmp .L_break_loop_with_status\n" + +".L_invalid_distance_too_far:\n" +" movl $4, 116(%%rsp)\n" +" jmp .L_break_loop_with_status\n" + +".L_break_loop:\n" +" movl $0, 116(%%rsp)\n" + +".L_break_loop_with_status:\n" +/* put in, out, bits, and hold back into ar and pop esp */ +" movq %%rsi, 16(%%rsp)\n" /* in */ +" movq %%rdi, 32(%%rsp)\n" /* out */ +" movl %%ebx, 88(%%rsp)\n" /* bits */ +" movq %%rdx, 80(%%rsp)\n" /* hold */ +" movq (%%rsp), %%rax\n" /* restore rbp and rsp */ +" movq 8(%%rsp), %%rbp\n" +" movq %%rax, %%rsp\n" + : + : "m" (ar) + : "memory", "%rax", "%rbx", "%rcx", "%rdx", "%rsi", "%rdi", + "%r8", "%r9", "%r10", "%r11", "%r12", "%r13", "%r14", "%r15" + ); +#elif ( defined( __GNUC__ ) || defined( __ICC ) ) && defined( __i386 ) + __asm__ __volatile__ ( +" leal %0, %%eax\n" +" movl %%esp, (%%eax)\n" /* save esp, ebp */ +" movl %%ebp, 4(%%eax)\n" +" movl %%eax, %%esp\n" +" movl 8(%%esp), %%esi\n" /* esi = in */ +" movl 16(%%esp), %%edi\n" /* edi = out */ +" movl 40(%%esp), %%edx\n" /* edx = hold */ +" movl 44(%%esp), %%ebx\n" /* ebx = bits */ +" movl 32(%%esp), %%ebp\n" /* ebp = lcode */ + +" cld\n" +" jmp .L_do_loop\n" + +".align 32,0x90\n" +".L_while_test:\n" +" cmpl %%edi, 24(%%esp)\n" /* out < end */ +" jbe .L_break_loop\n" +" cmpl %%esi, 12(%%esp)\n" /* in < last */ +" jbe .L_break_loop\n" + +".L_do_loop:\n" +" cmpb $15, %%bl\n" +" ja .L_get_length_code\n" /* if (15 < bits) */ + +" xorl %%eax, %%eax\n" +" lodsw\n" /* al = *(ushort *)in++ */ +" movb %%bl, %%cl\n" /* cl = bits, needs it for shifting */ +" addb $16, %%bl\n" /* bits += 16 */ +" shll %%cl, %%eax\n" +" orl %%eax, %%edx\n" /* hold |= *((ushort *)in)++ << bits */ + +".L_get_length_code:\n" +" movl 56(%%esp), %%eax\n" /* eax = lmask */ +" andl %%edx, %%eax\n" /* eax &= hold */ +" movl (%%ebp,%%eax,4), %%eax\n" /* eax = lcode[hold & lmask] */ + +".L_dolen:\n" +" movb %%ah, %%cl\n" /* cl = this.bits */ +" subb %%ah, %%bl\n" /* bits -= this.bits */ +" shrl %%cl, %%edx\n" /* hold >>= this.bits */ + +" testb %%al, %%al\n" +" jnz .L_test_for_length_base\n" /* if (op != 0) 45.7% */ + +" shrl $16, %%eax\n" /* output this.val char */ +" stosb\n" +" jmp .L_while_test\n" + +".align 32,0x90\n" +".L_test_for_length_base:\n" +" movl %%eax, %%ecx\n" /* len = this */ +" shrl $16, %%ecx\n" /* len = this.val */ +" movl %%ecx, 64(%%esp)\n" /* save len */ +" movb %%al, %%cl\n" + +" testb $16, %%al\n" +" jz .L_test_for_second_level_length\n" /* if ((op & 16) == 0) 8% */ +" andb $15, %%cl\n" /* op &= 15 */ +" jz .L_decode_distance\n" /* if (!op) */ +" cmpb %%cl, %%bl\n" +" jae .L_add_bits_to_len\n" /* if (op <= bits) */ + +" movb %%cl, %%ch\n" /* stash op in ch, freeing cl */ +" xorl %%eax, %%eax\n" +" lodsw\n" /* al = *(ushort *)in++ */ +" movb %%bl, %%cl\n" /* cl = bits, needs it for shifting */ +" addb $16, %%bl\n" /* bits += 16 */ +" shll %%cl, %%eax\n" +" orl %%eax, %%edx\n" /* hold |= *((ushort *)in)++ << bits */ +" movb %%ch, %%cl\n" /* move op back to ecx */ + +".L_add_bits_to_len:\n" +" subb %%cl, %%bl\n" +" xorl %%eax, %%eax\n" +" incl %%eax\n" +" shll %%cl, %%eax\n" +" decl %%eax\n" +" andl %%edx, %%eax\n" /* eax &= hold */ +" shrl %%cl, %%edx\n" +" addl %%eax, 64(%%esp)\n" /* len += hold & mask[op] */ + +".L_decode_distance:\n" +" cmpb $15, %%bl\n" +" ja .L_get_distance_code\n" /* if (15 < bits) */ + +" xorl %%eax, %%eax\n" +" lodsw\n" /* al = *(ushort *)in++ */ +" movb %%bl, %%cl\n" /* cl = bits, needs it for shifting */ +" addb $16, %%bl\n" /* bits += 16 */ +" shll %%cl, %%eax\n" +" orl %%eax, %%edx\n" /* hold |= *((ushort *)in)++ << bits */ + +".L_get_distance_code:\n" +" movl 60(%%esp), %%eax\n" /* eax = dmask */ +" movl 36(%%esp), %%ecx\n" /* ecx = dcode */ +" andl %%edx, %%eax\n" /* eax &= hold */ +" movl (%%ecx,%%eax,4), %%eax\n"/* eax = dcode[hold & dmask] */ + +".L_dodist:\n" +" movl %%eax, %%ebp\n" /* dist = this */ +" shrl $16, %%ebp\n" /* dist = this.val */ +" movb %%ah, %%cl\n" +" subb %%ah, %%bl\n" /* bits -= this.bits */ +" shrl %%cl, %%edx\n" /* hold >>= this.bits */ +" movb %%al, %%cl\n" /* cl = this.op */ + +" testb $16, %%al\n" /* if ((op & 16) == 0) */ +" jz .L_test_for_second_level_dist\n" +" andb $15, %%cl\n" /* op &= 15 */ +" jz .L_check_dist_one\n" +" cmpb %%cl, %%bl\n" +" jae .L_add_bits_to_dist\n" /* if (op <= bits) 97.6% */ + +" movb %%cl, %%ch\n" /* stash op in ch, freeing cl */ +" xorl %%eax, %%eax\n" +" lodsw\n" /* al = *(ushort *)in++ */ +" movb %%bl, %%cl\n" /* cl = bits, needs it for shifting */ +" addb $16, %%bl\n" /* bits += 16 */ +" shll %%cl, %%eax\n" +" orl %%eax, %%edx\n" /* hold |= *((ushort *)in)++ << bits */ +" movb %%ch, %%cl\n" /* move op back to ecx */ + +".L_add_bits_to_dist:\n" +" subb %%cl, %%bl\n" +" xorl %%eax, %%eax\n" +" incl %%eax\n" +" shll %%cl, %%eax\n" +" decl %%eax\n" /* (1 << op) - 1 */ +" andl %%edx, %%eax\n" /* eax &= hold */ +" shrl %%cl, %%edx\n" +" addl %%eax, %%ebp\n" /* dist += hold & ((1 << op) - 1) */ + +".L_check_window:\n" +" movl %%esi, 8(%%esp)\n" /* save in so from can use it's reg */ +" movl %%edi, %%eax\n" +" subl 20(%%esp), %%eax\n" /* nbytes = out - beg */ + +" cmpl %%ebp, %%eax\n" +" jb .L_clip_window\n" /* if (dist > nbytes) 4.2% */ + +" movl 64(%%esp), %%ecx\n" /* ecx = len */ +" movl %%edi, %%esi\n" +" subl %%ebp, %%esi\n" /* from = out - dist */ + +" sarl %%ecx\n" +" jnc .L_copy_two\n" /* if len % 2 == 0 */ + +" rep movsw\n" +" movb (%%esi), %%al\n" +" movb %%al, (%%edi)\n" +" incl %%edi\n" + +" movl 8(%%esp), %%esi\n" /* move in back to %esi, toss from */ +" movl 32(%%esp), %%ebp\n" /* ebp = lcode */ +" jmp .L_while_test\n" + +".L_copy_two:\n" +" rep movsw\n" +" movl 8(%%esp), %%esi\n" /* move in back to %esi, toss from */ +" movl 32(%%esp), %%ebp\n" /* ebp = lcode */ +" jmp .L_while_test\n" + +".align 32,0x90\n" +".L_check_dist_one:\n" +" cmpl $1, %%ebp\n" /* if dist 1, is a memset */ +" jne .L_check_window\n" +" cmpl %%edi, 20(%%esp)\n" +" je .L_check_window\n" /* out == beg, if outside window */ + +" movl 64(%%esp), %%ecx\n" /* ecx = len */ +" movb -1(%%edi), %%al\n" +" movb %%al, %%ah\n" + +" sarl %%ecx\n" +" jnc .L_set_two\n" +" movb %%al, (%%edi)\n" +" incl %%edi\n" + +".L_set_two:\n" +" rep stosw\n" +" movl 32(%%esp), %%ebp\n" /* ebp = lcode */ +" jmp .L_while_test\n" + +".align 32,0x90\n" +".L_test_for_second_level_length:\n" +" testb $64, %%al\n" +" jnz .L_test_for_end_of_block\n" /* if ((op & 64) != 0) */ + +" xorl %%eax, %%eax\n" +" incl %%eax\n" +" shll %%cl, %%eax\n" +" decl %%eax\n" +" andl %%edx, %%eax\n" /* eax &= hold */ +" addl 64(%%esp), %%eax\n" /* eax += len */ +" movl (%%ebp,%%eax,4), %%eax\n" /* eax = lcode[val+(hold&mask[op])]*/ +" jmp .L_dolen\n" + +".align 32,0x90\n" +".L_test_for_second_level_dist:\n" +" testb $64, %%al\n" +" jnz .L_invalid_distance_code\n" /* if ((op & 64) != 0) */ + +" xorl %%eax, %%eax\n" +" incl %%eax\n" +" shll %%cl, %%eax\n" +" decl %%eax\n" +" andl %%edx, %%eax\n" /* eax &= hold */ +" addl %%ebp, %%eax\n" /* eax += dist */ +" movl 36(%%esp), %%ecx\n" /* ecx = dcode */ +" movl (%%ecx,%%eax,4), %%eax\n" /* eax = dcode[val+(hold&mask[op])]*/ +" jmp .L_dodist\n" + +".align 32,0x90\n" +".L_clip_window:\n" +" movl %%eax, %%ecx\n" +" movl 48(%%esp), %%eax\n" /* eax = wsize */ +" negl %%ecx\n" /* nbytes = -nbytes */ +" movl 28(%%esp), %%esi\n" /* from = window */ + +" cmpl %%ebp, %%eax\n" +" jb .L_invalid_distance_too_far\n" /* if (dist > wsize) */ + +" addl %%ebp, %%ecx\n" /* nbytes = dist - nbytes */ +" cmpl $0, 52(%%esp)\n" +" jne .L_wrap_around_window\n" /* if (write != 0) */ + +" subl %%ecx, %%eax\n" +" addl %%eax, %%esi\n" /* from += wsize - nbytes */ + +" movl 64(%%esp), %%eax\n" /* eax = len */ +" cmpl %%ecx, %%eax\n" +" jbe .L_do_copy\n" /* if (nbytes >= len) */ + +" subl %%ecx, %%eax\n" /* len -= nbytes */ +" rep movsb\n" +" movl %%edi, %%esi\n" +" subl %%ebp, %%esi\n" /* from = out - dist */ +" jmp .L_do_copy\n" + +".align 32,0x90\n" +".L_wrap_around_window:\n" +" movl 52(%%esp), %%eax\n" /* eax = write */ +" cmpl %%eax, %%ecx\n" +" jbe .L_contiguous_in_window\n" /* if (write >= nbytes) */ + +" addl 48(%%esp), %%esi\n" /* from += wsize */ +" addl %%eax, %%esi\n" /* from += write */ +" subl %%ecx, %%esi\n" /* from -= nbytes */ +" subl %%eax, %%ecx\n" /* nbytes -= write */ + +" movl 64(%%esp), %%eax\n" /* eax = len */ +" cmpl %%ecx, %%eax\n" +" jbe .L_do_copy\n" /* if (nbytes >= len) */ + +" subl %%ecx, %%eax\n" /* len -= nbytes */ +" rep movsb\n" +" movl 28(%%esp), %%esi\n" /* from = window */ +" movl 52(%%esp), %%ecx\n" /* nbytes = write */ +" cmpl %%ecx, %%eax\n" +" jbe .L_do_copy\n" /* if (nbytes >= len) */ + +" subl %%ecx, %%eax\n" /* len -= nbytes */ +" rep movsb\n" +" movl %%edi, %%esi\n" +" subl %%ebp, %%esi\n" /* from = out - dist */ +" jmp .L_do_copy\n" + +".align 32,0x90\n" +".L_contiguous_in_window:\n" +" addl %%eax, %%esi\n" +" subl %%ecx, %%esi\n" /* from += write - nbytes */ + +" movl 64(%%esp), %%eax\n" /* eax = len */ +" cmpl %%ecx, %%eax\n" +" jbe .L_do_copy\n" /* if (nbytes >= len) */ + +" subl %%ecx, %%eax\n" /* len -= nbytes */ +" rep movsb\n" +" movl %%edi, %%esi\n" +" subl %%ebp, %%esi\n" /* from = out - dist */ +" jmp .L_do_copy\n" /* if (nbytes >= len) */ + +".align 32,0x90\n" +".L_do_copy:\n" +" movl %%eax, %%ecx\n" +" rep movsb\n" + +" movl 8(%%esp), %%esi\n" /* move in back to %esi, toss from */ +" movl 32(%%esp), %%ebp\n" /* ebp = lcode */ +" jmp .L_while_test\n" + +".L_test_for_end_of_block:\n" +" testb $32, %%al\n" +" jz .L_invalid_literal_length_code\n" +" movl $1, 72(%%esp)\n" +" jmp .L_break_loop_with_status\n" + +".L_invalid_literal_length_code:\n" +" movl $2, 72(%%esp)\n" +" jmp .L_break_loop_with_status\n" + +".L_invalid_distance_code:\n" +" movl $3, 72(%%esp)\n" +" jmp .L_break_loop_with_status\n" + +".L_invalid_distance_too_far:\n" +" movl 8(%%esp), %%esi\n" +" movl $4, 72(%%esp)\n" +" jmp .L_break_loop_with_status\n" + +".L_break_loop:\n" +" movl $0, 72(%%esp)\n" + +".L_break_loop_with_status:\n" +/* put in, out, bits, and hold back into ar and pop esp */ +" movl %%esi, 8(%%esp)\n" /* save in */ +" movl %%edi, 16(%%esp)\n" /* save out */ +" movl %%ebx, 44(%%esp)\n" /* save bits */ +" movl %%edx, 40(%%esp)\n" /* save hold */ +" movl 4(%%esp), %%ebp\n" /* restore esp, ebp */ +" movl (%%esp), %%esp\n" + : + : "m" (ar) + : "memory", "%eax", "%ebx", "%ecx", "%edx", "%esi", "%edi" + ); +#elif defined( _MSC_VER ) && ! defined( _M_AMD64 ) + __asm { + lea eax, ar + mov [eax], esp /* save esp, ebp */ + mov [eax+4], ebp + mov esp, eax + mov esi, [esp+8] /* esi = in */ + mov edi, [esp+16] /* edi = out */ + mov edx, [esp+40] /* edx = hold */ + mov ebx, [esp+44] /* ebx = bits */ + mov ebp, [esp+32] /* ebp = lcode */ + + cld + jmp L_do_loop + +ALIGN 4 +L_while_test: + cmp [esp+24], edi + jbe L_break_loop + cmp [esp+12], esi + jbe L_break_loop + +L_do_loop: + cmp bl, 15 + ja L_get_length_code /* if (15 < bits) */ + + xor eax, eax + lodsw /* al = *(ushort *)in++ */ + mov cl, bl /* cl = bits, needs it for shifting */ + add bl, 16 /* bits += 16 */ + shl eax, cl + or edx, eax /* hold |= *((ushort *)in)++ << bits */ + +L_get_length_code: + mov eax, [esp+56] /* eax = lmask */ + and eax, edx /* eax &= hold */ + mov eax, [ebp+eax*4] /* eax = lcode[hold & lmask] */ + +L_dolen: + mov cl, ah /* cl = this.bits */ + sub bl, ah /* bits -= this.bits */ + shr edx, cl /* hold >>= this.bits */ + + test al, al + jnz L_test_for_length_base /* if (op != 0) 45.7% */ + + shr eax, 16 /* output this.val char */ + stosb + jmp L_while_test + +ALIGN 4 +L_test_for_length_base: + mov ecx, eax /* len = this */ + shr ecx, 16 /* len = this.val */ + mov [esp+64], ecx /* save len */ + mov cl, al + + test al, 16 + jz L_test_for_second_level_length /* if ((op & 16) == 0) 8% */ + and cl, 15 /* op &= 15 */ + jz L_decode_distance /* if (!op) */ + cmp bl, cl + jae L_add_bits_to_len /* if (op <= bits) */ + + mov ch, cl /* stash op in ch, freeing cl */ + xor eax, eax + lodsw /* al = *(ushort *)in++ */ + mov cl, bl /* cl = bits, needs it for shifting */ + add bl, 16 /* bits += 16 */ + shl eax, cl + or edx, eax /* hold |= *((ushort *)in)++ << bits */ + mov cl, ch /* move op back to ecx */ + +L_add_bits_to_len: + sub bl, cl + xor eax, eax + inc eax + shl eax, cl + dec eax + and eax, edx /* eax &= hold */ + shr edx, cl + add [esp+64], eax /* len += hold & mask[op] */ + +L_decode_distance: + cmp bl, 15 + ja L_get_distance_code /* if (15 < bits) */ + + xor eax, eax + lodsw /* al = *(ushort *)in++ */ + mov cl, bl /* cl = bits, needs it for shifting */ + add bl, 16 /* bits += 16 */ + shl eax, cl + or edx, eax /* hold |= *((ushort *)in)++ << bits */ + +L_get_distance_code: + mov eax, [esp+60] /* eax = dmask */ + mov ecx, [esp+36] /* ecx = dcode */ + and eax, edx /* eax &= hold */ + mov eax, [ecx+eax*4]/* eax = dcode[hold & dmask] */ + +L_dodist: + mov ebp, eax /* dist = this */ + shr ebp, 16 /* dist = this.val */ + mov cl, ah + sub bl, ah /* bits -= this.bits */ + shr edx, cl /* hold >>= this.bits */ + mov cl, al /* cl = this.op */ + + test al, 16 /* if ((op & 16) == 0) */ + jz L_test_for_second_level_dist + and cl, 15 /* op &= 15 */ + jz L_check_dist_one + cmp bl, cl + jae L_add_bits_to_dist /* if (op <= bits) 97.6% */ + + mov ch, cl /* stash op in ch, freeing cl */ + xor eax, eax + lodsw /* al = *(ushort *)in++ */ + mov cl, bl /* cl = bits, needs it for shifting */ + add bl, 16 /* bits += 16 */ + shl eax, cl + or edx, eax /* hold |= *((ushort *)in)++ << bits */ + mov cl, ch /* move op back to ecx */ + +L_add_bits_to_dist: + sub bl, cl + xor eax, eax + inc eax + shl eax, cl + dec eax /* (1 << op) - 1 */ + and eax, edx /* eax &= hold */ + shr edx, cl + add ebp, eax /* dist += hold & ((1 << op) - 1) */ + +L_check_window: + mov [esp+8], esi /* save in so from can use it's reg */ + mov eax, edi + sub eax, [esp+20] /* nbytes = out - beg */ + + cmp eax, ebp + jb L_clip_window /* if (dist > nbytes) 4.2% */ + + mov ecx, [esp+64] /* ecx = len */ + mov esi, edi + sub esi, ebp /* from = out - dist */ + + sar ecx, 1 + jnc L_copy_two + + rep movsw + mov al, [esi] + mov [edi], al + inc edi + + mov esi, [esp+8] /* move in back to %esi, toss from */ + mov ebp, [esp+32] /* ebp = lcode */ + jmp L_while_test + +L_copy_two: + rep movsw + mov esi, [esp+8] /* move in back to %esi, toss from */ + mov ebp, [esp+32] /* ebp = lcode */ + jmp L_while_test + +ALIGN 4 +L_check_dist_one: + cmp ebp, 1 /* if dist 1, is a memset */ + jne L_check_window + cmp [esp+20], edi + je L_check_window /* out == beg, if outside window */ + + mov ecx, [esp+64] /* ecx = len */ + mov al, [edi-1] + mov ah, al + + sar ecx, 1 + jnc L_set_two + mov [edi], al /* memset out with from[-1] */ + inc edi + +L_set_two: + rep stosw + mov ebp, [esp+32] /* ebp = lcode */ + jmp L_while_test + +ALIGN 4 +L_test_for_second_level_length: + test al, 64 + jnz L_test_for_end_of_block /* if ((op & 64) != 0) */ + + xor eax, eax + inc eax + shl eax, cl + dec eax + and eax, edx /* eax &= hold */ + add eax, [esp+64] /* eax += len */ + mov eax, [ebp+eax*4] /* eax = lcode[val+(hold&mask[op])]*/ + jmp L_dolen + +ALIGN 4 +L_test_for_second_level_dist: + test al, 64 + jnz L_invalid_distance_code /* if ((op & 64) != 0) */ + + xor eax, eax + inc eax + shl eax, cl + dec eax + and eax, edx /* eax &= hold */ + add eax, ebp /* eax += dist */ + mov ecx, [esp+36] /* ecx = dcode */ + mov eax, [ecx+eax*4] /* eax = dcode[val+(hold&mask[op])]*/ + jmp L_dodist + +ALIGN 4 +L_clip_window: + mov ecx, eax + mov eax, [esp+48] /* eax = wsize */ + neg ecx /* nbytes = -nbytes */ + mov esi, [esp+28] /* from = window */ + + cmp eax, ebp + jb L_invalid_distance_too_far /* if (dist > wsize) */ + + add ecx, ebp /* nbytes = dist - nbytes */ + cmp dword ptr [esp+52], 0 + jne L_wrap_around_window /* if (write != 0) */ + + sub eax, ecx + add esi, eax /* from += wsize - nbytes */ + + mov eax, [esp+64] /* eax = len */ + cmp eax, ecx + jbe L_do_copy /* if (nbytes >= len) */ + + sub eax, ecx /* len -= nbytes */ + rep movsb + mov esi, edi + sub esi, ebp /* from = out - dist */ + jmp L_do_copy + +ALIGN 4 +L_wrap_around_window: + mov eax, [esp+52] /* eax = write */ + cmp ecx, eax + jbe L_contiguous_in_window /* if (write >= nbytes) */ + + add esi, [esp+48] /* from += wsize */ + add esi, eax /* from += write */ + sub esi, ecx /* from -= nbytes */ + sub ecx, eax /* nbytes -= write */ + + mov eax, [esp+64] /* eax = len */ + cmp eax, ecx + jbe L_do_copy /* if (nbytes >= len) */ + + sub eax, ecx /* len -= nbytes */ + rep movsb + mov esi, [esp+28] /* from = window */ + mov ecx, [esp+52] /* nbytes = write */ + cmp eax, ecx + jbe L_do_copy /* if (nbytes >= len) */ + + sub eax, ecx /* len -= nbytes */ + rep movsb + mov esi, edi + sub esi, ebp /* from = out - dist */ + jmp L_do_copy + +ALIGN 4 +L_contiguous_in_window: + add esi, eax + sub esi, ecx /* from += write - nbytes */ + + mov eax, [esp+64] /* eax = len */ + cmp eax, ecx + jbe L_do_copy /* if (nbytes >= len) */ + + sub eax, ecx /* len -= nbytes */ + rep movsb + mov esi, edi + sub esi, ebp /* from = out - dist */ + jmp L_do_copy + +ALIGN 4 +L_do_copy: + mov ecx, eax + rep movsb + + mov esi, [esp+8] /* move in back to %esi, toss from */ + mov ebp, [esp+32] /* ebp = lcode */ + jmp L_while_test + +L_test_for_end_of_block: + test al, 32 + jz L_invalid_literal_length_code + mov dword ptr [esp+72], 1 + jmp L_break_loop_with_status + +L_invalid_literal_length_code: + mov dword ptr [esp+72], 2 + jmp L_break_loop_with_status + +L_invalid_distance_code: + mov dword ptr [esp+72], 3 + jmp L_break_loop_with_status + +L_invalid_distance_too_far: + mov esi, [esp+4] + mov dword ptr [esp+72], 4 + jmp L_break_loop_with_status + +L_break_loop: + mov dword ptr [esp+72], 0 + +L_break_loop_with_status: +/* put in, out, bits, and hold back into ar and pop esp */ + mov [esp+8], esi /* save in */ + mov [esp+16], edi /* save out */ + mov [esp+44], ebx /* save bits */ + mov [esp+40], edx /* save hold */ + mov ebp, [esp+4] /* restore esp, ebp */ + mov esp, [esp] + } +#else +#error "x86 architecture not defined" +#endif + + if (ar.status > 1) { + if (ar.status == 2) + strm->msg = "invalid literal/length code"; + else if (ar.status == 3) + strm->msg = "invalid distance code"; + else + strm->msg = "invalid distance too far back"; + state->mode = BAD; + } + else if ( ar.status == 1 ) { + state->mode = TYPE; + } + + /* return unused bytes (on entry, bits < 8, so in won't go too far back) */ + ar.len = ar.bits >> 3; + ar.in -= ar.len; + ar.bits -= ar.len << 3; + ar.hold &= (1U << ar.bits) - 1; + + /* update state and return */ + strm->next_in = ar.in; + strm->next_out = ar.out; + strm->avail_in = (unsigned)(ar.in < ar.last ? + PAD_AVAIL_IN + (ar.last - ar.in) : + PAD_AVAIL_IN - (ar.in - ar.last)); + strm->avail_out = (unsigned)(ar.out < ar.end ? + PAD_AVAIL_OUT + (ar.end - ar.out) : + PAD_AVAIL_OUT - (ar.out - ar.end)); + state->hold = ar.hold; + state->bits = ar.bits; + return; +} + ADDED compat/zlib/contrib/inflate86/inffast.S Index: compat/zlib/contrib/inflate86/inffast.S ================================================================== --- /dev/null +++ compat/zlib/contrib/inflate86/inffast.S @@ -0,0 +1,1368 @@ +/* + * inffast.S is a hand tuned assembler version of: + * + * inffast.c -- fast decoding + * Copyright (C) 1995-2003 Mark Adler + * For conditions of distribution and use, see copyright notice in zlib.h + * + * Copyright (C) 2003 Chris Anderson + * Please use the copyright conditions above. + * + * This version (Jan-23-2003) of inflate_fast was coded and tested under + * GNU/Linux on a pentium 3, using the gcc-3.2 compiler distribution. On that + * machine, I found that gzip style archives decompressed about 20% faster than + * the gcc-3.2 -O3 -fomit-frame-pointer compiled version. Your results will + * depend on how large of a buffer is used for z_stream.next_in & next_out + * (8K-32K worked best for my 256K cpu cache) and how much overhead there is in + * stream processing I/O and crc32/addler32. In my case, this routine used + * 70% of the cpu time and crc32 used 20%. + * + * I am confident that this version will work in the general case, but I have + * not tested a wide variety of datasets or a wide variety of platforms. + * + * Jan-24-2003 -- Added -DUSE_MMX define for slightly faster inflating. + * It should be a runtime flag instead of compile time flag... + * + * Jan-26-2003 -- Added runtime check for MMX support with cpuid instruction. + * With -DUSE_MMX, only MMX code is compiled. With -DNO_MMX, only non-MMX code + * is compiled. Without either option, runtime detection is enabled. Runtime + * detection should work on all modern cpus and the recomended algorithm (flip + * ID bit on eflags and then use the cpuid instruction) is used in many + * multimedia applications. Tested under win2k with gcc-2.95 and gas-2.12 + * distributed with cygwin3. Compiling with gcc-2.95 -c inffast.S -o + * inffast.obj generates a COFF object which can then be linked with MSVC++ + * compiled code. Tested under FreeBSD 4.7 with gcc-2.95. + * + * Jan-28-2003 -- Tested Athlon XP... MMX mode is slower than no MMX (and + * slower than compiler generated code). Adjusted cpuid check to use the MMX + * code only for Pentiums < P4 until I have more data on the P4. Speed + * improvment is only about 15% on the Athlon when compared with code generated + * with MSVC++. Not sure yet, but I think the P4 will also be slower using the + * MMX mode because many of it's x86 ALU instructions execute in .5 cycles and + * have less latency than MMX ops. Added code to buffer the last 11 bytes of + * the input stream since the MMX code grabs bits in chunks of 32, which + * differs from the inffast.c algorithm. I don't think there would have been + * read overruns where a page boundary was crossed (a segfault), but there + * could have been overruns when next_in ends on unaligned memory (unintialized + * memory read). + * + * Mar-13-2003 -- P4 MMX is slightly slower than P4 NO_MMX. I created a C + * version of the non-MMX code so that it doesn't depend on zstrm and zstate + * structure offsets which are hard coded in this file. This was last tested + * with zlib-1.2.0 which is currently in beta testing, newer versions of this + * and inffas86.c can be found at http://www.eetbeetee.com/zlib/ and + * http://www.charm.net/~christop/zlib/ + */ + + +/* + * if you have underscore linking problems (_inflate_fast undefined), try + * using -DGAS_COFF + */ +#if ! defined( GAS_COFF ) && ! defined( GAS_ELF ) + +#if defined( WIN32 ) || defined( __CYGWIN__ ) +#define GAS_COFF /* windows object format */ +#else +#define GAS_ELF +#endif + +#endif /* ! GAS_COFF && ! GAS_ELF */ + + +#if defined( GAS_COFF ) + +/* coff externals have underscores */ +#define inflate_fast _inflate_fast +#define inflate_fast_use_mmx _inflate_fast_use_mmx + +#endif /* GAS_COFF */ + + +.file "inffast.S" + +.globl inflate_fast + +.text +.align 4,0 +.L_invalid_literal_length_code_msg: +.string "invalid literal/length code" + +.align 4,0 +.L_invalid_distance_code_msg: +.string "invalid distance code" + +.align 4,0 +.L_invalid_distance_too_far_msg: +.string "invalid distance too far back" + +#if ! defined( NO_MMX ) +.align 4,0 +.L_mask: /* mask[N] = ( 1 << N ) - 1 */ +.long 0 +.long 1 +.long 3 +.long 7 +.long 15 +.long 31 +.long 63 +.long 127 +.long 255 +.long 511 +.long 1023 +.long 2047 +.long 4095 +.long 8191 +.long 16383 +.long 32767 +.long 65535 +.long 131071 +.long 262143 +.long 524287 +.long 1048575 +.long 2097151 +.long 4194303 +.long 8388607 +.long 16777215 +.long 33554431 +.long 67108863 +.long 134217727 +.long 268435455 +.long 536870911 +.long 1073741823 +.long 2147483647 +.long 4294967295 +#endif /* NO_MMX */ + +.text + +/* + * struct z_stream offsets, in zlib.h + */ +#define next_in_strm 0 /* strm->next_in */ +#define avail_in_strm 4 /* strm->avail_in */ +#define next_out_strm 12 /* strm->next_out */ +#define avail_out_strm 16 /* strm->avail_out */ +#define msg_strm 24 /* strm->msg */ +#define state_strm 28 /* strm->state */ + +/* + * struct inflate_state offsets, in inflate.h + */ +#define mode_state 0 /* state->mode */ +#define wsize_state 32 /* state->wsize */ +#define write_state 40 /* state->write */ +#define window_state 44 /* state->window */ +#define hold_state 48 /* state->hold */ +#define bits_state 52 /* state->bits */ +#define lencode_state 68 /* state->lencode */ +#define distcode_state 72 /* state->distcode */ +#define lenbits_state 76 /* state->lenbits */ +#define distbits_state 80 /* state->distbits */ + +/* + * inflate_fast's activation record + */ +#define local_var_size 64 /* how much local space for vars */ +#define strm_sp 88 /* first arg: z_stream * (local_var_size + 24) */ +#define start_sp 92 /* second arg: unsigned int (local_var_size + 28) */ + +/* + * offsets for local vars on stack + */ +#define out 60 /* unsigned char* */ +#define window 56 /* unsigned char* */ +#define wsize 52 /* unsigned int */ +#define write 48 /* unsigned int */ +#define in 44 /* unsigned char* */ +#define beg 40 /* unsigned char* */ +#define buf 28 /* char[ 12 ] */ +#define len 24 /* unsigned int */ +#define last 20 /* unsigned char* */ +#define end 16 /* unsigned char* */ +#define dcode 12 /* code* */ +#define lcode 8 /* code* */ +#define dmask 4 /* unsigned int */ +#define lmask 0 /* unsigned int */ + +/* + * typedef enum inflate_mode consts, in inflate.h + */ +#define INFLATE_MODE_TYPE 11 /* state->mode flags enum-ed in inflate.h */ +#define INFLATE_MODE_BAD 26 + + +#if ! defined( USE_MMX ) && ! defined( NO_MMX ) + +#define RUN_TIME_MMX + +#define CHECK_MMX 1 +#define DO_USE_MMX 2 +#define DONT_USE_MMX 3 + +.globl inflate_fast_use_mmx + +.data + +.align 4,0 +inflate_fast_use_mmx: /* integer flag for run time control 1=check,2=mmx,3=no */ +.long CHECK_MMX + +#if defined( GAS_ELF ) +/* elf info */ +.type inflate_fast_use_mmx,@object +.size inflate_fast_use_mmx,4 +#endif + +#endif /* RUN_TIME_MMX */ + +#if defined( GAS_COFF ) +/* coff info: scl 2 = extern, type 32 = function */ +.def inflate_fast; .scl 2; .type 32; .endef +#endif + +.text + +.align 32,0x90 +inflate_fast: + pushl %edi + pushl %esi + pushl %ebp + pushl %ebx + pushf /* save eflags (strm_sp, state_sp assumes this is 32 bits) */ + subl $local_var_size, %esp + cld + +#define strm_r %esi +#define state_r %edi + + movl strm_sp(%esp), strm_r + movl state_strm(strm_r), state_r + + /* in = strm->next_in; + * out = strm->next_out; + * last = in + strm->avail_in - 11; + * beg = out - (start - strm->avail_out); + * end = out + (strm->avail_out - 257); + */ + movl avail_in_strm(strm_r), %edx + movl next_in_strm(strm_r), %eax + + addl %eax, %edx /* avail_in += next_in */ + subl $11, %edx /* avail_in -= 11 */ + + movl %eax, in(%esp) + movl %edx, last(%esp) + + movl start_sp(%esp), %ebp + movl avail_out_strm(strm_r), %ecx + movl next_out_strm(strm_r), %ebx + + subl %ecx, %ebp /* start -= avail_out */ + negl %ebp /* start = -start */ + addl %ebx, %ebp /* start += next_out */ + + subl $257, %ecx /* avail_out -= 257 */ + addl %ebx, %ecx /* avail_out += out */ + + movl %ebx, out(%esp) + movl %ebp, beg(%esp) + movl %ecx, end(%esp) + + /* wsize = state->wsize; + * write = state->write; + * window = state->window; + * hold = state->hold; + * bits = state->bits; + * lcode = state->lencode; + * dcode = state->distcode; + * lmask = ( 1 << state->lenbits ) - 1; + * dmask = ( 1 << state->distbits ) - 1; + */ + + movl lencode_state(state_r), %eax + movl distcode_state(state_r), %ecx + + movl %eax, lcode(%esp) + movl %ecx, dcode(%esp) + + movl $1, %eax + movl lenbits_state(state_r), %ecx + shll %cl, %eax + decl %eax + movl %eax, lmask(%esp) + + movl $1, %eax + movl distbits_state(state_r), %ecx + shll %cl, %eax + decl %eax + movl %eax, dmask(%esp) + + movl wsize_state(state_r), %eax + movl write_state(state_r), %ecx + movl window_state(state_r), %edx + + movl %eax, wsize(%esp) + movl %ecx, write(%esp) + movl %edx, window(%esp) + + movl hold_state(state_r), %ebp + movl bits_state(state_r), %ebx + +#undef strm_r +#undef state_r + +#define in_r %esi +#define from_r %esi +#define out_r %edi + + movl in(%esp), in_r + movl last(%esp), %ecx + cmpl in_r, %ecx + ja .L_align_long /* if in < last */ + + addl $11, %ecx /* ecx = &in[ avail_in ] */ + subl in_r, %ecx /* ecx = avail_in */ + movl $12, %eax + subl %ecx, %eax /* eax = 12 - avail_in */ + leal buf(%esp), %edi + rep movsb /* memcpy( buf, in, avail_in ) */ + movl %eax, %ecx + xorl %eax, %eax + rep stosb /* memset( &buf[ avail_in ], 0, 12 - avail_in ) */ + leal buf(%esp), in_r /* in = buf */ + movl in_r, last(%esp) /* last = in, do just one iteration */ + jmp .L_is_aligned + + /* align in_r on long boundary */ +.L_align_long: + testl $3, in_r + jz .L_is_aligned + xorl %eax, %eax + movb (in_r), %al + incl in_r + movl %ebx, %ecx + addl $8, %ebx + shll %cl, %eax + orl %eax, %ebp + jmp .L_align_long + +.L_is_aligned: + movl out(%esp), out_r + +#if defined( NO_MMX ) + jmp .L_do_loop +#endif + +#if defined( USE_MMX ) + jmp .L_init_mmx +#endif + +/*** Runtime MMX check ***/ + +#if defined( RUN_TIME_MMX ) +.L_check_mmx: + cmpl $DO_USE_MMX, inflate_fast_use_mmx + je .L_init_mmx + ja .L_do_loop /* > 2 */ + + pushl %eax + pushl %ebx + pushl %ecx + pushl %edx + pushf + movl (%esp), %eax /* copy eflags to eax */ + xorl $0x200000, (%esp) /* try toggling ID bit of eflags (bit 21) + * to see if cpu supports cpuid... + * ID bit method not supported by NexGen but + * bios may load a cpuid instruction and + * cpuid may be disabled on Cyrix 5-6x86 */ + popf + pushf + popl %edx /* copy new eflags to edx */ + xorl %eax, %edx /* test if ID bit is flipped */ + jz .L_dont_use_mmx /* not flipped if zero */ + xorl %eax, %eax + cpuid + cmpl $0x756e6547, %ebx /* check for GenuineIntel in ebx,ecx,edx */ + jne .L_dont_use_mmx + cmpl $0x6c65746e, %ecx + jne .L_dont_use_mmx + cmpl $0x49656e69, %edx + jne .L_dont_use_mmx + movl $1, %eax + cpuid /* get cpu features */ + shrl $8, %eax + andl $15, %eax + cmpl $6, %eax /* check for Pentium family, is 0xf for P4 */ + jne .L_dont_use_mmx + testl $0x800000, %edx /* test if MMX feature is set (bit 23) */ + jnz .L_use_mmx + jmp .L_dont_use_mmx +.L_use_mmx: + movl $DO_USE_MMX, inflate_fast_use_mmx + jmp .L_check_mmx_pop +.L_dont_use_mmx: + movl $DONT_USE_MMX, inflate_fast_use_mmx +.L_check_mmx_pop: + popl %edx + popl %ecx + popl %ebx + popl %eax + jmp .L_check_mmx +#endif + + +/*** Non-MMX code ***/ + +#if defined ( NO_MMX ) || defined( RUN_TIME_MMX ) + +#define hold_r %ebp +#define bits_r %bl +#define bitslong_r %ebx + +.align 32,0x90 +.L_while_test: + /* while (in < last && out < end) + */ + cmpl out_r, end(%esp) + jbe .L_break_loop /* if (out >= end) */ + + cmpl in_r, last(%esp) + jbe .L_break_loop + +.L_do_loop: + /* regs: %esi = in, %ebp = hold, %bl = bits, %edi = out + * + * do { + * if (bits < 15) { + * hold |= *((unsigned short *)in)++ << bits; + * bits += 16 + * } + * this = lcode[hold & lmask] + */ + cmpb $15, bits_r + ja .L_get_length_code /* if (15 < bits) */ + + xorl %eax, %eax + lodsw /* al = *(ushort *)in++ */ + movb bits_r, %cl /* cl = bits, needs it for shifting */ + addb $16, bits_r /* bits += 16 */ + shll %cl, %eax + orl %eax, hold_r /* hold |= *((ushort *)in)++ << bits */ + +.L_get_length_code: + movl lmask(%esp), %edx /* edx = lmask */ + movl lcode(%esp), %ecx /* ecx = lcode */ + andl hold_r, %edx /* edx &= hold */ + movl (%ecx,%edx,4), %eax /* eax = lcode[hold & lmask] */ + +.L_dolen: + /* regs: %esi = in, %ebp = hold, %bl = bits, %edi = out + * + * dolen: + * bits -= this.bits; + * hold >>= this.bits + */ + movb %ah, %cl /* cl = this.bits */ + subb %ah, bits_r /* bits -= this.bits */ + shrl %cl, hold_r /* hold >>= this.bits */ + + /* check if op is a literal + * if (op == 0) { + * PUP(out) = this.val; + * } + */ + testb %al, %al + jnz .L_test_for_length_base /* if (op != 0) 45.7% */ + + shrl $16, %eax /* output this.val char */ + stosb + jmp .L_while_test + +.L_test_for_length_base: + /* regs: %esi = in, %ebp = hold, %bl = bits, %edi = out, %edx = len + * + * else if (op & 16) { + * len = this.val + * op &= 15 + * if (op) { + * if (op > bits) { + * hold |= *((unsigned short *)in)++ << bits; + * bits += 16 + * } + * len += hold & mask[op]; + * bits -= op; + * hold >>= op; + * } + */ +#define len_r %edx + movl %eax, len_r /* len = this */ + shrl $16, len_r /* len = this.val */ + movb %al, %cl + + testb $16, %al + jz .L_test_for_second_level_length /* if ((op & 16) == 0) 8% */ + andb $15, %cl /* op &= 15 */ + jz .L_save_len /* if (!op) */ + cmpb %cl, bits_r + jae .L_add_bits_to_len /* if (op <= bits) */ + + movb %cl, %ch /* stash op in ch, freeing cl */ + xorl %eax, %eax + lodsw /* al = *(ushort *)in++ */ + movb bits_r, %cl /* cl = bits, needs it for shifting */ + addb $16, bits_r /* bits += 16 */ + shll %cl, %eax + orl %eax, hold_r /* hold |= *((ushort *)in)++ << bits */ + movb %ch, %cl /* move op back to ecx */ + +.L_add_bits_to_len: + movl $1, %eax + shll %cl, %eax + decl %eax + subb %cl, bits_r + andl hold_r, %eax /* eax &= hold */ + shrl %cl, hold_r + addl %eax, len_r /* len += hold & mask[op] */ + +.L_save_len: + movl len_r, len(%esp) /* save len */ +#undef len_r + +.L_decode_distance: + /* regs: %esi = in, %ebp = hold, %bl = bits, %edi = out, %edx = dist + * + * if (bits < 15) { + * hold |= *((unsigned short *)in)++ << bits; + * bits += 16 + * } + * this = dcode[hold & dmask]; + * dodist: + * bits -= this.bits; + * hold >>= this.bits; + * op = this.op; + */ + + cmpb $15, bits_r + ja .L_get_distance_code /* if (15 < bits) */ + + xorl %eax, %eax + lodsw /* al = *(ushort *)in++ */ + movb bits_r, %cl /* cl = bits, needs it for shifting */ + addb $16, bits_r /* bits += 16 */ + shll %cl, %eax + orl %eax, hold_r /* hold |= *((ushort *)in)++ << bits */ + +.L_get_distance_code: + movl dmask(%esp), %edx /* edx = dmask */ + movl dcode(%esp), %ecx /* ecx = dcode */ + andl hold_r, %edx /* edx &= hold */ + movl (%ecx,%edx,4), %eax /* eax = dcode[hold & dmask] */ + +#define dist_r %edx +.L_dodist: + movl %eax, dist_r /* dist = this */ + shrl $16, dist_r /* dist = this.val */ + movb %ah, %cl + subb %ah, bits_r /* bits -= this.bits */ + shrl %cl, hold_r /* hold >>= this.bits */ + + /* if (op & 16) { + * dist = this.val + * op &= 15 + * if (op > bits) { + * hold |= *((unsigned short *)in)++ << bits; + * bits += 16 + * } + * dist += hold & mask[op]; + * bits -= op; + * hold >>= op; + */ + movb %al, %cl /* cl = this.op */ + + testb $16, %al /* if ((op & 16) == 0) */ + jz .L_test_for_second_level_dist + andb $15, %cl /* op &= 15 */ + jz .L_check_dist_one + cmpb %cl, bits_r + jae .L_add_bits_to_dist /* if (op <= bits) 97.6% */ + + movb %cl, %ch /* stash op in ch, freeing cl */ + xorl %eax, %eax + lodsw /* al = *(ushort *)in++ */ + movb bits_r, %cl /* cl = bits, needs it for shifting */ + addb $16, bits_r /* bits += 16 */ + shll %cl, %eax + orl %eax, hold_r /* hold |= *((ushort *)in)++ << bits */ + movb %ch, %cl /* move op back to ecx */ + +.L_add_bits_to_dist: + movl $1, %eax + shll %cl, %eax + decl %eax /* (1 << op) - 1 */ + subb %cl, bits_r + andl hold_r, %eax /* eax &= hold */ + shrl %cl, hold_r + addl %eax, dist_r /* dist += hold & ((1 << op) - 1) */ + jmp .L_check_window + +.L_check_window: + /* regs: %esi = from, %ebp = hold, %bl = bits, %edi = out, %edx = dist + * %ecx = nbytes + * + * nbytes = out - beg; + * if (dist <= nbytes) { + * from = out - dist; + * do { + * PUP(out) = PUP(from); + * } while (--len > 0) { + * } + */ + + movl in_r, in(%esp) /* save in so from can use it's reg */ + movl out_r, %eax + subl beg(%esp), %eax /* nbytes = out - beg */ + + cmpl dist_r, %eax + jb .L_clip_window /* if (dist > nbytes) 4.2% */ + + movl len(%esp), %ecx + movl out_r, from_r + subl dist_r, from_r /* from = out - dist */ + + subl $3, %ecx + movb (from_r), %al + movb %al, (out_r) + movb 1(from_r), %al + movb 2(from_r), %dl + addl $3, from_r + movb %al, 1(out_r) + movb %dl, 2(out_r) + addl $3, out_r + rep movsb + + movl in(%esp), in_r /* move in back to %esi, toss from */ + jmp .L_while_test + +.align 16,0x90 +.L_check_dist_one: + cmpl $1, dist_r + jne .L_check_window + cmpl out_r, beg(%esp) + je .L_check_window + + decl out_r + movl len(%esp), %ecx + movb (out_r), %al + subl $3, %ecx + + movb %al, 1(out_r) + movb %al, 2(out_r) + movb %al, 3(out_r) + addl $4, out_r + rep stosb + + jmp .L_while_test + +.align 16,0x90 +.L_test_for_second_level_length: + /* else if ((op & 64) == 0) { + * this = lcode[this.val + (hold & mask[op])]; + * } + */ + testb $64, %al + jnz .L_test_for_end_of_block /* if ((op & 64) != 0) */ + + movl $1, %eax + shll %cl, %eax + decl %eax + andl hold_r, %eax /* eax &= hold */ + addl %edx, %eax /* eax += this.val */ + movl lcode(%esp), %edx /* edx = lcode */ + movl (%edx,%eax,4), %eax /* eax = lcode[val + (hold&mask[op])] */ + jmp .L_dolen + +.align 16,0x90 +.L_test_for_second_level_dist: + /* else if ((op & 64) == 0) { + * this = dcode[this.val + (hold & mask[op])]; + * } + */ + testb $64, %al + jnz .L_invalid_distance_code /* if ((op & 64) != 0) */ + + movl $1, %eax + shll %cl, %eax + decl %eax + andl hold_r, %eax /* eax &= hold */ + addl %edx, %eax /* eax += this.val */ + movl dcode(%esp), %edx /* edx = dcode */ + movl (%edx,%eax,4), %eax /* eax = dcode[val + (hold&mask[op])] */ + jmp .L_dodist + +.align 16,0x90 +.L_clip_window: + /* regs: %esi = from, %ebp = hold, %bl = bits, %edi = out, %edx = dist + * %ecx = nbytes + * + * else { + * if (dist > wsize) { + * invalid distance + * } + * from = window; + * nbytes = dist - nbytes; + * if (write == 0) { + * from += wsize - nbytes; + */ +#define nbytes_r %ecx + movl %eax, nbytes_r + movl wsize(%esp), %eax /* prepare for dist compare */ + negl nbytes_r /* nbytes = -nbytes */ + movl window(%esp), from_r /* from = window */ + + cmpl dist_r, %eax + jb .L_invalid_distance_too_far /* if (dist > wsize) */ + + addl dist_r, nbytes_r /* nbytes = dist - nbytes */ + cmpl $0, write(%esp) + jne .L_wrap_around_window /* if (write != 0) */ + + subl nbytes_r, %eax + addl %eax, from_r /* from += wsize - nbytes */ + + /* regs: %esi = from, %ebp = hold, %bl = bits, %edi = out, %edx = dist + * %ecx = nbytes, %eax = len + * + * if (nbytes < len) { + * len -= nbytes; + * do { + * PUP(out) = PUP(from); + * } while (--nbytes); + * from = out - dist; + * } + * } + */ +#define len_r %eax + movl len(%esp), len_r + cmpl nbytes_r, len_r + jbe .L_do_copy1 /* if (nbytes >= len) */ + + subl nbytes_r, len_r /* len -= nbytes */ + rep movsb + movl out_r, from_r + subl dist_r, from_r /* from = out - dist */ + jmp .L_do_copy1 + + cmpl nbytes_r, len_r + jbe .L_do_copy1 /* if (nbytes >= len) */ + + subl nbytes_r, len_r /* len -= nbytes */ + rep movsb + movl out_r, from_r + subl dist_r, from_r /* from = out - dist */ + jmp .L_do_copy1 + +.L_wrap_around_window: + /* regs: %esi = from, %ebp = hold, %bl = bits, %edi = out, %edx = dist + * %ecx = nbytes, %eax = write, %eax = len + * + * else if (write < nbytes) { + * from += wsize + write - nbytes; + * nbytes -= write; + * if (nbytes < len) { + * len -= nbytes; + * do { + * PUP(out) = PUP(from); + * } while (--nbytes); + * from = window; + * nbytes = write; + * if (nbytes < len) { + * len -= nbytes; + * do { + * PUP(out) = PUP(from); + * } while(--nbytes); + * from = out - dist; + * } + * } + * } + */ +#define write_r %eax + movl write(%esp), write_r + cmpl write_r, nbytes_r + jbe .L_contiguous_in_window /* if (write >= nbytes) */ + + addl wsize(%esp), from_r + addl write_r, from_r + subl nbytes_r, from_r /* from += wsize + write - nbytes */ + subl write_r, nbytes_r /* nbytes -= write */ +#undef write_r + + movl len(%esp), len_r + cmpl nbytes_r, len_r + jbe .L_do_copy1 /* if (nbytes >= len) */ + + subl nbytes_r, len_r /* len -= nbytes */ + rep movsb + movl window(%esp), from_r /* from = window */ + movl write(%esp), nbytes_r /* nbytes = write */ + cmpl nbytes_r, len_r + jbe .L_do_copy1 /* if (nbytes >= len) */ + + subl nbytes_r, len_r /* len -= nbytes */ + rep movsb + movl out_r, from_r + subl dist_r, from_r /* from = out - dist */ + jmp .L_do_copy1 + +.L_contiguous_in_window: + /* regs: %esi = from, %ebp = hold, %bl = bits, %edi = out, %edx = dist + * %ecx = nbytes, %eax = write, %eax = len + * + * else { + * from += write - nbytes; + * if (nbytes < len) { + * len -= nbytes; + * do { + * PUP(out) = PUP(from); + * } while (--nbytes); + * from = out - dist; + * } + * } + */ +#define write_r %eax + addl write_r, from_r + subl nbytes_r, from_r /* from += write - nbytes */ +#undef write_r + + movl len(%esp), len_r + cmpl nbytes_r, len_r + jbe .L_do_copy1 /* if (nbytes >= len) */ + + subl nbytes_r, len_r /* len -= nbytes */ + rep movsb + movl out_r, from_r + subl dist_r, from_r /* from = out - dist */ + +.L_do_copy1: + /* regs: %esi = from, %esi = in, %ebp = hold, %bl = bits, %edi = out + * %eax = len + * + * while (len > 0) { + * PUP(out) = PUP(from); + * len--; + * } + * } + * } while (in < last && out < end); + */ +#undef nbytes_r +#define in_r %esi + movl len_r, %ecx + rep movsb + + movl in(%esp), in_r /* move in back to %esi, toss from */ + jmp .L_while_test + +#undef len_r +#undef dist_r + +#endif /* NO_MMX || RUN_TIME_MMX */ + + +/*** MMX code ***/ + +#if defined( USE_MMX ) || defined( RUN_TIME_MMX ) + +.align 32,0x90 +.L_init_mmx: + emms + +#undef bits_r +#undef bitslong_r +#define bitslong_r %ebp +#define hold_mm %mm0 + movd %ebp, hold_mm + movl %ebx, bitslong_r + +#define used_mm %mm1 +#define dmask2_mm %mm2 +#define lmask2_mm %mm3 +#define lmask_mm %mm4 +#define dmask_mm %mm5 +#define tmp_mm %mm6 + + movd lmask(%esp), lmask_mm + movq lmask_mm, lmask2_mm + movd dmask(%esp), dmask_mm + movq dmask_mm, dmask2_mm + pxor used_mm, used_mm + movl lcode(%esp), %ebx /* ebx = lcode */ + jmp .L_do_loop_mmx + +.align 32,0x90 +.L_while_test_mmx: + /* while (in < last && out < end) + */ + cmpl out_r, end(%esp) + jbe .L_break_loop /* if (out >= end) */ + + cmpl in_r, last(%esp) + jbe .L_break_loop + +.L_do_loop_mmx: + psrlq used_mm, hold_mm /* hold_mm >>= last bit length */ + + cmpl $32, bitslong_r + ja .L_get_length_code_mmx /* if (32 < bits) */ + + movd bitslong_r, tmp_mm + movd (in_r), %mm7 + addl $4, in_r + psllq tmp_mm, %mm7 + addl $32, bitslong_r + por %mm7, hold_mm /* hold_mm |= *((uint *)in)++ << bits */ + +.L_get_length_code_mmx: + pand hold_mm, lmask_mm + movd lmask_mm, %eax + movq lmask2_mm, lmask_mm + movl (%ebx,%eax,4), %eax /* eax = lcode[hold & lmask] */ + +.L_dolen_mmx: + movzbl %ah, %ecx /* ecx = this.bits */ + movd %ecx, used_mm + subl %ecx, bitslong_r /* bits -= this.bits */ + + testb %al, %al + jnz .L_test_for_length_base_mmx /* if (op != 0) 45.7% */ + + shrl $16, %eax /* output this.val char */ + stosb + jmp .L_while_test_mmx + +.L_test_for_length_base_mmx: +#define len_r %edx + movl %eax, len_r /* len = this */ + shrl $16, len_r /* len = this.val */ + + testb $16, %al + jz .L_test_for_second_level_length_mmx /* if ((op & 16) == 0) 8% */ + andl $15, %eax /* op &= 15 */ + jz .L_decode_distance_mmx /* if (!op) */ + + psrlq used_mm, hold_mm /* hold_mm >>= last bit length */ + movd %eax, used_mm + movd hold_mm, %ecx + subl %eax, bitslong_r + andl .L_mask(,%eax,4), %ecx + addl %ecx, len_r /* len += hold & mask[op] */ + +.L_decode_distance_mmx: + psrlq used_mm, hold_mm /* hold_mm >>= last bit length */ + + cmpl $32, bitslong_r + ja .L_get_dist_code_mmx /* if (32 < bits) */ + + movd bitslong_r, tmp_mm + movd (in_r), %mm7 + addl $4, in_r + psllq tmp_mm, %mm7 + addl $32, bitslong_r + por %mm7, hold_mm /* hold_mm |= *((uint *)in)++ << bits */ + +.L_get_dist_code_mmx: + movl dcode(%esp), %ebx /* ebx = dcode */ + pand hold_mm, dmask_mm + movd dmask_mm, %eax + movq dmask2_mm, dmask_mm + movl (%ebx,%eax,4), %eax /* eax = dcode[hold & lmask] */ + +.L_dodist_mmx: +#define dist_r %ebx + movzbl %ah, %ecx /* ecx = this.bits */ + movl %eax, dist_r + shrl $16, dist_r /* dist = this.val */ + subl %ecx, bitslong_r /* bits -= this.bits */ + movd %ecx, used_mm + + testb $16, %al /* if ((op & 16) == 0) */ + jz .L_test_for_second_level_dist_mmx + andl $15, %eax /* op &= 15 */ + jz .L_check_dist_one_mmx + +.L_add_bits_to_dist_mmx: + psrlq used_mm, hold_mm /* hold_mm >>= last bit length */ + movd %eax, used_mm /* save bit length of current op */ + movd hold_mm, %ecx /* get the next bits on input stream */ + subl %eax, bitslong_r /* bits -= op bits */ + andl .L_mask(,%eax,4), %ecx /* ecx = hold & mask[op] */ + addl %ecx, dist_r /* dist += hold & mask[op] */ + +.L_check_window_mmx: + movl in_r, in(%esp) /* save in so from can use it's reg */ + movl out_r, %eax + subl beg(%esp), %eax /* nbytes = out - beg */ + + cmpl dist_r, %eax + jb .L_clip_window_mmx /* if (dist > nbytes) 4.2% */ + + movl len_r, %ecx + movl out_r, from_r + subl dist_r, from_r /* from = out - dist */ + + subl $3, %ecx + movb (from_r), %al + movb %al, (out_r) + movb 1(from_r), %al + movb 2(from_r), %dl + addl $3, from_r + movb %al, 1(out_r) + movb %dl, 2(out_r) + addl $3, out_r + rep movsb + + movl in(%esp), in_r /* move in back to %esi, toss from */ + movl lcode(%esp), %ebx /* move lcode back to %ebx, toss dist */ + jmp .L_while_test_mmx + +.align 16,0x90 +.L_check_dist_one_mmx: + cmpl $1, dist_r + jne .L_check_window_mmx + cmpl out_r, beg(%esp) + je .L_check_window_mmx + + decl out_r + movl len_r, %ecx + movb (out_r), %al + subl $3, %ecx + + movb %al, 1(out_r) + movb %al, 2(out_r) + movb %al, 3(out_r) + addl $4, out_r + rep stosb + + movl lcode(%esp), %ebx /* move lcode back to %ebx, toss dist */ + jmp .L_while_test_mmx + +.align 16,0x90 +.L_test_for_second_level_length_mmx: + testb $64, %al + jnz .L_test_for_end_of_block /* if ((op & 64) != 0) */ + + andl $15, %eax + psrlq used_mm, hold_mm /* hold_mm >>= last bit length */ + movd hold_mm, %ecx + andl .L_mask(,%eax,4), %ecx + addl len_r, %ecx + movl (%ebx,%ecx,4), %eax /* eax = lcode[hold & lmask] */ + jmp .L_dolen_mmx + +.align 16,0x90 +.L_test_for_second_level_dist_mmx: + testb $64, %al + jnz .L_invalid_distance_code /* if ((op & 64) != 0) */ + + andl $15, %eax + psrlq used_mm, hold_mm /* hold_mm >>= last bit length */ + movd hold_mm, %ecx + andl .L_mask(,%eax,4), %ecx + movl dcode(%esp), %eax /* ecx = dcode */ + addl dist_r, %ecx + movl (%eax,%ecx,4), %eax /* eax = lcode[hold & lmask] */ + jmp .L_dodist_mmx + +.align 16,0x90 +.L_clip_window_mmx: +#define nbytes_r %ecx + movl %eax, nbytes_r + movl wsize(%esp), %eax /* prepare for dist compare */ + negl nbytes_r /* nbytes = -nbytes */ + movl window(%esp), from_r /* from = window */ + + cmpl dist_r, %eax + jb .L_invalid_distance_too_far /* if (dist > wsize) */ + + addl dist_r, nbytes_r /* nbytes = dist - nbytes */ + cmpl $0, write(%esp) + jne .L_wrap_around_window_mmx /* if (write != 0) */ + + subl nbytes_r, %eax + addl %eax, from_r /* from += wsize - nbytes */ + + cmpl nbytes_r, len_r + jbe .L_do_copy1_mmx /* if (nbytes >= len) */ + + subl nbytes_r, len_r /* len -= nbytes */ + rep movsb + movl out_r, from_r + subl dist_r, from_r /* from = out - dist */ + jmp .L_do_copy1_mmx + + cmpl nbytes_r, len_r + jbe .L_do_copy1_mmx /* if (nbytes >= len) */ + + subl nbytes_r, len_r /* len -= nbytes */ + rep movsb + movl out_r, from_r + subl dist_r, from_r /* from = out - dist */ + jmp .L_do_copy1_mmx + +.L_wrap_around_window_mmx: +#define write_r %eax + movl write(%esp), write_r + cmpl write_r, nbytes_r + jbe .L_contiguous_in_window_mmx /* if (write >= nbytes) */ + + addl wsize(%esp), from_r + addl write_r, from_r + subl nbytes_r, from_r /* from += wsize + write - nbytes */ + subl write_r, nbytes_r /* nbytes -= write */ +#undef write_r + + cmpl nbytes_r, len_r + jbe .L_do_copy1_mmx /* if (nbytes >= len) */ + + subl nbytes_r, len_r /* len -= nbytes */ + rep movsb + movl window(%esp), from_r /* from = window */ + movl write(%esp), nbytes_r /* nbytes = write */ + cmpl nbytes_r, len_r + jbe .L_do_copy1_mmx /* if (nbytes >= len) */ + + subl nbytes_r, len_r /* len -= nbytes */ + rep movsb + movl out_r, from_r + subl dist_r, from_r /* from = out - dist */ + jmp .L_do_copy1_mmx + +.L_contiguous_in_window_mmx: +#define write_r %eax + addl write_r, from_r + subl nbytes_r, from_r /* from += write - nbytes */ +#undef write_r + + cmpl nbytes_r, len_r + jbe .L_do_copy1_mmx /* if (nbytes >= len) */ + + subl nbytes_r, len_r /* len -= nbytes */ + rep movsb + movl out_r, from_r + subl dist_r, from_r /* from = out - dist */ + +.L_do_copy1_mmx: +#undef nbytes_r +#define in_r %esi + movl len_r, %ecx + rep movsb + + movl in(%esp), in_r /* move in back to %esi, toss from */ + movl lcode(%esp), %ebx /* move lcode back to %ebx, toss dist */ + jmp .L_while_test_mmx + +#undef hold_r +#undef bitslong_r + +#endif /* USE_MMX || RUN_TIME_MMX */ + + +/*** USE_MMX, NO_MMX, and RUNTIME_MMX from here on ***/ + +.L_invalid_distance_code: + /* else { + * strm->msg = "invalid distance code"; + * state->mode = BAD; + * } + */ + movl $.L_invalid_distance_code_msg, %ecx + movl $INFLATE_MODE_BAD, %edx + jmp .L_update_stream_state + +.L_test_for_end_of_block: + /* else if (op & 32) { + * state->mode = TYPE; + * break; + * } + */ + testb $32, %al + jz .L_invalid_literal_length_code /* if ((op & 32) == 0) */ + + movl $0, %ecx + movl $INFLATE_MODE_TYPE, %edx + jmp .L_update_stream_state + +.L_invalid_literal_length_code: + /* else { + * strm->msg = "invalid literal/length code"; + * state->mode = BAD; + * } + */ + movl $.L_invalid_literal_length_code_msg, %ecx + movl $INFLATE_MODE_BAD, %edx + jmp .L_update_stream_state + +.L_invalid_distance_too_far: + /* strm->msg = "invalid distance too far back"; + * state->mode = BAD; + */ + movl in(%esp), in_r /* from_r has in's reg, put in back */ + movl $.L_invalid_distance_too_far_msg, %ecx + movl $INFLATE_MODE_BAD, %edx + jmp .L_update_stream_state + +.L_update_stream_state: + /* set strm->msg = %ecx, strm->state->mode = %edx */ + movl strm_sp(%esp), %eax + testl %ecx, %ecx /* if (msg != NULL) */ + jz .L_skip_msg + movl %ecx, msg_strm(%eax) /* strm->msg = msg */ +.L_skip_msg: + movl state_strm(%eax), %eax /* state = strm->state */ + movl %edx, mode_state(%eax) /* state->mode = edx (BAD | TYPE) */ + jmp .L_break_loop + +.align 32,0x90 +.L_break_loop: + +/* + * Regs: + * + * bits = %ebp when mmx, and in %ebx when non-mmx + * hold = %hold_mm when mmx, and in %ebp when non-mmx + * in = %esi + * out = %edi + */ + +#if defined( USE_MMX ) || defined( RUN_TIME_MMX ) + +#if defined( RUN_TIME_MMX ) + + cmpl $DO_USE_MMX, inflate_fast_use_mmx + jne .L_update_next_in + +#endif /* RUN_TIME_MMX */ + + movl %ebp, %ebx + +.L_update_next_in: + +#endif + +#define strm_r %eax +#define state_r %edx + + /* len = bits >> 3; + * in -= len; + * bits -= len << 3; + * hold &= (1U << bits) - 1; + * state->hold = hold; + * state->bits = bits; + * strm->next_in = in; + * strm->next_out = out; + */ + movl strm_sp(%esp), strm_r + movl %ebx, %ecx + movl state_strm(strm_r), state_r + shrl $3, %ecx + subl %ecx, in_r + shll $3, %ecx + subl %ecx, %ebx + movl out_r, next_out_strm(strm_r) + movl %ebx, bits_state(state_r) + movl %ebx, %ecx + + leal buf(%esp), %ebx + cmpl %ebx, last(%esp) + jne .L_buf_not_used /* if buf != last */ + + subl %ebx, in_r /* in -= buf */ + movl next_in_strm(strm_r), %ebx + movl %ebx, last(%esp) /* last = strm->next_in */ + addl %ebx, in_r /* in += strm->next_in */ + movl avail_in_strm(strm_r), %ebx + subl $11, %ebx + addl %ebx, last(%esp) /* last = &strm->next_in[ avail_in - 11 ] */ + +.L_buf_not_used: + movl in_r, next_in_strm(strm_r) + + movl $1, %ebx + shll %cl, %ebx + decl %ebx + +#if defined( USE_MMX ) || defined( RUN_TIME_MMX ) + +#if defined( RUN_TIME_MMX ) + + cmpl $DO_USE_MMX, inflate_fast_use_mmx + jne .L_update_hold + +#endif /* RUN_TIME_MMX */ + + psrlq used_mm, hold_mm /* hold_mm >>= last bit length */ + movd hold_mm, %ebp + + emms + +.L_update_hold: + +#endif /* USE_MMX || RUN_TIME_MMX */ + + andl %ebx, %ebp + movl %ebp, hold_state(state_r) + +#define last_r %ebx + + /* strm->avail_in = in < last ? 11 + (last - in) : 11 - (in - last) */ + movl last(%esp), last_r + cmpl in_r, last_r + jbe .L_last_is_smaller /* if (in >= last) */ + + subl in_r, last_r /* last -= in */ + addl $11, last_r /* last += 11 */ + movl last_r, avail_in_strm(strm_r) + jmp .L_fixup_out +.L_last_is_smaller: + subl last_r, in_r /* in -= last */ + negl in_r /* in = -in */ + addl $11, in_r /* in += 11 */ + movl in_r, avail_in_strm(strm_r) + +#undef last_r +#define end_r %ebx + +.L_fixup_out: + /* strm->avail_out = out < end ? 257 + (end - out) : 257 - (out - end)*/ + movl end(%esp), end_r + cmpl out_r, end_r + jbe .L_end_is_smaller /* if (out >= end) */ + + subl out_r, end_r /* end -= out */ + addl $257, end_r /* end += 257 */ + movl end_r, avail_out_strm(strm_r) + jmp .L_done +.L_end_is_smaller: + subl end_r, out_r /* out -= end */ + negl out_r /* out = -out */ + addl $257, out_r /* out += 257 */ + movl out_r, avail_out_strm(strm_r) + +#undef end_r +#undef strm_r +#undef state_r + +.L_done: + addl $local_var_size, %esp + popf + popl %ebx + popl %ebp + popl %esi + popl %edi + ret + +#if defined( GAS_ELF ) +/* elf info */ +.type inflate_fast,@function +.size inflate_fast,.-inflate_fast +#endif ADDED compat/zlib/contrib/iostream/test.cpp Index: compat/zlib/contrib/iostream/test.cpp ================================================================== --- /dev/null +++ compat/zlib/contrib/iostream/test.cpp @@ -0,0 +1,24 @@ + +#include "zfstream.h" + +int main() { + + // Construct a stream object with this filebuffer. Anything sent + // to this stream will go to standard out. + gzofstream os( 1, ios::out ); + + // This text is getting compressed and sent to stdout. + // To prove this, run 'test | zcat'. + os << "Hello, Mommy" << endl; + + os << setcompressionlevel( Z_NO_COMPRESSION ); + os << "hello, hello, hi, ho!" << endl; + + setcompressionlevel( os, Z_DEFAULT_COMPRESSION ) + << "I'm compressing again" << endl; + + os.close(); + + return 0; + +} ADDED compat/zlib/contrib/iostream/zfstream.cpp Index: compat/zlib/contrib/iostream/zfstream.cpp ================================================================== --- /dev/null +++ compat/zlib/contrib/iostream/zfstream.cpp @@ -0,0 +1,329 @@ + +#include "zfstream.h" + +gzfilebuf::gzfilebuf() : + file(NULL), + mode(0), + own_file_descriptor(0) +{ } + +gzfilebuf::~gzfilebuf() { + + sync(); + if ( own_file_descriptor ) + close(); + +} + +gzfilebuf *gzfilebuf::open( const char *name, + int io_mode ) { + + if ( is_open() ) + return NULL; + + char char_mode[10]; + char *p = char_mode; + + if ( io_mode & ios::in ) { + mode = ios::in; + *p++ = 'r'; + } else if ( io_mode & ios::app ) { + mode = ios::app; + *p++ = 'a'; + } else { + mode = ios::out; + *p++ = 'w'; + } + + if ( io_mode & ios::binary ) { + mode |= ios::binary; + *p++ = 'b'; + } + + // Hard code the compression level + if ( io_mode & (ios::out|ios::app )) { + *p++ = '9'; + } + + // Put the end-of-string indicator + *p = '\0'; + + if ( (file = gzopen(name, char_mode)) == NULL ) + return NULL; + + own_file_descriptor = 1; + + return this; + +} + +gzfilebuf *gzfilebuf::attach( int file_descriptor, + int io_mode ) { + + if ( is_open() ) + return NULL; + + char char_mode[10]; + char *p = char_mode; + + if ( io_mode & ios::in ) { + mode = ios::in; + *p++ = 'r'; + } else if ( io_mode & ios::app ) { + mode = ios::app; + *p++ = 'a'; + } else { + mode = ios::out; + *p++ = 'w'; + } + + if ( io_mode & ios::binary ) { + mode |= ios::binary; + *p++ = 'b'; + } + + // Hard code the compression level + if ( io_mode & (ios::out|ios::app )) { + *p++ = '9'; + } + + // Put the end-of-string indicator + *p = '\0'; + + if ( (file = gzdopen(file_descriptor, char_mode)) == NULL ) + return NULL; + + own_file_descriptor = 0; + + return this; + +} + +gzfilebuf *gzfilebuf::close() { + + if ( is_open() ) { + + sync(); + gzclose( file ); + file = NULL; + + } + + return this; + +} + +int gzfilebuf::setcompressionlevel( int comp_level ) { + + return gzsetparams(file, comp_level, -2); + +} + +int gzfilebuf::setcompressionstrategy( int comp_strategy ) { + + return gzsetparams(file, -2, comp_strategy); + +} + + +streampos gzfilebuf::seekoff( streamoff off, ios::seek_dir dir, int which ) { + + return streampos(EOF); + +} + +int gzfilebuf::underflow() { + + // If the file hasn't been opened for reading, error. + if ( !is_open() || !(mode & ios::in) ) + return EOF; + + // if a buffer doesn't exists, allocate one. + if ( !base() ) { + + if ( (allocate()) == EOF ) + return EOF; + setp(0,0); + + } else { + + if ( in_avail() ) + return (unsigned char) *gptr(); + + if ( out_waiting() ) { + if ( flushbuf() == EOF ) + return EOF; + } + + } + + // Attempt to fill the buffer. + + int result = fillbuf(); + if ( result == EOF ) { + // disable get area + setg(0,0,0); + return EOF; + } + + return (unsigned char) *gptr(); + +} + +int gzfilebuf::overflow( int c ) { + + if ( !is_open() || !(mode & ios::out) ) + return EOF; + + if ( !base() ) { + if ( allocate() == EOF ) + return EOF; + setg(0,0,0); + } else { + if (in_avail()) { + return EOF; + } + if (out_waiting()) { + if (flushbuf() == EOF) + return EOF; + } + } + + int bl = blen(); + setp( base(), base() + bl); + + if ( c != EOF ) { + + *pptr() = c; + pbump(1); + + } + + return 0; + +} + +int gzfilebuf::sync() { + + if ( !is_open() ) + return EOF; + + if ( out_waiting() ) + return flushbuf(); + + return 0; + +} + +int gzfilebuf::flushbuf() { + + int n; + char *q; + + q = pbase(); + n = pptr() - q; + + if ( gzwrite( file, q, n) < n ) + return EOF; + + setp(0,0); + + return 0; + +} + +int gzfilebuf::fillbuf() { + + int required; + char *p; + + p = base(); + + required = blen(); + + int t = gzread( file, p, required ); + + if ( t <= 0) return EOF; + + setg( base(), base(), base()+t); + + return t; + +} + +gzfilestream_common::gzfilestream_common() : + ios( gzfilestream_common::rdbuf() ) +{ } + +gzfilestream_common::~gzfilestream_common() +{ } + +void gzfilestream_common::attach( int fd, int io_mode ) { + + if ( !buffer.attach( fd, io_mode) ) + clear( ios::failbit | ios::badbit ); + else + clear(); + +} + +void gzfilestream_common::open( const char *name, int io_mode ) { + + if ( !buffer.open( name, io_mode ) ) + clear( ios::failbit | ios::badbit ); + else + clear(); + +} + +void gzfilestream_common::close() { + + if ( !buffer.close() ) + clear( ios::failbit | ios::badbit ); + +} + +gzfilebuf *gzfilestream_common::rdbuf() +{ + return &buffer; +} + +gzifstream::gzifstream() : + ios( gzfilestream_common::rdbuf() ) +{ + clear( ios::badbit ); +} + +gzifstream::gzifstream( const char *name, int io_mode ) : + ios( gzfilestream_common::rdbuf() ) +{ + gzfilestream_common::open( name, io_mode ); +} + +gzifstream::gzifstream( int fd, int io_mode ) : + ios( gzfilestream_common::rdbuf() ) +{ + gzfilestream_common::attach( fd, io_mode ); +} + +gzifstream::~gzifstream() { } + +gzofstream::gzofstream() : + ios( gzfilestream_common::rdbuf() ) +{ + clear( ios::badbit ); +} + +gzofstream::gzofstream( const char *name, int io_mode ) : + ios( gzfilestream_common::rdbuf() ) +{ + gzfilestream_common::open( name, io_mode ); +} + +gzofstream::gzofstream( int fd, int io_mode ) : + ios( gzfilestream_common::rdbuf() ) +{ + gzfilestream_common::attach( fd, io_mode ); +} + +gzofstream::~gzofstream() { } ADDED compat/zlib/contrib/iostream/zfstream.h Index: compat/zlib/contrib/iostream/zfstream.h ================================================================== --- /dev/null +++ compat/zlib/contrib/iostream/zfstream.h @@ -0,0 +1,128 @@ + +#ifndef zfstream_h +#define zfstream_h + +#include +#include "zlib.h" + +class gzfilebuf : public streambuf { + +public: + + gzfilebuf( ); + virtual ~gzfilebuf(); + + gzfilebuf *open( const char *name, int io_mode ); + gzfilebuf *attach( int file_descriptor, int io_mode ); + gzfilebuf *close(); + + int setcompressionlevel( int comp_level ); + int setcompressionstrategy( int comp_strategy ); + + inline int is_open() const { return (file !=NULL); } + + virtual streampos seekoff( streamoff, ios::seek_dir, int ); + + virtual int sync(); + +protected: + + virtual int underflow(); + virtual int overflow( int = EOF ); + +private: + + gzFile file; + short mode; + short own_file_descriptor; + + int flushbuf(); + int fillbuf(); + +}; + +class gzfilestream_common : virtual public ios { + + friend class gzifstream; + friend class gzofstream; + friend gzofstream &setcompressionlevel( gzofstream &, int ); + friend gzofstream &setcompressionstrategy( gzofstream &, int ); + +public: + virtual ~gzfilestream_common(); + + void attach( int fd, int io_mode ); + void open( const char *name, int io_mode ); + void close(); + +protected: + gzfilestream_common(); + +private: + gzfilebuf *rdbuf(); + + gzfilebuf buffer; + +}; + +class gzifstream : public gzfilestream_common, public istream { + +public: + + gzifstream(); + gzifstream( const char *name, int io_mode = ios::in ); + gzifstream( int fd, int io_mode = ios::in ); + + virtual ~gzifstream(); + +}; + +class gzofstream : public gzfilestream_common, public ostream { + +public: + + gzofstream(); + gzofstream( const char *name, int io_mode = ios::out ); + gzofstream( int fd, int io_mode = ios::out ); + + virtual ~gzofstream(); + +}; + +template class gzomanip { + friend gzofstream &operator<<(gzofstream &, const gzomanip &); +public: + gzomanip(gzofstream &(*f)(gzofstream &, T), T v) : func(f), val(v) { } +private: + gzofstream &(*func)(gzofstream &, T); + T val; +}; + +template gzofstream &operator<<(gzofstream &s, const gzomanip &m) +{ + return (*m.func)(s, m.val); +} + +inline gzofstream &setcompressionlevel( gzofstream &s, int l ) +{ + (s.rdbuf())->setcompressionlevel(l); + return s; +} + +inline gzofstream &setcompressionstrategy( gzofstream &s, int l ) +{ + (s.rdbuf())->setcompressionstrategy(l); + return s; +} + +inline gzomanip setcompressionlevel(int l) +{ + return gzomanip(&setcompressionlevel,l); +} + +inline gzomanip setcompressionstrategy(int l) +{ + return gzomanip(&setcompressionstrategy,l); +} + +#endif ADDED compat/zlib/contrib/iostream2/zstream.h Index: compat/zlib/contrib/iostream2/zstream.h ================================================================== --- /dev/null +++ compat/zlib/contrib/iostream2/zstream.h @@ -0,0 +1,307 @@ +/* + * + * Copyright (c) 1997 + * Christian Michelsen Research AS + * Advanced Computing + * Fantoftvegen 38, 5036 BERGEN, Norway + * http://www.cmr.no + * + * Permission to use, copy, modify, distribute and sell this software + * and its documentation for any purpose is hereby granted without fee, + * provided that the above copyright notice appear in all copies and + * that both that copyright notice and this permission notice appear + * in supporting documentation. Christian Michelsen Research AS makes no + * representations about the suitability of this software for any + * purpose. It is provided "as is" without express or implied warranty. + * + */ + +#ifndef ZSTREAM__H +#define ZSTREAM__H + +/* + * zstream.h - C++ interface to the 'zlib' general purpose compression library + * $Id: zstream.h 1.1 1997-06-25 12:00:56+02 tyge Exp tyge $ + */ + +#include +#include +#include +#include "zlib.h" + +#if defined(_WIN32) +# include +# include +# define SET_BINARY_MODE(file) setmode(fileno(file), O_BINARY) +#else +# define SET_BINARY_MODE(file) +#endif + +class zstringlen { +public: + zstringlen(class izstream&); + zstringlen(class ozstream&, const char*); + size_t value() const { return val.word; } +private: + struct Val { unsigned char byte; size_t word; } val; +}; + +// ----------------------------- izstream ----------------------------- + +class izstream +{ + public: + izstream() : m_fp(0) {} + izstream(FILE* fp) : m_fp(0) { open(fp); } + izstream(const char* name) : m_fp(0) { open(name); } + ~izstream() { close(); } + + /* Opens a gzip (.gz) file for reading. + * open() can be used to read a file which is not in gzip format; + * in this case read() will directly read from the file without + * decompression. errno can be checked to distinguish two error + * cases (if errno is zero, the zlib error is Z_MEM_ERROR). + */ + void open(const char* name) { + if (m_fp) close(); + m_fp = ::gzopen(name, "rb"); + } + + void open(FILE* fp) { + SET_BINARY_MODE(fp); + if (m_fp) close(); + m_fp = ::gzdopen(fileno(fp), "rb"); + } + + /* Flushes all pending input if necessary, closes the compressed file + * and deallocates all the (de)compression state. The return value is + * the zlib error number (see function error() below). + */ + int close() { + int r = ::gzclose(m_fp); + m_fp = 0; return r; + } + + /* Binary read the given number of bytes from the compressed file. + */ + int read(void* buf, size_t len) { + return ::gzread(m_fp, buf, len); + } + + /* Returns the error message for the last error which occurred on the + * given compressed file. errnum is set to zlib error number. If an + * error occurred in the file system and not in the compression library, + * errnum is set to Z_ERRNO and the application may consult errno + * to get the exact error code. + */ + const char* error(int* errnum) { + return ::gzerror(m_fp, errnum); + } + + gzFile fp() { return m_fp; } + + private: + gzFile m_fp; +}; + +/* + * Binary read the given (array of) object(s) from the compressed file. + * If the input file was not in gzip format, read() copies the objects number + * of bytes into the buffer. + * returns the number of uncompressed bytes actually read + * (0 for end of file, -1 for error). + */ +template +inline int read(izstream& zs, T* x, Items items) { + return ::gzread(zs.fp(), x, items*sizeof(T)); +} + +/* + * Binary input with the '>' operator. + */ +template +inline izstream& operator>(izstream& zs, T& x) { + ::gzread(zs.fp(), &x, sizeof(T)); + return zs; +} + + +inline zstringlen::zstringlen(izstream& zs) { + zs > val.byte; + if (val.byte == 255) zs > val.word; + else val.word = val.byte; +} + +/* + * Read length of string + the string with the '>' operator. + */ +inline izstream& operator>(izstream& zs, char* x) { + zstringlen len(zs); + ::gzread(zs.fp(), x, len.value()); + x[len.value()] = '\0'; + return zs; +} + +inline char* read_string(izstream& zs) { + zstringlen len(zs); + char* x = new char[len.value()+1]; + ::gzread(zs.fp(), x, len.value()); + x[len.value()] = '\0'; + return x; +} + +// ----------------------------- ozstream ----------------------------- + +class ozstream +{ + public: + ozstream() : m_fp(0), m_os(0) { + } + ozstream(FILE* fp, int level = Z_DEFAULT_COMPRESSION) + : m_fp(0), m_os(0) { + open(fp, level); + } + ozstream(const char* name, int level = Z_DEFAULT_COMPRESSION) + : m_fp(0), m_os(0) { + open(name, level); + } + ~ozstream() { + close(); + } + + /* Opens a gzip (.gz) file for writing. + * The compression level parameter should be in 0..9 + * errno can be checked to distinguish two error cases + * (if errno is zero, the zlib error is Z_MEM_ERROR). + */ + void open(const char* name, int level = Z_DEFAULT_COMPRESSION) { + char mode[4] = "wb\0"; + if (level != Z_DEFAULT_COMPRESSION) mode[2] = '0'+level; + if (m_fp) close(); + m_fp = ::gzopen(name, mode); + } + + /* open from a FILE pointer. + */ + void open(FILE* fp, int level = Z_DEFAULT_COMPRESSION) { + SET_BINARY_MODE(fp); + char mode[4] = "wb\0"; + if (level != Z_DEFAULT_COMPRESSION) mode[2] = '0'+level; + if (m_fp) close(); + m_fp = ::gzdopen(fileno(fp), mode); + } + + /* Flushes all pending output if necessary, closes the compressed file + * and deallocates all the (de)compression state. The return value is + * the zlib error number (see function error() below). + */ + int close() { + if (m_os) { + ::gzwrite(m_fp, m_os->str(), m_os->pcount()); + delete[] m_os->str(); delete m_os; m_os = 0; + } + int r = ::gzclose(m_fp); m_fp = 0; return r; + } + + /* Binary write the given number of bytes into the compressed file. + */ + int write(const void* buf, size_t len) { + return ::gzwrite(m_fp, (voidp) buf, len); + } + + /* Flushes all pending output into the compressed file. The parameter + * _flush is as in the deflate() function. The return value is the zlib + * error number (see function gzerror below). flush() returns Z_OK if + * the flush_ parameter is Z_FINISH and all output could be flushed. + * flush() should be called only when strictly necessary because it can + * degrade compression. + */ + int flush(int _flush) { + os_flush(); + return ::gzflush(m_fp, _flush); + } + + /* Returns the error message for the last error which occurred on the + * given compressed file. errnum is set to zlib error number. If an + * error occurred in the file system and not in the compression library, + * errnum is set to Z_ERRNO and the application may consult errno + * to get the exact error code. + */ + const char* error(int* errnum) { + return ::gzerror(m_fp, errnum); + } + + gzFile fp() { return m_fp; } + + ostream& os() { + if (m_os == 0) m_os = new ostrstream; + return *m_os; + } + + void os_flush() { + if (m_os && m_os->pcount()>0) { + ostrstream* oss = new ostrstream; + oss->fill(m_os->fill()); + oss->flags(m_os->flags()); + oss->precision(m_os->precision()); + oss->width(m_os->width()); + ::gzwrite(m_fp, m_os->str(), m_os->pcount()); + delete[] m_os->str(); delete m_os; m_os = oss; + } + } + + private: + gzFile m_fp; + ostrstream* m_os; +}; + +/* + * Binary write the given (array of) object(s) into the compressed file. + * returns the number of uncompressed bytes actually written + * (0 in case of error). + */ +template +inline int write(ozstream& zs, const T* x, Items items) { + return ::gzwrite(zs.fp(), (voidp) x, items*sizeof(T)); +} + +/* + * Binary output with the '<' operator. + */ +template +inline ozstream& operator<(ozstream& zs, const T& x) { + ::gzwrite(zs.fp(), (voidp) &x, sizeof(T)); + return zs; +} + +inline zstringlen::zstringlen(ozstream& zs, const char* x) { + val.byte = 255; val.word = ::strlen(x); + if (val.word < 255) zs < (val.byte = val.word); + else zs < val; +} + +/* + * Write length of string + the string with the '<' operator. + */ +inline ozstream& operator<(ozstream& zs, const char* x) { + zstringlen len(zs, x); + ::gzwrite(zs.fp(), (voidp) x, len.value()); + return zs; +} + +#ifdef _MSC_VER +inline ozstream& operator<(ozstream& zs, char* const& x) { + return zs < (const char*) x; +} +#endif + +/* + * Ascii write with the << operator; + */ +template +inline ostream& operator<<(ozstream& zs, const T& x) { + zs.os_flush(); + return zs.os() << x; +} + +#endif ADDED compat/zlib/contrib/iostream2/zstream_test.cpp Index: compat/zlib/contrib/iostream2/zstream_test.cpp ================================================================== --- /dev/null +++ compat/zlib/contrib/iostream2/zstream_test.cpp @@ -0,0 +1,25 @@ +#include "zstream.h" +#include +#include +#include + +void main() { + char h[256] = "Hello"; + char* g = "Goodbye"; + ozstream out("temp.gz"); + out < "This works well" < h < g; + out.close(); + + izstream in("temp.gz"); // read it back + char *x = read_string(in), *y = new char[256], z[256]; + in > y > z; + in.close(); + cout << x << endl << y << endl << z << endl; + + out.open("temp.gz"); // try ascii output; zcat temp.gz to see the results + out << setw(50) << setfill('#') << setprecision(20) << x << endl << y << endl << z << endl; + out << z << endl << y << endl << x << endl; + out << 1.1234567890123456789 << endl; + + delete[] x; delete[] y; +} ADDED compat/zlib/contrib/iostream3/README Index: compat/zlib/contrib/iostream3/README ================================================================== --- /dev/null +++ compat/zlib/contrib/iostream3/README @@ -0,0 +1,35 @@ +These classes provide a C++ stream interface to the zlib library. It allows you +to do things like: + + gzofstream outf("blah.gz"); + outf << "These go into the gzip file " << 123 << endl; + +It does this by deriving a specialized stream buffer for gzipped files, which is +the way Stroustrup would have done it. :-> + +The gzifstream and gzofstream classes were originally written by Kevin Ruland +and made available in the zlib contrib/iostream directory. The older version still +compiles under gcc 2.xx, but not under gcc 3.xx, which sparked the development of +this version. + +The new classes are as standard-compliant as possible, closely following the +approach of the standard library's fstream classes. It compiles under gcc versions +3.2 and 3.3, but not under gcc 2.xx. This is mainly due to changes in the standard +library naming scheme. The new version of gzifstream/gzofstream/gzfilebuf differs +from the previous one in the following respects: +- added showmanyc +- added setbuf, with support for unbuffered output via setbuf(0,0) +- a few bug fixes of stream behavior +- gzipped output file opened with default compression level instead of maximum level +- setcompressionlevel()/strategy() members replaced by single setcompression() + +The code is provided "as is", with the permission to use, copy, modify, distribute +and sell it for any purpose without fee. + +Ludwig Schwardt + + +DSP Lab +Electrical & Electronic Engineering Department +University of Stellenbosch +South Africa ADDED compat/zlib/contrib/iostream3/TODO Index: compat/zlib/contrib/iostream3/TODO ================================================================== --- /dev/null +++ compat/zlib/contrib/iostream3/TODO @@ -0,0 +1,17 @@ +Possible upgrades to gzfilebuf: + +- The ability to do putback (e.g. putbackfail) + +- The ability to seek (zlib supports this, but could be slow/tricky) + +- Simultaneous read/write access (does it make sense?) + +- Support for ios_base::ate open mode + +- Locale support? + +- Check public interface to see which calls give problems + (due to dependence on library internals) + +- Override operator<<(ostream&, gzfilebuf*) to allow direct copying + of stream buffer to stream ( i.e. os << is.rdbuf(); ) ADDED compat/zlib/contrib/iostream3/test.cc Index: compat/zlib/contrib/iostream3/test.cc ================================================================== --- /dev/null +++ compat/zlib/contrib/iostream3/test.cc @@ -0,0 +1,50 @@ +/* + * Test program for gzifstream and gzofstream + * + * by Ludwig Schwardt + * original version by Kevin Ruland + */ + +#include "zfstream.h" +#include // for cout + +int main() { + + gzofstream outf; + gzifstream inf; + char buf[80]; + + outf.open("test1.txt.gz"); + outf << "The quick brown fox sidestepped the lazy canine\n" + << 1.3 << "\nPlan " << 9 << std::endl; + outf.close(); + std::cout << "Wrote the following message to 'test1.txt.gz' (check with zcat or zless):\n" + << "The quick brown fox sidestepped the lazy canine\n" + << 1.3 << "\nPlan " << 9 << std::endl; + + std::cout << "\nReading 'test1.txt.gz' (buffered) produces:\n"; + inf.open("test1.txt.gz"); + while (inf.getline(buf,80,'\n')) { + std::cout << buf << "\t(" << inf.rdbuf()->in_avail() << " chars left in buffer)\n"; + } + inf.close(); + + outf.rdbuf()->pubsetbuf(0,0); + outf.open("test2.txt.gz"); + outf << setcompression(Z_NO_COMPRESSION) + << "The quick brown fox sidestepped the lazy canine\n" + << 1.3 << "\nPlan " << 9 << std::endl; + outf.close(); + std::cout << "\nWrote the same message to 'test2.txt.gz' in uncompressed form"; + + std::cout << "\nReading 'test2.txt.gz' (unbuffered) produces:\n"; + inf.rdbuf()->pubsetbuf(0,0); + inf.open("test2.txt.gz"); + while (inf.getline(buf,80,'\n')) { + std::cout << buf << "\t(" << inf.rdbuf()->in_avail() << " chars left in buffer)\n"; + } + inf.close(); + + return 0; + +} ADDED compat/zlib/contrib/iostream3/zfstream.cc Index: compat/zlib/contrib/iostream3/zfstream.cc ================================================================== --- /dev/null +++ compat/zlib/contrib/iostream3/zfstream.cc @@ -0,0 +1,479 @@ +/* + * A C++ I/O streams interface to the zlib gz* functions + * + * by Ludwig Schwardt + * original version by Kevin Ruland + * + * This version is standard-compliant and compatible with gcc 3.x. + */ + +#include "zfstream.h" +#include // for strcpy, strcat, strlen (mode strings) +#include // for BUFSIZ + +// Internal buffer sizes (default and "unbuffered" versions) +#define BIGBUFSIZE BUFSIZ +#define SMALLBUFSIZE 1 + +/*****************************************************************************/ + +// Default constructor +gzfilebuf::gzfilebuf() +: file(NULL), io_mode(std::ios_base::openmode(0)), own_fd(false), + buffer(NULL), buffer_size(BIGBUFSIZE), own_buffer(true) +{ + // No buffers to start with + this->disable_buffer(); +} + +// Destructor +gzfilebuf::~gzfilebuf() +{ + // Sync output buffer and close only if responsible for file + // (i.e. attached streams should be left open at this stage) + this->sync(); + if (own_fd) + this->close(); + // Make sure internal buffer is deallocated + this->disable_buffer(); +} + +// Set compression level and strategy +int +gzfilebuf::setcompression(int comp_level, + int comp_strategy) +{ + return gzsetparams(file, comp_level, comp_strategy); +} + +// Open gzipped file +gzfilebuf* +gzfilebuf::open(const char *name, + std::ios_base::openmode mode) +{ + // Fail if file already open + if (this->is_open()) + return NULL; + // Don't support simultaneous read/write access (yet) + if ((mode & std::ios_base::in) && (mode & std::ios_base::out)) + return NULL; + + // Build mode string for gzopen and check it [27.8.1.3.2] + char char_mode[6] = "\0\0\0\0\0"; + if (!this->open_mode(mode, char_mode)) + return NULL; + + // Attempt to open file + if ((file = gzopen(name, char_mode)) == NULL) + return NULL; + + // On success, allocate internal buffer and set flags + this->enable_buffer(); + io_mode = mode; + own_fd = true; + return this; +} + +// Attach to gzipped file +gzfilebuf* +gzfilebuf::attach(int fd, + std::ios_base::openmode mode) +{ + // Fail if file already open + if (this->is_open()) + return NULL; + // Don't support simultaneous read/write access (yet) + if ((mode & std::ios_base::in) && (mode & std::ios_base::out)) + return NULL; + + // Build mode string for gzdopen and check it [27.8.1.3.2] + char char_mode[6] = "\0\0\0\0\0"; + if (!this->open_mode(mode, char_mode)) + return NULL; + + // Attempt to attach to file + if ((file = gzdopen(fd, char_mode)) == NULL) + return NULL; + + // On success, allocate internal buffer and set flags + this->enable_buffer(); + io_mode = mode; + own_fd = false; + return this; +} + +// Close gzipped file +gzfilebuf* +gzfilebuf::close() +{ + // Fail immediately if no file is open + if (!this->is_open()) + return NULL; + // Assume success + gzfilebuf* retval = this; + // Attempt to sync and close gzipped file + if (this->sync() == -1) + retval = NULL; + if (gzclose(file) < 0) + retval = NULL; + // File is now gone anyway (postcondition [27.8.1.3.8]) + file = NULL; + own_fd = false; + // Destroy internal buffer if it exists + this->disable_buffer(); + return retval; +} + +/* * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * */ + +// Convert int open mode to mode string +bool +gzfilebuf::open_mode(std::ios_base::openmode mode, + char* c_mode) const +{ + bool testb = mode & std::ios_base::binary; + bool testi = mode & std::ios_base::in; + bool testo = mode & std::ios_base::out; + bool testt = mode & std::ios_base::trunc; + bool testa = mode & std::ios_base::app; + + // Check for valid flag combinations - see [27.8.1.3.2] (Table 92) + // Original zfstream hardcoded the compression level to maximum here... + // Double the time for less than 1% size improvement seems + // excessive though - keeping it at the default level + // To change back, just append "9" to the next three mode strings + if (!testi && testo && !testt && !testa) + strcpy(c_mode, "w"); + if (!testi && testo && !testt && testa) + strcpy(c_mode, "a"); + if (!testi && testo && testt && !testa) + strcpy(c_mode, "w"); + if (testi && !testo && !testt && !testa) + strcpy(c_mode, "r"); + // No read/write mode yet +// if (testi && testo && !testt && !testa) +// strcpy(c_mode, "r+"); +// if (testi && testo && testt && !testa) +// strcpy(c_mode, "w+"); + + // Mode string should be empty for invalid combination of flags + if (strlen(c_mode) == 0) + return false; + if (testb) + strcat(c_mode, "b"); + return true; +} + +// Determine number of characters in internal get buffer +std::streamsize +gzfilebuf::showmanyc() +{ + // Calls to underflow will fail if file not opened for reading + if (!this->is_open() || !(io_mode & std::ios_base::in)) + return -1; + // Make sure get area is in use + if (this->gptr() && (this->gptr() < this->egptr())) + return std::streamsize(this->egptr() - this->gptr()); + else + return 0; +} + +// Fill get area from gzipped file +gzfilebuf::int_type +gzfilebuf::underflow() +{ + // If something is left in the get area by chance, return it + // (this shouldn't normally happen, as underflow is only supposed + // to be called when gptr >= egptr, but it serves as error check) + if (this->gptr() && (this->gptr() < this->egptr())) + return traits_type::to_int_type(*(this->gptr())); + + // If the file hasn't been opened for reading, produce error + if (!this->is_open() || !(io_mode & std::ios_base::in)) + return traits_type::eof(); + + // Attempt to fill internal buffer from gzipped file + // (buffer must be guaranteed to exist...) + int bytes_read = gzread(file, buffer, buffer_size); + // Indicates error or EOF + if (bytes_read <= 0) + { + // Reset get area + this->setg(buffer, buffer, buffer); + return traits_type::eof(); + } + // Make all bytes read from file available as get area + this->setg(buffer, buffer, buffer + bytes_read); + + // Return next character in get area + return traits_type::to_int_type(*(this->gptr())); +} + +// Write put area to gzipped file +gzfilebuf::int_type +gzfilebuf::overflow(int_type c) +{ + // Determine whether put area is in use + if (this->pbase()) + { + // Double-check pointer range + if (this->pptr() > this->epptr() || this->pptr() < this->pbase()) + return traits_type::eof(); + // Add extra character to buffer if not EOF + if (!traits_type::eq_int_type(c, traits_type::eof())) + { + *(this->pptr()) = traits_type::to_char_type(c); + this->pbump(1); + } + // Number of characters to write to file + int bytes_to_write = this->pptr() - this->pbase(); + // Overflow doesn't fail if nothing is to be written + if (bytes_to_write > 0) + { + // If the file hasn't been opened for writing, produce error + if (!this->is_open() || !(io_mode & std::ios_base::out)) + return traits_type::eof(); + // If gzipped file won't accept all bytes written to it, fail + if (gzwrite(file, this->pbase(), bytes_to_write) != bytes_to_write) + return traits_type::eof(); + // Reset next pointer to point to pbase on success + this->pbump(-bytes_to_write); + } + } + // Write extra character to file if not EOF + else if (!traits_type::eq_int_type(c, traits_type::eof())) + { + // If the file hasn't been opened for writing, produce error + if (!this->is_open() || !(io_mode & std::ios_base::out)) + return traits_type::eof(); + // Impromptu char buffer (allows "unbuffered" output) + char_type last_char = traits_type::to_char_type(c); + // If gzipped file won't accept this character, fail + if (gzwrite(file, &last_char, 1) != 1) + return traits_type::eof(); + } + + // If you got here, you have succeeded (even if c was EOF) + // The return value should therefore be non-EOF + if (traits_type::eq_int_type(c, traits_type::eof())) + return traits_type::not_eof(c); + else + return c; +} + +// Assign new buffer +std::streambuf* +gzfilebuf::setbuf(char_type* p, + std::streamsize n) +{ + // First make sure stuff is sync'ed, for safety + if (this->sync() == -1) + return NULL; + // If buffering is turned off on purpose via setbuf(0,0), still allocate one... + // "Unbuffered" only really refers to put [27.8.1.4.10], while get needs at + // least a buffer of size 1 (very inefficient though, therefore make it bigger?) + // This follows from [27.5.2.4.3]/12 (gptr needs to point at something, it seems) + if (!p || !n) + { + // Replace existing buffer (if any) with small internal buffer + this->disable_buffer(); + buffer = NULL; + buffer_size = 0; + own_buffer = true; + this->enable_buffer(); + } + else + { + // Replace existing buffer (if any) with external buffer + this->disable_buffer(); + buffer = p; + buffer_size = n; + own_buffer = false; + this->enable_buffer(); + } + return this; +} + +// Write put area to gzipped file (i.e. ensures that put area is empty) +int +gzfilebuf::sync() +{ + return traits_type::eq_int_type(this->overflow(), traits_type::eof()) ? -1 : 0; +} + +/* * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * */ + +// Allocate internal buffer +void +gzfilebuf::enable_buffer() +{ + // If internal buffer required, allocate one + if (own_buffer && !buffer) + { + // Check for buffered vs. "unbuffered" + if (buffer_size > 0) + { + // Allocate internal buffer + buffer = new char_type[buffer_size]; + // Get area starts empty and will be expanded by underflow as need arises + this->setg(buffer, buffer, buffer); + // Setup entire internal buffer as put area. + // The one-past-end pointer actually points to the last element of the buffer, + // so that overflow(c) can safely add the extra character c to the sequence. + // These pointers remain in place for the duration of the buffer + this->setp(buffer, buffer + buffer_size - 1); + } + else + { + // Even in "unbuffered" case, (small?) get buffer is still required + buffer_size = SMALLBUFSIZE; + buffer = new char_type[buffer_size]; + this->setg(buffer, buffer, buffer); + // "Unbuffered" means no put buffer + this->setp(0, 0); + } + } + else + { + // If buffer already allocated, reset buffer pointers just to make sure no + // stale chars are lying around + this->setg(buffer, buffer, buffer); + this->setp(buffer, buffer + buffer_size - 1); + } +} + +// Destroy internal buffer +void +gzfilebuf::disable_buffer() +{ + // If internal buffer exists, deallocate it + if (own_buffer && buffer) + { + // Preserve unbuffered status by zeroing size + if (!this->pbase()) + buffer_size = 0; + delete[] buffer; + buffer = NULL; + this->setg(0, 0, 0); + this->setp(0, 0); + } + else + { + // Reset buffer pointers to initial state if external buffer exists + this->setg(buffer, buffer, buffer); + if (buffer) + this->setp(buffer, buffer + buffer_size - 1); + else + this->setp(0, 0); + } +} + +/*****************************************************************************/ + +// Default constructor initializes stream buffer +gzifstream::gzifstream() +: std::istream(NULL), sb() +{ this->init(&sb); } + +// Initialize stream buffer and open file +gzifstream::gzifstream(const char* name, + std::ios_base::openmode mode) +: std::istream(NULL), sb() +{ + this->init(&sb); + this->open(name, mode); +} + +// Initialize stream buffer and attach to file +gzifstream::gzifstream(int fd, + std::ios_base::openmode mode) +: std::istream(NULL), sb() +{ + this->init(&sb); + this->attach(fd, mode); +} + +// Open file and go into fail() state if unsuccessful +void +gzifstream::open(const char* name, + std::ios_base::openmode mode) +{ + if (!sb.open(name, mode | std::ios_base::in)) + this->setstate(std::ios_base::failbit); + else + this->clear(); +} + +// Attach to file and go into fail() state if unsuccessful +void +gzifstream::attach(int fd, + std::ios_base::openmode mode) +{ + if (!sb.attach(fd, mode | std::ios_base::in)) + this->setstate(std::ios_base::failbit); + else + this->clear(); +} + +// Close file +void +gzifstream::close() +{ + if (!sb.close()) + this->setstate(std::ios_base::failbit); +} + +/*****************************************************************************/ + +// Default constructor initializes stream buffer +gzofstream::gzofstream() +: std::ostream(NULL), sb() +{ this->init(&sb); } + +// Initialize stream buffer and open file +gzofstream::gzofstream(const char* name, + std::ios_base::openmode mode) +: std::ostream(NULL), sb() +{ + this->init(&sb); + this->open(name, mode); +} + +// Initialize stream buffer and attach to file +gzofstream::gzofstream(int fd, + std::ios_base::openmode mode) +: std::ostream(NULL), sb() +{ + this->init(&sb); + this->attach(fd, mode); +} + +// Open file and go into fail() state if unsuccessful +void +gzofstream::open(const char* name, + std::ios_base::openmode mode) +{ + if (!sb.open(name, mode | std::ios_base::out)) + this->setstate(std::ios_base::failbit); + else + this->clear(); +} + +// Attach to file and go into fail() state if unsuccessful +void +gzofstream::attach(int fd, + std::ios_base::openmode mode) +{ + if (!sb.attach(fd, mode | std::ios_base::out)) + this->setstate(std::ios_base::failbit); + else + this->clear(); +} + +// Close file +void +gzofstream::close() +{ + if (!sb.close()) + this->setstate(std::ios_base::failbit); +} ADDED compat/zlib/contrib/iostream3/zfstream.h Index: compat/zlib/contrib/iostream3/zfstream.h ================================================================== --- /dev/null +++ compat/zlib/contrib/iostream3/zfstream.h @@ -0,0 +1,466 @@ +/* + * A C++ I/O streams interface to the zlib gz* functions + * + * by Ludwig Schwardt + * original version by Kevin Ruland + * + * This version is standard-compliant and compatible with gcc 3.x. + */ + +#ifndef ZFSTREAM_H +#define ZFSTREAM_H + +#include // not iostream, since we don't need cin/cout +#include +#include "zlib.h" + +/*****************************************************************************/ + +/** + * @brief Gzipped file stream buffer class. + * + * This class implements basic_filebuf for gzipped files. It doesn't yet support + * seeking (allowed by zlib but slow/limited), putback and read/write access + * (tricky). Otherwise, it attempts to be a drop-in replacement for the standard + * file streambuf. +*/ +class gzfilebuf : public std::streambuf +{ +public: + // Default constructor. + gzfilebuf(); + + // Destructor. + virtual + ~gzfilebuf(); + + /** + * @brief Set compression level and strategy on the fly. + * @param comp_level Compression level (see zlib.h for allowed values) + * @param comp_strategy Compression strategy (see zlib.h for allowed values) + * @return Z_OK on success, Z_STREAM_ERROR otherwise. + * + * Unfortunately, these parameters cannot be modified separately, as the + * previous zfstream version assumed. Since the strategy is seldom changed, + * it can default and setcompression(level) then becomes like the old + * setcompressionlevel(level). + */ + int + setcompression(int comp_level, + int comp_strategy = Z_DEFAULT_STRATEGY); + + /** + * @brief Check if file is open. + * @return True if file is open. + */ + bool + is_open() const { return (file != NULL); } + + /** + * @brief Open gzipped file. + * @param name File name. + * @param mode Open mode flags. + * @return @c this on success, NULL on failure. + */ + gzfilebuf* + open(const char* name, + std::ios_base::openmode mode); + + /** + * @brief Attach to already open gzipped file. + * @param fd File descriptor. + * @param mode Open mode flags. + * @return @c this on success, NULL on failure. + */ + gzfilebuf* + attach(int fd, + std::ios_base::openmode mode); + + /** + * @brief Close gzipped file. + * @return @c this on success, NULL on failure. + */ + gzfilebuf* + close(); + +protected: + /** + * @brief Convert ios open mode int to mode string used by zlib. + * @return True if valid mode flag combination. + */ + bool + open_mode(std::ios_base::openmode mode, + char* c_mode) const; + + /** + * @brief Number of characters available in stream buffer. + * @return Number of characters. + * + * This indicates number of characters in get area of stream buffer. + * These characters can be read without accessing the gzipped file. + */ + virtual std::streamsize + showmanyc(); + + /** + * @brief Fill get area from gzipped file. + * @return First character in get area on success, EOF on error. + * + * This actually reads characters from gzipped file to stream + * buffer. Always buffered. + */ + virtual int_type + underflow(); + + /** + * @brief Write put area to gzipped file. + * @param c Extra character to add to buffer contents. + * @return Non-EOF on success, EOF on error. + * + * This actually writes characters in stream buffer to + * gzipped file. With unbuffered output this is done one + * character at a time. + */ + virtual int_type + overflow(int_type c = traits_type::eof()); + + /** + * @brief Installs external stream buffer. + * @param p Pointer to char buffer. + * @param n Size of external buffer. + * @return @c this on success, NULL on failure. + * + * Call setbuf(0,0) to enable unbuffered output. + */ + virtual std::streambuf* + setbuf(char_type* p, + std::streamsize n); + + /** + * @brief Flush stream buffer to file. + * @return 0 on success, -1 on error. + * + * This calls underflow(EOF) to do the job. + */ + virtual int + sync(); + +// +// Some future enhancements +// +// virtual int_type uflow(); +// virtual int_type pbackfail(int_type c = traits_type::eof()); +// virtual pos_type +// seekoff(off_type off, +// std::ios_base::seekdir way, +// std::ios_base::openmode mode = std::ios_base::in|std::ios_base::out); +// virtual pos_type +// seekpos(pos_type sp, +// std::ios_base::openmode mode = std::ios_base::in|std::ios_base::out); + +private: + /** + * @brief Allocate internal buffer. + * + * This function is safe to call multiple times. It will ensure + * that a proper internal buffer exists if it is required. If the + * buffer already exists or is external, the buffer pointers will be + * reset to their original state. + */ + void + enable_buffer(); + + /** + * @brief Destroy internal buffer. + * + * This function is safe to call multiple times. It will ensure + * that the internal buffer is deallocated if it exists. In any + * case, it will also reset the buffer pointers. + */ + void + disable_buffer(); + + /** + * Underlying file pointer. + */ + gzFile file; + + /** + * Mode in which file was opened. + */ + std::ios_base::openmode io_mode; + + /** + * @brief True if this object owns file descriptor. + * + * This makes the class responsible for closing the file + * upon destruction. + */ + bool own_fd; + + /** + * @brief Stream buffer. + * + * For simplicity this remains allocated on the free store for the + * entire life span of the gzfilebuf object, unless replaced by setbuf. + */ + char_type* buffer; + + /** + * @brief Stream buffer size. + * + * Defaults to system default buffer size (typically 8192 bytes). + * Modified by setbuf. + */ + std::streamsize buffer_size; + + /** + * @brief True if this object owns stream buffer. + * + * This makes the class responsible for deleting the buffer + * upon destruction. + */ + bool own_buffer; +}; + +/*****************************************************************************/ + +/** + * @brief Gzipped file input stream class. + * + * This class implements ifstream for gzipped files. Seeking and putback + * is not supported yet. +*/ +class gzifstream : public std::istream +{ +public: + // Default constructor + gzifstream(); + + /** + * @brief Construct stream on gzipped file to be opened. + * @param name File name. + * @param mode Open mode flags (forced to contain ios::in). + */ + explicit + gzifstream(const char* name, + std::ios_base::openmode mode = std::ios_base::in); + + /** + * @brief Construct stream on already open gzipped file. + * @param fd File descriptor. + * @param mode Open mode flags (forced to contain ios::in). + */ + explicit + gzifstream(int fd, + std::ios_base::openmode mode = std::ios_base::in); + + /** + * Obtain underlying stream buffer. + */ + gzfilebuf* + rdbuf() const + { return const_cast(&sb); } + + /** + * @brief Check if file is open. + * @return True if file is open. + */ + bool + is_open() { return sb.is_open(); } + + /** + * @brief Open gzipped file. + * @param name File name. + * @param mode Open mode flags (forced to contain ios::in). + * + * Stream will be in state good() if file opens successfully; + * otherwise in state fail(). This differs from the behavior of + * ifstream, which never sets the state to good() and therefore + * won't allow you to reuse the stream for a second file unless + * you manually clear() the state. The choice is a matter of + * convenience. + */ + void + open(const char* name, + std::ios_base::openmode mode = std::ios_base::in); + + /** + * @brief Attach to already open gzipped file. + * @param fd File descriptor. + * @param mode Open mode flags (forced to contain ios::in). + * + * Stream will be in state good() if attach succeeded; otherwise + * in state fail(). + */ + void + attach(int fd, + std::ios_base::openmode mode = std::ios_base::in); + + /** + * @brief Close gzipped file. + * + * Stream will be in state fail() if close failed. + */ + void + close(); + +private: + /** + * Underlying stream buffer. + */ + gzfilebuf sb; +}; + +/*****************************************************************************/ + +/** + * @brief Gzipped file output stream class. + * + * This class implements ofstream for gzipped files. Seeking and putback + * is not supported yet. +*/ +class gzofstream : public std::ostream +{ +public: + // Default constructor + gzofstream(); + + /** + * @brief Construct stream on gzipped file to be opened. + * @param name File name. + * @param mode Open mode flags (forced to contain ios::out). + */ + explicit + gzofstream(const char* name, + std::ios_base::openmode mode = std::ios_base::out); + + /** + * @brief Construct stream on already open gzipped file. + * @param fd File descriptor. + * @param mode Open mode flags (forced to contain ios::out). + */ + explicit + gzofstream(int fd, + std::ios_base::openmode mode = std::ios_base::out); + + /** + * Obtain underlying stream buffer. + */ + gzfilebuf* + rdbuf() const + { return const_cast(&sb); } + + /** + * @brief Check if file is open. + * @return True if file is open. + */ + bool + is_open() { return sb.is_open(); } + + /** + * @brief Open gzipped file. + * @param name File name. + * @param mode Open mode flags (forced to contain ios::out). + * + * Stream will be in state good() if file opens successfully; + * otherwise in state fail(). This differs from the behavior of + * ofstream, which never sets the state to good() and therefore + * won't allow you to reuse the stream for a second file unless + * you manually clear() the state. The choice is a matter of + * convenience. + */ + void + open(const char* name, + std::ios_base::openmode mode = std::ios_base::out); + + /** + * @brief Attach to already open gzipped file. + * @param fd File descriptor. + * @param mode Open mode flags (forced to contain ios::out). + * + * Stream will be in state good() if attach succeeded; otherwise + * in state fail(). + */ + void + attach(int fd, + std::ios_base::openmode mode = std::ios_base::out); + + /** + * @brief Close gzipped file. + * + * Stream will be in state fail() if close failed. + */ + void + close(); + +private: + /** + * Underlying stream buffer. + */ + gzfilebuf sb; +}; + +/*****************************************************************************/ + +/** + * @brief Gzipped file output stream manipulator class. + * + * This class defines a two-argument manipulator for gzofstream. It is used + * as base for the setcompression(int,int) manipulator. +*/ +template + class gzomanip2 + { + public: + // Allows insertor to peek at internals + template + friend gzofstream& + operator<<(gzofstream&, + const gzomanip2&); + + // Constructor + gzomanip2(gzofstream& (*f)(gzofstream&, T1, T2), + T1 v1, + T2 v2); + private: + // Underlying manipulator function + gzofstream& + (*func)(gzofstream&, T1, T2); + + // Arguments for manipulator function + T1 val1; + T2 val2; + }; + +/*****************************************************************************/ + +// Manipulator function thunks through to stream buffer +inline gzofstream& +setcompression(gzofstream &gzs, int l, int s = Z_DEFAULT_STRATEGY) +{ + (gzs.rdbuf())->setcompression(l, s); + return gzs; +} + +// Manipulator constructor stores arguments +template + inline + gzomanip2::gzomanip2(gzofstream &(*f)(gzofstream &, T1, T2), + T1 v1, + T2 v2) + : func(f), val1(v1), val2(v2) + { } + +// Insertor applies underlying manipulator function to stream +template + inline gzofstream& + operator<<(gzofstream& s, const gzomanip2& m) + { return (*m.func)(s, m.val1, m.val2); } + +// Insert this onto stream to simplify setting of compression level +inline gzomanip2 +setcompression(int l, int s = Z_DEFAULT_STRATEGY) +{ return gzomanip2(&setcompression, l, s); } + +#endif // ZFSTREAM_H ADDED compat/zlib/contrib/masmx64/bld_ml64.bat Index: compat/zlib/contrib/masmx64/bld_ml64.bat ================================================================== --- /dev/null +++ compat/zlib/contrib/masmx64/bld_ml64.bat @@ -0,0 +1,2 @@ +ml64.exe /Flinffasx64 /c /Zi inffasx64.asm +ml64.exe /Flgvmat64 /c /Zi gvmat64.asm ADDED compat/zlib/contrib/masmx64/gvmat64.asm Index: compat/zlib/contrib/masmx64/gvmat64.asm ================================================================== --- /dev/null +++ compat/zlib/contrib/masmx64/gvmat64.asm @@ -0,0 +1,553 @@ +;uInt longest_match_x64( +; deflate_state *s, +; IPos cur_match); /* current match */ + +; gvmat64.asm -- Asm portion of the optimized longest_match for 32 bits x86_64 +; (AMD64 on Athlon 64, Opteron, Phenom +; and Intel EM64T on Pentium 4 with EM64T, Pentium D, Core 2 Duo, Core I5/I7) +; Copyright (C) 1995-2010 Jean-loup Gailly, Brian Raiter and Gilles Vollant. +; +; File written by Gilles Vollant, by converting to assembly the longest_match +; from Jean-loup Gailly in deflate.c of zLib and infoZip zip. +; +; and by taking inspiration on asm686 with masm, optimised assembly code +; from Brian Raiter, written 1998 +; +; This software is provided 'as-is', without any express or implied +; warranty. In no event will the authors be held liable for any damages +; arising from the use of this software. +; +; Permission is granted to anyone to use this software for any purpose, +; including commercial applications, and to alter it and redistribute it +; freely, subject to the following restrictions: +; +; 1. The origin of this software must not be misrepresented; you must not +; claim that you wrote the original software. If you use this software +; in a product, an acknowledgment in the product documentation would be +; appreciated but is not required. +; 2. Altered source versions must be plainly marked as such, and must not be +; misrepresented as being the original software +; 3. This notice may not be removed or altered from any source distribution. +; +; +; +; http://www.zlib.net +; http://www.winimage.com/zLibDll +; http://www.muppetlabs.com/~breadbox/software/assembly.html +; +; to compile this file for infozip Zip, I use option: +; ml64.exe /Flgvmat64 /c /Zi /DINFOZIP gvmat64.asm +; +; to compile this file for zLib, I use option: +; ml64.exe /Flgvmat64 /c /Zi gvmat64.asm +; Be carrefull to adapt zlib1222add below to your version of zLib +; (if you use a version of zLib before 1.0.4 or after 1.2.2.2, change +; value of zlib1222add later) +; +; This file compile with Microsoft Macro Assembler (x64) for AMD64 +; +; ml64.exe is given with Visual Studio 2005/2008/2010 and Windows WDK +; +; (you can get Windows WDK with ml64 for AMD64 from +; http://www.microsoft.com/whdc/Devtools/wdk/default.mspx for low price) +; + + +;uInt longest_match(s, cur_match) +; deflate_state *s; +; IPos cur_match; /* current match */ +.code +longest_match PROC + + +;LocalVarsSize equ 88 + LocalVarsSize equ 72 + +; register used : rax,rbx,rcx,rdx,rsi,rdi,r8,r9,r10,r11,r12 +; free register : r14,r15 +; register can be saved : rsp + + chainlenwmask equ rsp + 8 - LocalVarsSize ; high word: current chain len + ; low word: s->wmask +;window equ rsp + xx - LocalVarsSize ; local copy of s->window ; stored in r10 +;windowbestlen equ rsp + xx - LocalVarsSize ; s->window + bestlen , use r10+r11 +;scanstart equ rsp + xx - LocalVarsSize ; first two bytes of string ; stored in r12w +;scanend equ rsp + xx - LocalVarsSize ; last two bytes of string use ebx +;scanalign equ rsp + xx - LocalVarsSize ; dword-misalignment of string r13 +;bestlen equ rsp + xx - LocalVarsSize ; size of best match so far -> r11d +;scan equ rsp + xx - LocalVarsSize ; ptr to string wanting match -> r9 +IFDEF INFOZIP +ELSE + nicematch equ (rsp + 16 - LocalVarsSize) ; a good enough match size +ENDIF + +save_rdi equ rsp + 24 - LocalVarsSize +save_rsi equ rsp + 32 - LocalVarsSize +save_rbx equ rsp + 40 - LocalVarsSize +save_rbp equ rsp + 48 - LocalVarsSize +save_r12 equ rsp + 56 - LocalVarsSize +save_r13 equ rsp + 64 - LocalVarsSize +;save_r14 equ rsp + 72 - LocalVarsSize +;save_r15 equ rsp + 80 - LocalVarsSize + + +; summary of register usage +; scanend ebx +; scanendw bx +; chainlenwmask edx +; curmatch rsi +; curmatchd esi +; windowbestlen r8 +; scanalign r9 +; scanalignd r9d +; window r10 +; bestlen r11 +; bestlend r11d +; scanstart r12d +; scanstartw r12w +; scan r13 +; nicematch r14d +; limit r15 +; limitd r15d +; prev rcx + +; all the +4 offsets are due to the addition of pending_buf_size (in zlib +; in the deflate_state structure since the asm code was first written +; (if you compile with zlib 1.0.4 or older, remove the +4). +; Note : these value are good with a 8 bytes boundary pack structure + + + MAX_MATCH equ 258 + MIN_MATCH equ 3 + MIN_LOOKAHEAD equ (MAX_MATCH+MIN_MATCH+1) + + +;;; Offsets for fields in the deflate_state structure. These numbers +;;; are calculated from the definition of deflate_state, with the +;;; assumption that the compiler will dword-align the fields. (Thus, +;;; changing the definition of deflate_state could easily cause this +;;; program to crash horribly, without so much as a warning at +;;; compile time. Sigh.) + +; all the +zlib1222add offsets are due to the addition of fields +; in zlib in the deflate_state structure since the asm code was first written +; (if you compile with zlib 1.0.4 or older, use "zlib1222add equ (-4)"). +; (if you compile with zlib between 1.0.5 and 1.2.2.1, use "zlib1222add equ 0"). +; if you compile with zlib 1.2.2.2 or later , use "zlib1222add equ 8"). + + +IFDEF INFOZIP + +_DATA SEGMENT +COMM window_size:DWORD +; WMask ; 7fff +COMM window:BYTE:010040H +COMM prev:WORD:08000H +; MatchLen : unused +; PrevMatch : unused +COMM strstart:DWORD +COMM match_start:DWORD +; Lookahead : ignore +COMM prev_length:DWORD ; PrevLen +COMM max_chain_length:DWORD +COMM good_match:DWORD +COMM nice_match:DWORD +prev_ad equ OFFSET prev +window_ad equ OFFSET window +nicematch equ nice_match +_DATA ENDS +WMask equ 07fffh + +ELSE + + IFNDEF zlib1222add + zlib1222add equ 8 + ENDIF +dsWSize equ 56+zlib1222add+(zlib1222add/2) +dsWMask equ 64+zlib1222add+(zlib1222add/2) +dsWindow equ 72+zlib1222add +dsPrev equ 88+zlib1222add +dsMatchLen equ 128+zlib1222add +dsPrevMatch equ 132+zlib1222add +dsStrStart equ 140+zlib1222add +dsMatchStart equ 144+zlib1222add +dsLookahead equ 148+zlib1222add +dsPrevLen equ 152+zlib1222add +dsMaxChainLen equ 156+zlib1222add +dsGoodMatch equ 172+zlib1222add +dsNiceMatch equ 176+zlib1222add + +window_size equ [ rcx + dsWSize] +WMask equ [ rcx + dsWMask] +window_ad equ [ rcx + dsWindow] +prev_ad equ [ rcx + dsPrev] +strstart equ [ rcx + dsStrStart] +match_start equ [ rcx + dsMatchStart] +Lookahead equ [ rcx + dsLookahead] ; 0ffffffffh on infozip +prev_length equ [ rcx + dsPrevLen] +max_chain_length equ [ rcx + dsMaxChainLen] +good_match equ [ rcx + dsGoodMatch] +nice_match equ [ rcx + dsNiceMatch] +ENDIF + +; parameter 1 in r8(deflate state s), param 2 in rdx (cur match) + +; see http://weblogs.asp.net/oldnewthing/archive/2004/01/14/58579.aspx and +; http://msdn.microsoft.com/library/en-us/kmarch/hh/kmarch/64bitAMD_8e951dd2-ee77-4728-8702-55ce4b5dd24a.xml.asp +; +; All registers must be preserved across the call, except for +; rax, rcx, rdx, r8, r9, r10, and r11, which are scratch. + + + +;;; Save registers that the compiler may be using, and adjust esp to +;;; make room for our stack frame. + + +;;; Retrieve the function arguments. r8d will hold cur_match +;;; throughout the entire function. edx will hold the pointer to the +;;; deflate_state structure during the function's setup (before +;;; entering the main loop. + +; parameter 1 in rcx (deflate_state* s), param 2 in edx -> r8 (cur match) + +; this clear high 32 bits of r8, which can be garbage in both r8 and rdx + + mov [save_rdi],rdi + mov [save_rsi],rsi + mov [save_rbx],rbx + mov [save_rbp],rbp +IFDEF INFOZIP + mov r8d,ecx +ELSE + mov r8d,edx +ENDIF + mov [save_r12],r12 + mov [save_r13],r13 +; mov [save_r14],r14 +; mov [save_r15],r15 + + +;;; uInt wmask = s->w_mask; +;;; unsigned chain_length = s->max_chain_length; +;;; if (s->prev_length >= s->good_match) { +;;; chain_length >>= 2; +;;; } + + mov edi, prev_length + mov esi, good_match + mov eax, WMask + mov ebx, max_chain_length + cmp edi, esi + jl LastMatchGood + shr ebx, 2 +LastMatchGood: + +;;; chainlen is decremented once beforehand so that the function can +;;; use the sign flag instead of the zero flag for the exit test. +;;; It is then shifted into the high word, to make room for the wmask +;;; value, which it will always accompany. + + dec ebx + shl ebx, 16 + or ebx, eax + +;;; on zlib only +;;; if ((uInt)nice_match > s->lookahead) nice_match = s->lookahead; + +IFDEF INFOZIP + mov [chainlenwmask], ebx +; on infozip nice_match = [nice_match] +ELSE + mov eax, nice_match + mov [chainlenwmask], ebx + mov r10d, Lookahead + cmp r10d, eax + cmovnl r10d, eax + mov [nicematch],r10d +ENDIF + +;;; register Bytef *scan = s->window + s->strstart; + mov r10, window_ad + mov ebp, strstart + lea r13, [r10 + rbp] + +;;; Determine how many bytes the scan ptr is off from being +;;; dword-aligned. + + mov r9,r13 + neg r13 + and r13,3 + +;;; IPos limit = s->strstart > (IPos)MAX_DIST(s) ? +;;; s->strstart - (IPos)MAX_DIST(s) : NIL; +IFDEF INFOZIP + mov eax,07efah ; MAX_DIST = (WSIZE-MIN_LOOKAHEAD) (0x8000-(3+8+1)) +ELSE + mov eax, window_size + sub eax, MIN_LOOKAHEAD +ENDIF + xor edi,edi + sub ebp, eax + + mov r11d, prev_length + + cmovng ebp,edi + +;;; int best_len = s->prev_length; + + +;;; Store the sum of s->window + best_len in esi locally, and in esi. + + lea rsi,[r10+r11] + +;;; register ush scan_start = *(ushf*)scan; +;;; register ush scan_end = *(ushf*)(scan+best_len-1); +;;; Posf *prev = s->prev; + + movzx r12d,word ptr [r9] + movzx ebx, word ptr [r9 + r11 - 1] + + mov rdi, prev_ad + +;;; Jump into the main loop. + + mov edx, [chainlenwmask] + + cmp bx,word ptr [rsi + r8 - 1] + jz LookupLoopIsZero + +LookupLoop1: + and r8d, edx + + movzx r8d, word ptr [rdi + r8*2] + cmp r8d, ebp + jbe LeaveNow + sub edx, 00010000h + js LeaveNow + +LoopEntry1: + cmp bx,word ptr [rsi + r8 - 1] + jz LookupLoopIsZero + +LookupLoop2: + and r8d, edx + + movzx r8d, word ptr [rdi + r8*2] + cmp r8d, ebp + jbe LeaveNow + sub edx, 00010000h + js LeaveNow + +LoopEntry2: + cmp bx,word ptr [rsi + r8 - 1] + jz LookupLoopIsZero + +LookupLoop4: + and r8d, edx + + movzx r8d, word ptr [rdi + r8*2] + cmp r8d, ebp + jbe LeaveNow + sub edx, 00010000h + js LeaveNow + +LoopEntry4: + + cmp bx,word ptr [rsi + r8 - 1] + jnz LookupLoop1 + jmp LookupLoopIsZero + + +;;; do { +;;; match = s->window + cur_match; +;;; if (*(ushf*)(match+best_len-1) != scan_end || +;;; *(ushf*)match != scan_start) continue; +;;; [...] +;;; } while ((cur_match = prev[cur_match & wmask]) > limit +;;; && --chain_length != 0); +;;; +;;; Here is the inner loop of the function. The function will spend the +;;; majority of its time in this loop, and majority of that time will +;;; be spent in the first ten instructions. +;;; +;;; Within this loop: +;;; ebx = scanend +;;; r8d = curmatch +;;; edx = chainlenwmask - i.e., ((chainlen << 16) | wmask) +;;; esi = windowbestlen - i.e., (window + bestlen) +;;; edi = prev +;;; ebp = limit + +LookupLoop: + and r8d, edx + + movzx r8d, word ptr [rdi + r8*2] + cmp r8d, ebp + jbe LeaveNow + sub edx, 00010000h + js LeaveNow + +LoopEntry: + + cmp bx,word ptr [rsi + r8 - 1] + jnz LookupLoop1 +LookupLoopIsZero: + cmp r12w, word ptr [r10 + r8] + jnz LookupLoop1 + + +;;; Store the current value of chainlen. + mov [chainlenwmask], edx + +;;; Point edi to the string under scrutiny, and esi to the string we +;;; are hoping to match it up with. In actuality, esi and edi are +;;; both pointed (MAX_MATCH_8 - scanalign) bytes ahead, and edx is +;;; initialized to -(MAX_MATCH_8 - scanalign). + + lea rsi,[r8+r10] + mov rdx, 0fffffffffffffef8h; -(MAX_MATCH_8) + lea rsi, [rsi + r13 + 0108h] ;MAX_MATCH_8] + lea rdi, [r9 + r13 + 0108h] ;MAX_MATCH_8] + + prefetcht1 [rsi+rdx] + prefetcht1 [rdi+rdx] + + +;;; Test the strings for equality, 8 bytes at a time. At the end, +;;; adjust rdx so that it is offset to the exact byte that mismatched. +;;; +;;; We already know at this point that the first three bytes of the +;;; strings match each other, and they can be safely passed over before +;;; starting the compare loop. So what this code does is skip over 0-3 +;;; bytes, as much as necessary in order to dword-align the edi +;;; pointer. (rsi will still be misaligned three times out of four.) +;;; +;;; It should be confessed that this loop usually does not represent +;;; much of the total running time. Replacing it with a more +;;; straightforward "rep cmpsb" would not drastically degrade +;;; performance. + + +LoopCmps: + mov rax, [rsi + rdx] + xor rax, [rdi + rdx] + jnz LeaveLoopCmps + + mov rax, [rsi + rdx + 8] + xor rax, [rdi + rdx + 8] + jnz LeaveLoopCmps8 + + + mov rax, [rsi + rdx + 8+8] + xor rax, [rdi + rdx + 8+8] + jnz LeaveLoopCmps16 + + add rdx,8+8+8 + + jnz short LoopCmps + jmp short LenMaximum +LeaveLoopCmps16: add rdx,8 +LeaveLoopCmps8: add rdx,8 +LeaveLoopCmps: + + test eax, 0000FFFFh + jnz LenLower + + test eax,0ffffffffh + + jnz LenLower32 + + add rdx,4 + shr rax,32 + or ax,ax + jnz LenLower + +LenLower32: + shr eax,16 + add rdx,2 +LenLower: sub al, 1 + adc rdx, 0 +;;; Calculate the length of the match. If it is longer than MAX_MATCH, +;;; then automatically accept it as the best possible match and leave. + + lea rax, [rdi + rdx] + sub rax, r9 + cmp eax, MAX_MATCH + jge LenMaximum + +;;; If the length of the match is not longer than the best match we +;;; have so far, then forget it and return to the lookup loop. +;/////////////////////////////////// + + cmp eax, r11d + jg LongerMatch + + lea rsi,[r10+r11] + + mov rdi, prev_ad + mov edx, [chainlenwmask] + jmp LookupLoop + +;;; s->match_start = cur_match; +;;; best_len = len; +;;; if (len >= nice_match) break; +;;; scan_end = *(ushf*)(scan+best_len-1); + +LongerMatch: + mov r11d, eax + mov match_start, r8d + cmp eax, [nicematch] + jge LeaveNow + + lea rsi,[r10+rax] + + movzx ebx, word ptr [r9 + rax - 1] + mov rdi, prev_ad + mov edx, [chainlenwmask] + jmp LookupLoop + +;;; Accept the current string, with the maximum possible length. + +LenMaximum: + mov r11d,MAX_MATCH + mov match_start, r8d + +;;; if ((uInt)best_len <= s->lookahead) return (uInt)best_len; +;;; return s->lookahead; + +LeaveNow: +IFDEF INFOZIP + mov eax,r11d +ELSE + mov eax, Lookahead + cmp r11d, eax + cmovng eax, r11d +ENDIF + +;;; Restore the stack and return from whence we came. + + + mov rsi,[save_rsi] + mov rdi,[save_rdi] + mov rbx,[save_rbx] + mov rbp,[save_rbp] + mov r12,[save_r12] + mov r13,[save_r13] +; mov r14,[save_r14] +; mov r15,[save_r15] + + + ret 0 +; please don't remove this string ! +; Your can freely use gvmat64 in any free or commercial app +; but it is far better don't remove the string in the binary! + db 0dh,0ah,"asm686 with masm, optimised assembly code from Brian Raiter, written 1998, converted to amd 64 by Gilles Vollant 2005",0dh,0ah,0 +longest_match ENDP + +match_init PROC + ret 0 +match_init ENDP + + +END ADDED compat/zlib/contrib/masmx64/inffas8664.c Index: compat/zlib/contrib/masmx64/inffas8664.c ================================================================== --- /dev/null +++ compat/zlib/contrib/masmx64/inffas8664.c @@ -0,0 +1,186 @@ +/* inffas8664.c is a hand tuned assembler version of inffast.c - fast decoding + * version for AMD64 on Windows using Microsoft C compiler + * + * Copyright (C) 1995-2003 Mark Adler + * For conditions of distribution and use, see copyright notice in zlib.h + * + * Copyright (C) 2003 Chris Anderson + * Please use the copyright conditions above. + * + * 2005 - Adaptation to Microsoft C Compiler for AMD64 by Gilles Vollant + * + * inffas8664.c call function inffas8664fnc in inffasx64.asm + * inffasx64.asm is automatically convert from AMD64 portion of inffas86.c + * + * Dec-29-2003 -- I added AMD64 inflate asm support. This version is also + * slightly quicker on x86 systems because, instead of using rep movsb to copy + * data, it uses rep movsw, which moves data in 2-byte chunks instead of single + * bytes. I've tested the AMD64 code on a Fedora Core 1 + the x86_64 updates + * from http://fedora.linux.duke.edu/fc1_x86_64 + * which is running on an Athlon 64 3000+ / Gigabyte GA-K8VT800M system with + * 1GB ram. The 64-bit version is about 4% faster than the 32-bit version, + * when decompressing mozilla-source-1.3.tar.gz. + * + * Mar-13-2003 -- Most of this is derived from inffast.S which is derived from + * the gcc -S output of zlib-1.2.0/inffast.c. Zlib-1.2.0 is in beta release at + * the moment. I have successfully compiled and tested this code with gcc2.96, + * gcc3.2, icc5.0, msvc6.0. It is very close to the speed of inffast.S + * compiled with gcc -DNO_MMX, but inffast.S is still faster on the P3 with MMX + * enabled. I will attempt to merge the MMX code into this version. Newer + * versions of this and inffast.S can be found at + * http://www.eetbeetee.com/zlib/ and http://www.charm.net/~christop/zlib/ + * + */ + +#include +#include "zutil.h" +#include "inftrees.h" +#include "inflate.h" +#include "inffast.h" + +/* Mark Adler's comments from inffast.c: */ + +/* + Decode literal, length, and distance codes and write out the resulting + literal and match bytes until either not enough input or output is + available, an end-of-block is encountered, or a data error is encountered. + When large enough input and output buffers are supplied to inflate(), for + example, a 16K input buffer and a 64K output buffer, more than 95% of the + inflate execution time is spent in this routine. + + Entry assumptions: + + state->mode == LEN + strm->avail_in >= 6 + strm->avail_out >= 258 + start >= strm->avail_out + state->bits < 8 + + On return, state->mode is one of: + + LEN -- ran out of enough output space or enough available input + TYPE -- reached end of block code, inflate() to interpret next block + BAD -- error in block data + + Notes: + + - The maximum input bits used by a length/distance pair is 15 bits for the + length code, 5 bits for the length extra, 15 bits for the distance code, + and 13 bits for the distance extra. This totals 48 bits, or six bytes. + Therefore if strm->avail_in >= 6, then there is enough input to avoid + checking for available input while decoding. + + - The maximum bytes that a single length/distance pair can output is 258 + bytes, which is the maximum length that can be coded. inflate_fast() + requires strm->avail_out >= 258 for each loop to avoid checking for + output space. + */ + + + + typedef struct inffast_ar { +/* 64 32 x86 x86_64 */ +/* ar offset register */ +/* 0 0 */ void *esp; /* esp save */ +/* 8 4 */ void *ebp; /* ebp save */ +/* 16 8 */ unsigned char FAR *in; /* esi rsi local strm->next_in */ +/* 24 12 */ unsigned char FAR *last; /* r9 while in < last */ +/* 32 16 */ unsigned char FAR *out; /* edi rdi local strm->next_out */ +/* 40 20 */ unsigned char FAR *beg; /* inflate()'s init next_out */ +/* 48 24 */ unsigned char FAR *end; /* r10 while out < end */ +/* 56 28 */ unsigned char FAR *window;/* size of window, wsize!=0 */ +/* 64 32 */ code const FAR *lcode; /* ebp rbp local strm->lencode */ +/* 72 36 */ code const FAR *dcode; /* r11 local strm->distcode */ +/* 80 40 */ size_t /*unsigned long */hold; /* edx rdx local strm->hold */ +/* 88 44 */ unsigned bits; /* ebx rbx local strm->bits */ +/* 92 48 */ unsigned wsize; /* window size */ +/* 96 52 */ unsigned write; /* window write index */ +/*100 56 */ unsigned lmask; /* r12 mask for lcode */ +/*104 60 */ unsigned dmask; /* r13 mask for dcode */ +/*108 64 */ unsigned len; /* r14 match length */ +/*112 68 */ unsigned dist; /* r15 match distance */ +/*116 72 */ unsigned status; /* set when state chng*/ + } type_ar; +#ifdef ASMINF + +void inflate_fast(strm, start) +z_streamp strm; +unsigned start; /* inflate()'s starting value for strm->avail_out */ +{ + struct inflate_state FAR *state; + type_ar ar; + void inffas8664fnc(struct inffast_ar * par); + + + +#if (defined( __GNUC__ ) && defined( __amd64__ ) && ! defined( __i386 )) || (defined(_MSC_VER) && defined(_M_AMD64)) +#define PAD_AVAIL_IN 6 +#define PAD_AVAIL_OUT 258 +#else +#define PAD_AVAIL_IN 5 +#define PAD_AVAIL_OUT 257 +#endif + + /* copy state to local variables */ + state = (struct inflate_state FAR *)strm->state; + + ar.in = strm->next_in; + ar.last = ar.in + (strm->avail_in - PAD_AVAIL_IN); + ar.out = strm->next_out; + ar.beg = ar.out - (start - strm->avail_out); + ar.end = ar.out + (strm->avail_out - PAD_AVAIL_OUT); + ar.wsize = state->wsize; + ar.write = state->wnext; + ar.window = state->window; + ar.hold = state->hold; + ar.bits = state->bits; + ar.lcode = state->lencode; + ar.dcode = state->distcode; + ar.lmask = (1U << state->lenbits) - 1; + ar.dmask = (1U << state->distbits) - 1; + + /* decode literals and length/distances until end-of-block or not enough + input data or output space */ + + /* align in on 1/2 hold size boundary */ + while (((size_t)(void *)ar.in & (sizeof(ar.hold) / 2 - 1)) != 0) { + ar.hold += (unsigned long)*ar.in++ << ar.bits; + ar.bits += 8; + } + + inffas8664fnc(&ar); + + if (ar.status > 1) { + if (ar.status == 2) + strm->msg = "invalid literal/length code"; + else if (ar.status == 3) + strm->msg = "invalid distance code"; + else + strm->msg = "invalid distance too far back"; + state->mode = BAD; + } + else if ( ar.status == 1 ) { + state->mode = TYPE; + } + + /* return unused bytes (on entry, bits < 8, so in won't go too far back) */ + ar.len = ar.bits >> 3; + ar.in -= ar.len; + ar.bits -= ar.len << 3; + ar.hold &= (1U << ar.bits) - 1; + + /* update state and return */ + strm->next_in = ar.in; + strm->next_out = ar.out; + strm->avail_in = (unsigned)(ar.in < ar.last ? + PAD_AVAIL_IN + (ar.last - ar.in) : + PAD_AVAIL_IN - (ar.in - ar.last)); + strm->avail_out = (unsigned)(ar.out < ar.end ? + PAD_AVAIL_OUT + (ar.end - ar.out) : + PAD_AVAIL_OUT - (ar.out - ar.end)); + state->hold = (unsigned long)ar.hold; + state->bits = ar.bits; + return; +} + +#endif ADDED compat/zlib/contrib/masmx64/inffasx64.asm Index: compat/zlib/contrib/masmx64/inffasx64.asm ================================================================== --- /dev/null +++ compat/zlib/contrib/masmx64/inffasx64.asm @@ -0,0 +1,396 @@ +; inffasx64.asm is a hand tuned assembler version of inffast.c - fast decoding +; version for AMD64 on Windows using Microsoft C compiler +; +; inffasx64.asm is automatically convert from AMD64 portion of inffas86.c +; inffasx64.asm is called by inffas8664.c, which contain more info. + + +; to compile this file, I use option +; ml64.exe /Flinffasx64 /c /Zi inffasx64.asm +; with Microsoft Macro Assembler (x64) for AMD64 +; + +; This file compile with Microsoft Macro Assembler (x64) for AMD64 +; +; ml64.exe is given with Visual Studio 2005/2008/2010 and Windows WDK +; +; (you can get Windows WDK with ml64 for AMD64 from +; http://www.microsoft.com/whdc/Devtools/wdk/default.mspx for low price) +; + + +.code +inffas8664fnc PROC + +; see http://weblogs.asp.net/oldnewthing/archive/2004/01/14/58579.aspx and +; http://msdn.microsoft.com/library/en-us/kmarch/hh/kmarch/64bitAMD_8e951dd2-ee77-4728-8702-55ce4b5dd24a.xml.asp +; +; All registers must be preserved across the call, except for +; rax, rcx, rdx, r8, r-9, r10, and r11, which are scratch. + + + mov [rsp-8],rsi + mov [rsp-16],rdi + mov [rsp-24],r12 + mov [rsp-32],r13 + mov [rsp-40],r14 + mov [rsp-48],r15 + mov [rsp-56],rbx + + mov rax,rcx + + mov [rax+8], rbp ; /* save regs rbp and rsp */ + mov [rax], rsp + + mov rsp, rax ; /* make rsp point to &ar */ + + mov rsi, [rsp+16] ; /* rsi = in */ + mov rdi, [rsp+32] ; /* rdi = out */ + mov r9, [rsp+24] ; /* r9 = last */ + mov r10, [rsp+48] ; /* r10 = end */ + mov rbp, [rsp+64] ; /* rbp = lcode */ + mov r11, [rsp+72] ; /* r11 = dcode */ + mov rdx, [rsp+80] ; /* rdx = hold */ + mov ebx, [rsp+88] ; /* ebx = bits */ + mov r12d, [rsp+100] ; /* r12d = lmask */ + mov r13d, [rsp+104] ; /* r13d = dmask */ + ; /* r14d = len */ + ; /* r15d = dist */ + + + cld + cmp r10, rdi + je L_one_time ; /* if only one decode left */ + cmp r9, rsi + + jne L_do_loop + + +L_one_time: + mov r8, r12 ; /* r8 = lmask */ + cmp bl, 32 + ja L_get_length_code_one_time + + lodsd ; /* eax = *(uint *)in++ */ + mov cl, bl ; /* cl = bits, needs it for shifting */ + add bl, 32 ; /* bits += 32 */ + shl rax, cl + or rdx, rax ; /* hold |= *((uint *)in)++ << bits */ + jmp L_get_length_code_one_time + +ALIGN 4 +L_while_test: + cmp r10, rdi + jbe L_break_loop + cmp r9, rsi + jbe L_break_loop + +L_do_loop: + mov r8, r12 ; /* r8 = lmask */ + cmp bl, 32 + ja L_get_length_code ; /* if (32 < bits) */ + + lodsd ; /* eax = *(uint *)in++ */ + mov cl, bl ; /* cl = bits, needs it for shifting */ + add bl, 32 ; /* bits += 32 */ + shl rax, cl + or rdx, rax ; /* hold |= *((uint *)in)++ << bits */ + +L_get_length_code: + and r8, rdx ; /* r8 &= hold */ + mov eax, [rbp+r8*4] ; /* eax = lcode[hold & lmask] */ + + mov cl, ah ; /* cl = this.bits */ + sub bl, ah ; /* bits -= this.bits */ + shr rdx, cl ; /* hold >>= this.bits */ + + test al, al + jnz L_test_for_length_base ; /* if (op != 0) 45.7% */ + + mov r8, r12 ; /* r8 = lmask */ + shr eax, 16 ; /* output this.val char */ + stosb + +L_get_length_code_one_time: + and r8, rdx ; /* r8 &= hold */ + mov eax, [rbp+r8*4] ; /* eax = lcode[hold & lmask] */ + +L_dolen: + mov cl, ah ; /* cl = this.bits */ + sub bl, ah ; /* bits -= this.bits */ + shr rdx, cl ; /* hold >>= this.bits */ + + test al, al + jnz L_test_for_length_base ; /* if (op != 0) 45.7% */ + + shr eax, 16 ; /* output this.val char */ + stosb + jmp L_while_test + +ALIGN 4 +L_test_for_length_base: + mov r14d, eax ; /* len = this */ + shr r14d, 16 ; /* len = this.val */ + mov cl, al + + test al, 16 + jz L_test_for_second_level_length ; /* if ((op & 16) == 0) 8% */ + and cl, 15 ; /* op &= 15 */ + jz L_decode_distance ; /* if (!op) */ + +L_add_bits_to_len: + sub bl, cl + xor eax, eax + inc eax + shl eax, cl + dec eax + and eax, edx ; /* eax &= hold */ + shr rdx, cl + add r14d, eax ; /* len += hold & mask[op] */ + +L_decode_distance: + mov r8, r13 ; /* r8 = dmask */ + cmp bl, 32 + ja L_get_distance_code ; /* if (32 < bits) */ + + lodsd ; /* eax = *(uint *)in++ */ + mov cl, bl ; /* cl = bits, needs it for shifting */ + add bl, 32 ; /* bits += 32 */ + shl rax, cl + or rdx, rax ; /* hold |= *((uint *)in)++ << bits */ + +L_get_distance_code: + and r8, rdx ; /* r8 &= hold */ + mov eax, [r11+r8*4] ; /* eax = dcode[hold & dmask] */ + +L_dodist: + mov r15d, eax ; /* dist = this */ + shr r15d, 16 ; /* dist = this.val */ + mov cl, ah + sub bl, ah ; /* bits -= this.bits */ + shr rdx, cl ; /* hold >>= this.bits */ + mov cl, al ; /* cl = this.op */ + + test al, 16 ; /* if ((op & 16) == 0) */ + jz L_test_for_second_level_dist + and cl, 15 ; /* op &= 15 */ + jz L_check_dist_one + +L_add_bits_to_dist: + sub bl, cl + xor eax, eax + inc eax + shl eax, cl + dec eax ; /* (1 << op) - 1 */ + and eax, edx ; /* eax &= hold */ + shr rdx, cl + add r15d, eax ; /* dist += hold & ((1 << op) - 1) */ + +L_check_window: + mov r8, rsi ; /* save in so from can use it's reg */ + mov rax, rdi + sub rax, [rsp+40] ; /* nbytes = out - beg */ + + cmp eax, r15d + jb L_clip_window ; /* if (dist > nbytes) 4.2% */ + + mov ecx, r14d ; /* ecx = len */ + mov rsi, rdi + sub rsi, r15 ; /* from = out - dist */ + + sar ecx, 1 + jnc L_copy_two ; /* if len % 2 == 0 */ + + rep movsw + mov al, [rsi] + mov [rdi], al + inc rdi + + mov rsi, r8 ; /* move in back to %rsi, toss from */ + jmp L_while_test + +L_copy_two: + rep movsw + mov rsi, r8 ; /* move in back to %rsi, toss from */ + jmp L_while_test + +ALIGN 4 +L_check_dist_one: + cmp r15d, 1 ; /* if dist 1, is a memset */ + jne L_check_window + cmp [rsp+40], rdi ; /* if out == beg, outside window */ + je L_check_window + + mov ecx, r14d ; /* ecx = len */ + mov al, [rdi-1] + mov ah, al + + sar ecx, 1 + jnc L_set_two + mov [rdi], al + inc rdi + +L_set_two: + rep stosw + jmp L_while_test + +ALIGN 4 +L_test_for_second_level_length: + test al, 64 + jnz L_test_for_end_of_block ; /* if ((op & 64) != 0) */ + + xor eax, eax + inc eax + shl eax, cl + dec eax + and eax, edx ; /* eax &= hold */ + add eax, r14d ; /* eax += len */ + mov eax, [rbp+rax*4] ; /* eax = lcode[val+(hold&mask[op])]*/ + jmp L_dolen + +ALIGN 4 +L_test_for_second_level_dist: + test al, 64 + jnz L_invalid_distance_code ; /* if ((op & 64) != 0) */ + + xor eax, eax + inc eax + shl eax, cl + dec eax + and eax, edx ; /* eax &= hold */ + add eax, r15d ; /* eax += dist */ + mov eax, [r11+rax*4] ; /* eax = dcode[val+(hold&mask[op])]*/ + jmp L_dodist + +ALIGN 4 +L_clip_window: + mov ecx, eax ; /* ecx = nbytes */ + mov eax, [rsp+92] ; /* eax = wsize, prepare for dist cmp */ + neg ecx ; /* nbytes = -nbytes */ + + cmp eax, r15d + jb L_invalid_distance_too_far ; /* if (dist > wsize) */ + + add ecx, r15d ; /* nbytes = dist - nbytes */ + cmp dword ptr [rsp+96], 0 + jne L_wrap_around_window ; /* if (write != 0) */ + + mov rsi, [rsp+56] ; /* from = window */ + sub eax, ecx ; /* eax -= nbytes */ + add rsi, rax ; /* from += wsize - nbytes */ + + mov eax, r14d ; /* eax = len */ + cmp r14d, ecx + jbe L_do_copy ; /* if (nbytes >= len) */ + + sub eax, ecx ; /* eax -= nbytes */ + rep movsb + mov rsi, rdi + sub rsi, r15 ; /* from = &out[ -dist ] */ + jmp L_do_copy + +ALIGN 4 +L_wrap_around_window: + mov eax, [rsp+96] ; /* eax = write */ + cmp ecx, eax + jbe L_contiguous_in_window ; /* if (write >= nbytes) */ + + mov esi, [rsp+92] ; /* from = wsize */ + add rsi, [rsp+56] ; /* from += window */ + add rsi, rax ; /* from += write */ + sub rsi, rcx ; /* from -= nbytes */ + sub ecx, eax ; /* nbytes -= write */ + + mov eax, r14d ; /* eax = len */ + cmp eax, ecx + jbe L_do_copy ; /* if (nbytes >= len) */ + + sub eax, ecx ; /* len -= nbytes */ + rep movsb + mov rsi, [rsp+56] ; /* from = window */ + mov ecx, [rsp+96] ; /* nbytes = write */ + cmp eax, ecx + jbe L_do_copy ; /* if (nbytes >= len) */ + + sub eax, ecx ; /* len -= nbytes */ + rep movsb + mov rsi, rdi + sub rsi, r15 ; /* from = out - dist */ + jmp L_do_copy + +ALIGN 4 +L_contiguous_in_window: + mov rsi, [rsp+56] ; /* rsi = window */ + add rsi, rax + sub rsi, rcx ; /* from += write - nbytes */ + + mov eax, r14d ; /* eax = len */ + cmp eax, ecx + jbe L_do_copy ; /* if (nbytes >= len) */ + + sub eax, ecx ; /* len -= nbytes */ + rep movsb + mov rsi, rdi + sub rsi, r15 ; /* from = out - dist */ + jmp L_do_copy ; /* if (nbytes >= len) */ + +ALIGN 4 +L_do_copy: + mov ecx, eax ; /* ecx = len */ + rep movsb + + mov rsi, r8 ; /* move in back to %esi, toss from */ + jmp L_while_test + +L_test_for_end_of_block: + test al, 32 + jz L_invalid_literal_length_code + mov dword ptr [rsp+116], 1 + jmp L_break_loop_with_status + +L_invalid_literal_length_code: + mov dword ptr [rsp+116], 2 + jmp L_break_loop_with_status + +L_invalid_distance_code: + mov dword ptr [rsp+116], 3 + jmp L_break_loop_with_status + +L_invalid_distance_too_far: + mov dword ptr [rsp+116], 4 + jmp L_break_loop_with_status + +L_break_loop: + mov dword ptr [rsp+116], 0 + +L_break_loop_with_status: +; /* put in, out, bits, and hold back into ar and pop esp */ + mov [rsp+16], rsi ; /* in */ + mov [rsp+32], rdi ; /* out */ + mov [rsp+88], ebx ; /* bits */ + mov [rsp+80], rdx ; /* hold */ + + mov rax, [rsp] ; /* restore rbp and rsp */ + mov rbp, [rsp+8] + mov rsp, rax + + + + mov rsi,[rsp-8] + mov rdi,[rsp-16] + mov r12,[rsp-24] + mov r13,[rsp-32] + mov r14,[rsp-40] + mov r15,[rsp-48] + mov rbx,[rsp-56] + + ret 0 +; : +; : "m" (ar) +; : "memory", "%rax", "%rbx", "%rcx", "%rdx", "%rsi", "%rdi", +; "%r8", "%r9", "%r10", "%r11", "%r12", "%r13", "%r14", "%r15" +; ); + +inffas8664fnc ENDP +;_TEXT ENDS +END ADDED compat/zlib/contrib/masmx64/readme.txt Index: compat/zlib/contrib/masmx64/readme.txt ================================================================== --- /dev/null +++ compat/zlib/contrib/masmx64/readme.txt @@ -0,0 +1,31 @@ +Summary +------- +This directory contains ASM implementations of the functions +longest_match() and inflate_fast(), for 64 bits x86 (both AMD64 and Intel EM64t), +for use with Microsoft Macro Assembler (x64) for AMD64 and Microsoft C++ 64 bits. + +gvmat64.asm is written by Gilles Vollant (2005), by using Brian Raiter 686/32 bits + assembly optimized version from Jean-loup Gailly original longest_match function + +inffasx64.asm and inffas8664.c were written by Chris Anderson, by optimizing + original function from Mark Adler + +Use instructions +---------------- +Assemble the .asm files using MASM and put the object files into the zlib source +directory. You can also get object files here: + + http://www.winimage.com/zLibDll/zlib124_masm_obj.zip + +define ASMV and ASMINF in your project. Include inffas8664.c in your source tree, +and inffasx64.obj and gvmat64.obj as object to link. + + +Build instructions +------------------ +run bld_64.bat with Microsoft Macro Assembler (x64) for AMD64 (ml64.exe) + +ml64.exe is given with Visual Studio 2005, Windows 2003 server DDK + +You can get Windows 2003 server DDK with ml64 and cl for AMD64 from + http://www.microsoft.com/whdc/devtools/ddk/default.mspx for low price) ADDED compat/zlib/contrib/masmx86/bld_ml32.bat Index: compat/zlib/contrib/masmx86/bld_ml32.bat ================================================================== --- /dev/null +++ compat/zlib/contrib/masmx86/bld_ml32.bat @@ -0,0 +1,2 @@ +ml /coff /Zi /c /Flmatch686.lst match686.asm +ml /coff /Zi /c /Flinffas32.lst inffas32.asm ADDED compat/zlib/contrib/masmx86/inffas32.asm Index: compat/zlib/contrib/masmx86/inffas32.asm ================================================================== --- /dev/null +++ compat/zlib/contrib/masmx86/inffas32.asm @@ -0,0 +1,1080 @@ +;/* inffas32.asm is a hand tuned assembler version of inffast.c -- fast decoding +; * +; * inffas32.asm is derivated from inffas86.c, with translation of assembly code +; * +; * Copyright (C) 1995-2003 Mark Adler +; * For conditions of distribution and use, see copyright notice in zlib.h +; * +; * Copyright (C) 2003 Chris Anderson +; * Please use the copyright conditions above. +; * +; * Mar-13-2003 -- Most of this is derived from inffast.S which is derived from +; * the gcc -S output of zlib-1.2.0/inffast.c. Zlib-1.2.0 is in beta release at +; * the moment. I have successfully compiled and tested this code with gcc2.96, +; * gcc3.2, icc5.0, msvc6.0. It is very close to the speed of inffast.S +; * compiled with gcc -DNO_MMX, but inffast.S is still faster on the P3 with MMX +; * enabled. I will attempt to merge the MMX code into this version. Newer +; * versions of this and inffast.S can be found at +; * http://www.eetbeetee.com/zlib/ and http://www.charm.net/~christop/zlib/ +; * +; * 2005 : modification by Gilles Vollant +; */ +; For Visual C++ 4.x and higher and ML 6.x and higher +; ml.exe is in directory \MASM611C of Win95 DDK +; ml.exe is also distributed in http://www.masm32.com/masmdl.htm +; and in VC++2003 toolkit at http://msdn.microsoft.com/visualc/vctoolkit2003/ +; +; +; compile with command line option +; ml /coff /Zi /c /Flinffas32.lst inffas32.asm + +; if you define NO_GZIP (see inflate.h), compile with +; ml /coff /Zi /c /Flinffas32.lst /DNO_GUNZIP inffas32.asm + + +; zlib122sup is 0 fort zlib 1.2.2.1 and lower +; zlib122sup is 8 fort zlib 1.2.2.2 and more (with addition of dmax and head +; in inflate_state in inflate.h) +zlib1222sup equ 8 + + +IFDEF GUNZIP + INFLATE_MODE_TYPE equ 11 + INFLATE_MODE_BAD equ 26 +ELSE + IFNDEF NO_GUNZIP + INFLATE_MODE_TYPE equ 11 + INFLATE_MODE_BAD equ 26 + ELSE + INFLATE_MODE_TYPE equ 3 + INFLATE_MODE_BAD equ 17 + ENDIF +ENDIF + + +; 75 "inffast.S" +;FILE "inffast.S" + +;;;GLOBAL _inflate_fast + +;;;SECTION .text + + + + .586p + .mmx + + name inflate_fast_x86 + .MODEL FLAT + +_DATA segment +inflate_fast_use_mmx: + dd 1 + + +_TEXT segment + + + +ALIGN 4 + db 'Fast decoding Code from Chris Anderson' + db 0 + +ALIGN 4 +invalid_literal_length_code_msg: + db 'invalid literal/length code' + db 0 + +ALIGN 4 +invalid_distance_code_msg: + db 'invalid distance code' + db 0 + +ALIGN 4 +invalid_distance_too_far_msg: + db 'invalid distance too far back' + db 0 + + +ALIGN 4 +inflate_fast_mask: +dd 0 +dd 1 +dd 3 +dd 7 +dd 15 +dd 31 +dd 63 +dd 127 +dd 255 +dd 511 +dd 1023 +dd 2047 +dd 4095 +dd 8191 +dd 16383 +dd 32767 +dd 65535 +dd 131071 +dd 262143 +dd 524287 +dd 1048575 +dd 2097151 +dd 4194303 +dd 8388607 +dd 16777215 +dd 33554431 +dd 67108863 +dd 134217727 +dd 268435455 +dd 536870911 +dd 1073741823 +dd 2147483647 +dd 4294967295 + + +mode_state equ 0 ;/* state->mode */ +wsize_state equ (32+zlib1222sup) ;/* state->wsize */ +write_state equ (36+4+zlib1222sup) ;/* state->write */ +window_state equ (40+4+zlib1222sup) ;/* state->window */ +hold_state equ (44+4+zlib1222sup) ;/* state->hold */ +bits_state equ (48+4+zlib1222sup) ;/* state->bits */ +lencode_state equ (64+4+zlib1222sup) ;/* state->lencode */ +distcode_state equ (68+4+zlib1222sup) ;/* state->distcode */ +lenbits_state equ (72+4+zlib1222sup) ;/* state->lenbits */ +distbits_state equ (76+4+zlib1222sup) ;/* state->distbits */ + + +;;SECTION .text +; 205 "inffast.S" +;GLOBAL inflate_fast_use_mmx + +;SECTION .data + + +; GLOBAL inflate_fast_use_mmx:object +;.size inflate_fast_use_mmx, 4 +; 226 "inffast.S" +;SECTION .text + +ALIGN 4 +_inflate_fast proc near +.FPO (16, 4, 0, 0, 1, 0) + push edi + push esi + push ebp + push ebx + pushfd + sub esp,64 + cld + + + + + mov esi, [esp+88] + mov edi, [esi+28] + + + + + + + + mov edx, [esi+4] + mov eax, [esi+0] + + add edx,eax + sub edx,11 + + mov [esp+44],eax + mov [esp+20],edx + + mov ebp, [esp+92] + mov ecx, [esi+16] + mov ebx, [esi+12] + + sub ebp,ecx + neg ebp + add ebp,ebx + + sub ecx,257 + add ecx,ebx + + mov [esp+60],ebx + mov [esp+40],ebp + mov [esp+16],ecx +; 285 "inffast.S" + mov eax, [edi+lencode_state] + mov ecx, [edi+distcode_state] + + mov [esp+8],eax + mov [esp+12],ecx + + mov eax,1 + mov ecx, [edi+lenbits_state] + shl eax,cl + dec eax + mov [esp+0],eax + + mov eax,1 + mov ecx, [edi+distbits_state] + shl eax,cl + dec eax + mov [esp+4],eax + + mov eax, [edi+wsize_state] + mov ecx, [edi+write_state] + mov edx, [edi+window_state] + + mov [esp+52],eax + mov [esp+48],ecx + mov [esp+56],edx + + mov ebp, [edi+hold_state] + mov ebx, [edi+bits_state] +; 321 "inffast.S" + mov esi, [esp+44] + mov ecx, [esp+20] + cmp ecx,esi + ja L_align_long + + add ecx,11 + sub ecx,esi + mov eax,12 + sub eax,ecx + lea edi, [esp+28] + rep movsb + mov ecx,eax + xor eax,eax + rep stosb + lea esi, [esp+28] + mov [esp+20],esi + jmp L_is_aligned + + +L_align_long: + test esi,3 + jz L_is_aligned + xor eax,eax + mov al, [esi] + inc esi + mov ecx,ebx + add ebx,8 + shl eax,cl + or ebp,eax + jmp L_align_long + +L_is_aligned: + mov edi, [esp+60] +; 366 "inffast.S" +L_check_mmx: + cmp dword ptr [inflate_fast_use_mmx],2 + je L_init_mmx + ja L_do_loop + + push eax + push ebx + push ecx + push edx + pushfd + mov eax, [esp] + xor dword ptr [esp],0200000h + + + + + popfd + pushfd + pop edx + xor edx,eax + jz L_dont_use_mmx + xor eax,eax + cpuid + cmp ebx,0756e6547h + jne L_dont_use_mmx + cmp ecx,06c65746eh + jne L_dont_use_mmx + cmp edx,049656e69h + jne L_dont_use_mmx + mov eax,1 + cpuid + shr eax,8 + and eax,15 + cmp eax,6 + jne L_dont_use_mmx + test edx,0800000h + jnz L_use_mmx + jmp L_dont_use_mmx +L_use_mmx: + mov dword ptr [inflate_fast_use_mmx],2 + jmp L_check_mmx_pop +L_dont_use_mmx: + mov dword ptr [inflate_fast_use_mmx],3 +L_check_mmx_pop: + pop edx + pop ecx + pop ebx + pop eax + jmp L_check_mmx +; 426 "inffast.S" +ALIGN 4 +L_do_loop: +; 437 "inffast.S" + cmp bl,15 + ja L_get_length_code + + xor eax,eax + lodsw + mov cl,bl + add bl,16 + shl eax,cl + or ebp,eax + +L_get_length_code: + mov edx, [esp+0] + mov ecx, [esp+8] + and edx,ebp + mov eax, [ecx+edx*4] + +L_dolen: + + + + + + + mov cl,ah + sub bl,ah + shr ebp,cl + + + + + + + test al,al + jnz L_test_for_length_base + + shr eax,16 + stosb + +L_while_test: + + + cmp [esp+16],edi + jbe L_break_loop + + cmp [esp+20],esi + ja L_do_loop + jmp L_break_loop + +L_test_for_length_base: +; 502 "inffast.S" + mov edx,eax + shr edx,16 + mov cl,al + + test al,16 + jz L_test_for_second_level_length + and cl,15 + jz L_save_len + cmp bl,cl + jae L_add_bits_to_len + + mov ch,cl + xor eax,eax + lodsw + mov cl,bl + add bl,16 + shl eax,cl + or ebp,eax + mov cl,ch + +L_add_bits_to_len: + mov eax,1 + shl eax,cl + dec eax + sub bl,cl + and eax,ebp + shr ebp,cl + add edx,eax + +L_save_len: + mov [esp+24],edx + + +L_decode_distance: +; 549 "inffast.S" + cmp bl,15 + ja L_get_distance_code + + xor eax,eax + lodsw + mov cl,bl + add bl,16 + shl eax,cl + or ebp,eax + +L_get_distance_code: + mov edx, [esp+4] + mov ecx, [esp+12] + and edx,ebp + mov eax, [ecx+edx*4] + + +L_dodist: + mov edx,eax + shr edx,16 + mov cl,ah + sub bl,ah + shr ebp,cl +; 584 "inffast.S" + mov cl,al + + test al,16 + jz L_test_for_second_level_dist + and cl,15 + jz L_check_dist_one + cmp bl,cl + jae L_add_bits_to_dist + + mov ch,cl + xor eax,eax + lodsw + mov cl,bl + add bl,16 + shl eax,cl + or ebp,eax + mov cl,ch + +L_add_bits_to_dist: + mov eax,1 + shl eax,cl + dec eax + sub bl,cl + and eax,ebp + shr ebp,cl + add edx,eax + jmp L_check_window + +L_check_window: +; 625 "inffast.S" + mov [esp+44],esi + mov eax,edi + sub eax, [esp+40] + + cmp eax,edx + jb L_clip_window + + mov ecx, [esp+24] + mov esi,edi + sub esi,edx + + sub ecx,3 + mov al, [esi] + mov [edi],al + mov al, [esi+1] + mov dl, [esi+2] + add esi,3 + mov [edi+1],al + mov [edi+2],dl + add edi,3 + rep movsb + + mov esi, [esp+44] + jmp L_while_test + +ALIGN 4 +L_check_dist_one: + cmp edx,1 + jne L_check_window + cmp [esp+40],edi + je L_check_window + + dec edi + mov ecx, [esp+24] + mov al, [edi] + sub ecx,3 + + mov [edi+1],al + mov [edi+2],al + mov [edi+3],al + add edi,4 + rep stosb + + jmp L_while_test + +ALIGN 4 +L_test_for_second_level_length: + + + + + test al,64 + jnz L_test_for_end_of_block + + mov eax,1 + shl eax,cl + dec eax + and eax,ebp + add eax,edx + mov edx, [esp+8] + mov eax, [edx+eax*4] + jmp L_dolen + +ALIGN 4 +L_test_for_second_level_dist: + + + + + test al,64 + jnz L_invalid_distance_code + + mov eax,1 + shl eax,cl + dec eax + and eax,ebp + add eax,edx + mov edx, [esp+12] + mov eax, [edx+eax*4] + jmp L_dodist + +ALIGN 4 +L_clip_window: +; 721 "inffast.S" + mov ecx,eax + mov eax, [esp+52] + neg ecx + mov esi, [esp+56] + + cmp eax,edx + jb L_invalid_distance_too_far + + add ecx,edx + cmp dword ptr [esp+48],0 + jne L_wrap_around_window + + sub eax,ecx + add esi,eax +; 749 "inffast.S" + mov eax, [esp+24] + cmp eax,ecx + jbe L_do_copy1 + + sub eax,ecx + rep movsb + mov esi,edi + sub esi,edx + jmp L_do_copy1 + + cmp eax,ecx + jbe L_do_copy1 + + sub eax,ecx + rep movsb + mov esi,edi + sub esi,edx + jmp L_do_copy1 + +L_wrap_around_window: +; 793 "inffast.S" + mov eax, [esp+48] + cmp ecx,eax + jbe L_contiguous_in_window + + add esi, [esp+52] + add esi,eax + sub esi,ecx + sub ecx,eax + + + mov eax, [esp+24] + cmp eax,ecx + jbe L_do_copy1 + + sub eax,ecx + rep movsb + mov esi, [esp+56] + mov ecx, [esp+48] + cmp eax,ecx + jbe L_do_copy1 + + sub eax,ecx + rep movsb + mov esi,edi + sub esi,edx + jmp L_do_copy1 + +L_contiguous_in_window: +; 836 "inffast.S" + add esi,eax + sub esi,ecx + + + mov eax, [esp+24] + cmp eax,ecx + jbe L_do_copy1 + + sub eax,ecx + rep movsb + mov esi,edi + sub esi,edx + +L_do_copy1: +; 862 "inffast.S" + mov ecx,eax + rep movsb + + mov esi, [esp+44] + jmp L_while_test +; 878 "inffast.S" +ALIGN 4 +L_init_mmx: + emms + + + + + + movd mm0,ebp + mov ebp,ebx +; 896 "inffast.S" + movd mm4,dword ptr [esp+0] + movq mm3,mm4 + movd mm5,dword ptr [esp+4] + movq mm2,mm5 + pxor mm1,mm1 + mov ebx, [esp+8] + jmp L_do_loop_mmx + +ALIGN 4 +L_do_loop_mmx: + psrlq mm0,mm1 + + cmp ebp,32 + ja L_get_length_code_mmx + + movd mm6,ebp + movd mm7,dword ptr [esi] + add esi,4 + psllq mm7,mm6 + add ebp,32 + por mm0,mm7 + +L_get_length_code_mmx: + pand mm4,mm0 + movd eax,mm4 + movq mm4,mm3 + mov eax, [ebx+eax*4] + +L_dolen_mmx: + movzx ecx,ah + movd mm1,ecx + sub ebp,ecx + + test al,al + jnz L_test_for_length_base_mmx + + shr eax,16 + stosb + +L_while_test_mmx: + + + cmp [esp+16],edi + jbe L_break_loop + + cmp [esp+20],esi + ja L_do_loop_mmx + jmp L_break_loop + +L_test_for_length_base_mmx: + + mov edx,eax + shr edx,16 + + test al,16 + jz L_test_for_second_level_length_mmx + and eax,15 + jz L_decode_distance_mmx + + psrlq mm0,mm1 + movd mm1,eax + movd ecx,mm0 + sub ebp,eax + and ecx, [inflate_fast_mask+eax*4] + add edx,ecx + +L_decode_distance_mmx: + psrlq mm0,mm1 + + cmp ebp,32 + ja L_get_dist_code_mmx + + movd mm6,ebp + movd mm7,dword ptr [esi] + add esi,4 + psllq mm7,mm6 + add ebp,32 + por mm0,mm7 + +L_get_dist_code_mmx: + mov ebx, [esp+12] + pand mm5,mm0 + movd eax,mm5 + movq mm5,mm2 + mov eax, [ebx+eax*4] + +L_dodist_mmx: + + movzx ecx,ah + mov ebx,eax + shr ebx,16 + sub ebp,ecx + movd mm1,ecx + + test al,16 + jz L_test_for_second_level_dist_mmx + and eax,15 + jz L_check_dist_one_mmx + +L_add_bits_to_dist_mmx: + psrlq mm0,mm1 + movd mm1,eax + movd ecx,mm0 + sub ebp,eax + and ecx, [inflate_fast_mask+eax*4] + add ebx,ecx + +L_check_window_mmx: + mov [esp+44],esi + mov eax,edi + sub eax, [esp+40] + + cmp eax,ebx + jb L_clip_window_mmx + + mov ecx,edx + mov esi,edi + sub esi,ebx + + sub ecx,3 + mov al, [esi] + mov [edi],al + mov al, [esi+1] + mov dl, [esi+2] + add esi,3 + mov [edi+1],al + mov [edi+2],dl + add edi,3 + rep movsb + + mov esi, [esp+44] + mov ebx, [esp+8] + jmp L_while_test_mmx + +ALIGN 4 +L_check_dist_one_mmx: + cmp ebx,1 + jne L_check_window_mmx + cmp [esp+40],edi + je L_check_window_mmx + + dec edi + mov ecx,edx + mov al, [edi] + sub ecx,3 + + mov [edi+1],al + mov [edi+2],al + mov [edi+3],al + add edi,4 + rep stosb + + mov ebx, [esp+8] + jmp L_while_test_mmx + +ALIGN 4 +L_test_for_second_level_length_mmx: + test al,64 + jnz L_test_for_end_of_block + + and eax,15 + psrlq mm0,mm1 + movd ecx,mm0 + and ecx, [inflate_fast_mask+eax*4] + add ecx,edx + mov eax, [ebx+ecx*4] + jmp L_dolen_mmx + +ALIGN 4 +L_test_for_second_level_dist_mmx: + test al,64 + jnz L_invalid_distance_code + + and eax,15 + psrlq mm0,mm1 + movd ecx,mm0 + and ecx, [inflate_fast_mask+eax*4] + mov eax, [esp+12] + add ecx,ebx + mov eax, [eax+ecx*4] + jmp L_dodist_mmx + +ALIGN 4 +L_clip_window_mmx: + + mov ecx,eax + mov eax, [esp+52] + neg ecx + mov esi, [esp+56] + + cmp eax,ebx + jb L_invalid_distance_too_far + + add ecx,ebx + cmp dword ptr [esp+48],0 + jne L_wrap_around_window_mmx + + sub eax,ecx + add esi,eax + + cmp edx,ecx + jbe L_do_copy1_mmx + + sub edx,ecx + rep movsb + mov esi,edi + sub esi,ebx + jmp L_do_copy1_mmx + + cmp edx,ecx + jbe L_do_copy1_mmx + + sub edx,ecx + rep movsb + mov esi,edi + sub esi,ebx + jmp L_do_copy1_mmx + +L_wrap_around_window_mmx: + + mov eax, [esp+48] + cmp ecx,eax + jbe L_contiguous_in_window_mmx + + add esi, [esp+52] + add esi,eax + sub esi,ecx + sub ecx,eax + + + cmp edx,ecx + jbe L_do_copy1_mmx + + sub edx,ecx + rep movsb + mov esi, [esp+56] + mov ecx, [esp+48] + cmp edx,ecx + jbe L_do_copy1_mmx + + sub edx,ecx + rep movsb + mov esi,edi + sub esi,ebx + jmp L_do_copy1_mmx + +L_contiguous_in_window_mmx: + + add esi,eax + sub esi,ecx + + + cmp edx,ecx + jbe L_do_copy1_mmx + + sub edx,ecx + rep movsb + mov esi,edi + sub esi,ebx + +L_do_copy1_mmx: + + + mov ecx,edx + rep movsb + + mov esi, [esp+44] + mov ebx, [esp+8] + jmp L_while_test_mmx +; 1174 "inffast.S" +L_invalid_distance_code: + + + + + + mov ecx, invalid_distance_code_msg + mov edx,INFLATE_MODE_BAD + jmp L_update_stream_state + +L_test_for_end_of_block: + + + + + + test al,32 + jz L_invalid_literal_length_code + + mov ecx,0 + mov edx,INFLATE_MODE_TYPE + jmp L_update_stream_state + +L_invalid_literal_length_code: + + + + + + mov ecx, invalid_literal_length_code_msg + mov edx,INFLATE_MODE_BAD + jmp L_update_stream_state + +L_invalid_distance_too_far: + + + + mov esi, [esp+44] + mov ecx, invalid_distance_too_far_msg + mov edx,INFLATE_MODE_BAD + jmp L_update_stream_state + +L_update_stream_state: + + mov eax, [esp+88] + test ecx,ecx + jz L_skip_msg + mov [eax+24],ecx +L_skip_msg: + mov eax, [eax+28] + mov [eax+mode_state],edx + jmp L_break_loop + +ALIGN 4 +L_break_loop: +; 1243 "inffast.S" + cmp dword ptr [inflate_fast_use_mmx],2 + jne L_update_next_in + + + + mov ebx,ebp + +L_update_next_in: +; 1266 "inffast.S" + mov eax, [esp+88] + mov ecx,ebx + mov edx, [eax+28] + shr ecx,3 + sub esi,ecx + shl ecx,3 + sub ebx,ecx + mov [eax+12],edi + mov [edx+bits_state],ebx + mov ecx,ebx + + lea ebx, [esp+28] + cmp [esp+20],ebx + jne L_buf_not_used + + sub esi,ebx + mov ebx, [eax+0] + mov [esp+20],ebx + add esi,ebx + mov ebx, [eax+4] + sub ebx,11 + add [esp+20],ebx + +L_buf_not_used: + mov [eax+0],esi + + mov ebx,1 + shl ebx,cl + dec ebx + + + + + + cmp dword ptr [inflate_fast_use_mmx],2 + jne L_update_hold + + + + psrlq mm0,mm1 + movd ebp,mm0 + + emms + +L_update_hold: + + + + and ebp,ebx + mov [edx+hold_state],ebp + + + + + mov ebx, [esp+20] + cmp ebx,esi + jbe L_last_is_smaller + + sub ebx,esi + add ebx,11 + mov [eax+4],ebx + jmp L_fixup_out +L_last_is_smaller: + sub esi,ebx + neg esi + add esi,11 + mov [eax+4],esi + + + + +L_fixup_out: + + mov ebx, [esp+16] + cmp ebx,edi + jbe L_end_is_smaller + + sub ebx,edi + add ebx,257 + mov [eax+16],ebx + jmp L_done +L_end_is_smaller: + sub edi,ebx + neg edi + add edi,257 + mov [eax+16],edi + + + + + +L_done: + add esp,64 + popfd + pop ebx + pop ebp + pop esi + pop edi + ret +_inflate_fast endp + +_TEXT ends +end ADDED compat/zlib/contrib/masmx86/match686.asm Index: compat/zlib/contrib/masmx86/match686.asm ================================================================== --- /dev/null +++ compat/zlib/contrib/masmx86/match686.asm @@ -0,0 +1,479 @@ +; match686.asm -- Asm portion of the optimized longest_match for 32 bits x86 +; Copyright (C) 1995-1996 Jean-loup Gailly, Brian Raiter and Gilles Vollant. +; File written by Gilles Vollant, by converting match686.S from Brian Raiter +; for MASM. This is as assembly version of longest_match +; from Jean-loup Gailly in deflate.c +; +; http://www.zlib.net +; http://www.winimage.com/zLibDll +; http://www.muppetlabs.com/~breadbox/software/assembly.html +; +; For Visual C++ 4.x and higher and ML 6.x and higher +; ml.exe is distributed in +; http://www.microsoft.com/downloads/details.aspx?FamilyID=7a1c9da0-0510-44a2-b042-7ef370530c64 +; +; this file contain two implementation of longest_match +; +; this longest_match was written by Brian raiter (1998), optimized for Pentium Pro +; (and the faster known version of match_init on modern Core 2 Duo and AMD Phenom) +; +; for using an assembly version of longest_match, you need define ASMV in project +; +; compile the asm file running +; ml /coff /Zi /c /Flmatch686.lst match686.asm +; and do not include match686.obj in your project +; +; note: contrib of zLib 1.2.3 and earlier contained both a deprecated version for +; Pentium (prior Pentium Pro) and this version for Pentium Pro and modern processor +; with autoselect (with cpu detection code) +; if you want support the old pentium optimization, you can still use these version +; +; this file is not optimized for old pentium, but it compatible with all x86 32 bits +; processor (starting 80386) +; +; +; see below : zlib1222add must be adjuster if you use a zlib version < 1.2.2.2 + +;uInt longest_match(s, cur_match) +; deflate_state *s; +; IPos cur_match; /* current match */ + + NbStack equ 76 + cur_match equ dword ptr[esp+NbStack-0] + str_s equ dword ptr[esp+NbStack-4] +; 5 dword on top (ret,ebp,esi,edi,ebx) + adrret equ dword ptr[esp+NbStack-8] + pushebp equ dword ptr[esp+NbStack-12] + pushedi equ dword ptr[esp+NbStack-16] + pushesi equ dword ptr[esp+NbStack-20] + pushebx equ dword ptr[esp+NbStack-24] + + chain_length equ dword ptr [esp+NbStack-28] + limit equ dword ptr [esp+NbStack-32] + best_len equ dword ptr [esp+NbStack-36] + window equ dword ptr [esp+NbStack-40] + prev equ dword ptr [esp+NbStack-44] + scan_start equ word ptr [esp+NbStack-48] + wmask equ dword ptr [esp+NbStack-52] + match_start_ptr equ dword ptr [esp+NbStack-56] + nice_match equ dword ptr [esp+NbStack-60] + scan equ dword ptr [esp+NbStack-64] + + windowlen equ dword ptr [esp+NbStack-68] + match_start equ dword ptr [esp+NbStack-72] + strend equ dword ptr [esp+NbStack-76] + NbStackAdd equ (NbStack-24) + + .386p + + name gvmatch + .MODEL FLAT + + + +; all the +zlib1222add offsets are due to the addition of fields +; in zlib in the deflate_state structure since the asm code was first written +; (if you compile with zlib 1.0.4 or older, use "zlib1222add equ (-4)"). +; (if you compile with zlib between 1.0.5 and 1.2.2.1, use "zlib1222add equ 0"). +; if you compile with zlib 1.2.2.2 or later , use "zlib1222add equ 8"). + + zlib1222add equ 8 + +; Note : these value are good with a 8 bytes boundary pack structure + dep_chain_length equ 74h+zlib1222add + dep_window equ 30h+zlib1222add + dep_strstart equ 64h+zlib1222add + dep_prev_length equ 70h+zlib1222add + dep_nice_match equ 88h+zlib1222add + dep_w_size equ 24h+zlib1222add + dep_prev equ 38h+zlib1222add + dep_w_mask equ 2ch+zlib1222add + dep_good_match equ 84h+zlib1222add + dep_match_start equ 68h+zlib1222add + dep_lookahead equ 6ch+zlib1222add + + +_TEXT segment + +IFDEF NOUNDERLINE + public longest_match + public match_init +ELSE + public _longest_match + public _match_init +ENDIF + + MAX_MATCH equ 258 + MIN_MATCH equ 3 + MIN_LOOKAHEAD equ (MAX_MATCH+MIN_MATCH+1) + + + +MAX_MATCH equ 258 +MIN_MATCH equ 3 +MIN_LOOKAHEAD equ (MAX_MATCH + MIN_MATCH + 1) +MAX_MATCH_8_ equ ((MAX_MATCH + 7) AND 0FFF0h) + + +;;; stack frame offsets + +chainlenwmask equ esp + 0 ; high word: current chain len + ; low word: s->wmask +window equ esp + 4 ; local copy of s->window +windowbestlen equ esp + 8 ; s->window + bestlen +scanstart equ esp + 16 ; first two bytes of string +scanend equ esp + 12 ; last two bytes of string +scanalign equ esp + 20 ; dword-misalignment of string +nicematch equ esp + 24 ; a good enough match size +bestlen equ esp + 28 ; size of best match so far +scan equ esp + 32 ; ptr to string wanting match + +LocalVarsSize equ 36 +; saved ebx byte esp + 36 +; saved edi byte esp + 40 +; saved esi byte esp + 44 +; saved ebp byte esp + 48 +; return address byte esp + 52 +deflatestate equ esp + 56 ; the function arguments +curmatch equ esp + 60 + +;;; Offsets for fields in the deflate_state structure. These numbers +;;; are calculated from the definition of deflate_state, with the +;;; assumption that the compiler will dword-align the fields. (Thus, +;;; changing the definition of deflate_state could easily cause this +;;; program to crash horribly, without so much as a warning at +;;; compile time. Sigh.) + +dsWSize equ 36+zlib1222add +dsWMask equ 44+zlib1222add +dsWindow equ 48+zlib1222add +dsPrev equ 56+zlib1222add +dsMatchLen equ 88+zlib1222add +dsPrevMatch equ 92+zlib1222add +dsStrStart equ 100+zlib1222add +dsMatchStart equ 104+zlib1222add +dsLookahead equ 108+zlib1222add +dsPrevLen equ 112+zlib1222add +dsMaxChainLen equ 116+zlib1222add +dsGoodMatch equ 132+zlib1222add +dsNiceMatch equ 136+zlib1222add + + +;;; match686.asm -- Pentium-Pro-optimized version of longest_match() +;;; Written for zlib 1.1.2 +;;; Copyright (C) 1998 Brian Raiter +;;; You can look at http://www.muppetlabs.com/~breadbox/software/assembly.html +;;; +;; +;; This software is provided 'as-is', without any express or implied +;; warranty. In no event will the authors be held liable for any damages +;; arising from the use of this software. +;; +;; Permission is granted to anyone to use this software for any purpose, +;; including commercial applications, and to alter it and redistribute it +;; freely, subject to the following restrictions: +;; +;; 1. The origin of this software must not be misrepresented; you must not +;; claim that you wrote the original software. If you use this software +;; in a product, an acknowledgment in the product documentation would be +;; appreciated but is not required. +;; 2. Altered source versions must be plainly marked as such, and must not be +;; misrepresented as being the original software +;; 3. This notice may not be removed or altered from any source distribution. +;; + +;GLOBAL _longest_match, _match_init + + +;SECTION .text + +;;; uInt longest_match(deflate_state *deflatestate, IPos curmatch) + +;_longest_match: + IFDEF NOUNDERLINE + longest_match proc near + ELSE + _longest_match proc near + ENDIF +.FPO (9, 4, 0, 0, 1, 0) + +;;; Save registers that the compiler may be using, and adjust esp to +;;; make room for our stack frame. + + push ebp + push edi + push esi + push ebx + sub esp, LocalVarsSize + +;;; Retrieve the function arguments. ecx will hold cur_match +;;; throughout the entire function. edx will hold the pointer to the +;;; deflate_state structure during the function's setup (before +;;; entering the main loop. + + mov edx, [deflatestate] + mov ecx, [curmatch] + +;;; uInt wmask = s->w_mask; +;;; unsigned chain_length = s->max_chain_length; +;;; if (s->prev_length >= s->good_match) { +;;; chain_length >>= 2; +;;; } + + mov eax, [edx + dsPrevLen] + mov ebx, [edx + dsGoodMatch] + cmp eax, ebx + mov eax, [edx + dsWMask] + mov ebx, [edx + dsMaxChainLen] + jl LastMatchGood + shr ebx, 2 +LastMatchGood: + +;;; chainlen is decremented once beforehand so that the function can +;;; use the sign flag instead of the zero flag for the exit test. +;;; It is then shifted into the high word, to make room for the wmask +;;; value, which it will always accompany. + + dec ebx + shl ebx, 16 + or ebx, eax + mov [chainlenwmask], ebx + +;;; if ((uInt)nice_match > s->lookahead) nice_match = s->lookahead; + + mov eax, [edx + dsNiceMatch] + mov ebx, [edx + dsLookahead] + cmp ebx, eax + jl LookaheadLess + mov ebx, eax +LookaheadLess: mov [nicematch], ebx + +;;; register Bytef *scan = s->window + s->strstart; + + mov esi, [edx + dsWindow] + mov [window], esi + mov ebp, [edx + dsStrStart] + lea edi, [esi + ebp] + mov [scan], edi + +;;; Determine how many bytes the scan ptr is off from being +;;; dword-aligned. + + mov eax, edi + neg eax + and eax, 3 + mov [scanalign], eax + +;;; IPos limit = s->strstart > (IPos)MAX_DIST(s) ? +;;; s->strstart - (IPos)MAX_DIST(s) : NIL; + + mov eax, [edx + dsWSize] + sub eax, MIN_LOOKAHEAD + sub ebp, eax + jg LimitPositive + xor ebp, ebp +LimitPositive: + +;;; int best_len = s->prev_length; + + mov eax, [edx + dsPrevLen] + mov [bestlen], eax + +;;; Store the sum of s->window + best_len in esi locally, and in esi. + + add esi, eax + mov [windowbestlen], esi + +;;; register ush scan_start = *(ushf*)scan; +;;; register ush scan_end = *(ushf*)(scan+best_len-1); +;;; Posf *prev = s->prev; + + movzx ebx, word ptr [edi] + mov [scanstart], ebx + movzx ebx, word ptr [edi + eax - 1] + mov [scanend], ebx + mov edi, [edx + dsPrev] + +;;; Jump into the main loop. + + mov edx, [chainlenwmask] + jmp short LoopEntry + +align 4 + +;;; do { +;;; match = s->window + cur_match; +;;; if (*(ushf*)(match+best_len-1) != scan_end || +;;; *(ushf*)match != scan_start) continue; +;;; [...] +;;; } while ((cur_match = prev[cur_match & wmask]) > limit +;;; && --chain_length != 0); +;;; +;;; Here is the inner loop of the function. The function will spend the +;;; majority of its time in this loop, and majority of that time will +;;; be spent in the first ten instructions. +;;; +;;; Within this loop: +;;; ebx = scanend +;;; ecx = curmatch +;;; edx = chainlenwmask - i.e., ((chainlen << 16) | wmask) +;;; esi = windowbestlen - i.e., (window + bestlen) +;;; edi = prev +;;; ebp = limit + +LookupLoop: + and ecx, edx + movzx ecx, word ptr [edi + ecx*2] + cmp ecx, ebp + jbe LeaveNow + sub edx, 00010000h + js LeaveNow +LoopEntry: movzx eax, word ptr [esi + ecx - 1] + cmp eax, ebx + jnz LookupLoop + mov eax, [window] + movzx eax, word ptr [eax + ecx] + cmp eax, [scanstart] + jnz LookupLoop + +;;; Store the current value of chainlen. + + mov [chainlenwmask], edx + +;;; Point edi to the string under scrutiny, and esi to the string we +;;; are hoping to match it up with. In actuality, esi and edi are +;;; both pointed (MAX_MATCH_8 - scanalign) bytes ahead, and edx is +;;; initialized to -(MAX_MATCH_8 - scanalign). + + mov esi, [window] + mov edi, [scan] + add esi, ecx + mov eax, [scanalign] + mov edx, 0fffffef8h; -(MAX_MATCH_8) + lea edi, [edi + eax + 0108h] ;MAX_MATCH_8] + lea esi, [esi + eax + 0108h] ;MAX_MATCH_8] + +;;; Test the strings for equality, 8 bytes at a time. At the end, +;;; adjust edx so that it is offset to the exact byte that mismatched. +;;; +;;; We already know at this point that the first three bytes of the +;;; strings match each other, and they can be safely passed over before +;;; starting the compare loop. So what this code does is skip over 0-3 +;;; bytes, as much as necessary in order to dword-align the edi +;;; pointer. (esi will still be misaligned three times out of four.) +;;; +;;; It should be confessed that this loop usually does not represent +;;; much of the total running time. Replacing it with a more +;;; straightforward "rep cmpsb" would not drastically degrade +;;; performance. + +LoopCmps: + mov eax, [esi + edx] + xor eax, [edi + edx] + jnz LeaveLoopCmps + mov eax, [esi + edx + 4] + xor eax, [edi + edx + 4] + jnz LeaveLoopCmps4 + add edx, 8 + jnz LoopCmps + jmp short LenMaximum +LeaveLoopCmps4: add edx, 4 +LeaveLoopCmps: test eax, 0000FFFFh + jnz LenLower + add edx, 2 + shr eax, 16 +LenLower: sub al, 1 + adc edx, 0 + +;;; Calculate the length of the match. If it is longer than MAX_MATCH, +;;; then automatically accept it as the best possible match and leave. + + lea eax, [edi + edx] + mov edi, [scan] + sub eax, edi + cmp eax, MAX_MATCH + jge LenMaximum + +;;; If the length of the match is not longer than the best match we +;;; have so far, then forget it and return to the lookup loop. + + mov edx, [deflatestate] + mov ebx, [bestlen] + cmp eax, ebx + jg LongerMatch + mov esi, [windowbestlen] + mov edi, [edx + dsPrev] + mov ebx, [scanend] + mov edx, [chainlenwmask] + jmp LookupLoop + +;;; s->match_start = cur_match; +;;; best_len = len; +;;; if (len >= nice_match) break; +;;; scan_end = *(ushf*)(scan+best_len-1); + +LongerMatch: mov ebx, [nicematch] + mov [bestlen], eax + mov [edx + dsMatchStart], ecx + cmp eax, ebx + jge LeaveNow + mov esi, [window] + add esi, eax + mov [windowbestlen], esi + movzx ebx, word ptr [edi + eax - 1] + mov edi, [edx + dsPrev] + mov [scanend], ebx + mov edx, [chainlenwmask] + jmp LookupLoop + +;;; Accept the current string, with the maximum possible length. + +LenMaximum: mov edx, [deflatestate] + mov dword ptr [bestlen], MAX_MATCH + mov [edx + dsMatchStart], ecx + +;;; if ((uInt)best_len <= s->lookahead) return (uInt)best_len; +;;; return s->lookahead; + +LeaveNow: + mov edx, [deflatestate] + mov ebx, [bestlen] + mov eax, [edx + dsLookahead] + cmp ebx, eax + jg LookaheadRet + mov eax, ebx +LookaheadRet: + +;;; Restore the stack and return from whence we came. + + add esp, LocalVarsSize + pop ebx + pop esi + pop edi + pop ebp + + ret +; please don't remove this string ! +; Your can freely use match686 in any free or commercial app if you don't remove the string in the binary! + db 0dh,0ah,"asm686 with masm, optimised assembly code from Brian Raiter, written 1998",0dh,0ah + + + IFDEF NOUNDERLINE + longest_match endp + ELSE + _longest_match endp + ENDIF + + IFDEF NOUNDERLINE + match_init proc near + ret + match_init endp + ELSE + _match_init proc near + ret + _match_init endp + ENDIF + + +_TEXT ends +end ADDED compat/zlib/contrib/masmx86/readme.txt Index: compat/zlib/contrib/masmx86/readme.txt ================================================================== --- /dev/null +++ compat/zlib/contrib/masmx86/readme.txt @@ -0,0 +1,27 @@ + +Summary +------- +This directory contains ASM implementations of the functions +longest_match() and inflate_fast(). + + +Use instructions +---------------- +Assemble using MASM, and copy the object files into the zlib source +directory, then run the appropriate makefile, as suggested below. You can +donwload MASM from here: + + http://www.microsoft.com/downloads/details.aspx?displaylang=en&FamilyID=7a1c9da0-0510-44a2-b042-7ef370530c64 + +You can also get objects files here: + + http://www.winimage.com/zLibDll/zlib124_masm_obj.zip + +Build instructions +------------------ +* With Microsoft C and MASM: +nmake -f win32/Makefile.msc LOC="-DASMV -DASMINF" OBJA="match686.obj inffas32.obj" + +* With Borland C and TASM: +make -f win32/Makefile.bor LOCAL_ZLIB="-DASMV -DASMINF" OBJA="match686.obj inffas32.obj" OBJPA="+match686c.obj+match686.obj+inffas32.obj" + ADDED compat/zlib/contrib/minizip/Makefile Index: compat/zlib/contrib/minizip/Makefile ================================================================== --- /dev/null +++ compat/zlib/contrib/minizip/Makefile @@ -0,0 +1,25 @@ +CC=cc +CFLAGS=-O -I../.. + +UNZ_OBJS = miniunz.o unzip.o ioapi.o ../../libz.a +ZIP_OBJS = minizip.o zip.o ioapi.o ../../libz.a + +.c.o: + $(CC) -c $(CFLAGS) $*.c + +all: miniunz minizip + +miniunz: $(UNZ_OBJS) + $(CC) $(CFLAGS) -o $@ $(UNZ_OBJS) + +minizip: $(ZIP_OBJS) + $(CC) $(CFLAGS) -o $@ $(ZIP_OBJS) + +test: miniunz minizip + ./minizip test readme.txt + ./miniunz -l test.zip + mv readme.txt readme.old + ./miniunz test.zip + +clean: + /bin/rm -f *.o *~ minizip miniunz ADDED compat/zlib/contrib/minizip/Makefile.am Index: compat/zlib/contrib/minizip/Makefile.am ================================================================== --- /dev/null +++ compat/zlib/contrib/minizip/Makefile.am @@ -0,0 +1,45 @@ +lib_LTLIBRARIES = libminizip.la + +if COND_DEMOS +bin_PROGRAMS = miniunzip minizip +endif + +zlib_top_srcdir = $(top_srcdir)/../.. +zlib_top_builddir = $(top_builddir)/../.. + +AM_CPPFLAGS = -I$(zlib_top_srcdir) +AM_LDFLAGS = -L$(zlib_top_builddir) + +if WIN32 +iowin32_src = iowin32.c +iowin32_h = iowin32.h +endif + +libminizip_la_SOURCES = \ + ioapi.c \ + mztools.c \ + unzip.c \ + zip.c \ + ${iowin32_src} + +libminizip_la_LDFLAGS = $(AM_LDFLAGS) -version-info 1:0:0 -lz + +minizip_includedir = $(includedir)/minizip +minizip_include_HEADERS = \ + crypt.h \ + ioapi.h \ + mztools.h \ + unzip.h \ + zip.h \ + ${iowin32_h} + +pkgconfigdir = $(libdir)/pkgconfig +pkgconfig_DATA = minizip.pc + +EXTRA_PROGRAMS = miniunzip minizip + +miniunzip_SOURCES = miniunz.c +miniunzip_LDADD = libminizip.la + +minizip_SOURCES = minizip.c +minizip_LDADD = libminizip.la -lz ADDED compat/zlib/contrib/minizip/MiniZip64_Changes.txt Index: compat/zlib/contrib/minizip/MiniZip64_Changes.txt ================================================================== --- /dev/null +++ compat/zlib/contrib/minizip/MiniZip64_Changes.txt @@ -0,0 +1,6 @@ + +MiniZip 1.1 was derrived from MiniZip at version 1.01f + +Change in 1.0 (Okt 2009) + - **TODO - Add history** + ADDED compat/zlib/contrib/minizip/MiniZip64_info.txt Index: compat/zlib/contrib/minizip/MiniZip64_info.txt ================================================================== --- /dev/null +++ compat/zlib/contrib/minizip/MiniZip64_info.txt @@ -0,0 +1,74 @@ +MiniZip - Copyright (c) 1998-2010 - by Gilles Vollant - version 1.1 64 bits from Mathias Svensson + +Introduction +--------------------- +MiniZip 1.1 is built from MiniZip 1.0 by Gilles Vollant ( http://www.winimage.com/zLibDll/minizip.html ) + +When adding ZIP64 support into minizip it would result into risk of breaking compatibility with minizip 1.0. +All possible work was done for compatibility. + + +Background +--------------------- +When adding ZIP64 support Mathias Svensson found that Even Rouault have added ZIP64 +support for unzip.c into minizip for a open source project called gdal ( http://www.gdal.org/ ) + +That was used as a starting point. And after that ZIP64 support was added to zip.c +some refactoring and code cleanup was also done. + + +Changed from MiniZip 1.0 to MiniZip 1.1 +--------------------------------------- +* Added ZIP64 support for unzip ( by Even Rouault ) +* Added ZIP64 support for zip ( by Mathias Svensson ) +* Reverted some changed that Even Rouault did. +* Bunch of patches received from Gulles Vollant that he received for MiniZip from various users. +* Added unzip patch for BZIP Compression method (patch create by Daniel Borca) +* Added BZIP Compress method for zip +* Did some refactoring and code cleanup + + +Credits + + Gilles Vollant - Original MiniZip author + Even Rouault - ZIP64 unzip Support + Daniel Borca - BZip Compression method support in unzip + Mathias Svensson - ZIP64 zip support + Mathias Svensson - BZip Compression method support in zip + + Resources + + ZipLayout http://result42.com/projects/ZipFileLayout + Command line tool for Windows that shows the layout and information of the headers in a zip archive. + Used when debugging and validating the creation of zip files using MiniZip64 + + + ZIP App Note http://www.pkware.com/documents/casestudies/APPNOTE.TXT + Zip File specification + + +Notes. + * To be able to use BZip compression method in zip64.c or unzip64.c the BZIP2 lib is needed and HAVE_BZIP2 need to be defined. + +License +---------------------------------------------------------- + Condition of use and distribution are the same than zlib : + + This software is provided 'as-is', without any express or implied + warranty. In no event will the authors be held liable for any damages + arising from the use of this software. + + Permission is granted to anyone to use this software for any purpose, + including commercial applications, and to alter it and redistribute it + freely, subject to the following restrictions: + + 1. The origin of this software must not be misrepresented; you must not + claim that you wrote the original software. If you use this software + in a product, an acknowledgment in the product documentation would be + appreciated but is not required. + 2. Altered source versions must be plainly marked as such, and must not be + misrepresented as being the original software. + 3. This notice may not be removed or altered from any source distribution. + +---------------------------------------------------------- + ADDED compat/zlib/contrib/minizip/configure.ac Index: compat/zlib/contrib/minizip/configure.ac ================================================================== --- /dev/null +++ compat/zlib/contrib/minizip/configure.ac @@ -0,0 +1,32 @@ +# -*- Autoconf -*- +# Process this file with autoconf to produce a configure script. + +AC_INIT([minizip], [1.2.11], [bugzilla.redhat.com]) +AC_CONFIG_SRCDIR([minizip.c]) +AM_INIT_AUTOMAKE([foreign]) +LT_INIT + +AC_MSG_CHECKING([whether to build example programs]) +AC_ARG_ENABLE([demos], AC_HELP_STRING([--enable-demos], [build example programs])) +AM_CONDITIONAL([COND_DEMOS], [test "$enable_demos" = yes]) +if test "$enable_demos" = yes +then + AC_MSG_RESULT([yes]) +else + AC_MSG_RESULT([no]) +fi + +case "${host}" in + *-mingw* | mingw*) + WIN32="yes" + ;; + *) + ;; +esac +AM_CONDITIONAL([WIN32], [test "${WIN32}" = "yes"]) + + +AC_SUBST([HAVE_UNISTD_H], [0]) +AC_CHECK_HEADER([unistd.h], [HAVE_UNISTD_H=1], []) +AC_CONFIG_FILES([Makefile minizip.pc]) +AC_OUTPUT ADDED compat/zlib/contrib/minizip/crypt.h Index: compat/zlib/contrib/minizip/crypt.h ================================================================== --- /dev/null +++ compat/zlib/contrib/minizip/crypt.h @@ -0,0 +1,131 @@ +/* crypt.h -- base code for crypt/uncrypt ZIPfile + + + Version 1.01e, February 12th, 2005 + + Copyright (C) 1998-2005 Gilles Vollant + + This code is a modified version of crypting code in Infozip distribution + + The encryption/decryption parts of this source code (as opposed to the + non-echoing password parts) were originally written in Europe. The + whole source package can be freely distributed, including from the USA. + (Prior to January 2000, re-export from the US was a violation of US law.) + + This encryption code is a direct transcription of the algorithm from + Roger Schlafly, described by Phil Katz in the file appnote.txt. This + file (appnote.txt) is distributed with the PKZIP program (even in the + version without encryption capabilities). + + If you don't need crypting in your application, just define symbols + NOCRYPT and NOUNCRYPT. + + This code support the "Traditional PKWARE Encryption". + + The new AES encryption added on Zip format by Winzip (see the page + http://www.winzip.com/aes_info.htm ) and PKWare PKZip 5.x Strong + Encryption is not supported. +*/ + +#define CRC32(c, b) ((*(pcrc_32_tab+(((int)(c) ^ (b)) & 0xff))) ^ ((c) >> 8)) + +/*********************************************************************** + * Return the next byte in the pseudo-random sequence + */ +static int decrypt_byte(unsigned long* pkeys, const z_crc_t* pcrc_32_tab) +{ + unsigned temp; /* POTENTIAL BUG: temp*(temp^1) may overflow in an + * unpredictable manner on 16-bit systems; not a problem + * with any known compiler so far, though */ + + temp = ((unsigned)(*(pkeys+2)) & 0xffff) | 2; + return (int)(((temp * (temp ^ 1)) >> 8) & 0xff); +} + +/*********************************************************************** + * Update the encryption keys with the next byte of plain text + */ +static int update_keys(unsigned long* pkeys,const z_crc_t* pcrc_32_tab,int c) +{ + (*(pkeys+0)) = CRC32((*(pkeys+0)), c); + (*(pkeys+1)) += (*(pkeys+0)) & 0xff; + (*(pkeys+1)) = (*(pkeys+1)) * 134775813L + 1; + { + register int keyshift = (int)((*(pkeys+1)) >> 24); + (*(pkeys+2)) = CRC32((*(pkeys+2)), keyshift); + } + return c; +} + + +/*********************************************************************** + * Initialize the encryption keys and the random header according to + * the given password. + */ +static void init_keys(const char* passwd,unsigned long* pkeys,const z_crc_t* pcrc_32_tab) +{ + *(pkeys+0) = 305419896L; + *(pkeys+1) = 591751049L; + *(pkeys+2) = 878082192L; + while (*passwd != '\0') { + update_keys(pkeys,pcrc_32_tab,(int)*passwd); + passwd++; + } +} + +#define zdecode(pkeys,pcrc_32_tab,c) \ + (update_keys(pkeys,pcrc_32_tab,c ^= decrypt_byte(pkeys,pcrc_32_tab))) + +#define zencode(pkeys,pcrc_32_tab,c,t) \ + (t=decrypt_byte(pkeys,pcrc_32_tab), update_keys(pkeys,pcrc_32_tab,c), t^(c)) + +#ifdef INCLUDECRYPTINGCODE_IFCRYPTALLOWED + +#define RAND_HEAD_LEN 12 + /* "last resort" source for second part of crypt seed pattern */ +# ifndef ZCR_SEED2 +# define ZCR_SEED2 3141592654UL /* use PI as default pattern */ +# endif + +static int crypthead(const char* passwd, /* password string */ + unsigned char* buf, /* where to write header */ + int bufSize, + unsigned long* pkeys, + const z_crc_t* pcrc_32_tab, + unsigned long crcForCrypting) +{ + int n; /* index in random header */ + int t; /* temporary */ + int c; /* random byte */ + unsigned char header[RAND_HEAD_LEN-2]; /* random header */ + static unsigned calls = 0; /* ensure different random header each time */ + + if (bufSize> 7) & 0xff; + header[n] = (unsigned char)zencode(pkeys, pcrc_32_tab, c, t); + } + /* Encrypt random header (last two bytes is high word of crc) */ + init_keys(passwd, pkeys, pcrc_32_tab); + for (n = 0; n < RAND_HEAD_LEN-2; n++) + { + buf[n] = (unsigned char)zencode(pkeys, pcrc_32_tab, header[n], t); + } + buf[n++] = (unsigned char)zencode(pkeys, pcrc_32_tab, (int)(crcForCrypting >> 16) & 0xff, t); + buf[n++] = (unsigned char)zencode(pkeys, pcrc_32_tab, (int)(crcForCrypting >> 24) & 0xff, t); + return n; +} + +#endif ADDED compat/zlib/contrib/minizip/ioapi.c Index: compat/zlib/contrib/minizip/ioapi.c ================================================================== --- /dev/null +++ compat/zlib/contrib/minizip/ioapi.c @@ -0,0 +1,247 @@ +/* ioapi.h -- IO base function header for compress/uncompress .zip + part of the MiniZip project - ( http://www.winimage.com/zLibDll/minizip.html ) + + Copyright (C) 1998-2010 Gilles Vollant (minizip) ( http://www.winimage.com/zLibDll/minizip.html ) + + Modifications for Zip64 support + Copyright (C) 2009-2010 Mathias Svensson ( http://result42.com ) + + For more info read MiniZip_info.txt + +*/ + +#if defined(_WIN32) && (!(defined(_CRT_SECURE_NO_WARNINGS))) + #define _CRT_SECURE_NO_WARNINGS +#endif + +#if defined(__APPLE__) || defined(IOAPI_NO_64) +// In darwin and perhaps other BSD variants off_t is a 64 bit value, hence no need for specific 64 bit functions +#define FOPEN_FUNC(filename, mode) fopen(filename, mode) +#define FTELLO_FUNC(stream) ftello(stream) +#define FSEEKO_FUNC(stream, offset, origin) fseeko(stream, offset, origin) +#else +#define FOPEN_FUNC(filename, mode) fopen64(filename, mode) +#define FTELLO_FUNC(stream) ftello64(stream) +#define FSEEKO_FUNC(stream, offset, origin) fseeko64(stream, offset, origin) +#endif + + +#include "ioapi.h" + +voidpf call_zopen64 (const zlib_filefunc64_32_def* pfilefunc,const void*filename,int mode) +{ + if (pfilefunc->zfile_func64.zopen64_file != NULL) + return (*(pfilefunc->zfile_func64.zopen64_file)) (pfilefunc->zfile_func64.opaque,filename,mode); + else + { + return (*(pfilefunc->zopen32_file))(pfilefunc->zfile_func64.opaque,(const char*)filename,mode); + } +} + +long call_zseek64 (const zlib_filefunc64_32_def* pfilefunc,voidpf filestream, ZPOS64_T offset, int origin) +{ + if (pfilefunc->zfile_func64.zseek64_file != NULL) + return (*(pfilefunc->zfile_func64.zseek64_file)) (pfilefunc->zfile_func64.opaque,filestream,offset,origin); + else + { + uLong offsetTruncated = (uLong)offset; + if (offsetTruncated != offset) + return -1; + else + return (*(pfilefunc->zseek32_file))(pfilefunc->zfile_func64.opaque,filestream,offsetTruncated,origin); + } +} + +ZPOS64_T call_ztell64 (const zlib_filefunc64_32_def* pfilefunc,voidpf filestream) +{ + if (pfilefunc->zfile_func64.zseek64_file != NULL) + return (*(pfilefunc->zfile_func64.ztell64_file)) (pfilefunc->zfile_func64.opaque,filestream); + else + { + uLong tell_uLong = (*(pfilefunc->ztell32_file))(pfilefunc->zfile_func64.opaque,filestream); + if ((tell_uLong) == MAXU32) + return (ZPOS64_T)-1; + else + return tell_uLong; + } +} + +void fill_zlib_filefunc64_32_def_from_filefunc32(zlib_filefunc64_32_def* p_filefunc64_32,const zlib_filefunc_def* p_filefunc32) +{ + p_filefunc64_32->zfile_func64.zopen64_file = NULL; + p_filefunc64_32->zopen32_file = p_filefunc32->zopen_file; + p_filefunc64_32->zfile_func64.zerror_file = p_filefunc32->zerror_file; + p_filefunc64_32->zfile_func64.zread_file = p_filefunc32->zread_file; + p_filefunc64_32->zfile_func64.zwrite_file = p_filefunc32->zwrite_file; + p_filefunc64_32->zfile_func64.ztell64_file = NULL; + p_filefunc64_32->zfile_func64.zseek64_file = NULL; + p_filefunc64_32->zfile_func64.zclose_file = p_filefunc32->zclose_file; + p_filefunc64_32->zfile_func64.zerror_file = p_filefunc32->zerror_file; + p_filefunc64_32->zfile_func64.opaque = p_filefunc32->opaque; + p_filefunc64_32->zseek32_file = p_filefunc32->zseek_file; + p_filefunc64_32->ztell32_file = p_filefunc32->ztell_file; +} + + + +static voidpf ZCALLBACK fopen_file_func OF((voidpf opaque, const char* filename, int mode)); +static uLong ZCALLBACK fread_file_func OF((voidpf opaque, voidpf stream, void* buf, uLong size)); +static uLong ZCALLBACK fwrite_file_func OF((voidpf opaque, voidpf stream, const void* buf,uLong size)); +static ZPOS64_T ZCALLBACK ftell64_file_func OF((voidpf opaque, voidpf stream)); +static long ZCALLBACK fseek64_file_func OF((voidpf opaque, voidpf stream, ZPOS64_T offset, int origin)); +static int ZCALLBACK fclose_file_func OF((voidpf opaque, voidpf stream)); +static int ZCALLBACK ferror_file_func OF((voidpf opaque, voidpf stream)); + +static voidpf ZCALLBACK fopen_file_func (voidpf opaque, const char* filename, int mode) +{ + FILE* file = NULL; + const char* mode_fopen = NULL; + if ((mode & ZLIB_FILEFUNC_MODE_READWRITEFILTER)==ZLIB_FILEFUNC_MODE_READ) + mode_fopen = "rb"; + else + if (mode & ZLIB_FILEFUNC_MODE_EXISTING) + mode_fopen = "r+b"; + else + if (mode & ZLIB_FILEFUNC_MODE_CREATE) + mode_fopen = "wb"; + + if ((filename!=NULL) && (mode_fopen != NULL)) + file = fopen(filename, mode_fopen); + return file; +} + +static voidpf ZCALLBACK fopen64_file_func (voidpf opaque, const void* filename, int mode) +{ + FILE* file = NULL; + const char* mode_fopen = NULL; + if ((mode & ZLIB_FILEFUNC_MODE_READWRITEFILTER)==ZLIB_FILEFUNC_MODE_READ) + mode_fopen = "rb"; + else + if (mode & ZLIB_FILEFUNC_MODE_EXISTING) + mode_fopen = "r+b"; + else + if (mode & ZLIB_FILEFUNC_MODE_CREATE) + mode_fopen = "wb"; + + if ((filename!=NULL) && (mode_fopen != NULL)) + file = FOPEN_FUNC((const char*)filename, mode_fopen); + return file; +} + + +static uLong ZCALLBACK fread_file_func (voidpf opaque, voidpf stream, void* buf, uLong size) +{ + uLong ret; + ret = (uLong)fread(buf, 1, (size_t)size, (FILE *)stream); + return ret; +} + +static uLong ZCALLBACK fwrite_file_func (voidpf opaque, voidpf stream, const void* buf, uLong size) +{ + uLong ret; + ret = (uLong)fwrite(buf, 1, (size_t)size, (FILE *)stream); + return ret; +} + +static long ZCALLBACK ftell_file_func (voidpf opaque, voidpf stream) +{ + long ret; + ret = ftell((FILE *)stream); + return ret; +} + + +static ZPOS64_T ZCALLBACK ftell64_file_func (voidpf opaque, voidpf stream) +{ + ZPOS64_T ret; + ret = FTELLO_FUNC((FILE *)stream); + return ret; +} + +static long ZCALLBACK fseek_file_func (voidpf opaque, voidpf stream, uLong offset, int origin) +{ + int fseek_origin=0; + long ret; + switch (origin) + { + case ZLIB_FILEFUNC_SEEK_CUR : + fseek_origin = SEEK_CUR; + break; + case ZLIB_FILEFUNC_SEEK_END : + fseek_origin = SEEK_END; + break; + case ZLIB_FILEFUNC_SEEK_SET : + fseek_origin = SEEK_SET; + break; + default: return -1; + } + ret = 0; + if (fseek((FILE *)stream, offset, fseek_origin) != 0) + ret = -1; + return ret; +} + +static long ZCALLBACK fseek64_file_func (voidpf opaque, voidpf stream, ZPOS64_T offset, int origin) +{ + int fseek_origin=0; + long ret; + switch (origin) + { + case ZLIB_FILEFUNC_SEEK_CUR : + fseek_origin = SEEK_CUR; + break; + case ZLIB_FILEFUNC_SEEK_END : + fseek_origin = SEEK_END; + break; + case ZLIB_FILEFUNC_SEEK_SET : + fseek_origin = SEEK_SET; + break; + default: return -1; + } + ret = 0; + + if(FSEEKO_FUNC((FILE *)stream, offset, fseek_origin) != 0) + ret = -1; + + return ret; +} + + +static int ZCALLBACK fclose_file_func (voidpf opaque, voidpf stream) +{ + int ret; + ret = fclose((FILE *)stream); + return ret; +} + +static int ZCALLBACK ferror_file_func (voidpf opaque, voidpf stream) +{ + int ret; + ret = ferror((FILE *)stream); + return ret; +} + +void fill_fopen_filefunc (pzlib_filefunc_def) + zlib_filefunc_def* pzlib_filefunc_def; +{ + pzlib_filefunc_def->zopen_file = fopen_file_func; + pzlib_filefunc_def->zread_file = fread_file_func; + pzlib_filefunc_def->zwrite_file = fwrite_file_func; + pzlib_filefunc_def->ztell_file = ftell_file_func; + pzlib_filefunc_def->zseek_file = fseek_file_func; + pzlib_filefunc_def->zclose_file = fclose_file_func; + pzlib_filefunc_def->zerror_file = ferror_file_func; + pzlib_filefunc_def->opaque = NULL; +} + +void fill_fopen64_filefunc (zlib_filefunc64_def* pzlib_filefunc_def) +{ + pzlib_filefunc_def->zopen64_file = fopen64_file_func; + pzlib_filefunc_def->zread_file = fread_file_func; + pzlib_filefunc_def->zwrite_file = fwrite_file_func; + pzlib_filefunc_def->ztell64_file = ftell64_file_func; + pzlib_filefunc_def->zseek64_file = fseek64_file_func; + pzlib_filefunc_def->zclose_file = fclose_file_func; + pzlib_filefunc_def->zerror_file = ferror_file_func; + pzlib_filefunc_def->opaque = NULL; +} ADDED compat/zlib/contrib/minizip/ioapi.h Index: compat/zlib/contrib/minizip/ioapi.h ================================================================== --- /dev/null +++ compat/zlib/contrib/minizip/ioapi.h @@ -0,0 +1,208 @@ +/* ioapi.h -- IO base function header for compress/uncompress .zip + part of the MiniZip project - ( http://www.winimage.com/zLibDll/minizip.html ) + + Copyright (C) 1998-2010 Gilles Vollant (minizip) ( http://www.winimage.com/zLibDll/minizip.html ) + + Modifications for Zip64 support + Copyright (C) 2009-2010 Mathias Svensson ( http://result42.com ) + + For more info read MiniZip_info.txt + + Changes + + Oct-2009 - Defined ZPOS64_T to fpos_t on windows and u_int64_t on linux. (might need to find a better why for this) + Oct-2009 - Change to fseeko64, ftello64 and fopen64 so large files would work on linux. + More if/def section may be needed to support other platforms + Oct-2009 - Defined fxxxx64 calls to normal fopen/ftell/fseek so they would compile on windows. + (but you should use iowin32.c for windows instead) + +*/ + +#ifndef _ZLIBIOAPI64_H +#define _ZLIBIOAPI64_H + +#if (!defined(_WIN32)) && (!defined(WIN32)) && (!defined(__APPLE__)) + + // Linux needs this to support file operation on files larger then 4+GB + // But might need better if/def to select just the platforms that needs them. + + #ifndef __USE_FILE_OFFSET64 + #define __USE_FILE_OFFSET64 + #endif + #ifndef __USE_LARGEFILE64 + #define __USE_LARGEFILE64 + #endif + #ifndef _LARGEFILE64_SOURCE + #define _LARGEFILE64_SOURCE + #endif + #ifndef _FILE_OFFSET_BIT + #define _FILE_OFFSET_BIT 64 + #endif + +#endif + +#include +#include +#include "zlib.h" + +#if defined(USE_FILE32API) +#define fopen64 fopen +#define ftello64 ftell +#define fseeko64 fseek +#else +#ifdef __FreeBSD__ +#define fopen64 fopen +#define ftello64 ftello +#define fseeko64 fseeko +#endif +#ifdef _MSC_VER + #define fopen64 fopen + #if (_MSC_VER >= 1400) && (!(defined(NO_MSCVER_FILE64_FUNC))) + #define ftello64 _ftelli64 + #define fseeko64 _fseeki64 + #else // old MSC + #define ftello64 ftell + #define fseeko64 fseek + #endif +#endif +#endif + +/* +#ifndef ZPOS64_T + #ifdef _WIN32 + #define ZPOS64_T fpos_t + #else + #include + #define ZPOS64_T uint64_t + #endif +#endif +*/ + +#ifdef HAVE_MINIZIP64_CONF_H +#include "mz64conf.h" +#endif + +/* a type choosen by DEFINE */ +#ifdef HAVE_64BIT_INT_CUSTOM +typedef 64BIT_INT_CUSTOM_TYPE ZPOS64_T; +#else +#ifdef HAS_STDINT_H +#include "stdint.h" +typedef uint64_t ZPOS64_T; +#else + +/* Maximum unsigned 32-bit value used as placeholder for zip64 */ +#define MAXU32 0xffffffff + +#if defined(_MSC_VER) || defined(__BORLANDC__) +typedef unsigned __int64 ZPOS64_T; +#else +typedef unsigned long long int ZPOS64_T; +#endif +#endif +#endif + + + +#ifdef __cplusplus +extern "C" { +#endif + + +#define ZLIB_FILEFUNC_SEEK_CUR (1) +#define ZLIB_FILEFUNC_SEEK_END (2) +#define ZLIB_FILEFUNC_SEEK_SET (0) + +#define ZLIB_FILEFUNC_MODE_READ (1) +#define ZLIB_FILEFUNC_MODE_WRITE (2) +#define ZLIB_FILEFUNC_MODE_READWRITEFILTER (3) + +#define ZLIB_FILEFUNC_MODE_EXISTING (4) +#define ZLIB_FILEFUNC_MODE_CREATE (8) + + +#ifndef ZCALLBACK + #if (defined(WIN32) || defined(_WIN32) || defined (WINDOWS) || defined (_WINDOWS)) && defined(CALLBACK) && defined (USEWINDOWS_CALLBACK) + #define ZCALLBACK CALLBACK + #else + #define ZCALLBACK + #endif +#endif + + + + +typedef voidpf (ZCALLBACK *open_file_func) OF((voidpf opaque, const char* filename, int mode)); +typedef uLong (ZCALLBACK *read_file_func) OF((voidpf opaque, voidpf stream, void* buf, uLong size)); +typedef uLong (ZCALLBACK *write_file_func) OF((voidpf opaque, voidpf stream, const void* buf, uLong size)); +typedef int (ZCALLBACK *close_file_func) OF((voidpf opaque, voidpf stream)); +typedef int (ZCALLBACK *testerror_file_func) OF((voidpf opaque, voidpf stream)); + +typedef long (ZCALLBACK *tell_file_func) OF((voidpf opaque, voidpf stream)); +typedef long (ZCALLBACK *seek_file_func) OF((voidpf opaque, voidpf stream, uLong offset, int origin)); + + +/* here is the "old" 32 bits structure structure */ +typedef struct zlib_filefunc_def_s +{ + open_file_func zopen_file; + read_file_func zread_file; + write_file_func zwrite_file; + tell_file_func ztell_file; + seek_file_func zseek_file; + close_file_func zclose_file; + testerror_file_func zerror_file; + voidpf opaque; +} zlib_filefunc_def; + +typedef ZPOS64_T (ZCALLBACK *tell64_file_func) OF((voidpf opaque, voidpf stream)); +typedef long (ZCALLBACK *seek64_file_func) OF((voidpf opaque, voidpf stream, ZPOS64_T offset, int origin)); +typedef voidpf (ZCALLBACK *open64_file_func) OF((voidpf opaque, const void* filename, int mode)); + +typedef struct zlib_filefunc64_def_s +{ + open64_file_func zopen64_file; + read_file_func zread_file; + write_file_func zwrite_file; + tell64_file_func ztell64_file; + seek64_file_func zseek64_file; + close_file_func zclose_file; + testerror_file_func zerror_file; + voidpf opaque; +} zlib_filefunc64_def; + +void fill_fopen64_filefunc OF((zlib_filefunc64_def* pzlib_filefunc_def)); +void fill_fopen_filefunc OF((zlib_filefunc_def* pzlib_filefunc_def)); + +/* now internal definition, only for zip.c and unzip.h */ +typedef struct zlib_filefunc64_32_def_s +{ + zlib_filefunc64_def zfile_func64; + open_file_func zopen32_file; + tell_file_func ztell32_file; + seek_file_func zseek32_file; +} zlib_filefunc64_32_def; + + +#define ZREAD64(filefunc,filestream,buf,size) ((*((filefunc).zfile_func64.zread_file)) ((filefunc).zfile_func64.opaque,filestream,buf,size)) +#define ZWRITE64(filefunc,filestream,buf,size) ((*((filefunc).zfile_func64.zwrite_file)) ((filefunc).zfile_func64.opaque,filestream,buf,size)) +//#define ZTELL64(filefunc,filestream) ((*((filefunc).ztell64_file)) ((filefunc).opaque,filestream)) +//#define ZSEEK64(filefunc,filestream,pos,mode) ((*((filefunc).zseek64_file)) ((filefunc).opaque,filestream,pos,mode)) +#define ZCLOSE64(filefunc,filestream) ((*((filefunc).zfile_func64.zclose_file)) ((filefunc).zfile_func64.opaque,filestream)) +#define ZERROR64(filefunc,filestream) ((*((filefunc).zfile_func64.zerror_file)) ((filefunc).zfile_func64.opaque,filestream)) + +voidpf call_zopen64 OF((const zlib_filefunc64_32_def* pfilefunc,const void*filename,int mode)); +long call_zseek64 OF((const zlib_filefunc64_32_def* pfilefunc,voidpf filestream, ZPOS64_T offset, int origin)); +ZPOS64_T call_ztell64 OF((const zlib_filefunc64_32_def* pfilefunc,voidpf filestream)); + +void fill_zlib_filefunc64_32_def_from_filefunc32(zlib_filefunc64_32_def* p_filefunc64_32,const zlib_filefunc_def* p_filefunc32); + +#define ZOPEN64(filefunc,filename,mode) (call_zopen64((&(filefunc)),(filename),(mode))) +#define ZTELL64(filefunc,filestream) (call_ztell64((&(filefunc)),(filestream))) +#define ZSEEK64(filefunc,filestream,pos,mode) (call_zseek64((&(filefunc)),(filestream),(pos),(mode))) + +#ifdef __cplusplus +} +#endif + +#endif ADDED compat/zlib/contrib/minizip/iowin32.c Index: compat/zlib/contrib/minizip/iowin32.c ================================================================== --- /dev/null +++ compat/zlib/contrib/minizip/iowin32.c @@ -0,0 +1,462 @@ +/* iowin32.c -- IO base function header for compress/uncompress .zip + Version 1.1, February 14h, 2010 + part of the MiniZip project - ( http://www.winimage.com/zLibDll/minizip.html ) + + Copyright (C) 1998-2010 Gilles Vollant (minizip) ( http://www.winimage.com/zLibDll/minizip.html ) + + Modifications for Zip64 support + Copyright (C) 2009-2010 Mathias Svensson ( http://result42.com ) + + For more info read MiniZip_info.txt + +*/ + +#include + +#include "zlib.h" +#include "ioapi.h" +#include "iowin32.h" + +#ifndef INVALID_HANDLE_VALUE +#define INVALID_HANDLE_VALUE (0xFFFFFFFF) +#endif + +#ifndef INVALID_SET_FILE_POINTER +#define INVALID_SET_FILE_POINTER ((DWORD)-1) +#endif + + +// see Include/shared/winapifamily.h in the Windows Kit +#if defined(WINAPI_FAMILY_PARTITION) && (!(defined(IOWIN32_USING_WINRT_API))) +#if WINAPI_FAMILY_ONE_PARTITION(WINAPI_FAMILY, WINAPI_PARTITION_APP) +#define IOWIN32_USING_WINRT_API 1 +#endif +#endif + +voidpf ZCALLBACK win32_open_file_func OF((voidpf opaque, const char* filename, int mode)); +uLong ZCALLBACK win32_read_file_func OF((voidpf opaque, voidpf stream, void* buf, uLong size)); +uLong ZCALLBACK win32_write_file_func OF((voidpf opaque, voidpf stream, const void* buf, uLong size)); +ZPOS64_T ZCALLBACK win32_tell64_file_func OF((voidpf opaque, voidpf stream)); +long ZCALLBACK win32_seek64_file_func OF((voidpf opaque, voidpf stream, ZPOS64_T offset, int origin)); +int ZCALLBACK win32_close_file_func OF((voidpf opaque, voidpf stream)); +int ZCALLBACK win32_error_file_func OF((voidpf opaque, voidpf stream)); + +typedef struct +{ + HANDLE hf; + int error; +} WIN32FILE_IOWIN; + + +static void win32_translate_open_mode(int mode, + DWORD* lpdwDesiredAccess, + DWORD* lpdwCreationDisposition, + DWORD* lpdwShareMode, + DWORD* lpdwFlagsAndAttributes) +{ + *lpdwDesiredAccess = *lpdwShareMode = *lpdwFlagsAndAttributes = *lpdwCreationDisposition = 0; + + if ((mode & ZLIB_FILEFUNC_MODE_READWRITEFILTER)==ZLIB_FILEFUNC_MODE_READ) + { + *lpdwDesiredAccess = GENERIC_READ; + *lpdwCreationDisposition = OPEN_EXISTING; + *lpdwShareMode = FILE_SHARE_READ; + } + else if (mode & ZLIB_FILEFUNC_MODE_EXISTING) + { + *lpdwDesiredAccess = GENERIC_WRITE | GENERIC_READ; + *lpdwCreationDisposition = OPEN_EXISTING; + } + else if (mode & ZLIB_FILEFUNC_MODE_CREATE) + { + *lpdwDesiredAccess = GENERIC_WRITE | GENERIC_READ; + *lpdwCreationDisposition = CREATE_ALWAYS; + } +} + +static voidpf win32_build_iowin(HANDLE hFile) +{ + voidpf ret=NULL; + + if ((hFile != NULL) && (hFile != INVALID_HANDLE_VALUE)) + { + WIN32FILE_IOWIN w32fiow; + w32fiow.hf = hFile; + w32fiow.error = 0; + ret = malloc(sizeof(WIN32FILE_IOWIN)); + + if (ret==NULL) + CloseHandle(hFile); + else + *((WIN32FILE_IOWIN*)ret) = w32fiow; + } + return ret; +} + +voidpf ZCALLBACK win32_open64_file_func (voidpf opaque,const void* filename,int mode) +{ + const char* mode_fopen = NULL; + DWORD dwDesiredAccess,dwCreationDisposition,dwShareMode,dwFlagsAndAttributes ; + HANDLE hFile = NULL; + + win32_translate_open_mode(mode,&dwDesiredAccess,&dwCreationDisposition,&dwShareMode,&dwFlagsAndAttributes); + +#ifdef IOWIN32_USING_WINRT_API +#ifdef UNICODE + if ((filename!=NULL) && (dwDesiredAccess != 0)) + hFile = CreateFile2((LPCTSTR)filename, dwDesiredAccess, dwShareMode, dwCreationDisposition, NULL); +#else + if ((filename!=NULL) && (dwDesiredAccess != 0)) + { + WCHAR filenameW[FILENAME_MAX + 0x200 + 1]; + MultiByteToWideChar(CP_ACP,0,(const char*)filename,-1,filenameW,FILENAME_MAX + 0x200); + hFile = CreateFile2(filenameW, dwDesiredAccess, dwShareMode, dwCreationDisposition, NULL); + } +#endif +#else + if ((filename!=NULL) && (dwDesiredAccess != 0)) + hFile = CreateFile((LPCTSTR)filename, dwDesiredAccess, dwShareMode, NULL, dwCreationDisposition, dwFlagsAndAttributes, NULL); +#endif + + return win32_build_iowin(hFile); +} + + +voidpf ZCALLBACK win32_open64_file_funcA (voidpf opaque,const void* filename,int mode) +{ + const char* mode_fopen = NULL; + DWORD dwDesiredAccess,dwCreationDisposition,dwShareMode,dwFlagsAndAttributes ; + HANDLE hFile = NULL; + + win32_translate_open_mode(mode,&dwDesiredAccess,&dwCreationDisposition,&dwShareMode,&dwFlagsAndAttributes); + +#ifdef IOWIN32_USING_WINRT_API + if ((filename!=NULL) && (dwDesiredAccess != 0)) + { + WCHAR filenameW[FILENAME_MAX + 0x200 + 1]; + MultiByteToWideChar(CP_ACP,0,(const char*)filename,-1,filenameW,FILENAME_MAX + 0x200); + hFile = CreateFile2(filenameW, dwDesiredAccess, dwShareMode, dwCreationDisposition, NULL); + } +#else + if ((filename!=NULL) && (dwDesiredAccess != 0)) + hFile = CreateFileA((LPCSTR)filename, dwDesiredAccess, dwShareMode, NULL, dwCreationDisposition, dwFlagsAndAttributes, NULL); +#endif + + return win32_build_iowin(hFile); +} + + +voidpf ZCALLBACK win32_open64_file_funcW (voidpf opaque,const void* filename,int mode) +{ + const char* mode_fopen = NULL; + DWORD dwDesiredAccess,dwCreationDisposition,dwShareMode,dwFlagsAndAttributes ; + HANDLE hFile = NULL; + + win32_translate_open_mode(mode,&dwDesiredAccess,&dwCreationDisposition,&dwShareMode,&dwFlagsAndAttributes); + +#ifdef IOWIN32_USING_WINRT_API + if ((filename!=NULL) && (dwDesiredAccess != 0)) + hFile = CreateFile2((LPCWSTR)filename, dwDesiredAccess, dwShareMode, dwCreationDisposition,NULL); +#else + if ((filename!=NULL) && (dwDesiredAccess != 0)) + hFile = CreateFileW((LPCWSTR)filename, dwDesiredAccess, dwShareMode, NULL, dwCreationDisposition, dwFlagsAndAttributes, NULL); +#endif + + return win32_build_iowin(hFile); +} + + +voidpf ZCALLBACK win32_open_file_func (voidpf opaque,const char* filename,int mode) +{ + const char* mode_fopen = NULL; + DWORD dwDesiredAccess,dwCreationDisposition,dwShareMode,dwFlagsAndAttributes ; + HANDLE hFile = NULL; + + win32_translate_open_mode(mode,&dwDesiredAccess,&dwCreationDisposition,&dwShareMode,&dwFlagsAndAttributes); + +#ifdef IOWIN32_USING_WINRT_API +#ifdef UNICODE + if ((filename!=NULL) && (dwDesiredAccess != 0)) + hFile = CreateFile2((LPCTSTR)filename, dwDesiredAccess, dwShareMode, dwCreationDisposition, NULL); +#else + if ((filename!=NULL) && (dwDesiredAccess != 0)) + { + WCHAR filenameW[FILENAME_MAX + 0x200 + 1]; + MultiByteToWideChar(CP_ACP,0,(const char*)filename,-1,filenameW,FILENAME_MAX + 0x200); + hFile = CreateFile2(filenameW, dwDesiredAccess, dwShareMode, dwCreationDisposition, NULL); + } +#endif +#else + if ((filename!=NULL) && (dwDesiredAccess != 0)) + hFile = CreateFile((LPCTSTR)filename, dwDesiredAccess, dwShareMode, NULL, dwCreationDisposition, dwFlagsAndAttributes, NULL); +#endif + + return win32_build_iowin(hFile); +} + + +uLong ZCALLBACK win32_read_file_func (voidpf opaque, voidpf stream, void* buf,uLong size) +{ + uLong ret=0; + HANDLE hFile = NULL; + if (stream!=NULL) + hFile = ((WIN32FILE_IOWIN*)stream) -> hf; + + if (hFile != NULL) + { + if (!ReadFile(hFile, buf, size, &ret, NULL)) + { + DWORD dwErr = GetLastError(); + if (dwErr == ERROR_HANDLE_EOF) + dwErr = 0; + ((WIN32FILE_IOWIN*)stream) -> error=(int)dwErr; + } + } + + return ret; +} + + +uLong ZCALLBACK win32_write_file_func (voidpf opaque,voidpf stream,const void* buf,uLong size) +{ + uLong ret=0; + HANDLE hFile = NULL; + if (stream!=NULL) + hFile = ((WIN32FILE_IOWIN*)stream) -> hf; + + if (hFile != NULL) + { + if (!WriteFile(hFile, buf, size, &ret, NULL)) + { + DWORD dwErr = GetLastError(); + if (dwErr == ERROR_HANDLE_EOF) + dwErr = 0; + ((WIN32FILE_IOWIN*)stream) -> error=(int)dwErr; + } + } + + return ret; +} + +static BOOL MySetFilePointerEx(HANDLE hFile, LARGE_INTEGER pos, LARGE_INTEGER *newPos, DWORD dwMoveMethod) +{ +#ifdef IOWIN32_USING_WINRT_API + return SetFilePointerEx(hFile, pos, newPos, dwMoveMethod); +#else + LONG lHigh = pos.HighPart; + DWORD dwNewPos = SetFilePointer(hFile, pos.LowPart, &lHigh, dwMoveMethod); + BOOL fOk = TRUE; + if (dwNewPos == 0xFFFFFFFF) + if (GetLastError() != NO_ERROR) + fOk = FALSE; + if ((newPos != NULL) && (fOk)) + { + newPos->LowPart = dwNewPos; + newPos->HighPart = lHigh; + } + return fOk; +#endif +} + +long ZCALLBACK win32_tell_file_func (voidpf opaque,voidpf stream) +{ + long ret=-1; + HANDLE hFile = NULL; + if (stream!=NULL) + hFile = ((WIN32FILE_IOWIN*)stream) -> hf; + if (hFile != NULL) + { + LARGE_INTEGER pos; + pos.QuadPart = 0; + + if (!MySetFilePointerEx(hFile, pos, &pos, FILE_CURRENT)) + { + DWORD dwErr = GetLastError(); + ((WIN32FILE_IOWIN*)stream) -> error=(int)dwErr; + ret = -1; + } + else + ret=(long)pos.LowPart; + } + return ret; +} + +ZPOS64_T ZCALLBACK win32_tell64_file_func (voidpf opaque, voidpf stream) +{ + ZPOS64_T ret= (ZPOS64_T)-1; + HANDLE hFile = NULL; + if (stream!=NULL) + hFile = ((WIN32FILE_IOWIN*)stream)->hf; + + if (hFile) + { + LARGE_INTEGER pos; + pos.QuadPart = 0; + + if (!MySetFilePointerEx(hFile, pos, &pos, FILE_CURRENT)) + { + DWORD dwErr = GetLastError(); + ((WIN32FILE_IOWIN*)stream) -> error=(int)dwErr; + ret = (ZPOS64_T)-1; + } + else + ret=pos.QuadPart; + } + return ret; +} + + +long ZCALLBACK win32_seek_file_func (voidpf opaque,voidpf stream,uLong offset,int origin) +{ + DWORD dwMoveMethod=0xFFFFFFFF; + HANDLE hFile = NULL; + + long ret=-1; + if (stream!=NULL) + hFile = ((WIN32FILE_IOWIN*)stream) -> hf; + switch (origin) + { + case ZLIB_FILEFUNC_SEEK_CUR : + dwMoveMethod = FILE_CURRENT; + break; + case ZLIB_FILEFUNC_SEEK_END : + dwMoveMethod = FILE_END; + break; + case ZLIB_FILEFUNC_SEEK_SET : + dwMoveMethod = FILE_BEGIN; + break; + default: return -1; + } + + if (hFile != NULL) + { + LARGE_INTEGER pos; + pos.QuadPart = offset; + if (!MySetFilePointerEx(hFile, pos, NULL, dwMoveMethod)) + { + DWORD dwErr = GetLastError(); + ((WIN32FILE_IOWIN*)stream) -> error=(int)dwErr; + ret = -1; + } + else + ret=0; + } + return ret; +} + +long ZCALLBACK win32_seek64_file_func (voidpf opaque, voidpf stream,ZPOS64_T offset,int origin) +{ + DWORD dwMoveMethod=0xFFFFFFFF; + HANDLE hFile = NULL; + long ret=-1; + + if (stream!=NULL) + hFile = ((WIN32FILE_IOWIN*)stream)->hf; + + switch (origin) + { + case ZLIB_FILEFUNC_SEEK_CUR : + dwMoveMethod = FILE_CURRENT; + break; + case ZLIB_FILEFUNC_SEEK_END : + dwMoveMethod = FILE_END; + break; + case ZLIB_FILEFUNC_SEEK_SET : + dwMoveMethod = FILE_BEGIN; + break; + default: return -1; + } + + if (hFile) + { + LARGE_INTEGER pos; + pos.QuadPart = offset; + if (!MySetFilePointerEx(hFile, pos, NULL, dwMoveMethod)) + { + DWORD dwErr = GetLastError(); + ((WIN32FILE_IOWIN*)stream) -> error=(int)dwErr; + ret = -1; + } + else + ret=0; + } + return ret; +} + +int ZCALLBACK win32_close_file_func (voidpf opaque, voidpf stream) +{ + int ret=-1; + + if (stream!=NULL) + { + HANDLE hFile; + hFile = ((WIN32FILE_IOWIN*)stream) -> hf; + if (hFile != NULL) + { + CloseHandle(hFile); + ret=0; + } + free(stream); + } + return ret; +} + +int ZCALLBACK win32_error_file_func (voidpf opaque,voidpf stream) +{ + int ret=-1; + if (stream!=NULL) + { + ret = ((WIN32FILE_IOWIN*)stream) -> error; + } + return ret; +} + +void fill_win32_filefunc (zlib_filefunc_def* pzlib_filefunc_def) +{ + pzlib_filefunc_def->zopen_file = win32_open_file_func; + pzlib_filefunc_def->zread_file = win32_read_file_func; + pzlib_filefunc_def->zwrite_file = win32_write_file_func; + pzlib_filefunc_def->ztell_file = win32_tell_file_func; + pzlib_filefunc_def->zseek_file = win32_seek_file_func; + pzlib_filefunc_def->zclose_file = win32_close_file_func; + pzlib_filefunc_def->zerror_file = win32_error_file_func; + pzlib_filefunc_def->opaque = NULL; +} + +void fill_win32_filefunc64(zlib_filefunc64_def* pzlib_filefunc_def) +{ + pzlib_filefunc_def->zopen64_file = win32_open64_file_func; + pzlib_filefunc_def->zread_file = win32_read_file_func; + pzlib_filefunc_def->zwrite_file = win32_write_file_func; + pzlib_filefunc_def->ztell64_file = win32_tell64_file_func; + pzlib_filefunc_def->zseek64_file = win32_seek64_file_func; + pzlib_filefunc_def->zclose_file = win32_close_file_func; + pzlib_filefunc_def->zerror_file = win32_error_file_func; + pzlib_filefunc_def->opaque = NULL; +} + + +void fill_win32_filefunc64A(zlib_filefunc64_def* pzlib_filefunc_def) +{ + pzlib_filefunc_def->zopen64_file = win32_open64_file_funcA; + pzlib_filefunc_def->zread_file = win32_read_file_func; + pzlib_filefunc_def->zwrite_file = win32_write_file_func; + pzlib_filefunc_def->ztell64_file = win32_tell64_file_func; + pzlib_filefunc_def->zseek64_file = win32_seek64_file_func; + pzlib_filefunc_def->zclose_file = win32_close_file_func; + pzlib_filefunc_def->zerror_file = win32_error_file_func; + pzlib_filefunc_def->opaque = NULL; +} + + +void fill_win32_filefunc64W(zlib_filefunc64_def* pzlib_filefunc_def) +{ + pzlib_filefunc_def->zopen64_file = win32_open64_file_funcW; + pzlib_filefunc_def->zread_file = win32_read_file_func; + pzlib_filefunc_def->zwrite_file = win32_write_file_func; + pzlib_filefunc_def->ztell64_file = win32_tell64_file_func; + pzlib_filefunc_def->zseek64_file = win32_seek64_file_func; + pzlib_filefunc_def->zclose_file = win32_close_file_func; + pzlib_filefunc_def->zerror_file = win32_error_file_func; + pzlib_filefunc_def->opaque = NULL; +} ADDED compat/zlib/contrib/minizip/iowin32.h Index: compat/zlib/contrib/minizip/iowin32.h ================================================================== --- /dev/null +++ compat/zlib/contrib/minizip/iowin32.h @@ -0,0 +1,28 @@ +/* iowin32.h -- IO base function header for compress/uncompress .zip + Version 1.1, February 14h, 2010 + part of the MiniZip project - ( http://www.winimage.com/zLibDll/minizip.html ) + + Copyright (C) 1998-2010 Gilles Vollant (minizip) ( http://www.winimage.com/zLibDll/minizip.html ) + + Modifications for Zip64 support + Copyright (C) 2009-2010 Mathias Svensson ( http://result42.com ) + + For more info read MiniZip_info.txt + +*/ + +#include + + +#ifdef __cplusplus +extern "C" { +#endif + +void fill_win32_filefunc OF((zlib_filefunc_def* pzlib_filefunc_def)); +void fill_win32_filefunc64 OF((zlib_filefunc64_def* pzlib_filefunc_def)); +void fill_win32_filefunc64A OF((zlib_filefunc64_def* pzlib_filefunc_def)); +void fill_win32_filefunc64W OF((zlib_filefunc64_def* pzlib_filefunc_def)); + +#ifdef __cplusplus +} +#endif ADDED compat/zlib/contrib/minizip/make_vms.com Index: compat/zlib/contrib/minizip/make_vms.com ================================================================== --- /dev/null +++ compat/zlib/contrib/minizip/make_vms.com @@ -0,0 +1,25 @@ +$ if f$search("ioapi.h_orig") .eqs. "" then copy ioapi.h ioapi.h_orig +$ open/write zdef vmsdefs.h +$ copy sys$input: zdef +$ deck +#define unix +#define fill_zlib_filefunc64_32_def_from_filefunc32 fillzffunc64from +#define Write_Zip64EndOfCentralDirectoryLocator Write_Zip64EoDLocator +#define Write_Zip64EndOfCentralDirectoryRecord Write_Zip64EoDRecord +#define Write_EndOfCentralDirectoryRecord Write_EoDRecord +$ eod +$ close zdef +$ copy vmsdefs.h,ioapi.h_orig ioapi.h +$ cc/include=[--]/prefix=all ioapi.c +$ cc/include=[--]/prefix=all miniunz.c +$ cc/include=[--]/prefix=all unzip.c +$ cc/include=[--]/prefix=all minizip.c +$ cc/include=[--]/prefix=all zip.c +$ link miniunz,unzip,ioapi,[--]libz.olb/lib +$ link minizip,zip,ioapi,[--]libz.olb/lib +$ mcr []minizip test minizip_info.txt +$ mcr []miniunz -l test.zip +$ rename minizip_info.txt; minizip_info.txt_old +$ mcr []miniunz test.zip +$ delete test.zip;* +$exit ADDED compat/zlib/contrib/minizip/miniunz.c Index: compat/zlib/contrib/minizip/miniunz.c ================================================================== --- /dev/null +++ compat/zlib/contrib/minizip/miniunz.c @@ -0,0 +1,660 @@ +/* + miniunz.c + Version 1.1, February 14h, 2010 + sample part of the MiniZip project - ( http://www.winimage.com/zLibDll/minizip.html ) + + Copyright (C) 1998-2010 Gilles Vollant (minizip) ( http://www.winimage.com/zLibDll/minizip.html ) + + Modifications of Unzip for Zip64 + Copyright (C) 2007-2008 Even Rouault + + Modifications for Zip64 support on both zip and unzip + Copyright (C) 2009-2010 Mathias Svensson ( http://result42.com ) +*/ + +#if (!defined(_WIN32)) && (!defined(WIN32)) && (!defined(__APPLE__)) + #ifndef __USE_FILE_OFFSET64 + #define __USE_FILE_OFFSET64 + #endif + #ifndef __USE_LARGEFILE64 + #define __USE_LARGEFILE64 + #endif + #ifndef _LARGEFILE64_SOURCE + #define _LARGEFILE64_SOURCE + #endif + #ifndef _FILE_OFFSET_BIT + #define _FILE_OFFSET_BIT 64 + #endif +#endif + +#ifdef __APPLE__ +// In darwin and perhaps other BSD variants off_t is a 64 bit value, hence no need for specific 64 bit functions +#define FOPEN_FUNC(filename, mode) fopen(filename, mode) +#define FTELLO_FUNC(stream) ftello(stream) +#define FSEEKO_FUNC(stream, offset, origin) fseeko(stream, offset, origin) +#else +#define FOPEN_FUNC(filename, mode) fopen64(filename, mode) +#define FTELLO_FUNC(stream) ftello64(stream) +#define FSEEKO_FUNC(stream, offset, origin) fseeko64(stream, offset, origin) +#endif + + +#include +#include +#include +#include +#include +#include + +#ifdef _WIN32 +# include +# include +#else +# include +# include +#endif + + +#include "unzip.h" + +#define CASESENSITIVITY (0) +#define WRITEBUFFERSIZE (8192) +#define MAXFILENAME (256) + +#ifdef _WIN32 +#define USEWIN32IOAPI +#include "iowin32.h" +#endif +/* + mini unzip, demo of unzip package + + usage : + Usage : miniunz [-exvlo] file.zip [file_to_extract] [-d extractdir] + + list the file in the zipfile, and print the content of FILE_ID.ZIP or README.TXT + if it exists +*/ + + +/* change_file_date : change the date/time of a file + filename : the filename of the file where date/time must be modified + dosdate : the new date at the MSDos format (4 bytes) + tmu_date : the SAME new date at the tm_unz format */ +void change_file_date(filename,dosdate,tmu_date) + const char *filename; + uLong dosdate; + tm_unz tmu_date; +{ +#ifdef _WIN32 + HANDLE hFile; + FILETIME ftm,ftLocal,ftCreate,ftLastAcc,ftLastWrite; + + hFile = CreateFileA(filename,GENERIC_READ | GENERIC_WRITE, + 0,NULL,OPEN_EXISTING,0,NULL); + GetFileTime(hFile,&ftCreate,&ftLastAcc,&ftLastWrite); + DosDateTimeToFileTime((WORD)(dosdate>>16),(WORD)dosdate,&ftLocal); + LocalFileTimeToFileTime(&ftLocal,&ftm); + SetFileTime(hFile,&ftm,&ftLastAcc,&ftm); + CloseHandle(hFile); +#else +#ifdef unix || __APPLE__ + struct utimbuf ut; + struct tm newdate; + newdate.tm_sec = tmu_date.tm_sec; + newdate.tm_min=tmu_date.tm_min; + newdate.tm_hour=tmu_date.tm_hour; + newdate.tm_mday=tmu_date.tm_mday; + newdate.tm_mon=tmu_date.tm_mon; + if (tmu_date.tm_year > 1900) + newdate.tm_year=tmu_date.tm_year - 1900; + else + newdate.tm_year=tmu_date.tm_year ; + newdate.tm_isdst=-1; + + ut.actime=ut.modtime=mktime(&newdate); + utime(filename,&ut); +#endif +#endif +} + + +/* mymkdir and change_file_date are not 100 % portable + As I don't know well Unix, I wait feedback for the unix portion */ + +int mymkdir(dirname) + const char* dirname; +{ + int ret=0; +#ifdef _WIN32 + ret = _mkdir(dirname); +#elif unix + ret = mkdir (dirname,0775); +#elif __APPLE__ + ret = mkdir (dirname,0775); +#endif + return ret; +} + +int makedir (newdir) + char *newdir; +{ + char *buffer ; + char *p; + int len = (int)strlen(newdir); + + if (len <= 0) + return 0; + + buffer = (char*)malloc(len+1); + if (buffer==NULL) + { + printf("Error allocating memory\n"); + return UNZ_INTERNALERROR; + } + strcpy(buffer,newdir); + + if (buffer[len-1] == '/') { + buffer[len-1] = '\0'; + } + if (mymkdir(buffer) == 0) + { + free(buffer); + return 1; + } + + p = buffer+1; + while (1) + { + char hold; + + while(*p && *p != '\\' && *p != '/') + p++; + hold = *p; + *p = 0; + if ((mymkdir(buffer) == -1) && (errno == ENOENT)) + { + printf("couldn't create directory %s\n",buffer); + free(buffer); + return 0; + } + if (hold == 0) + break; + *p++ = hold; + } + free(buffer); + return 1; +} + +void do_banner() +{ + printf("MiniUnz 1.01b, demo of zLib + Unz package written by Gilles Vollant\n"); + printf("more info at http://www.winimage.com/zLibDll/unzip.html\n\n"); +} + +void do_help() +{ + printf("Usage : miniunz [-e] [-x] [-v] [-l] [-o] [-p password] file.zip [file_to_extr.] [-d extractdir]\n\n" \ + " -e Extract without pathname (junk paths)\n" \ + " -x Extract with pathname\n" \ + " -v list files\n" \ + " -l list files\n" \ + " -d directory to extract into\n" \ + " -o overwrite files without prompting\n" \ + " -p extract crypted file using password\n\n"); +} + +void Display64BitsSize(ZPOS64_T n, int size_char) +{ + /* to avoid compatibility problem , we do here the conversion */ + char number[21]; + int offset=19; + int pos_string = 19; + number[20]=0; + for (;;) { + number[offset]=(char)((n%10)+'0'); + if (number[offset] != '0') + pos_string=offset; + n/=10; + if (offset==0) + break; + offset--; + } + { + int size_display_string = 19-pos_string; + while (size_char > size_display_string) + { + size_char--; + printf(" "); + } + } + + printf("%s",&number[pos_string]); +} + +int do_list(uf) + unzFile uf; +{ + uLong i; + unz_global_info64 gi; + int err; + + err = unzGetGlobalInfo64(uf,&gi); + if (err!=UNZ_OK) + printf("error %d with zipfile in unzGetGlobalInfo \n",err); + printf(" Length Method Size Ratio Date Time CRC-32 Name\n"); + printf(" ------ ------ ---- ----- ---- ---- ------ ----\n"); + for (i=0;i0) + ratio = (uLong)((file_info.compressed_size*100)/file_info.uncompressed_size); + + /* display a '*' if the file is crypted */ + if ((file_info.flag & 1) != 0) + charCrypt='*'; + + if (file_info.compression_method==0) + string_method="Stored"; + else + if (file_info.compression_method==Z_DEFLATED) + { + uInt iLevel=(uInt)((file_info.flag & 0x6)/2); + if (iLevel==0) + string_method="Defl:N"; + else if (iLevel==1) + string_method="Defl:X"; + else if ((iLevel==2) || (iLevel==3)) + string_method="Defl:F"; /* 2:fast , 3 : extra fast*/ + } + else + if (file_info.compression_method==Z_BZIP2ED) + { + string_method="BZip2 "; + } + else + string_method="Unkn. "; + + Display64BitsSize(file_info.uncompressed_size,7); + printf(" %6s%c",string_method,charCrypt); + Display64BitsSize(file_info.compressed_size,7); + printf(" %3lu%% %2.2lu-%2.2lu-%2.2lu %2.2lu:%2.2lu %8.8lx %s\n", + ratio, + (uLong)file_info.tmu_date.tm_mon + 1, + (uLong)file_info.tmu_date.tm_mday, + (uLong)file_info.tmu_date.tm_year % 100, + (uLong)file_info.tmu_date.tm_hour,(uLong)file_info.tmu_date.tm_min, + (uLong)file_info.crc,filename_inzip); + if ((i+1)='a') && (rep<='z')) + rep -= 0x20; + } + while ((rep!='Y') && (rep!='N') && (rep!='A')); + } + + if (rep == 'N') + skip = 1; + + if (rep == 'A') + *popt_overwrite=1; + } + + if ((skip==0) && (err==UNZ_OK)) + { + fout=FOPEN_FUNC(write_filename,"wb"); + /* some zipfile don't contain directory alone before file */ + if ((fout==NULL) && ((*popt_extract_without_path)==0) && + (filename_withoutpath!=(char*)filename_inzip)) + { + char c=*(filename_withoutpath-1); + *(filename_withoutpath-1)='\0'; + makedir(write_filename); + *(filename_withoutpath-1)=c; + fout=FOPEN_FUNC(write_filename,"wb"); + } + + if (fout==NULL) + { + printf("error opening %s\n",write_filename); + } + } + + if (fout!=NULL) + { + printf(" extracting: %s\n",write_filename); + + do + { + err = unzReadCurrentFile(uf,buf,size_buf); + if (err<0) + { + printf("error %d with zipfile in unzReadCurrentFile\n",err); + break; + } + if (err>0) + if (fwrite(buf,err,1,fout)!=1) + { + printf("error in writing extracted file\n"); + err=UNZ_ERRNO; + break; + } + } + while (err>0); + if (fout) + fclose(fout); + + if (err==0) + change_file_date(write_filename,file_info.dosDate, + file_info.tmu_date); + } + + if (err==UNZ_OK) + { + err = unzCloseCurrentFile (uf); + if (err!=UNZ_OK) + { + printf("error %d with zipfile in unzCloseCurrentFile\n",err); + } + } + else + unzCloseCurrentFile(uf); /* don't lose the error */ + } + + free(buf); + return err; +} + + +int do_extract(uf,opt_extract_without_path,opt_overwrite,password) + unzFile uf; + int opt_extract_without_path; + int opt_overwrite; + const char* password; +{ + uLong i; + unz_global_info64 gi; + int err; + FILE* fout=NULL; + + err = unzGetGlobalInfo64(uf,&gi); + if (err!=UNZ_OK) + printf("error %d with zipfile in unzGetGlobalInfo \n",err); + + for (i=0;i insert n+1 empty lines +.\" for manpage-specific macros, see man(7) +.SH NAME +miniunzip - uncompress and examine ZIP archives +.SH SYNOPSIS +.B miniunzip +.RI [ -exvlo ] +zipfile [ files_to_extract ] [-d tempdir] +.SH DESCRIPTION +.B minizip +is a simple tool which allows the extraction of compressed file +archives in the ZIP format used by the MS-DOS utility PKZIP. It was +written as a demonstration of the +.IR zlib (3) +library and therefore lack many of the features of the +.IR unzip (1) +program. +.SH OPTIONS +A number of options are supported. With the exception of +.BI \-d\ tempdir +these must be supplied before any +other arguments and are: +.TP +.BI \-l\ ,\ \-\-v +List the files in the archive without extracting them. +.TP +.B \-o +Overwrite files without prompting for confirmation. +.TP +.B \-x +Extract files (default). +.PP +The +.I zipfile +argument is the name of the archive to process. The next argument can be used +to specify a single file to extract from the archive. + +Lastly, the following option can be specified at the end of the command-line: +.TP +.BI \-d\ tempdir +Extract the archive in the directory +.I tempdir +rather than the current directory. +.SH SEE ALSO +.BR minizip (1), +.BR zlib (3), +.BR unzip (1). +.SH AUTHOR +This program was written by Gilles Vollant. This manual page was +written by Mark Brown . The -d tempdir option +was added by Dirk Eddelbuettel . ADDED compat/zlib/contrib/minizip/minizip.1 Index: compat/zlib/contrib/minizip/minizip.1 ================================================================== --- /dev/null +++ compat/zlib/contrib/minizip/minizip.1 @@ -0,0 +1,46 @@ +.\" Hey, EMACS: -*- nroff -*- +.TH minizip 1 "May 2, 2001" +.\" Please adjust this date whenever revising the manpage. +.\" +.\" Some roff macros, for reference: +.\" .nh disable hyphenation +.\" .hy enable hyphenation +.\" .ad l left justify +.\" .ad b justify to both left and right margins +.\" .nf disable filling +.\" .fi enable filling +.\" .br insert line break +.\" .sp insert n+1 empty lines +.\" for manpage-specific macros, see man(7) +.SH NAME +minizip - create ZIP archives +.SH SYNOPSIS +.B minizip +.RI [ -o ] +zipfile [ " files" ... ] +.SH DESCRIPTION +.B minizip +is a simple tool which allows the creation of compressed file archives +in the ZIP format used by the MS-DOS utility PKZIP. It was written as +a demonstration of the +.IR zlib (3) +library and therefore lack many of the features of the +.IR zip (1) +program. +.SH OPTIONS +The first argument supplied is the name of the ZIP archive to create or +.RI -o +in which case it is ignored and the second argument treated as the +name of the ZIP file. If the ZIP file already exists it will be +overwritten. +.PP +Subsequent arguments specify a list of files to place in the ZIP +archive. If none are specified then an empty archive will be created. +.SH SEE ALSO +.BR miniunzip (1), +.BR zlib (3), +.BR zip (1). +.SH AUTHOR +This program was written by Gilles Vollant. This manual page was +written by Mark Brown . + ADDED compat/zlib/contrib/minizip/minizip.c Index: compat/zlib/contrib/minizip/minizip.c ================================================================== --- /dev/null +++ compat/zlib/contrib/minizip/minizip.c @@ -0,0 +1,520 @@ +/* + minizip.c + Version 1.1, February 14h, 2010 + sample part of the MiniZip project - ( http://www.winimage.com/zLibDll/minizip.html ) + + Copyright (C) 1998-2010 Gilles Vollant (minizip) ( http://www.winimage.com/zLibDll/minizip.html ) + + Modifications of Unzip for Zip64 + Copyright (C) 2007-2008 Even Rouault + + Modifications for Zip64 support on both zip and unzip + Copyright (C) 2009-2010 Mathias Svensson ( http://result42.com ) +*/ + + +#if (!defined(_WIN32)) && (!defined(WIN32)) && (!defined(__APPLE__)) + #ifndef __USE_FILE_OFFSET64 + #define __USE_FILE_OFFSET64 + #endif + #ifndef __USE_LARGEFILE64 + #define __USE_LARGEFILE64 + #endif + #ifndef _LARGEFILE64_SOURCE + #define _LARGEFILE64_SOURCE + #endif + #ifndef _FILE_OFFSET_BIT + #define _FILE_OFFSET_BIT 64 + #endif +#endif + +#ifdef __APPLE__ +// In darwin and perhaps other BSD variants off_t is a 64 bit value, hence no need for specific 64 bit functions +#define FOPEN_FUNC(filename, mode) fopen(filename, mode) +#define FTELLO_FUNC(stream) ftello(stream) +#define FSEEKO_FUNC(stream, offset, origin) fseeko(stream, offset, origin) +#else +#define FOPEN_FUNC(filename, mode) fopen64(filename, mode) +#define FTELLO_FUNC(stream) ftello64(stream) +#define FSEEKO_FUNC(stream, offset, origin) fseeko64(stream, offset, origin) +#endif + + + +#include +#include +#include +#include +#include +#include + +#ifdef _WIN32 +# include +# include +#else +# include +# include +# include +# include +#endif + +#include "zip.h" + +#ifdef _WIN32 + #define USEWIN32IOAPI + #include "iowin32.h" +#endif + + + +#define WRITEBUFFERSIZE (16384) +#define MAXFILENAME (256) + +#ifdef _WIN32 +uLong filetime(f, tmzip, dt) + char *f; /* name of file to get info on */ + tm_zip *tmzip; /* return value: access, modific. and creation times */ + uLong *dt; /* dostime */ +{ + int ret = 0; + { + FILETIME ftLocal; + HANDLE hFind; + WIN32_FIND_DATAA ff32; + + hFind = FindFirstFileA(f,&ff32); + if (hFind != INVALID_HANDLE_VALUE) + { + FileTimeToLocalFileTime(&(ff32.ftLastWriteTime),&ftLocal); + FileTimeToDosDateTime(&ftLocal,((LPWORD)dt)+1,((LPWORD)dt)+0); + FindClose(hFind); + ret = 1; + } + } + return ret; +} +#else +#ifdef unix || __APPLE__ +uLong filetime(f, tmzip, dt) + char *f; /* name of file to get info on */ + tm_zip *tmzip; /* return value: access, modific. and creation times */ + uLong *dt; /* dostime */ +{ + int ret=0; + struct stat s; /* results of stat() */ + struct tm* filedate; + time_t tm_t=0; + + if (strcmp(f,"-")!=0) + { + char name[MAXFILENAME+1]; + int len = strlen(f); + if (len > MAXFILENAME) + len = MAXFILENAME; + + strncpy(name, f,MAXFILENAME-1); + /* strncpy doesnt append the trailing NULL, of the string is too long. */ + name[ MAXFILENAME ] = '\0'; + + if (name[len - 1] == '/') + name[len - 1] = '\0'; + /* not all systems allow stat'ing a file with / appended */ + if (stat(name,&s)==0) + { + tm_t = s.st_mtime; + ret = 1; + } + } + filedate = localtime(&tm_t); + + tmzip->tm_sec = filedate->tm_sec; + tmzip->tm_min = filedate->tm_min; + tmzip->tm_hour = filedate->tm_hour; + tmzip->tm_mday = filedate->tm_mday; + tmzip->tm_mon = filedate->tm_mon ; + tmzip->tm_year = filedate->tm_year; + + return ret; +} +#else +uLong filetime(f, tmzip, dt) + char *f; /* name of file to get info on */ + tm_zip *tmzip; /* return value: access, modific. and creation times */ + uLong *dt; /* dostime */ +{ + return 0; +} +#endif +#endif + + + + +int check_exist_file(filename) + const char* filename; +{ + FILE* ftestexist; + int ret = 1; + ftestexist = FOPEN_FUNC(filename,"rb"); + if (ftestexist==NULL) + ret = 0; + else + fclose(ftestexist); + return ret; +} + +void do_banner() +{ + printf("MiniZip 1.1, demo of zLib + MiniZip64 package, written by Gilles Vollant\n"); + printf("more info on MiniZip at http://www.winimage.com/zLibDll/minizip.html\n\n"); +} + +void do_help() +{ + printf("Usage : minizip [-o] [-a] [-0 to -9] [-p password] [-j] file.zip [files_to_add]\n\n" \ + " -o Overwrite existing file.zip\n" \ + " -a Append to existing file.zip\n" \ + " -0 Store only\n" \ + " -1 Compress faster\n" \ + " -9 Compress better\n\n" \ + " -j exclude path. store only the file name.\n\n"); +} + +/* calculate the CRC32 of a file, + because to encrypt a file, we need known the CRC32 of the file before */ +int getFileCrc(const char* filenameinzip,void*buf,unsigned long size_buf,unsigned long* result_crc) +{ + unsigned long calculate_crc=0; + int err=ZIP_OK; + FILE * fin = FOPEN_FUNC(filenameinzip,"rb"); + + unsigned long size_read = 0; + unsigned long total_read = 0; + if (fin==NULL) + { + err = ZIP_ERRNO; + } + + if (err == ZIP_OK) + do + { + err = ZIP_OK; + size_read = (int)fread(buf,1,size_buf,fin); + if (size_read < size_buf) + if (feof(fin)==0) + { + printf("error in reading %s\n",filenameinzip); + err = ZIP_ERRNO; + } + + if (size_read>0) + calculate_crc = crc32(calculate_crc,buf,size_read); + total_read += size_read; + + } while ((err == ZIP_OK) && (size_read>0)); + + if (fin) + fclose(fin); + + *result_crc=calculate_crc; + printf("file %s crc %lx\n", filenameinzip, calculate_crc); + return err; +} + +int isLargeFile(const char* filename) +{ + int largeFile = 0; + ZPOS64_T pos = 0; + FILE* pFile = FOPEN_FUNC(filename, "rb"); + + if(pFile != NULL) + { + int n = FSEEKO_FUNC(pFile, 0, SEEK_END); + pos = FTELLO_FUNC(pFile); + + printf("File : %s is %lld bytes\n", filename, pos); + + if(pos >= 0xffffffff) + largeFile = 1; + + fclose(pFile); + } + + return largeFile; +} + +int main(argc,argv) + int argc; + char *argv[]; +{ + int i; + int opt_overwrite=0; + int opt_compress_level=Z_DEFAULT_COMPRESSION; + int opt_exclude_path=0; + int zipfilenamearg = 0; + char filename_try[MAXFILENAME+16]; + int zipok; + int err=0; + int size_buf=0; + void* buf=NULL; + const char* password=NULL; + + + do_banner(); + if (argc==1) + { + do_help(); + return 0; + } + else + { + for (i=1;i='0') && (c<='9')) + opt_compress_level = c-'0'; + if ((c=='j') || (c=='J')) + opt_exclude_path = 1; + + if (((c=='p') || (c=='P')) && (i+1='a') && (rep<='z')) + rep -= 0x20; + } + while ((rep!='Y') && (rep!='N') && (rep!='A')); + if (rep=='N') + zipok = 0; + if (rep=='A') + opt_overwrite = 2; + } + } + + if (zipok==1) + { + zipFile zf; + int errclose; +# ifdef USEWIN32IOAPI + zlib_filefunc64_def ffunc; + fill_win32_filefunc64A(&ffunc); + zf = zipOpen2_64(filename_try,(opt_overwrite==2) ? 2 : 0,NULL,&ffunc); +# else + zf = zipOpen64(filename_try,(opt_overwrite==2) ? 2 : 0); +# endif + + if (zf == NULL) + { + printf("error opening %s\n",filename_try); + err= ZIP_ERRNO; + } + else + printf("creating %s\n",filename_try); + + for (i=zipfilenamearg+1;(i='0') || (argv[i][1]<='9'))) && + (strlen(argv[i]) == 2))) + { + FILE * fin; + int size_read; + const char* filenameinzip = argv[i]; + const char *savefilenameinzip; + zip_fileinfo zi; + unsigned long crcFile=0; + int zip64 = 0; + + zi.tmz_date.tm_sec = zi.tmz_date.tm_min = zi.tmz_date.tm_hour = + zi.tmz_date.tm_mday = zi.tmz_date.tm_mon = zi.tmz_date.tm_year = 0; + zi.dosDate = 0; + zi.internal_fa = 0; + zi.external_fa = 0; + filetime(filenameinzip,&zi.tmz_date,&zi.dosDate); + +/* + err = zipOpenNewFileInZip(zf,filenameinzip,&zi, + NULL,0,NULL,0,NULL / * comment * /, + (opt_compress_level != 0) ? Z_DEFLATED : 0, + opt_compress_level); +*/ + if ((password != NULL) && (err==ZIP_OK)) + err = getFileCrc(filenameinzip,buf,size_buf,&crcFile); + + zip64 = isLargeFile(filenameinzip); + + /* The path name saved, should not include a leading slash. */ + /*if it did, windows/xp and dynazip couldn't read the zip file. */ + savefilenameinzip = filenameinzip; + while( savefilenameinzip[0] == '\\' || savefilenameinzip[0] == '/' ) + { + savefilenameinzip++; + } + + /*should the zip file contain any path at all?*/ + if( opt_exclude_path ) + { + const char *tmpptr; + const char *lastslash = 0; + for( tmpptr = savefilenameinzip; *tmpptr; tmpptr++) + { + if( *tmpptr == '\\' || *tmpptr == '/') + { + lastslash = tmpptr; + } + } + if( lastslash != NULL ) + { + savefilenameinzip = lastslash+1; // base filename follows last slash. + } + } + + /**/ + err = zipOpenNewFileInZip3_64(zf,savefilenameinzip,&zi, + NULL,0,NULL,0,NULL /* comment*/, + (opt_compress_level != 0) ? Z_DEFLATED : 0, + opt_compress_level,0, + /* -MAX_WBITS, DEF_MEM_LEVEL, Z_DEFAULT_STRATEGY, */ + -MAX_WBITS, DEF_MEM_LEVEL, Z_DEFAULT_STRATEGY, + password,crcFile, zip64); + + if (err != ZIP_OK) + printf("error in opening %s in zipfile\n",filenameinzip); + else + { + fin = FOPEN_FUNC(filenameinzip,"rb"); + if (fin==NULL) + { + err=ZIP_ERRNO; + printf("error in opening %s for reading\n",filenameinzip); + } + } + + if (err == ZIP_OK) + do + { + err = ZIP_OK; + size_read = (int)fread(buf,1,size_buf,fin); + if (size_read < size_buf) + if (feof(fin)==0) + { + printf("error in reading %s\n",filenameinzip); + err = ZIP_ERRNO; + } + + if (size_read>0) + { + err = zipWriteInFileInZip (zf,buf,size_read); + if (err<0) + { + printf("error in writing %s in the zipfile\n", + filenameinzip); + } + + } + } while ((err == ZIP_OK) && (size_read>0)); + + if (fin) + fclose(fin); + + if (err<0) + err=ZIP_ERRNO; + else + { + err = zipCloseFileInZip(zf); + if (err!=ZIP_OK) + printf("error in closing %s in the zipfile\n", + filenameinzip); + } + } + } + errclose = zipClose(zf,NULL); + if (errclose != ZIP_OK) + printf("error in closing %s\n",filename_try); + } + else + { + do_help(); + } + + free(buf); + return 0; +} ADDED compat/zlib/contrib/minizip/minizip.pc.in Index: compat/zlib/contrib/minizip/minizip.pc.in ================================================================== --- /dev/null +++ compat/zlib/contrib/minizip/minizip.pc.in @@ -0,0 +1,12 @@ +prefix=@prefix@ +exec_prefix=@exec_prefix@ +libdir=@libdir@ +includedir=@includedir@/minizip + +Name: minizip +Description: Minizip zip file manipulation library +Requires: +Version: @PACKAGE_VERSION@ +Libs: -L${libdir} -lminizip +Libs.private: -lz +Cflags: -I${includedir} ADDED compat/zlib/contrib/minizip/mztools.c Index: compat/zlib/contrib/minizip/mztools.c ================================================================== --- /dev/null +++ compat/zlib/contrib/minizip/mztools.c @@ -0,0 +1,291 @@ +/* + Additional tools for Minizip + Code: Xavier Roche '2004 + License: Same as ZLIB (www.gzip.org) +*/ + +/* Code */ +#include +#include +#include +#include "zlib.h" +#include "unzip.h" + +#define READ_8(adr) ((unsigned char)*(adr)) +#define READ_16(adr) ( READ_8(adr) | (READ_8(adr+1) << 8) ) +#define READ_32(adr) ( READ_16(adr) | (READ_16((adr)+2) << 16) ) + +#define WRITE_8(buff, n) do { \ + *((unsigned char*)(buff)) = (unsigned char) ((n) & 0xff); \ +} while(0) +#define WRITE_16(buff, n) do { \ + WRITE_8((unsigned char*)(buff), n); \ + WRITE_8(((unsigned char*)(buff)) + 1, (n) >> 8); \ +} while(0) +#define WRITE_32(buff, n) do { \ + WRITE_16((unsigned char*)(buff), (n) & 0xffff); \ + WRITE_16((unsigned char*)(buff) + 2, (n) >> 16); \ +} while(0) + +extern int ZEXPORT unzRepair(file, fileOut, fileOutTmp, nRecovered, bytesRecovered) +const char* file; +const char* fileOut; +const char* fileOutTmp; +uLong* nRecovered; +uLong* bytesRecovered; +{ + int err = Z_OK; + FILE* fpZip = fopen(file, "rb"); + FILE* fpOut = fopen(fileOut, "wb"); + FILE* fpOutCD = fopen(fileOutTmp, "wb"); + if (fpZip != NULL && fpOut != NULL) { + int entries = 0; + uLong totalBytes = 0; + char header[30]; + char filename[1024]; + char extra[1024]; + int offset = 0; + int offsetCD = 0; + while ( fread(header, 1, 30, fpZip) == 30 ) { + int currentOffset = offset; + + /* File entry */ + if (READ_32(header) == 0x04034b50) { + unsigned int version = READ_16(header + 4); + unsigned int gpflag = READ_16(header + 6); + unsigned int method = READ_16(header + 8); + unsigned int filetime = READ_16(header + 10); + unsigned int filedate = READ_16(header + 12); + unsigned int crc = READ_32(header + 14); /* crc */ + unsigned int cpsize = READ_32(header + 18); /* compressed size */ + unsigned int uncpsize = READ_32(header + 22); /* uncompressed sz */ + unsigned int fnsize = READ_16(header + 26); /* file name length */ + unsigned int extsize = READ_16(header + 28); /* extra field length */ + filename[0] = extra[0] = '\0'; + + /* Header */ + if (fwrite(header, 1, 30, fpOut) == 30) { + offset += 30; + } else { + err = Z_ERRNO; + break; + } + + /* Filename */ + if (fnsize > 0) { + if (fnsize < sizeof(filename)) { + if (fread(filename, 1, fnsize, fpZip) == fnsize) { + if (fwrite(filename, 1, fnsize, fpOut) == fnsize) { + offset += fnsize; + } else { + err = Z_ERRNO; + break; + } + } else { + err = Z_ERRNO; + break; + } + } else { + err = Z_ERRNO; + break; + } + } else { + err = Z_STREAM_ERROR; + break; + } + + /* Extra field */ + if (extsize > 0) { + if (extsize < sizeof(extra)) { + if (fread(extra, 1, extsize, fpZip) == extsize) { + if (fwrite(extra, 1, extsize, fpOut) == extsize) { + offset += extsize; + } else { + err = Z_ERRNO; + break; + } + } else { + err = Z_ERRNO; + break; + } + } else { + err = Z_ERRNO; + break; + } + } + + /* Data */ + { + int dataSize = cpsize; + if (dataSize == 0) { + dataSize = uncpsize; + } + if (dataSize > 0) { + char* data = malloc(dataSize); + if (data != NULL) { + if ((int)fread(data, 1, dataSize, fpZip) == dataSize) { + if ((int)fwrite(data, 1, dataSize, fpOut) == dataSize) { + offset += dataSize; + totalBytes += dataSize; + } else { + err = Z_ERRNO; + } + } else { + err = Z_ERRNO; + } + free(data); + if (err != Z_OK) { + break; + } + } else { + err = Z_MEM_ERROR; + break; + } + } + } + + /* Central directory entry */ + { + char header[46]; + char* comment = ""; + int comsize = (int) strlen(comment); + WRITE_32(header, 0x02014b50); + WRITE_16(header + 4, version); + WRITE_16(header + 6, version); + WRITE_16(header + 8, gpflag); + WRITE_16(header + 10, method); + WRITE_16(header + 12, filetime); + WRITE_16(header + 14, filedate); + WRITE_32(header + 16, crc); + WRITE_32(header + 20, cpsize); + WRITE_32(header + 24, uncpsize); + WRITE_16(header + 28, fnsize); + WRITE_16(header + 30, extsize); + WRITE_16(header + 32, comsize); + WRITE_16(header + 34, 0); /* disk # */ + WRITE_16(header + 36, 0); /* int attrb */ + WRITE_32(header + 38, 0); /* ext attrb */ + WRITE_32(header + 42, currentOffset); + /* Header */ + if (fwrite(header, 1, 46, fpOutCD) == 46) { + offsetCD += 46; + + /* Filename */ + if (fnsize > 0) { + if (fwrite(filename, 1, fnsize, fpOutCD) == fnsize) { + offsetCD += fnsize; + } else { + err = Z_ERRNO; + break; + } + } else { + err = Z_STREAM_ERROR; + break; + } + + /* Extra field */ + if (extsize > 0) { + if (fwrite(extra, 1, extsize, fpOutCD) == extsize) { + offsetCD += extsize; + } else { + err = Z_ERRNO; + break; + } + } + + /* Comment field */ + if (comsize > 0) { + if ((int)fwrite(comment, 1, comsize, fpOutCD) == comsize) { + offsetCD += comsize; + } else { + err = Z_ERRNO; + break; + } + } + + + } else { + err = Z_ERRNO; + break; + } + } + + /* Success */ + entries++; + + } else { + break; + } + } + + /* Final central directory */ + { + int entriesZip = entries; + char header[22]; + char* comment = ""; // "ZIP File recovered by zlib/minizip/mztools"; + int comsize = (int) strlen(comment); + if (entriesZip > 0xffff) { + entriesZip = 0xffff; + } + WRITE_32(header, 0x06054b50); + WRITE_16(header + 4, 0); /* disk # */ + WRITE_16(header + 6, 0); /* disk # */ + WRITE_16(header + 8, entriesZip); /* hack */ + WRITE_16(header + 10, entriesZip); /* hack */ + WRITE_32(header + 12, offsetCD); /* size of CD */ + WRITE_32(header + 16, offset); /* offset to CD */ + WRITE_16(header + 20, comsize); /* comment */ + + /* Header */ + if (fwrite(header, 1, 22, fpOutCD) == 22) { + + /* Comment field */ + if (comsize > 0) { + if ((int)fwrite(comment, 1, comsize, fpOutCD) != comsize) { + err = Z_ERRNO; + } + } + + } else { + err = Z_ERRNO; + } + } + + /* Final merge (file + central directory) */ + fclose(fpOutCD); + if (err == Z_OK) { + fpOutCD = fopen(fileOutTmp, "rb"); + if (fpOutCD != NULL) { + int nRead; + char buffer[8192]; + while ( (nRead = (int)fread(buffer, 1, sizeof(buffer), fpOutCD)) > 0) { + if ((int)fwrite(buffer, 1, nRead, fpOut) != nRead) { + err = Z_ERRNO; + break; + } + } + fclose(fpOutCD); + } + } + + /* Close */ + fclose(fpZip); + fclose(fpOut); + + /* Wipe temporary file */ + (void)remove(fileOutTmp); + + /* Number of recovered entries */ + if (err == Z_OK) { + if (nRecovered != NULL) { + *nRecovered = entries; + } + if (bytesRecovered != NULL) { + *bytesRecovered = totalBytes; + } + } + } else { + err = Z_STREAM_ERROR; + } + return err; +} ADDED compat/zlib/contrib/minizip/mztools.h Index: compat/zlib/contrib/minizip/mztools.h ================================================================== --- /dev/null +++ compat/zlib/contrib/minizip/mztools.h @@ -0,0 +1,37 @@ +/* + Additional tools for Minizip + Code: Xavier Roche '2004 + License: Same as ZLIB (www.gzip.org) +*/ + +#ifndef _zip_tools_H +#define _zip_tools_H + +#ifdef __cplusplus +extern "C" { +#endif + +#ifndef _ZLIB_H +#include "zlib.h" +#endif + +#include "unzip.h" + +/* Repair a ZIP file (missing central directory) + file: file to recover + fileOut: output file after recovery + fileOutTmp: temporary file name used for recovery +*/ +extern int ZEXPORT unzRepair(const char* file, + const char* fileOut, + const char* fileOutTmp, + uLong* nRecovered, + uLong* bytesRecovered); + + +#ifdef __cplusplus +} +#endif + + +#endif ADDED compat/zlib/contrib/minizip/unzip.c Index: compat/zlib/contrib/minizip/unzip.c ================================================================== --- /dev/null +++ compat/zlib/contrib/minizip/unzip.c @@ -0,0 +1,2125 @@ +/* unzip.c -- IO for uncompress .zip files using zlib + Version 1.1, February 14h, 2010 + part of the MiniZip project - ( http://www.winimage.com/zLibDll/minizip.html ) + + Copyright (C) 1998-2010 Gilles Vollant (minizip) ( http://www.winimage.com/zLibDll/minizip.html ) + + Modifications of Unzip for Zip64 + Copyright (C) 2007-2008 Even Rouault + + Modifications for Zip64 support on both zip and unzip + Copyright (C) 2009-2010 Mathias Svensson ( http://result42.com ) + + For more info read MiniZip_info.txt + + + ------------------------------------------------------------------------------------ + Decryption code comes from crypt.c by Info-ZIP but has been greatly reduced in terms of + compatibility with older software. The following is from the original crypt.c. + Code woven in by Terry Thorsen 1/2003. + + Copyright (c) 1990-2000 Info-ZIP. All rights reserved. + + See the accompanying file LICENSE, version 2000-Apr-09 or later + (the contents of which are also included in zip.h) for terms of use. + If, for some reason, all these files are missing, the Info-ZIP license + also may be found at: ftp://ftp.info-zip.org/pub/infozip/license.html + + crypt.c (full version) by Info-ZIP. Last revised: [see crypt.h] + + The encryption/decryption parts of this source code (as opposed to the + non-echoing password parts) were originally written in Europe. The + whole source package can be freely distributed, including from the USA. + (Prior to January 2000, re-export from the US was a violation of US law.) + + This encryption code is a direct transcription of the algorithm from + Roger Schlafly, described by Phil Katz in the file appnote.txt. This + file (appnote.txt) is distributed with the PKZIP program (even in the + version without encryption capabilities). + + ------------------------------------------------------------------------------------ + + Changes in unzip.c + + 2007-2008 - Even Rouault - Addition of cpl_unzGetCurrentFileZStreamPos + 2007-2008 - Even Rouault - Decoration of symbol names unz* -> cpl_unz* + 2007-2008 - Even Rouault - Remove old C style function prototypes + 2007-2008 - Even Rouault - Add unzip support for ZIP64 + + Copyright (C) 2007-2008 Even Rouault + + + Oct-2009 - Mathias Svensson - Removed cpl_* from symbol names (Even Rouault added them but since this is now moved to a new project (minizip64) I renamed them again). + Oct-2009 - Mathias Svensson - Fixed problem if uncompressed size was > 4G and compressed size was <4G + should only read the compressed/uncompressed size from the Zip64 format if + the size from normal header was 0xFFFFFFFF + Oct-2009 - Mathias Svensson - Applied some bug fixes from paches recived from Gilles Vollant + Oct-2009 - Mathias Svensson - Applied support to unzip files with compression mathod BZIP2 (bzip2 lib is required) + Patch created by Daniel Borca + + Jan-2010 - back to unzip and minizip 1.0 name scheme, with compatibility layer + + Copyright (C) 1998 - 2010 Gilles Vollant, Even Rouault, Mathias Svensson + +*/ + + +#include +#include +#include + +#ifndef NOUNCRYPT + #define NOUNCRYPT +#endif + +#include "zlib.h" +#include "unzip.h" + +#ifdef STDC +# include +# include +# include +#endif +#ifdef NO_ERRNO_H + extern int errno; +#else +# include +#endif + + +#ifndef local +# define local static +#endif +/* compile with -Dlocal if your debugger can't find static symbols */ + + +#ifndef CASESENSITIVITYDEFAULT_NO +# if !defined(unix) && !defined(CASESENSITIVITYDEFAULT_YES) +# define CASESENSITIVITYDEFAULT_NO +# endif +#endif + + +#ifndef UNZ_BUFSIZE +#define UNZ_BUFSIZE (16384) +#endif + +#ifndef UNZ_MAXFILENAMEINZIP +#define UNZ_MAXFILENAMEINZIP (256) +#endif + +#ifndef ALLOC +# define ALLOC(size) (malloc(size)) +#endif +#ifndef TRYFREE +# define TRYFREE(p) {if (p) free(p);} +#endif + +#define SIZECENTRALDIRITEM (0x2e) +#define SIZEZIPLOCALHEADER (0x1e) + + +const char unz_copyright[] = + " unzip 1.01 Copyright 1998-2004 Gilles Vollant - http://www.winimage.com/zLibDll"; + +/* unz_file_info_interntal contain internal info about a file in zipfile*/ +typedef struct unz_file_info64_internal_s +{ + ZPOS64_T offset_curfile;/* relative offset of local header 8 bytes */ +} unz_file_info64_internal; + + +/* file_in_zip_read_info_s contain internal information about a file in zipfile, + when reading and decompress it */ +typedef struct +{ + char *read_buffer; /* internal buffer for compressed data */ + z_stream stream; /* zLib stream structure for inflate */ + +#ifdef HAVE_BZIP2 + bz_stream bstream; /* bzLib stream structure for bziped */ +#endif + + ZPOS64_T pos_in_zipfile; /* position in byte on the zipfile, for fseek*/ + uLong stream_initialised; /* flag set if stream structure is initialised*/ + + ZPOS64_T offset_local_extrafield;/* offset of the local extra field */ + uInt size_local_extrafield;/* size of the local extra field */ + ZPOS64_T pos_local_extrafield; /* position in the local extra field in read*/ + ZPOS64_T total_out_64; + + uLong crc32; /* crc32 of all data uncompressed */ + uLong crc32_wait; /* crc32 we must obtain after decompress all */ + ZPOS64_T rest_read_compressed; /* number of byte to be decompressed */ + ZPOS64_T rest_read_uncompressed;/*number of byte to be obtained after decomp*/ + zlib_filefunc64_32_def z_filefunc; + voidpf filestream; /* io structore of the zipfile */ + uLong compression_method; /* compression method (0==store) */ + ZPOS64_T byte_before_the_zipfile;/* byte before the zipfile, (>0 for sfx)*/ + int raw; +} file_in_zip64_read_info_s; + + +/* unz64_s contain internal information about the zipfile +*/ +typedef struct +{ + zlib_filefunc64_32_def z_filefunc; + int is64bitOpenFunction; + voidpf filestream; /* io structore of the zipfile */ + unz_global_info64 gi; /* public global information */ + ZPOS64_T byte_before_the_zipfile;/* byte before the zipfile, (>0 for sfx)*/ + ZPOS64_T num_file; /* number of the current file in the zipfile*/ + ZPOS64_T pos_in_central_dir; /* pos of the current file in the central dir*/ + ZPOS64_T current_file_ok; /* flag about the usability of the current file*/ + ZPOS64_T central_pos; /* position of the beginning of the central dir*/ + + ZPOS64_T size_central_dir; /* size of the central directory */ + ZPOS64_T offset_central_dir; /* offset of start of central directory with + respect to the starting disk number */ + + unz_file_info64 cur_file_info; /* public info about the current file in zip*/ + unz_file_info64_internal cur_file_info_internal; /* private info about it*/ + file_in_zip64_read_info_s* pfile_in_zip_read; /* structure about the current + file if we are decompressing it */ + int encrypted; + + int isZip64; + +# ifndef NOUNCRYPT + unsigned long keys[3]; /* keys defining the pseudo-random sequence */ + const z_crc_t* pcrc_32_tab; +# endif +} unz64_s; + + +#ifndef NOUNCRYPT +#include "crypt.h" +#endif + +/* =========================================================================== + Read a byte from a gz_stream; update next_in and avail_in. Return EOF + for end of file. + IN assertion: the stream s has been successfully opened for reading. +*/ + + +local int unz64local_getByte OF(( + const zlib_filefunc64_32_def* pzlib_filefunc_def, + voidpf filestream, + int *pi)); + +local int unz64local_getByte(const zlib_filefunc64_32_def* pzlib_filefunc_def, voidpf filestream, int *pi) +{ + unsigned char c; + int err = (int)ZREAD64(*pzlib_filefunc_def,filestream,&c,1); + if (err==1) + { + *pi = (int)c; + return UNZ_OK; + } + else + { + if (ZERROR64(*pzlib_filefunc_def,filestream)) + return UNZ_ERRNO; + else + return UNZ_EOF; + } +} + + +/* =========================================================================== + Reads a long in LSB order from the given gz_stream. Sets +*/ +local int unz64local_getShort OF(( + const zlib_filefunc64_32_def* pzlib_filefunc_def, + voidpf filestream, + uLong *pX)); + +local int unz64local_getShort (const zlib_filefunc64_32_def* pzlib_filefunc_def, + voidpf filestream, + uLong *pX) +{ + uLong x ; + int i = 0; + int err; + + err = unz64local_getByte(pzlib_filefunc_def,filestream,&i); + x = (uLong)i; + + if (err==UNZ_OK) + err = unz64local_getByte(pzlib_filefunc_def,filestream,&i); + x |= ((uLong)i)<<8; + + if (err==UNZ_OK) + *pX = x; + else + *pX = 0; + return err; +} + +local int unz64local_getLong OF(( + const zlib_filefunc64_32_def* pzlib_filefunc_def, + voidpf filestream, + uLong *pX)); + +local int unz64local_getLong (const zlib_filefunc64_32_def* pzlib_filefunc_def, + voidpf filestream, + uLong *pX) +{ + uLong x ; + int i = 0; + int err; + + err = unz64local_getByte(pzlib_filefunc_def,filestream,&i); + x = (uLong)i; + + if (err==UNZ_OK) + err = unz64local_getByte(pzlib_filefunc_def,filestream,&i); + x |= ((uLong)i)<<8; + + if (err==UNZ_OK) + err = unz64local_getByte(pzlib_filefunc_def,filestream,&i); + x |= ((uLong)i)<<16; + + if (err==UNZ_OK) + err = unz64local_getByte(pzlib_filefunc_def,filestream,&i); + x += ((uLong)i)<<24; + + if (err==UNZ_OK) + *pX = x; + else + *pX = 0; + return err; +} + +local int unz64local_getLong64 OF(( + const zlib_filefunc64_32_def* pzlib_filefunc_def, + voidpf filestream, + ZPOS64_T *pX)); + + +local int unz64local_getLong64 (const zlib_filefunc64_32_def* pzlib_filefunc_def, + voidpf filestream, + ZPOS64_T *pX) +{ + ZPOS64_T x ; + int i = 0; + int err; + + err = unz64local_getByte(pzlib_filefunc_def,filestream,&i); + x = (ZPOS64_T)i; + + if (err==UNZ_OK) + err = unz64local_getByte(pzlib_filefunc_def,filestream,&i); + x |= ((ZPOS64_T)i)<<8; + + if (err==UNZ_OK) + err = unz64local_getByte(pzlib_filefunc_def,filestream,&i); + x |= ((ZPOS64_T)i)<<16; + + if (err==UNZ_OK) + err = unz64local_getByte(pzlib_filefunc_def,filestream,&i); + x |= ((ZPOS64_T)i)<<24; + + if (err==UNZ_OK) + err = unz64local_getByte(pzlib_filefunc_def,filestream,&i); + x |= ((ZPOS64_T)i)<<32; + + if (err==UNZ_OK) + err = unz64local_getByte(pzlib_filefunc_def,filestream,&i); + x |= ((ZPOS64_T)i)<<40; + + if (err==UNZ_OK) + err = unz64local_getByte(pzlib_filefunc_def,filestream,&i); + x |= ((ZPOS64_T)i)<<48; + + if (err==UNZ_OK) + err = unz64local_getByte(pzlib_filefunc_def,filestream,&i); + x |= ((ZPOS64_T)i)<<56; + + if (err==UNZ_OK) + *pX = x; + else + *pX = 0; + return err; +} + +/* My own strcmpi / strcasecmp */ +local int strcmpcasenosensitive_internal (const char* fileName1, const char* fileName2) +{ + for (;;) + { + char c1=*(fileName1++); + char c2=*(fileName2++); + if ((c1>='a') && (c1<='z')) + c1 -= 0x20; + if ((c2>='a') && (c2<='z')) + c2 -= 0x20; + if (c1=='\0') + return ((c2=='\0') ? 0 : -1); + if (c2=='\0') + return 1; + if (c1c2) + return 1; + } +} + + +#ifdef CASESENSITIVITYDEFAULT_NO +#define CASESENSITIVITYDEFAULTVALUE 2 +#else +#define CASESENSITIVITYDEFAULTVALUE 1 +#endif + +#ifndef STRCMPCASENOSENTIVEFUNCTION +#define STRCMPCASENOSENTIVEFUNCTION strcmpcasenosensitive_internal +#endif + +/* + Compare two filename (fileName1,fileName2). + If iCaseSenisivity = 1, comparision is case sensitivity (like strcmp) + If iCaseSenisivity = 2, comparision is not case sensitivity (like strcmpi + or strcasecmp) + If iCaseSenisivity = 0, case sensitivity is defaut of your operating system + (like 1 on Unix, 2 on Windows) + +*/ +extern int ZEXPORT unzStringFileNameCompare (const char* fileName1, + const char* fileName2, + int iCaseSensitivity) + +{ + if (iCaseSensitivity==0) + iCaseSensitivity=CASESENSITIVITYDEFAULTVALUE; + + if (iCaseSensitivity==1) + return strcmp(fileName1,fileName2); + + return STRCMPCASENOSENTIVEFUNCTION(fileName1,fileName2); +} + +#ifndef BUFREADCOMMENT +#define BUFREADCOMMENT (0x400) +#endif + +/* + Locate the Central directory of a zipfile (at the end, just before + the global comment) +*/ +local ZPOS64_T unz64local_SearchCentralDir OF((const zlib_filefunc64_32_def* pzlib_filefunc_def, voidpf filestream)); +local ZPOS64_T unz64local_SearchCentralDir(const zlib_filefunc64_32_def* pzlib_filefunc_def, voidpf filestream) +{ + unsigned char* buf; + ZPOS64_T uSizeFile; + ZPOS64_T uBackRead; + ZPOS64_T uMaxBack=0xffff; /* maximum size of global comment */ + ZPOS64_T uPosFound=0; + + if (ZSEEK64(*pzlib_filefunc_def,filestream,0,ZLIB_FILEFUNC_SEEK_END) != 0) + return 0; + + + uSizeFile = ZTELL64(*pzlib_filefunc_def,filestream); + + if (uMaxBack>uSizeFile) + uMaxBack = uSizeFile; + + buf = (unsigned char*)ALLOC(BUFREADCOMMENT+4); + if (buf==NULL) + return 0; + + uBackRead = 4; + while (uBackReaduMaxBack) + uBackRead = uMaxBack; + else + uBackRead+=BUFREADCOMMENT; + uReadPos = uSizeFile-uBackRead ; + + uReadSize = ((BUFREADCOMMENT+4) < (uSizeFile-uReadPos)) ? + (BUFREADCOMMENT+4) : (uLong)(uSizeFile-uReadPos); + if (ZSEEK64(*pzlib_filefunc_def,filestream,uReadPos,ZLIB_FILEFUNC_SEEK_SET)!=0) + break; + + if (ZREAD64(*pzlib_filefunc_def,filestream,buf,uReadSize)!=uReadSize) + break; + + for (i=(int)uReadSize-3; (i--)>0;) + if (((*(buf+i))==0x50) && ((*(buf+i+1))==0x4b) && + ((*(buf+i+2))==0x05) && ((*(buf+i+3))==0x06)) + { + uPosFound = uReadPos+i; + break; + } + + if (uPosFound!=0) + break; + } + TRYFREE(buf); + return uPosFound; +} + + +/* + Locate the Central directory 64 of a zipfile (at the end, just before + the global comment) +*/ +local ZPOS64_T unz64local_SearchCentralDir64 OF(( + const zlib_filefunc64_32_def* pzlib_filefunc_def, + voidpf filestream)); + +local ZPOS64_T unz64local_SearchCentralDir64(const zlib_filefunc64_32_def* pzlib_filefunc_def, + voidpf filestream) +{ + unsigned char* buf; + ZPOS64_T uSizeFile; + ZPOS64_T uBackRead; + ZPOS64_T uMaxBack=0xffff; /* maximum size of global comment */ + ZPOS64_T uPosFound=0; + uLong uL; + ZPOS64_T relativeOffset; + + if (ZSEEK64(*pzlib_filefunc_def,filestream,0,ZLIB_FILEFUNC_SEEK_END) != 0) + return 0; + + + uSizeFile = ZTELL64(*pzlib_filefunc_def,filestream); + + if (uMaxBack>uSizeFile) + uMaxBack = uSizeFile; + + buf = (unsigned char*)ALLOC(BUFREADCOMMENT+4); + if (buf==NULL) + return 0; + + uBackRead = 4; + while (uBackReaduMaxBack) + uBackRead = uMaxBack; + else + uBackRead+=BUFREADCOMMENT; + uReadPos = uSizeFile-uBackRead ; + + uReadSize = ((BUFREADCOMMENT+4) < (uSizeFile-uReadPos)) ? + (BUFREADCOMMENT+4) : (uLong)(uSizeFile-uReadPos); + if (ZSEEK64(*pzlib_filefunc_def,filestream,uReadPos,ZLIB_FILEFUNC_SEEK_SET)!=0) + break; + + if (ZREAD64(*pzlib_filefunc_def,filestream,buf,uReadSize)!=uReadSize) + break; + + for (i=(int)uReadSize-3; (i--)>0;) + if (((*(buf+i))==0x50) && ((*(buf+i+1))==0x4b) && + ((*(buf+i+2))==0x06) && ((*(buf+i+3))==0x07)) + { + uPosFound = uReadPos+i; + break; + } + + if (uPosFound!=0) + break; + } + TRYFREE(buf); + if (uPosFound == 0) + return 0; + + /* Zip64 end of central directory locator */ + if (ZSEEK64(*pzlib_filefunc_def,filestream, uPosFound,ZLIB_FILEFUNC_SEEK_SET)!=0) + return 0; + + /* the signature, already checked */ + if (unz64local_getLong(pzlib_filefunc_def,filestream,&uL)!=UNZ_OK) + return 0; + + /* number of the disk with the start of the zip64 end of central directory */ + if (unz64local_getLong(pzlib_filefunc_def,filestream,&uL)!=UNZ_OK) + return 0; + if (uL != 0) + return 0; + + /* relative offset of the zip64 end of central directory record */ + if (unz64local_getLong64(pzlib_filefunc_def,filestream,&relativeOffset)!=UNZ_OK) + return 0; + + /* total number of disks */ + if (unz64local_getLong(pzlib_filefunc_def,filestream,&uL)!=UNZ_OK) + return 0; + if (uL != 1) + return 0; + + /* Goto end of central directory record */ + if (ZSEEK64(*pzlib_filefunc_def,filestream, relativeOffset,ZLIB_FILEFUNC_SEEK_SET)!=0) + return 0; + + /* the signature */ + if (unz64local_getLong(pzlib_filefunc_def,filestream,&uL)!=UNZ_OK) + return 0; + + if (uL != 0x06064b50) + return 0; + + return relativeOffset; +} + +/* + Open a Zip file. path contain the full pathname (by example, + on a Windows NT computer "c:\\test\\zlib114.zip" or on an Unix computer + "zlib/zlib114.zip". + If the zipfile cannot be opened (file doesn't exist or in not valid), the + return value is NULL. + Else, the return value is a unzFile Handle, usable with other function + of this unzip package. +*/ +local unzFile unzOpenInternal (const void *path, + zlib_filefunc64_32_def* pzlib_filefunc64_32_def, + int is64bitOpenFunction) +{ + unz64_s us; + unz64_s *s; + ZPOS64_T central_pos; + uLong uL; + + uLong number_disk; /* number of the current dist, used for + spaning ZIP, unsupported, always 0*/ + uLong number_disk_with_CD; /* number the the disk with central dir, used + for spaning ZIP, unsupported, always 0*/ + ZPOS64_T number_entry_CD; /* total number of entries in + the central dir + (same than number_entry on nospan) */ + + int err=UNZ_OK; + + if (unz_copyright[0]!=' ') + return NULL; + + us.z_filefunc.zseek32_file = NULL; + us.z_filefunc.ztell32_file = NULL; + if (pzlib_filefunc64_32_def==NULL) + fill_fopen64_filefunc(&us.z_filefunc.zfile_func64); + else + us.z_filefunc = *pzlib_filefunc64_32_def; + us.is64bitOpenFunction = is64bitOpenFunction; + + + + us.filestream = ZOPEN64(us.z_filefunc, + path, + ZLIB_FILEFUNC_MODE_READ | + ZLIB_FILEFUNC_MODE_EXISTING); + if (us.filestream==NULL) + return NULL; + + central_pos = unz64local_SearchCentralDir64(&us.z_filefunc,us.filestream); + if (central_pos) + { + uLong uS; + ZPOS64_T uL64; + + us.isZip64 = 1; + + if (ZSEEK64(us.z_filefunc, us.filestream, + central_pos,ZLIB_FILEFUNC_SEEK_SET)!=0) + err=UNZ_ERRNO; + + /* the signature, already checked */ + if (unz64local_getLong(&us.z_filefunc, us.filestream,&uL)!=UNZ_OK) + err=UNZ_ERRNO; + + /* size of zip64 end of central directory record */ + if (unz64local_getLong64(&us.z_filefunc, us.filestream,&uL64)!=UNZ_OK) + err=UNZ_ERRNO; + + /* version made by */ + if (unz64local_getShort(&us.z_filefunc, us.filestream,&uS)!=UNZ_OK) + err=UNZ_ERRNO; + + /* version needed to extract */ + if (unz64local_getShort(&us.z_filefunc, us.filestream,&uS)!=UNZ_OK) + err=UNZ_ERRNO; + + /* number of this disk */ + if (unz64local_getLong(&us.z_filefunc, us.filestream,&number_disk)!=UNZ_OK) + err=UNZ_ERRNO; + + /* number of the disk with the start of the central directory */ + if (unz64local_getLong(&us.z_filefunc, us.filestream,&number_disk_with_CD)!=UNZ_OK) + err=UNZ_ERRNO; + + /* total number of entries in the central directory on this disk */ + if (unz64local_getLong64(&us.z_filefunc, us.filestream,&us.gi.number_entry)!=UNZ_OK) + err=UNZ_ERRNO; + + /* total number of entries in the central directory */ + if (unz64local_getLong64(&us.z_filefunc, us.filestream,&number_entry_CD)!=UNZ_OK) + err=UNZ_ERRNO; + + if ((number_entry_CD!=us.gi.number_entry) || + (number_disk_with_CD!=0) || + (number_disk!=0)) + err=UNZ_BADZIPFILE; + + /* size of the central directory */ + if (unz64local_getLong64(&us.z_filefunc, us.filestream,&us.size_central_dir)!=UNZ_OK) + err=UNZ_ERRNO; + + /* offset of start of central directory with respect to the + starting disk number */ + if (unz64local_getLong64(&us.z_filefunc, us.filestream,&us.offset_central_dir)!=UNZ_OK) + err=UNZ_ERRNO; + + us.gi.size_comment = 0; + } + else + { + central_pos = unz64local_SearchCentralDir(&us.z_filefunc,us.filestream); + if (central_pos==0) + err=UNZ_ERRNO; + + us.isZip64 = 0; + + if (ZSEEK64(us.z_filefunc, us.filestream, + central_pos,ZLIB_FILEFUNC_SEEK_SET)!=0) + err=UNZ_ERRNO; + + /* the signature, already checked */ + if (unz64local_getLong(&us.z_filefunc, us.filestream,&uL)!=UNZ_OK) + err=UNZ_ERRNO; + + /* number of this disk */ + if (unz64local_getShort(&us.z_filefunc, us.filestream,&number_disk)!=UNZ_OK) + err=UNZ_ERRNO; + + /* number of the disk with the start of the central directory */ + if (unz64local_getShort(&us.z_filefunc, us.filestream,&number_disk_with_CD)!=UNZ_OK) + err=UNZ_ERRNO; + + /* total number of entries in the central dir on this disk */ + if (unz64local_getShort(&us.z_filefunc, us.filestream,&uL)!=UNZ_OK) + err=UNZ_ERRNO; + us.gi.number_entry = uL; + + /* total number of entries in the central dir */ + if (unz64local_getShort(&us.z_filefunc, us.filestream,&uL)!=UNZ_OK) + err=UNZ_ERRNO; + number_entry_CD = uL; + + if ((number_entry_CD!=us.gi.number_entry) || + (number_disk_with_CD!=0) || + (number_disk!=0)) + err=UNZ_BADZIPFILE; + + /* size of the central directory */ + if (unz64local_getLong(&us.z_filefunc, us.filestream,&uL)!=UNZ_OK) + err=UNZ_ERRNO; + us.size_central_dir = uL; + + /* offset of start of central directory with respect to the + starting disk number */ + if (unz64local_getLong(&us.z_filefunc, us.filestream,&uL)!=UNZ_OK) + err=UNZ_ERRNO; + us.offset_central_dir = uL; + + /* zipfile comment length */ + if (unz64local_getShort(&us.z_filefunc, us.filestream,&us.gi.size_comment)!=UNZ_OK) + err=UNZ_ERRNO; + } + + if ((central_pospfile_in_zip_read!=NULL) + unzCloseCurrentFile(file); + + ZCLOSE64(s->z_filefunc, s->filestream); + TRYFREE(s); + return UNZ_OK; +} + + +/* + Write info about the ZipFile in the *pglobal_info structure. + No preparation of the structure is needed + return UNZ_OK if there is no problem. */ +extern int ZEXPORT unzGetGlobalInfo64 (unzFile file, unz_global_info64* pglobal_info) +{ + unz64_s* s; + if (file==NULL) + return UNZ_PARAMERROR; + s=(unz64_s*)file; + *pglobal_info=s->gi; + return UNZ_OK; +} + +extern int ZEXPORT unzGetGlobalInfo (unzFile file, unz_global_info* pglobal_info32) +{ + unz64_s* s; + if (file==NULL) + return UNZ_PARAMERROR; + s=(unz64_s*)file; + /* to do : check if number_entry is not truncated */ + pglobal_info32->number_entry = (uLong)s->gi.number_entry; + pglobal_info32->size_comment = s->gi.size_comment; + return UNZ_OK; +} +/* + Translate date/time from Dos format to tm_unz (readable more easilty) +*/ +local void unz64local_DosDateToTmuDate (ZPOS64_T ulDosDate, tm_unz* ptm) +{ + ZPOS64_T uDate; + uDate = (ZPOS64_T)(ulDosDate>>16); + ptm->tm_mday = (uInt)(uDate&0x1f) ; + ptm->tm_mon = (uInt)((((uDate)&0x1E0)/0x20)-1) ; + ptm->tm_year = (uInt)(((uDate&0x0FE00)/0x0200)+1980) ; + + ptm->tm_hour = (uInt) ((ulDosDate &0xF800)/0x800); + ptm->tm_min = (uInt) ((ulDosDate&0x7E0)/0x20) ; + ptm->tm_sec = (uInt) (2*(ulDosDate&0x1f)) ; +} + +/* + Get Info about the current file in the zipfile, with internal only info +*/ +local int unz64local_GetCurrentFileInfoInternal OF((unzFile file, + unz_file_info64 *pfile_info, + unz_file_info64_internal + *pfile_info_internal, + char *szFileName, + uLong fileNameBufferSize, + void *extraField, + uLong extraFieldBufferSize, + char *szComment, + uLong commentBufferSize)); + +local int unz64local_GetCurrentFileInfoInternal (unzFile file, + unz_file_info64 *pfile_info, + unz_file_info64_internal + *pfile_info_internal, + char *szFileName, + uLong fileNameBufferSize, + void *extraField, + uLong extraFieldBufferSize, + char *szComment, + uLong commentBufferSize) +{ + unz64_s* s; + unz_file_info64 file_info; + unz_file_info64_internal file_info_internal; + int err=UNZ_OK; + uLong uMagic; + long lSeek=0; + uLong uL; + + if (file==NULL) + return UNZ_PARAMERROR; + s=(unz64_s*)file; + if (ZSEEK64(s->z_filefunc, s->filestream, + s->pos_in_central_dir+s->byte_before_the_zipfile, + ZLIB_FILEFUNC_SEEK_SET)!=0) + err=UNZ_ERRNO; + + + /* we check the magic */ + if (err==UNZ_OK) + { + if (unz64local_getLong(&s->z_filefunc, s->filestream,&uMagic) != UNZ_OK) + err=UNZ_ERRNO; + else if (uMagic!=0x02014b50) + err=UNZ_BADZIPFILE; + } + + if (unz64local_getShort(&s->z_filefunc, s->filestream,&file_info.version) != UNZ_OK) + err=UNZ_ERRNO; + + if (unz64local_getShort(&s->z_filefunc, s->filestream,&file_info.version_needed) != UNZ_OK) + err=UNZ_ERRNO; + + if (unz64local_getShort(&s->z_filefunc, s->filestream,&file_info.flag) != UNZ_OK) + err=UNZ_ERRNO; + + if (unz64local_getShort(&s->z_filefunc, s->filestream,&file_info.compression_method) != UNZ_OK) + err=UNZ_ERRNO; + + if (unz64local_getLong(&s->z_filefunc, s->filestream,&file_info.dosDate) != UNZ_OK) + err=UNZ_ERRNO; + + unz64local_DosDateToTmuDate(file_info.dosDate,&file_info.tmu_date); + + if (unz64local_getLong(&s->z_filefunc, s->filestream,&file_info.crc) != UNZ_OK) + err=UNZ_ERRNO; + + if (unz64local_getLong(&s->z_filefunc, s->filestream,&uL) != UNZ_OK) + err=UNZ_ERRNO; + file_info.compressed_size = uL; + + if (unz64local_getLong(&s->z_filefunc, s->filestream,&uL) != UNZ_OK) + err=UNZ_ERRNO; + file_info.uncompressed_size = uL; + + if (unz64local_getShort(&s->z_filefunc, s->filestream,&file_info.size_filename) != UNZ_OK) + err=UNZ_ERRNO; + + if (unz64local_getShort(&s->z_filefunc, s->filestream,&file_info.size_file_extra) != UNZ_OK) + err=UNZ_ERRNO; + + if (unz64local_getShort(&s->z_filefunc, s->filestream,&file_info.size_file_comment) != UNZ_OK) + err=UNZ_ERRNO; + + if (unz64local_getShort(&s->z_filefunc, s->filestream,&file_info.disk_num_start) != UNZ_OK) + err=UNZ_ERRNO; + + if (unz64local_getShort(&s->z_filefunc, s->filestream,&file_info.internal_fa) != UNZ_OK) + err=UNZ_ERRNO; + + if (unz64local_getLong(&s->z_filefunc, s->filestream,&file_info.external_fa) != UNZ_OK) + err=UNZ_ERRNO; + + // relative offset of local header + if (unz64local_getLong(&s->z_filefunc, s->filestream,&uL) != UNZ_OK) + err=UNZ_ERRNO; + file_info_internal.offset_curfile = uL; + + lSeek+=file_info.size_filename; + if ((err==UNZ_OK) && (szFileName!=NULL)) + { + uLong uSizeRead ; + if (file_info.size_filename0) && (fileNameBufferSize>0)) + if (ZREAD64(s->z_filefunc, s->filestream,szFileName,uSizeRead)!=uSizeRead) + err=UNZ_ERRNO; + lSeek -= uSizeRead; + } + + // Read extrafield + if ((err==UNZ_OK) && (extraField!=NULL)) + { + ZPOS64_T uSizeRead ; + if (file_info.size_file_extraz_filefunc, s->filestream,lSeek,ZLIB_FILEFUNC_SEEK_CUR)==0) + lSeek=0; + else + err=UNZ_ERRNO; + } + + if ((file_info.size_file_extra>0) && (extraFieldBufferSize>0)) + if (ZREAD64(s->z_filefunc, s->filestream,extraField,(uLong)uSizeRead)!=uSizeRead) + err=UNZ_ERRNO; + + lSeek += file_info.size_file_extra - (uLong)uSizeRead; + } + else + lSeek += file_info.size_file_extra; + + + if ((err==UNZ_OK) && (file_info.size_file_extra != 0)) + { + uLong acc = 0; + + // since lSeek now points to after the extra field we need to move back + lSeek -= file_info.size_file_extra; + + if (lSeek!=0) + { + if (ZSEEK64(s->z_filefunc, s->filestream,lSeek,ZLIB_FILEFUNC_SEEK_CUR)==0) + lSeek=0; + else + err=UNZ_ERRNO; + } + + while(acc < file_info.size_file_extra) + { + uLong headerId; + uLong dataSize; + + if (unz64local_getShort(&s->z_filefunc, s->filestream,&headerId) != UNZ_OK) + err=UNZ_ERRNO; + + if (unz64local_getShort(&s->z_filefunc, s->filestream,&dataSize) != UNZ_OK) + err=UNZ_ERRNO; + + /* ZIP64 extra fields */ + if (headerId == 0x0001) + { + uLong uL; + + if(file_info.uncompressed_size == MAXU32) + { + if (unz64local_getLong64(&s->z_filefunc, s->filestream,&file_info.uncompressed_size) != UNZ_OK) + err=UNZ_ERRNO; + } + + if(file_info.compressed_size == MAXU32) + { + if (unz64local_getLong64(&s->z_filefunc, s->filestream,&file_info.compressed_size) != UNZ_OK) + err=UNZ_ERRNO; + } + + if(file_info_internal.offset_curfile == MAXU32) + { + /* Relative Header offset */ + if (unz64local_getLong64(&s->z_filefunc, s->filestream,&file_info_internal.offset_curfile) != UNZ_OK) + err=UNZ_ERRNO; + } + + if(file_info.disk_num_start == MAXU32) + { + /* Disk Start Number */ + if (unz64local_getLong(&s->z_filefunc, s->filestream,&uL) != UNZ_OK) + err=UNZ_ERRNO; + } + + } + else + { + if (ZSEEK64(s->z_filefunc, s->filestream,dataSize,ZLIB_FILEFUNC_SEEK_CUR)!=0) + err=UNZ_ERRNO; + } + + acc += 2 + 2 + dataSize; + } + } + + if ((err==UNZ_OK) && (szComment!=NULL)) + { + uLong uSizeRead ; + if (file_info.size_file_commentz_filefunc, s->filestream,lSeek,ZLIB_FILEFUNC_SEEK_CUR)==0) + lSeek=0; + else + err=UNZ_ERRNO; + } + + if ((file_info.size_file_comment>0) && (commentBufferSize>0)) + if (ZREAD64(s->z_filefunc, s->filestream,szComment,uSizeRead)!=uSizeRead) + err=UNZ_ERRNO; + lSeek+=file_info.size_file_comment - uSizeRead; + } + else + lSeek+=file_info.size_file_comment; + + + if ((err==UNZ_OK) && (pfile_info!=NULL)) + *pfile_info=file_info; + + if ((err==UNZ_OK) && (pfile_info_internal!=NULL)) + *pfile_info_internal=file_info_internal; + + return err; +} + + + +/* + Write info about the ZipFile in the *pglobal_info structure. + No preparation of the structure is needed + return UNZ_OK if there is no problem. +*/ +extern int ZEXPORT unzGetCurrentFileInfo64 (unzFile file, + unz_file_info64 * pfile_info, + char * szFileName, uLong fileNameBufferSize, + void *extraField, uLong extraFieldBufferSize, + char* szComment, uLong commentBufferSize) +{ + return unz64local_GetCurrentFileInfoInternal(file,pfile_info,NULL, + szFileName,fileNameBufferSize, + extraField,extraFieldBufferSize, + szComment,commentBufferSize); +} + +extern int ZEXPORT unzGetCurrentFileInfo (unzFile file, + unz_file_info * pfile_info, + char * szFileName, uLong fileNameBufferSize, + void *extraField, uLong extraFieldBufferSize, + char* szComment, uLong commentBufferSize) +{ + int err; + unz_file_info64 file_info64; + err = unz64local_GetCurrentFileInfoInternal(file,&file_info64,NULL, + szFileName,fileNameBufferSize, + extraField,extraFieldBufferSize, + szComment,commentBufferSize); + if ((err==UNZ_OK) && (pfile_info != NULL)) + { + pfile_info->version = file_info64.version; + pfile_info->version_needed = file_info64.version_needed; + pfile_info->flag = file_info64.flag; + pfile_info->compression_method = file_info64.compression_method; + pfile_info->dosDate = file_info64.dosDate; + pfile_info->crc = file_info64.crc; + + pfile_info->size_filename = file_info64.size_filename; + pfile_info->size_file_extra = file_info64.size_file_extra; + pfile_info->size_file_comment = file_info64.size_file_comment; + + pfile_info->disk_num_start = file_info64.disk_num_start; + pfile_info->internal_fa = file_info64.internal_fa; + pfile_info->external_fa = file_info64.external_fa; + + pfile_info->tmu_date = file_info64.tmu_date, + + + pfile_info->compressed_size = (uLong)file_info64.compressed_size; + pfile_info->uncompressed_size = (uLong)file_info64.uncompressed_size; + + } + return err; +} +/* + Set the current file of the zipfile to the first file. + return UNZ_OK if there is no problem +*/ +extern int ZEXPORT unzGoToFirstFile (unzFile file) +{ + int err=UNZ_OK; + unz64_s* s; + if (file==NULL) + return UNZ_PARAMERROR; + s=(unz64_s*)file; + s->pos_in_central_dir=s->offset_central_dir; + s->num_file=0; + err=unz64local_GetCurrentFileInfoInternal(file,&s->cur_file_info, + &s->cur_file_info_internal, + NULL,0,NULL,0,NULL,0); + s->current_file_ok = (err == UNZ_OK); + return err; +} + +/* + Set the current file of the zipfile to the next file. + return UNZ_OK if there is no problem + return UNZ_END_OF_LIST_OF_FILE if the actual file was the latest. +*/ +extern int ZEXPORT unzGoToNextFile (unzFile file) +{ + unz64_s* s; + int err; + + if (file==NULL) + return UNZ_PARAMERROR; + s=(unz64_s*)file; + if (!s->current_file_ok) + return UNZ_END_OF_LIST_OF_FILE; + if (s->gi.number_entry != 0xffff) /* 2^16 files overflow hack */ + if (s->num_file+1==s->gi.number_entry) + return UNZ_END_OF_LIST_OF_FILE; + + s->pos_in_central_dir += SIZECENTRALDIRITEM + s->cur_file_info.size_filename + + s->cur_file_info.size_file_extra + s->cur_file_info.size_file_comment ; + s->num_file++; + err = unz64local_GetCurrentFileInfoInternal(file,&s->cur_file_info, + &s->cur_file_info_internal, + NULL,0,NULL,0,NULL,0); + s->current_file_ok = (err == UNZ_OK); + return err; +} + + +/* + Try locate the file szFileName in the zipfile. + For the iCaseSensitivity signification, see unzStringFileNameCompare + + return value : + UNZ_OK if the file is found. It becomes the current file. + UNZ_END_OF_LIST_OF_FILE if the file is not found +*/ +extern int ZEXPORT unzLocateFile (unzFile file, const char *szFileName, int iCaseSensitivity) +{ + unz64_s* s; + int err; + + /* We remember the 'current' position in the file so that we can jump + * back there if we fail. + */ + unz_file_info64 cur_file_infoSaved; + unz_file_info64_internal cur_file_info_internalSaved; + ZPOS64_T num_fileSaved; + ZPOS64_T pos_in_central_dirSaved; + + + if (file==NULL) + return UNZ_PARAMERROR; + + if (strlen(szFileName)>=UNZ_MAXFILENAMEINZIP) + return UNZ_PARAMERROR; + + s=(unz64_s*)file; + if (!s->current_file_ok) + return UNZ_END_OF_LIST_OF_FILE; + + /* Save the current state */ + num_fileSaved = s->num_file; + pos_in_central_dirSaved = s->pos_in_central_dir; + cur_file_infoSaved = s->cur_file_info; + cur_file_info_internalSaved = s->cur_file_info_internal; + + err = unzGoToFirstFile(file); + + while (err == UNZ_OK) + { + char szCurrentFileName[UNZ_MAXFILENAMEINZIP+1]; + err = unzGetCurrentFileInfo64(file,NULL, + szCurrentFileName,sizeof(szCurrentFileName)-1, + NULL,0,NULL,0); + if (err == UNZ_OK) + { + if (unzStringFileNameCompare(szCurrentFileName, + szFileName,iCaseSensitivity)==0) + return UNZ_OK; + err = unzGoToNextFile(file); + } + } + + /* We failed, so restore the state of the 'current file' to where we + * were. + */ + s->num_file = num_fileSaved ; + s->pos_in_central_dir = pos_in_central_dirSaved ; + s->cur_file_info = cur_file_infoSaved; + s->cur_file_info_internal = cur_file_info_internalSaved; + return err; +} + + +/* +/////////////////////////////////////////// +// Contributed by Ryan Haksi (mailto://cryogen@infoserve.net) +// I need random access +// +// Further optimization could be realized by adding an ability +// to cache the directory in memory. The goal being a single +// comprehensive file read to put the file I need in a memory. +*/ + +/* +typedef struct unz_file_pos_s +{ + ZPOS64_T pos_in_zip_directory; // offset in file + ZPOS64_T num_of_file; // # of file +} unz_file_pos; +*/ + +extern int ZEXPORT unzGetFilePos64(unzFile file, unz64_file_pos* file_pos) +{ + unz64_s* s; + + if (file==NULL || file_pos==NULL) + return UNZ_PARAMERROR; + s=(unz64_s*)file; + if (!s->current_file_ok) + return UNZ_END_OF_LIST_OF_FILE; + + file_pos->pos_in_zip_directory = s->pos_in_central_dir; + file_pos->num_of_file = s->num_file; + + return UNZ_OK; +} + +extern int ZEXPORT unzGetFilePos( + unzFile file, + unz_file_pos* file_pos) +{ + unz64_file_pos file_pos64; + int err = unzGetFilePos64(file,&file_pos64); + if (err==UNZ_OK) + { + file_pos->pos_in_zip_directory = (uLong)file_pos64.pos_in_zip_directory; + file_pos->num_of_file = (uLong)file_pos64.num_of_file; + } + return err; +} + +extern int ZEXPORT unzGoToFilePos64(unzFile file, const unz64_file_pos* file_pos) +{ + unz64_s* s; + int err; + + if (file==NULL || file_pos==NULL) + return UNZ_PARAMERROR; + s=(unz64_s*)file; + + /* jump to the right spot */ + s->pos_in_central_dir = file_pos->pos_in_zip_directory; + s->num_file = file_pos->num_of_file; + + /* set the current file */ + err = unz64local_GetCurrentFileInfoInternal(file,&s->cur_file_info, + &s->cur_file_info_internal, + NULL,0,NULL,0,NULL,0); + /* return results */ + s->current_file_ok = (err == UNZ_OK); + return err; +} + +extern int ZEXPORT unzGoToFilePos( + unzFile file, + unz_file_pos* file_pos) +{ + unz64_file_pos file_pos64; + if (file_pos == NULL) + return UNZ_PARAMERROR; + + file_pos64.pos_in_zip_directory = file_pos->pos_in_zip_directory; + file_pos64.num_of_file = file_pos->num_of_file; + return unzGoToFilePos64(file,&file_pos64); +} + +/* +// Unzip Helper Functions - should be here? +/////////////////////////////////////////// +*/ + +/* + Read the local header of the current zipfile + Check the coherency of the local header and info in the end of central + directory about this file + store in *piSizeVar the size of extra info in local header + (filename and size of extra field data) +*/ +local int unz64local_CheckCurrentFileCoherencyHeader (unz64_s* s, uInt* piSizeVar, + ZPOS64_T * poffset_local_extrafield, + uInt * psize_local_extrafield) +{ + uLong uMagic,uData,uFlags; + uLong size_filename; + uLong size_extra_field; + int err=UNZ_OK; + + *piSizeVar = 0; + *poffset_local_extrafield = 0; + *psize_local_extrafield = 0; + + if (ZSEEK64(s->z_filefunc, s->filestream,s->cur_file_info_internal.offset_curfile + + s->byte_before_the_zipfile,ZLIB_FILEFUNC_SEEK_SET)!=0) + return UNZ_ERRNO; + + + if (err==UNZ_OK) + { + if (unz64local_getLong(&s->z_filefunc, s->filestream,&uMagic) != UNZ_OK) + err=UNZ_ERRNO; + else if (uMagic!=0x04034b50) + err=UNZ_BADZIPFILE; + } + + if (unz64local_getShort(&s->z_filefunc, s->filestream,&uData) != UNZ_OK) + err=UNZ_ERRNO; +/* + else if ((err==UNZ_OK) && (uData!=s->cur_file_info.wVersion)) + err=UNZ_BADZIPFILE; +*/ + if (unz64local_getShort(&s->z_filefunc, s->filestream,&uFlags) != UNZ_OK) + err=UNZ_ERRNO; + + if (unz64local_getShort(&s->z_filefunc, s->filestream,&uData) != UNZ_OK) + err=UNZ_ERRNO; + else if ((err==UNZ_OK) && (uData!=s->cur_file_info.compression_method)) + err=UNZ_BADZIPFILE; + + if ((err==UNZ_OK) && (s->cur_file_info.compression_method!=0) && +/* #ifdef HAVE_BZIP2 */ + (s->cur_file_info.compression_method!=Z_BZIP2ED) && +/* #endif */ + (s->cur_file_info.compression_method!=Z_DEFLATED)) + err=UNZ_BADZIPFILE; + + if (unz64local_getLong(&s->z_filefunc, s->filestream,&uData) != UNZ_OK) /* date/time */ + err=UNZ_ERRNO; + + if (unz64local_getLong(&s->z_filefunc, s->filestream,&uData) != UNZ_OK) /* crc */ + err=UNZ_ERRNO; + else if ((err==UNZ_OK) && (uData!=s->cur_file_info.crc) && ((uFlags & 8)==0)) + err=UNZ_BADZIPFILE; + + if (unz64local_getLong(&s->z_filefunc, s->filestream,&uData) != UNZ_OK) /* size compr */ + err=UNZ_ERRNO; + else if (uData != 0xFFFFFFFF && (err==UNZ_OK) && (uData!=s->cur_file_info.compressed_size) && ((uFlags & 8)==0)) + err=UNZ_BADZIPFILE; + + if (unz64local_getLong(&s->z_filefunc, s->filestream,&uData) != UNZ_OK) /* size uncompr */ + err=UNZ_ERRNO; + else if (uData != 0xFFFFFFFF && (err==UNZ_OK) && (uData!=s->cur_file_info.uncompressed_size) && ((uFlags & 8)==0)) + err=UNZ_BADZIPFILE; + + if (unz64local_getShort(&s->z_filefunc, s->filestream,&size_filename) != UNZ_OK) + err=UNZ_ERRNO; + else if ((err==UNZ_OK) && (size_filename!=s->cur_file_info.size_filename)) + err=UNZ_BADZIPFILE; + + *piSizeVar += (uInt)size_filename; + + if (unz64local_getShort(&s->z_filefunc, s->filestream,&size_extra_field) != UNZ_OK) + err=UNZ_ERRNO; + *poffset_local_extrafield= s->cur_file_info_internal.offset_curfile + + SIZEZIPLOCALHEADER + size_filename; + *psize_local_extrafield = (uInt)size_extra_field; + + *piSizeVar += (uInt)size_extra_field; + + return err; +} + +/* + Open for reading data the current file in the zipfile. + If there is no error and the file is opened, the return value is UNZ_OK. +*/ +extern int ZEXPORT unzOpenCurrentFile3 (unzFile file, int* method, + int* level, int raw, const char* password) +{ + int err=UNZ_OK; + uInt iSizeVar; + unz64_s* s; + file_in_zip64_read_info_s* pfile_in_zip_read_info; + ZPOS64_T offset_local_extrafield; /* offset of the local extra field */ + uInt size_local_extrafield; /* size of the local extra field */ +# ifndef NOUNCRYPT + char source[12]; +# else + if (password != NULL) + return UNZ_PARAMERROR; +# endif + + if (file==NULL) + return UNZ_PARAMERROR; + s=(unz64_s*)file; + if (!s->current_file_ok) + return UNZ_PARAMERROR; + + if (s->pfile_in_zip_read != NULL) + unzCloseCurrentFile(file); + + if (unz64local_CheckCurrentFileCoherencyHeader(s,&iSizeVar, &offset_local_extrafield,&size_local_extrafield)!=UNZ_OK) + return UNZ_BADZIPFILE; + + pfile_in_zip_read_info = (file_in_zip64_read_info_s*)ALLOC(sizeof(file_in_zip64_read_info_s)); + if (pfile_in_zip_read_info==NULL) + return UNZ_INTERNALERROR; + + pfile_in_zip_read_info->read_buffer=(char*)ALLOC(UNZ_BUFSIZE); + pfile_in_zip_read_info->offset_local_extrafield = offset_local_extrafield; + pfile_in_zip_read_info->size_local_extrafield = size_local_extrafield; + pfile_in_zip_read_info->pos_local_extrafield=0; + pfile_in_zip_read_info->raw=raw; + + if (pfile_in_zip_read_info->read_buffer==NULL) + { + TRYFREE(pfile_in_zip_read_info); + return UNZ_INTERNALERROR; + } + + pfile_in_zip_read_info->stream_initialised=0; + + if (method!=NULL) + *method = (int)s->cur_file_info.compression_method; + + if (level!=NULL) + { + *level = 6; + switch (s->cur_file_info.flag & 0x06) + { + case 6 : *level = 1; break; + case 4 : *level = 2; break; + case 2 : *level = 9; break; + } + } + + if ((s->cur_file_info.compression_method!=0) && +/* #ifdef HAVE_BZIP2 */ + (s->cur_file_info.compression_method!=Z_BZIP2ED) && +/* #endif */ + (s->cur_file_info.compression_method!=Z_DEFLATED)) + + err=UNZ_BADZIPFILE; + + pfile_in_zip_read_info->crc32_wait=s->cur_file_info.crc; + pfile_in_zip_read_info->crc32=0; + pfile_in_zip_read_info->total_out_64=0; + pfile_in_zip_read_info->compression_method = s->cur_file_info.compression_method; + pfile_in_zip_read_info->filestream=s->filestream; + pfile_in_zip_read_info->z_filefunc=s->z_filefunc; + pfile_in_zip_read_info->byte_before_the_zipfile=s->byte_before_the_zipfile; + + pfile_in_zip_read_info->stream.total_out = 0; + + if ((s->cur_file_info.compression_method==Z_BZIP2ED) && (!raw)) + { +#ifdef HAVE_BZIP2 + pfile_in_zip_read_info->bstream.bzalloc = (void *(*) (void *, int, int))0; + pfile_in_zip_read_info->bstream.bzfree = (free_func)0; + pfile_in_zip_read_info->bstream.opaque = (voidpf)0; + pfile_in_zip_read_info->bstream.state = (voidpf)0; + + pfile_in_zip_read_info->stream.zalloc = (alloc_func)0; + pfile_in_zip_read_info->stream.zfree = (free_func)0; + pfile_in_zip_read_info->stream.opaque = (voidpf)0; + pfile_in_zip_read_info->stream.next_in = (voidpf)0; + pfile_in_zip_read_info->stream.avail_in = 0; + + err=BZ2_bzDecompressInit(&pfile_in_zip_read_info->bstream, 0, 0); + if (err == Z_OK) + pfile_in_zip_read_info->stream_initialised=Z_BZIP2ED; + else + { + TRYFREE(pfile_in_zip_read_info); + return err; + } +#else + pfile_in_zip_read_info->raw=1; +#endif + } + else if ((s->cur_file_info.compression_method==Z_DEFLATED) && (!raw)) + { + pfile_in_zip_read_info->stream.zalloc = (alloc_func)0; + pfile_in_zip_read_info->stream.zfree = (free_func)0; + pfile_in_zip_read_info->stream.opaque = (voidpf)0; + pfile_in_zip_read_info->stream.next_in = 0; + pfile_in_zip_read_info->stream.avail_in = 0; + + err=inflateInit2(&pfile_in_zip_read_info->stream, -MAX_WBITS); + if (err == Z_OK) + pfile_in_zip_read_info->stream_initialised=Z_DEFLATED; + else + { + TRYFREE(pfile_in_zip_read_info); + return err; + } + /* windowBits is passed < 0 to tell that there is no zlib header. + * Note that in this case inflate *requires* an extra "dummy" byte + * after the compressed stream in order to complete decompression and + * return Z_STREAM_END. + * In unzip, i don't wait absolutely Z_STREAM_END because I known the + * size of both compressed and uncompressed data + */ + } + pfile_in_zip_read_info->rest_read_compressed = + s->cur_file_info.compressed_size ; + pfile_in_zip_read_info->rest_read_uncompressed = + s->cur_file_info.uncompressed_size ; + + + pfile_in_zip_read_info->pos_in_zipfile = + s->cur_file_info_internal.offset_curfile + SIZEZIPLOCALHEADER + + iSizeVar; + + pfile_in_zip_read_info->stream.avail_in = (uInt)0; + + s->pfile_in_zip_read = pfile_in_zip_read_info; + s->encrypted = 0; + +# ifndef NOUNCRYPT + if (password != NULL) + { + int i; + s->pcrc_32_tab = get_crc_table(); + init_keys(password,s->keys,s->pcrc_32_tab); + if (ZSEEK64(s->z_filefunc, s->filestream, + s->pfile_in_zip_read->pos_in_zipfile + + s->pfile_in_zip_read->byte_before_the_zipfile, + SEEK_SET)!=0) + return UNZ_INTERNALERROR; + if(ZREAD64(s->z_filefunc, s->filestream,source, 12)<12) + return UNZ_INTERNALERROR; + + for (i = 0; i<12; i++) + zdecode(s->keys,s->pcrc_32_tab,source[i]); + + s->pfile_in_zip_read->pos_in_zipfile+=12; + s->encrypted=1; + } +# endif + + + return UNZ_OK; +} + +extern int ZEXPORT unzOpenCurrentFile (unzFile file) +{ + return unzOpenCurrentFile3(file, NULL, NULL, 0, NULL); +} + +extern int ZEXPORT unzOpenCurrentFilePassword (unzFile file, const char* password) +{ + return unzOpenCurrentFile3(file, NULL, NULL, 0, password); +} + +extern int ZEXPORT unzOpenCurrentFile2 (unzFile file, int* method, int* level, int raw) +{ + return unzOpenCurrentFile3(file, method, level, raw, NULL); +} + +/** Addition for GDAL : START */ + +extern ZPOS64_T ZEXPORT unzGetCurrentFileZStreamPos64( unzFile file) +{ + unz64_s* s; + file_in_zip64_read_info_s* pfile_in_zip_read_info; + s=(unz64_s*)file; + if (file==NULL) + return 0; //UNZ_PARAMERROR; + pfile_in_zip_read_info=s->pfile_in_zip_read; + if (pfile_in_zip_read_info==NULL) + return 0; //UNZ_PARAMERROR; + return pfile_in_zip_read_info->pos_in_zipfile + + pfile_in_zip_read_info->byte_before_the_zipfile; +} + +/** Addition for GDAL : END */ + +/* + Read bytes from the current file. + buf contain buffer where data must be copied + len the size of buf. + + return the number of byte copied if somes bytes are copied + return 0 if the end of file was reached + return <0 with error code if there is an error + (UNZ_ERRNO for IO error, or zLib error for uncompress error) +*/ +extern int ZEXPORT unzReadCurrentFile (unzFile file, voidp buf, unsigned len) +{ + int err=UNZ_OK; + uInt iRead = 0; + unz64_s* s; + file_in_zip64_read_info_s* pfile_in_zip_read_info; + if (file==NULL) + return UNZ_PARAMERROR; + s=(unz64_s*)file; + pfile_in_zip_read_info=s->pfile_in_zip_read; + + if (pfile_in_zip_read_info==NULL) + return UNZ_PARAMERROR; + + + if (pfile_in_zip_read_info->read_buffer == NULL) + return UNZ_END_OF_LIST_OF_FILE; + if (len==0) + return 0; + + pfile_in_zip_read_info->stream.next_out = (Bytef*)buf; + + pfile_in_zip_read_info->stream.avail_out = (uInt)len; + + if ((len>pfile_in_zip_read_info->rest_read_uncompressed) && + (!(pfile_in_zip_read_info->raw))) + pfile_in_zip_read_info->stream.avail_out = + (uInt)pfile_in_zip_read_info->rest_read_uncompressed; + + if ((len>pfile_in_zip_read_info->rest_read_compressed+ + pfile_in_zip_read_info->stream.avail_in) && + (pfile_in_zip_read_info->raw)) + pfile_in_zip_read_info->stream.avail_out = + (uInt)pfile_in_zip_read_info->rest_read_compressed+ + pfile_in_zip_read_info->stream.avail_in; + + while (pfile_in_zip_read_info->stream.avail_out>0) + { + if ((pfile_in_zip_read_info->stream.avail_in==0) && + (pfile_in_zip_read_info->rest_read_compressed>0)) + { + uInt uReadThis = UNZ_BUFSIZE; + if (pfile_in_zip_read_info->rest_read_compressedrest_read_compressed; + if (uReadThis == 0) + return UNZ_EOF; + if (ZSEEK64(pfile_in_zip_read_info->z_filefunc, + pfile_in_zip_read_info->filestream, + pfile_in_zip_read_info->pos_in_zipfile + + pfile_in_zip_read_info->byte_before_the_zipfile, + ZLIB_FILEFUNC_SEEK_SET)!=0) + return UNZ_ERRNO; + if (ZREAD64(pfile_in_zip_read_info->z_filefunc, + pfile_in_zip_read_info->filestream, + pfile_in_zip_read_info->read_buffer, + uReadThis)!=uReadThis) + return UNZ_ERRNO; + + +# ifndef NOUNCRYPT + if(s->encrypted) + { + uInt i; + for(i=0;iread_buffer[i] = + zdecode(s->keys,s->pcrc_32_tab, + pfile_in_zip_read_info->read_buffer[i]); + } +# endif + + + pfile_in_zip_read_info->pos_in_zipfile += uReadThis; + + pfile_in_zip_read_info->rest_read_compressed-=uReadThis; + + pfile_in_zip_read_info->stream.next_in = + (Bytef*)pfile_in_zip_read_info->read_buffer; + pfile_in_zip_read_info->stream.avail_in = (uInt)uReadThis; + } + + if ((pfile_in_zip_read_info->compression_method==0) || (pfile_in_zip_read_info->raw)) + { + uInt uDoCopy,i ; + + if ((pfile_in_zip_read_info->stream.avail_in == 0) && + (pfile_in_zip_read_info->rest_read_compressed == 0)) + return (iRead==0) ? UNZ_EOF : iRead; + + if (pfile_in_zip_read_info->stream.avail_out < + pfile_in_zip_read_info->stream.avail_in) + uDoCopy = pfile_in_zip_read_info->stream.avail_out ; + else + uDoCopy = pfile_in_zip_read_info->stream.avail_in ; + + for (i=0;istream.next_out+i) = + *(pfile_in_zip_read_info->stream.next_in+i); + + pfile_in_zip_read_info->total_out_64 = pfile_in_zip_read_info->total_out_64 + uDoCopy; + + pfile_in_zip_read_info->crc32 = crc32(pfile_in_zip_read_info->crc32, + pfile_in_zip_read_info->stream.next_out, + uDoCopy); + pfile_in_zip_read_info->rest_read_uncompressed-=uDoCopy; + pfile_in_zip_read_info->stream.avail_in -= uDoCopy; + pfile_in_zip_read_info->stream.avail_out -= uDoCopy; + pfile_in_zip_read_info->stream.next_out += uDoCopy; + pfile_in_zip_read_info->stream.next_in += uDoCopy; + pfile_in_zip_read_info->stream.total_out += uDoCopy; + iRead += uDoCopy; + } + else if (pfile_in_zip_read_info->compression_method==Z_BZIP2ED) + { +#ifdef HAVE_BZIP2 + uLong uTotalOutBefore,uTotalOutAfter; + const Bytef *bufBefore; + uLong uOutThis; + + pfile_in_zip_read_info->bstream.next_in = (char*)pfile_in_zip_read_info->stream.next_in; + pfile_in_zip_read_info->bstream.avail_in = pfile_in_zip_read_info->stream.avail_in; + pfile_in_zip_read_info->bstream.total_in_lo32 = pfile_in_zip_read_info->stream.total_in; + pfile_in_zip_read_info->bstream.total_in_hi32 = 0; + pfile_in_zip_read_info->bstream.next_out = (char*)pfile_in_zip_read_info->stream.next_out; + pfile_in_zip_read_info->bstream.avail_out = pfile_in_zip_read_info->stream.avail_out; + pfile_in_zip_read_info->bstream.total_out_lo32 = pfile_in_zip_read_info->stream.total_out; + pfile_in_zip_read_info->bstream.total_out_hi32 = 0; + + uTotalOutBefore = pfile_in_zip_read_info->bstream.total_out_lo32; + bufBefore = (const Bytef *)pfile_in_zip_read_info->bstream.next_out; + + err=BZ2_bzDecompress(&pfile_in_zip_read_info->bstream); + + uTotalOutAfter = pfile_in_zip_read_info->bstream.total_out_lo32; + uOutThis = uTotalOutAfter-uTotalOutBefore; + + pfile_in_zip_read_info->total_out_64 = pfile_in_zip_read_info->total_out_64 + uOutThis; + + pfile_in_zip_read_info->crc32 = crc32(pfile_in_zip_read_info->crc32,bufBefore, (uInt)(uOutThis)); + pfile_in_zip_read_info->rest_read_uncompressed -= uOutThis; + iRead += (uInt)(uTotalOutAfter - uTotalOutBefore); + + pfile_in_zip_read_info->stream.next_in = (Bytef*)pfile_in_zip_read_info->bstream.next_in; + pfile_in_zip_read_info->stream.avail_in = pfile_in_zip_read_info->bstream.avail_in; + pfile_in_zip_read_info->stream.total_in = pfile_in_zip_read_info->bstream.total_in_lo32; + pfile_in_zip_read_info->stream.next_out = (Bytef*)pfile_in_zip_read_info->bstream.next_out; + pfile_in_zip_read_info->stream.avail_out = pfile_in_zip_read_info->bstream.avail_out; + pfile_in_zip_read_info->stream.total_out = pfile_in_zip_read_info->bstream.total_out_lo32; + + if (err==BZ_STREAM_END) + return (iRead==0) ? UNZ_EOF : iRead; + if (err!=BZ_OK) + break; +#endif + } // end Z_BZIP2ED + else + { + ZPOS64_T uTotalOutBefore,uTotalOutAfter; + const Bytef *bufBefore; + ZPOS64_T uOutThis; + int flush=Z_SYNC_FLUSH; + + uTotalOutBefore = pfile_in_zip_read_info->stream.total_out; + bufBefore = pfile_in_zip_read_info->stream.next_out; + + /* + if ((pfile_in_zip_read_info->rest_read_uncompressed == + pfile_in_zip_read_info->stream.avail_out) && + (pfile_in_zip_read_info->rest_read_compressed == 0)) + flush = Z_FINISH; + */ + err=inflate(&pfile_in_zip_read_info->stream,flush); + + if ((err>=0) && (pfile_in_zip_read_info->stream.msg!=NULL)) + err = Z_DATA_ERROR; + + uTotalOutAfter = pfile_in_zip_read_info->stream.total_out; + uOutThis = uTotalOutAfter-uTotalOutBefore; + + pfile_in_zip_read_info->total_out_64 = pfile_in_zip_read_info->total_out_64 + uOutThis; + + pfile_in_zip_read_info->crc32 = + crc32(pfile_in_zip_read_info->crc32,bufBefore, + (uInt)(uOutThis)); + + pfile_in_zip_read_info->rest_read_uncompressed -= + uOutThis; + + iRead += (uInt)(uTotalOutAfter - uTotalOutBefore); + + if (err==Z_STREAM_END) + return (iRead==0) ? UNZ_EOF : iRead; + if (err!=Z_OK) + break; + } + } + + if (err==Z_OK) + return iRead; + return err; +} + + +/* + Give the current position in uncompressed data +*/ +extern z_off_t ZEXPORT unztell (unzFile file) +{ + unz64_s* s; + file_in_zip64_read_info_s* pfile_in_zip_read_info; + if (file==NULL) + return UNZ_PARAMERROR; + s=(unz64_s*)file; + pfile_in_zip_read_info=s->pfile_in_zip_read; + + if (pfile_in_zip_read_info==NULL) + return UNZ_PARAMERROR; + + return (z_off_t)pfile_in_zip_read_info->stream.total_out; +} + +extern ZPOS64_T ZEXPORT unztell64 (unzFile file) +{ + + unz64_s* s; + file_in_zip64_read_info_s* pfile_in_zip_read_info; + if (file==NULL) + return (ZPOS64_T)-1; + s=(unz64_s*)file; + pfile_in_zip_read_info=s->pfile_in_zip_read; + + if (pfile_in_zip_read_info==NULL) + return (ZPOS64_T)-1; + + return pfile_in_zip_read_info->total_out_64; +} + + +/* + return 1 if the end of file was reached, 0 elsewhere +*/ +extern int ZEXPORT unzeof (unzFile file) +{ + unz64_s* s; + file_in_zip64_read_info_s* pfile_in_zip_read_info; + if (file==NULL) + return UNZ_PARAMERROR; + s=(unz64_s*)file; + pfile_in_zip_read_info=s->pfile_in_zip_read; + + if (pfile_in_zip_read_info==NULL) + return UNZ_PARAMERROR; + + if (pfile_in_zip_read_info->rest_read_uncompressed == 0) + return 1; + else + return 0; +} + + + +/* +Read extra field from the current file (opened by unzOpenCurrentFile) +This is the local-header version of the extra field (sometimes, there is +more info in the local-header version than in the central-header) + + if buf==NULL, it return the size of the local extra field that can be read + + if buf!=NULL, len is the size of the buffer, the extra header is copied in + buf. + the return value is the number of bytes copied in buf, or (if <0) + the error code +*/ +extern int ZEXPORT unzGetLocalExtrafield (unzFile file, voidp buf, unsigned len) +{ + unz64_s* s; + file_in_zip64_read_info_s* pfile_in_zip_read_info; + uInt read_now; + ZPOS64_T size_to_read; + + if (file==NULL) + return UNZ_PARAMERROR; + s=(unz64_s*)file; + pfile_in_zip_read_info=s->pfile_in_zip_read; + + if (pfile_in_zip_read_info==NULL) + return UNZ_PARAMERROR; + + size_to_read = (pfile_in_zip_read_info->size_local_extrafield - + pfile_in_zip_read_info->pos_local_extrafield); + + if (buf==NULL) + return (int)size_to_read; + + if (len>size_to_read) + read_now = (uInt)size_to_read; + else + read_now = (uInt)len ; + + if (read_now==0) + return 0; + + if (ZSEEK64(pfile_in_zip_read_info->z_filefunc, + pfile_in_zip_read_info->filestream, + pfile_in_zip_read_info->offset_local_extrafield + + pfile_in_zip_read_info->pos_local_extrafield, + ZLIB_FILEFUNC_SEEK_SET)!=0) + return UNZ_ERRNO; + + if (ZREAD64(pfile_in_zip_read_info->z_filefunc, + pfile_in_zip_read_info->filestream, + buf,read_now)!=read_now) + return UNZ_ERRNO; + + return (int)read_now; +} + +/* + Close the file in zip opened with unzOpenCurrentFile + Return UNZ_CRCERROR if all the file was read but the CRC is not good +*/ +extern int ZEXPORT unzCloseCurrentFile (unzFile file) +{ + int err=UNZ_OK; + + unz64_s* s; + file_in_zip64_read_info_s* pfile_in_zip_read_info; + if (file==NULL) + return UNZ_PARAMERROR; + s=(unz64_s*)file; + pfile_in_zip_read_info=s->pfile_in_zip_read; + + if (pfile_in_zip_read_info==NULL) + return UNZ_PARAMERROR; + + + if ((pfile_in_zip_read_info->rest_read_uncompressed == 0) && + (!pfile_in_zip_read_info->raw)) + { + if (pfile_in_zip_read_info->crc32 != pfile_in_zip_read_info->crc32_wait) + err=UNZ_CRCERROR; + } + + + TRYFREE(pfile_in_zip_read_info->read_buffer); + pfile_in_zip_read_info->read_buffer = NULL; + if (pfile_in_zip_read_info->stream_initialised == Z_DEFLATED) + inflateEnd(&pfile_in_zip_read_info->stream); +#ifdef HAVE_BZIP2 + else if (pfile_in_zip_read_info->stream_initialised == Z_BZIP2ED) + BZ2_bzDecompressEnd(&pfile_in_zip_read_info->bstream); +#endif + + + pfile_in_zip_read_info->stream_initialised = 0; + TRYFREE(pfile_in_zip_read_info); + + s->pfile_in_zip_read=NULL; + + return err; +} + + +/* + Get the global comment string of the ZipFile, in the szComment buffer. + uSizeBuf is the size of the szComment buffer. + return the number of byte copied or an error code <0 +*/ +extern int ZEXPORT unzGetGlobalComment (unzFile file, char * szComment, uLong uSizeBuf) +{ + unz64_s* s; + uLong uReadThis ; + if (file==NULL) + return (int)UNZ_PARAMERROR; + s=(unz64_s*)file; + + uReadThis = uSizeBuf; + if (uReadThis>s->gi.size_comment) + uReadThis = s->gi.size_comment; + + if (ZSEEK64(s->z_filefunc,s->filestream,s->central_pos+22,ZLIB_FILEFUNC_SEEK_SET)!=0) + return UNZ_ERRNO; + + if (uReadThis>0) + { + *szComment='\0'; + if (ZREAD64(s->z_filefunc,s->filestream,szComment,uReadThis)!=uReadThis) + return UNZ_ERRNO; + } + + if ((szComment != NULL) && (uSizeBuf > s->gi.size_comment)) + *(szComment+s->gi.size_comment)='\0'; + return (int)uReadThis; +} + +/* Additions by RX '2004 */ +extern ZPOS64_T ZEXPORT unzGetOffset64(unzFile file) +{ + unz64_s* s; + + if (file==NULL) + return 0; //UNZ_PARAMERROR; + s=(unz64_s*)file; + if (!s->current_file_ok) + return 0; + if (s->gi.number_entry != 0 && s->gi.number_entry != 0xffff) + if (s->num_file==s->gi.number_entry) + return 0; + return s->pos_in_central_dir; +} + +extern uLong ZEXPORT unzGetOffset (unzFile file) +{ + ZPOS64_T offset64; + + if (file==NULL) + return 0; //UNZ_PARAMERROR; + offset64 = unzGetOffset64(file); + return (uLong)offset64; +} + +extern int ZEXPORT unzSetOffset64(unzFile file, ZPOS64_T pos) +{ + unz64_s* s; + int err; + + if (file==NULL) + return UNZ_PARAMERROR; + s=(unz64_s*)file; + + s->pos_in_central_dir = pos; + s->num_file = s->gi.number_entry; /* hack */ + err = unz64local_GetCurrentFileInfoInternal(file,&s->cur_file_info, + &s->cur_file_info_internal, + NULL,0,NULL,0,NULL,0); + s->current_file_ok = (err == UNZ_OK); + return err; +} + +extern int ZEXPORT unzSetOffset (unzFile file, uLong pos) +{ + return unzSetOffset64(file,pos); +} ADDED compat/zlib/contrib/minizip/unzip.h Index: compat/zlib/contrib/minizip/unzip.h ================================================================== --- /dev/null +++ compat/zlib/contrib/minizip/unzip.h @@ -0,0 +1,437 @@ +/* unzip.h -- IO for uncompress .zip files using zlib + Version 1.1, February 14h, 2010 + part of the MiniZip project - ( http://www.winimage.com/zLibDll/minizip.html ) + + Copyright (C) 1998-2010 Gilles Vollant (minizip) ( http://www.winimage.com/zLibDll/minizip.html ) + + Modifications of Unzip for Zip64 + Copyright (C) 2007-2008 Even Rouault + + Modifications for Zip64 support on both zip and unzip + Copyright (C) 2009-2010 Mathias Svensson ( http://result42.com ) + + For more info read MiniZip_info.txt + + --------------------------------------------------------------------------------- + + Condition of use and distribution are the same than zlib : + + This software is provided 'as-is', without any express or implied + warranty. In no event will the authors be held liable for any damages + arising from the use of this software. + + Permission is granted to anyone to use this software for any purpose, + including commercial applications, and to alter it and redistribute it + freely, subject to the following restrictions: + + 1. The origin of this software must not be misrepresented; you must not + claim that you wrote the original software. If you use this software + in a product, an acknowledgment in the product documentation would be + appreciated but is not required. + 2. Altered source versions must be plainly marked as such, and must not be + misrepresented as being the original software. + 3. This notice may not be removed or altered from any source distribution. + + --------------------------------------------------------------------------------- + + Changes + + See header of unzip64.c + +*/ + +#ifndef _unz64_H +#define _unz64_H + +#ifdef __cplusplus +extern "C" { +#endif + +#ifndef _ZLIB_H +#include "zlib.h" +#endif + +#ifndef _ZLIBIOAPI_H +#include "ioapi.h" +#endif + +#ifdef HAVE_BZIP2 +#include "bzlib.h" +#endif + +#define Z_BZIP2ED 12 + +#if defined(STRICTUNZIP) || defined(STRICTZIPUNZIP) +/* like the STRICT of WIN32, we define a pointer that cannot be converted + from (void*) without cast */ +typedef struct TagunzFile__ { int unused; } unzFile__; +typedef unzFile__ *unzFile; +#else +typedef voidp unzFile; +#endif + + +#define UNZ_OK (0) +#define UNZ_END_OF_LIST_OF_FILE (-100) +#define UNZ_ERRNO (Z_ERRNO) +#define UNZ_EOF (0) +#define UNZ_PARAMERROR (-102) +#define UNZ_BADZIPFILE (-103) +#define UNZ_INTERNALERROR (-104) +#define UNZ_CRCERROR (-105) + +/* tm_unz contain date/time info */ +typedef struct tm_unz_s +{ + uInt tm_sec; /* seconds after the minute - [0,59] */ + uInt tm_min; /* minutes after the hour - [0,59] */ + uInt tm_hour; /* hours since midnight - [0,23] */ + uInt tm_mday; /* day of the month - [1,31] */ + uInt tm_mon; /* months since January - [0,11] */ + uInt tm_year; /* years - [1980..2044] */ +} tm_unz; + +/* unz_global_info structure contain global data about the ZIPfile + These data comes from the end of central dir */ +typedef struct unz_global_info64_s +{ + ZPOS64_T number_entry; /* total number of entries in + the central dir on this disk */ + uLong size_comment; /* size of the global comment of the zipfile */ +} unz_global_info64; + +typedef struct unz_global_info_s +{ + uLong number_entry; /* total number of entries in + the central dir on this disk */ + uLong size_comment; /* size of the global comment of the zipfile */ +} unz_global_info; + +/* unz_file_info contain information about a file in the zipfile */ +typedef struct unz_file_info64_s +{ + uLong version; /* version made by 2 bytes */ + uLong version_needed; /* version needed to extract 2 bytes */ + uLong flag; /* general purpose bit flag 2 bytes */ + uLong compression_method; /* compression method 2 bytes */ + uLong dosDate; /* last mod file date in Dos fmt 4 bytes */ + uLong crc; /* crc-32 4 bytes */ + ZPOS64_T compressed_size; /* compressed size 8 bytes */ + ZPOS64_T uncompressed_size; /* uncompressed size 8 bytes */ + uLong size_filename; /* filename length 2 bytes */ + uLong size_file_extra; /* extra field length 2 bytes */ + uLong size_file_comment; /* file comment length 2 bytes */ + + uLong disk_num_start; /* disk number start 2 bytes */ + uLong internal_fa; /* internal file attributes 2 bytes */ + uLong external_fa; /* external file attributes 4 bytes */ + + tm_unz tmu_date; +} unz_file_info64; + +typedef struct unz_file_info_s +{ + uLong version; /* version made by 2 bytes */ + uLong version_needed; /* version needed to extract 2 bytes */ + uLong flag; /* general purpose bit flag 2 bytes */ + uLong compression_method; /* compression method 2 bytes */ + uLong dosDate; /* last mod file date in Dos fmt 4 bytes */ + uLong crc; /* crc-32 4 bytes */ + uLong compressed_size; /* compressed size 4 bytes */ + uLong uncompressed_size; /* uncompressed size 4 bytes */ + uLong size_filename; /* filename length 2 bytes */ + uLong size_file_extra; /* extra field length 2 bytes */ + uLong size_file_comment; /* file comment length 2 bytes */ + + uLong disk_num_start; /* disk number start 2 bytes */ + uLong internal_fa; /* internal file attributes 2 bytes */ + uLong external_fa; /* external file attributes 4 bytes */ + + tm_unz tmu_date; +} unz_file_info; + +extern int ZEXPORT unzStringFileNameCompare OF ((const char* fileName1, + const char* fileName2, + int iCaseSensitivity)); +/* + Compare two filename (fileName1,fileName2). + If iCaseSenisivity = 1, comparision is case sensitivity (like strcmp) + If iCaseSenisivity = 2, comparision is not case sensitivity (like strcmpi + or strcasecmp) + If iCaseSenisivity = 0, case sensitivity is defaut of your operating system + (like 1 on Unix, 2 on Windows) +*/ + + +extern unzFile ZEXPORT unzOpen OF((const char *path)); +extern unzFile ZEXPORT unzOpen64 OF((const void *path)); +/* + Open a Zip file. path contain the full pathname (by example, + on a Windows XP computer "c:\\zlib\\zlib113.zip" or on an Unix computer + "zlib/zlib113.zip". + If the zipfile cannot be opened (file don't exist or in not valid), the + return value is NULL. + Else, the return value is a unzFile Handle, usable with other function + of this unzip package. + the "64" function take a const void* pointer, because the path is just the + value passed to the open64_file_func callback. + Under Windows, if UNICODE is defined, using fill_fopen64_filefunc, the path + is a pointer to a wide unicode string (LPCTSTR is LPCWSTR), so const char* + does not describe the reality +*/ + + +extern unzFile ZEXPORT unzOpen2 OF((const char *path, + zlib_filefunc_def* pzlib_filefunc_def)); +/* + Open a Zip file, like unzOpen, but provide a set of file low level API + for read/write the zip file (see ioapi.h) +*/ + +extern unzFile ZEXPORT unzOpen2_64 OF((const void *path, + zlib_filefunc64_def* pzlib_filefunc_def)); +/* + Open a Zip file, like unz64Open, but provide a set of file low level API + for read/write the zip file (see ioapi.h) +*/ + +extern int ZEXPORT unzClose OF((unzFile file)); +/* + Close a ZipFile opened with unzOpen. + If there is files inside the .Zip opened with unzOpenCurrentFile (see later), + these files MUST be closed with unzCloseCurrentFile before call unzClose. + return UNZ_OK if there is no problem. */ + +extern int ZEXPORT unzGetGlobalInfo OF((unzFile file, + unz_global_info *pglobal_info)); + +extern int ZEXPORT unzGetGlobalInfo64 OF((unzFile file, + unz_global_info64 *pglobal_info)); +/* + Write info about the ZipFile in the *pglobal_info structure. + No preparation of the structure is needed + return UNZ_OK if there is no problem. */ + + +extern int ZEXPORT unzGetGlobalComment OF((unzFile file, + char *szComment, + uLong uSizeBuf)); +/* + Get the global comment string of the ZipFile, in the szComment buffer. + uSizeBuf is the size of the szComment buffer. + return the number of byte copied or an error code <0 +*/ + + +/***************************************************************************/ +/* Unzip package allow you browse the directory of the zipfile */ + +extern int ZEXPORT unzGoToFirstFile OF((unzFile file)); +/* + Set the current file of the zipfile to the first file. + return UNZ_OK if there is no problem +*/ + +extern int ZEXPORT unzGoToNextFile OF((unzFile file)); +/* + Set the current file of the zipfile to the next file. + return UNZ_OK if there is no problem + return UNZ_END_OF_LIST_OF_FILE if the actual file was the latest. +*/ + +extern int ZEXPORT unzLocateFile OF((unzFile file, + const char *szFileName, + int iCaseSensitivity)); +/* + Try locate the file szFileName in the zipfile. + For the iCaseSensitivity signification, see unzStringFileNameCompare + + return value : + UNZ_OK if the file is found. It becomes the current file. + UNZ_END_OF_LIST_OF_FILE if the file is not found +*/ + + +/* ****************************************** */ +/* Ryan supplied functions */ +/* unz_file_info contain information about a file in the zipfile */ +typedef struct unz_file_pos_s +{ + uLong pos_in_zip_directory; /* offset in zip file directory */ + uLong num_of_file; /* # of file */ +} unz_file_pos; + +extern int ZEXPORT unzGetFilePos( + unzFile file, + unz_file_pos* file_pos); + +extern int ZEXPORT unzGoToFilePos( + unzFile file, + unz_file_pos* file_pos); + +typedef struct unz64_file_pos_s +{ + ZPOS64_T pos_in_zip_directory; /* offset in zip file directory */ + ZPOS64_T num_of_file; /* # of file */ +} unz64_file_pos; + +extern int ZEXPORT unzGetFilePos64( + unzFile file, + unz64_file_pos* file_pos); + +extern int ZEXPORT unzGoToFilePos64( + unzFile file, + const unz64_file_pos* file_pos); + +/* ****************************************** */ + +extern int ZEXPORT unzGetCurrentFileInfo64 OF((unzFile file, + unz_file_info64 *pfile_info, + char *szFileName, + uLong fileNameBufferSize, + void *extraField, + uLong extraFieldBufferSize, + char *szComment, + uLong commentBufferSize)); + +extern int ZEXPORT unzGetCurrentFileInfo OF((unzFile file, + unz_file_info *pfile_info, + char *szFileName, + uLong fileNameBufferSize, + void *extraField, + uLong extraFieldBufferSize, + char *szComment, + uLong commentBufferSize)); +/* + Get Info about the current file + if pfile_info!=NULL, the *pfile_info structure will contain somes info about + the current file + if szFileName!=NULL, the filemane string will be copied in szFileName + (fileNameBufferSize is the size of the buffer) + if extraField!=NULL, the extra field information will be copied in extraField + (extraFieldBufferSize is the size of the buffer). + This is the Central-header version of the extra field + if szComment!=NULL, the comment string of the file will be copied in szComment + (commentBufferSize is the size of the buffer) +*/ + + +/** Addition for GDAL : START */ + +extern ZPOS64_T ZEXPORT unzGetCurrentFileZStreamPos64 OF((unzFile file)); + +/** Addition for GDAL : END */ + + +/***************************************************************************/ +/* for reading the content of the current zipfile, you can open it, read data + from it, and close it (you can close it before reading all the file) + */ + +extern int ZEXPORT unzOpenCurrentFile OF((unzFile file)); +/* + Open for reading data the current file in the zipfile. + If there is no error, the return value is UNZ_OK. +*/ + +extern int ZEXPORT unzOpenCurrentFilePassword OF((unzFile file, + const char* password)); +/* + Open for reading data the current file in the zipfile. + password is a crypting password + If there is no error, the return value is UNZ_OK. +*/ + +extern int ZEXPORT unzOpenCurrentFile2 OF((unzFile file, + int* method, + int* level, + int raw)); +/* + Same than unzOpenCurrentFile, but open for read raw the file (not uncompress) + if raw==1 + *method will receive method of compression, *level will receive level of + compression + note : you can set level parameter as NULL (if you did not want known level, + but you CANNOT set method parameter as NULL +*/ + +extern int ZEXPORT unzOpenCurrentFile3 OF((unzFile file, + int* method, + int* level, + int raw, + const char* password)); +/* + Same than unzOpenCurrentFile, but open for read raw the file (not uncompress) + if raw==1 + *method will receive method of compression, *level will receive level of + compression + note : you can set level parameter as NULL (if you did not want known level, + but you CANNOT set method parameter as NULL +*/ + + +extern int ZEXPORT unzCloseCurrentFile OF((unzFile file)); +/* + Close the file in zip opened with unzOpenCurrentFile + Return UNZ_CRCERROR if all the file was read but the CRC is not good +*/ + +extern int ZEXPORT unzReadCurrentFile OF((unzFile file, + voidp buf, + unsigned len)); +/* + Read bytes from the current file (opened by unzOpenCurrentFile) + buf contain buffer where data must be copied + len the size of buf. + + return the number of byte copied if somes bytes are copied + return 0 if the end of file was reached + return <0 with error code if there is an error + (UNZ_ERRNO for IO error, or zLib error for uncompress error) +*/ + +extern z_off_t ZEXPORT unztell OF((unzFile file)); + +extern ZPOS64_T ZEXPORT unztell64 OF((unzFile file)); +/* + Give the current position in uncompressed data +*/ + +extern int ZEXPORT unzeof OF((unzFile file)); +/* + return 1 if the end of file was reached, 0 elsewhere +*/ + +extern int ZEXPORT unzGetLocalExtrafield OF((unzFile file, + voidp buf, + unsigned len)); +/* + Read extra field from the current file (opened by unzOpenCurrentFile) + This is the local-header version of the extra field (sometimes, there is + more info in the local-header version than in the central-header) + + if buf==NULL, it return the size of the local extra field + + if buf!=NULL, len is the size of the buffer, the extra header is copied in + buf. + the return value is the number of bytes copied in buf, or (if <0) + the error code +*/ + +/***************************************************************************/ + +/* Get the current file offset */ +extern ZPOS64_T ZEXPORT unzGetOffset64 (unzFile file); +extern uLong ZEXPORT unzGetOffset (unzFile file); + +/* Set the current file offset */ +extern int ZEXPORT unzSetOffset64 (unzFile file, ZPOS64_T pos); +extern int ZEXPORT unzSetOffset (unzFile file, uLong pos); + + + +#ifdef __cplusplus +} +#endif + +#endif /* _unz64_H */ ADDED compat/zlib/contrib/minizip/zip.c Index: compat/zlib/contrib/minizip/zip.c ================================================================== --- /dev/null +++ compat/zlib/contrib/minizip/zip.c @@ -0,0 +1,2007 @@ +/* zip.c -- IO on .zip files using zlib + Version 1.1, February 14h, 2010 + part of the MiniZip project - ( http://www.winimage.com/zLibDll/minizip.html ) + + Copyright (C) 1998-2010 Gilles Vollant (minizip) ( http://www.winimage.com/zLibDll/minizip.html ) + + Modifications for Zip64 support + Copyright (C) 2009-2010 Mathias Svensson ( http://result42.com ) + + For more info read MiniZip_info.txt + + Changes + Oct-2009 - Mathias Svensson - Remove old C style function prototypes + Oct-2009 - Mathias Svensson - Added Zip64 Support when creating new file archives + Oct-2009 - Mathias Svensson - Did some code cleanup and refactoring to get better overview of some functions. + Oct-2009 - Mathias Svensson - Added zipRemoveExtraInfoBlock to strip extra field data from its ZIP64 data + It is used when recreting zip archive with RAW when deleting items from a zip. + ZIP64 data is automatically added to items that needs it, and existing ZIP64 data need to be removed. + Oct-2009 - Mathias Svensson - Added support for BZIP2 as compression mode (bzip2 lib is required) + Jan-2010 - back to unzip and minizip 1.0 name scheme, with compatibility layer + +*/ + + +#include +#include +#include +#include +#include "zlib.h" +#include "zip.h" + +#ifdef STDC +# include +# include +# include +#endif +#ifdef NO_ERRNO_H + extern int errno; +#else +# include +#endif + + +#ifndef local +# define local static +#endif +/* compile with -Dlocal if your debugger can't find static symbols */ + +#ifndef VERSIONMADEBY +# define VERSIONMADEBY (0x0) /* platform depedent */ +#endif + +#ifndef Z_BUFSIZE +#define Z_BUFSIZE (64*1024) //(16384) +#endif + +#ifndef Z_MAXFILENAMEINZIP +#define Z_MAXFILENAMEINZIP (256) +#endif + +#ifndef ALLOC +# define ALLOC(size) (malloc(size)) +#endif +#ifndef TRYFREE +# define TRYFREE(p) {if (p) free(p);} +#endif + +/* +#define SIZECENTRALDIRITEM (0x2e) +#define SIZEZIPLOCALHEADER (0x1e) +*/ + +/* I've found an old Unix (a SunOS 4.1.3_U1) without all SEEK_* defined.... */ + + +// NOT sure that this work on ALL platform +#define MAKEULONG64(a, b) ((ZPOS64_T)(((unsigned long)(a)) | ((ZPOS64_T)((unsigned long)(b))) << 32)) + +#ifndef SEEK_CUR +#define SEEK_CUR 1 +#endif + +#ifndef SEEK_END +#define SEEK_END 2 +#endif + +#ifndef SEEK_SET +#define SEEK_SET 0 +#endif + +#ifndef DEF_MEM_LEVEL +#if MAX_MEM_LEVEL >= 8 +# define DEF_MEM_LEVEL 8 +#else +# define DEF_MEM_LEVEL MAX_MEM_LEVEL +#endif +#endif +const char zip_copyright[] =" zip 1.01 Copyright 1998-2004 Gilles Vollant - http://www.winimage.com/zLibDll"; + + +#define SIZEDATA_INDATABLOCK (4096-(4*4)) + +#define LOCALHEADERMAGIC (0x04034b50) +#define CENTRALHEADERMAGIC (0x02014b50) +#define ENDHEADERMAGIC (0x06054b50) +#define ZIP64ENDHEADERMAGIC (0x6064b50) +#define ZIP64ENDLOCHEADERMAGIC (0x7064b50) + +#define FLAG_LOCALHEADER_OFFSET (0x06) +#define CRC_LOCALHEADER_OFFSET (0x0e) + +#define SIZECENTRALHEADER (0x2e) /* 46 */ + +typedef struct linkedlist_datablock_internal_s +{ + struct linkedlist_datablock_internal_s* next_datablock; + uLong avail_in_this_block; + uLong filled_in_this_block; + uLong unused; /* for future use and alignment */ + unsigned char data[SIZEDATA_INDATABLOCK]; +} linkedlist_datablock_internal; + +typedef struct linkedlist_data_s +{ + linkedlist_datablock_internal* first_block; + linkedlist_datablock_internal* last_block; +} linkedlist_data; + + +typedef struct +{ + z_stream stream; /* zLib stream structure for inflate */ +#ifdef HAVE_BZIP2 + bz_stream bstream; /* bzLib stream structure for bziped */ +#endif + + int stream_initialised; /* 1 is stream is initialised */ + uInt pos_in_buffered_data; /* last written byte in buffered_data */ + + ZPOS64_T pos_local_header; /* offset of the local header of the file + currenty writing */ + char* central_header; /* central header data for the current file */ + uLong size_centralExtra; + uLong size_centralheader; /* size of the central header for cur file */ + uLong size_centralExtraFree; /* Extra bytes allocated to the centralheader but that are not used */ + uLong flag; /* flag of the file currently writing */ + + int method; /* compression method of file currenty wr.*/ + int raw; /* 1 for directly writing raw data */ + Byte buffered_data[Z_BUFSIZE];/* buffer contain compressed data to be writ*/ + uLong dosDate; + uLong crc32; + int encrypt; + int zip64; /* Add ZIP64 extened information in the extra field */ + ZPOS64_T pos_zip64extrainfo; + ZPOS64_T totalCompressedData; + ZPOS64_T totalUncompressedData; +#ifndef NOCRYPT + unsigned long keys[3]; /* keys defining the pseudo-random sequence */ + const z_crc_t* pcrc_32_tab; + int crypt_header_size; +#endif +} curfile64_info; + +typedef struct +{ + zlib_filefunc64_32_def z_filefunc; + voidpf filestream; /* io structore of the zipfile */ + linkedlist_data central_dir;/* datablock with central dir in construction*/ + int in_opened_file_inzip; /* 1 if a file in the zip is currently writ.*/ + curfile64_info ci; /* info on the file curretly writing */ + + ZPOS64_T begin_pos; /* position of the beginning of the zipfile */ + ZPOS64_T add_position_when_writing_offset; + ZPOS64_T number_entry; + +#ifndef NO_ADDFILEINEXISTINGZIP + char *globalcomment; +#endif + +} zip64_internal; + + +#ifndef NOCRYPT +#define INCLUDECRYPTINGCODE_IFCRYPTALLOWED +#include "crypt.h" +#endif + +local linkedlist_datablock_internal* allocate_new_datablock() +{ + linkedlist_datablock_internal* ldi; + ldi = (linkedlist_datablock_internal*) + ALLOC(sizeof(linkedlist_datablock_internal)); + if (ldi!=NULL) + { + ldi->next_datablock = NULL ; + ldi->filled_in_this_block = 0 ; + ldi->avail_in_this_block = SIZEDATA_INDATABLOCK ; + } + return ldi; +} + +local void free_datablock(linkedlist_datablock_internal* ldi) +{ + while (ldi!=NULL) + { + linkedlist_datablock_internal* ldinext = ldi->next_datablock; + TRYFREE(ldi); + ldi = ldinext; + } +} + +local void init_linkedlist(linkedlist_data* ll) +{ + ll->first_block = ll->last_block = NULL; +} + +local void free_linkedlist(linkedlist_data* ll) +{ + free_datablock(ll->first_block); + ll->first_block = ll->last_block = NULL; +} + + +local int add_data_in_datablock(linkedlist_data* ll, const void* buf, uLong len) +{ + linkedlist_datablock_internal* ldi; + const unsigned char* from_copy; + + if (ll==NULL) + return ZIP_INTERNALERROR; + + if (ll->last_block == NULL) + { + ll->first_block = ll->last_block = allocate_new_datablock(); + if (ll->first_block == NULL) + return ZIP_INTERNALERROR; + } + + ldi = ll->last_block; + from_copy = (unsigned char*)buf; + + while (len>0) + { + uInt copy_this; + uInt i; + unsigned char* to_copy; + + if (ldi->avail_in_this_block==0) + { + ldi->next_datablock = allocate_new_datablock(); + if (ldi->next_datablock == NULL) + return ZIP_INTERNALERROR; + ldi = ldi->next_datablock ; + ll->last_block = ldi; + } + + if (ldi->avail_in_this_block < len) + copy_this = (uInt)ldi->avail_in_this_block; + else + copy_this = (uInt)len; + + to_copy = &(ldi->data[ldi->filled_in_this_block]); + + for (i=0;ifilled_in_this_block += copy_this; + ldi->avail_in_this_block -= copy_this; + from_copy += copy_this ; + len -= copy_this; + } + return ZIP_OK; +} + + + +/****************************************************************************/ + +#ifndef NO_ADDFILEINEXISTINGZIP +/* =========================================================================== + Inputs a long in LSB order to the given file + nbByte == 1, 2 ,4 or 8 (byte, short or long, ZPOS64_T) +*/ + +local int zip64local_putValue OF((const zlib_filefunc64_32_def* pzlib_filefunc_def, voidpf filestream, ZPOS64_T x, int nbByte)); +local int zip64local_putValue (const zlib_filefunc64_32_def* pzlib_filefunc_def, voidpf filestream, ZPOS64_T x, int nbByte) +{ + unsigned char buf[8]; + int n; + for (n = 0; n < nbByte; n++) + { + buf[n] = (unsigned char)(x & 0xff); + x >>= 8; + } + if (x != 0) + { /* data overflow - hack for ZIP64 (X Roche) */ + for (n = 0; n < nbByte; n++) + { + buf[n] = 0xff; + } + } + + if (ZWRITE64(*pzlib_filefunc_def,filestream,buf,nbByte)!=(uLong)nbByte) + return ZIP_ERRNO; + else + return ZIP_OK; +} + +local void zip64local_putValue_inmemory OF((void* dest, ZPOS64_T x, int nbByte)); +local void zip64local_putValue_inmemory (void* dest, ZPOS64_T x, int nbByte) +{ + unsigned char* buf=(unsigned char*)dest; + int n; + for (n = 0; n < nbByte; n++) { + buf[n] = (unsigned char)(x & 0xff); + x >>= 8; + } + + if (x != 0) + { /* data overflow - hack for ZIP64 */ + for (n = 0; n < nbByte; n++) + { + buf[n] = 0xff; + } + } +} + +/****************************************************************************/ + + +local uLong zip64local_TmzDateToDosDate(const tm_zip* ptm) +{ + uLong year = (uLong)ptm->tm_year; + if (year>=1980) + year-=1980; + else if (year>=80) + year-=80; + return + (uLong) (((ptm->tm_mday) + (32 * (ptm->tm_mon+1)) + (512 * year)) << 16) | + ((ptm->tm_sec/2) + (32* ptm->tm_min) + (2048 * (uLong)ptm->tm_hour)); +} + + +/****************************************************************************/ + +local int zip64local_getByte OF((const zlib_filefunc64_32_def* pzlib_filefunc_def, voidpf filestream, int *pi)); + +local int zip64local_getByte(const zlib_filefunc64_32_def* pzlib_filefunc_def,voidpf filestream,int* pi) +{ + unsigned char c; + int err = (int)ZREAD64(*pzlib_filefunc_def,filestream,&c,1); + if (err==1) + { + *pi = (int)c; + return ZIP_OK; + } + else + { + if (ZERROR64(*pzlib_filefunc_def,filestream)) + return ZIP_ERRNO; + else + return ZIP_EOF; + } +} + + +/* =========================================================================== + Reads a long in LSB order from the given gz_stream. Sets +*/ +local int zip64local_getShort OF((const zlib_filefunc64_32_def* pzlib_filefunc_def, voidpf filestream, uLong *pX)); + +local int zip64local_getShort (const zlib_filefunc64_32_def* pzlib_filefunc_def, voidpf filestream, uLong* pX) +{ + uLong x ; + int i = 0; + int err; + + err = zip64local_getByte(pzlib_filefunc_def,filestream,&i); + x = (uLong)i; + + if (err==ZIP_OK) + err = zip64local_getByte(pzlib_filefunc_def,filestream,&i); + x += ((uLong)i)<<8; + + if (err==ZIP_OK) + *pX = x; + else + *pX = 0; + return err; +} + +local int zip64local_getLong OF((const zlib_filefunc64_32_def* pzlib_filefunc_def, voidpf filestream, uLong *pX)); + +local int zip64local_getLong (const zlib_filefunc64_32_def* pzlib_filefunc_def, voidpf filestream, uLong* pX) +{ + uLong x ; + int i = 0; + int err; + + err = zip64local_getByte(pzlib_filefunc_def,filestream,&i); + x = (uLong)i; + + if (err==ZIP_OK) + err = zip64local_getByte(pzlib_filefunc_def,filestream,&i); + x += ((uLong)i)<<8; + + if (err==ZIP_OK) + err = zip64local_getByte(pzlib_filefunc_def,filestream,&i); + x += ((uLong)i)<<16; + + if (err==ZIP_OK) + err = zip64local_getByte(pzlib_filefunc_def,filestream,&i); + x += ((uLong)i)<<24; + + if (err==ZIP_OK) + *pX = x; + else + *pX = 0; + return err; +} + +local int zip64local_getLong64 OF((const zlib_filefunc64_32_def* pzlib_filefunc_def, voidpf filestream, ZPOS64_T *pX)); + + +local int zip64local_getLong64 (const zlib_filefunc64_32_def* pzlib_filefunc_def, voidpf filestream, ZPOS64_T *pX) +{ + ZPOS64_T x; + int i = 0; + int err; + + err = zip64local_getByte(pzlib_filefunc_def,filestream,&i); + x = (ZPOS64_T)i; + + if (err==ZIP_OK) + err = zip64local_getByte(pzlib_filefunc_def,filestream,&i); + x += ((ZPOS64_T)i)<<8; + + if (err==ZIP_OK) + err = zip64local_getByte(pzlib_filefunc_def,filestream,&i); + x += ((ZPOS64_T)i)<<16; + + if (err==ZIP_OK) + err = zip64local_getByte(pzlib_filefunc_def,filestream,&i); + x += ((ZPOS64_T)i)<<24; + + if (err==ZIP_OK) + err = zip64local_getByte(pzlib_filefunc_def,filestream,&i); + x += ((ZPOS64_T)i)<<32; + + if (err==ZIP_OK) + err = zip64local_getByte(pzlib_filefunc_def,filestream,&i); + x += ((ZPOS64_T)i)<<40; + + if (err==ZIP_OK) + err = zip64local_getByte(pzlib_filefunc_def,filestream,&i); + x += ((ZPOS64_T)i)<<48; + + if (err==ZIP_OK) + err = zip64local_getByte(pzlib_filefunc_def,filestream,&i); + x += ((ZPOS64_T)i)<<56; + + if (err==ZIP_OK) + *pX = x; + else + *pX = 0; + + return err; +} + +#ifndef BUFREADCOMMENT +#define BUFREADCOMMENT (0x400) +#endif +/* + Locate the Central directory of a zipfile (at the end, just before + the global comment) +*/ +local ZPOS64_T zip64local_SearchCentralDir OF((const zlib_filefunc64_32_def* pzlib_filefunc_def, voidpf filestream)); + +local ZPOS64_T zip64local_SearchCentralDir(const zlib_filefunc64_32_def* pzlib_filefunc_def, voidpf filestream) +{ + unsigned char* buf; + ZPOS64_T uSizeFile; + ZPOS64_T uBackRead; + ZPOS64_T uMaxBack=0xffff; /* maximum size of global comment */ + ZPOS64_T uPosFound=0; + + if (ZSEEK64(*pzlib_filefunc_def,filestream,0,ZLIB_FILEFUNC_SEEK_END) != 0) + return 0; + + + uSizeFile = ZTELL64(*pzlib_filefunc_def,filestream); + + if (uMaxBack>uSizeFile) + uMaxBack = uSizeFile; + + buf = (unsigned char*)ALLOC(BUFREADCOMMENT+4); + if (buf==NULL) + return 0; + + uBackRead = 4; + while (uBackReaduMaxBack) + uBackRead = uMaxBack; + else + uBackRead+=BUFREADCOMMENT; + uReadPos = uSizeFile-uBackRead ; + + uReadSize = ((BUFREADCOMMENT+4) < (uSizeFile-uReadPos)) ? + (BUFREADCOMMENT+4) : (uLong)(uSizeFile-uReadPos); + if (ZSEEK64(*pzlib_filefunc_def,filestream,uReadPos,ZLIB_FILEFUNC_SEEK_SET)!=0) + break; + + if (ZREAD64(*pzlib_filefunc_def,filestream,buf,uReadSize)!=uReadSize) + break; + + for (i=(int)uReadSize-3; (i--)>0;) + if (((*(buf+i))==0x50) && ((*(buf+i+1))==0x4b) && + ((*(buf+i+2))==0x05) && ((*(buf+i+3))==0x06)) + { + uPosFound = uReadPos+i; + break; + } + + if (uPosFound!=0) + break; + } + TRYFREE(buf); + return uPosFound; +} + +/* +Locate the End of Zip64 Central directory locator and from there find the CD of a zipfile (at the end, just before +the global comment) +*/ +local ZPOS64_T zip64local_SearchCentralDir64 OF((const zlib_filefunc64_32_def* pzlib_filefunc_def, voidpf filestream)); + +local ZPOS64_T zip64local_SearchCentralDir64(const zlib_filefunc64_32_def* pzlib_filefunc_def, voidpf filestream) +{ + unsigned char* buf; + ZPOS64_T uSizeFile; + ZPOS64_T uBackRead; + ZPOS64_T uMaxBack=0xffff; /* maximum size of global comment */ + ZPOS64_T uPosFound=0; + uLong uL; + ZPOS64_T relativeOffset; + + if (ZSEEK64(*pzlib_filefunc_def,filestream,0,ZLIB_FILEFUNC_SEEK_END) != 0) + return 0; + + uSizeFile = ZTELL64(*pzlib_filefunc_def,filestream); + + if (uMaxBack>uSizeFile) + uMaxBack = uSizeFile; + + buf = (unsigned char*)ALLOC(BUFREADCOMMENT+4); + if (buf==NULL) + return 0; + + uBackRead = 4; + while (uBackReaduMaxBack) + uBackRead = uMaxBack; + else + uBackRead+=BUFREADCOMMENT; + uReadPos = uSizeFile-uBackRead ; + + uReadSize = ((BUFREADCOMMENT+4) < (uSizeFile-uReadPos)) ? + (BUFREADCOMMENT+4) : (uLong)(uSizeFile-uReadPos); + if (ZSEEK64(*pzlib_filefunc_def,filestream,uReadPos,ZLIB_FILEFUNC_SEEK_SET)!=0) + break; + + if (ZREAD64(*pzlib_filefunc_def,filestream,buf,uReadSize)!=uReadSize) + break; + + for (i=(int)uReadSize-3; (i--)>0;) + { + // Signature "0x07064b50" Zip64 end of central directory locater + if (((*(buf+i))==0x50) && ((*(buf+i+1))==0x4b) && ((*(buf+i+2))==0x06) && ((*(buf+i+3))==0x07)) + { + uPosFound = uReadPos+i; + break; + } + } + + if (uPosFound!=0) + break; + } + + TRYFREE(buf); + if (uPosFound == 0) + return 0; + + /* Zip64 end of central directory locator */ + if (ZSEEK64(*pzlib_filefunc_def,filestream, uPosFound,ZLIB_FILEFUNC_SEEK_SET)!=0) + return 0; + + /* the signature, already checked */ + if (zip64local_getLong(pzlib_filefunc_def,filestream,&uL)!=ZIP_OK) + return 0; + + /* number of the disk with the start of the zip64 end of central directory */ + if (zip64local_getLong(pzlib_filefunc_def,filestream,&uL)!=ZIP_OK) + return 0; + if (uL != 0) + return 0; + + /* relative offset of the zip64 end of central directory record */ + if (zip64local_getLong64(pzlib_filefunc_def,filestream,&relativeOffset)!=ZIP_OK) + return 0; + + /* total number of disks */ + if (zip64local_getLong(pzlib_filefunc_def,filestream,&uL)!=ZIP_OK) + return 0; + if (uL != 1) + return 0; + + /* Goto Zip64 end of central directory record */ + if (ZSEEK64(*pzlib_filefunc_def,filestream, relativeOffset,ZLIB_FILEFUNC_SEEK_SET)!=0) + return 0; + + /* the signature */ + if (zip64local_getLong(pzlib_filefunc_def,filestream,&uL)!=ZIP_OK) + return 0; + + if (uL != 0x06064b50) // signature of 'Zip64 end of central directory' + return 0; + + return relativeOffset; +} + +int LoadCentralDirectoryRecord(zip64_internal* pziinit) +{ + int err=ZIP_OK; + ZPOS64_T byte_before_the_zipfile;/* byte before the zipfile, (>0 for sfx)*/ + + ZPOS64_T size_central_dir; /* size of the central directory */ + ZPOS64_T offset_central_dir; /* offset of start of central directory */ + ZPOS64_T central_pos; + uLong uL; + + uLong number_disk; /* number of the current dist, used for + spaning ZIP, unsupported, always 0*/ + uLong number_disk_with_CD; /* number the the disk with central dir, used + for spaning ZIP, unsupported, always 0*/ + ZPOS64_T number_entry; + ZPOS64_T number_entry_CD; /* total number of entries in + the central dir + (same than number_entry on nospan) */ + uLong VersionMadeBy; + uLong VersionNeeded; + uLong size_comment; + + int hasZIP64Record = 0; + + // check first if we find a ZIP64 record + central_pos = zip64local_SearchCentralDir64(&pziinit->z_filefunc,pziinit->filestream); + if(central_pos > 0) + { + hasZIP64Record = 1; + } + else if(central_pos == 0) + { + central_pos = zip64local_SearchCentralDir(&pziinit->z_filefunc,pziinit->filestream); + } + +/* disable to allow appending to empty ZIP archive + if (central_pos==0) + err=ZIP_ERRNO; +*/ + + if(hasZIP64Record) + { + ZPOS64_T sizeEndOfCentralDirectory; + if (ZSEEK64(pziinit->z_filefunc, pziinit->filestream, central_pos, ZLIB_FILEFUNC_SEEK_SET) != 0) + err=ZIP_ERRNO; + + /* the signature, already checked */ + if (zip64local_getLong(&pziinit->z_filefunc, pziinit->filestream,&uL)!=ZIP_OK) + err=ZIP_ERRNO; + + /* size of zip64 end of central directory record */ + if (zip64local_getLong64(&pziinit->z_filefunc, pziinit->filestream, &sizeEndOfCentralDirectory)!=ZIP_OK) + err=ZIP_ERRNO; + + /* version made by */ + if (zip64local_getShort(&pziinit->z_filefunc, pziinit->filestream, &VersionMadeBy)!=ZIP_OK) + err=ZIP_ERRNO; + + /* version needed to extract */ + if (zip64local_getShort(&pziinit->z_filefunc, pziinit->filestream, &VersionNeeded)!=ZIP_OK) + err=ZIP_ERRNO; + + /* number of this disk */ + if (zip64local_getLong(&pziinit->z_filefunc, pziinit->filestream,&number_disk)!=ZIP_OK) + err=ZIP_ERRNO; + + /* number of the disk with the start of the central directory */ + if (zip64local_getLong(&pziinit->z_filefunc, pziinit->filestream,&number_disk_with_CD)!=ZIP_OK) + err=ZIP_ERRNO; + + /* total number of entries in the central directory on this disk */ + if (zip64local_getLong64(&pziinit->z_filefunc, pziinit->filestream, &number_entry)!=ZIP_OK) + err=ZIP_ERRNO; + + /* total number of entries in the central directory */ + if (zip64local_getLong64(&pziinit->z_filefunc, pziinit->filestream,&number_entry_CD)!=ZIP_OK) + err=ZIP_ERRNO; + + if ((number_entry_CD!=number_entry) || (number_disk_with_CD!=0) || (number_disk!=0)) + err=ZIP_BADZIPFILE; + + /* size of the central directory */ + if (zip64local_getLong64(&pziinit->z_filefunc, pziinit->filestream,&size_central_dir)!=ZIP_OK) + err=ZIP_ERRNO; + + /* offset of start of central directory with respect to the + starting disk number */ + if (zip64local_getLong64(&pziinit->z_filefunc, pziinit->filestream,&offset_central_dir)!=ZIP_OK) + err=ZIP_ERRNO; + + // TODO.. + // read the comment from the standard central header. + size_comment = 0; + } + else + { + // Read End of central Directory info + if (ZSEEK64(pziinit->z_filefunc, pziinit->filestream, central_pos,ZLIB_FILEFUNC_SEEK_SET)!=0) + err=ZIP_ERRNO; + + /* the signature, already checked */ + if (zip64local_getLong(&pziinit->z_filefunc, pziinit->filestream,&uL)!=ZIP_OK) + err=ZIP_ERRNO; + + /* number of this disk */ + if (zip64local_getShort(&pziinit->z_filefunc, pziinit->filestream,&number_disk)!=ZIP_OK) + err=ZIP_ERRNO; + + /* number of the disk with the start of the central directory */ + if (zip64local_getShort(&pziinit->z_filefunc, pziinit->filestream,&number_disk_with_CD)!=ZIP_OK) + err=ZIP_ERRNO; + + /* total number of entries in the central dir on this disk */ + number_entry = 0; + if (zip64local_getShort(&pziinit->z_filefunc, pziinit->filestream, &uL)!=ZIP_OK) + err=ZIP_ERRNO; + else + number_entry = uL; + + /* total number of entries in the central dir */ + number_entry_CD = 0; + if (zip64local_getShort(&pziinit->z_filefunc, pziinit->filestream, &uL)!=ZIP_OK) + err=ZIP_ERRNO; + else + number_entry_CD = uL; + + if ((number_entry_CD!=number_entry) || (number_disk_with_CD!=0) || (number_disk!=0)) + err=ZIP_BADZIPFILE; + + /* size of the central directory */ + size_central_dir = 0; + if (zip64local_getLong(&pziinit->z_filefunc, pziinit->filestream, &uL)!=ZIP_OK) + err=ZIP_ERRNO; + else + size_central_dir = uL; + + /* offset of start of central directory with respect to the starting disk number */ + offset_central_dir = 0; + if (zip64local_getLong(&pziinit->z_filefunc, pziinit->filestream, &uL)!=ZIP_OK) + err=ZIP_ERRNO; + else + offset_central_dir = uL; + + + /* zipfile global comment length */ + if (zip64local_getShort(&pziinit->z_filefunc, pziinit->filestream, &size_comment)!=ZIP_OK) + err=ZIP_ERRNO; + } + + if ((central_posz_filefunc, pziinit->filestream); + return ZIP_ERRNO; + } + + if (size_comment>0) + { + pziinit->globalcomment = (char*)ALLOC(size_comment+1); + if (pziinit->globalcomment) + { + size_comment = ZREAD64(pziinit->z_filefunc, pziinit->filestream, pziinit->globalcomment,size_comment); + pziinit->globalcomment[size_comment]=0; + } + } + + byte_before_the_zipfile = central_pos - (offset_central_dir+size_central_dir); + pziinit->add_position_when_writing_offset = byte_before_the_zipfile; + + { + ZPOS64_T size_central_dir_to_read = size_central_dir; + size_t buf_size = SIZEDATA_INDATABLOCK; + void* buf_read = (void*)ALLOC(buf_size); + if (ZSEEK64(pziinit->z_filefunc, pziinit->filestream, offset_central_dir + byte_before_the_zipfile, ZLIB_FILEFUNC_SEEK_SET) != 0) + err=ZIP_ERRNO; + + while ((size_central_dir_to_read>0) && (err==ZIP_OK)) + { + ZPOS64_T read_this = SIZEDATA_INDATABLOCK; + if (read_this > size_central_dir_to_read) + read_this = size_central_dir_to_read; + + if (ZREAD64(pziinit->z_filefunc, pziinit->filestream,buf_read,(uLong)read_this) != read_this) + err=ZIP_ERRNO; + + if (err==ZIP_OK) + err = add_data_in_datablock(&pziinit->central_dir,buf_read, (uLong)read_this); + + size_central_dir_to_read-=read_this; + } + TRYFREE(buf_read); + } + pziinit->begin_pos = byte_before_the_zipfile; + pziinit->number_entry = number_entry_CD; + + if (ZSEEK64(pziinit->z_filefunc, pziinit->filestream, offset_central_dir+byte_before_the_zipfile,ZLIB_FILEFUNC_SEEK_SET) != 0) + err=ZIP_ERRNO; + + return err; +} + + +#endif /* !NO_ADDFILEINEXISTINGZIP*/ + + +/************************************************************/ +extern zipFile ZEXPORT zipOpen3 (const void *pathname, int append, zipcharpc* globalcomment, zlib_filefunc64_32_def* pzlib_filefunc64_32_def) +{ + zip64_internal ziinit; + zip64_internal* zi; + int err=ZIP_OK; + + ziinit.z_filefunc.zseek32_file = NULL; + ziinit.z_filefunc.ztell32_file = NULL; + if (pzlib_filefunc64_32_def==NULL) + fill_fopen64_filefunc(&ziinit.z_filefunc.zfile_func64); + else + ziinit.z_filefunc = *pzlib_filefunc64_32_def; + + ziinit.filestream = ZOPEN64(ziinit.z_filefunc, + pathname, + (append == APPEND_STATUS_CREATE) ? + (ZLIB_FILEFUNC_MODE_READ | ZLIB_FILEFUNC_MODE_WRITE | ZLIB_FILEFUNC_MODE_CREATE) : + (ZLIB_FILEFUNC_MODE_READ | ZLIB_FILEFUNC_MODE_WRITE | ZLIB_FILEFUNC_MODE_EXISTING)); + + if (ziinit.filestream == NULL) + return NULL; + + if (append == APPEND_STATUS_CREATEAFTER) + ZSEEK64(ziinit.z_filefunc,ziinit.filestream,0,SEEK_END); + + ziinit.begin_pos = ZTELL64(ziinit.z_filefunc,ziinit.filestream); + ziinit.in_opened_file_inzip = 0; + ziinit.ci.stream_initialised = 0; + ziinit.number_entry = 0; + ziinit.add_position_when_writing_offset = 0; + init_linkedlist(&(ziinit.central_dir)); + + + + zi = (zip64_internal*)ALLOC(sizeof(zip64_internal)); + if (zi==NULL) + { + ZCLOSE64(ziinit.z_filefunc,ziinit.filestream); + return NULL; + } + + /* now we add file in a zipfile */ +# ifndef NO_ADDFILEINEXISTINGZIP + ziinit.globalcomment = NULL; + if (append == APPEND_STATUS_ADDINZIP) + { + // Read and Cache Central Directory Records + err = LoadCentralDirectoryRecord(&ziinit); + } + + if (globalcomment) + { + *globalcomment = ziinit.globalcomment; + } +# endif /* !NO_ADDFILEINEXISTINGZIP*/ + + if (err != ZIP_OK) + { +# ifndef NO_ADDFILEINEXISTINGZIP + TRYFREE(ziinit.globalcomment); +# endif /* !NO_ADDFILEINEXISTINGZIP*/ + TRYFREE(zi); + return NULL; + } + else + { + *zi = ziinit; + return (zipFile)zi; + } +} + +extern zipFile ZEXPORT zipOpen2 (const char *pathname, int append, zipcharpc* globalcomment, zlib_filefunc_def* pzlib_filefunc32_def) +{ + if (pzlib_filefunc32_def != NULL) + { + zlib_filefunc64_32_def zlib_filefunc64_32_def_fill; + fill_zlib_filefunc64_32_def_from_filefunc32(&zlib_filefunc64_32_def_fill,pzlib_filefunc32_def); + return zipOpen3(pathname, append, globalcomment, &zlib_filefunc64_32_def_fill); + } + else + return zipOpen3(pathname, append, globalcomment, NULL); +} + +extern zipFile ZEXPORT zipOpen2_64 (const void *pathname, int append, zipcharpc* globalcomment, zlib_filefunc64_def* pzlib_filefunc_def) +{ + if (pzlib_filefunc_def != NULL) + { + zlib_filefunc64_32_def zlib_filefunc64_32_def_fill; + zlib_filefunc64_32_def_fill.zfile_func64 = *pzlib_filefunc_def; + zlib_filefunc64_32_def_fill.ztell32_file = NULL; + zlib_filefunc64_32_def_fill.zseek32_file = NULL; + return zipOpen3(pathname, append, globalcomment, &zlib_filefunc64_32_def_fill); + } + else + return zipOpen3(pathname, append, globalcomment, NULL); +} + + + +extern zipFile ZEXPORT zipOpen (const char* pathname, int append) +{ + return zipOpen3((const void*)pathname,append,NULL,NULL); +} + +extern zipFile ZEXPORT zipOpen64 (const void* pathname, int append) +{ + return zipOpen3(pathname,append,NULL,NULL); +} + +int Write_LocalFileHeader(zip64_internal* zi, const char* filename, uInt size_extrafield_local, const void* extrafield_local) +{ + /* write the local header */ + int err; + uInt size_filename = (uInt)strlen(filename); + uInt size_extrafield = size_extrafield_local; + + err = zip64local_putValue(&zi->z_filefunc,zi->filestream,(uLong)LOCALHEADERMAGIC, 4); + + if (err==ZIP_OK) + { + if(zi->ci.zip64) + err = zip64local_putValue(&zi->z_filefunc,zi->filestream,(uLong)45,2);/* version needed to extract */ + else + err = zip64local_putValue(&zi->z_filefunc,zi->filestream,(uLong)20,2);/* version needed to extract */ + } + + if (err==ZIP_OK) + err = zip64local_putValue(&zi->z_filefunc,zi->filestream,(uLong)zi->ci.flag,2); + + if (err==ZIP_OK) + err = zip64local_putValue(&zi->z_filefunc,zi->filestream,(uLong)zi->ci.method,2); + + if (err==ZIP_OK) + err = zip64local_putValue(&zi->z_filefunc,zi->filestream,(uLong)zi->ci.dosDate,4); + + // CRC / Compressed size / Uncompressed size will be filled in later and rewritten later + if (err==ZIP_OK) + err = zip64local_putValue(&zi->z_filefunc,zi->filestream,(uLong)0,4); /* crc 32, unknown */ + if (err==ZIP_OK) + { + if(zi->ci.zip64) + err = zip64local_putValue(&zi->z_filefunc,zi->filestream,(uLong)0xFFFFFFFF,4); /* compressed size, unknown */ + else + err = zip64local_putValue(&zi->z_filefunc,zi->filestream,(uLong)0,4); /* compressed size, unknown */ + } + if (err==ZIP_OK) + { + if(zi->ci.zip64) + err = zip64local_putValue(&zi->z_filefunc,zi->filestream,(uLong)0xFFFFFFFF,4); /* uncompressed size, unknown */ + else + err = zip64local_putValue(&zi->z_filefunc,zi->filestream,(uLong)0,4); /* uncompressed size, unknown */ + } + + if (err==ZIP_OK) + err = zip64local_putValue(&zi->z_filefunc,zi->filestream,(uLong)size_filename,2); + + if(zi->ci.zip64) + { + size_extrafield += 20; + } + + if (err==ZIP_OK) + err = zip64local_putValue(&zi->z_filefunc,zi->filestream,(uLong)size_extrafield,2); + + if ((err==ZIP_OK) && (size_filename > 0)) + { + if (ZWRITE64(zi->z_filefunc,zi->filestream,filename,size_filename)!=size_filename) + err = ZIP_ERRNO; + } + + if ((err==ZIP_OK) && (size_extrafield_local > 0)) + { + if (ZWRITE64(zi->z_filefunc, zi->filestream, extrafield_local, size_extrafield_local) != size_extrafield_local) + err = ZIP_ERRNO; + } + + + if ((err==ZIP_OK) && (zi->ci.zip64)) + { + // write the Zip64 extended info + short HeaderID = 1; + short DataSize = 16; + ZPOS64_T CompressedSize = 0; + ZPOS64_T UncompressedSize = 0; + + // Remember position of Zip64 extended info for the local file header. (needed when we update size after done with file) + zi->ci.pos_zip64extrainfo = ZTELL64(zi->z_filefunc,zi->filestream); + + err = zip64local_putValue(&zi->z_filefunc, zi->filestream, (short)HeaderID,2); + err = zip64local_putValue(&zi->z_filefunc, zi->filestream, (short)DataSize,2); + + err = zip64local_putValue(&zi->z_filefunc, zi->filestream, (ZPOS64_T)UncompressedSize,8); + err = zip64local_putValue(&zi->z_filefunc, zi->filestream, (ZPOS64_T)CompressedSize,8); + } + + return err; +} + +/* + NOTE. + When writing RAW the ZIP64 extended information in extrafield_local and extrafield_global needs to be stripped + before calling this function it can be done with zipRemoveExtraInfoBlock + + It is not done here because then we need to realloc a new buffer since parameters are 'const' and I want to minimize + unnecessary allocations. + */ +extern int ZEXPORT zipOpenNewFileInZip4_64 (zipFile file, const char* filename, const zip_fileinfo* zipfi, + const void* extrafield_local, uInt size_extrafield_local, + const void* extrafield_global, uInt size_extrafield_global, + const char* comment, int method, int level, int raw, + int windowBits,int memLevel, int strategy, + const char* password, uLong crcForCrypting, + uLong versionMadeBy, uLong flagBase, int zip64) +{ + zip64_internal* zi; + uInt size_filename; + uInt size_comment; + uInt i; + int err = ZIP_OK; + +# ifdef NOCRYPT + (crcForCrypting); + if (password != NULL) + return ZIP_PARAMERROR; +# endif + + if (file == NULL) + return ZIP_PARAMERROR; + +#ifdef HAVE_BZIP2 + if ((method!=0) && (method!=Z_DEFLATED) && (method!=Z_BZIP2ED)) + return ZIP_PARAMERROR; +#else + if ((method!=0) && (method!=Z_DEFLATED)) + return ZIP_PARAMERROR; +#endif + + zi = (zip64_internal*)file; + + if (zi->in_opened_file_inzip == 1) + { + err = zipCloseFileInZip (file); + if (err != ZIP_OK) + return err; + } + + if (filename==NULL) + filename="-"; + + if (comment==NULL) + size_comment = 0; + else + size_comment = (uInt)strlen(comment); + + size_filename = (uInt)strlen(filename); + + if (zipfi == NULL) + zi->ci.dosDate = 0; + else + { + if (zipfi->dosDate != 0) + zi->ci.dosDate = zipfi->dosDate; + else + zi->ci.dosDate = zip64local_TmzDateToDosDate(&zipfi->tmz_date); + } + + zi->ci.flag = flagBase; + if ((level==8) || (level==9)) + zi->ci.flag |= 2; + if (level==2) + zi->ci.flag |= 4; + if (level==1) + zi->ci.flag |= 6; + if (password != NULL) + zi->ci.flag |= 1; + + zi->ci.crc32 = 0; + zi->ci.method = method; + zi->ci.encrypt = 0; + zi->ci.stream_initialised = 0; + zi->ci.pos_in_buffered_data = 0; + zi->ci.raw = raw; + zi->ci.pos_local_header = ZTELL64(zi->z_filefunc,zi->filestream); + + zi->ci.size_centralheader = SIZECENTRALHEADER + size_filename + size_extrafield_global + size_comment; + zi->ci.size_centralExtraFree = 32; // Extra space we have reserved in case we need to add ZIP64 extra info data + + zi->ci.central_header = (char*)ALLOC((uInt)zi->ci.size_centralheader + zi->ci.size_centralExtraFree); + + zi->ci.size_centralExtra = size_extrafield_global; + zip64local_putValue_inmemory(zi->ci.central_header,(uLong)CENTRALHEADERMAGIC,4); + /* version info */ + zip64local_putValue_inmemory(zi->ci.central_header+4,(uLong)versionMadeBy,2); + zip64local_putValue_inmemory(zi->ci.central_header+6,(uLong)20,2); + zip64local_putValue_inmemory(zi->ci.central_header+8,(uLong)zi->ci.flag,2); + zip64local_putValue_inmemory(zi->ci.central_header+10,(uLong)zi->ci.method,2); + zip64local_putValue_inmemory(zi->ci.central_header+12,(uLong)zi->ci.dosDate,4); + zip64local_putValue_inmemory(zi->ci.central_header+16,(uLong)0,4); /*crc*/ + zip64local_putValue_inmemory(zi->ci.central_header+20,(uLong)0,4); /*compr size*/ + zip64local_putValue_inmemory(zi->ci.central_header+24,(uLong)0,4); /*uncompr size*/ + zip64local_putValue_inmemory(zi->ci.central_header+28,(uLong)size_filename,2); + zip64local_putValue_inmemory(zi->ci.central_header+30,(uLong)size_extrafield_global,2); + zip64local_putValue_inmemory(zi->ci.central_header+32,(uLong)size_comment,2); + zip64local_putValue_inmemory(zi->ci.central_header+34,(uLong)0,2); /*disk nm start*/ + + if (zipfi==NULL) + zip64local_putValue_inmemory(zi->ci.central_header+36,(uLong)0,2); + else + zip64local_putValue_inmemory(zi->ci.central_header+36,(uLong)zipfi->internal_fa,2); + + if (zipfi==NULL) + zip64local_putValue_inmemory(zi->ci.central_header+38,(uLong)0,4); + else + zip64local_putValue_inmemory(zi->ci.central_header+38,(uLong)zipfi->external_fa,4); + + if(zi->ci.pos_local_header >= 0xffffffff) + zip64local_putValue_inmemory(zi->ci.central_header+42,(uLong)0xffffffff,4); + else + zip64local_putValue_inmemory(zi->ci.central_header+42,(uLong)zi->ci.pos_local_header - zi->add_position_when_writing_offset,4); + + for (i=0;ici.central_header+SIZECENTRALHEADER+i) = *(filename+i); + + for (i=0;ici.central_header+SIZECENTRALHEADER+size_filename+i) = + *(((const char*)extrafield_global)+i); + + for (i=0;ici.central_header+SIZECENTRALHEADER+size_filename+ + size_extrafield_global+i) = *(comment+i); + if (zi->ci.central_header == NULL) + return ZIP_INTERNALERROR; + + zi->ci.zip64 = zip64; + zi->ci.totalCompressedData = 0; + zi->ci.totalUncompressedData = 0; + zi->ci.pos_zip64extrainfo = 0; + + err = Write_LocalFileHeader(zi, filename, size_extrafield_local, extrafield_local); + +#ifdef HAVE_BZIP2 + zi->ci.bstream.avail_in = (uInt)0; + zi->ci.bstream.avail_out = (uInt)Z_BUFSIZE; + zi->ci.bstream.next_out = (char*)zi->ci.buffered_data; + zi->ci.bstream.total_in_hi32 = 0; + zi->ci.bstream.total_in_lo32 = 0; + zi->ci.bstream.total_out_hi32 = 0; + zi->ci.bstream.total_out_lo32 = 0; +#endif + + zi->ci.stream.avail_in = (uInt)0; + zi->ci.stream.avail_out = (uInt)Z_BUFSIZE; + zi->ci.stream.next_out = zi->ci.buffered_data; + zi->ci.stream.total_in = 0; + zi->ci.stream.total_out = 0; + zi->ci.stream.data_type = Z_BINARY; + +#ifdef HAVE_BZIP2 + if ((err==ZIP_OK) && (zi->ci.method == Z_DEFLATED || zi->ci.method == Z_BZIP2ED) && (!zi->ci.raw)) +#else + if ((err==ZIP_OK) && (zi->ci.method == Z_DEFLATED) && (!zi->ci.raw)) +#endif + { + if(zi->ci.method == Z_DEFLATED) + { + zi->ci.stream.zalloc = (alloc_func)0; + zi->ci.stream.zfree = (free_func)0; + zi->ci.stream.opaque = (voidpf)0; + + if (windowBits>0) + windowBits = -windowBits; + + err = deflateInit2(&zi->ci.stream, level, Z_DEFLATED, windowBits, memLevel, strategy); + + if (err==Z_OK) + zi->ci.stream_initialised = Z_DEFLATED; + } + else if(zi->ci.method == Z_BZIP2ED) + { +#ifdef HAVE_BZIP2 + // Init BZip stuff here + zi->ci.bstream.bzalloc = 0; + zi->ci.bstream.bzfree = 0; + zi->ci.bstream.opaque = (voidpf)0; + + err = BZ2_bzCompressInit(&zi->ci.bstream, level, 0,35); + if(err == BZ_OK) + zi->ci.stream_initialised = Z_BZIP2ED; +#endif + } + + } + +# ifndef NOCRYPT + zi->ci.crypt_header_size = 0; + if ((err==Z_OK) && (password != NULL)) + { + unsigned char bufHead[RAND_HEAD_LEN]; + unsigned int sizeHead; + zi->ci.encrypt = 1; + zi->ci.pcrc_32_tab = get_crc_table(); + /*init_keys(password,zi->ci.keys,zi->ci.pcrc_32_tab);*/ + + sizeHead=crypthead(password,bufHead,RAND_HEAD_LEN,zi->ci.keys,zi->ci.pcrc_32_tab,crcForCrypting); + zi->ci.crypt_header_size = sizeHead; + + if (ZWRITE64(zi->z_filefunc,zi->filestream,bufHead,sizeHead) != sizeHead) + err = ZIP_ERRNO; + } +# endif + + if (err==Z_OK) + zi->in_opened_file_inzip = 1; + return err; +} + +extern int ZEXPORT zipOpenNewFileInZip4 (zipFile file, const char* filename, const zip_fileinfo* zipfi, + const void* extrafield_local, uInt size_extrafield_local, + const void* extrafield_global, uInt size_extrafield_global, + const char* comment, int method, int level, int raw, + int windowBits,int memLevel, int strategy, + const char* password, uLong crcForCrypting, + uLong versionMadeBy, uLong flagBase) +{ + return zipOpenNewFileInZip4_64 (file, filename, zipfi, + extrafield_local, size_extrafield_local, + extrafield_global, size_extrafield_global, + comment, method, level, raw, + windowBits, memLevel, strategy, + password, crcForCrypting, versionMadeBy, flagBase, 0); +} + +extern int ZEXPORT zipOpenNewFileInZip3 (zipFile file, const char* filename, const zip_fileinfo* zipfi, + const void* extrafield_local, uInt size_extrafield_local, + const void* extrafield_global, uInt size_extrafield_global, + const char* comment, int method, int level, int raw, + int windowBits,int memLevel, int strategy, + const char* password, uLong crcForCrypting) +{ + return zipOpenNewFileInZip4_64 (file, filename, zipfi, + extrafield_local, size_extrafield_local, + extrafield_global, size_extrafield_global, + comment, method, level, raw, + windowBits, memLevel, strategy, + password, crcForCrypting, VERSIONMADEBY, 0, 0); +} + +extern int ZEXPORT zipOpenNewFileInZip3_64(zipFile file, const char* filename, const zip_fileinfo* zipfi, + const void* extrafield_local, uInt size_extrafield_local, + const void* extrafield_global, uInt size_extrafield_global, + const char* comment, int method, int level, int raw, + int windowBits,int memLevel, int strategy, + const char* password, uLong crcForCrypting, int zip64) +{ + return zipOpenNewFileInZip4_64 (file, filename, zipfi, + extrafield_local, size_extrafield_local, + extrafield_global, size_extrafield_global, + comment, method, level, raw, + windowBits, memLevel, strategy, + password, crcForCrypting, VERSIONMADEBY, 0, zip64); +} + +extern int ZEXPORT zipOpenNewFileInZip2(zipFile file, const char* filename, const zip_fileinfo* zipfi, + const void* extrafield_local, uInt size_extrafield_local, + const void* extrafield_global, uInt size_extrafield_global, + const char* comment, int method, int level, int raw) +{ + return zipOpenNewFileInZip4_64 (file, filename, zipfi, + extrafield_local, size_extrafield_local, + extrafield_global, size_extrafield_global, + comment, method, level, raw, + -MAX_WBITS, DEF_MEM_LEVEL, Z_DEFAULT_STRATEGY, + NULL, 0, VERSIONMADEBY, 0, 0); +} + +extern int ZEXPORT zipOpenNewFileInZip2_64(zipFile file, const char* filename, const zip_fileinfo* zipfi, + const void* extrafield_local, uInt size_extrafield_local, + const void* extrafield_global, uInt size_extrafield_global, + const char* comment, int method, int level, int raw, int zip64) +{ + return zipOpenNewFileInZip4_64 (file, filename, zipfi, + extrafield_local, size_extrafield_local, + extrafield_global, size_extrafield_global, + comment, method, level, raw, + -MAX_WBITS, DEF_MEM_LEVEL, Z_DEFAULT_STRATEGY, + NULL, 0, VERSIONMADEBY, 0, zip64); +} + +extern int ZEXPORT zipOpenNewFileInZip64 (zipFile file, const char* filename, const zip_fileinfo* zipfi, + const void* extrafield_local, uInt size_extrafield_local, + const void*extrafield_global, uInt size_extrafield_global, + const char* comment, int method, int level, int zip64) +{ + return zipOpenNewFileInZip4_64 (file, filename, zipfi, + extrafield_local, size_extrafield_local, + extrafield_global, size_extrafield_global, + comment, method, level, 0, + -MAX_WBITS, DEF_MEM_LEVEL, Z_DEFAULT_STRATEGY, + NULL, 0, VERSIONMADEBY, 0, zip64); +} + +extern int ZEXPORT zipOpenNewFileInZip (zipFile file, const char* filename, const zip_fileinfo* zipfi, + const void* extrafield_local, uInt size_extrafield_local, + const void*extrafield_global, uInt size_extrafield_global, + const char* comment, int method, int level) +{ + return zipOpenNewFileInZip4_64 (file, filename, zipfi, + extrafield_local, size_extrafield_local, + extrafield_global, size_extrafield_global, + comment, method, level, 0, + -MAX_WBITS, DEF_MEM_LEVEL, Z_DEFAULT_STRATEGY, + NULL, 0, VERSIONMADEBY, 0, 0); +} + +local int zip64FlushWriteBuffer(zip64_internal* zi) +{ + int err=ZIP_OK; + + if (zi->ci.encrypt != 0) + { +#ifndef NOCRYPT + uInt i; + int t; + for (i=0;ici.pos_in_buffered_data;i++) + zi->ci.buffered_data[i] = zencode(zi->ci.keys, zi->ci.pcrc_32_tab, zi->ci.buffered_data[i],t); +#endif + } + + if (ZWRITE64(zi->z_filefunc,zi->filestream,zi->ci.buffered_data,zi->ci.pos_in_buffered_data) != zi->ci.pos_in_buffered_data) + err = ZIP_ERRNO; + + zi->ci.totalCompressedData += zi->ci.pos_in_buffered_data; + +#ifdef HAVE_BZIP2 + if(zi->ci.method == Z_BZIP2ED) + { + zi->ci.totalUncompressedData += zi->ci.bstream.total_in_lo32; + zi->ci.bstream.total_in_lo32 = 0; + zi->ci.bstream.total_in_hi32 = 0; + } + else +#endif + { + zi->ci.totalUncompressedData += zi->ci.stream.total_in; + zi->ci.stream.total_in = 0; + } + + + zi->ci.pos_in_buffered_data = 0; + + return err; +} + +extern int ZEXPORT zipWriteInFileInZip (zipFile file,const void* buf,unsigned int len) +{ + zip64_internal* zi; + int err=ZIP_OK; + + if (file == NULL) + return ZIP_PARAMERROR; + zi = (zip64_internal*)file; + + if (zi->in_opened_file_inzip == 0) + return ZIP_PARAMERROR; + + zi->ci.crc32 = crc32(zi->ci.crc32,buf,(uInt)len); + +#ifdef HAVE_BZIP2 + if(zi->ci.method == Z_BZIP2ED && (!zi->ci.raw)) + { + zi->ci.bstream.next_in = (void*)buf; + zi->ci.bstream.avail_in = len; + err = BZ_RUN_OK; + + while ((err==BZ_RUN_OK) && (zi->ci.bstream.avail_in>0)) + { + if (zi->ci.bstream.avail_out == 0) + { + if (zip64FlushWriteBuffer(zi) == ZIP_ERRNO) + err = ZIP_ERRNO; + zi->ci.bstream.avail_out = (uInt)Z_BUFSIZE; + zi->ci.bstream.next_out = (char*)zi->ci.buffered_data; + } + + + if(err != BZ_RUN_OK) + break; + + if ((zi->ci.method == Z_BZIP2ED) && (!zi->ci.raw)) + { + uLong uTotalOutBefore_lo = zi->ci.bstream.total_out_lo32; +// uLong uTotalOutBefore_hi = zi->ci.bstream.total_out_hi32; + err=BZ2_bzCompress(&zi->ci.bstream, BZ_RUN); + + zi->ci.pos_in_buffered_data += (uInt)(zi->ci.bstream.total_out_lo32 - uTotalOutBefore_lo) ; + } + } + + if(err == BZ_RUN_OK) + err = ZIP_OK; + } + else +#endif + { + zi->ci.stream.next_in = (Bytef*)buf; + zi->ci.stream.avail_in = len; + + while ((err==ZIP_OK) && (zi->ci.stream.avail_in>0)) + { + if (zi->ci.stream.avail_out == 0) + { + if (zip64FlushWriteBuffer(zi) == ZIP_ERRNO) + err = ZIP_ERRNO; + zi->ci.stream.avail_out = (uInt)Z_BUFSIZE; + zi->ci.stream.next_out = zi->ci.buffered_data; + } + + + if(err != ZIP_OK) + break; + + if ((zi->ci.method == Z_DEFLATED) && (!zi->ci.raw)) + { + uLong uTotalOutBefore = zi->ci.stream.total_out; + err=deflate(&zi->ci.stream, Z_NO_FLUSH); + if(uTotalOutBefore > zi->ci.stream.total_out) + { + int bBreak = 0; + bBreak++; + } + + zi->ci.pos_in_buffered_data += (uInt)(zi->ci.stream.total_out - uTotalOutBefore) ; + } + else + { + uInt copy_this,i; + if (zi->ci.stream.avail_in < zi->ci.stream.avail_out) + copy_this = zi->ci.stream.avail_in; + else + copy_this = zi->ci.stream.avail_out; + + for (i = 0; i < copy_this; i++) + *(((char*)zi->ci.stream.next_out)+i) = + *(((const char*)zi->ci.stream.next_in)+i); + { + zi->ci.stream.avail_in -= copy_this; + zi->ci.stream.avail_out-= copy_this; + zi->ci.stream.next_in+= copy_this; + zi->ci.stream.next_out+= copy_this; + zi->ci.stream.total_in+= copy_this; + zi->ci.stream.total_out+= copy_this; + zi->ci.pos_in_buffered_data += copy_this; + } + } + }// while(...) + } + + return err; +} + +extern int ZEXPORT zipCloseFileInZipRaw (zipFile file, uLong uncompressed_size, uLong crc32) +{ + return zipCloseFileInZipRaw64 (file, uncompressed_size, crc32); +} + +extern int ZEXPORT zipCloseFileInZipRaw64 (zipFile file, ZPOS64_T uncompressed_size, uLong crc32) +{ + zip64_internal* zi; + ZPOS64_T compressed_size; + uLong invalidValue = 0xffffffff; + short datasize = 0; + int err=ZIP_OK; + + if (file == NULL) + return ZIP_PARAMERROR; + zi = (zip64_internal*)file; + + if (zi->in_opened_file_inzip == 0) + return ZIP_PARAMERROR; + zi->ci.stream.avail_in = 0; + + if ((zi->ci.method == Z_DEFLATED) && (!zi->ci.raw)) + { + while (err==ZIP_OK) + { + uLong uTotalOutBefore; + if (zi->ci.stream.avail_out == 0) + { + if (zip64FlushWriteBuffer(zi) == ZIP_ERRNO) + err = ZIP_ERRNO; + zi->ci.stream.avail_out = (uInt)Z_BUFSIZE; + zi->ci.stream.next_out = zi->ci.buffered_data; + } + uTotalOutBefore = zi->ci.stream.total_out; + err=deflate(&zi->ci.stream, Z_FINISH); + zi->ci.pos_in_buffered_data += (uInt)(zi->ci.stream.total_out - uTotalOutBefore) ; + } + } + else if ((zi->ci.method == Z_BZIP2ED) && (!zi->ci.raw)) + { +#ifdef HAVE_BZIP2 + err = BZ_FINISH_OK; + while (err==BZ_FINISH_OK) + { + uLong uTotalOutBefore; + if (zi->ci.bstream.avail_out == 0) + { + if (zip64FlushWriteBuffer(zi) == ZIP_ERRNO) + err = ZIP_ERRNO; + zi->ci.bstream.avail_out = (uInt)Z_BUFSIZE; + zi->ci.bstream.next_out = (char*)zi->ci.buffered_data; + } + uTotalOutBefore = zi->ci.bstream.total_out_lo32; + err=BZ2_bzCompress(&zi->ci.bstream, BZ_FINISH); + if(err == BZ_STREAM_END) + err = Z_STREAM_END; + + zi->ci.pos_in_buffered_data += (uInt)(zi->ci.bstream.total_out_lo32 - uTotalOutBefore); + } + + if(err == BZ_FINISH_OK) + err = ZIP_OK; +#endif + } + + if (err==Z_STREAM_END) + err=ZIP_OK; /* this is normal */ + + if ((zi->ci.pos_in_buffered_data>0) && (err==ZIP_OK)) + { + if (zip64FlushWriteBuffer(zi)==ZIP_ERRNO) + err = ZIP_ERRNO; + } + + if ((zi->ci.method == Z_DEFLATED) && (!zi->ci.raw)) + { + int tmp_err = deflateEnd(&zi->ci.stream); + if (err == ZIP_OK) + err = tmp_err; + zi->ci.stream_initialised = 0; + } +#ifdef HAVE_BZIP2 + else if((zi->ci.method == Z_BZIP2ED) && (!zi->ci.raw)) + { + int tmperr = BZ2_bzCompressEnd(&zi->ci.bstream); + if (err==ZIP_OK) + err = tmperr; + zi->ci.stream_initialised = 0; + } +#endif + + if (!zi->ci.raw) + { + crc32 = (uLong)zi->ci.crc32; + uncompressed_size = zi->ci.totalUncompressedData; + } + compressed_size = zi->ci.totalCompressedData; + +# ifndef NOCRYPT + compressed_size += zi->ci.crypt_header_size; +# endif + + // update Current Item crc and sizes, + if(compressed_size >= 0xffffffff || uncompressed_size >= 0xffffffff || zi->ci.pos_local_header >= 0xffffffff) + { + /*version Made by*/ + zip64local_putValue_inmemory(zi->ci.central_header+4,(uLong)45,2); + /*version needed*/ + zip64local_putValue_inmemory(zi->ci.central_header+6,(uLong)45,2); + + } + + zip64local_putValue_inmemory(zi->ci.central_header+16,crc32,4); /*crc*/ + + + if(compressed_size >= 0xffffffff) + zip64local_putValue_inmemory(zi->ci.central_header+20, invalidValue,4); /*compr size*/ + else + zip64local_putValue_inmemory(zi->ci.central_header+20, compressed_size,4); /*compr size*/ + + /// set internal file attributes field + if (zi->ci.stream.data_type == Z_ASCII) + zip64local_putValue_inmemory(zi->ci.central_header+36,(uLong)Z_ASCII,2); + + if(uncompressed_size >= 0xffffffff) + zip64local_putValue_inmemory(zi->ci.central_header+24, invalidValue,4); /*uncompr size*/ + else + zip64local_putValue_inmemory(zi->ci.central_header+24, uncompressed_size,4); /*uncompr size*/ + + // Add ZIP64 extra info field for uncompressed size + if(uncompressed_size >= 0xffffffff) + datasize += 8; + + // Add ZIP64 extra info field for compressed size + if(compressed_size >= 0xffffffff) + datasize += 8; + + // Add ZIP64 extra info field for relative offset to local file header of current file + if(zi->ci.pos_local_header >= 0xffffffff) + datasize += 8; + + if(datasize > 0) + { + char* p = NULL; + + if((uLong)(datasize + 4) > zi->ci.size_centralExtraFree) + { + // we can not write more data to the buffer that we have room for. + return ZIP_BADZIPFILE; + } + + p = zi->ci.central_header + zi->ci.size_centralheader; + + // Add Extra Information Header for 'ZIP64 information' + zip64local_putValue_inmemory(p, 0x0001, 2); // HeaderID + p += 2; + zip64local_putValue_inmemory(p, datasize, 2); // DataSize + p += 2; + + if(uncompressed_size >= 0xffffffff) + { + zip64local_putValue_inmemory(p, uncompressed_size, 8); + p += 8; + } + + if(compressed_size >= 0xffffffff) + { + zip64local_putValue_inmemory(p, compressed_size, 8); + p += 8; + } + + if(zi->ci.pos_local_header >= 0xffffffff) + { + zip64local_putValue_inmemory(p, zi->ci.pos_local_header, 8); + p += 8; + } + + // Update how much extra free space we got in the memory buffer + // and increase the centralheader size so the new ZIP64 fields are included + // ( 4 below is the size of HeaderID and DataSize field ) + zi->ci.size_centralExtraFree -= datasize + 4; + zi->ci.size_centralheader += datasize + 4; + + // Update the extra info size field + zi->ci.size_centralExtra += datasize + 4; + zip64local_putValue_inmemory(zi->ci.central_header+30,(uLong)zi->ci.size_centralExtra,2); + } + + if (err==ZIP_OK) + err = add_data_in_datablock(&zi->central_dir, zi->ci.central_header, (uLong)zi->ci.size_centralheader); + + free(zi->ci.central_header); + + if (err==ZIP_OK) + { + // Update the LocalFileHeader with the new values. + + ZPOS64_T cur_pos_inzip = ZTELL64(zi->z_filefunc,zi->filestream); + + if (ZSEEK64(zi->z_filefunc,zi->filestream, zi->ci.pos_local_header + 14,ZLIB_FILEFUNC_SEEK_SET)!=0) + err = ZIP_ERRNO; + + if (err==ZIP_OK) + err = zip64local_putValue(&zi->z_filefunc,zi->filestream,crc32,4); /* crc 32, unknown */ + + if(uncompressed_size >= 0xffffffff || compressed_size >= 0xffffffff ) + { + if(zi->ci.pos_zip64extrainfo > 0) + { + // Update the size in the ZIP64 extended field. + if (ZSEEK64(zi->z_filefunc,zi->filestream, zi->ci.pos_zip64extrainfo + 4,ZLIB_FILEFUNC_SEEK_SET)!=0) + err = ZIP_ERRNO; + + if (err==ZIP_OK) /* compressed size, unknown */ + err = zip64local_putValue(&zi->z_filefunc, zi->filestream, uncompressed_size, 8); + + if (err==ZIP_OK) /* uncompressed size, unknown */ + err = zip64local_putValue(&zi->z_filefunc, zi->filestream, compressed_size, 8); + } + else + err = ZIP_BADZIPFILE; // Caller passed zip64 = 0, so no room for zip64 info -> fatal + } + else + { + if (err==ZIP_OK) /* compressed size, unknown */ + err = zip64local_putValue(&zi->z_filefunc,zi->filestream,compressed_size,4); + + if (err==ZIP_OK) /* uncompressed size, unknown */ + err = zip64local_putValue(&zi->z_filefunc,zi->filestream,uncompressed_size,4); + } + + if (ZSEEK64(zi->z_filefunc,zi->filestream, cur_pos_inzip,ZLIB_FILEFUNC_SEEK_SET)!=0) + err = ZIP_ERRNO; + } + + zi->number_entry ++; + zi->in_opened_file_inzip = 0; + + return err; +} + +extern int ZEXPORT zipCloseFileInZip (zipFile file) +{ + return zipCloseFileInZipRaw (file,0,0); +} + +int Write_Zip64EndOfCentralDirectoryLocator(zip64_internal* zi, ZPOS64_T zip64eocd_pos_inzip) +{ + int err = ZIP_OK; + ZPOS64_T pos = zip64eocd_pos_inzip - zi->add_position_when_writing_offset; + + err = zip64local_putValue(&zi->z_filefunc,zi->filestream,(uLong)ZIP64ENDLOCHEADERMAGIC,4); + + /*num disks*/ + if (err==ZIP_OK) /* number of the disk with the start of the central directory */ + err = zip64local_putValue(&zi->z_filefunc,zi->filestream,(uLong)0,4); + + /*relative offset*/ + if (err==ZIP_OK) /* Relative offset to the Zip64EndOfCentralDirectory */ + err = zip64local_putValue(&zi->z_filefunc,zi->filestream, pos,8); + + /*total disks*/ /* Do not support spawning of disk so always say 1 here*/ + if (err==ZIP_OK) /* number of the disk with the start of the central directory */ + err = zip64local_putValue(&zi->z_filefunc,zi->filestream,(uLong)1,4); + + return err; +} + +int Write_Zip64EndOfCentralDirectoryRecord(zip64_internal* zi, uLong size_centraldir, ZPOS64_T centraldir_pos_inzip) +{ + int err = ZIP_OK; + + uLong Zip64DataSize = 44; + + err = zip64local_putValue(&zi->z_filefunc,zi->filestream,(uLong)ZIP64ENDHEADERMAGIC,4); + + if (err==ZIP_OK) /* size of this 'zip64 end of central directory' */ + err = zip64local_putValue(&zi->z_filefunc,zi->filestream,(ZPOS64_T)Zip64DataSize,8); // why ZPOS64_T of this ? + + if (err==ZIP_OK) /* version made by */ + err = zip64local_putValue(&zi->z_filefunc,zi->filestream,(uLong)45,2); + + if (err==ZIP_OK) /* version needed */ + err = zip64local_putValue(&zi->z_filefunc,zi->filestream,(uLong)45,2); + + if (err==ZIP_OK) /* number of this disk */ + err = zip64local_putValue(&zi->z_filefunc,zi->filestream,(uLong)0,4); + + if (err==ZIP_OK) /* number of the disk with the start of the central directory */ + err = zip64local_putValue(&zi->z_filefunc,zi->filestream,(uLong)0,4); + + if (err==ZIP_OK) /* total number of entries in the central dir on this disk */ + err = zip64local_putValue(&zi->z_filefunc, zi->filestream, zi->number_entry, 8); + + if (err==ZIP_OK) /* total number of entries in the central dir */ + err = zip64local_putValue(&zi->z_filefunc, zi->filestream, zi->number_entry, 8); + + if (err==ZIP_OK) /* size of the central directory */ + err = zip64local_putValue(&zi->z_filefunc,zi->filestream,(ZPOS64_T)size_centraldir,8); + + if (err==ZIP_OK) /* offset of start of central directory with respect to the starting disk number */ + { + ZPOS64_T pos = centraldir_pos_inzip - zi->add_position_when_writing_offset; + err = zip64local_putValue(&zi->z_filefunc,zi->filestream, (ZPOS64_T)pos,8); + } + return err; +} +int Write_EndOfCentralDirectoryRecord(zip64_internal* zi, uLong size_centraldir, ZPOS64_T centraldir_pos_inzip) +{ + int err = ZIP_OK; + + /*signature*/ + err = zip64local_putValue(&zi->z_filefunc,zi->filestream,(uLong)ENDHEADERMAGIC,4); + + if (err==ZIP_OK) /* number of this disk */ + err = zip64local_putValue(&zi->z_filefunc,zi->filestream,(uLong)0,2); + + if (err==ZIP_OK) /* number of the disk with the start of the central directory */ + err = zip64local_putValue(&zi->z_filefunc,zi->filestream,(uLong)0,2); + + if (err==ZIP_OK) /* total number of entries in the central dir on this disk */ + { + { + if(zi->number_entry >= 0xFFFF) + err = zip64local_putValue(&zi->z_filefunc,zi->filestream,(uLong)0xffff,2); // use value in ZIP64 record + else + err = zip64local_putValue(&zi->z_filefunc,zi->filestream,(uLong)zi->number_entry,2); + } + } + + if (err==ZIP_OK) /* total number of entries in the central dir */ + { + if(zi->number_entry >= 0xFFFF) + err = zip64local_putValue(&zi->z_filefunc,zi->filestream,(uLong)0xffff,2); // use value in ZIP64 record + else + err = zip64local_putValue(&zi->z_filefunc,zi->filestream,(uLong)zi->number_entry,2); + } + + if (err==ZIP_OK) /* size of the central directory */ + err = zip64local_putValue(&zi->z_filefunc,zi->filestream,(uLong)size_centraldir,4); + + if (err==ZIP_OK) /* offset of start of central directory with respect to the starting disk number */ + { + ZPOS64_T pos = centraldir_pos_inzip - zi->add_position_when_writing_offset; + if(pos >= 0xffffffff) + { + err = zip64local_putValue(&zi->z_filefunc,zi->filestream, (uLong)0xffffffff,4); + } + else + err = zip64local_putValue(&zi->z_filefunc,zi->filestream, (uLong)(centraldir_pos_inzip - zi->add_position_when_writing_offset),4); + } + + return err; +} + +int Write_GlobalComment(zip64_internal* zi, const char* global_comment) +{ + int err = ZIP_OK; + uInt size_global_comment = 0; + + if(global_comment != NULL) + size_global_comment = (uInt)strlen(global_comment); + + err = zip64local_putValue(&zi->z_filefunc,zi->filestream,(uLong)size_global_comment,2); + + if (err == ZIP_OK && size_global_comment > 0) + { + if (ZWRITE64(zi->z_filefunc,zi->filestream, global_comment, size_global_comment) != size_global_comment) + err = ZIP_ERRNO; + } + return err; +} + +extern int ZEXPORT zipClose (zipFile file, const char* global_comment) +{ + zip64_internal* zi; + int err = 0; + uLong size_centraldir = 0; + ZPOS64_T centraldir_pos_inzip; + ZPOS64_T pos; + + if (file == NULL) + return ZIP_PARAMERROR; + + zi = (zip64_internal*)file; + + if (zi->in_opened_file_inzip == 1) + { + err = zipCloseFileInZip (file); + } + +#ifndef NO_ADDFILEINEXISTINGZIP + if (global_comment==NULL) + global_comment = zi->globalcomment; +#endif + + centraldir_pos_inzip = ZTELL64(zi->z_filefunc,zi->filestream); + + if (err==ZIP_OK) + { + linkedlist_datablock_internal* ldi = zi->central_dir.first_block; + while (ldi!=NULL) + { + if ((err==ZIP_OK) && (ldi->filled_in_this_block>0)) + { + if (ZWRITE64(zi->z_filefunc,zi->filestream, ldi->data, ldi->filled_in_this_block) != ldi->filled_in_this_block) + err = ZIP_ERRNO; + } + + size_centraldir += ldi->filled_in_this_block; + ldi = ldi->next_datablock; + } + } + free_linkedlist(&(zi->central_dir)); + + pos = centraldir_pos_inzip - zi->add_position_when_writing_offset; + if(pos >= 0xffffffff || zi->number_entry > 0xFFFF) + { + ZPOS64_T Zip64EOCDpos = ZTELL64(zi->z_filefunc,zi->filestream); + Write_Zip64EndOfCentralDirectoryRecord(zi, size_centraldir, centraldir_pos_inzip); + + Write_Zip64EndOfCentralDirectoryLocator(zi, Zip64EOCDpos); + } + + if (err==ZIP_OK) + err = Write_EndOfCentralDirectoryRecord(zi, size_centraldir, centraldir_pos_inzip); + + if(err == ZIP_OK) + err = Write_GlobalComment(zi, global_comment); + + if (ZCLOSE64(zi->z_filefunc,zi->filestream) != 0) + if (err == ZIP_OK) + err = ZIP_ERRNO; + +#ifndef NO_ADDFILEINEXISTINGZIP + TRYFREE(zi->globalcomment); +#endif + TRYFREE(zi); + + return err; +} + +extern int ZEXPORT zipRemoveExtraInfoBlock (char* pData, int* dataLen, short sHeader) +{ + char* p = pData; + int size = 0; + char* pNewHeader; + char* pTmp; + short header; + short dataSize; + + int retVal = ZIP_OK; + + if(pData == NULL || *dataLen < 4) + return ZIP_PARAMERROR; + + pNewHeader = (char*)ALLOC(*dataLen); + pTmp = pNewHeader; + + while(p < (pData + *dataLen)) + { + header = *(short*)p; + dataSize = *(((short*)p)+1); + + if( header == sHeader ) // Header found. + { + p += dataSize + 4; // skip it. do not copy to temp buffer + } + else + { + // Extra Info block should not be removed, So copy it to the temp buffer. + memcpy(pTmp, p, dataSize + 4); + p += dataSize + 4; + size += dataSize + 4; + } + + } + + if(size < *dataLen) + { + // clean old extra info block. + memset(pData,0, *dataLen); + + // copy the new extra info block over the old + if(size > 0) + memcpy(pData, pNewHeader, size); + + // set the new extra info size + *dataLen = size; + + retVal = ZIP_OK; + } + else + retVal = ZIP_ERRNO; + + TRYFREE(pNewHeader); + + return retVal; +} ADDED compat/zlib/contrib/minizip/zip.h Index: compat/zlib/contrib/minizip/zip.h ================================================================== --- /dev/null +++ compat/zlib/contrib/minizip/zip.h @@ -0,0 +1,362 @@ +/* zip.h -- IO on .zip files using zlib + Version 1.1, February 14h, 2010 + part of the MiniZip project - ( http://www.winimage.com/zLibDll/minizip.html ) + + Copyright (C) 1998-2010 Gilles Vollant (minizip) ( http://www.winimage.com/zLibDll/minizip.html ) + + Modifications for Zip64 support + Copyright (C) 2009-2010 Mathias Svensson ( http://result42.com ) + + For more info read MiniZip_info.txt + + --------------------------------------------------------------------------- + + Condition of use and distribution are the same than zlib : + + This software is provided 'as-is', without any express or implied + warranty. In no event will the authors be held liable for any damages + arising from the use of this software. + + Permission is granted to anyone to use this software for any purpose, + including commercial applications, and to alter it and redistribute it + freely, subject to the following restrictions: + + 1. The origin of this software must not be misrepresented; you must not + claim that you wrote the original software. If you use this software + in a product, an acknowledgment in the product documentation would be + appreciated but is not required. + 2. Altered source versions must be plainly marked as such, and must not be + misrepresented as being the original software. + 3. This notice may not be removed or altered from any source distribution. + + --------------------------------------------------------------------------- + + Changes + + See header of zip.h + +*/ + +#ifndef _zip12_H +#define _zip12_H + +#ifdef __cplusplus +extern "C" { +#endif + +//#define HAVE_BZIP2 + +#ifndef _ZLIB_H +#include "zlib.h" +#endif + +#ifndef _ZLIBIOAPI_H +#include "ioapi.h" +#endif + +#ifdef HAVE_BZIP2 +#include "bzlib.h" +#endif + +#define Z_BZIP2ED 12 + +#if defined(STRICTZIP) || defined(STRICTZIPUNZIP) +/* like the STRICT of WIN32, we define a pointer that cannot be converted + from (void*) without cast */ +typedef struct TagzipFile__ { int unused; } zipFile__; +typedef zipFile__ *zipFile; +#else +typedef voidp zipFile; +#endif + +#define ZIP_OK (0) +#define ZIP_EOF (0) +#define ZIP_ERRNO (Z_ERRNO) +#define ZIP_PARAMERROR (-102) +#define ZIP_BADZIPFILE (-103) +#define ZIP_INTERNALERROR (-104) + +#ifndef DEF_MEM_LEVEL +# if MAX_MEM_LEVEL >= 8 +# define DEF_MEM_LEVEL 8 +# else +# define DEF_MEM_LEVEL MAX_MEM_LEVEL +# endif +#endif +/* default memLevel */ + +/* tm_zip contain date/time info */ +typedef struct tm_zip_s +{ + uInt tm_sec; /* seconds after the minute - [0,59] */ + uInt tm_min; /* minutes after the hour - [0,59] */ + uInt tm_hour; /* hours since midnight - [0,23] */ + uInt tm_mday; /* day of the month - [1,31] */ + uInt tm_mon; /* months since January - [0,11] */ + uInt tm_year; /* years - [1980..2044] */ +} tm_zip; + +typedef struct +{ + tm_zip tmz_date; /* date in understandable format */ + uLong dosDate; /* if dos_date == 0, tmu_date is used */ +/* uLong flag; */ /* general purpose bit flag 2 bytes */ + + uLong internal_fa; /* internal file attributes 2 bytes */ + uLong external_fa; /* external file attributes 4 bytes */ +} zip_fileinfo; + +typedef const char* zipcharpc; + + +#define APPEND_STATUS_CREATE (0) +#define APPEND_STATUS_CREATEAFTER (1) +#define APPEND_STATUS_ADDINZIP (2) + +extern zipFile ZEXPORT zipOpen OF((const char *pathname, int append)); +extern zipFile ZEXPORT zipOpen64 OF((const void *pathname, int append)); +/* + Create a zipfile. + pathname contain on Windows XP a filename like "c:\\zlib\\zlib113.zip" or on + an Unix computer "zlib/zlib113.zip". + if the file pathname exist and append==APPEND_STATUS_CREATEAFTER, the zip + will be created at the end of the file. + (useful if the file contain a self extractor code) + if the file pathname exist and append==APPEND_STATUS_ADDINZIP, we will + add files in existing zip (be sure you don't add file that doesn't exist) + If the zipfile cannot be opened, the return value is NULL. + Else, the return value is a zipFile Handle, usable with other function + of this zip package. +*/ + +/* Note : there is no delete function into a zipfile. + If you want delete file into a zipfile, you must open a zipfile, and create another + Of couse, you can use RAW reading and writing to copy the file you did not want delte +*/ + +extern zipFile ZEXPORT zipOpen2 OF((const char *pathname, + int append, + zipcharpc* globalcomment, + zlib_filefunc_def* pzlib_filefunc_def)); + +extern zipFile ZEXPORT zipOpen2_64 OF((const void *pathname, + int append, + zipcharpc* globalcomment, + zlib_filefunc64_def* pzlib_filefunc_def)); + +extern int ZEXPORT zipOpenNewFileInZip OF((zipFile file, + const char* filename, + const zip_fileinfo* zipfi, + const void* extrafield_local, + uInt size_extrafield_local, + const void* extrafield_global, + uInt size_extrafield_global, + const char* comment, + int method, + int level)); + +extern int ZEXPORT zipOpenNewFileInZip64 OF((zipFile file, + const char* filename, + const zip_fileinfo* zipfi, + const void* extrafield_local, + uInt size_extrafield_local, + const void* extrafield_global, + uInt size_extrafield_global, + const char* comment, + int method, + int level, + int zip64)); + +/* + Open a file in the ZIP for writing. + filename : the filename in zip (if NULL, '-' without quote will be used + *zipfi contain supplemental information + if extrafield_local!=NULL and size_extrafield_local>0, extrafield_local + contains the extrafield data the the local header + if extrafield_global!=NULL and size_extrafield_global>0, extrafield_global + contains the extrafield data the the local header + if comment != NULL, comment contain the comment string + method contain the compression method (0 for store, Z_DEFLATED for deflate) + level contain the level of compression (can be Z_DEFAULT_COMPRESSION) + zip64 is set to 1 if a zip64 extended information block should be added to the local file header. + this MUST be '1' if the uncompressed size is >= 0xffffffff. + +*/ + + +extern int ZEXPORT zipOpenNewFileInZip2 OF((zipFile file, + const char* filename, + const zip_fileinfo* zipfi, + const void* extrafield_local, + uInt size_extrafield_local, + const void* extrafield_global, + uInt size_extrafield_global, + const char* comment, + int method, + int level, + int raw)); + + +extern int ZEXPORT zipOpenNewFileInZip2_64 OF((zipFile file, + const char* filename, + const zip_fileinfo* zipfi, + const void* extrafield_local, + uInt size_extrafield_local, + const void* extrafield_global, + uInt size_extrafield_global, + const char* comment, + int method, + int level, + int raw, + int zip64)); +/* + Same than zipOpenNewFileInZip, except if raw=1, we write raw file + */ + +extern int ZEXPORT zipOpenNewFileInZip3 OF((zipFile file, + const char* filename, + const zip_fileinfo* zipfi, + const void* extrafield_local, + uInt size_extrafield_local, + const void* extrafield_global, + uInt size_extrafield_global, + const char* comment, + int method, + int level, + int raw, + int windowBits, + int memLevel, + int strategy, + const char* password, + uLong crcForCrypting)); + +extern int ZEXPORT zipOpenNewFileInZip3_64 OF((zipFile file, + const char* filename, + const zip_fileinfo* zipfi, + const void* extrafield_local, + uInt size_extrafield_local, + const void* extrafield_global, + uInt size_extrafield_global, + const char* comment, + int method, + int level, + int raw, + int windowBits, + int memLevel, + int strategy, + const char* password, + uLong crcForCrypting, + int zip64 + )); + +/* + Same than zipOpenNewFileInZip2, except + windowBits,memLevel,,strategy : see parameter strategy in deflateInit2 + password : crypting password (NULL for no crypting) + crcForCrypting : crc of file to compress (needed for crypting) + */ + +extern int ZEXPORT zipOpenNewFileInZip4 OF((zipFile file, + const char* filename, + const zip_fileinfo* zipfi, + const void* extrafield_local, + uInt size_extrafield_local, + const void* extrafield_global, + uInt size_extrafield_global, + const char* comment, + int method, + int level, + int raw, + int windowBits, + int memLevel, + int strategy, + const char* password, + uLong crcForCrypting, + uLong versionMadeBy, + uLong flagBase + )); + + +extern int ZEXPORT zipOpenNewFileInZip4_64 OF((zipFile file, + const char* filename, + const zip_fileinfo* zipfi, + const void* extrafield_local, + uInt size_extrafield_local, + const void* extrafield_global, + uInt size_extrafield_global, + const char* comment, + int method, + int level, + int raw, + int windowBits, + int memLevel, + int strategy, + const char* password, + uLong crcForCrypting, + uLong versionMadeBy, + uLong flagBase, + int zip64 + )); +/* + Same than zipOpenNewFileInZip4, except + versionMadeBy : value for Version made by field + flag : value for flag field (compression level info will be added) + */ + + +extern int ZEXPORT zipWriteInFileInZip OF((zipFile file, + const void* buf, + unsigned len)); +/* + Write data in the zipfile +*/ + +extern int ZEXPORT zipCloseFileInZip OF((zipFile file)); +/* + Close the current file in the zipfile +*/ + +extern int ZEXPORT zipCloseFileInZipRaw OF((zipFile file, + uLong uncompressed_size, + uLong crc32)); + +extern int ZEXPORT zipCloseFileInZipRaw64 OF((zipFile file, + ZPOS64_T uncompressed_size, + uLong crc32)); + +/* + Close the current file in the zipfile, for file opened with + parameter raw=1 in zipOpenNewFileInZip2 + uncompressed_size and crc32 are value for the uncompressed size +*/ + +extern int ZEXPORT zipClose OF((zipFile file, + const char* global_comment)); +/* + Close the zipfile +*/ + + +extern int ZEXPORT zipRemoveExtraInfoBlock OF((char* pData, int* dataLen, short sHeader)); +/* + zipRemoveExtraInfoBlock - Added by Mathias Svensson + + Remove extra information block from a extra information data for the local file header or central directory header + + It is needed to remove ZIP64 extra information blocks when before data is written if using RAW mode. + + 0x0001 is the signature header for the ZIP64 extra information blocks + + usage. + Remove ZIP64 Extra information from a central director extra field data + zipRemoveExtraInfoBlock(pCenDirExtraFieldData, &nCenDirExtraFieldDataLen, 0x0001); + + Remove ZIP64 Extra information from a Local File Header extra field data + zipRemoveExtraInfoBlock(pLocalHeaderExtraFieldData, &nLocalHeaderExtraFieldDataLen, 0x0001); +*/ + +#ifdef __cplusplus +} +#endif + +#endif /* _zip64_H */ ADDED compat/zlib/contrib/pascal/example.pas Index: compat/zlib/contrib/pascal/example.pas ================================================================== --- /dev/null +++ compat/zlib/contrib/pascal/example.pas @@ -0,0 +1,599 @@ +(* example.c -- usage example of the zlib compression library + * Copyright (C) 1995-2003 Jean-loup Gailly. + * For conditions of distribution and use, see copyright notice in zlib.h + * + * Pascal translation + * Copyright (C) 1998 by Jacques Nomssi Nzali. + * For conditions of distribution and use, see copyright notice in readme.txt + * + * Adaptation to the zlibpas interface + * Copyright (C) 2003 by Cosmin Truta. + * For conditions of distribution and use, see copyright notice in readme.txt + *) + +program example; + +{$DEFINE TEST_COMPRESS} +{DO NOT $DEFINE TEST_GZIO} +{$DEFINE TEST_DEFLATE} +{$DEFINE TEST_INFLATE} +{$DEFINE TEST_FLUSH} +{$DEFINE TEST_SYNC} +{$DEFINE TEST_DICT} + +uses SysUtils, zlibpas; + +const TESTFILE = 'foo.gz'; + +(* "hello world" would be more standard, but the repeated "hello" + * stresses the compression code better, sorry... + *) +const hello: PChar = 'hello, hello!'; + +const dictionary: PChar = 'hello'; + +var dictId: LongInt; (* Adler32 value of the dictionary *) + +procedure CHECK_ERR(err: Integer; msg: String); +begin + if err <> Z_OK then + begin + WriteLn(msg, ' error: ', err); + Halt(1); + end; +end; + +procedure EXIT_ERR(const msg: String); +begin + WriteLn('Error: ', msg); + Halt(1); +end; + +(* =========================================================================== + * Test compress and uncompress + *) +{$IFDEF TEST_COMPRESS} +procedure test_compress(compr: Pointer; comprLen: LongInt; + uncompr: Pointer; uncomprLen: LongInt); +var err: Integer; + len: LongInt; +begin + len := StrLen(hello)+1; + + err := compress(compr, comprLen, hello, len); + CHECK_ERR(err, 'compress'); + + StrCopy(PChar(uncompr), 'garbage'); + + err := uncompress(uncompr, uncomprLen, compr, comprLen); + CHECK_ERR(err, 'uncompress'); + + if StrComp(PChar(uncompr), hello) <> 0 then + EXIT_ERR('bad uncompress') + else + WriteLn('uncompress(): ', PChar(uncompr)); +end; +{$ENDIF} + +(* =========================================================================== + * Test read/write of .gz files + *) +{$IFDEF TEST_GZIO} +procedure test_gzio(const fname: PChar; (* compressed file name *) + uncompr: Pointer; + uncomprLen: LongInt); +var err: Integer; + len: Integer; + zfile: gzFile; + pos: LongInt; +begin + len := StrLen(hello)+1; + + zfile := gzopen(fname, 'wb'); + if zfile = NIL then + begin + WriteLn('gzopen error'); + Halt(1); + end; + gzputc(zfile, 'h'); + if gzputs(zfile, 'ello') <> 4 then + begin + WriteLn('gzputs err: ', gzerror(zfile, err)); + Halt(1); + end; + {$IFDEF GZ_FORMAT_STRING} + if gzprintf(zfile, ', %s!', 'hello') <> 8 then + begin + WriteLn('gzprintf err: ', gzerror(zfile, err)); + Halt(1); + end; + {$ELSE} + if gzputs(zfile, ', hello!') <> 8 then + begin + WriteLn('gzputs err: ', gzerror(zfile, err)); + Halt(1); + end; + {$ENDIF} + gzseek(zfile, 1, SEEK_CUR); (* add one zero byte *) + gzclose(zfile); + + zfile := gzopen(fname, 'rb'); + if zfile = NIL then + begin + WriteLn('gzopen error'); + Halt(1); + end; + + StrCopy(PChar(uncompr), 'garbage'); + + if gzread(zfile, uncompr, uncomprLen) <> len then + begin + WriteLn('gzread err: ', gzerror(zfile, err)); + Halt(1); + end; + if StrComp(PChar(uncompr), hello) <> 0 then + begin + WriteLn('bad gzread: ', PChar(uncompr)); + Halt(1); + end + else + WriteLn('gzread(): ', PChar(uncompr)); + + pos := gzseek(zfile, -8, SEEK_CUR); + if (pos <> 6) or (gztell(zfile) <> pos) then + begin + WriteLn('gzseek error, pos=', pos, ', gztell=', gztell(zfile)); + Halt(1); + end; + + if gzgetc(zfile) <> ' ' then + begin + WriteLn('gzgetc error'); + Halt(1); + end; + + if gzungetc(' ', zfile) <> ' ' then + begin + WriteLn('gzungetc error'); + Halt(1); + end; + + gzgets(zfile, PChar(uncompr), uncomprLen); + uncomprLen := StrLen(PChar(uncompr)); + if uncomprLen <> 7 then (* " hello!" *) + begin + WriteLn('gzgets err after gzseek: ', gzerror(zfile, err)); + Halt(1); + end; + if StrComp(PChar(uncompr), hello + 6) <> 0 then + begin + WriteLn('bad gzgets after gzseek'); + Halt(1); + end + else + WriteLn('gzgets() after gzseek: ', PChar(uncompr)); + + gzclose(zfile); +end; +{$ENDIF} + +(* =========================================================================== + * Test deflate with small buffers + *) +{$IFDEF TEST_DEFLATE} +procedure test_deflate(compr: Pointer; comprLen: LongInt); +var c_stream: z_stream; (* compression stream *) + err: Integer; + len: LongInt; +begin + len := StrLen(hello)+1; + + c_stream.zalloc := NIL; + c_stream.zfree := NIL; + c_stream.opaque := NIL; + + err := deflateInit(c_stream, Z_DEFAULT_COMPRESSION); + CHECK_ERR(err, 'deflateInit'); + + c_stream.next_in := hello; + c_stream.next_out := compr; + + while (c_stream.total_in <> len) and + (c_stream.total_out < comprLen) do + begin + c_stream.avail_out := 1; { force small buffers } + c_stream.avail_in := 1; + err := deflate(c_stream, Z_NO_FLUSH); + CHECK_ERR(err, 'deflate'); + end; + + (* Finish the stream, still forcing small buffers: *) + while TRUE do + begin + c_stream.avail_out := 1; + err := deflate(c_stream, Z_FINISH); + if err = Z_STREAM_END then + break; + CHECK_ERR(err, 'deflate'); + end; + + err := deflateEnd(c_stream); + CHECK_ERR(err, 'deflateEnd'); +end; +{$ENDIF} + +(* =========================================================================== + * Test inflate with small buffers + *) +{$IFDEF TEST_INFLATE} +procedure test_inflate(compr: Pointer; comprLen : LongInt; + uncompr: Pointer; uncomprLen : LongInt); +var err: Integer; + d_stream: z_stream; (* decompression stream *) +begin + StrCopy(PChar(uncompr), 'garbage'); + + d_stream.zalloc := NIL; + d_stream.zfree := NIL; + d_stream.opaque := NIL; + + d_stream.next_in := compr; + d_stream.avail_in := 0; + d_stream.next_out := uncompr; + + err := inflateInit(d_stream); + CHECK_ERR(err, 'inflateInit'); + + while (d_stream.total_out < uncomprLen) and + (d_stream.total_in < comprLen) do + begin + d_stream.avail_out := 1; (* force small buffers *) + d_stream.avail_in := 1; + err := inflate(d_stream, Z_NO_FLUSH); + if err = Z_STREAM_END then + break; + CHECK_ERR(err, 'inflate'); + end; + + err := inflateEnd(d_stream); + CHECK_ERR(err, 'inflateEnd'); + + if StrComp(PChar(uncompr), hello) <> 0 then + EXIT_ERR('bad inflate') + else + WriteLn('inflate(): ', PChar(uncompr)); +end; +{$ENDIF} + +(* =========================================================================== + * Test deflate with large buffers and dynamic change of compression level + *) +{$IFDEF TEST_DEFLATE} +procedure test_large_deflate(compr: Pointer; comprLen: LongInt; + uncompr: Pointer; uncomprLen: LongInt); +var c_stream: z_stream; (* compression stream *) + err: Integer; +begin + c_stream.zalloc := NIL; + c_stream.zfree := NIL; + c_stream.opaque := NIL; + + err := deflateInit(c_stream, Z_BEST_SPEED); + CHECK_ERR(err, 'deflateInit'); + + c_stream.next_out := compr; + c_stream.avail_out := Integer(comprLen); + + (* At this point, uncompr is still mostly zeroes, so it should compress + * very well: + *) + c_stream.next_in := uncompr; + c_stream.avail_in := Integer(uncomprLen); + err := deflate(c_stream, Z_NO_FLUSH); + CHECK_ERR(err, 'deflate'); + if c_stream.avail_in <> 0 then + EXIT_ERR('deflate not greedy'); + + (* Feed in already compressed data and switch to no compression: *) + deflateParams(c_stream, Z_NO_COMPRESSION, Z_DEFAULT_STRATEGY); + c_stream.next_in := compr; + c_stream.avail_in := Integer(comprLen div 2); + err := deflate(c_stream, Z_NO_FLUSH); + CHECK_ERR(err, 'deflate'); + + (* Switch back to compressing mode: *) + deflateParams(c_stream, Z_BEST_COMPRESSION, Z_FILTERED); + c_stream.next_in := uncompr; + c_stream.avail_in := Integer(uncomprLen); + err := deflate(c_stream, Z_NO_FLUSH); + CHECK_ERR(err, 'deflate'); + + err := deflate(c_stream, Z_FINISH); + if err <> Z_STREAM_END then + EXIT_ERR('deflate should report Z_STREAM_END'); + + err := deflateEnd(c_stream); + CHECK_ERR(err, 'deflateEnd'); +end; +{$ENDIF} + +(* =========================================================================== + * Test inflate with large buffers + *) +{$IFDEF TEST_INFLATE} +procedure test_large_inflate(compr: Pointer; comprLen: LongInt; + uncompr: Pointer; uncomprLen: LongInt); +var err: Integer; + d_stream: z_stream; (* decompression stream *) +begin + StrCopy(PChar(uncompr), 'garbage'); + + d_stream.zalloc := NIL; + d_stream.zfree := NIL; + d_stream.opaque := NIL; + + d_stream.next_in := compr; + d_stream.avail_in := Integer(comprLen); + + err := inflateInit(d_stream); + CHECK_ERR(err, 'inflateInit'); + + while TRUE do + begin + d_stream.next_out := uncompr; (* discard the output *) + d_stream.avail_out := Integer(uncomprLen); + err := inflate(d_stream, Z_NO_FLUSH); + if err = Z_STREAM_END then + break; + CHECK_ERR(err, 'large inflate'); + end; + + err := inflateEnd(d_stream); + CHECK_ERR(err, 'inflateEnd'); + + if d_stream.total_out <> 2 * uncomprLen + comprLen div 2 then + begin + WriteLn('bad large inflate: ', d_stream.total_out); + Halt(1); + end + else + WriteLn('large_inflate(): OK'); +end; +{$ENDIF} + +(* =========================================================================== + * Test deflate with full flush + *) +{$IFDEF TEST_FLUSH} +procedure test_flush(compr: Pointer; var comprLen : LongInt); +var c_stream: z_stream; (* compression stream *) + err: Integer; + len: Integer; +begin + len := StrLen(hello)+1; + + c_stream.zalloc := NIL; + c_stream.zfree := NIL; + c_stream.opaque := NIL; + + err := deflateInit(c_stream, Z_DEFAULT_COMPRESSION); + CHECK_ERR(err, 'deflateInit'); + + c_stream.next_in := hello; + c_stream.next_out := compr; + c_stream.avail_in := 3; + c_stream.avail_out := Integer(comprLen); + err := deflate(c_stream, Z_FULL_FLUSH); + CHECK_ERR(err, 'deflate'); + + Inc(PByteArray(compr)^[3]); (* force an error in first compressed block *) + c_stream.avail_in := len - 3; + + err := deflate(c_stream, Z_FINISH); + if err <> Z_STREAM_END then + CHECK_ERR(err, 'deflate'); + + err := deflateEnd(c_stream); + CHECK_ERR(err, 'deflateEnd'); + + comprLen := c_stream.total_out; +end; +{$ENDIF} + +(* =========================================================================== + * Test inflateSync() + *) +{$IFDEF TEST_SYNC} +procedure test_sync(compr: Pointer; comprLen: LongInt; + uncompr: Pointer; uncomprLen : LongInt); +var err: Integer; + d_stream: z_stream; (* decompression stream *) +begin + StrCopy(PChar(uncompr), 'garbage'); + + d_stream.zalloc := NIL; + d_stream.zfree := NIL; + d_stream.opaque := NIL; + + d_stream.next_in := compr; + d_stream.avail_in := 2; (* just read the zlib header *) + + err := inflateInit(d_stream); + CHECK_ERR(err, 'inflateInit'); + + d_stream.next_out := uncompr; + d_stream.avail_out := Integer(uncomprLen); + + inflate(d_stream, Z_NO_FLUSH); + CHECK_ERR(err, 'inflate'); + + d_stream.avail_in := Integer(comprLen-2); (* read all compressed data *) + err := inflateSync(d_stream); (* but skip the damaged part *) + CHECK_ERR(err, 'inflateSync'); + + err := inflate(d_stream, Z_FINISH); + if err <> Z_DATA_ERROR then + EXIT_ERR('inflate should report DATA_ERROR'); + (* Because of incorrect adler32 *) + + err := inflateEnd(d_stream); + CHECK_ERR(err, 'inflateEnd'); + + WriteLn('after inflateSync(): hel', PChar(uncompr)); +end; +{$ENDIF} + +(* =========================================================================== + * Test deflate with preset dictionary + *) +{$IFDEF TEST_DICT} +procedure test_dict_deflate(compr: Pointer; comprLen: LongInt); +var c_stream: z_stream; (* compression stream *) + err: Integer; +begin + c_stream.zalloc := NIL; + c_stream.zfree := NIL; + c_stream.opaque := NIL; + + err := deflateInit(c_stream, Z_BEST_COMPRESSION); + CHECK_ERR(err, 'deflateInit'); + + err := deflateSetDictionary(c_stream, dictionary, StrLen(dictionary)); + CHECK_ERR(err, 'deflateSetDictionary'); + + dictId := c_stream.adler; + c_stream.next_out := compr; + c_stream.avail_out := Integer(comprLen); + + c_stream.next_in := hello; + c_stream.avail_in := StrLen(hello)+1; + + err := deflate(c_stream, Z_FINISH); + if err <> Z_STREAM_END then + EXIT_ERR('deflate should report Z_STREAM_END'); + + err := deflateEnd(c_stream); + CHECK_ERR(err, 'deflateEnd'); +end; +{$ENDIF} + +(* =========================================================================== + * Test inflate with a preset dictionary + *) +{$IFDEF TEST_DICT} +procedure test_dict_inflate(compr: Pointer; comprLen: LongInt; + uncompr: Pointer; uncomprLen: LongInt); +var err: Integer; + d_stream: z_stream; (* decompression stream *) +begin + StrCopy(PChar(uncompr), 'garbage'); + + d_stream.zalloc := NIL; + d_stream.zfree := NIL; + d_stream.opaque := NIL; + + d_stream.next_in := compr; + d_stream.avail_in := Integer(comprLen); + + err := inflateInit(d_stream); + CHECK_ERR(err, 'inflateInit'); + + d_stream.next_out := uncompr; + d_stream.avail_out := Integer(uncomprLen); + + while TRUE do + begin + err := inflate(d_stream, Z_NO_FLUSH); + if err = Z_STREAM_END then + break; + if err = Z_NEED_DICT then + begin + if d_stream.adler <> dictId then + EXIT_ERR('unexpected dictionary'); + err := inflateSetDictionary(d_stream, dictionary, StrLen(dictionary)); + end; + CHECK_ERR(err, 'inflate with dict'); + end; + + err := inflateEnd(d_stream); + CHECK_ERR(err, 'inflateEnd'); + + if StrComp(PChar(uncompr), hello) <> 0 then + EXIT_ERR('bad inflate with dict') + else + WriteLn('inflate with dictionary: ', PChar(uncompr)); +end; +{$ENDIF} + +var compr, uncompr: Pointer; + comprLen, uncomprLen: LongInt; + +begin + if zlibVersion^ <> ZLIB_VERSION[1] then + EXIT_ERR('Incompatible zlib version'); + + WriteLn('zlib version: ', zlibVersion); + WriteLn('zlib compile flags: ', Format('0x%x', [zlibCompileFlags])); + + comprLen := 10000 * SizeOf(Integer); (* don't overflow on MSDOS *) + uncomprLen := comprLen; + GetMem(compr, comprLen); + GetMem(uncompr, uncomprLen); + if (compr = NIL) or (uncompr = NIL) then + EXIT_ERR('Out of memory'); + (* compr and uncompr are cleared to avoid reading uninitialized + * data and to ensure that uncompr compresses well. + *) + FillChar(compr^, comprLen, 0); + FillChar(uncompr^, uncomprLen, 0); + + {$IFDEF TEST_COMPRESS} + WriteLn('** Testing compress'); + test_compress(compr, comprLen, uncompr, uncomprLen); + {$ENDIF} + + {$IFDEF TEST_GZIO} + WriteLn('** Testing gzio'); + if ParamCount >= 1 then + test_gzio(ParamStr(1), uncompr, uncomprLen) + else + test_gzio(TESTFILE, uncompr, uncomprLen); + {$ENDIF} + + {$IFDEF TEST_DEFLATE} + WriteLn('** Testing deflate with small buffers'); + test_deflate(compr, comprLen); + {$ENDIF} + {$IFDEF TEST_INFLATE} + WriteLn('** Testing inflate with small buffers'); + test_inflate(compr, comprLen, uncompr, uncomprLen); + {$ENDIF} + + {$IFDEF TEST_DEFLATE} + WriteLn('** Testing deflate with large buffers'); + test_large_deflate(compr, comprLen, uncompr, uncomprLen); + {$ENDIF} + {$IFDEF TEST_INFLATE} + WriteLn('** Testing inflate with large buffers'); + test_large_inflate(compr, comprLen, uncompr, uncomprLen); + {$ENDIF} + + {$IFDEF TEST_FLUSH} + WriteLn('** Testing deflate with full flush'); + test_flush(compr, comprLen); + {$ENDIF} + {$IFDEF TEST_SYNC} + WriteLn('** Testing inflateSync'); + test_sync(compr, comprLen, uncompr, uncomprLen); + {$ENDIF} + comprLen := uncomprLen; + + {$IFDEF TEST_DICT} + WriteLn('** Testing deflate and inflate with preset dictionary'); + test_dict_deflate(compr, comprLen); + test_dict_inflate(compr, comprLen, uncompr, uncomprLen); + {$ENDIF} + + FreeMem(compr, comprLen); + FreeMem(uncompr, uncomprLen); +end. ADDED compat/zlib/contrib/pascal/readme.txt Index: compat/zlib/contrib/pascal/readme.txt ================================================================== --- /dev/null +++ compat/zlib/contrib/pascal/readme.txt @@ -0,0 +1,76 @@ + +This directory contains a Pascal (Delphi, Kylix) interface to the +zlib data compression library. + + +Directory listing +================= + +zlibd32.mak makefile for Borland C++ +example.pas usage example of zlib +zlibpas.pas the Pascal interface to zlib +readme.txt this file + + +Compatibility notes +=================== + +- Although the name "zlib" would have been more normal for the + zlibpas unit, this name is already taken by Borland's ZLib unit. + This is somehow unfortunate, because that unit is not a genuine + interface to the full-fledged zlib functionality, but a suite of + class wrappers around zlib streams. Other essential features, + such as checksums, are missing. + It would have been more appropriate for that unit to have a name + like "ZStreams", or something similar. + +- The C and zlib-supplied types int, uInt, long, uLong, etc. are + translated directly into Pascal types of similar sizes (Integer, + LongInt, etc.), to avoid namespace pollution. In particular, + there is no conversion of unsigned int into a Pascal unsigned + integer. The Word type is non-portable and has the same size + (16 bits) both in a 16-bit and in a 32-bit environment, unlike + Integer. Even if there is a 32-bit Cardinal type, there is no + real need for unsigned int in zlib under a 32-bit environment. + +- Except for the callbacks, the zlib function interfaces are + assuming the calling convention normally used in Pascal + (__pascal for DOS and Windows16, __fastcall for Windows32). + Since the cdecl keyword is used, the old Turbo Pascal does + not work with this interface. + +- The gz* function interfaces are not translated, to avoid + interfacing problems with the C runtime library. Besides, + gzprintf(gzFile file, const char *format, ...) + cannot be translated into Pascal. + + +Legal issues +============ + +The zlibpas interface is: + Copyright (C) 1995-2003 Jean-loup Gailly and Mark Adler. + Copyright (C) 1998 by Bob Dellaca. + Copyright (C) 2003 by Cosmin Truta. + +The example program is: + Copyright (C) 1995-2003 by Jean-loup Gailly. + Copyright (C) 1998,1999,2000 by Jacques Nomssi Nzali. + Copyright (C) 2003 by Cosmin Truta. + + This software is provided 'as-is', without any express or implied + warranty. In no event will the author be held liable for any damages + arising from the use of this software. + + Permission is granted to anyone to use this software for any purpose, + including commercial applications, and to alter it and redistribute it + freely, subject to the following restrictions: + + 1. The origin of this software must not be misrepresented; you must not + claim that you wrote the original software. If you use this software + in a product, an acknowledgment in the product documentation would be + appreciated but is not required. + 2. Altered source versions must be plainly marked as such, and must not be + misrepresented as being the original software. + 3. This notice may not be removed or altered from any source distribution. + ADDED compat/zlib/contrib/pascal/zlibd32.mak Index: compat/zlib/contrib/pascal/zlibd32.mak ================================================================== --- /dev/null +++ compat/zlib/contrib/pascal/zlibd32.mak @@ -0,0 +1,99 @@ +# Makefile for zlib +# For use with Delphi and C++ Builder under Win32 +# Updated for zlib 1.2.x by Cosmin Truta + +# ------------ Borland C++ ------------ + +# This project uses the Delphi (fastcall/register) calling convention: +LOC = -DZEXPORT=__fastcall -DZEXPORTVA=__cdecl + +CC = bcc32 +LD = bcc32 +AR = tlib +# do not use "-pr" in CFLAGS +CFLAGS = -a -d -k- -O2 $(LOC) +LDFLAGS = + + +# variables +ZLIB_LIB = zlib.lib + +OBJ1 = adler32.obj compress.obj crc32.obj deflate.obj gzclose.obj gzlib.obj gzread.obj +OBJ2 = gzwrite.obj infback.obj inffast.obj inflate.obj inftrees.obj trees.obj uncompr.obj zutil.obj +OBJP1 = +adler32.obj+compress.obj+crc32.obj+deflate.obj+gzclose.obj+gzlib.obj+gzread.obj +OBJP2 = +gzwrite.obj+infback.obj+inffast.obj+inflate.obj+inftrees.obj+trees.obj+uncompr.obj+zutil.obj + + +# targets +all: $(ZLIB_LIB) example.exe minigzip.exe + +.c.obj: + $(CC) -c $(CFLAGS) $*.c + +adler32.obj: adler32.c zlib.h zconf.h + +compress.obj: compress.c zlib.h zconf.h + +crc32.obj: crc32.c zlib.h zconf.h crc32.h + +deflate.obj: deflate.c deflate.h zutil.h zlib.h zconf.h + +gzclose.obj: gzclose.c zlib.h zconf.h gzguts.h + +gzlib.obj: gzlib.c zlib.h zconf.h gzguts.h + +gzread.obj: gzread.c zlib.h zconf.h gzguts.h + +gzwrite.obj: gzwrite.c zlib.h zconf.h gzguts.h + +infback.obj: infback.c zutil.h zlib.h zconf.h inftrees.h inflate.h \ + inffast.h inffixed.h + +inffast.obj: inffast.c zutil.h zlib.h zconf.h inftrees.h inflate.h \ + inffast.h + +inflate.obj: inflate.c zutil.h zlib.h zconf.h inftrees.h inflate.h \ + inffast.h inffixed.h + +inftrees.obj: inftrees.c zutil.h zlib.h zconf.h inftrees.h + +trees.obj: trees.c zutil.h zlib.h zconf.h deflate.h trees.h + +uncompr.obj: uncompr.c zlib.h zconf.h + +zutil.obj: zutil.c zutil.h zlib.h zconf.h + +example.obj: test/example.c zlib.h zconf.h + +minigzip.obj: test/minigzip.c zlib.h zconf.h + + +# For the sake of the old Borland make, +# the command line is cut to fit in the MS-DOS 128 byte limit: +$(ZLIB_LIB): $(OBJ1) $(OBJ2) + -del $(ZLIB_LIB) + $(AR) $(ZLIB_LIB) $(OBJP1) + $(AR) $(ZLIB_LIB) $(OBJP2) + + +# testing +test: example.exe minigzip.exe + example + echo hello world | minigzip | minigzip -d + +example.exe: example.obj $(ZLIB_LIB) + $(LD) $(LDFLAGS) example.obj $(ZLIB_LIB) + +minigzip.exe: minigzip.obj $(ZLIB_LIB) + $(LD) $(LDFLAGS) minigzip.obj $(ZLIB_LIB) + + +# cleanup +clean: + -del *.obj + -del *.exe + -del *.lib + -del *.tds + -del zlib.bak + -del foo.gz + ADDED compat/zlib/contrib/pascal/zlibpas.pas Index: compat/zlib/contrib/pascal/zlibpas.pas ================================================================== --- /dev/null +++ compat/zlib/contrib/pascal/zlibpas.pas @@ -0,0 +1,276 @@ +(* zlibpas -- Pascal interface to the zlib data compression library + * + * Copyright (C) 2003 Cosmin Truta. + * Derived from original sources by Bob Dellaca. + * For conditions of distribution and use, see copyright notice in readme.txt + *) + +unit zlibpas; + +interface + +const + ZLIB_VERSION = '1.2.11'; + ZLIB_VERNUM = $12a0; + +type + alloc_func = function(opaque: Pointer; items, size: Integer): Pointer; + cdecl; + free_func = procedure(opaque, address: Pointer); + cdecl; + + in_func = function(opaque: Pointer; var buf: PByte): Integer; + cdecl; + out_func = function(opaque: Pointer; buf: PByte; size: Integer): Integer; + cdecl; + + z_streamp = ^z_stream; + z_stream = packed record + next_in: PChar; (* next input byte *) + avail_in: Integer; (* number of bytes available at next_in *) + total_in: LongInt; (* total nb of input bytes read so far *) + + next_out: PChar; (* next output byte should be put there *) + avail_out: Integer; (* remaining free space at next_out *) + total_out: LongInt; (* total nb of bytes output so far *) + + msg: PChar; (* last error message, NULL if no error *) + state: Pointer; (* not visible by applications *) + + zalloc: alloc_func; (* used to allocate the internal state *) + zfree: free_func; (* used to free the internal state *) + opaque: Pointer; (* private data object passed to zalloc and zfree *) + + data_type: Integer; (* best guess about the data type: ascii or binary *) + adler: LongInt; (* adler32 value of the uncompressed data *) + reserved: LongInt; (* reserved for future use *) + end; + + gz_headerp = ^gz_header; + gz_header = packed record + text: Integer; (* true if compressed data believed to be text *) + time: LongInt; (* modification time *) + xflags: Integer; (* extra flags (not used when writing a gzip file) *) + os: Integer; (* operating system *) + extra: PChar; (* pointer to extra field or Z_NULL if none *) + extra_len: Integer; (* extra field length (valid if extra != Z_NULL) *) + extra_max: Integer; (* space at extra (only when reading header) *) + name: PChar; (* pointer to zero-terminated file name or Z_NULL *) + name_max: Integer; (* space at name (only when reading header) *) + comment: PChar; (* pointer to zero-terminated comment or Z_NULL *) + comm_max: Integer; (* space at comment (only when reading header) *) + hcrc: Integer; (* true if there was or will be a header crc *) + done: Integer; (* true when done reading gzip header *) + end; + +(* constants *) +const + Z_NO_FLUSH = 0; + Z_PARTIAL_FLUSH = 1; + Z_SYNC_FLUSH = 2; + Z_FULL_FLUSH = 3; + Z_FINISH = 4; + Z_BLOCK = 5; + Z_TREES = 6; + + Z_OK = 0; + Z_STREAM_END = 1; + Z_NEED_DICT = 2; + Z_ERRNO = -1; + Z_STREAM_ERROR = -2; + Z_DATA_ERROR = -3; + Z_MEM_ERROR = -4; + Z_BUF_ERROR = -5; + Z_VERSION_ERROR = -6; + + Z_NO_COMPRESSION = 0; + Z_BEST_SPEED = 1; + Z_BEST_COMPRESSION = 9; + Z_DEFAULT_COMPRESSION = -1; + + Z_FILTERED = 1; + Z_HUFFMAN_ONLY = 2; + Z_RLE = 3; + Z_FIXED = 4; + Z_DEFAULT_STRATEGY = 0; + + Z_BINARY = 0; + Z_TEXT = 1; + Z_ASCII = 1; + Z_UNKNOWN = 2; + + Z_DEFLATED = 8; + +(* basic functions *) +function zlibVersion: PChar; +function deflateInit(var strm: z_stream; level: Integer): Integer; +function deflate(var strm: z_stream; flush: Integer): Integer; +function deflateEnd(var strm: z_stream): Integer; +function inflateInit(var strm: z_stream): Integer; +function inflate(var strm: z_stream; flush: Integer): Integer; +function inflateEnd(var strm: z_stream): Integer; + +(* advanced functions *) +function deflateInit2(var strm: z_stream; level, method, windowBits, + memLevel, strategy: Integer): Integer; +function deflateSetDictionary(var strm: z_stream; const dictionary: PChar; + dictLength: Integer): Integer; +function deflateCopy(var dest, source: z_stream): Integer; +function deflateReset(var strm: z_stream): Integer; +function deflateParams(var strm: z_stream; level, strategy: Integer): Integer; +function deflateTune(var strm: z_stream; good_length, max_lazy, nice_length, max_chain: Integer): Integer; +function deflateBound(var strm: z_stream; sourceLen: LongInt): LongInt; +function deflatePending(var strm: z_stream; var pending: Integer; var bits: Integer): Integer; +function deflatePrime(var strm: z_stream; bits, value: Integer): Integer; +function deflateSetHeader(var strm: z_stream; head: gz_header): Integer; +function inflateInit2(var strm: z_stream; windowBits: Integer): Integer; +function inflateSetDictionary(var strm: z_stream; const dictionary: PChar; + dictLength: Integer): Integer; +function inflateSync(var strm: z_stream): Integer; +function inflateCopy(var dest, source: z_stream): Integer; +function inflateReset(var strm: z_stream): Integer; +function inflateReset2(var strm: z_stream; windowBits: Integer): Integer; +function inflatePrime(var strm: z_stream; bits, value: Integer): Integer; +function inflateMark(var strm: z_stream): LongInt; +function inflateGetHeader(var strm: z_stream; var head: gz_header): Integer; +function inflateBackInit(var strm: z_stream; + windowBits: Integer; window: PChar): Integer; +function inflateBack(var strm: z_stream; in_fn: in_func; in_desc: Pointer; + out_fn: out_func; out_desc: Pointer): Integer; +function inflateBackEnd(var strm: z_stream): Integer; +function zlibCompileFlags: LongInt; + +(* utility functions *) +function compress(dest: PChar; var destLen: LongInt; + const source: PChar; sourceLen: LongInt): Integer; +function compress2(dest: PChar; var destLen: LongInt; + const source: PChar; sourceLen: LongInt; + level: Integer): Integer; +function compressBound(sourceLen: LongInt): LongInt; +function uncompress(dest: PChar; var destLen: LongInt; + const source: PChar; sourceLen: LongInt): Integer; + +(* checksum functions *) +function adler32(adler: LongInt; const buf: PChar; len: Integer): LongInt; +function adler32_combine(adler1, adler2, len2: LongInt): LongInt; +function crc32(crc: LongInt; const buf: PChar; len: Integer): LongInt; +function crc32_combine(crc1, crc2, len2: LongInt): LongInt; + +(* various hacks, don't look :) *) +function deflateInit_(var strm: z_stream; level: Integer; + const version: PChar; stream_size: Integer): Integer; +function inflateInit_(var strm: z_stream; const version: PChar; + stream_size: Integer): Integer; +function deflateInit2_(var strm: z_stream; + level, method, windowBits, memLevel, strategy: Integer; + const version: PChar; stream_size: Integer): Integer; +function inflateInit2_(var strm: z_stream; windowBits: Integer; + const version: PChar; stream_size: Integer): Integer; +function inflateBackInit_(var strm: z_stream; + windowBits: Integer; window: PChar; + const version: PChar; stream_size: Integer): Integer; + + +implementation + +{$L adler32.obj} +{$L compress.obj} +{$L crc32.obj} +{$L deflate.obj} +{$L infback.obj} +{$L inffast.obj} +{$L inflate.obj} +{$L inftrees.obj} +{$L trees.obj} +{$L uncompr.obj} +{$L zutil.obj} + +function adler32; external; +function adler32_combine; external; +function compress; external; +function compress2; external; +function compressBound; external; +function crc32; external; +function crc32_combine; external; +function deflate; external; +function deflateBound; external; +function deflateCopy; external; +function deflateEnd; external; +function deflateInit_; external; +function deflateInit2_; external; +function deflateParams; external; +function deflatePending; external; +function deflatePrime; external; +function deflateReset; external; +function deflateSetDictionary; external; +function deflateSetHeader; external; +function deflateTune; external; +function inflate; external; +function inflateBack; external; +function inflateBackEnd; external; +function inflateBackInit_; external; +function inflateCopy; external; +function inflateEnd; external; +function inflateGetHeader; external; +function inflateInit_; external; +function inflateInit2_; external; +function inflateMark; external; +function inflatePrime; external; +function inflateReset; external; +function inflateReset2; external; +function inflateSetDictionary; external; +function inflateSync; external; +function uncompress; external; +function zlibCompileFlags; external; +function zlibVersion; external; + +function deflateInit(var strm: z_stream; level: Integer): Integer; +begin + Result := deflateInit_(strm, level, ZLIB_VERSION, sizeof(z_stream)); +end; + +function deflateInit2(var strm: z_stream; level, method, windowBits, memLevel, + strategy: Integer): Integer; +begin + Result := deflateInit2_(strm, level, method, windowBits, memLevel, strategy, + ZLIB_VERSION, sizeof(z_stream)); +end; + +function inflateInit(var strm: z_stream): Integer; +begin + Result := inflateInit_(strm, ZLIB_VERSION, sizeof(z_stream)); +end; + +function inflateInit2(var strm: z_stream; windowBits: Integer): Integer; +begin + Result := inflateInit2_(strm, windowBits, ZLIB_VERSION, sizeof(z_stream)); +end; + +function inflateBackInit(var strm: z_stream; + windowBits: Integer; window: PChar): Integer; +begin + Result := inflateBackInit_(strm, windowBits, window, + ZLIB_VERSION, sizeof(z_stream)); +end; + +function _malloc(Size: Integer): Pointer; cdecl; +begin + GetMem(Result, Size); +end; + +procedure _free(Block: Pointer); cdecl; +begin + FreeMem(Block); +end; + +procedure _memset(P: Pointer; B: Byte; count: Integer); cdecl; +begin + FillChar(P^, count, B); +end; + +procedure _memcpy(dest, source: Pointer; count: Integer); cdecl; +begin + Move(source^, dest^, count); +end; + +end. ADDED compat/zlib/contrib/puff/Makefile Index: compat/zlib/contrib/puff/Makefile ================================================================== --- /dev/null +++ compat/zlib/contrib/puff/Makefile @@ -0,0 +1,42 @@ +CFLAGS=-O + +puff: puff.o pufftest.o + +puff.o: puff.h + +pufftest.o: puff.h + +test: puff + puff zeros.raw + +puft: puff.c puff.h pufftest.o + cc -fprofile-arcs -ftest-coverage -o puft puff.c pufftest.o + +# puff full coverage test (should say 100%) +cov: puft + @rm -f *.gcov *.gcda + @puft -w zeros.raw 2>&1 | cat > /dev/null + @echo '04' | xxd -r -p | puft 2> /dev/null || test $$? -eq 2 + @echo '00' | xxd -r -p | puft 2> /dev/null || test $$? -eq 2 + @echo '00 00 00 00 00' | xxd -r -p | puft 2> /dev/null || test $$? -eq 254 + @echo '00 01 00 fe ff' | xxd -r -p | puft 2> /dev/null || test $$? -eq 2 + @echo '01 01 00 fe ff 0a' | xxd -r -p | puft -f 2>&1 | cat > /dev/null + @echo '02 7e ff ff' | xxd -r -p | puft 2> /dev/null || test $$? -eq 246 + @echo '02' | xxd -r -p | puft 2> /dev/null || test $$? -eq 2 + @echo '04 80 49 92 24 49 92 24 0f b4 ff ff c3 04' | xxd -r -p | puft 2> /dev/null || test $$? -eq 2 + @echo '04 80 49 92 24 49 92 24 71 ff ff 93 11 00' | xxd -r -p | puft 2> /dev/null || test $$? -eq 249 + @echo '04 c0 81 08 00 00 00 00 20 7f eb 0b 00 00' | xxd -r -p | puft 2> /dev/null || test $$? -eq 246 + @echo '0b 00 00' | xxd -r -p | puft -f 2>&1 | cat > /dev/null + @echo '1a 07' | xxd -r -p | puft 2> /dev/null || test $$? -eq 246 + @echo '0c c0 81 00 00 00 00 00 90 ff 6b 04' | xxd -r -p | puft 2> /dev/null || test $$? -eq 245 + @puft -f zeros.raw 2>&1 | cat > /dev/null + @echo 'fc 00 00' | xxd -r -p | puft 2> /dev/null || test $$? -eq 253 + @echo '04 00 fe ff' | xxd -r -p | puft 2> /dev/null || test $$? -eq 252 + @echo '04 00 24 49' | xxd -r -p | puft 2> /dev/null || test $$? -eq 251 + @echo '04 80 49 92 24 49 92 24 0f b4 ff ff c3 84' | xxd -r -p | puft 2> /dev/null || test $$? -eq 248 + @echo '04 00 24 e9 ff ff' | xxd -r -p | puft 2> /dev/null || test $$? -eq 250 + @echo '04 00 24 e9 ff 6d' | xxd -r -p | puft 2> /dev/null || test $$? -eq 247 + @gcov -n puff.c + +clean: + rm -f puff puft *.o *.gc* ADDED compat/zlib/contrib/puff/README Index: compat/zlib/contrib/puff/README ================================================================== --- /dev/null +++ compat/zlib/contrib/puff/README @@ -0,0 +1,63 @@ +Puff -- A Simple Inflate +3 Mar 2003 +Mark Adler +madler@alumni.caltech.edu + +What this is -- + +puff.c provides the routine puff() to decompress the deflate data format. It +does so more slowly than zlib, but the code is about one-fifth the size of the +inflate code in zlib, and written to be very easy to read. + +Why I wrote this -- + +puff.c was written to document the deflate format unambiguously, by virtue of +being working C code. It is meant to supplement RFC 1951, which formally +describes the deflate format. I have received many questions on details of the +deflate format, and I hope that reading this code will answer those questions. +puff.c is heavily commented with details of the deflate format, especially +those little nooks and cranies of the format that might not be obvious from a +specification. + +puff.c may also be useful in applications where code size or memory usage is a +very limited resource, and speed is not as important. + +How to use it -- + +Well, most likely you should just be reading puff.c and using zlib for actual +applications, but if you must ... + +Include puff.h in your code, which provides this prototype: + +int puff(unsigned char *dest, /* pointer to destination pointer */ + unsigned long *destlen, /* amount of output space */ + unsigned char *source, /* pointer to source data pointer */ + unsigned long *sourcelen); /* amount of input available */ + +Then you can call puff() to decompress a deflate stream that is in memory in +its entirety at source, to a sufficiently sized block of memory for the +decompressed data at dest. puff() is the only external symbol in puff.c The +only C library functions that puff.c needs are setjmp() and longjmp(), which +are used to simplify error checking in the code to improve readabilty. puff.c +does no memory allocation, and uses less than 2K bytes off of the stack. + +If destlen is not enough space for the uncompressed data, then inflate will +return an error without writing more than destlen bytes. Note that this means +that in order to decompress the deflate data successfully, you need to know +the size of the uncompressed data ahead of time. + +If needed, puff() can determine the size of the uncompressed data with no +output space. This is done by passing dest equal to (unsigned char *)0. Then +the initial value of *destlen is ignored and *destlen is set to the length of +the uncompressed data. So if the size of the uncompressed data is not known, +then two passes of puff() can be used--first to determine the size, and second +to do the actual inflation after allocating the appropriate memory. Not +pretty, but it works. (This is one of the reasons you should be using zlib.) + +The deflate format is self-terminating. If the deflate stream does not end +in *sourcelen bytes, puff() will return an error without reading at or past +endsource. + +On return, *sourcelen is updated to the amount of input data consumed, and +*destlen is updated to the size of the uncompressed data. See the comments +in puff.c for the possible return codes for puff(). ADDED compat/zlib/contrib/puff/puff.c Index: compat/zlib/contrib/puff/puff.c ================================================================== --- /dev/null +++ compat/zlib/contrib/puff/puff.c @@ -0,0 +1,840 @@ +/* + * puff.c + * Copyright (C) 2002-2013 Mark Adler + * For conditions of distribution and use, see copyright notice in puff.h + * version 2.3, 21 Jan 2013 + * + * puff.c is a simple inflate written to be an unambiguous way to specify the + * deflate format. It is not written for speed but rather simplicity. As a + * side benefit, this code might actually be useful when small code is more + * important than speed, such as bootstrap applications. For typical deflate + * data, zlib's inflate() is about four times as fast as puff(). zlib's + * inflate compiles to around 20K on my machine, whereas puff.c compiles to + * around 4K on my machine (a PowerPC using GNU cc). If the faster decode() + * function here is used, then puff() is only twice as slow as zlib's + * inflate(). + * + * All dynamically allocated memory comes from the stack. The stack required + * is less than 2K bytes. This code is compatible with 16-bit int's and + * assumes that long's are at least 32 bits. puff.c uses the short data type, + * assumed to be 16 bits, for arrays in order to conserve memory. The code + * works whether integers are stored big endian or little endian. + * + * In the comments below are "Format notes" that describe the inflate process + * and document some of the less obvious aspects of the format. This source + * code is meant to supplement RFC 1951, which formally describes the deflate + * format: + * + * http://www.zlib.org/rfc-deflate.html + */ + +/* + * Change history: + * + * 1.0 10 Feb 2002 - First version + * 1.1 17 Feb 2002 - Clarifications of some comments and notes + * - Update puff() dest and source pointers on negative + * errors to facilitate debugging deflators + * - Remove longest from struct huffman -- not needed + * - Simplify offs[] index in construct() + * - Add input size and checking, using longjmp() to + * maintain easy readability + * - Use short data type for large arrays + * - Use pointers instead of long to specify source and + * destination sizes to avoid arbitrary 4 GB limits + * 1.2 17 Mar 2002 - Add faster version of decode(), doubles speed (!), + * but leave simple version for readabilty + * - Make sure invalid distances detected if pointers + * are 16 bits + * - Fix fixed codes table error + * - Provide a scanning mode for determining size of + * uncompressed data + * 1.3 20 Mar 2002 - Go back to lengths for puff() parameters [Gailly] + * - Add a puff.h file for the interface + * - Add braces in puff() for else do [Gailly] + * - Use indexes instead of pointers for readability + * 1.4 31 Mar 2002 - Simplify construct() code set check + * - Fix some comments + * - Add FIXLCODES #define + * 1.5 6 Apr 2002 - Minor comment fixes + * 1.6 7 Aug 2002 - Minor format changes + * 1.7 3 Mar 2003 - Added test code for distribution + * - Added zlib-like license + * 1.8 9 Jan 2004 - Added some comments on no distance codes case + * 1.9 21 Feb 2008 - Fix bug on 16-bit integer architectures [Pohland] + * - Catch missing end-of-block symbol error + * 2.0 25 Jul 2008 - Add #define to permit distance too far back + * - Add option in TEST code for puff to write the data + * - Add option in TEST code to skip input bytes + * - Allow TEST code to read from piped stdin + * 2.1 4 Apr 2010 - Avoid variable initialization for happier compilers + * - Avoid unsigned comparisons for even happier compilers + * 2.2 25 Apr 2010 - Fix bug in variable initializations [Oberhumer] + * - Add const where appropriate [Oberhumer] + * - Split if's and ?'s for coverage testing + * - Break out test code to separate file + * - Move NIL to puff.h + * - Allow incomplete code only if single code length is 1 + * - Add full code coverage test to Makefile + * 2.3 21 Jan 2013 - Check for invalid code length codes in dynamic blocks + */ + +#include /* for setjmp(), longjmp(), and jmp_buf */ +#include "puff.h" /* prototype for puff() */ + +#define local static /* for local function definitions */ + +/* + * Maximums for allocations and loops. It is not useful to change these -- + * they are fixed by the deflate format. + */ +#define MAXBITS 15 /* maximum bits in a code */ +#define MAXLCODES 286 /* maximum number of literal/length codes */ +#define MAXDCODES 30 /* maximum number of distance codes */ +#define MAXCODES (MAXLCODES+MAXDCODES) /* maximum codes lengths to read */ +#define FIXLCODES 288 /* number of fixed literal/length codes */ + +/* input and output state */ +struct state { + /* output state */ + unsigned char *out; /* output buffer */ + unsigned long outlen; /* available space at out */ + unsigned long outcnt; /* bytes written to out so far */ + + /* input state */ + const unsigned char *in; /* input buffer */ + unsigned long inlen; /* available input at in */ + unsigned long incnt; /* bytes read so far */ + int bitbuf; /* bit buffer */ + int bitcnt; /* number of bits in bit buffer */ + + /* input limit error return state for bits() and decode() */ + jmp_buf env; +}; + +/* + * Return need bits from the input stream. This always leaves less than + * eight bits in the buffer. bits() works properly for need == 0. + * + * Format notes: + * + * - Bits are stored in bytes from the least significant bit to the most + * significant bit. Therefore bits are dropped from the bottom of the bit + * buffer, using shift right, and new bytes are appended to the top of the + * bit buffer, using shift left. + */ +local int bits(struct state *s, int need) +{ + long val; /* bit accumulator (can use up to 20 bits) */ + + /* load at least need bits into val */ + val = s->bitbuf; + while (s->bitcnt < need) { + if (s->incnt == s->inlen) + longjmp(s->env, 1); /* out of input */ + val |= (long)(s->in[s->incnt++]) << s->bitcnt; /* load eight bits */ + s->bitcnt += 8; + } + + /* drop need bits and update buffer, always zero to seven bits left */ + s->bitbuf = (int)(val >> need); + s->bitcnt -= need; + + /* return need bits, zeroing the bits above that */ + return (int)(val & ((1L << need) - 1)); +} + +/* + * Process a stored block. + * + * Format notes: + * + * - After the two-bit stored block type (00), the stored block length and + * stored bytes are byte-aligned for fast copying. Therefore any leftover + * bits in the byte that has the last bit of the type, as many as seven, are + * discarded. The value of the discarded bits are not defined and should not + * be checked against any expectation. + * + * - The second inverted copy of the stored block length does not have to be + * checked, but it's probably a good idea to do so anyway. + * + * - A stored block can have zero length. This is sometimes used to byte-align + * subsets of the compressed data for random access or partial recovery. + */ +local int stored(struct state *s) +{ + unsigned len; /* length of stored block */ + + /* discard leftover bits from current byte (assumes s->bitcnt < 8) */ + s->bitbuf = 0; + s->bitcnt = 0; + + /* get length and check against its one's complement */ + if (s->incnt + 4 > s->inlen) + return 2; /* not enough input */ + len = s->in[s->incnt++]; + len |= s->in[s->incnt++] << 8; + if (s->in[s->incnt++] != (~len & 0xff) || + s->in[s->incnt++] != ((~len >> 8) & 0xff)) + return -2; /* didn't match complement! */ + + /* copy len bytes from in to out */ + if (s->incnt + len > s->inlen) + return 2; /* not enough input */ + if (s->out != NIL) { + if (s->outcnt + len > s->outlen) + return 1; /* not enough output space */ + while (len--) + s->out[s->outcnt++] = s->in[s->incnt++]; + } + else { /* just scanning */ + s->outcnt += len; + s->incnt += len; + } + + /* done with a valid stored block */ + return 0; +} + +/* + * Huffman code decoding tables. count[1..MAXBITS] is the number of symbols of + * each length, which for a canonical code are stepped through in order. + * symbol[] are the symbol values in canonical order, where the number of + * entries is the sum of the counts in count[]. The decoding process can be + * seen in the function decode() below. + */ +struct huffman { + short *count; /* number of symbols of each length */ + short *symbol; /* canonically ordered symbols */ +}; + +/* + * Decode a code from the stream s using huffman table h. Return the symbol or + * a negative value if there is an error. If all of the lengths are zero, i.e. + * an empty code, or if the code is incomplete and an invalid code is received, + * then -10 is returned after reading MAXBITS bits. + * + * Format notes: + * + * - The codes as stored in the compressed data are bit-reversed relative to + * a simple integer ordering of codes of the same lengths. Hence below the + * bits are pulled from the compressed data one at a time and used to + * build the code value reversed from what is in the stream in order to + * permit simple integer comparisons for decoding. A table-based decoding + * scheme (as used in zlib) does not need to do this reversal. + * + * - The first code for the shortest length is all zeros. Subsequent codes of + * the same length are simply integer increments of the previous code. When + * moving up a length, a zero bit is appended to the code. For a complete + * code, the last code of the longest length will be all ones. + * + * - Incomplete codes are handled by this decoder, since they are permitted + * in the deflate format. See the format notes for fixed() and dynamic(). + */ +#ifdef SLOW +local int decode(struct state *s, const struct huffman *h) +{ + int len; /* current number of bits in code */ + int code; /* len bits being decoded */ + int first; /* first code of length len */ + int count; /* number of codes of length len */ + int index; /* index of first code of length len in symbol table */ + + code = first = index = 0; + for (len = 1; len <= MAXBITS; len++) { + code |= bits(s, 1); /* get next bit */ + count = h->count[len]; + if (code - count < first) /* if length len, return symbol */ + return h->symbol[index + (code - first)]; + index += count; /* else update for next length */ + first += count; + first <<= 1; + code <<= 1; + } + return -10; /* ran out of codes */ +} + +/* + * A faster version of decode() for real applications of this code. It's not + * as readable, but it makes puff() twice as fast. And it only makes the code + * a few percent larger. + */ +#else /* !SLOW */ +local int decode(struct state *s, const struct huffman *h) +{ + int len; /* current number of bits in code */ + int code; /* len bits being decoded */ + int first; /* first code of length len */ + int count; /* number of codes of length len */ + int index; /* index of first code of length len in symbol table */ + int bitbuf; /* bits from stream */ + int left; /* bits left in next or left to process */ + short *next; /* next number of codes */ + + bitbuf = s->bitbuf; + left = s->bitcnt; + code = first = index = 0; + len = 1; + next = h->count + 1; + while (1) { + while (left--) { + code |= bitbuf & 1; + bitbuf >>= 1; + count = *next++; + if (code - count < first) { /* if length len, return symbol */ + s->bitbuf = bitbuf; + s->bitcnt = (s->bitcnt - len) & 7; + return h->symbol[index + (code - first)]; + } + index += count; /* else update for next length */ + first += count; + first <<= 1; + code <<= 1; + len++; + } + left = (MAXBITS+1) - len; + if (left == 0) + break; + if (s->incnt == s->inlen) + longjmp(s->env, 1); /* out of input */ + bitbuf = s->in[s->incnt++]; + if (left > 8) + left = 8; + } + return -10; /* ran out of codes */ +} +#endif /* SLOW */ + +/* + * Given the list of code lengths length[0..n-1] representing a canonical + * Huffman code for n symbols, construct the tables required to decode those + * codes. Those tables are the number of codes of each length, and the symbols + * sorted by length, retaining their original order within each length. The + * return value is zero for a complete code set, negative for an over- + * subscribed code set, and positive for an incomplete code set. The tables + * can be used if the return value is zero or positive, but they cannot be used + * if the return value is negative. If the return value is zero, it is not + * possible for decode() using that table to return an error--any stream of + * enough bits will resolve to a symbol. If the return value is positive, then + * it is possible for decode() using that table to return an error for received + * codes past the end of the incomplete lengths. + * + * Not used by decode(), but used for error checking, h->count[0] is the number + * of the n symbols not in the code. So n - h->count[0] is the number of + * codes. This is useful for checking for incomplete codes that have more than + * one symbol, which is an error in a dynamic block. + * + * Assumption: for all i in 0..n-1, 0 <= length[i] <= MAXBITS + * This is assured by the construction of the length arrays in dynamic() and + * fixed() and is not verified by construct(). + * + * Format notes: + * + * - Permitted and expected examples of incomplete codes are one of the fixed + * codes and any code with a single symbol which in deflate is coded as one + * bit instead of zero bits. See the format notes for fixed() and dynamic(). + * + * - Within a given code length, the symbols are kept in ascending order for + * the code bits definition. + */ +local int construct(struct huffman *h, const short *length, int n) +{ + int symbol; /* current symbol when stepping through length[] */ + int len; /* current length when stepping through h->count[] */ + int left; /* number of possible codes left of current length */ + short offs[MAXBITS+1]; /* offsets in symbol table for each length */ + + /* count number of codes of each length */ + for (len = 0; len <= MAXBITS; len++) + h->count[len] = 0; + for (symbol = 0; symbol < n; symbol++) + (h->count[length[symbol]])++; /* assumes lengths are within bounds */ + if (h->count[0] == n) /* no codes! */ + return 0; /* complete, but decode() will fail */ + + /* check for an over-subscribed or incomplete set of lengths */ + left = 1; /* one possible code of zero length */ + for (len = 1; len <= MAXBITS; len++) { + left <<= 1; /* one more bit, double codes left */ + left -= h->count[len]; /* deduct count from possible codes */ + if (left < 0) + return left; /* over-subscribed--return negative */ + } /* left > 0 means incomplete */ + + /* generate offsets into symbol table for each length for sorting */ + offs[1] = 0; + for (len = 1; len < MAXBITS; len++) + offs[len + 1] = offs[len] + h->count[len]; + + /* + * put symbols in table sorted by length, by symbol order within each + * length + */ + for (symbol = 0; symbol < n; symbol++) + if (length[symbol] != 0) + h->symbol[offs[length[symbol]]++] = symbol; + + /* return zero for complete set, positive for incomplete set */ + return left; +} + +/* + * Decode literal/length and distance codes until an end-of-block code. + * + * Format notes: + * + * - Compressed data that is after the block type if fixed or after the code + * description if dynamic is a combination of literals and length/distance + * pairs terminated by and end-of-block code. Literals are simply Huffman + * coded bytes. A length/distance pair is a coded length followed by a + * coded distance to represent a string that occurs earlier in the + * uncompressed data that occurs again at the current location. + * + * - Literals, lengths, and the end-of-block code are combined into a single + * code of up to 286 symbols. They are 256 literals (0..255), 29 length + * symbols (257..285), and the end-of-block symbol (256). + * + * - There are 256 possible lengths (3..258), and so 29 symbols are not enough + * to represent all of those. Lengths 3..10 and 258 are in fact represented + * by just a length symbol. Lengths 11..257 are represented as a symbol and + * some number of extra bits that are added as an integer to the base length + * of the length symbol. The number of extra bits is determined by the base + * length symbol. These are in the static arrays below, lens[] for the base + * lengths and lext[] for the corresponding number of extra bits. + * + * - The reason that 258 gets its own symbol is that the longest length is used + * often in highly redundant files. Note that 258 can also be coded as the + * base value 227 plus the maximum extra value of 31. While a good deflate + * should never do this, it is not an error, and should be decoded properly. + * + * - If a length is decoded, including its extra bits if any, then it is + * followed a distance code. There are up to 30 distance symbols. Again + * there are many more possible distances (1..32768), so extra bits are added + * to a base value represented by the symbol. The distances 1..4 get their + * own symbol, but the rest require extra bits. The base distances and + * corresponding number of extra bits are below in the static arrays dist[] + * and dext[]. + * + * - Literal bytes are simply written to the output. A length/distance pair is + * an instruction to copy previously uncompressed bytes to the output. The + * copy is from distance bytes back in the output stream, copying for length + * bytes. + * + * - Distances pointing before the beginning of the output data are not + * permitted. + * + * - Overlapped copies, where the length is greater than the distance, are + * allowed and common. For example, a distance of one and a length of 258 + * simply copies the last byte 258 times. A distance of four and a length of + * twelve copies the last four bytes three times. A simple forward copy + * ignoring whether the length is greater than the distance or not implements + * this correctly. You should not use memcpy() since its behavior is not + * defined for overlapped arrays. You should not use memmove() or bcopy() + * since though their behavior -is- defined for overlapping arrays, it is + * defined to do the wrong thing in this case. + */ +local int codes(struct state *s, + const struct huffman *lencode, + const struct huffman *distcode) +{ + int symbol; /* decoded symbol */ + int len; /* length for copy */ + unsigned dist; /* distance for copy */ + static const short lens[29] = { /* Size base for length codes 257..285 */ + 3, 4, 5, 6, 7, 8, 9, 10, 11, 13, 15, 17, 19, 23, 27, 31, + 35, 43, 51, 59, 67, 83, 99, 115, 131, 163, 195, 227, 258}; + static const short lext[29] = { /* Extra bits for length codes 257..285 */ + 0, 0, 0, 0, 0, 0, 0, 0, 1, 1, 1, 1, 2, 2, 2, 2, + 3, 3, 3, 3, 4, 4, 4, 4, 5, 5, 5, 5, 0}; + static const short dists[30] = { /* Offset base for distance codes 0..29 */ + 1, 2, 3, 4, 5, 7, 9, 13, 17, 25, 33, 49, 65, 97, 129, 193, + 257, 385, 513, 769, 1025, 1537, 2049, 3073, 4097, 6145, + 8193, 12289, 16385, 24577}; + static const short dext[30] = { /* Extra bits for distance codes 0..29 */ + 0, 0, 0, 0, 1, 1, 2, 2, 3, 3, 4, 4, 5, 5, 6, 6, + 7, 7, 8, 8, 9, 9, 10, 10, 11, 11, + 12, 12, 13, 13}; + + /* decode literals and length/distance pairs */ + do { + symbol = decode(s, lencode); + if (symbol < 0) + return symbol; /* invalid symbol */ + if (symbol < 256) { /* literal: symbol is the byte */ + /* write out the literal */ + if (s->out != NIL) { + if (s->outcnt == s->outlen) + return 1; + s->out[s->outcnt] = symbol; + } + s->outcnt++; + } + else if (symbol > 256) { /* length */ + /* get and compute length */ + symbol -= 257; + if (symbol >= 29) + return -10; /* invalid fixed code */ + len = lens[symbol] + bits(s, lext[symbol]); + + /* get and check distance */ + symbol = decode(s, distcode); + if (symbol < 0) + return symbol; /* invalid symbol */ + dist = dists[symbol] + bits(s, dext[symbol]); +#ifndef INFLATE_ALLOW_INVALID_DISTANCE_TOOFAR_ARRR + if (dist > s->outcnt) + return -11; /* distance too far back */ +#endif + + /* copy length bytes from distance bytes back */ + if (s->out != NIL) { + if (s->outcnt + len > s->outlen) + return 1; + while (len--) { + s->out[s->outcnt] = +#ifdef INFLATE_ALLOW_INVALID_DISTANCE_TOOFAR_ARRR + dist > s->outcnt ? + 0 : +#endif + s->out[s->outcnt - dist]; + s->outcnt++; + } + } + else + s->outcnt += len; + } + } while (symbol != 256); /* end of block symbol */ + + /* done with a valid fixed or dynamic block */ + return 0; +} + +/* + * Process a fixed codes block. + * + * Format notes: + * + * - This block type can be useful for compressing small amounts of data for + * which the size of the code descriptions in a dynamic block exceeds the + * benefit of custom codes for that block. For fixed codes, no bits are + * spent on code descriptions. Instead the code lengths for literal/length + * codes and distance codes are fixed. The specific lengths for each symbol + * can be seen in the "for" loops below. + * + * - The literal/length code is complete, but has two symbols that are invalid + * and should result in an error if received. This cannot be implemented + * simply as an incomplete code since those two symbols are in the "middle" + * of the code. They are eight bits long and the longest literal/length\ + * code is nine bits. Therefore the code must be constructed with those + * symbols, and the invalid symbols must be detected after decoding. + * + * - The fixed distance codes also have two invalid symbols that should result + * in an error if received. Since all of the distance codes are the same + * length, this can be implemented as an incomplete code. Then the invalid + * codes are detected while decoding. + */ +local int fixed(struct state *s) +{ + static int virgin = 1; + static short lencnt[MAXBITS+1], lensym[FIXLCODES]; + static short distcnt[MAXBITS+1], distsym[MAXDCODES]; + static struct huffman lencode, distcode; + + /* build fixed huffman tables if first call (may not be thread safe) */ + if (virgin) { + int symbol; + short lengths[FIXLCODES]; + + /* construct lencode and distcode */ + lencode.count = lencnt; + lencode.symbol = lensym; + distcode.count = distcnt; + distcode.symbol = distsym; + + /* literal/length table */ + for (symbol = 0; symbol < 144; symbol++) + lengths[symbol] = 8; + for (; symbol < 256; symbol++) + lengths[symbol] = 9; + for (; symbol < 280; symbol++) + lengths[symbol] = 7; + for (; symbol < FIXLCODES; symbol++) + lengths[symbol] = 8; + construct(&lencode, lengths, FIXLCODES); + + /* distance table */ + for (symbol = 0; symbol < MAXDCODES; symbol++) + lengths[symbol] = 5; + construct(&distcode, lengths, MAXDCODES); + + /* do this just once */ + virgin = 0; + } + + /* decode data until end-of-block code */ + return codes(s, &lencode, &distcode); +} + +/* + * Process a dynamic codes block. + * + * Format notes: + * + * - A dynamic block starts with a description of the literal/length and + * distance codes for that block. New dynamic blocks allow the compressor to + * rapidly adapt to changing data with new codes optimized for that data. + * + * - The codes used by the deflate format are "canonical", which means that + * the actual bits of the codes are generated in an unambiguous way simply + * from the number of bits in each code. Therefore the code descriptions + * are simply a list of code lengths for each symbol. + * + * - The code lengths are stored in order for the symbols, so lengths are + * provided for each of the literal/length symbols, and for each of the + * distance symbols. + * + * - If a symbol is not used in the block, this is represented by a zero as + * as the code length. This does not mean a zero-length code, but rather + * that no code should be created for this symbol. There is no way in the + * deflate format to represent a zero-length code. + * + * - The maximum number of bits in a code is 15, so the possible lengths for + * any code are 1..15. + * + * - The fact that a length of zero is not permitted for a code has an + * interesting consequence. Normally if only one symbol is used for a given + * code, then in fact that code could be represented with zero bits. However + * in deflate, that code has to be at least one bit. So for example, if + * only a single distance base symbol appears in a block, then it will be + * represented by a single code of length one, in particular one 0 bit. This + * is an incomplete code, since if a 1 bit is received, it has no meaning, + * and should result in an error. So incomplete distance codes of one symbol + * should be permitted, and the receipt of invalid codes should be handled. + * + * - It is also possible to have a single literal/length code, but that code + * must be the end-of-block code, since every dynamic block has one. This + * is not the most efficient way to create an empty block (an empty fixed + * block is fewer bits), but it is allowed by the format. So incomplete + * literal/length codes of one symbol should also be permitted. + * + * - If there are only literal codes and no lengths, then there are no distance + * codes. This is represented by one distance code with zero bits. + * + * - The list of up to 286 length/literal lengths and up to 30 distance lengths + * are themselves compressed using Huffman codes and run-length encoding. In + * the list of code lengths, a 0 symbol means no code, a 1..15 symbol means + * that length, and the symbols 16, 17, and 18 are run-length instructions. + * Each of 16, 17, and 18 are follwed by extra bits to define the length of + * the run. 16 copies the last length 3 to 6 times. 17 represents 3 to 10 + * zero lengths, and 18 represents 11 to 138 zero lengths. Unused symbols + * are common, hence the special coding for zero lengths. + * + * - The symbols for 0..18 are Huffman coded, and so that code must be + * described first. This is simply a sequence of up to 19 three-bit values + * representing no code (0) or the code length for that symbol (1..7). + * + * - A dynamic block starts with three fixed-size counts from which is computed + * the number of literal/length code lengths, the number of distance code + * lengths, and the number of code length code lengths (ok, you come up with + * a better name!) in the code descriptions. For the literal/length and + * distance codes, lengths after those provided are considered zero, i.e. no + * code. The code length code lengths are received in a permuted order (see + * the order[] array below) to make a short code length code length list more + * likely. As it turns out, very short and very long codes are less likely + * to be seen in a dynamic code description, hence what may appear initially + * to be a peculiar ordering. + * + * - Given the number of literal/length code lengths (nlen) and distance code + * lengths (ndist), then they are treated as one long list of nlen + ndist + * code lengths. Therefore run-length coding can and often does cross the + * boundary between the two sets of lengths. + * + * - So to summarize, the code description at the start of a dynamic block is + * three counts for the number of code lengths for the literal/length codes, + * the distance codes, and the code length codes. This is followed by the + * code length code lengths, three bits each. This is used to construct the + * code length code which is used to read the remainder of the lengths. Then + * the literal/length code lengths and distance lengths are read as a single + * set of lengths using the code length codes. Codes are constructed from + * the resulting two sets of lengths, and then finally you can start + * decoding actual compressed data in the block. + * + * - For reference, a "typical" size for the code description in a dynamic + * block is around 80 bytes. + */ +local int dynamic(struct state *s) +{ + int nlen, ndist, ncode; /* number of lengths in descriptor */ + int index; /* index of lengths[] */ + int err; /* construct() return value */ + short lengths[MAXCODES]; /* descriptor code lengths */ + short lencnt[MAXBITS+1], lensym[MAXLCODES]; /* lencode memory */ + short distcnt[MAXBITS+1], distsym[MAXDCODES]; /* distcode memory */ + struct huffman lencode, distcode; /* length and distance codes */ + static const short order[19] = /* permutation of code length codes */ + {16, 17, 18, 0, 8, 7, 9, 6, 10, 5, 11, 4, 12, 3, 13, 2, 14, 1, 15}; + + /* construct lencode and distcode */ + lencode.count = lencnt; + lencode.symbol = lensym; + distcode.count = distcnt; + distcode.symbol = distsym; + + /* get number of lengths in each table, check lengths */ + nlen = bits(s, 5) + 257; + ndist = bits(s, 5) + 1; + ncode = bits(s, 4) + 4; + if (nlen > MAXLCODES || ndist > MAXDCODES) + return -3; /* bad counts */ + + /* read code length code lengths (really), missing lengths are zero */ + for (index = 0; index < ncode; index++) + lengths[order[index]] = bits(s, 3); + for (; index < 19; index++) + lengths[order[index]] = 0; + + /* build huffman table for code lengths codes (use lencode temporarily) */ + err = construct(&lencode, lengths, 19); + if (err != 0) /* require complete code set here */ + return -4; + + /* read length/literal and distance code length tables */ + index = 0; + while (index < nlen + ndist) { + int symbol; /* decoded value */ + int len; /* last length to repeat */ + + symbol = decode(s, &lencode); + if (symbol < 0) + return symbol; /* invalid symbol */ + if (symbol < 16) /* length in 0..15 */ + lengths[index++] = symbol; + else { /* repeat instruction */ + len = 0; /* assume repeating zeros */ + if (symbol == 16) { /* repeat last length 3..6 times */ + if (index == 0) + return -5; /* no last length! */ + len = lengths[index - 1]; /* last length */ + symbol = 3 + bits(s, 2); + } + else if (symbol == 17) /* repeat zero 3..10 times */ + symbol = 3 + bits(s, 3); + else /* == 18, repeat zero 11..138 times */ + symbol = 11 + bits(s, 7); + if (index + symbol > nlen + ndist) + return -6; /* too many lengths! */ + while (symbol--) /* repeat last or zero symbol times */ + lengths[index++] = len; + } + } + + /* check for end-of-block code -- there better be one! */ + if (lengths[256] == 0) + return -9; + + /* build huffman table for literal/length codes */ + err = construct(&lencode, lengths, nlen); + if (err && (err < 0 || nlen != lencode.count[0] + lencode.count[1])) + return -7; /* incomplete code ok only for single length 1 code */ + + /* build huffman table for distance codes */ + err = construct(&distcode, lengths + nlen, ndist); + if (err && (err < 0 || ndist != distcode.count[0] + distcode.count[1])) + return -8; /* incomplete code ok only for single length 1 code */ + + /* decode data until end-of-block code */ + return codes(s, &lencode, &distcode); +} + +/* + * Inflate source to dest. On return, destlen and sourcelen are updated to the + * size of the uncompressed data and the size of the deflate data respectively. + * On success, the return value of puff() is zero. If there is an error in the + * source data, i.e. it is not in the deflate format, then a negative value is + * returned. If there is not enough input available or there is not enough + * output space, then a positive error is returned. In that case, destlen and + * sourcelen are not updated to facilitate retrying from the beginning with the + * provision of more input data or more output space. In the case of invalid + * inflate data (a negative error), the dest and source pointers are updated to + * facilitate the debugging of deflators. + * + * puff() also has a mode to determine the size of the uncompressed output with + * no output written. For this dest must be (unsigned char *)0. In this case, + * the input value of *destlen is ignored, and on return *destlen is set to the + * size of the uncompressed output. + * + * The return codes are: + * + * 2: available inflate data did not terminate + * 1: output space exhausted before completing inflate + * 0: successful inflate + * -1: invalid block type (type == 3) + * -2: stored block length did not match one's complement + * -3: dynamic block code description: too many length or distance codes + * -4: dynamic block code description: code lengths codes incomplete + * -5: dynamic block code description: repeat lengths with no first length + * -6: dynamic block code description: repeat more than specified lengths + * -7: dynamic block code description: invalid literal/length code lengths + * -8: dynamic block code description: invalid distance code lengths + * -9: dynamic block code description: missing end-of-block code + * -10: invalid literal/length or distance code in fixed or dynamic block + * -11: distance is too far back in fixed or dynamic block + * + * Format notes: + * + * - Three bits are read for each block to determine the kind of block and + * whether or not it is the last block. Then the block is decoded and the + * process repeated if it was not the last block. + * + * - The leftover bits in the last byte of the deflate data after the last + * block (if it was a fixed or dynamic block) are undefined and have no + * expected values to check. + */ +int puff(unsigned char *dest, /* pointer to destination pointer */ + unsigned long *destlen, /* amount of output space */ + const unsigned char *source, /* pointer to source data pointer */ + unsigned long *sourcelen) /* amount of input available */ +{ + struct state s; /* input/output state */ + int last, type; /* block information */ + int err; /* return value */ + + /* initialize output state */ + s.out = dest; + s.outlen = *destlen; /* ignored if dest is NIL */ + s.outcnt = 0; + + /* initialize input state */ + s.in = source; + s.inlen = *sourcelen; + s.incnt = 0; + s.bitbuf = 0; + s.bitcnt = 0; + + /* return if bits() or decode() tries to read past available input */ + if (setjmp(s.env) != 0) /* if came back here via longjmp() */ + err = 2; /* then skip do-loop, return error */ + else { + /* process blocks until last block or error */ + do { + last = bits(&s, 1); /* one if last block */ + type = bits(&s, 2); /* block type 0..3 */ + err = type == 0 ? + stored(&s) : + (type == 1 ? + fixed(&s) : + (type == 2 ? + dynamic(&s) : + -1)); /* type == 3, invalid */ + if (err != 0) + break; /* return with error */ + } while (!last); + } + + /* update the lengths and return */ + if (err <= 0) { + *destlen = s.outcnt; + *sourcelen = s.incnt; + } + return err; +} ADDED compat/zlib/contrib/puff/puff.h Index: compat/zlib/contrib/puff/puff.h ================================================================== --- /dev/null +++ compat/zlib/contrib/puff/puff.h @@ -0,0 +1,35 @@ +/* puff.h + Copyright (C) 2002-2013 Mark Adler, all rights reserved + version 2.3, 21 Jan 2013 + + This software is provided 'as-is', without any express or implied + warranty. In no event will the author be held liable for any damages + arising from the use of this software. + + Permission is granted to anyone to use this software for any purpose, + including commercial applications, and to alter it and redistribute it + freely, subject to the following restrictions: + + 1. The origin of this software must not be misrepresented; you must not + claim that you wrote the original software. If you use this software + in a product, an acknowledgment in the product documentation would be + appreciated but is not required. + 2. Altered source versions must be plainly marked as such, and must not be + misrepresented as being the original software. + 3. This notice may not be removed or altered from any source distribution. + + Mark Adler madler@alumni.caltech.edu + */ + + +/* + * See puff.c for purpose and usage. + */ +#ifndef NIL +# define NIL ((unsigned char *)0) /* for no output option */ +#endif + +int puff(unsigned char *dest, /* pointer to destination pointer */ + unsigned long *destlen, /* amount of output space */ + const unsigned char *source, /* pointer to source data pointer */ + unsigned long *sourcelen); /* amount of input available */ ADDED compat/zlib/contrib/puff/pufftest.c Index: compat/zlib/contrib/puff/pufftest.c ================================================================== --- /dev/null +++ compat/zlib/contrib/puff/pufftest.c @@ -0,0 +1,165 @@ +/* + * pufftest.c + * Copyright (C) 2002-2013 Mark Adler + * For conditions of distribution and use, see copyright notice in puff.h + * version 2.3, 21 Jan 2013 + */ + +/* Example of how to use puff(). + + Usage: puff [-w] [-f] [-nnn] file + ... | puff [-w] [-f] [-nnn] + + where file is the input file with deflate data, nnn is the number of bytes + of input to skip before inflating (e.g. to skip a zlib or gzip header), and + -w is used to write the decompressed data to stdout. -f is for coverage + testing, and causes pufftest to fail with not enough output space (-f does + a write like -w, so -w is not required). */ + +#include +#include +#include "puff.h" + +#if defined(MSDOS) || defined(OS2) || defined(WIN32) || defined(__CYGWIN__) +# include +# include +# define SET_BINARY_MODE(file) setmode(fileno(file), O_BINARY) +#else +# define SET_BINARY_MODE(file) +#endif + +#define local static + +/* Return size times approximately the cube root of 2, keeping the result as 1, + 3, or 5 times a power of 2 -- the result is always > size, until the result + is the maximum value of an unsigned long, where it remains. This is useful + to keep reallocations less than ~33% over the actual data. */ +local size_t bythirds(size_t size) +{ + int n; + size_t m; + + m = size; + for (n = 0; m; n++) + m >>= 1; + if (n < 3) + return size + 1; + n -= 3; + m = size >> n; + m += m == 6 ? 2 : 1; + m <<= n; + return m > size ? m : (size_t)(-1); +} + +/* Read the input file *name, or stdin if name is NULL, into allocated memory. + Reallocate to larger buffers until the entire file is read in. Return a + pointer to the allocated data, or NULL if there was a memory allocation + failure. *len is the number of bytes of data read from the input file (even + if load() returns NULL). If the input file was empty or could not be opened + or read, *len is zero. */ +local void *load(const char *name, size_t *len) +{ + size_t size; + void *buf, *swap; + FILE *in; + + *len = 0; + buf = malloc(size = 4096); + if (buf == NULL) + return NULL; + in = name == NULL ? stdin : fopen(name, "rb"); + if (in != NULL) { + for (;;) { + *len += fread((char *)buf + *len, 1, size - *len, in); + if (*len < size) break; + size = bythirds(size); + if (size == *len || (swap = realloc(buf, size)) == NULL) { + free(buf); + buf = NULL; + break; + } + buf = swap; + } + fclose(in); + } + return buf; +} + +int main(int argc, char **argv) +{ + int ret, put = 0, fail = 0; + unsigned skip = 0; + char *arg, *name = NULL; + unsigned char *source = NULL, *dest; + size_t len = 0; + unsigned long sourcelen, destlen; + + /* process arguments */ + while (arg = *++argv, --argc) + if (arg[0] == '-') { + if (arg[1] == 'w' && arg[2] == 0) + put = 1; + else if (arg[1] == 'f' && arg[2] == 0) + fail = 1, put = 1; + else if (arg[1] >= '0' && arg[1] <= '9') + skip = (unsigned)atoi(arg + 1); + else { + fprintf(stderr, "invalid option %s\n", arg); + return 3; + } + } + else if (name != NULL) { + fprintf(stderr, "only one file name allowed\n"); + return 3; + } + else + name = arg; + source = load(name, &len); + if (source == NULL) { + fprintf(stderr, "memory allocation failure\n"); + return 4; + } + if (len == 0) { + fprintf(stderr, "could not read %s, or it was empty\n", + name == NULL ? "" : name); + free(source); + return 3; + } + if (skip >= len) { + fprintf(stderr, "skip request of %d leaves no input\n", skip); + free(source); + return 3; + } + + /* test inflate data with offset skip */ + len -= skip; + sourcelen = (unsigned long)len; + ret = puff(NIL, &destlen, source + skip, &sourcelen); + if (ret) + fprintf(stderr, "puff() failed with return code %d\n", ret); + else { + fprintf(stderr, "puff() succeeded uncompressing %lu bytes\n", destlen); + if (sourcelen < len) fprintf(stderr, "%lu compressed bytes unused\n", + len - sourcelen); + } + + /* if requested, inflate again and write decompressd data to stdout */ + if (put && ret == 0) { + if (fail) + destlen >>= 1; + dest = malloc(destlen); + if (dest == NULL) { + fprintf(stderr, "memory allocation failure\n"); + free(source); + return 4; + } + puff(dest, &destlen, source + skip, &sourcelen); + SET_BINARY_MODE(stdout); + fwrite(dest, 1, destlen, stdout); + free(dest); + } + + /* clean up */ + free(source); + return ret; +} ADDED compat/zlib/contrib/puff/zeros.raw Index: compat/zlib/contrib/puff/zeros.raw ================================================================== --- /dev/null +++ compat/zlib/contrib/puff/zeros.raw cannot compute difference between binary files ADDED compat/zlib/contrib/testzlib/testzlib.c Index: compat/zlib/contrib/testzlib/testzlib.c ================================================================== --- /dev/null +++ compat/zlib/contrib/testzlib/testzlib.c @@ -0,0 +1,275 @@ +#include +#include +#include + +#include "zlib.h" + + +void MyDoMinus64(LARGE_INTEGER *R,LARGE_INTEGER A,LARGE_INTEGER B) +{ + R->HighPart = A.HighPart - B.HighPart; + if (A.LowPart >= B.LowPart) + R->LowPart = A.LowPart - B.LowPart; + else + { + R->LowPart = A.LowPart - B.LowPart; + R->HighPart --; + } +} + +#ifdef _M_X64 +// see http://msdn2.microsoft.com/library/twchhe95(en-us,vs.80).aspx for __rdtsc +unsigned __int64 __rdtsc(void); +void BeginCountRdtsc(LARGE_INTEGER * pbeginTime64) +{ + // printf("rdtsc = %I64x\n",__rdtsc()); + pbeginTime64->QuadPart=__rdtsc(); +} + +LARGE_INTEGER GetResRdtsc(LARGE_INTEGER beginTime64,BOOL fComputeTimeQueryPerf) +{ + LARGE_INTEGER LIres; + unsigned _int64 res=__rdtsc()-((unsigned _int64)(beginTime64.QuadPart)); + LIres.QuadPart=res; + // printf("rdtsc = %I64x\n",__rdtsc()); + return LIres; +} +#else +#ifdef _M_IX86 +void myGetRDTSC32(LARGE_INTEGER * pbeginTime64) +{ + DWORD dwEdx,dwEax; + _asm + { + rdtsc + mov dwEax,eax + mov dwEdx,edx + } + pbeginTime64->LowPart=dwEax; + pbeginTime64->HighPart=dwEdx; +} + +void BeginCountRdtsc(LARGE_INTEGER * pbeginTime64) +{ + myGetRDTSC32(pbeginTime64); +} + +LARGE_INTEGER GetResRdtsc(LARGE_INTEGER beginTime64,BOOL fComputeTimeQueryPerf) +{ + LARGE_INTEGER LIres,endTime64; + myGetRDTSC32(&endTime64); + + LIres.LowPart=LIres.HighPart=0; + MyDoMinus64(&LIres,endTime64,beginTime64); + return LIres; +} +#else +void myGetRDTSC32(LARGE_INTEGER * pbeginTime64) +{ +} + +void BeginCountRdtsc(LARGE_INTEGER * pbeginTime64) +{ +} + +LARGE_INTEGER GetResRdtsc(LARGE_INTEGER beginTime64,BOOL fComputeTimeQueryPerf) +{ + LARGE_INTEGER lr; + lr.QuadPart=0; + return lr; +} +#endif +#endif + +void BeginCountPerfCounter(LARGE_INTEGER * pbeginTime64,BOOL fComputeTimeQueryPerf) +{ + if ((!fComputeTimeQueryPerf) || (!QueryPerformanceCounter(pbeginTime64))) + { + pbeginTime64->LowPart = GetTickCount(); + pbeginTime64->HighPart = 0; + } +} + +DWORD GetMsecSincePerfCounter(LARGE_INTEGER beginTime64,BOOL fComputeTimeQueryPerf) +{ + LARGE_INTEGER endTime64,ticksPerSecond,ticks; + DWORDLONG ticksShifted,tickSecShifted; + DWORD dwLog=16+0; + DWORD dwRet; + if ((!fComputeTimeQueryPerf) || (!QueryPerformanceCounter(&endTime64))) + dwRet = (GetTickCount() - beginTime64.LowPart)*1; + else + { + MyDoMinus64(&ticks,endTime64,beginTime64); + QueryPerformanceFrequency(&ticksPerSecond); + + + { + ticksShifted = Int64ShrlMod32(*(DWORDLONG*)&ticks,dwLog); + tickSecShifted = Int64ShrlMod32(*(DWORDLONG*)&ticksPerSecond,dwLog); + + } + + dwRet = (DWORD)((((DWORD)ticksShifted)*1000)/(DWORD)(tickSecShifted)); + dwRet *=1; + } + return dwRet; +} + +int ReadFileMemory(const char* filename,long* plFileSize,unsigned char** pFilePtr) +{ + FILE* stream; + unsigned char* ptr; + int retVal=1; + stream=fopen(filename, "rb"); + if (stream==NULL) + return 0; + + fseek(stream,0,SEEK_END); + + *plFileSize=ftell(stream); + fseek(stream,0,SEEK_SET); + ptr=malloc((*plFileSize)+1); + if (ptr==NULL) + retVal=0; + else + { + if (fread(ptr, 1, *plFileSize,stream) != (*plFileSize)) + retVal=0; + } + fclose(stream); + *pFilePtr=ptr; + return retVal; +} + +int main(int argc, char *argv[]) +{ + int BlockSizeCompress=0x8000; + int BlockSizeUncompress=0x8000; + int cprLevel=Z_DEFAULT_COMPRESSION ; + long lFileSize; + unsigned char* FilePtr; + long lBufferSizeCpr; + long lBufferSizeUncpr; + long lCompressedSize=0; + unsigned char* CprPtr; + unsigned char* UncprPtr; + long lSizeCpr,lSizeUncpr; + DWORD dwGetTick,dwMsecQP; + LARGE_INTEGER li_qp,li_rdtsc,dwResRdtsc; + + if (argc<=1) + { + printf("run TestZlib [BlockSizeCompress] [BlockSizeUncompress] [compres. level]\n"); + return 0; + } + + if (ReadFileMemory(argv[1],&lFileSize,&FilePtr)==0) + { + printf("error reading %s\n",argv[1]); + return 1; + } + else printf("file %s read, %u bytes\n",argv[1],lFileSize); + + if (argc>=3) + BlockSizeCompress=atol(argv[2]); + + if (argc>=4) + BlockSizeUncompress=atol(argv[3]); + + if (argc>=5) + cprLevel=(int)atol(argv[4]); + + lBufferSizeCpr = lFileSize + (lFileSize/0x10) + 0x200; + lBufferSizeUncpr = lBufferSizeCpr; + + CprPtr=(unsigned char*)malloc(lBufferSizeCpr + BlockSizeCompress); + + BeginCountPerfCounter(&li_qp,TRUE); + dwGetTick=GetTickCount(); + BeginCountRdtsc(&li_rdtsc); + { + z_stream zcpr; + int ret=Z_OK; + long lOrigToDo = lFileSize; + long lOrigDone = 0; + int step=0; + memset(&zcpr,0,sizeof(z_stream)); + deflateInit(&zcpr,cprLevel); + + zcpr.next_in = FilePtr; + zcpr.next_out = CprPtr; + + + do + { + long all_read_before = zcpr.total_in; + zcpr.avail_in = min(lOrigToDo,BlockSizeCompress); + zcpr.avail_out = BlockSizeCompress; + ret=deflate(&zcpr,(zcpr.avail_in==lOrigToDo) ? Z_FINISH : Z_SYNC_FLUSH); + lOrigDone += (zcpr.total_in-all_read_before); + lOrigToDo -= (zcpr.total_in-all_read_before); + step++; + } while (ret==Z_OK); + + lSizeCpr=zcpr.total_out; + deflateEnd(&zcpr); + dwGetTick=GetTickCount()-dwGetTick; + dwMsecQP=GetMsecSincePerfCounter(li_qp,TRUE); + dwResRdtsc=GetResRdtsc(li_rdtsc,TRUE); + printf("total compress size = %u, in %u step\n",lSizeCpr,step); + printf("time = %u msec = %f sec\n",dwGetTick,dwGetTick/(double)1000.); + printf("defcpr time QP = %u msec = %f sec\n",dwMsecQP,dwMsecQP/(double)1000.); + printf("defcpr result rdtsc = %I64x\n\n",dwResRdtsc.QuadPart); + } + + CprPtr=(unsigned char*)realloc(CprPtr,lSizeCpr); + UncprPtr=(unsigned char*)malloc(lBufferSizeUncpr + BlockSizeUncompress); + + BeginCountPerfCounter(&li_qp,TRUE); + dwGetTick=GetTickCount(); + BeginCountRdtsc(&li_rdtsc); + { + z_stream zcpr; + int ret=Z_OK; + long lOrigToDo = lSizeCpr; + long lOrigDone = 0; + int step=0; + memset(&zcpr,0,sizeof(z_stream)); + inflateInit(&zcpr); + + zcpr.next_in = CprPtr; + zcpr.next_out = UncprPtr; + + + do + { + long all_read_before = zcpr.total_in; + zcpr.avail_in = min(lOrigToDo,BlockSizeUncompress); + zcpr.avail_out = BlockSizeUncompress; + ret=inflate(&zcpr,Z_SYNC_FLUSH); + lOrigDone += (zcpr.total_in-all_read_before); + lOrigToDo -= (zcpr.total_in-all_read_before); + step++; + } while (ret==Z_OK); + + lSizeUncpr=zcpr.total_out; + inflateEnd(&zcpr); + dwGetTick=GetTickCount()-dwGetTick; + dwMsecQP=GetMsecSincePerfCounter(li_qp,TRUE); + dwResRdtsc=GetResRdtsc(li_rdtsc,TRUE); + printf("total uncompress size = %u, in %u step\n",lSizeUncpr,step); + printf("time = %u msec = %f sec\n",dwGetTick,dwGetTick/(double)1000.); + printf("uncpr time QP = %u msec = %f sec\n",dwMsecQP,dwMsecQP/(double)1000.); + printf("uncpr result rdtsc = %I64x\n\n",dwResRdtsc.QuadPart); + } + + if (lSizeUncpr==lFileSize) + { + if (memcmp(FilePtr,UncprPtr,lFileSize)==0) + printf("compare ok\n"); + + } + + return 0; +} ADDED compat/zlib/contrib/testzlib/testzlib.txt Index: compat/zlib/contrib/testzlib/testzlib.txt ================================================================== --- /dev/null +++ compat/zlib/contrib/testzlib/testzlib.txt @@ -0,0 +1,10 @@ +To build testzLib with Visual Studio 2005: + +copy to a directory file from : +- root of zLib tree +- contrib/testzlib +- contrib/masmx86 +- contrib/masmx64 +- contrib/vstudio/vc7 + +and open testzlib8.sln ADDED compat/zlib/contrib/untgz/Makefile Index: compat/zlib/contrib/untgz/Makefile ================================================================== --- /dev/null +++ compat/zlib/contrib/untgz/Makefile @@ -0,0 +1,14 @@ +CC=cc +CFLAGS=-g + +untgz: untgz.o ../../libz.a + $(CC) $(CFLAGS) -o untgz untgz.o -L../.. -lz + +untgz.o: untgz.c ../../zlib.h + $(CC) $(CFLAGS) -c -I../.. untgz.c + +../../libz.a: + cd ../..; ./configure; make + +clean: + rm -f untgz untgz.o *~ ADDED compat/zlib/contrib/untgz/Makefile.msc Index: compat/zlib/contrib/untgz/Makefile.msc ================================================================== --- /dev/null +++ compat/zlib/contrib/untgz/Makefile.msc @@ -0,0 +1,17 @@ +CC=cl +CFLAGS=-MD + +untgz.exe: untgz.obj ..\..\zlib.lib + $(CC) $(CFLAGS) untgz.obj ..\..\zlib.lib + +untgz.obj: untgz.c ..\..\zlib.h + $(CC) $(CFLAGS) -c -I..\.. untgz.c + +..\..\zlib.lib: + cd ..\.. + $(MAKE) -f win32\makefile.msc + cd contrib\untgz + +clean: + -del untgz.obj + -del untgz.exe ADDED compat/zlib/contrib/untgz/untgz.c Index: compat/zlib/contrib/untgz/untgz.c ================================================================== --- /dev/null +++ compat/zlib/contrib/untgz/untgz.c @@ -0,0 +1,674 @@ +/* + * untgz.c -- Display contents and extract files from a gzip'd TAR file + * + * written by Pedro A. Aranda Gutierrez + * adaptation to Unix by Jean-loup Gailly + * various fixes by Cosmin Truta + */ + +#include +#include +#include +#include +#include + +#include "zlib.h" + +#ifdef unix +# include +#else +# include +# include +#endif + +#ifdef WIN32 +#include +# ifndef F_OK +# define F_OK 0 +# endif +# define mkdir(dirname,mode) _mkdir(dirname) +# ifdef _MSC_VER +# define access(path,mode) _access(path,mode) +# define chmod(path,mode) _chmod(path,mode) +# define strdup(str) _strdup(str) +# endif +#else +# include +#endif + + +/* values used in typeflag field */ + +#define REGTYPE '0' /* regular file */ +#define AREGTYPE '\0' /* regular file */ +#define LNKTYPE '1' /* link */ +#define SYMTYPE '2' /* reserved */ +#define CHRTYPE '3' /* character special */ +#define BLKTYPE '4' /* block special */ +#define DIRTYPE '5' /* directory */ +#define FIFOTYPE '6' /* FIFO special */ +#define CONTTYPE '7' /* reserved */ + +/* GNU tar extensions */ + +#define GNUTYPE_DUMPDIR 'D' /* file names from dumped directory */ +#define GNUTYPE_LONGLINK 'K' /* long link name */ +#define GNUTYPE_LONGNAME 'L' /* long file name */ +#define GNUTYPE_MULTIVOL 'M' /* continuation of file from another volume */ +#define GNUTYPE_NAMES 'N' /* file name that does not fit into main hdr */ +#define GNUTYPE_SPARSE 'S' /* sparse file */ +#define GNUTYPE_VOLHDR 'V' /* tape/volume header */ + + +/* tar header */ + +#define BLOCKSIZE 512 +#define SHORTNAMESIZE 100 + +struct tar_header +{ /* byte offset */ + char name[100]; /* 0 */ + char mode[8]; /* 100 */ + char uid[8]; /* 108 */ + char gid[8]; /* 116 */ + char size[12]; /* 124 */ + char mtime[12]; /* 136 */ + char chksum[8]; /* 148 */ + char typeflag; /* 156 */ + char linkname[100]; /* 157 */ + char magic[6]; /* 257 */ + char version[2]; /* 263 */ + char uname[32]; /* 265 */ + char gname[32]; /* 297 */ + char devmajor[8]; /* 329 */ + char devminor[8]; /* 337 */ + char prefix[155]; /* 345 */ + /* 500 */ +}; + +union tar_buffer +{ + char buffer[BLOCKSIZE]; + struct tar_header header; +}; + +struct attr_item +{ + struct attr_item *next; + char *fname; + int mode; + time_t time; +}; + +enum { TGZ_EXTRACT, TGZ_LIST, TGZ_INVALID }; + +char *TGZfname OF((const char *)); +void TGZnotfound OF((const char *)); + +int getoct OF((char *, int)); +char *strtime OF((time_t *)); +int setfiletime OF((char *, time_t)); +void push_attr OF((struct attr_item **, char *, int, time_t)); +void restore_attr OF((struct attr_item **)); + +int ExprMatch OF((char *, char *)); + +int makedir OF((char *)); +int matchname OF((int, int, char **, char *)); + +void error OF((const char *)); +int tar OF((gzFile, int, int, int, char **)); + +void help OF((int)); +int main OF((int, char **)); + +char *prog; + +const char *TGZsuffix[] = { "\0", ".tar", ".tar.gz", ".taz", ".tgz", NULL }; + +/* return the file name of the TGZ archive */ +/* or NULL if it does not exist */ + +char *TGZfname (const char *arcname) +{ + static char buffer[1024]; + int origlen,i; + + strcpy(buffer,arcname); + origlen = strlen(buffer); + + for (i=0; TGZsuffix[i]; i++) + { + strcpy(buffer+origlen,TGZsuffix[i]); + if (access(buffer,F_OK) == 0) + return buffer; + } + return NULL; +} + + +/* error message for the filename */ + +void TGZnotfound (const char *arcname) +{ + int i; + + fprintf(stderr,"%s: Couldn't find ",prog); + for (i=0;TGZsuffix[i];i++) + fprintf(stderr,(TGZsuffix[i+1]) ? "%s%s, " : "or %s%s\n", + arcname, + TGZsuffix[i]); + exit(1); +} + + +/* convert octal digits to int */ +/* on error return -1 */ + +int getoct (char *p,int width) +{ + int result = 0; + char c; + + while (width--) + { + c = *p++; + if (c == 0) + break; + if (c == ' ') + continue; + if (c < '0' || c > '7') + return -1; + result = result * 8 + (c - '0'); + } + return result; +} + + +/* convert time_t to string */ +/* use the "YYYY/MM/DD hh:mm:ss" format */ + +char *strtime (time_t *t) +{ + struct tm *local; + static char result[32]; + + local = localtime(t); + sprintf(result,"%4d/%02d/%02d %02d:%02d:%02d", + local->tm_year+1900, local->tm_mon+1, local->tm_mday, + local->tm_hour, local->tm_min, local->tm_sec); + return result; +} + + +/* set file time */ + +int setfiletime (char *fname,time_t ftime) +{ +#ifdef WIN32 + static int isWinNT = -1; + SYSTEMTIME st; + FILETIME locft, modft; + struct tm *loctm; + HANDLE hFile; + int result; + + loctm = localtime(&ftime); + if (loctm == NULL) + return -1; + + st.wYear = (WORD)loctm->tm_year + 1900; + st.wMonth = (WORD)loctm->tm_mon + 1; + st.wDayOfWeek = (WORD)loctm->tm_wday; + st.wDay = (WORD)loctm->tm_mday; + st.wHour = (WORD)loctm->tm_hour; + st.wMinute = (WORD)loctm->tm_min; + st.wSecond = (WORD)loctm->tm_sec; + st.wMilliseconds = 0; + if (!SystemTimeToFileTime(&st, &locft) || + !LocalFileTimeToFileTime(&locft, &modft)) + return -1; + + if (isWinNT < 0) + isWinNT = (GetVersion() < 0x80000000) ? 1 : 0; + hFile = CreateFile(fname, GENERIC_WRITE, 0, NULL, OPEN_EXISTING, + (isWinNT ? FILE_FLAG_BACKUP_SEMANTICS : 0), + NULL); + if (hFile == INVALID_HANDLE_VALUE) + return -1; + result = SetFileTime(hFile, NULL, NULL, &modft) ? 0 : -1; + CloseHandle(hFile); + return result; +#else + struct utimbuf settime; + + settime.actime = settime.modtime = ftime; + return utime(fname,&settime); +#endif +} + + +/* push file attributes */ + +void push_attr(struct attr_item **list,char *fname,int mode,time_t time) +{ + struct attr_item *item; + + item = (struct attr_item *)malloc(sizeof(struct attr_item)); + if (item == NULL) + error("Out of memory"); + item->fname = strdup(fname); + item->mode = mode; + item->time = time; + item->next = *list; + *list = item; +} + + +/* restore file attributes */ + +void restore_attr(struct attr_item **list) +{ + struct attr_item *item, *prev; + + for (item = *list; item != NULL; ) + { + setfiletime(item->fname,item->time); + chmod(item->fname,item->mode); + prev = item; + item = item->next; + free(prev); + } + *list = NULL; +} + + +/* match regular expression */ + +#define ISSPECIAL(c) (((c) == '*') || ((c) == '/')) + +int ExprMatch (char *string,char *expr) +{ + while (1) + { + if (ISSPECIAL(*expr)) + { + if (*expr == '/') + { + if (*string != '\\' && *string != '/') + return 0; + string ++; expr++; + } + else if (*expr == '*') + { + if (*expr ++ == 0) + return 1; + while (*++string != *expr) + if (*string == 0) + return 0; + } + } + else + { + if (*string != *expr) + return 0; + if (*expr++ == 0) + return 1; + string++; + } + } +} + + +/* recursive mkdir */ +/* abort on ENOENT; ignore other errors like "directory already exists" */ +/* return 1 if OK */ +/* 0 on error */ + +int makedir (char *newdir) +{ + char *buffer = strdup(newdir); + char *p; + int len = strlen(buffer); + + if (len <= 0) { + free(buffer); + return 0; + } + if (buffer[len-1] == '/') { + buffer[len-1] = '\0'; + } + if (mkdir(buffer, 0755) == 0) + { + free(buffer); + return 1; + } + + p = buffer+1; + while (1) + { + char hold; + + while(*p && *p != '\\' && *p != '/') + p++; + hold = *p; + *p = 0; + if ((mkdir(buffer, 0755) == -1) && (errno == ENOENT)) + { + fprintf(stderr,"%s: Couldn't create directory %s\n",prog,buffer); + free(buffer); + return 0; + } + if (hold == 0) + break; + *p++ = hold; + } + free(buffer); + return 1; +} + + +int matchname (int arg,int argc,char **argv,char *fname) +{ + if (arg == argc) /* no arguments given (untgz tgzarchive) */ + return 1; + + while (arg < argc) + if (ExprMatch(fname,argv[arg++])) + return 1; + + return 0; /* ignore this for the moment being */ +} + + +/* tar file list or extract */ + +int tar (gzFile in,int action,int arg,int argc,char **argv) +{ + union tar_buffer buffer; + int len; + int err; + int getheader = 1; + int remaining = 0; + FILE *outfile = NULL; + char fname[BLOCKSIZE]; + int tarmode; + time_t tartime; + struct attr_item *attributes = NULL; + + if (action == TGZ_LIST) + printf(" date time size file\n" + " ---------- -------- --------- -------------------------------------\n"); + while (1) + { + len = gzread(in, &buffer, BLOCKSIZE); + if (len < 0) + error(gzerror(in, &err)); + /* + * Always expect complete blocks to process + * the tar information. + */ + if (len != BLOCKSIZE) + { + action = TGZ_INVALID; /* force error exit */ + remaining = 0; /* force I/O cleanup */ + } + + /* + * If we have to get a tar header + */ + if (getheader >= 1) + { + /* + * if we met the end of the tar + * or the end-of-tar block, + * we are done + */ + if (len == 0 || buffer.header.name[0] == 0) + break; + + tarmode = getoct(buffer.header.mode,8); + tartime = (time_t)getoct(buffer.header.mtime,12); + if (tarmode == -1 || tartime == (time_t)-1) + { + buffer.header.name[0] = 0; + action = TGZ_INVALID; + } + + if (getheader == 1) + { + strncpy(fname,buffer.header.name,SHORTNAMESIZE); + if (fname[SHORTNAMESIZE-1] != 0) + fname[SHORTNAMESIZE] = 0; + } + else + { + /* + * The file name is longer than SHORTNAMESIZE + */ + if (strncmp(fname,buffer.header.name,SHORTNAMESIZE-1) != 0) + error("bad long name"); + getheader = 1; + } + + /* + * Act according to the type flag + */ + switch (buffer.header.typeflag) + { + case DIRTYPE: + if (action == TGZ_LIST) + printf(" %s %s\n",strtime(&tartime),fname); + if (action == TGZ_EXTRACT) + { + makedir(fname); + push_attr(&attributes,fname,tarmode,tartime); + } + break; + case REGTYPE: + case AREGTYPE: + remaining = getoct(buffer.header.size,12); + if (remaining == -1) + { + action = TGZ_INVALID; + break; + } + if (action == TGZ_LIST) + printf(" %s %9d %s\n",strtime(&tartime),remaining,fname); + else if (action == TGZ_EXTRACT) + { + if (matchname(arg,argc,argv,fname)) + { + outfile = fopen(fname,"wb"); + if (outfile == NULL) { + /* try creating directory */ + char *p = strrchr(fname, '/'); + if (p != NULL) { + *p = '\0'; + makedir(fname); + *p = '/'; + outfile = fopen(fname,"wb"); + } + } + if (outfile != NULL) + printf("Extracting %s\n",fname); + else + fprintf(stderr, "%s: Couldn't create %s",prog,fname); + } + else + outfile = NULL; + } + getheader = 0; + break; + case GNUTYPE_LONGLINK: + case GNUTYPE_LONGNAME: + remaining = getoct(buffer.header.size,12); + if (remaining < 0 || remaining >= BLOCKSIZE) + { + action = TGZ_INVALID; + break; + } + len = gzread(in, fname, BLOCKSIZE); + if (len < 0) + error(gzerror(in, &err)); + if (fname[BLOCKSIZE-1] != 0 || (int)strlen(fname) > remaining) + { + action = TGZ_INVALID; + break; + } + getheader = 2; + break; + default: + if (action == TGZ_LIST) + printf(" %s <---> %s\n",strtime(&tartime),fname); + break; + } + } + else + { + unsigned int bytes = (remaining > BLOCKSIZE) ? BLOCKSIZE : remaining; + + if (outfile != NULL) + { + if (fwrite(&buffer,sizeof(char),bytes,outfile) != bytes) + { + fprintf(stderr, + "%s: Error writing %s -- skipping\n",prog,fname); + fclose(outfile); + outfile = NULL; + remove(fname); + } + } + remaining -= bytes; + } + + if (remaining == 0) + { + getheader = 1; + if (outfile != NULL) + { + fclose(outfile); + outfile = NULL; + if (action != TGZ_INVALID) + push_attr(&attributes,fname,tarmode,tartime); + } + } + + /* + * Abandon if errors are found + */ + if (action == TGZ_INVALID) + { + error("broken archive"); + break; + } + } + + /* + * Restore file modes and time stamps + */ + restore_attr(&attributes); + + if (gzclose(in) != Z_OK) + error("failed gzclose"); + + return 0; +} + + +/* ============================================================ */ + +void help(int exitval) +{ + printf("untgz version 0.2.1\n" + " using zlib version %s\n\n", + zlibVersion()); + printf("Usage: untgz file.tgz extract all files\n" + " untgz file.tgz fname ... extract selected files\n" + " untgz -l file.tgz list archive contents\n" + " untgz -h display this help\n"); + exit(exitval); +} + +void error(const char *msg) +{ + fprintf(stderr, "%s: %s\n", prog, msg); + exit(1); +} + + +/* ============================================================ */ + +#if defined(WIN32) && defined(__GNUC__) +int _CRT_glob = 0; /* disable argument globbing in MinGW */ +#endif + +int main(int argc,char **argv) +{ + int action = TGZ_EXTRACT; + int arg = 1; + char *TGZfile; + gzFile *f; + + prog = strrchr(argv[0],'\\'); + if (prog == NULL) + { + prog = strrchr(argv[0],'/'); + if (prog == NULL) + { + prog = strrchr(argv[0],':'); + if (prog == NULL) + prog = argv[0]; + else + prog++; + } + else + prog++; + } + else + prog++; + + if (argc == 1) + help(0); + + if (strcmp(argv[arg],"-l") == 0) + { + action = TGZ_LIST; + if (argc == ++arg) + help(0); + } + else if (strcmp(argv[arg],"-h") == 0) + { + help(0); + } + + if ((TGZfile = TGZfname(argv[arg])) == NULL) + TGZnotfound(argv[arg]); + + ++arg; + if ((action == TGZ_LIST) && (arg != argc)) + help(1); + +/* + * Process the TGZ file + */ + switch(action) + { + case TGZ_LIST: + case TGZ_EXTRACT: + f = gzopen(TGZfile,"rb"); + if (f == NULL) + { + fprintf(stderr,"%s: Couldn't gzopen %s\n",prog,TGZfile); + return 1; + } + exit(tar(f, action, arg, argc, argv)); + break; + + default: + error("Unknown option"); + exit(1); + } + + return 0; +} ADDED compat/zlib/contrib/vstudio/readme.txt Index: compat/zlib/contrib/vstudio/readme.txt ================================================================== --- /dev/null +++ compat/zlib/contrib/vstudio/readme.txt @@ -0,0 +1,78 @@ +Building instructions for the DLL versions of Zlib 1.2.11 +======================================================== + +This directory contains projects that build zlib and minizip using +Microsoft Visual C++ 9.0/10.0. + +You don't need to build these projects yourself. You can download the +binaries from: + http://www.winimage.com/zLibDll + +More information can be found at this site. + + + + + +Build instructions for Visual Studio 2008 (32 bits or 64 bits) +-------------------------------------------------------------- +- Decompress current zlib, including all contrib/* files +- Compile assembly code (with Visual Studio Command Prompt) by running: + bld_ml64.bat (in contrib\masmx64) + bld_ml32.bat (in contrib\masmx86) +- Open contrib\vstudio\vc9\zlibvc.sln with Microsoft Visual C++ 2008 +- Or run: vcbuild /rebuild contrib\vstudio\vc9\zlibvc.sln "Release|Win32" + +Build instructions for Visual Studio 2010 (32 bits or 64 bits) +-------------------------------------------------------------- +- Decompress current zlib, including all contrib/* files +- Open contrib\vstudio\vc10\zlibvc.sln with Microsoft Visual C++ 2010 + +Build instructions for Visual Studio 2012 (32 bits or 64 bits) +-------------------------------------------------------------- +- Decompress current zlib, including all contrib/* files +- Open contrib\vstudio\vc11\zlibvc.sln with Microsoft Visual C++ 2012 + +Build instructions for Visual Studio 2013 (32 bits or 64 bits) +-------------------------------------------------------------- +- Decompress current zlib, including all contrib/* files +- Open contrib\vstudio\vc12\zlibvc.sln with Microsoft Visual C++ 2013 + +Build instructions for Visual Studio 2015 (32 bits or 64 bits) +-------------------------------------------------------------- +- Decompress current zlib, including all contrib/* files +- Open contrib\vstudio\vc14\zlibvc.sln with Microsoft Visual C++ 2015 + + +Important +--------- +- To use zlibwapi.dll in your application, you must define the + macro ZLIB_WINAPI when compiling your application's source files. + + +Additional notes +---------------- +- This DLL, named zlibwapi.dll, is compatible to the old zlib.dll built + by Gilles Vollant from the zlib 1.1.x sources, and distributed at + http://www.winimage.com/zLibDll + It uses the WINAPI calling convention for the exported functions, and + includes the minizip functionality. If your application needs that + particular build of zlib.dll, you can rename zlibwapi.dll to zlib.dll. + +- The new DLL was renamed because there exist several incompatible + versions of zlib.dll on the Internet. + +- There is also an official DLL build of zlib, named zlib1.dll. This one + is exporting the functions using the CDECL convention. See the file + win32\DLL_FAQ.txt found in this zlib distribution. + +- There used to be a ZLIB_DLL macro in zlib 1.1.x, but now this symbol + has a slightly different effect. To avoid compatibility problems, do + not define it here. + + +Gilles Vollant +info@winimage.com + +Visual Studio 2013 and 2015 Projects from Sean Hunt +seandhunt_7@yahoo.com ADDED compat/zlib/contrib/vstudio/vc10/miniunz.vcxproj Index: compat/zlib/contrib/vstudio/vc10/miniunz.vcxproj ================================================================== --- /dev/null +++ compat/zlib/contrib/vstudio/vc10/miniunz.vcxproj @@ -0,0 +1,310 @@ + + + + + Debug + Itanium + + + Debug + Win32 + + + Debug + x64 + + + Release + Itanium + + + Release + Win32 + + + Release + x64 + + + + {C52F9E7B-498A-42BE-8DB4-85A15694382A} + Win32Proj + + + + Application + MultiByte + + + Application + MultiByte + + + Application + MultiByte + + + Application + MultiByte + + + Application + MultiByte + + + Application + MultiByte + + + + + + + + + + + + + + + + + + + + + + + + + <_ProjectFileVersion>10.0.30128.1 + x86\MiniUnzip$(Configuration)\ + x86\MiniUnzip$(Configuration)\Tmp\ + true + false + x86\MiniUnzip$(Configuration)\ + x86\MiniUnzip$(Configuration)\Tmp\ + false + false + x64\MiniUnzip$(Configuration)\ + x64\MiniUnzip$(Configuration)\Tmp\ + true + false + ia64\MiniUnzip$(Configuration)\ + ia64\MiniUnzip$(Configuration)\Tmp\ + true + false + x64\MiniUnzip$(Configuration)\ + x64\MiniUnzip$(Configuration)\Tmp\ + false + false + ia64\MiniUnzip$(Configuration)\ + ia64\MiniUnzip$(Configuration)\Tmp\ + false + false + AllRules.ruleset + + + AllRules.ruleset + + + AllRules.ruleset + + + AllRules.ruleset + + + AllRules.ruleset + + + AllRules.ruleset + + + + + + Disabled + ..\..\..;..\..\minizip;%(AdditionalIncludeDirectories) + WIN32;_CRT_NONSTDC_NO_DEPRECATE;_CRT_SECURE_NO_DEPRECATE;ZLIB_WINAPI;_DEBUG;_CONSOLE;%(PreprocessorDefinitions) + true + Default + MultiThreadedDebug + false + + + $(IntDir) + Level3 + EditAndContinue + + + x86\ZlibDllDebug\zlibwapi.lib;%(AdditionalDependencies) + $(OutDir)miniunz.exe + true + $(OutDir)miniunz.pdb + Console + false + + + MachineX86 + + + + + MaxSpeed + OnlyExplicitInline + true + ..\..\..;..\..\minizip;%(AdditionalIncludeDirectories) + WIN32;_CRT_NONSTDC_NO_DEPRECATE;_CRT_SECURE_NO_DEPRECATE;ZLIB_WINAPI;NDEBUG;_CONSOLE;%(PreprocessorDefinitions) + true + Default + MultiThreaded + false + true + + + $(IntDir) + Level3 + ProgramDatabase + + + x86\ZlibDllRelease\zlibwapi.lib;%(AdditionalDependencies) + $(OutDir)miniunz.exe + true + Console + true + true + false + + + MachineX86 + + + + + X64 + + + Disabled + ..\..\..;..\..\minizip;%(AdditionalIncludeDirectories) + _CRT_NONSTDC_NO_DEPRECATE;_CRT_SECURE_NO_DEPRECATE;ZLIB_WINAPI;_DEBUG;_CONSOLE;WIN64;%(PreprocessorDefinitions) + true + Default + MultiThreadedDebugDLL + false + + + $(IntDir) + Level3 + ProgramDatabase + + + x64\ZlibDllDebug\zlibwapi.lib;%(AdditionalDependencies) + $(OutDir)miniunz.exe + true + $(OutDir)miniunz.pdb + Console + MachineX64 + + + + + Itanium + + + Disabled + ..\..\..;..\..\minizip;%(AdditionalIncludeDirectories) + _CRT_NONSTDC_NO_DEPRECATE;_CRT_SECURE_NO_DEPRECATE;ZLIB_WINAPI;_DEBUG;_CONSOLE;WIN64;%(PreprocessorDefinitions) + true + Default + MultiThreadedDebugDLL + false + + + $(IntDir) + Level3 + ProgramDatabase + + + ia64\ZlibDllDebug\zlibwapi.lib;%(AdditionalDependencies) + $(OutDir)miniunz.exe + true + $(OutDir)miniunz.pdb + Console + MachineIA64 + + + + + X64 + + + MaxSpeed + OnlyExplicitInline + true + ..\..\..;..\..\minizip;%(AdditionalIncludeDirectories) + _CRT_NONSTDC_NO_DEPRECATE;_CRT_SECURE_NO_DEPRECATE;ZLIB_WINAPI;NDEBUG;_CONSOLE;WIN64;%(PreprocessorDefinitions) + true + Default + MultiThreadedDLL + false + true + + + $(IntDir) + Level3 + ProgramDatabase + + + x64\ZlibDllRelease\zlibwapi.lib;%(AdditionalDependencies) + $(OutDir)miniunz.exe + true + Console + true + true + MachineX64 + + + + + Itanium + + + MaxSpeed + OnlyExplicitInline + true + ..\..\..;..\..\minizip;%(AdditionalIncludeDirectories) + _CRT_NONSTDC_NO_DEPRECATE;_CRT_SECURE_NO_DEPRECATE;ZLIB_WINAPI;NDEBUG;_CONSOLE;WIN64;%(PreprocessorDefinitions) + true + Default + MultiThreadedDLL + false + true + + + $(IntDir) + Level3 + ProgramDatabase + + + ia64\ZlibDllRelease\zlibwapi.lib;%(AdditionalDependencies) + $(OutDir)miniunz.exe + true + Console + true + true + MachineIA64 + + + + + + + + {8fd826f8-3739-44e6-8cc8-997122e53b8d} + + + + + + ADDED compat/zlib/contrib/vstudio/vc10/miniunz.vcxproj.filters Index: compat/zlib/contrib/vstudio/vc10/miniunz.vcxproj.filters ================================================================== --- /dev/null +++ compat/zlib/contrib/vstudio/vc10/miniunz.vcxproj.filters @@ -0,0 +1,22 @@ + + + + + {048af943-022b-4db6-beeb-a54c34774ee2} + cpp;c;cxx;def;odl;idl;hpj;bat;asm + + + {c1d600d2-888f-4aea-b73e-8b0dd9befa0c} + h;hpp;hxx;hm;inl;inc + + + {0844199a-966b-4f19-81db-1e0125e141b9} + rc;ico;cur;bmp;dlg;rc2;rct;bin;rgs;gif;jpg;jpeg;jpe + + + + + Source Files + + + ADDED compat/zlib/contrib/vstudio/vc10/minizip.vcxproj Index: compat/zlib/contrib/vstudio/vc10/minizip.vcxproj ================================================================== --- /dev/null +++ compat/zlib/contrib/vstudio/vc10/minizip.vcxproj @@ -0,0 +1,307 @@ + + + + + Debug + Itanium + + + Debug + Win32 + + + Debug + x64 + + + Release + Itanium + + + Release + Win32 + + + Release + x64 + + + + {48CDD9DC-E09F-4135-9C0C-4FE50C3C654B} + Win32Proj + + + + Application + MultiByte + + + Application + MultiByte + + + Application + MultiByte + + + Application + MultiByte + + + Application + MultiByte + + + Application + MultiByte + + + + + + + + + + + + + + + + + + + + + + + + + <_ProjectFileVersion>10.0.30128.1 + x86\MiniZip$(Configuration)\ + x86\MiniZip$(Configuration)\Tmp\ + true + false + x86\MiniZip$(Configuration)\ + x86\MiniZip$(Configuration)\Tmp\ + false + x64\$(Configuration)\ + x64\$(Configuration)\ + true + false + ia64\$(Configuration)\ + ia64\$(Configuration)\ + true + false + x64\$(Configuration)\ + x64\$(Configuration)\ + false + ia64\$(Configuration)\ + ia64\$(Configuration)\ + false + AllRules.ruleset + + + AllRules.ruleset + + + AllRules.ruleset + + + AllRules.ruleset + + + AllRules.ruleset + + + AllRules.ruleset + + + + + + Disabled + ..\..\..;..\..\minizip;%(AdditionalIncludeDirectories) + WIN32;_CRT_NONSTDC_NO_DEPRECATE;_CRT_SECURE_NO_DEPRECATE;ZLIB_WINAPI;_DEBUG;_CONSOLE;%(PreprocessorDefinitions) + true + Default + MultiThreadedDebug + false + + + $(IntDir) + Level3 + EditAndContinue + + + x86\ZlibDllDebug\zlibwapi.lib;%(AdditionalDependencies) + $(OutDir)minizip.exe + true + $(OutDir)minizip.pdb + Console + false + + + MachineX86 + + + + + MaxSpeed + OnlyExplicitInline + true + ..\..\..;..\..\minizip;%(AdditionalIncludeDirectories) + WIN32;_CRT_NONSTDC_NO_DEPRECATE;_CRT_SECURE_NO_DEPRECATE;ZLIB_WINAPI;NDEBUG;_CONSOLE;%(PreprocessorDefinitions) + true + Default + MultiThreaded + false + true + + + $(IntDir) + Level3 + ProgramDatabase + + + x86\ZlibDllRelease\zlibwapi.lib;%(AdditionalDependencies) + $(OutDir)minizip.exe + true + Console + true + true + false + + + MachineX86 + + + + + X64 + + + Disabled + ..\..\..;..\..\minizip;%(AdditionalIncludeDirectories) + _CRT_NONSTDC_NO_DEPRECATE;_CRT_SECURE_NO_DEPRECATE;ZLIB_WINAPI;_DEBUG;_CONSOLE;WIN64;%(PreprocessorDefinitions) + true + Default + MultiThreadedDebugDLL + false + + + $(IntDir) + Level3 + ProgramDatabase + + + x64\ZlibDllDebug\zlibwapi.lib;%(AdditionalDependencies) + $(OutDir)minizip.exe + true + $(OutDir)minizip.pdb + Console + MachineX64 + + + + + Itanium + + + Disabled + ..\..\..;..\..\minizip;%(AdditionalIncludeDirectories) + _CRT_NONSTDC_NO_DEPRECATE;_CRT_SECURE_NO_DEPRECATE;ZLIB_WINAPI;_DEBUG;_CONSOLE;WIN64;%(PreprocessorDefinitions) + true + Default + MultiThreadedDebugDLL + false + + + $(IntDir) + Level3 + ProgramDatabase + + + ia64\ZlibDllDebug\zlibwapi.lib;%(AdditionalDependencies) + $(OutDir)minizip.exe + true + $(OutDir)minizip.pdb + Console + MachineIA64 + + + + + X64 + + + MaxSpeed + OnlyExplicitInline + true + ..\..\..;..\..\minizip;%(AdditionalIncludeDirectories) + _CRT_NONSTDC_NO_DEPRECATE;_CRT_SECURE_NO_DEPRECATE;ZLIB_WINAPI;NDEBUG;_CONSOLE;WIN64;%(PreprocessorDefinitions) + true + Default + MultiThreadedDLL + false + true + + + $(IntDir) + Level3 + ProgramDatabase + + + x64\ZlibDllRelease\zlibwapi.lib;%(AdditionalDependencies) + $(OutDir)minizip.exe + true + Console + true + true + MachineX64 + + + + + Itanium + + + MaxSpeed + OnlyExplicitInline + true + ..\..\..;..\..\minizip;%(AdditionalIncludeDirectories) + _CRT_NONSTDC_NO_DEPRECATE;_CRT_SECURE_NO_DEPRECATE;ZLIB_WINAPI;NDEBUG;_CONSOLE;WIN64;%(PreprocessorDefinitions) + true + Default + MultiThreadedDLL + false + true + + + $(IntDir) + Level3 + ProgramDatabase + + + ia64\ZlibDllRelease\zlibwapi.lib;%(AdditionalDependencies) + $(OutDir)minizip.exe + true + Console + true + true + MachineIA64 + + + + + + + + {8fd826f8-3739-44e6-8cc8-997122e53b8d} + + + + + + ADDED compat/zlib/contrib/vstudio/vc10/minizip.vcxproj.filters Index: compat/zlib/contrib/vstudio/vc10/minizip.vcxproj.filters ================================================================== --- /dev/null +++ compat/zlib/contrib/vstudio/vc10/minizip.vcxproj.filters @@ -0,0 +1,22 @@ + + + + + {c0419b40-bf50-40da-b153-ff74215b79de} + cpp;c;cxx;def;odl;idl;hpj;bat;asm + + + {bb87b070-735b-478e-92ce-7383abb2f36c} + h;hpp;hxx;hm;inl;inc + + + {f46ab6a6-548f-43cb-ae96-681abb5bd5db} + rc;ico;cur;bmp;dlg;rc2;rct;bin;rgs;gif;jpg;jpeg;jpe + + + + + Source Files + + + ADDED compat/zlib/contrib/vstudio/vc10/testzlib.vcxproj Index: compat/zlib/contrib/vstudio/vc10/testzlib.vcxproj ================================================================== --- /dev/null +++ compat/zlib/contrib/vstudio/vc10/testzlib.vcxproj @@ -0,0 +1,420 @@ + + + + + Debug + Itanium + + + Debug + Win32 + + + Debug + x64 + + + ReleaseWithoutAsm + Itanium + + + ReleaseWithoutAsm + Win32 + + + ReleaseWithoutAsm + x64 + + + Release + Itanium + + + Release + Win32 + + + Release + x64 + + + + {AA6666AA-E09F-4135-9C0C-4FE50C3C654B} + testzlib + Win32Proj + + + + Application + MultiByte + true + + + Application + MultiByte + true + + + Application + MultiByte + + + Application + MultiByte + true + + + Application + MultiByte + true + + + Application + MultiByte + + + Application + true + + + Application + true + + + Application + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + <_ProjectFileVersion>10.0.30128.1 + x86\TestZlib$(Configuration)\ + x86\TestZlib$(Configuration)\Tmp\ + true + false + x86\TestZlib$(Configuration)\ + x86\TestZlib$(Configuration)\Tmp\ + false + false + x86\TestZlib$(Configuration)\ + x86\TestZlib$(Configuration)\Tmp\ + false + false + x64\TestZlib$(Configuration)\ + x64\TestZlib$(Configuration)\Tmp\ + false + ia64\TestZlib$(Configuration)\ + ia64\TestZlib$(Configuration)\Tmp\ + true + false + x64\TestZlib$(Configuration)\ + x64\TestZlib$(Configuration)\Tmp\ + false + ia64\TestZlib$(Configuration)\ + ia64\TestZlib$(Configuration)\Tmp\ + false + false + x64\TestZlib$(Configuration)\ + x64\TestZlib$(Configuration)\Tmp\ + false + ia64\TestZlib$(Configuration)\ + ia64\TestZlib$(Configuration)\Tmp\ + false + false + AllRules.ruleset + + + AllRules.ruleset + + + AllRules.ruleset + + + AllRules.ruleset + + + AllRules.ruleset + + + AllRules.ruleset + + + AllRules.ruleset + + + AllRules.ruleset + + + AllRules.ruleset + + + + + + Disabled + ..\..\..;%(AdditionalIncludeDirectories) + ASMV;ASMINF;WIN32;ZLIB_WINAPI;_DEBUG;_CONSOLE;_CRT_NONSTDC_NO_DEPRECATE;_CRT_SECURE_NO_DEPRECATE;_CRT_NONSTDC_NO_WARNINGS;%(PreprocessorDefinitions) + true + Default + MultiThreadedDebug + false + + + AssemblyAndSourceCode + $(IntDir) + Level3 + EditAndContinue + + + ..\..\masmx86\match686.obj;..\..\masmx86\inffas32.obj;%(AdditionalDependencies) + $(OutDir)testzlib.exe + true + $(OutDir)testzlib.pdb + Console + false + + + MachineX86 + + + + + MaxSpeed + OnlyExplicitInline + true + ..\..\..;%(AdditionalIncludeDirectories) + WIN32;ZLIB_WINAPI;NDEBUG;_CONSOLE;_CRT_NONSTDC_NO_DEPRECATE;_CRT_SECURE_NO_DEPRECATE;_CRT_NONSTDC_NO_WARNINGS;%(PreprocessorDefinitions) + true + Default + MultiThreaded + false + true + + + $(IntDir) + Level3 + ProgramDatabase + + + $(OutDir)testzlib.exe + true + Console + true + true + false + + + MachineX86 + + + + + MaxSpeed + OnlyExplicitInline + true + ..\..\..;%(AdditionalIncludeDirectories) + ASMV;ASMINF;WIN32;ZLIB_WINAPI;NDEBUG;_CONSOLE;_CRT_NONSTDC_NO_DEPRECATE;_CRT_SECURE_NO_DEPRECATE;_CRT_NONSTDC_NO_WARNINGS;%(PreprocessorDefinitions) + true + Default + MultiThreaded + false + true + + + $(IntDir) + Level3 + ProgramDatabase + + + ..\..\masmx86\match686.obj;..\..\masmx86\inffas32.obj;%(AdditionalDependencies) + $(OutDir)testzlib.exe + true + Console + true + true + false + + + MachineX86 + + + + + ..\..\..;%(AdditionalIncludeDirectories) + ASMV;ASMINF;WIN32;ZLIB_WINAPI;_DEBUG;_CONSOLE;_CRT_NONSTDC_NO_DEPRECATE;_CRT_SECURE_NO_DEPRECATE;_CRT_NONSTDC_NO_WARNINGS;%(PreprocessorDefinitions) + Default + MultiThreadedDebugDLL + false + $(IntDir) + + + ..\..\masmx64\gvmat64.obj;..\..\masmx64\inffasx64.obj;%(AdditionalDependencies) + + + + + Itanium + + + Disabled + ..\..\..;%(AdditionalIncludeDirectories) + ZLIB_WINAPI;_DEBUG;_CONSOLE;_CRT_NONSTDC_NO_DEPRECATE;_CRT_SECURE_NO_DEPRECATE;_CRT_NONSTDC_NO_WARNINGS;WIN64;%(PreprocessorDefinitions) + true + Default + MultiThreadedDebugDLL + false + + + AssemblyAndSourceCode + $(IntDir) + Level3 + ProgramDatabase + + + $(OutDir)testzlib.exe + true + $(OutDir)testzlib.pdb + Console + MachineIA64 + + + + + ..\..\..;%(AdditionalIncludeDirectories) + WIN32;ZLIB_WINAPI;NDEBUG;_CONSOLE;_CRT_NONSTDC_NO_DEPRECATE;_CRT_SECURE_NO_DEPRECATE;_CRT_NONSTDC_NO_WARNINGS;%(PreprocessorDefinitions) + Default + MultiThreadedDLL + false + $(IntDir) + + + %(AdditionalDependencies) + + + + + Itanium + + + MaxSpeed + OnlyExplicitInline + true + ..\..\..;%(AdditionalIncludeDirectories) + ZLIB_WINAPI;NDEBUG;_CONSOLE;_CRT_NONSTDC_NO_DEPRECATE;_CRT_SECURE_NO_DEPRECATE;_CRT_NONSTDC_NO_WARNINGS;WIN64;%(PreprocessorDefinitions) + true + Default + MultiThreadedDLL + false + true + + + $(IntDir) + Level3 + ProgramDatabase + + + $(OutDir)testzlib.exe + true + Console + true + true + MachineIA64 + + + + + ..\..\..;%(AdditionalIncludeDirectories) + ASMV;ASMINF;WIN32;ZLIB_WINAPI;NDEBUG;_CONSOLE;_CRT_NONSTDC_NO_DEPRECATE;_CRT_SECURE_NO_DEPRECATE;_CRT_NONSTDC_NO_WARNINGS;%(PreprocessorDefinitions) + Default + MultiThreadedDLL + false + $(IntDir) + + + ..\..\masmx64\gvmat64.obj;..\..\masmx64\inffasx64.obj;%(AdditionalDependencies) + + + + + Itanium + + + MaxSpeed + OnlyExplicitInline + true + ..\..\..;%(AdditionalIncludeDirectories) + ZLIB_WINAPI;NDEBUG;_CONSOLE;_CRT_NONSTDC_NO_DEPRECATE;_CRT_SECURE_NO_DEPRECATE;_CRT_NONSTDC_NO_WARNINGS;WIN64;%(PreprocessorDefinitions) + true + Default + MultiThreadedDLL + false + true + + + $(IntDir) + Level3 + ProgramDatabase + + + $(OutDir)testzlib.exe + true + Console + true + true + MachineIA64 + + + + + + + + + + true + true + true + true + true + true + + + + + + + + + + + + + ADDED compat/zlib/contrib/vstudio/vc10/testzlib.vcxproj.filters Index: compat/zlib/contrib/vstudio/vc10/testzlib.vcxproj.filters ================================================================== --- /dev/null +++ compat/zlib/contrib/vstudio/vc10/testzlib.vcxproj.filters @@ -0,0 +1,58 @@ + + + + + {c1f6a2e3-5da5-4955-8653-310d3efe05a9} + cpp;c;cxx;def;odl;idl;hpj;bat;asm + + + {c2aaffdc-2c95-4d6f-8466-4bec5890af2c} + h;hpp;hxx;hm;inl;inc + + + {c274fe07-05f2-461c-964b-f6341e4e7eb5} + rc;ico;cur;bmp;dlg;rc2;rct;bin;rgs;gif;jpg;jpeg;jpe + + + + + Source Files + + + Source Files + + + Source Files + + + Source Files + + + Source Files + + + Source Files + + + Source Files + + + Source Files + + + Source Files + + + Source Files + + + Source Files + + + Source Files + + + Source Files + + + ADDED compat/zlib/contrib/vstudio/vc10/testzlibdll.vcxproj Index: compat/zlib/contrib/vstudio/vc10/testzlibdll.vcxproj ================================================================== --- /dev/null +++ compat/zlib/contrib/vstudio/vc10/testzlibdll.vcxproj @@ -0,0 +1,310 @@ + + + + + Debug + Itanium + + + Debug + Win32 + + + Debug + x64 + + + Release + Itanium + + + Release + Win32 + + + Release + x64 + + + + {C52F9E7B-498A-42BE-8DB4-85A15694366A} + Win32Proj + + + + Application + MultiByte + + + Application + MultiByte + + + Application + MultiByte + + + Application + MultiByte + + + Application + MultiByte + + + Application + MultiByte + + + + + + + + + + + + + + + + + + + + + + + + + <_ProjectFileVersion>10.0.30128.1 + x86\TestZlibDll$(Configuration)\ + x86\TestZlibDll$(Configuration)\Tmp\ + true + false + x86\TestZlibDll$(Configuration)\ + x86\TestZlibDll$(Configuration)\Tmp\ + false + false + x64\TestZlibDll$(Configuration)\ + x64\TestZlibDll$(Configuration)\Tmp\ + true + false + ia64\TestZlibDll$(Configuration)\ + ia64\TestZlibDll$(Configuration)\Tmp\ + true + false + x64\TestZlibDll$(Configuration)\ + x64\TestZlibDll$(Configuration)\Tmp\ + false + false + ia64\TestZlibDll$(Configuration)\ + ia64\TestZlibDll$(Configuration)\Tmp\ + false + false + AllRules.ruleset + + + AllRules.ruleset + + + AllRules.ruleset + + + AllRules.ruleset + + + AllRules.ruleset + + + AllRules.ruleset + + + + + + Disabled + ..\..\..;..\..\minizip;%(AdditionalIncludeDirectories) + WIN32;_CRT_NONSTDC_NO_DEPRECATE;_CRT_SECURE_NO_DEPRECATE;ZLIB_WINAPI;_DEBUG;_CONSOLE;%(PreprocessorDefinitions) + true + Default + MultiThreadedDebug + false + + + $(IntDir) + Level3 + EditAndContinue + + + x86\ZlibDllDebug\zlibwapi.lib;%(AdditionalDependencies) + $(OutDir)testzlibdll.exe + true + $(OutDir)testzlib.pdb + Console + false + + + MachineX86 + + + + + MaxSpeed + OnlyExplicitInline + true + ..\..\..;..\..\minizip;%(AdditionalIncludeDirectories) + WIN32;_CRT_NONSTDC_NO_DEPRECATE;_CRT_SECURE_NO_DEPRECATE;ZLIB_WINAPI;NDEBUG;_CONSOLE;%(PreprocessorDefinitions) + true + Default + MultiThreaded + false + true + + + $(IntDir) + Level3 + ProgramDatabase + + + x86\ZlibDllRelease\zlibwapi.lib;%(AdditionalDependencies) + $(OutDir)testzlibdll.exe + true + Console + true + true + false + + + MachineX86 + + + + + X64 + + + Disabled + ..\..\..;..\..\minizip;%(AdditionalIncludeDirectories) + _CRT_NONSTDC_NO_DEPRECATE;_CRT_SECURE_NO_DEPRECATE;ZLIB_WINAPI;_DEBUG;_CONSOLE;WIN64;%(PreprocessorDefinitions) + true + Default + MultiThreadedDebugDLL + false + + + $(IntDir) + Level3 + ProgramDatabase + + + x64\ZlibDllDebug\zlibwapi.lib;%(AdditionalDependencies) + $(OutDir)testzlibdll.exe + true + $(OutDir)testzlib.pdb + Console + MachineX64 + + + + + Itanium + + + Disabled + ..\..\..;..\..\minizip;%(AdditionalIncludeDirectories) + _CRT_NONSTDC_NO_DEPRECATE;_CRT_SECURE_NO_DEPRECATE;ZLIB_WINAPI;_DEBUG;_CONSOLE;WIN64;%(PreprocessorDefinitions) + true + Default + MultiThreadedDebugDLL + false + + + $(IntDir) + Level3 + ProgramDatabase + + + ia64\ZlibDllDebug\zlibwapi.lib;%(AdditionalDependencies) + $(OutDir)testzlibdll.exe + true + $(OutDir)testzlib.pdb + Console + MachineIA64 + + + + + X64 + + + MaxSpeed + OnlyExplicitInline + true + ..\..\..;..\..\minizip;%(AdditionalIncludeDirectories) + _CRT_NONSTDC_NO_DEPRECATE;_CRT_SECURE_NO_DEPRECATE;ZLIB_WINAPI;NDEBUG;_CONSOLE;WIN64;%(PreprocessorDefinitions) + true + Default + MultiThreadedDLL + false + true + + + $(IntDir) + Level3 + ProgramDatabase + + + x64\ZlibDllRelease\zlibwapi.lib;%(AdditionalDependencies) + $(OutDir)testzlibdll.exe + true + Console + true + true + MachineX64 + + + + + Itanium + + + MaxSpeed + OnlyExplicitInline + true + ..\..\..;..\..\minizip;%(AdditionalIncludeDirectories) + _CRT_NONSTDC_NO_DEPRECATE;_CRT_SECURE_NO_DEPRECATE;ZLIB_WINAPI;NDEBUG;_CONSOLE;WIN64;%(PreprocessorDefinitions) + true + Default + MultiThreadedDLL + false + true + + + $(IntDir) + Level3 + ProgramDatabase + + + ia64\ZlibDllRelease\zlibwapi.lib;%(AdditionalDependencies) + $(OutDir)testzlibdll.exe + true + Console + true + true + MachineIA64 + + + + + + + + {8fd826f8-3739-44e6-8cc8-997122e53b8d} + + + + + + ADDED compat/zlib/contrib/vstudio/vc10/testzlibdll.vcxproj.filters Index: compat/zlib/contrib/vstudio/vc10/testzlibdll.vcxproj.filters ================================================================== --- /dev/null +++ compat/zlib/contrib/vstudio/vc10/testzlibdll.vcxproj.filters @@ -0,0 +1,22 @@ + + + + + {fa61a89f-93fc-4c89-b29e-36224b7592f4} + cpp;c;cxx;def;odl;idl;hpj;bat;asm + + + {d4b85da0-2ba2-4934-b57f-e2584e3848ee} + h;hpp;hxx;hm;inl;inc + + + {e573e075-00bd-4a7d-bd67-a8cc9bfc5aca} + rc;ico;cur;bmp;dlg;rc2;rct;bin;rgs;gif;jpg;jpeg;jpe + + + + + Source Files + + + ADDED compat/zlib/contrib/vstudio/vc10/zlib.rc Index: compat/zlib/contrib/vstudio/vc10/zlib.rc ================================================================== --- /dev/null +++ compat/zlib/contrib/vstudio/vc10/zlib.rc @@ -0,0 +1,32 @@ +#include + +#define IDR_VERSION1 1 +IDR_VERSION1 VERSIONINFO MOVEABLE IMPURE LOADONCALL DISCARDABLE + FILEVERSION 1, 2, 11, 0 + PRODUCTVERSION 1, 2, 11, 0 + FILEFLAGSMASK VS_FFI_FILEFLAGSMASK + FILEFLAGS 0 + FILEOS VOS_DOS_WINDOWS32 + FILETYPE VFT_DLL + FILESUBTYPE 0 // not used +BEGIN + BLOCK "StringFileInfo" + BEGIN + BLOCK "040904E4" + //language ID = U.S. English, char set = Windows, Multilingual + + BEGIN + VALUE "FileDescription", "zlib data compression and ZIP file I/O library\0" + VALUE "FileVersion", "1.2.11\0" + VALUE "InternalName", "zlib\0" + VALUE "OriginalFilename", "zlibwapi.dll\0" + VALUE "ProductName", "ZLib.DLL\0" + VALUE "Comments","DLL support by Alessandro Iacopetti & Gilles Vollant\0" + VALUE "LegalCopyright", "(C) 1995-2017 Jean-loup Gailly & Mark Adler\0" + END + END + BLOCK "VarFileInfo" + BEGIN + VALUE "Translation", 0x0409, 1252 + END +END ADDED compat/zlib/contrib/vstudio/vc10/zlibstat.vcxproj Index: compat/zlib/contrib/vstudio/vc10/zlibstat.vcxproj ================================================================== --- /dev/null +++ compat/zlib/contrib/vstudio/vc10/zlibstat.vcxproj @@ -0,0 +1,473 @@ + + + + + Debug + Itanium + + + Debug + Win32 + + + Debug + x64 + + + ReleaseWithoutAsm + Itanium + + + ReleaseWithoutAsm + Win32 + + + ReleaseWithoutAsm + x64 + + + Release + Itanium + + + Release + Win32 + + + Release + x64 + + + + {745DEC58-EBB3-47A9-A9B8-4C6627C01BF8} + + + + StaticLibrary + false + + + StaticLibrary + false + + + StaticLibrary + false + + + StaticLibrary + false + + + StaticLibrary + false + + + StaticLibrary + false + + + StaticLibrary + false + + + StaticLibrary + false + + + StaticLibrary + false + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + <_ProjectFileVersion>10.0.30128.1 + x86\ZlibStat$(Configuration)\ + x86\ZlibStat$(Configuration)\Tmp\ + x86\ZlibStat$(Configuration)\ + x86\ZlibStat$(Configuration)\Tmp\ + x86\ZlibStat$(Configuration)\ + x86\ZlibStat$(Configuration)\Tmp\ + x64\ZlibStat$(Configuration)\ + x64\ZlibStat$(Configuration)\Tmp\ + ia64\ZlibStat$(Configuration)\ + ia64\ZlibStat$(Configuration)\Tmp\ + x64\ZlibStat$(Configuration)\ + x64\ZlibStat$(Configuration)\Tmp\ + ia64\ZlibStat$(Configuration)\ + ia64\ZlibStat$(Configuration)\Tmp\ + x64\ZlibStat$(Configuration)\ + x64\ZlibStat$(Configuration)\Tmp\ + ia64\ZlibStat$(Configuration)\ + ia64\ZlibStat$(Configuration)\Tmp\ + AllRules.ruleset + + + AllRules.ruleset + + + AllRules.ruleset + + + AllRules.ruleset + + + AllRules.ruleset + + + AllRules.ruleset + + + AllRules.ruleset + + + AllRules.ruleset + + + AllRules.ruleset + + + + + + Disabled + ..\..\..;..\..\masmx86;%(AdditionalIncludeDirectories) + WIN32;ZLIB_WINAPI;_CRT_NONSTDC_NO_DEPRECATE;_CRT_SECURE_NO_DEPRECATE;_CRT_NONSTDC_NO_WARNINGS;%(PreprocessorDefinitions) + + + MultiThreadedDebug + false + $(IntDir)zlibstat.pch + $(IntDir) + $(IntDir) + $(OutDir) + Level3 + true + OldStyle + + + 0x040c + + + /MACHINE:X86 /NODEFAULTLIB %(AdditionalOptions) + $(OutDir)zlibstat.lib + true + + + cd ..\..\masmx86 +bld_ml32.bat + + + + + OnlyExplicitInline + ..\..\..;..\..\masmx86;%(AdditionalIncludeDirectories) + WIN32;ZLIB_WINAPI;_CRT_NONSTDC_NO_DEPRECATE;_CRT_SECURE_NO_DEPRECATE;_CRT_NONSTDC_NO_WARNINGS;ASMV;ASMINF;%(PreprocessorDefinitions) + true + + + MultiThreaded + false + true + $(IntDir)zlibstat.pch + $(IntDir) + $(IntDir) + $(OutDir) + Level3 + true + + + 0x040c + + + /MACHINE:X86 /NODEFAULTLIB %(AdditionalOptions) + ..\..\masmx86\match686.obj;..\..\masmx86\inffas32.obj;%(AdditionalDependencies) + $(OutDir)zlibstat.lib + true + + + cd ..\..\masmx86 +bld_ml32.bat + + + + + OnlyExplicitInline + ..\..\..;..\..\masmx86;%(AdditionalIncludeDirectories) + WIN32;ZLIB_WINAPI;_CRT_NONSTDC_NO_DEPRECATE;_CRT_SECURE_NO_DEPRECATE;_CRT_NONSTDC_NO_WARNINGS;%(PreprocessorDefinitions) + true + + + MultiThreaded + false + true + $(IntDir)zlibstat.pch + $(IntDir) + $(IntDir) + $(OutDir) + Level3 + true + + + 0x040c + + + /MACHINE:X86 /NODEFAULTLIB %(AdditionalOptions) + $(OutDir)zlibstat.lib + true + + + + + X64 + + + Disabled + ..\..\..;..\..\masmx86;%(AdditionalIncludeDirectories) + ZLIB_WINAPI;_CRT_NONSTDC_NO_DEPRECATE;_CRT_SECURE_NO_DEPRECATE;_CRT_NONSTDC_NO_WARNINGS;WIN64;%(PreprocessorDefinitions) + + + MultiThreadedDebugDLL + false + $(IntDir)zlibstat.pch + $(IntDir) + $(IntDir) + $(OutDir) + Level3 + true + OldStyle + + + 0x040c + + + /MACHINE:AMD64 /NODEFAULTLIB %(AdditionalOptions) + $(OutDir)zlibstat.lib + true + + + cd ..\..\masmx64 +bld_ml64.bat + + + + + Itanium + + + Disabled + ..\..\..;..\..\masmx86;%(AdditionalIncludeDirectories) + ZLIB_WINAPI;_CRT_NONSTDC_NO_DEPRECATE;_CRT_SECURE_NO_DEPRECATE;_CRT_NONSTDC_NO_WARNINGS;WIN64;%(PreprocessorDefinitions) + + + MultiThreadedDebugDLL + false + $(IntDir)zlibstat.pch + $(IntDir) + $(IntDir) + $(OutDir) + Level3 + true + OldStyle + + + 0x040c + + + /MACHINE:IA64 /NODEFAULTLIB %(AdditionalOptions) + $(OutDir)zlibstat.lib + true + + + + + X64 + + + OnlyExplicitInline + ..\..\..;..\..\masmx86;%(AdditionalIncludeDirectories) + ZLIB_WINAPI;_CRT_NONSTDC_NO_DEPRECATE;_CRT_SECURE_NO_DEPRECATE;_CRT_NONSTDC_NO_WARNINGS;ASMV;ASMINF;WIN64;%(PreprocessorDefinitions) + true + + + MultiThreadedDLL + false + true + $(IntDir)zlibstat.pch + $(IntDir) + $(IntDir) + $(OutDir) + Level3 + true + + + 0x040c + + + /MACHINE:AMD64 /NODEFAULTLIB %(AdditionalOptions) + ..\..\masmx64\gvmat64.obj;..\..\masmx64\inffasx64.obj;%(AdditionalDependencies) + $(OutDir)zlibstat.lib + true + + + cd ..\..\masmx64 +bld_ml64.bat + + + + + Itanium + + + OnlyExplicitInline + ..\..\..;..\..\masmx86;%(AdditionalIncludeDirectories) + ZLIB_WINAPI;_CRT_NONSTDC_NO_DEPRECATE;_CRT_SECURE_NO_DEPRECATE;_CRT_NONSTDC_NO_WARNINGS;WIN64;%(PreprocessorDefinitions) + true + + + MultiThreadedDLL + false + true + $(IntDir)zlibstat.pch + $(IntDir) + $(IntDir) + $(OutDir) + Level3 + true + + + 0x040c + + + /MACHINE:IA64 /NODEFAULTLIB %(AdditionalOptions) + $(OutDir)zlibstat.lib + true + + + + + X64 + + + OnlyExplicitInline + ..\..\..;..\..\masmx86;%(AdditionalIncludeDirectories) + ZLIB_WINAPI;_CRT_NONSTDC_NO_DEPRECATE;_CRT_SECURE_NO_DEPRECATE;_CRT_NONSTDC_NO_WARNINGS;WIN64;%(PreprocessorDefinitions) + true + + + MultiThreadedDLL + false + true + $(IntDir)zlibstat.pch + $(IntDir) + $(IntDir) + $(OutDir) + Level3 + true + + + 0x040c + + + /MACHINE:AMD64 /NODEFAULTLIB %(AdditionalOptions) + $(OutDir)zlibstat.lib + true + + + + + Itanium + + + OnlyExplicitInline + ..\..\..;..\..\masmx86;%(AdditionalIncludeDirectories) + ZLIB_WINAPI;_CRT_NONSTDC_NO_DEPRECATE;_CRT_SECURE_NO_DEPRECATE;_CRT_NONSTDC_NO_WARNINGS;WIN64;%(PreprocessorDefinitions) + true + + + MultiThreadedDLL + false + true + $(IntDir)zlibstat.pch + $(IntDir) + $(IntDir) + $(OutDir) + Level3 + true + + + 0x040c + + + /MACHINE:IA64 /NODEFAULTLIB %(AdditionalOptions) + $(OutDir)zlibstat.lib + true + + + + + + + + + + + + + + true + true + true + true + true + true + + + + + + + + + + + + + + + + + + + + + ADDED compat/zlib/contrib/vstudio/vc10/zlibstat.vcxproj.filters Index: compat/zlib/contrib/vstudio/vc10/zlibstat.vcxproj.filters ================================================================== --- /dev/null +++ compat/zlib/contrib/vstudio/vc10/zlibstat.vcxproj.filters @@ -0,0 +1,77 @@ + + + + + {174213f6-7f66-4ae8-a3a8-a1e0a1e6ffdd} + + + + + Source Files + + + Source Files + + + Source Files + + + Source Files + + + Source Files + + + Source Files + + + Source Files + + + Source Files + + + Source Files + + + Source Files + + + Source Files + + + Source Files + + + Source Files + + + Source Files + + + Source Files + + + Source Files + + + Source Files + + + Source Files + + + Source Files + + + + + Source Files + + + + + Source Files + + + ADDED compat/zlib/contrib/vstudio/vc10/zlibvc.def Index: compat/zlib/contrib/vstudio/vc10/zlibvc.def ================================================================== --- /dev/null +++ compat/zlib/contrib/vstudio/vc10/zlibvc.def @@ -0,0 +1,153 @@ +LIBRARY +; zlib data compression and ZIP file I/O library + +VERSION 1.2 + +EXPORTS + adler32 @1 + compress @2 + crc32 @3 + deflate @4 + deflateCopy @5 + deflateEnd @6 + deflateInit2_ @7 + deflateInit_ @8 + deflateParams @9 + deflateReset @10 + deflateSetDictionary @11 + gzclose @12 + gzdopen @13 + gzerror @14 + gzflush @15 + gzopen @16 + gzread @17 + gzwrite @18 + inflate @19 + inflateEnd @20 + inflateInit2_ @21 + inflateInit_ @22 + inflateReset @23 + inflateSetDictionary @24 + inflateSync @25 + uncompress @26 + zlibVersion @27 + gzprintf @28 + gzputc @29 + gzgetc @30 + gzseek @31 + gzrewind @32 + gztell @33 + gzeof @34 + gzsetparams @35 + zError @36 + inflateSyncPoint @37 + get_crc_table @38 + compress2 @39 + gzputs @40 + gzgets @41 + inflateCopy @42 + inflateBackInit_ @43 + inflateBack @44 + inflateBackEnd @45 + compressBound @46 + deflateBound @47 + gzclearerr @48 + gzungetc @49 + zlibCompileFlags @50 + deflatePrime @51 + deflatePending @52 + + unzOpen @61 + unzClose @62 + unzGetGlobalInfo @63 + unzGetCurrentFileInfo @64 + unzGoToFirstFile @65 + unzGoToNextFile @66 + unzOpenCurrentFile @67 + unzReadCurrentFile @68 + unzOpenCurrentFile3 @69 + unztell @70 + unzeof @71 + unzCloseCurrentFile @72 + unzGetGlobalComment @73 + unzStringFileNameCompare @74 + unzLocateFile @75 + unzGetLocalExtrafield @76 + unzOpen2 @77 + unzOpenCurrentFile2 @78 + unzOpenCurrentFilePassword @79 + + zipOpen @80 + zipOpenNewFileInZip @81 + zipWriteInFileInZip @82 + zipCloseFileInZip @83 + zipClose @84 + zipOpenNewFileInZip2 @86 + zipCloseFileInZipRaw @87 + zipOpen2 @88 + zipOpenNewFileInZip3 @89 + + unzGetFilePos @100 + unzGoToFilePos @101 + + fill_win32_filefunc @110 + +; zlibwapi v1.2.4 added: + fill_win32_filefunc64 @111 + fill_win32_filefunc64A @112 + fill_win32_filefunc64W @113 + + unzOpen64 @120 + unzOpen2_64 @121 + unzGetGlobalInfo64 @122 + unzGetCurrentFileInfo64 @124 + unzGetCurrentFileZStreamPos64 @125 + unztell64 @126 + unzGetFilePos64 @127 + unzGoToFilePos64 @128 + + zipOpen64 @130 + zipOpen2_64 @131 + zipOpenNewFileInZip64 @132 + zipOpenNewFileInZip2_64 @133 + zipOpenNewFileInZip3_64 @134 + zipOpenNewFileInZip4_64 @135 + zipCloseFileInZipRaw64 @136 + +; zlib1 v1.2.4 added: + adler32_combine @140 + crc32_combine @142 + deflateSetHeader @144 + deflateTune @145 + gzbuffer @146 + gzclose_r @147 + gzclose_w @148 + gzdirect @149 + gzoffset @150 + inflateGetHeader @156 + inflateMark @157 + inflatePrime @158 + inflateReset2 @159 + inflateUndermine @160 + +; zlib1 v1.2.6 added: + gzgetc_ @161 + inflateResetKeep @163 + deflateResetKeep @164 + +; zlib1 v1.2.7 added: + gzopen_w @165 + +; zlib1 v1.2.8 added: + inflateGetDictionary @166 + gzvprintf @167 + +; zlib1 v1.2.9 added: + inflateCodesUsed @168 + inflateValidate @169 + uncompress2 @170 + gzfread @171 + gzfwrite @172 + deflateGetDictionary @173 + adler32_z @174 + crc32_z @175 ADDED compat/zlib/contrib/vstudio/vc10/zlibvc.sln Index: compat/zlib/contrib/vstudio/vc10/zlibvc.sln ================================================================== --- /dev/null +++ compat/zlib/contrib/vstudio/vc10/zlibvc.sln @@ -0,0 +1,135 @@ + +Microsoft Visual Studio Solution File, Format Version 11.00 +# Visual Studio 2010 +Project("{8BC9CEB8-8B4A-11D0-8D11-00A0C91BC942}") = "zlibvc", "zlibvc.vcxproj", "{8FD826F8-3739-44E6-8CC8-997122E53B8D}" +EndProject +Project("{8BC9CEB8-8B4A-11D0-8D11-00A0C91BC942}") = "zlibstat", "zlibstat.vcxproj", "{745DEC58-EBB3-47A9-A9B8-4C6627C01BF8}" +EndProject +Project("{8BC9CEB8-8B4A-11D0-8D11-00A0C91BC942}") = "testzlib", "testzlib.vcxproj", "{AA6666AA-E09F-4135-9C0C-4FE50C3C654B}" +EndProject +Project("{8BC9CEB8-8B4A-11D0-8D11-00A0C91BC942}") = "testzlibdll", "testzlibdll.vcxproj", "{C52F9E7B-498A-42BE-8DB4-85A15694366A}" +EndProject +Project("{8BC9CEB8-8B4A-11D0-8D11-00A0C91BC942}") = "minizip", "minizip.vcxproj", "{48CDD9DC-E09F-4135-9C0C-4FE50C3C654B}" +EndProject +Project("{8BC9CEB8-8B4A-11D0-8D11-00A0C91BC942}") = "miniunz", "miniunz.vcxproj", "{C52F9E7B-498A-42BE-8DB4-85A15694382A}" +EndProject +Global + GlobalSection(SolutionConfigurationPlatforms) = preSolution + Debug|Itanium = Debug|Itanium + Debug|Win32 = Debug|Win32 + Debug|x64 = Debug|x64 + Release|Itanium = Release|Itanium + Release|Win32 = Release|Win32 + Release|x64 = Release|x64 + ReleaseWithoutAsm|Itanium = ReleaseWithoutAsm|Itanium + ReleaseWithoutAsm|Win32 = ReleaseWithoutAsm|Win32 + ReleaseWithoutAsm|x64 = ReleaseWithoutAsm|x64 + EndGlobalSection + GlobalSection(ProjectConfigurationPlatforms) = postSolution + {8FD826F8-3739-44E6-8CC8-997122E53B8D}.Debug|Itanium.ActiveCfg = Debug|Itanium + {8FD826F8-3739-44E6-8CC8-997122E53B8D}.Debug|Itanium.Build.0 = Debug|Itanium + {8FD826F8-3739-44E6-8CC8-997122E53B8D}.Debug|Win32.ActiveCfg = Debug|Win32 + {8FD826F8-3739-44E6-8CC8-997122E53B8D}.Debug|Win32.Build.0 = Debug|Win32 + {8FD826F8-3739-44E6-8CC8-997122E53B8D}.Debug|x64.ActiveCfg = Debug|x64 + {8FD826F8-3739-44E6-8CC8-997122E53B8D}.Debug|x64.Build.0 = Debug|x64 + {8FD826F8-3739-44E6-8CC8-997122E53B8D}.Release|Itanium.ActiveCfg = Release|Itanium + {8FD826F8-3739-44E6-8CC8-997122E53B8D}.Release|Itanium.Build.0 = Release|Itanium + {8FD826F8-3739-44E6-8CC8-997122E53B8D}.Release|Win32.ActiveCfg = Release|Win32 + {8FD826F8-3739-44E6-8CC8-997122E53B8D}.Release|Win32.Build.0 = Release|Win32 + {8FD826F8-3739-44E6-8CC8-997122E53B8D}.Release|x64.ActiveCfg = Release|x64 + {8FD826F8-3739-44E6-8CC8-997122E53B8D}.Release|x64.Build.0 = Release|x64 + {8FD826F8-3739-44E6-8CC8-997122E53B8D}.ReleaseWithoutAsm|Itanium.ActiveCfg = ReleaseWithoutAsm|Itanium + {8FD826F8-3739-44E6-8CC8-997122E53B8D}.ReleaseWithoutAsm|Itanium.Build.0 = ReleaseWithoutAsm|Itanium + {8FD826F8-3739-44E6-8CC8-997122E53B8D}.ReleaseWithoutAsm|Win32.ActiveCfg = ReleaseWithoutAsm|Win32 + {8FD826F8-3739-44E6-8CC8-997122E53B8D}.ReleaseWithoutAsm|Win32.Build.0 = ReleaseWithoutAsm|Win32 + {8FD826F8-3739-44E6-8CC8-997122E53B8D}.ReleaseWithoutAsm|x64.ActiveCfg = ReleaseWithoutAsm|x64 + {8FD826F8-3739-44E6-8CC8-997122E53B8D}.ReleaseWithoutAsm|x64.Build.0 = ReleaseWithoutAsm|x64 + {745DEC58-EBB3-47A9-A9B8-4C6627C01BF8}.Debug|Itanium.ActiveCfg = Debug|Itanium + {745DEC58-EBB3-47A9-A9B8-4C6627C01BF8}.Debug|Itanium.Build.0 = Debug|Itanium + {745DEC58-EBB3-47A9-A9B8-4C6627C01BF8}.Debug|Win32.ActiveCfg = Debug|Win32 + {745DEC58-EBB3-47A9-A9B8-4C6627C01BF8}.Debug|Win32.Build.0 = Debug|Win32 + {745DEC58-EBB3-47A9-A9B8-4C6627C01BF8}.Debug|x64.ActiveCfg = Debug|x64 + {745DEC58-EBB3-47A9-A9B8-4C6627C01BF8}.Debug|x64.Build.0 = Debug|x64 + {745DEC58-EBB3-47A9-A9B8-4C6627C01BF8}.Release|Itanium.ActiveCfg = Release|Itanium + {745DEC58-EBB3-47A9-A9B8-4C6627C01BF8}.Release|Itanium.Build.0 = Release|Itanium + {745DEC58-EBB3-47A9-A9B8-4C6627C01BF8}.Release|Win32.ActiveCfg = Release|Win32 + {745DEC58-EBB3-47A9-A9B8-4C6627C01BF8}.Release|Win32.Build.0 = Release|Win32 + {745DEC58-EBB3-47A9-A9B8-4C6627C01BF8}.Release|x64.ActiveCfg = Release|x64 + {745DEC58-EBB3-47A9-A9B8-4C6627C01BF8}.Release|x64.Build.0 = Release|x64 + {745DEC58-EBB3-47A9-A9B8-4C6627C01BF8}.ReleaseWithoutAsm|Itanium.ActiveCfg = ReleaseWithoutAsm|Itanium + {745DEC58-EBB3-47A9-A9B8-4C6627C01BF8}.ReleaseWithoutAsm|Itanium.Build.0 = ReleaseWithoutAsm|Itanium + {745DEC58-EBB3-47A9-A9B8-4C6627C01BF8}.ReleaseWithoutAsm|Win32.ActiveCfg = ReleaseWithoutAsm|Win32 + {745DEC58-EBB3-47A9-A9B8-4C6627C01BF8}.ReleaseWithoutAsm|Win32.Build.0 = ReleaseWithoutAsm|Win32 + {745DEC58-EBB3-47A9-A9B8-4C6627C01BF8}.ReleaseWithoutAsm|x64.ActiveCfg = ReleaseWithoutAsm|x64 + {745DEC58-EBB3-47A9-A9B8-4C6627C01BF8}.ReleaseWithoutAsm|x64.Build.0 = ReleaseWithoutAsm|x64 + {AA6666AA-E09F-4135-9C0C-4FE50C3C654B}.Debug|Itanium.ActiveCfg = Debug|Itanium + {AA6666AA-E09F-4135-9C0C-4FE50C3C654B}.Debug|Itanium.Build.0 = Debug|Itanium + {AA6666AA-E09F-4135-9C0C-4FE50C3C654B}.Debug|Win32.ActiveCfg = Debug|Win32 + {AA6666AA-E09F-4135-9C0C-4FE50C3C654B}.Debug|Win32.Build.0 = Debug|Win32 + {AA6666AA-E09F-4135-9C0C-4FE50C3C654B}.Debug|x64.ActiveCfg = Debug|x64 + {AA6666AA-E09F-4135-9C0C-4FE50C3C654B}.Debug|x64.Build.0 = Debug|x64 + {AA6666AA-E09F-4135-9C0C-4FE50C3C654B}.Release|Itanium.ActiveCfg = Release|Itanium + {AA6666AA-E09F-4135-9C0C-4FE50C3C654B}.Release|Itanium.Build.0 = Release|Itanium + {AA6666AA-E09F-4135-9C0C-4FE50C3C654B}.Release|Win32.ActiveCfg = Release|Win32 + {AA6666AA-E09F-4135-9C0C-4FE50C3C654B}.Release|Win32.Build.0 = Release|Win32 + {AA6666AA-E09F-4135-9C0C-4FE50C3C654B}.Release|x64.ActiveCfg = Release|x64 + {AA6666AA-E09F-4135-9C0C-4FE50C3C654B}.Release|x64.Build.0 = Release|x64 + {AA6666AA-E09F-4135-9C0C-4FE50C3C654B}.ReleaseWithoutAsm|Itanium.ActiveCfg = ReleaseWithoutAsm|Itanium + {AA6666AA-E09F-4135-9C0C-4FE50C3C654B}.ReleaseWithoutAsm|Itanium.Build.0 = ReleaseWithoutAsm|Itanium + {AA6666AA-E09F-4135-9C0C-4FE50C3C654B}.ReleaseWithoutAsm|Win32.ActiveCfg = ReleaseWithoutAsm|Win32 + {AA6666AA-E09F-4135-9C0C-4FE50C3C654B}.ReleaseWithoutAsm|Win32.Build.0 = ReleaseWithoutAsm|Win32 + {AA6666AA-E09F-4135-9C0C-4FE50C3C654B}.ReleaseWithoutAsm|x64.ActiveCfg = ReleaseWithoutAsm|x64 + {AA6666AA-E09F-4135-9C0C-4FE50C3C654B}.ReleaseWithoutAsm|x64.Build.0 = ReleaseWithoutAsm|x64 + {C52F9E7B-498A-42BE-8DB4-85A15694366A}.Debug|Itanium.ActiveCfg = Debug|Itanium + {C52F9E7B-498A-42BE-8DB4-85A15694366A}.Debug|Itanium.Build.0 = Debug|Itanium + {C52F9E7B-498A-42BE-8DB4-85A15694366A}.Debug|Win32.ActiveCfg = Debug|Win32 + {C52F9E7B-498A-42BE-8DB4-85A15694366A}.Debug|Win32.Build.0 = Debug|Win32 + {C52F9E7B-498A-42BE-8DB4-85A15694366A}.Debug|x64.ActiveCfg = Debug|x64 + {C52F9E7B-498A-42BE-8DB4-85A15694366A}.Debug|x64.Build.0 = Debug|x64 + {C52F9E7B-498A-42BE-8DB4-85A15694366A}.Release|Itanium.ActiveCfg = Release|Itanium + {C52F9E7B-498A-42BE-8DB4-85A15694366A}.Release|Itanium.Build.0 = Release|Itanium + {C52F9E7B-498A-42BE-8DB4-85A15694366A}.Release|Win32.ActiveCfg = Release|Win32 + {C52F9E7B-498A-42BE-8DB4-85A15694366A}.Release|Win32.Build.0 = Release|Win32 + {C52F9E7B-498A-42BE-8DB4-85A15694366A}.Release|x64.ActiveCfg = Release|x64 + {C52F9E7B-498A-42BE-8DB4-85A15694366A}.Release|x64.Build.0 = Release|x64 + {C52F9E7B-498A-42BE-8DB4-85A15694366A}.ReleaseWithoutAsm|Itanium.ActiveCfg = Release|Itanium + {C52F9E7B-498A-42BE-8DB4-85A15694366A}.ReleaseWithoutAsm|Itanium.Build.0 = Release|Itanium + {C52F9E7B-498A-42BE-8DB4-85A15694366A}.ReleaseWithoutAsm|Win32.ActiveCfg = Release|Win32 + {C52F9E7B-498A-42BE-8DB4-85A15694366A}.ReleaseWithoutAsm|x64.ActiveCfg = Release|x64 + {48CDD9DC-E09F-4135-9C0C-4FE50C3C654B}.Debug|Itanium.ActiveCfg = Debug|Itanium + {48CDD9DC-E09F-4135-9C0C-4FE50C3C654B}.Debug|Itanium.Build.0 = Debug|Itanium + {48CDD9DC-E09F-4135-9C0C-4FE50C3C654B}.Debug|Win32.ActiveCfg = Debug|Win32 + {48CDD9DC-E09F-4135-9C0C-4FE50C3C654B}.Debug|Win32.Build.0 = Debug|Win32 + {48CDD9DC-E09F-4135-9C0C-4FE50C3C654B}.Debug|x64.ActiveCfg = Debug|x64 + {48CDD9DC-E09F-4135-9C0C-4FE50C3C654B}.Debug|x64.Build.0 = Debug|x64 + {48CDD9DC-E09F-4135-9C0C-4FE50C3C654B}.Release|Itanium.ActiveCfg = Release|Itanium + {48CDD9DC-E09F-4135-9C0C-4FE50C3C654B}.Release|Itanium.Build.0 = Release|Itanium + {48CDD9DC-E09F-4135-9C0C-4FE50C3C654B}.Release|Win32.ActiveCfg = Release|Win32 + {48CDD9DC-E09F-4135-9C0C-4FE50C3C654B}.Release|Win32.Build.0 = Release|Win32 + {48CDD9DC-E09F-4135-9C0C-4FE50C3C654B}.Release|x64.ActiveCfg = Release|x64 + {48CDD9DC-E09F-4135-9C0C-4FE50C3C654B}.Release|x64.Build.0 = Release|x64 + {48CDD9DC-E09F-4135-9C0C-4FE50C3C654B}.ReleaseWithoutAsm|Itanium.ActiveCfg = Release|Itanium + {48CDD9DC-E09F-4135-9C0C-4FE50C3C654B}.ReleaseWithoutAsm|Itanium.Build.0 = Release|Itanium + {48CDD9DC-E09F-4135-9C0C-4FE50C3C654B}.ReleaseWithoutAsm|Win32.ActiveCfg = Release|Win32 + {48CDD9DC-E09F-4135-9C0C-4FE50C3C654B}.ReleaseWithoutAsm|x64.ActiveCfg = Release|x64 + {C52F9E7B-498A-42BE-8DB4-85A15694382A}.Debug|Itanium.ActiveCfg = Debug|Itanium + {C52F9E7B-498A-42BE-8DB4-85A15694382A}.Debug|Itanium.Build.0 = Debug|Itanium + {C52F9E7B-498A-42BE-8DB4-85A15694382A}.Debug|Win32.ActiveCfg = Debug|Win32 + {C52F9E7B-498A-42BE-8DB4-85A15694382A}.Debug|Win32.Build.0 = Debug|Win32 + {C52F9E7B-498A-42BE-8DB4-85A15694382A}.Debug|x64.ActiveCfg = Debug|x64 + {C52F9E7B-498A-42BE-8DB4-85A15694382A}.Debug|x64.Build.0 = Debug|x64 + {C52F9E7B-498A-42BE-8DB4-85A15694382A}.Release|Itanium.ActiveCfg = Release|Itanium + {C52F9E7B-498A-42BE-8DB4-85A15694382A}.Release|Itanium.Build.0 = Release|Itanium + {C52F9E7B-498A-42BE-8DB4-85A15694382A}.Release|Win32.ActiveCfg = Release|Win32 + {C52F9E7B-498A-42BE-8DB4-85A15694382A}.Release|Win32.Build.0 = Release|Win32 + {C52F9E7B-498A-42BE-8DB4-85A15694382A}.Release|x64.ActiveCfg = Release|x64 + {C52F9E7B-498A-42BE-8DB4-85A15694382A}.Release|x64.Build.0 = Release|x64 + {C52F9E7B-498A-42BE-8DB4-85A15694382A}.ReleaseWithoutAsm|Itanium.ActiveCfg = Release|Itanium + {C52F9E7B-498A-42BE-8DB4-85A15694382A}.ReleaseWithoutAsm|Itanium.Build.0 = Release|Itanium + {C52F9E7B-498A-42BE-8DB4-85A15694382A}.ReleaseWithoutAsm|Win32.ActiveCfg = Release|Win32 + {C52F9E7B-498A-42BE-8DB4-85A15694382A}.ReleaseWithoutAsm|x64.ActiveCfg = Release|x64 + EndGlobalSection + GlobalSection(SolutionProperties) = preSolution + HideSolutionNode = FALSE + EndGlobalSection +EndGlobal ADDED compat/zlib/contrib/vstudio/vc10/zlibvc.vcxproj Index: compat/zlib/contrib/vstudio/vc10/zlibvc.vcxproj ================================================================== --- /dev/null +++ compat/zlib/contrib/vstudio/vc10/zlibvc.vcxproj @@ -0,0 +1,657 @@ + + + + + Debug + Itanium + + + Debug + Win32 + + + Debug + x64 + + + ReleaseWithoutAsm + Itanium + + + ReleaseWithoutAsm + Win32 + + + ReleaseWithoutAsm + x64 + + + Release + Itanium + + + Release + Win32 + + + Release + x64 + + + + {8FD826F8-3739-44E6-8CC8-997122E53B8D} + + + + DynamicLibrary + false + true + + + DynamicLibrary + false + true + + + DynamicLibrary + false + + + DynamicLibrary + false + true + + + DynamicLibrary + false + true + + + DynamicLibrary + false + + + DynamicLibrary + false + true + + + DynamicLibrary + false + true + + + DynamicLibrary + false + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + <_ProjectFileVersion>10.0.30128.1 + x86\ZlibDll$(Configuration)\ + x86\ZlibDll$(Configuration)\Tmp\ + true + false + x86\ZlibDll$(Configuration)\ + x86\ZlibDll$(Configuration)\Tmp\ + false + false + x86\ZlibDll$(Configuration)\ + x86\ZlibDll$(Configuration)\Tmp\ + false + false + x64\ZlibDll$(Configuration)\ + x64\ZlibDll$(Configuration)\Tmp\ + true + false + ia64\ZlibDll$(Configuration)\ + ia64\ZlibDll$(Configuration)\Tmp\ + true + false + x64\ZlibDll$(Configuration)\ + x64\ZlibDll$(Configuration)\Tmp\ + false + false + ia64\ZlibDll$(Configuration)\ + ia64\ZlibDll$(Configuration)\Tmp\ + false + false + x64\ZlibDll$(Configuration)\ + x64\ZlibDll$(Configuration)\Tmp\ + false + false + ia64\ZlibDll$(Configuration)\ + ia64\ZlibDll$(Configuration)\Tmp\ + false + false + AllRules.ruleset + + + AllRules.ruleset + + + AllRules.ruleset + + + AllRules.ruleset + + + AllRules.ruleset + + + AllRules.ruleset + + + AllRules.ruleset + + + AllRules.ruleset + + + AllRules.ruleset + + + zlibwapid + zlibwapi + zlibwapi + zlibwapid + zlibwapi + zlibwapi + + + + _DEBUG;%(PreprocessorDefinitions) + true + true + Win32 + $(OutDir)zlibvc.tlb + + + Disabled + ..\..\..;..\..\masmx86;%(AdditionalIncludeDirectories) + WIN32;_CRT_NONSTDC_NO_DEPRECATE;_CRT_SECURE_NO_DEPRECATE;_CRT_NONSTDC_NO_WARNINGS;ZLIB_WINAPI;ASMV;ASMINF;%(PreprocessorDefinitions) + + + MultiThreadedDebug + false + $(IntDir)zlibvc.pch + $(IntDir) + $(IntDir) + $(OutDir) + + + Level3 + true + EditAndContinue + + + _DEBUG;%(PreprocessorDefinitions) + 0x040c + + + /MACHINE:I386 %(AdditionalOptions) + ..\..\masmx86\match686.obj;..\..\masmx86\inffas32.obj;%(AdditionalDependencies) + true + .\zlibvc.def + true + true + Windows + false + + + + + cd ..\..\masmx86 +bld_ml32.bat + + + + + NDEBUG;%(PreprocessorDefinitions) + true + true + Win32 + $(OutDir)zlibvc.tlb + + + OnlyExplicitInline + ..\..\..;..\..\masmx86;%(AdditionalIncludeDirectories) + WIN32;_CRT_NONSTDC_NO_DEPRECATE;_CRT_SECURE_NO_DEPRECATE;_CRT_NONSTDC_NO_WARNINGS;ZLIB_WINAPI;%(PreprocessorDefinitions) + true + + + MultiThreadedDLL + false + true + $(IntDir)zlibvc.pch + All + $(IntDir) + $(IntDir) + $(OutDir) + + + Level3 + true + + + NDEBUG;%(PreprocessorDefinitions) + 0x040c + + + /MACHINE:I386 %(AdditionalOptions) + true + false + .\zlibvc.def + true + Windows + false + + + + + + + NDEBUG;%(PreprocessorDefinitions) + true + true + Win32 + $(OutDir)zlibvc.tlb + + + OnlyExplicitInline + ..\..\..;..\..\masmx86;%(AdditionalIncludeDirectories) + WIN32;_CRT_NONSTDC_NO_DEPRECATE;_CRT_SECURE_NO_DEPRECATE;_CRT_NONSTDC_NO_WARNINGS;ZLIB_WINAPI;ASMV;ASMINF;%(PreprocessorDefinitions) + true + + + MultiThreaded + false + true + $(IntDir)zlibvc.pch + All + $(IntDir) + $(IntDir) + $(OutDir) + + + Level3 + true + + + NDEBUG;%(PreprocessorDefinitions) + 0x040c + + + /MACHINE:I386 %(AdditionalOptions) + ..\..\masmx86\match686.obj;..\..\masmx86\inffas32.obj;%(AdditionalDependencies) + true + false + .\zlibvc.def + true + Windows + false + + + + + cd ..\..\masmx86 +bld_ml32.bat + + + + + _DEBUG;%(PreprocessorDefinitions) + true + true + X64 + $(OutDir)zlibvc.tlb + + + Disabled + ..\..\..;..\..\masmx86;%(AdditionalIncludeDirectories) + WIN32;_CRT_NONSTDC_NO_DEPRECATE;_CRT_SECURE_NO_DEPRECATE;_CRT_NONSTDC_NO_WARNINGS;ZLIB_WINAPI;ASMV;ASMINF;WIN64;%(PreprocessorDefinitions) + + + MultiThreadedDebugDLL + false + $(IntDir)zlibvc.pch + $(IntDir) + $(IntDir) + $(OutDir) + + + Level3 + true + ProgramDatabase + + + _DEBUG;%(PreprocessorDefinitions) + 0x040c + + + ..\..\masmx64\gvmat64.obj;..\..\masmx64\inffasx64.obj;%(AdditionalDependencies) + true + .\zlibvc.def + true + true + Windows + MachineX64 + + + cd ..\..\masmx64 +bld_ml64.bat + + + + + _DEBUG;%(PreprocessorDefinitions) + true + true + Itanium + $(OutDir)zlibvc.tlb + + + Disabled + ..\..\..;..\..\masmx86;%(AdditionalIncludeDirectories) + WIN32;_CRT_NONSTDC_NO_DEPRECATE;_CRT_SECURE_NO_DEPRECATE;_CRT_NONSTDC_NO_WARNINGS;ZLIB_WINAPI;WIN64;%(PreprocessorDefinitions) + + + MultiThreadedDebugDLL + false + $(IntDir)zlibvc.pch + $(IntDir) + $(IntDir) + $(OutDir) + + + Level3 + true + ProgramDatabase + + + _DEBUG;%(PreprocessorDefinitions) + 0x040c + + + $(OutDir)zlibwapi.dll + true + .\zlibvc.def + true + $(OutDir)zlibwapi.pdb + true + $(OutDir)zlibwapi.map + Windows + $(OutDir)zlibwapi.lib + MachineIA64 + + + + + NDEBUG;%(PreprocessorDefinitions) + true + true + X64 + $(OutDir)zlibvc.tlb + + + OnlyExplicitInline + ..\..\..;..\..\masmx86;%(AdditionalIncludeDirectories) + WIN32;_CRT_NONSTDC_NO_DEPRECATE;_CRT_SECURE_NO_DEPRECATE;_CRT_NONSTDC_NO_WARNINGS;ZLIB_WINAPI;WIN64;%(PreprocessorDefinitions) + true + + + MultiThreadedDLL + false + true + $(IntDir)zlibvc.pch + All + $(IntDir) + $(IntDir) + $(OutDir) + + + Level3 + true + + + NDEBUG;%(PreprocessorDefinitions) + 0x040c + + + true + false + .\zlibvc.def + true + Windows + MachineX64 + + + + + NDEBUG;%(PreprocessorDefinitions) + true + true + Itanium + $(OutDir)zlibvc.tlb + + + OnlyExplicitInline + ..\..\..;..\..\masmx86;%(AdditionalIncludeDirectories) + WIN32;_CRT_NONSTDC_NO_DEPRECATE;_CRT_SECURE_NO_DEPRECATE;_CRT_NONSTDC_NO_WARNINGS;ZLIB_WINAPI;WIN64;%(PreprocessorDefinitions) + true + + + MultiThreadedDLL + false + true + $(IntDir)zlibvc.pch + All + $(IntDir) + $(IntDir) + $(OutDir) + + + Level3 + true + + + NDEBUG;%(PreprocessorDefinitions) + 0x040c + + + $(OutDir)zlibwapi.dll + true + false + .\zlibvc.def + $(OutDir)zlibwapi.pdb + true + $(OutDir)zlibwapi.map + Windows + $(OutDir)zlibwapi.lib + MachineIA64 + + + + + NDEBUG;%(PreprocessorDefinitions) + true + true + X64 + $(OutDir)zlibvc.tlb + + + OnlyExplicitInline + ..\..\..;..\..\masmx86;%(AdditionalIncludeDirectories) + _CRT_NONSTDC_NO_DEPRECATE;_CRT_SECURE_NO_DEPRECATE;_CRT_NONSTDC_NO_WARNINGS;ZLIB_WINAPI;ASMV;ASMINF;WIN64;%(PreprocessorDefinitions) + true + + + MultiThreadedDLL + false + true + $(IntDir)zlibvc.pch + All + $(IntDir) + $(IntDir) + $(OutDir) + + + Level3 + true + + + NDEBUG;%(PreprocessorDefinitions) + 0x040c + + + ..\..\masmx64\gvmat64.obj;..\..\masmx64\inffasx64.obj;%(AdditionalDependencies) + true + false + .\zlibvc.def + true + Windows + MachineX64 + + + cd ..\..\masmx64 +bld_ml64.bat + + + + + NDEBUG;%(PreprocessorDefinitions) + true + true + Itanium + $(OutDir)zlibvc.tlb + + + OnlyExplicitInline + ..\..\..;..\..\masmx86;%(AdditionalIncludeDirectories) + _CRT_NONSTDC_NO_DEPRECATE;_CRT_SECURE_NO_DEPRECATE;_CRT_NONSTDC_NO_WARNINGS;ZLIB_WINAPI;WIN64;%(PreprocessorDefinitions) + true + + + MultiThreadedDLL + false + true + $(IntDir)zlibvc.pch + All + $(IntDir) + $(IntDir) + $(OutDir) + + + Level3 + true + + + NDEBUG;%(PreprocessorDefinitions) + 0x040c + + + $(OutDir)zlibwapi.dll + true + false + .\zlibvc.def + $(OutDir)zlibwapi.pdb + true + $(OutDir)zlibwapi.map + Windows + $(OutDir)zlibwapi.lib + MachineIA64 + + + + + + + + + + + + + + true + true + true + true + true + true + + + + + + + + + + %(AdditionalIncludeDirectories) + ZLIB_INTERNAL;%(PreprocessorDefinitions) + %(AdditionalIncludeDirectories) + ZLIB_INTERNAL;%(PreprocessorDefinitions) + %(AdditionalIncludeDirectories) + ZLIB_INTERNAL;%(PreprocessorDefinitions) + + + %(AdditionalIncludeDirectories) + ZLIB_INTERNAL;%(PreprocessorDefinitions) + %(AdditionalIncludeDirectories) + ZLIB_INTERNAL;%(PreprocessorDefinitions) + %(AdditionalIncludeDirectories) + ZLIB_INTERNAL;%(PreprocessorDefinitions) + + + + + + + + + + + + + + + + + + + + + + + + ADDED compat/zlib/contrib/vstudio/vc10/zlibvc.vcxproj.filters Index: compat/zlib/contrib/vstudio/vc10/zlibvc.vcxproj.filters ================================================================== --- /dev/null +++ compat/zlib/contrib/vstudio/vc10/zlibvc.vcxproj.filters @@ -0,0 +1,118 @@ + + + + + {07934a85-8b61-443d-a0ee-b2eedb74f3cd} + cpp;c;cxx;rc;def;r;odl;hpj;bat;for;f90 + + + {1d99675b-433d-4a21-9e50-ed4ab8b19762} + h;hpp;hxx;hm;inl;fi;fd + + + {431c0958-fa71-44d0-9084-2d19d100c0cc} + ico;cur;bmp;dlg;rc2;rct;bin;cnt;rtf;gif;jpg;jpeg;jpe + + + + + Source Files + + + Source Files + + + Source Files + + + Source Files + + + Source Files + + + Source Files + + + Source Files + + + Source Files + + + Source Files + + + Source Files + + + Source Files + + + Source Files + + + Source Files + + + Source Files + + + Source Files + + + Source Files + + + Source Files + + + Source Files + + + Source Files + + + Source Files + + + + + Source Files + + + + + Source Files + + + + + Header Files + + + Header Files + + + Header Files + + + Header Files + + + Header Files + + + Header Files + + + Header Files + + + Header Files + + + Header Files + + + ADDED compat/zlib/contrib/vstudio/vc11/miniunz.vcxproj Index: compat/zlib/contrib/vstudio/vc11/miniunz.vcxproj ================================================================== --- /dev/null +++ compat/zlib/contrib/vstudio/vc11/miniunz.vcxproj @@ -0,0 +1,314 @@ + + + + + Debug + Itanium + + + Debug + Win32 + + + Debug + x64 + + + Release + Itanium + + + Release + Win32 + + + Release + x64 + + + + {C52F9E7B-498A-42BE-8DB4-85A15694382A} + Win32Proj + + + + Application + MultiByte + v110 + + + Application + Unicode + v110 + + + Application + MultiByte + + + Application + MultiByte + + + Application + MultiByte + v110 + + + Application + MultiByte + v110 + + + + + + + + + + + + + + + + + + + + + + + + + <_ProjectFileVersion>10.0.30128.1 + x86\MiniUnzip$(Configuration)\ + x86\MiniUnzip$(Configuration)\Tmp\ + true + false + x86\MiniUnzip$(Configuration)\ + x86\MiniUnzip$(Configuration)\Tmp\ + false + false + x64\MiniUnzip$(Configuration)\ + x64\MiniUnzip$(Configuration)\Tmp\ + true + false + ia64\MiniUnzip$(Configuration)\ + ia64\MiniUnzip$(Configuration)\Tmp\ + true + false + x64\MiniUnzip$(Configuration)\ + x64\MiniUnzip$(Configuration)\Tmp\ + false + false + ia64\MiniUnzip$(Configuration)\ + ia64\MiniUnzip$(Configuration)\Tmp\ + false + false + AllRules.ruleset + + + AllRules.ruleset + + + AllRules.ruleset + + + AllRules.ruleset + + + AllRules.ruleset + + + AllRules.ruleset + + + + + + Disabled + ..\..\..;..\..\minizip;%(AdditionalIncludeDirectories) + WIN32;_CRT_NONSTDC_NO_DEPRECATE;_CRT_SECURE_NO_DEPRECATE;ZLIB_WINAPI;_DEBUG;_CONSOLE;%(PreprocessorDefinitions) + true + Default + MultiThreadedDebugDLL + false + + + $(IntDir) + Level3 + ProgramDatabase + + + x86\ZlibDllDebug\zlibwapi.lib;%(AdditionalDependencies) + $(OutDir)miniunz.exe + true + $(OutDir)miniunz.pdb + Console + false + + + MachineX86 + + + + + MaxSpeed + OnlyExplicitInline + true + ..\..\..;..\..\minizip;%(AdditionalIncludeDirectories) + WIN32;_CRT_NONSTDC_NO_DEPRECATE;_CRT_SECURE_NO_DEPRECATE;ZLIB_WINAPI;NDEBUG;_CONSOLE;%(PreprocessorDefinitions) + true + Default + MultiThreaded + false + true + + + $(IntDir) + Level3 + ProgramDatabase + + + x86\ZlibDllRelease\zlibwapi.lib;%(AdditionalDependencies) + $(OutDir)miniunz.exe + true + Console + true + true + false + + + MachineX86 + + + + + X64 + + + Disabled + ..\..\..;..\..\minizip;%(AdditionalIncludeDirectories) + _CRT_NONSTDC_NO_DEPRECATE;_CRT_SECURE_NO_DEPRECATE;ZLIB_WINAPI;_DEBUG;_CONSOLE;WIN64;%(PreprocessorDefinitions) + true + Default + MultiThreadedDebugDLL + false + + + $(IntDir) + Level3 + ProgramDatabase + + + x64\ZlibDllDebug\zlibwapi.lib;%(AdditionalDependencies) + $(OutDir)miniunz.exe + true + $(OutDir)miniunz.pdb + Console + MachineX64 + + + + + Itanium + + + Disabled + ..\..\..;..\..\minizip;%(AdditionalIncludeDirectories) + _CRT_NONSTDC_NO_DEPRECATE;_CRT_SECURE_NO_DEPRECATE;ZLIB_WINAPI;_DEBUG;_CONSOLE;WIN64;%(PreprocessorDefinitions) + true + Default + MultiThreadedDebugDLL + false + + + $(IntDir) + Level3 + ProgramDatabase + + + ia64\ZlibDllDebug\zlibwapi.lib;%(AdditionalDependencies) + $(OutDir)miniunz.exe + true + $(OutDir)miniunz.pdb + Console + MachineIA64 + + + + + X64 + + + MaxSpeed + OnlyExplicitInline + true + ..\..\..;..\..\minizip;%(AdditionalIncludeDirectories) + _CRT_NONSTDC_NO_DEPRECATE;_CRT_SECURE_NO_DEPRECATE;ZLIB_WINAPI;NDEBUG;_CONSOLE;WIN64;%(PreprocessorDefinitions) + true + Default + MultiThreadedDLL + false + true + + + $(IntDir) + Level3 + ProgramDatabase + + + x64\ZlibDllRelease\zlibwapi.lib;%(AdditionalDependencies) + $(OutDir)miniunz.exe + true + Console + true + true + MachineX64 + + + + + Itanium + + + MaxSpeed + OnlyExplicitInline + true + ..\..\..;..\..\minizip;%(AdditionalIncludeDirectories) + _CRT_NONSTDC_NO_DEPRECATE;_CRT_SECURE_NO_DEPRECATE;ZLIB_WINAPI;NDEBUG;_CONSOLE;WIN64;%(PreprocessorDefinitions) + true + Default + MultiThreadedDLL + false + true + + + $(IntDir) + Level3 + ProgramDatabase + + + ia64\ZlibDllRelease\zlibwapi.lib;%(AdditionalDependencies) + $(OutDir)miniunz.exe + true + Console + true + true + MachineIA64 + + + + + + + + {8fd826f8-3739-44e6-8cc8-997122e53b8d} + + + + + + ADDED compat/zlib/contrib/vstudio/vc11/minizip.vcxproj Index: compat/zlib/contrib/vstudio/vc11/minizip.vcxproj ================================================================== --- /dev/null +++ compat/zlib/contrib/vstudio/vc11/minizip.vcxproj @@ -0,0 +1,311 @@ + + + + + Debug + Itanium + + + Debug + Win32 + + + Debug + x64 + + + Release + Itanium + + + Release + Win32 + + + Release + x64 + + + + {48CDD9DC-E09F-4135-9C0C-4FE50C3C654B} + Win32Proj + + + + Application + MultiByte + v110 + + + Application + Unicode + v110 + + + Application + MultiByte + + + Application + MultiByte + + + Application + MultiByte + v110 + + + Application + MultiByte + v110 + + + + + + + + + + + + + + + + + + + + + + + + + <_ProjectFileVersion>10.0.30128.1 + x86\MiniZip$(Configuration)\ + x86\MiniZip$(Configuration)\Tmp\ + true + false + x86\MiniZip$(Configuration)\ + x86\MiniZip$(Configuration)\Tmp\ + false + x64\$(Configuration)\ + x64\$(Configuration)\ + true + false + ia64\$(Configuration)\ + ia64\$(Configuration)\ + true + false + x64\$(Configuration)\ + x64\$(Configuration)\ + false + ia64\$(Configuration)\ + ia64\$(Configuration)\ + false + AllRules.ruleset + + + AllRules.ruleset + + + AllRules.ruleset + + + AllRules.ruleset + + + AllRules.ruleset + + + AllRules.ruleset + + + + + + Disabled + ..\..\..;..\..\minizip;%(AdditionalIncludeDirectories) + WIN32;_CRT_NONSTDC_NO_DEPRECATE;_CRT_SECURE_NO_DEPRECATE;ZLIB_WINAPI;_DEBUG;_CONSOLE;%(PreprocessorDefinitions) + true + Default + MultiThreadedDebugDLL + false + + + $(IntDir) + Level3 + ProgramDatabase + + + x86\ZlibDllDebug\zlibwapi.lib;%(AdditionalDependencies) + $(OutDir)minizip.exe + true + $(OutDir)minizip.pdb + Console + false + + + MachineX86 + + + + + MaxSpeed + OnlyExplicitInline + true + ..\..\..;..\..\minizip;%(AdditionalIncludeDirectories) + WIN32;_CRT_NONSTDC_NO_DEPRECATE;_CRT_SECURE_NO_DEPRECATE;ZLIB_WINAPI;NDEBUG;_CONSOLE;%(PreprocessorDefinitions) + true + Default + MultiThreaded + false + true + + + $(IntDir) + Level3 + ProgramDatabase + + + x86\ZlibDllRelease\zlibwapi.lib;%(AdditionalDependencies) + $(OutDir)minizip.exe + true + Console + true + true + false + + + MachineX86 + + + + + X64 + + + Disabled + ..\..\..;..\..\minizip;%(AdditionalIncludeDirectories) + _CRT_NONSTDC_NO_DEPRECATE;_CRT_SECURE_NO_DEPRECATE;ZLIB_WINAPI;_DEBUG;_CONSOLE;WIN64;%(PreprocessorDefinitions) + true + Default + MultiThreadedDebugDLL + false + + + $(IntDir) + Level3 + ProgramDatabase + + + x64\ZlibDllDebug\zlibwapi.lib;%(AdditionalDependencies) + $(OutDir)minizip.exe + true + $(OutDir)minizip.pdb + Console + MachineX64 + + + + + Itanium + + + Disabled + ..\..\..;..\..\minizip;%(AdditionalIncludeDirectories) + _CRT_NONSTDC_NO_DEPRECATE;_CRT_SECURE_NO_DEPRECATE;ZLIB_WINAPI;_DEBUG;_CONSOLE;WIN64;%(PreprocessorDefinitions) + true + Default + MultiThreadedDebugDLL + false + + + $(IntDir) + Level3 + ProgramDatabase + + + ia64\ZlibDllDebug\zlibwapi.lib;%(AdditionalDependencies) + $(OutDir)minizip.exe + true + $(OutDir)minizip.pdb + Console + MachineIA64 + + + + + X64 + + + MaxSpeed + OnlyExplicitInline + true + ..\..\..;..\..\minizip;%(AdditionalIncludeDirectories) + _CRT_NONSTDC_NO_DEPRECATE;_CRT_SECURE_NO_DEPRECATE;ZLIB_WINAPI;NDEBUG;_CONSOLE;WIN64;%(PreprocessorDefinitions) + true + Default + MultiThreadedDLL + false + true + + + $(IntDir) + Level3 + ProgramDatabase + + + x64\ZlibDllRelease\zlibwapi.lib;%(AdditionalDependencies) + $(OutDir)minizip.exe + true + Console + true + true + MachineX64 + + + + + Itanium + + + MaxSpeed + OnlyExplicitInline + true + ..\..\..;..\..\minizip;%(AdditionalIncludeDirectories) + _CRT_NONSTDC_NO_DEPRECATE;_CRT_SECURE_NO_DEPRECATE;ZLIB_WINAPI;NDEBUG;_CONSOLE;WIN64;%(PreprocessorDefinitions) + true + Default + MultiThreadedDLL + false + true + + + $(IntDir) + Level3 + ProgramDatabase + + + ia64\ZlibDllRelease\zlibwapi.lib;%(AdditionalDependencies) + $(OutDir)minizip.exe + true + Console + true + true + MachineIA64 + + + + + + + + {8fd826f8-3739-44e6-8cc8-997122e53b8d} + + + + + + ADDED compat/zlib/contrib/vstudio/vc11/testzlib.vcxproj Index: compat/zlib/contrib/vstudio/vc11/testzlib.vcxproj ================================================================== --- /dev/null +++ compat/zlib/contrib/vstudio/vc11/testzlib.vcxproj @@ -0,0 +1,426 @@ + + + + + Debug + Itanium + + + Debug + Win32 + + + Debug + x64 + + + ReleaseWithoutAsm + Itanium + + + ReleaseWithoutAsm + Win32 + + + ReleaseWithoutAsm + x64 + + + Release + Itanium + + + Release + Win32 + + + Release + x64 + + + + {AA6666AA-E09F-4135-9C0C-4FE50C3C654B} + testzlib + Win32Proj + + + + Application + MultiByte + true + v110 + + + Application + MultiByte + true + v110 + + + Application + Unicode + v110 + + + Application + MultiByte + true + + + Application + MultiByte + true + + + Application + MultiByte + + + Application + true + v110 + + + Application + true + v110 + + + Application + v110 + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + <_ProjectFileVersion>10.0.30128.1 + x86\TestZlib$(Configuration)\ + x86\TestZlib$(Configuration)\Tmp\ + true + false + x86\TestZlib$(Configuration)\ + x86\TestZlib$(Configuration)\Tmp\ + false + false + x86\TestZlib$(Configuration)\ + x86\TestZlib$(Configuration)\Tmp\ + false + false + x64\TestZlib$(Configuration)\ + x64\TestZlib$(Configuration)\Tmp\ + false + ia64\TestZlib$(Configuration)\ + ia64\TestZlib$(Configuration)\Tmp\ + true + false + x64\TestZlib$(Configuration)\ + x64\TestZlib$(Configuration)\Tmp\ + false + ia64\TestZlib$(Configuration)\ + ia64\TestZlib$(Configuration)\Tmp\ + false + false + x64\TestZlib$(Configuration)\ + x64\TestZlib$(Configuration)\Tmp\ + false + ia64\TestZlib$(Configuration)\ + ia64\TestZlib$(Configuration)\Tmp\ + false + false + AllRules.ruleset + + + AllRules.ruleset + + + AllRules.ruleset + + + AllRules.ruleset + + + AllRules.ruleset + + + AllRules.ruleset + + + AllRules.ruleset + + + AllRules.ruleset + + + AllRules.ruleset + + + + + + Disabled + ..\..\..;%(AdditionalIncludeDirectories) + ASMV;ASMINF;WIN32;ZLIB_WINAPI;_DEBUG;_CONSOLE;_CRT_NONSTDC_NO_DEPRECATE;_CRT_SECURE_NO_DEPRECATE;_CRT_NONSTDC_NO_WARNINGS;%(PreprocessorDefinitions) + true + Default + MultiThreadedDebugDLL + false + + + AssemblyAndSourceCode + $(IntDir) + Level3 + ProgramDatabase + + + ..\..\masmx86\match686.obj;..\..\masmx86\inffas32.obj;%(AdditionalDependencies) + $(OutDir)testzlib.exe + true + $(OutDir)testzlib.pdb + Console + false + + + MachineX86 + + + + + MaxSpeed + OnlyExplicitInline + true + ..\..\..;%(AdditionalIncludeDirectories) + WIN32;ZLIB_WINAPI;NDEBUG;_CONSOLE;_CRT_NONSTDC_NO_DEPRECATE;_CRT_SECURE_NO_DEPRECATE;_CRT_NONSTDC_NO_WARNINGS;%(PreprocessorDefinitions) + true + Default + MultiThreaded + false + true + + + $(IntDir) + Level3 + ProgramDatabase + + + $(OutDir)testzlib.exe + true + Console + true + true + false + + + MachineX86 + + + + + MaxSpeed + OnlyExplicitInline + true + ..\..\..;%(AdditionalIncludeDirectories) + ASMV;ASMINF;WIN32;ZLIB_WINAPI;NDEBUG;_CONSOLE;_CRT_NONSTDC_NO_DEPRECATE;_CRT_SECURE_NO_DEPRECATE;_CRT_NONSTDC_NO_WARNINGS;%(PreprocessorDefinitions) + true + Default + MultiThreaded + false + true + + + $(IntDir) + Level3 + ProgramDatabase + + + ..\..\masmx86\match686.obj;..\..\masmx86\inffas32.obj;%(AdditionalDependencies) + $(OutDir)testzlib.exe + true + Console + true + true + false + + + MachineX86 + + + + + ..\..\..;%(AdditionalIncludeDirectories) + ASMV;ASMINF;WIN32;ZLIB_WINAPI;_DEBUG;_CONSOLE;_CRT_NONSTDC_NO_DEPRECATE;_CRT_SECURE_NO_DEPRECATE;_CRT_NONSTDC_NO_WARNINGS;%(PreprocessorDefinitions) + Default + MultiThreadedDebugDLL + false + $(IntDir) + + + ..\..\masmx64\gvmat64.obj;..\..\masmx64\inffasx64.obj;%(AdditionalDependencies) + + + + + Itanium + + + Disabled + ..\..\..;%(AdditionalIncludeDirectories) + ZLIB_WINAPI;_DEBUG;_CONSOLE;_CRT_NONSTDC_NO_DEPRECATE;_CRT_SECURE_NO_DEPRECATE;_CRT_NONSTDC_NO_WARNINGS;WIN64;%(PreprocessorDefinitions) + true + Default + MultiThreadedDebugDLL + false + + + AssemblyAndSourceCode + $(IntDir) + Level3 + ProgramDatabase + + + $(OutDir)testzlib.exe + true + $(OutDir)testzlib.pdb + Console + MachineIA64 + + + + + ..\..\..;%(AdditionalIncludeDirectories) + WIN32;ZLIB_WINAPI;NDEBUG;_CONSOLE;_CRT_NONSTDC_NO_DEPRECATE;_CRT_SECURE_NO_DEPRECATE;_CRT_NONSTDC_NO_WARNINGS;%(PreprocessorDefinitions) + Default + MultiThreadedDLL + false + $(IntDir) + + + %(AdditionalDependencies) + + + + + Itanium + + + MaxSpeed + OnlyExplicitInline + true + ..\..\..;%(AdditionalIncludeDirectories) + ZLIB_WINAPI;NDEBUG;_CONSOLE;_CRT_NONSTDC_NO_DEPRECATE;_CRT_SECURE_NO_DEPRECATE;_CRT_NONSTDC_NO_WARNINGS;WIN64;%(PreprocessorDefinitions) + true + Default + MultiThreadedDLL + false + true + + + $(IntDir) + Level3 + ProgramDatabase + + + $(OutDir)testzlib.exe + true + Console + true + true + MachineIA64 + + + + + ..\..\..;%(AdditionalIncludeDirectories) + ASMV;ASMINF;WIN32;ZLIB_WINAPI;NDEBUG;_CONSOLE;_CRT_NONSTDC_NO_DEPRECATE;_CRT_SECURE_NO_DEPRECATE;_CRT_NONSTDC_NO_WARNINGS;%(PreprocessorDefinitions) + Default + MultiThreadedDLL + false + $(IntDir) + + + ..\..\masmx64\gvmat64.obj;..\..\masmx64\inffasx64.obj;%(AdditionalDependencies) + + + + + Itanium + + + MaxSpeed + OnlyExplicitInline + true + ..\..\..;%(AdditionalIncludeDirectories) + ZLIB_WINAPI;NDEBUG;_CONSOLE;_CRT_NONSTDC_NO_DEPRECATE;_CRT_SECURE_NO_DEPRECATE;_CRT_NONSTDC_NO_WARNINGS;WIN64;%(PreprocessorDefinitions) + true + Default + MultiThreadedDLL + false + true + + + $(IntDir) + Level3 + ProgramDatabase + + + $(OutDir)testzlib.exe + true + Console + true + true + MachineIA64 + + + + + + + + + + true + true + true + true + true + true + + + + + + + + + + + + + ADDED compat/zlib/contrib/vstudio/vc11/testzlibdll.vcxproj Index: compat/zlib/contrib/vstudio/vc11/testzlibdll.vcxproj ================================================================== --- /dev/null +++ compat/zlib/contrib/vstudio/vc11/testzlibdll.vcxproj @@ -0,0 +1,314 @@ + + + + + Debug + Itanium + + + Debug + Win32 + + + Debug + x64 + + + Release + Itanium + + + Release + Win32 + + + Release + x64 + + + + {C52F9E7B-498A-42BE-8DB4-85A15694366A} + Win32Proj + + + + Application + MultiByte + v110 + + + Application + Unicode + v110 + + + Application + MultiByte + + + Application + MultiByte + + + Application + MultiByte + v110 + + + Application + MultiByte + v110 + + + + + + + + + + + + + + + + + + + + + + + + + <_ProjectFileVersion>10.0.30128.1 + x86\TestZlibDll$(Configuration)\ + x86\TestZlibDll$(Configuration)\Tmp\ + true + false + x86\TestZlibDll$(Configuration)\ + x86\TestZlibDll$(Configuration)\Tmp\ + false + false + x64\TestZlibDll$(Configuration)\ + x64\TestZlibDll$(Configuration)\Tmp\ + true + false + ia64\TestZlibDll$(Configuration)\ + ia64\TestZlibDll$(Configuration)\Tmp\ + true + false + x64\TestZlibDll$(Configuration)\ + x64\TestZlibDll$(Configuration)\Tmp\ + false + false + ia64\TestZlibDll$(Configuration)\ + ia64\TestZlibDll$(Configuration)\Tmp\ + false + false + AllRules.ruleset + + + AllRules.ruleset + + + AllRules.ruleset + + + AllRules.ruleset + + + AllRules.ruleset + + + AllRules.ruleset + + + + + + Disabled + ..\..\..;..\..\minizip;%(AdditionalIncludeDirectories) + WIN32;_CRT_NONSTDC_NO_DEPRECATE;_CRT_SECURE_NO_DEPRECATE;ZLIB_WINAPI;_DEBUG;_CONSOLE;%(PreprocessorDefinitions) + true + Default + MultiThreadedDebugDLL + false + + + $(IntDir) + Level3 + ProgramDatabase + + + x86\ZlibDllDebug\zlibwapi.lib;%(AdditionalDependencies) + $(OutDir)testzlibdll.exe + true + $(OutDir)testzlib.pdb + Console + false + + + MachineX86 + + + + + MaxSpeed + OnlyExplicitInline + true + ..\..\..;..\..\minizip;%(AdditionalIncludeDirectories) + WIN32;_CRT_NONSTDC_NO_DEPRECATE;_CRT_SECURE_NO_DEPRECATE;ZLIB_WINAPI;NDEBUG;_CONSOLE;%(PreprocessorDefinitions) + true + Default + MultiThreaded + false + true + + + $(IntDir) + Level3 + ProgramDatabase + + + x86\ZlibDllRelease\zlibwapi.lib;%(AdditionalDependencies) + $(OutDir)testzlibdll.exe + true + Console + true + true + false + + + MachineX86 + + + + + X64 + + + Disabled + ..\..\..;..\..\minizip;%(AdditionalIncludeDirectories) + _CRT_NONSTDC_NO_DEPRECATE;_CRT_SECURE_NO_DEPRECATE;ZLIB_WINAPI;_DEBUG;_CONSOLE;WIN64;%(PreprocessorDefinitions) + true + Default + MultiThreadedDebugDLL + false + + + $(IntDir) + Level3 + ProgramDatabase + + + x64\ZlibDllDebug\zlibwapi.lib;%(AdditionalDependencies) + $(OutDir)testzlibdll.exe + true + $(OutDir)testzlib.pdb + Console + MachineX64 + + + + + Itanium + + + Disabled + ..\..\..;..\..\minizip;%(AdditionalIncludeDirectories) + _CRT_NONSTDC_NO_DEPRECATE;_CRT_SECURE_NO_DEPRECATE;ZLIB_WINAPI;_DEBUG;_CONSOLE;WIN64;%(PreprocessorDefinitions) + true + Default + MultiThreadedDebugDLL + false + + + $(IntDir) + Level3 + ProgramDatabase + + + ia64\ZlibDllDebug\zlibwapi.lib;%(AdditionalDependencies) + $(OutDir)testzlibdll.exe + true + $(OutDir)testzlib.pdb + Console + MachineIA64 + + + + + X64 + + + MaxSpeed + OnlyExplicitInline + true + ..\..\..;..\..\minizip;%(AdditionalIncludeDirectories) + _CRT_NONSTDC_NO_DEPRECATE;_CRT_SECURE_NO_DEPRECATE;ZLIB_WINAPI;NDEBUG;_CONSOLE;WIN64;%(PreprocessorDefinitions) + true + Default + MultiThreadedDLL + false + true + + + $(IntDir) + Level3 + ProgramDatabase + + + x64\ZlibDllRelease\zlibwapi.lib;%(AdditionalDependencies) + $(OutDir)testzlibdll.exe + true + Console + true + true + MachineX64 + + + + + Itanium + + + MaxSpeed + OnlyExplicitInline + true + ..\..\..;..\..\minizip;%(AdditionalIncludeDirectories) + _CRT_NONSTDC_NO_DEPRECATE;_CRT_SECURE_NO_DEPRECATE;ZLIB_WINAPI;NDEBUG;_CONSOLE;WIN64;%(PreprocessorDefinitions) + true + Default + MultiThreadedDLL + false + true + + + $(IntDir) + Level3 + ProgramDatabase + + + ia64\ZlibDllRelease\zlibwapi.lib;%(AdditionalDependencies) + $(OutDir)testzlibdll.exe + true + Console + true + true + MachineIA64 + + + + + + + + {8fd826f8-3739-44e6-8cc8-997122e53b8d} + + + + + + ADDED compat/zlib/contrib/vstudio/vc11/zlib.rc Index: compat/zlib/contrib/vstudio/vc11/zlib.rc ================================================================== --- /dev/null +++ compat/zlib/contrib/vstudio/vc11/zlib.rc @@ -0,0 +1,32 @@ +#include + +#define IDR_VERSION1 1 +IDR_VERSION1 VERSIONINFO MOVEABLE IMPURE LOADONCALL DISCARDABLE + FILEVERSION 1, 2, 11, 0 + PRODUCTVERSION 1, 2, 11, 0 + FILEFLAGSMASK VS_FFI_FILEFLAGSMASK + FILEFLAGS 0 + FILEOS VOS_DOS_WINDOWS32 + FILETYPE VFT_DLL + FILESUBTYPE 0 // not used +BEGIN + BLOCK "StringFileInfo" + BEGIN + BLOCK "040904E4" + //language ID = U.S. English, char set = Windows, Multilingual + + BEGIN + VALUE "FileDescription", "zlib data compression and ZIP file I/O library\0" + VALUE "FileVersion", "1.2.11\0" + VALUE "InternalName", "zlib\0" + VALUE "OriginalFilename", "zlibwapi.dll\0" + VALUE "ProductName", "ZLib.DLL\0" + VALUE "Comments","DLL support by Alessandro Iacopetti & Gilles Vollant\0" + VALUE "LegalCopyright", "(C) 1995-2017 Jean-loup Gailly & Mark Adler\0" + END + END + BLOCK "VarFileInfo" + BEGIN + VALUE "Translation", 0x0409, 1252 + END +END ADDED compat/zlib/contrib/vstudio/vc11/zlibstat.vcxproj Index: compat/zlib/contrib/vstudio/vc11/zlibstat.vcxproj ================================================================== --- /dev/null +++ compat/zlib/contrib/vstudio/vc11/zlibstat.vcxproj @@ -0,0 +1,464 @@ + + + + + Debug + Itanium + + + Debug + Win32 + + + Debug + x64 + + + ReleaseWithoutAsm + Itanium + + + ReleaseWithoutAsm + Win32 + + + ReleaseWithoutAsm + x64 + + + Release + Itanium + + + Release + Win32 + + + Release + x64 + + + + {745DEC58-EBB3-47A9-A9B8-4C6627C01BF8} + + + + StaticLibrary + false + v110 + + + StaticLibrary + false + v110 + + + StaticLibrary + false + v110 + Unicode + + + StaticLibrary + false + + + StaticLibrary + false + + + StaticLibrary + false + + + StaticLibrary + false + v110 + + + StaticLibrary + false + v110 + + + StaticLibrary + false + v110 + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + <_ProjectFileVersion>10.0.30128.1 + x86\ZlibStat$(Configuration)\ + x86\ZlibStat$(Configuration)\Tmp\ + x86\ZlibStat$(Configuration)\ + x86\ZlibStat$(Configuration)\Tmp\ + x86\ZlibStat$(Configuration)\ + x86\ZlibStat$(Configuration)\Tmp\ + x64\ZlibStat$(Configuration)\ + x64\ZlibStat$(Configuration)\Tmp\ + ia64\ZlibStat$(Configuration)\ + ia64\ZlibStat$(Configuration)\Tmp\ + x64\ZlibStat$(Configuration)\ + x64\ZlibStat$(Configuration)\Tmp\ + ia64\ZlibStat$(Configuration)\ + ia64\ZlibStat$(Configuration)\Tmp\ + x64\ZlibStat$(Configuration)\ + x64\ZlibStat$(Configuration)\Tmp\ + ia64\ZlibStat$(Configuration)\ + ia64\ZlibStat$(Configuration)\Tmp\ + AllRules.ruleset + + + AllRules.ruleset + + + AllRules.ruleset + + + AllRules.ruleset + + + AllRules.ruleset + + + AllRules.ruleset + + + AllRules.ruleset + + + AllRules.ruleset + + + AllRules.ruleset + + + + + + Disabled + ..\..\..;..\..\masmx86;%(AdditionalIncludeDirectories) + WIN32;ZLIB_WINAPI;_CRT_NONSTDC_NO_DEPRECATE;_CRT_SECURE_NO_DEPRECATE;_CRT_NONSTDC_NO_WARNINGS;%(PreprocessorDefinitions) + + + MultiThreadedDebugDLL + false + $(IntDir)zlibstat.pch + $(IntDir) + $(IntDir) + $(OutDir) + Level3 + true + OldStyle + + + 0x040c + + + /MACHINE:X86 /NODEFAULTLIB %(AdditionalOptions) + $(OutDir)zlibstat.lib + true + + + + + OnlyExplicitInline + ..\..\..;..\..\masmx86;%(AdditionalIncludeDirectories) + WIN32;ZLIB_WINAPI;_CRT_NONSTDC_NO_DEPRECATE;_CRT_SECURE_NO_DEPRECATE;_CRT_NONSTDC_NO_WARNINGS;ASMV;ASMINF;%(PreprocessorDefinitions) + true + + + MultiThreaded + false + true + $(IntDir)zlibstat.pch + $(IntDir) + $(IntDir) + $(OutDir) + Level3 + true + + + 0x040c + + + /MACHINE:X86 /NODEFAULTLIB %(AdditionalOptions) + ..\..\masmx86\match686.obj;..\..\masmx86\inffas32.obj;%(AdditionalDependencies) + $(OutDir)zlibstat.lib + true + + + + + OnlyExplicitInline + ..\..\..;..\..\masmx86;%(AdditionalIncludeDirectories) + WIN32;ZLIB_WINAPI;_CRT_NONSTDC_NO_DEPRECATE;_CRT_SECURE_NO_DEPRECATE;_CRT_NONSTDC_NO_WARNINGS;%(PreprocessorDefinitions) + true + + + MultiThreaded + false + true + $(IntDir)zlibstat.pch + $(IntDir) + $(IntDir) + $(OutDir) + Level3 + true + + + 0x040c + + + /MACHINE:X86 /NODEFAULTLIB %(AdditionalOptions) + $(OutDir)zlibstat.lib + true + + + + + X64 + + + Disabled + ..\..\..;..\..\masmx86;%(AdditionalIncludeDirectories) + ZLIB_WINAPI;_CRT_NONSTDC_NO_DEPRECATE;_CRT_SECURE_NO_DEPRECATE;_CRT_NONSTDC_NO_WARNINGS;WIN64;%(PreprocessorDefinitions) + + + MultiThreadedDebugDLL + false + $(IntDir)zlibstat.pch + $(IntDir) + $(IntDir) + $(OutDir) + Level3 + true + OldStyle + + + 0x040c + + + /MACHINE:AMD64 /NODEFAULTLIB %(AdditionalOptions) + $(OutDir)zlibstat.lib + true + + + + + Itanium + + + Disabled + ..\..\..;..\..\masmx86;%(AdditionalIncludeDirectories) + ZLIB_WINAPI;_CRT_NONSTDC_NO_DEPRECATE;_CRT_SECURE_NO_DEPRECATE;_CRT_NONSTDC_NO_WARNINGS;WIN64;%(PreprocessorDefinitions) + + + MultiThreadedDebugDLL + false + $(IntDir)zlibstat.pch + $(IntDir) + $(IntDir) + $(OutDir) + Level3 + true + OldStyle + + + 0x040c + + + /MACHINE:IA64 /NODEFAULTLIB %(AdditionalOptions) + $(OutDir)zlibstat.lib + true + + + + + X64 + + + OnlyExplicitInline + ..\..\..;..\..\masmx86;%(AdditionalIncludeDirectories) + ZLIB_WINAPI;_CRT_NONSTDC_NO_DEPRECATE;_CRT_SECURE_NO_DEPRECATE;_CRT_NONSTDC_NO_WARNINGS;ASMV;ASMINF;WIN64;%(PreprocessorDefinitions) + true + + + MultiThreadedDLL + false + true + $(IntDir)zlibstat.pch + $(IntDir) + $(IntDir) + $(OutDir) + Level3 + true + + + 0x040c + + + /MACHINE:AMD64 /NODEFAULTLIB %(AdditionalOptions) + ..\..\masmx64\gvmat64.obj;..\..\masmx64\inffasx64.obj;%(AdditionalDependencies) + $(OutDir)zlibstat.lib + true + + + + + Itanium + + + OnlyExplicitInline + ..\..\..;..\..\masmx86;%(AdditionalIncludeDirectories) + ZLIB_WINAPI;_CRT_NONSTDC_NO_DEPRECATE;_CRT_SECURE_NO_DEPRECATE;_CRT_NONSTDC_NO_WARNINGS;WIN64;%(PreprocessorDefinitions) + true + + + MultiThreadedDLL + false + true + $(IntDir)zlibstat.pch + $(IntDir) + $(IntDir) + $(OutDir) + Level3 + true + + + 0x040c + + + /MACHINE:IA64 /NODEFAULTLIB %(AdditionalOptions) + $(OutDir)zlibstat.lib + true + + + + + X64 + + + OnlyExplicitInline + ..\..\..;..\..\masmx86;%(AdditionalIncludeDirectories) + ZLIB_WINAPI;_CRT_NONSTDC_NO_DEPRECATE;_CRT_SECURE_NO_DEPRECATE;_CRT_NONSTDC_NO_WARNINGS;WIN64;%(PreprocessorDefinitions) + true + + + MultiThreadedDLL + false + true + $(IntDir)zlibstat.pch + $(IntDir) + $(IntDir) + $(OutDir) + Level3 + true + + + 0x040c + + + /MACHINE:AMD64 /NODEFAULTLIB %(AdditionalOptions) + $(OutDir)zlibstat.lib + true + + + + + Itanium + + + OnlyExplicitInline + ..\..\..;..\..\masmx86;%(AdditionalIncludeDirectories) + ZLIB_WINAPI;_CRT_NONSTDC_NO_DEPRECATE;_CRT_SECURE_NO_DEPRECATE;_CRT_NONSTDC_NO_WARNINGS;WIN64;%(PreprocessorDefinitions) + true + + + MultiThreadedDLL + false + true + $(IntDir)zlibstat.pch + $(IntDir) + $(IntDir) + $(OutDir) + Level3 + true + + + 0x040c + + + /MACHINE:IA64 /NODEFAULTLIB %(AdditionalOptions) + $(OutDir)zlibstat.lib + true + + + + + + + + + + + + + + true + true + true + true + true + true + + + + + + + + + + + + + + + + + + + + + ADDED compat/zlib/contrib/vstudio/vc11/zlibvc.def Index: compat/zlib/contrib/vstudio/vc11/zlibvc.def ================================================================== --- /dev/null +++ compat/zlib/contrib/vstudio/vc11/zlibvc.def @@ -0,0 +1,153 @@ +LIBRARY +; zlib data compression and ZIP file I/O library + +VERSION 1.2 + +EXPORTS + adler32 @1 + compress @2 + crc32 @3 + deflate @4 + deflateCopy @5 + deflateEnd @6 + deflateInit2_ @7 + deflateInit_ @8 + deflateParams @9 + deflateReset @10 + deflateSetDictionary @11 + gzclose @12 + gzdopen @13 + gzerror @14 + gzflush @15 + gzopen @16 + gzread @17 + gzwrite @18 + inflate @19 + inflateEnd @20 + inflateInit2_ @21 + inflateInit_ @22 + inflateReset @23 + inflateSetDictionary @24 + inflateSync @25 + uncompress @26 + zlibVersion @27 + gzprintf @28 + gzputc @29 + gzgetc @30 + gzseek @31 + gzrewind @32 + gztell @33 + gzeof @34 + gzsetparams @35 + zError @36 + inflateSyncPoint @37 + get_crc_table @38 + compress2 @39 + gzputs @40 + gzgets @41 + inflateCopy @42 + inflateBackInit_ @43 + inflateBack @44 + inflateBackEnd @45 + compressBound @46 + deflateBound @47 + gzclearerr @48 + gzungetc @49 + zlibCompileFlags @50 + deflatePrime @51 + deflatePending @52 + + unzOpen @61 + unzClose @62 + unzGetGlobalInfo @63 + unzGetCurrentFileInfo @64 + unzGoToFirstFile @65 + unzGoToNextFile @66 + unzOpenCurrentFile @67 + unzReadCurrentFile @68 + unzOpenCurrentFile3 @69 + unztell @70 + unzeof @71 + unzCloseCurrentFile @72 + unzGetGlobalComment @73 + unzStringFileNameCompare @74 + unzLocateFile @75 + unzGetLocalExtrafield @76 + unzOpen2 @77 + unzOpenCurrentFile2 @78 + unzOpenCurrentFilePassword @79 + + zipOpen @80 + zipOpenNewFileInZip @81 + zipWriteInFileInZip @82 + zipCloseFileInZip @83 + zipClose @84 + zipOpenNewFileInZip2 @86 + zipCloseFileInZipRaw @87 + zipOpen2 @88 + zipOpenNewFileInZip3 @89 + + unzGetFilePos @100 + unzGoToFilePos @101 + + fill_win32_filefunc @110 + +; zlibwapi v1.2.4 added: + fill_win32_filefunc64 @111 + fill_win32_filefunc64A @112 + fill_win32_filefunc64W @113 + + unzOpen64 @120 + unzOpen2_64 @121 + unzGetGlobalInfo64 @122 + unzGetCurrentFileInfo64 @124 + unzGetCurrentFileZStreamPos64 @125 + unztell64 @126 + unzGetFilePos64 @127 + unzGoToFilePos64 @128 + + zipOpen64 @130 + zipOpen2_64 @131 + zipOpenNewFileInZip64 @132 + zipOpenNewFileInZip2_64 @133 + zipOpenNewFileInZip3_64 @134 + zipOpenNewFileInZip4_64 @135 + zipCloseFileInZipRaw64 @136 + +; zlib1 v1.2.4 added: + adler32_combine @140 + crc32_combine @142 + deflateSetHeader @144 + deflateTune @145 + gzbuffer @146 + gzclose_r @147 + gzclose_w @148 + gzdirect @149 + gzoffset @150 + inflateGetHeader @156 + inflateMark @157 + inflatePrime @158 + inflateReset2 @159 + inflateUndermine @160 + +; zlib1 v1.2.6 added: + gzgetc_ @161 + inflateResetKeep @163 + deflateResetKeep @164 + +; zlib1 v1.2.7 added: + gzopen_w @165 + +; zlib1 v1.2.8 added: + inflateGetDictionary @166 + gzvprintf @167 + +; zlib1 v1.2.9 added: + inflateCodesUsed @168 + inflateValidate @169 + uncompress2 @170 + gzfread @171 + gzfwrite @172 + deflateGetDictionary @173 + adler32_z @174 + crc32_z @175 ADDED compat/zlib/contrib/vstudio/vc11/zlibvc.sln Index: compat/zlib/contrib/vstudio/vc11/zlibvc.sln ================================================================== --- /dev/null +++ compat/zlib/contrib/vstudio/vc11/zlibvc.sln @@ -0,0 +1,117 @@ + +Microsoft Visual Studio Solution File, Format Version 12.00 +# Visual Studio 2012 +Project("{8BC9CEB8-8B4A-11D0-8D11-00A0C91BC942}") = "zlibvc", "zlibvc.vcxproj", "{8FD826F8-3739-44E6-8CC8-997122E53B8D}" +EndProject +Project("{8BC9CEB8-8B4A-11D0-8D11-00A0C91BC942}") = "zlibstat", "zlibstat.vcxproj", "{745DEC58-EBB3-47A9-A9B8-4C6627C01BF8}" +EndProject +Project("{8BC9CEB8-8B4A-11D0-8D11-00A0C91BC942}") = "testzlib", "testzlib.vcxproj", "{AA6666AA-E09F-4135-9C0C-4FE50C3C654B}" +EndProject +Project("{8BC9CEB8-8B4A-11D0-8D11-00A0C91BC942}") = "testzlibdll", "testzlibdll.vcxproj", "{C52F9E7B-498A-42BE-8DB4-85A15694366A}" +EndProject +Project("{8BC9CEB8-8B4A-11D0-8D11-00A0C91BC942}") = "minizip", "minizip.vcxproj", "{48CDD9DC-E09F-4135-9C0C-4FE50C3C654B}" +EndProject +Project("{8BC9CEB8-8B4A-11D0-8D11-00A0C91BC942}") = "miniunz", "miniunz.vcxproj", "{C52F9E7B-498A-42BE-8DB4-85A15694382A}" +EndProject +Global + GlobalSection(SolutionConfigurationPlatforms) = preSolution + Debug|Itanium = Debug|Itanium + Debug|Win32 = Debug|Win32 + Debug|x64 = Debug|x64 + Release|Itanium = Release|Itanium + Release|Win32 = Release|Win32 + Release|x64 = Release|x64 + ReleaseWithoutAsm|Itanium = ReleaseWithoutAsm|Itanium + ReleaseWithoutAsm|Win32 = ReleaseWithoutAsm|Win32 + ReleaseWithoutAsm|x64 = ReleaseWithoutAsm|x64 + EndGlobalSection + GlobalSection(ProjectConfigurationPlatforms) = postSolution + {8FD826F8-3739-44E6-8CC8-997122E53B8D}.Debug|Itanium.ActiveCfg = Debug|Win32 + {8FD826F8-3739-44E6-8CC8-997122E53B8D}.Debug|Win32.ActiveCfg = Debug|Win32 + {8FD826F8-3739-44E6-8CC8-997122E53B8D}.Debug|Win32.Build.0 = Debug|Win32 + {8FD826F8-3739-44E6-8CC8-997122E53B8D}.Debug|x64.ActiveCfg = Debug|x64 + {8FD826F8-3739-44E6-8CC8-997122E53B8D}.Debug|x64.Build.0 = Debug|x64 + {8FD826F8-3739-44E6-8CC8-997122E53B8D}.Release|Itanium.ActiveCfg = Release|Win32 + {8FD826F8-3739-44E6-8CC8-997122E53B8D}.Release|Win32.ActiveCfg = Release|Win32 + {8FD826F8-3739-44E6-8CC8-997122E53B8D}.Release|Win32.Build.0 = Release|Win32 + {8FD826F8-3739-44E6-8CC8-997122E53B8D}.Release|x64.ActiveCfg = Release|x64 + {8FD826F8-3739-44E6-8CC8-997122E53B8D}.Release|x64.Build.0 = Release|x64 + {8FD826F8-3739-44E6-8CC8-997122E53B8D}.ReleaseWithoutAsm|Itanium.ActiveCfg = ReleaseWithoutAsm|Win32 + {8FD826F8-3739-44E6-8CC8-997122E53B8D}.ReleaseWithoutAsm|Win32.ActiveCfg = ReleaseWithoutAsm|Win32 + {8FD826F8-3739-44E6-8CC8-997122E53B8D}.ReleaseWithoutAsm|Win32.Build.0 = ReleaseWithoutAsm|Win32 + {8FD826F8-3739-44E6-8CC8-997122E53B8D}.ReleaseWithoutAsm|x64.ActiveCfg = ReleaseWithoutAsm|x64 + {8FD826F8-3739-44E6-8CC8-997122E53B8D}.ReleaseWithoutAsm|x64.Build.0 = ReleaseWithoutAsm|x64 + {745DEC58-EBB3-47A9-A9B8-4C6627C01BF8}.Debug|Itanium.ActiveCfg = Debug|Win32 + {745DEC58-EBB3-47A9-A9B8-4C6627C01BF8}.Debug|Win32.ActiveCfg = Debug|Win32 + {745DEC58-EBB3-47A9-A9B8-4C6627C01BF8}.Debug|Win32.Build.0 = Debug|Win32 + {745DEC58-EBB3-47A9-A9B8-4C6627C01BF8}.Debug|x64.ActiveCfg = Debug|x64 + {745DEC58-EBB3-47A9-A9B8-4C6627C01BF8}.Debug|x64.Build.0 = Debug|x64 + {745DEC58-EBB3-47A9-A9B8-4C6627C01BF8}.Release|Itanium.ActiveCfg = Release|Win32 + {745DEC58-EBB3-47A9-A9B8-4C6627C01BF8}.Release|Win32.ActiveCfg = Release|Win32 + {745DEC58-EBB3-47A9-A9B8-4C6627C01BF8}.Release|Win32.Build.0 = Release|Win32 + {745DEC58-EBB3-47A9-A9B8-4C6627C01BF8}.Release|x64.ActiveCfg = Release|x64 + {745DEC58-EBB3-47A9-A9B8-4C6627C01BF8}.Release|x64.Build.0 = Release|x64 + {745DEC58-EBB3-47A9-A9B8-4C6627C01BF8}.ReleaseWithoutAsm|Itanium.ActiveCfg = ReleaseWithoutAsm|Win32 + {745DEC58-EBB3-47A9-A9B8-4C6627C01BF8}.ReleaseWithoutAsm|Win32.ActiveCfg = ReleaseWithoutAsm|Win32 + {745DEC58-EBB3-47A9-A9B8-4C6627C01BF8}.ReleaseWithoutAsm|Win32.Build.0 = ReleaseWithoutAsm|Win32 + {745DEC58-EBB3-47A9-A9B8-4C6627C01BF8}.ReleaseWithoutAsm|x64.ActiveCfg = ReleaseWithoutAsm|x64 + {745DEC58-EBB3-47A9-A9B8-4C6627C01BF8}.ReleaseWithoutAsm|x64.Build.0 = ReleaseWithoutAsm|x64 + {AA6666AA-E09F-4135-9C0C-4FE50C3C654B}.Debug|Itanium.ActiveCfg = Debug|Win32 + {AA6666AA-E09F-4135-9C0C-4FE50C3C654B}.Debug|Win32.ActiveCfg = Debug|Win32 + {AA6666AA-E09F-4135-9C0C-4FE50C3C654B}.Debug|Win32.Build.0 = Debug|Win32 + {AA6666AA-E09F-4135-9C0C-4FE50C3C654B}.Debug|x64.ActiveCfg = Debug|x64 + {AA6666AA-E09F-4135-9C0C-4FE50C3C654B}.Debug|x64.Build.0 = Debug|x64 + {AA6666AA-E09F-4135-9C0C-4FE50C3C654B}.Release|Itanium.ActiveCfg = Release|Win32 + {AA6666AA-E09F-4135-9C0C-4FE50C3C654B}.Release|Win32.ActiveCfg = Release|Win32 + {AA6666AA-E09F-4135-9C0C-4FE50C3C654B}.Release|Win32.Build.0 = Release|Win32 + {AA6666AA-E09F-4135-9C0C-4FE50C3C654B}.Release|x64.ActiveCfg = Release|x64 + {AA6666AA-E09F-4135-9C0C-4FE50C3C654B}.Release|x64.Build.0 = Release|x64 + {AA6666AA-E09F-4135-9C0C-4FE50C3C654B}.ReleaseWithoutAsm|Itanium.ActiveCfg = ReleaseWithoutAsm|Win32 + {AA6666AA-E09F-4135-9C0C-4FE50C3C654B}.ReleaseWithoutAsm|Win32.ActiveCfg = ReleaseWithoutAsm|Win32 + {AA6666AA-E09F-4135-9C0C-4FE50C3C654B}.ReleaseWithoutAsm|Win32.Build.0 = ReleaseWithoutAsm|Win32 + {AA6666AA-E09F-4135-9C0C-4FE50C3C654B}.ReleaseWithoutAsm|x64.ActiveCfg = ReleaseWithoutAsm|x64 + {AA6666AA-E09F-4135-9C0C-4FE50C3C654B}.ReleaseWithoutAsm|x64.Build.0 = ReleaseWithoutAsm|x64 + {C52F9E7B-498A-42BE-8DB4-85A15694366A}.Debug|Itanium.ActiveCfg = Debug|Win32 + {C52F9E7B-498A-42BE-8DB4-85A15694366A}.Debug|Win32.ActiveCfg = Debug|Win32 + {C52F9E7B-498A-42BE-8DB4-85A15694366A}.Debug|Win32.Build.0 = Debug|Win32 + {C52F9E7B-498A-42BE-8DB4-85A15694366A}.Debug|x64.ActiveCfg = Debug|x64 + {C52F9E7B-498A-42BE-8DB4-85A15694366A}.Debug|x64.Build.0 = Debug|x64 + {C52F9E7B-498A-42BE-8DB4-85A15694366A}.Release|Itanium.ActiveCfg = Release|Win32 + {C52F9E7B-498A-42BE-8DB4-85A15694366A}.Release|Win32.ActiveCfg = Release|Win32 + {C52F9E7B-498A-42BE-8DB4-85A15694366A}.Release|Win32.Build.0 = Release|Win32 + {C52F9E7B-498A-42BE-8DB4-85A15694366A}.Release|x64.ActiveCfg = Release|x64 + {C52F9E7B-498A-42BE-8DB4-85A15694366A}.Release|x64.Build.0 = Release|x64 + {C52F9E7B-498A-42BE-8DB4-85A15694366A}.ReleaseWithoutAsm|Itanium.ActiveCfg = Release|Win32 + {C52F9E7B-498A-42BE-8DB4-85A15694366A}.ReleaseWithoutAsm|Win32.ActiveCfg = Release|Win32 + {C52F9E7B-498A-42BE-8DB4-85A15694366A}.ReleaseWithoutAsm|x64.ActiveCfg = Release|x64 + {48CDD9DC-E09F-4135-9C0C-4FE50C3C654B}.Debug|Itanium.ActiveCfg = Debug|Win32 + {48CDD9DC-E09F-4135-9C0C-4FE50C3C654B}.Debug|Win32.ActiveCfg = Debug|Win32 + {48CDD9DC-E09F-4135-9C0C-4FE50C3C654B}.Debug|Win32.Build.0 = Debug|Win32 + {48CDD9DC-E09F-4135-9C0C-4FE50C3C654B}.Debug|x64.ActiveCfg = Debug|x64 + {48CDD9DC-E09F-4135-9C0C-4FE50C3C654B}.Debug|x64.Build.0 = Debug|x64 + {48CDD9DC-E09F-4135-9C0C-4FE50C3C654B}.Release|Itanium.ActiveCfg = Release|Win32 + {48CDD9DC-E09F-4135-9C0C-4FE50C3C654B}.Release|Win32.ActiveCfg = Release|Win32 + {48CDD9DC-E09F-4135-9C0C-4FE50C3C654B}.Release|Win32.Build.0 = Release|Win32 + {48CDD9DC-E09F-4135-9C0C-4FE50C3C654B}.Release|x64.ActiveCfg = Release|x64 + {48CDD9DC-E09F-4135-9C0C-4FE50C3C654B}.Release|x64.Build.0 = Release|x64 + {48CDD9DC-E09F-4135-9C0C-4FE50C3C654B}.ReleaseWithoutAsm|Itanium.ActiveCfg = Release|Win32 + {48CDD9DC-E09F-4135-9C0C-4FE50C3C654B}.ReleaseWithoutAsm|Win32.ActiveCfg = Release|Win32 + {48CDD9DC-E09F-4135-9C0C-4FE50C3C654B}.ReleaseWithoutAsm|x64.ActiveCfg = Release|x64 + {C52F9E7B-498A-42BE-8DB4-85A15694382A}.Debug|Itanium.ActiveCfg = Debug|Win32 + {C52F9E7B-498A-42BE-8DB4-85A15694382A}.Debug|Win32.ActiveCfg = Debug|Win32 + {C52F9E7B-498A-42BE-8DB4-85A15694382A}.Debug|Win32.Build.0 = Debug|Win32 + {C52F9E7B-498A-42BE-8DB4-85A15694382A}.Debug|x64.ActiveCfg = Debug|x64 + {C52F9E7B-498A-42BE-8DB4-85A15694382A}.Debug|x64.Build.0 = Debug|x64 + {C52F9E7B-498A-42BE-8DB4-85A15694382A}.Release|Itanium.ActiveCfg = Release|Win32 + {C52F9E7B-498A-42BE-8DB4-85A15694382A}.Release|Win32.ActiveCfg = Release|Win32 + {C52F9E7B-498A-42BE-8DB4-85A15694382A}.Release|Win32.Build.0 = Release|Win32 + {C52F9E7B-498A-42BE-8DB4-85A15694382A}.Release|x64.ActiveCfg = Release|x64 + {C52F9E7B-498A-42BE-8DB4-85A15694382A}.Release|x64.Build.0 = Release|x64 + {C52F9E7B-498A-42BE-8DB4-85A15694382A}.ReleaseWithoutAsm|Itanium.ActiveCfg = Release|Win32 + {C52F9E7B-498A-42BE-8DB4-85A15694382A}.ReleaseWithoutAsm|Win32.ActiveCfg = Release|Win32 + {C52F9E7B-498A-42BE-8DB4-85A15694382A}.ReleaseWithoutAsm|x64.ActiveCfg = Release|x64 + EndGlobalSection + GlobalSection(SolutionProperties) = preSolution + HideSolutionNode = FALSE + EndGlobalSection +EndGlobal ADDED compat/zlib/contrib/vstudio/vc11/zlibvc.vcxproj Index: compat/zlib/contrib/vstudio/vc11/zlibvc.vcxproj ================================================================== --- /dev/null +++ compat/zlib/contrib/vstudio/vc11/zlibvc.vcxproj @@ -0,0 +1,688 @@ + + + + + Debug + Itanium + + + Debug + Win32 + + + Debug + x64 + + + ReleaseWithoutAsm + Itanium + + + ReleaseWithoutAsm + Win32 + + + ReleaseWithoutAsm + x64 + + + Release + Itanium + + + Release + Win32 + + + Release + x64 + + + + {8FD826F8-3739-44E6-8CC8-997122E53B8D} + + + + DynamicLibrary + false + true + v110 + + + DynamicLibrary + false + true + v110 + + + DynamicLibrary + false + v110 + Unicode + + + DynamicLibrary + false + true + + + DynamicLibrary + false + true + + + DynamicLibrary + false + + + DynamicLibrary + false + true + v110 + + + DynamicLibrary + false + true + v110 + + + DynamicLibrary + false + v110 + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + <_ProjectFileVersion>10.0.30128.1 + x86\ZlibDll$(Configuration)\ + x86\ZlibDll$(Configuration)\Tmp\ + true + false + x86\ZlibDll$(Configuration)\ + x86\ZlibDll$(Configuration)\Tmp\ + false + false + x86\ZlibDll$(Configuration)\ + x86\ZlibDll$(Configuration)\Tmp\ + false + false + x64\ZlibDll$(Configuration)\ + x64\ZlibDll$(Configuration)\Tmp\ + true + false + ia64\ZlibDll$(Configuration)\ + ia64\ZlibDll$(Configuration)\Tmp\ + true + false + x64\ZlibDll$(Configuration)\ + x64\ZlibDll$(Configuration)\Tmp\ + false + false + ia64\ZlibDll$(Configuration)\ + ia64\ZlibDll$(Configuration)\Tmp\ + false + false + x64\ZlibDll$(Configuration)\ + x64\ZlibDll$(Configuration)\Tmp\ + false + false + ia64\ZlibDll$(Configuration)\ + ia64\ZlibDll$(Configuration)\Tmp\ + false + false + AllRules.ruleset + + + AllRules.ruleset + + + AllRules.ruleset + + + AllRules.ruleset + + + AllRules.ruleset + + + AllRules.ruleset + + + AllRules.ruleset + + + AllRules.ruleset + + + AllRules.ruleset + + + zlibwapi + zlibwapi + zlibwapi + zlibwapi + zlibwapi + zlibwapi + + + + _DEBUG;%(PreprocessorDefinitions) + true + true + Win32 + $(OutDir)zlibvc.tlb + + + Disabled + ..\..\..;..\..\masmx86;%(AdditionalIncludeDirectories) + WIN32;_CRT_NONSTDC_NO_DEPRECATE;_CRT_SECURE_NO_DEPRECATE;_CRT_NONSTDC_NO_WARNINGS;ZLIB_WINAPI;ASMV;ASMINF;%(PreprocessorDefinitions) + + + MultiThreadedDebugDLL + false + $(IntDir)zlibvc.pch + $(IntDir) + $(IntDir) + $(OutDir) + + + Level3 + true + ProgramDatabase + + + _DEBUG;%(PreprocessorDefinitions) + 0x040c + + + /MACHINE:I386 %(AdditionalOptions) + ..\..\masmx86\match686.obj;..\..\masmx86\inffas32.obj;%(AdditionalDependencies) + $(OutDir)zlibwapi.dll + true + .\zlibvc.def + true + $(OutDir)zlibwapi.pdb + true + $(OutDir)zlibwapi.map + Windows + false + + + $(OutDir)zlibwapi.lib + + + cd ..\..\masmx86 +bld_ml32.bat + + + + + NDEBUG;%(PreprocessorDefinitions) + true + true + Win32 + $(OutDir)zlibvc.tlb + + + OnlyExplicitInline + ..\..\..;..\..\masmx86;%(AdditionalIncludeDirectories) + WIN32;_CRT_NONSTDC_NO_DEPRECATE;_CRT_SECURE_NO_DEPRECATE;_CRT_NONSTDC_NO_WARNINGS;ZLIB_WINAPI;%(PreprocessorDefinitions) + true + + + MultiThreadedDLL + false + true + $(IntDir)zlibvc.pch + All + $(IntDir) + $(IntDir) + $(OutDir) + + + Level3 + true + + + NDEBUG;%(PreprocessorDefinitions) + 0x040c + + + /MACHINE:I386 %(AdditionalOptions) + $(OutDir)zlibwapi.dll + true + false + .\zlibvc.def + $(OutDir)zlibwapi.pdb + true + $(OutDir)zlibwapi.map + Windows + false + + + $(OutDir)zlibwapi.lib + + + + + NDEBUG;%(PreprocessorDefinitions) + true + true + Win32 + $(OutDir)zlibvc.tlb + + + OnlyExplicitInline + ..\..\..;..\..\masmx86;%(AdditionalIncludeDirectories) + WIN32;_CRT_NONSTDC_NO_DEPRECATE;_CRT_SECURE_NO_DEPRECATE;_CRT_NONSTDC_NO_WARNINGS;ZLIB_WINAPI;ASMV;ASMINF;%(PreprocessorDefinitions) + true + + + MultiThreaded + false + true + $(IntDir)zlibvc.pch + All + $(IntDir) + $(IntDir) + $(OutDir) + + + Level3 + true + + + NDEBUG;%(PreprocessorDefinitions) + 0x040c + + + /MACHINE:I386 %(AdditionalOptions) + ..\..\masmx86\match686.obj;..\..\masmx86\inffas32.obj;%(AdditionalDependencies) + $(OutDir)zlibwapi.dll + true + false + .\zlibvc.def + $(OutDir)zlibwapi.pdb + true + $(OutDir)zlibwapi.map + Windows + false + + + $(OutDir)zlibwapi.lib + + + cd ..\..\masmx86 +bld_ml32.bat + + + + + _DEBUG;%(PreprocessorDefinitions) + true + true + X64 + $(OutDir)zlibvc.tlb + + + Disabled + ..\..\..;..\..\masmx86;%(AdditionalIncludeDirectories) + WIN32;_CRT_NONSTDC_NO_DEPRECATE;_CRT_SECURE_NO_DEPRECATE;_CRT_NONSTDC_NO_WARNINGS;ZLIB_WINAPI;ASMV;ASMINF;WIN64;%(PreprocessorDefinitions) + + + MultiThreadedDebugDLL + false + $(IntDir)zlibvc.pch + $(IntDir) + $(IntDir) + $(OutDir) + + + Level3 + true + ProgramDatabase + + + _DEBUG;%(PreprocessorDefinitions) + 0x040c + + + ..\..\masmx64\gvmat64.obj;..\..\masmx64\inffasx64.obj;%(AdditionalDependencies) + $(OutDir)zlibwapi.dll + true + .\zlibvc.def + true + $(OutDir)zlibwapi.pdb + true + $(OutDir)zlibwapi.map + Windows + $(OutDir)zlibwapi.lib + MachineX64 + + + cd ..\..\contrib\masmx64 +bld_ml64.bat + + + + + _DEBUG;%(PreprocessorDefinitions) + true + true + Itanium + $(OutDir)zlibvc.tlb + + + Disabled + ..\..\..;..\..\masmx86;%(AdditionalIncludeDirectories) + WIN32;_CRT_NONSTDC_NO_DEPRECATE;_CRT_SECURE_NO_DEPRECATE;_CRT_NONSTDC_NO_WARNINGS;ZLIB_WINAPI;WIN64;%(PreprocessorDefinitions) + + + MultiThreadedDebugDLL + false + $(IntDir)zlibvc.pch + $(IntDir) + $(IntDir) + $(OutDir) + + + Level3 + true + ProgramDatabase + + + _DEBUG;%(PreprocessorDefinitions) + 0x040c + + + $(OutDir)zlibwapi.dll + true + .\zlibvc.def + true + $(OutDir)zlibwapi.pdb + true + $(OutDir)zlibwapi.map + Windows + $(OutDir)zlibwapi.lib + MachineIA64 + + + + + NDEBUG;%(PreprocessorDefinitions) + true + true + X64 + $(OutDir)zlibvc.tlb + + + OnlyExplicitInline + ..\..\..;..\..\masmx86;%(AdditionalIncludeDirectories) + WIN32;_CRT_NONSTDC_NO_DEPRECATE;_CRT_SECURE_NO_DEPRECATE;_CRT_NONSTDC_NO_WARNINGS;ZLIB_WINAPI;WIN64;%(PreprocessorDefinitions) + true + + + MultiThreadedDLL + false + true + $(IntDir)zlibvc.pch + All + $(IntDir) + $(IntDir) + $(OutDir) + + + Level3 + true + + + NDEBUG;%(PreprocessorDefinitions) + 0x040c + + + $(OutDir)zlibwapi.dll + true + false + .\zlibvc.def + $(OutDir)zlibwapi.pdb + true + $(OutDir)zlibwapi.map + Windows + $(OutDir)zlibwapi.lib + MachineX64 + + + + + NDEBUG;%(PreprocessorDefinitions) + true + true + Itanium + $(OutDir)zlibvc.tlb + + + OnlyExplicitInline + ..\..\..;..\..\masmx86;%(AdditionalIncludeDirectories) + WIN32;_CRT_NONSTDC_NO_DEPRECATE;_CRT_SECURE_NO_DEPRECATE;_CRT_NONSTDC_NO_WARNINGS;ZLIB_WINAPI;WIN64;%(PreprocessorDefinitions) + true + + + MultiThreadedDLL + false + true + $(IntDir)zlibvc.pch + All + $(IntDir) + $(IntDir) + $(OutDir) + + + Level3 + true + + + NDEBUG;%(PreprocessorDefinitions) + 0x040c + + + $(OutDir)zlibwapi.dll + true + false + .\zlibvc.def + $(OutDir)zlibwapi.pdb + true + $(OutDir)zlibwapi.map + Windows + $(OutDir)zlibwapi.lib + MachineIA64 + + + + + NDEBUG;%(PreprocessorDefinitions) + true + true + X64 + $(OutDir)zlibvc.tlb + + + OnlyExplicitInline + ..\..\..;..\..\masmx86;%(AdditionalIncludeDirectories) + _CRT_NONSTDC_NO_DEPRECATE;_CRT_SECURE_NO_DEPRECATE;_CRT_NONSTDC_NO_WARNINGS;ZLIB_WINAPI;ASMV;ASMINF;WIN64;%(PreprocessorDefinitions) + true + + + MultiThreadedDLL + false + true + $(IntDir)zlibvc.pch + All + $(IntDir) + $(IntDir) + $(OutDir) + + + Level3 + true + + + NDEBUG;%(PreprocessorDefinitions) + 0x040c + + + ..\..\masmx64\gvmat64.obj;..\..\masmx64\inffasx64.obj;%(AdditionalDependencies) + $(OutDir)zlibwapi.dll + true + false + .\zlibvc.def + $(OutDir)zlibwapi.pdb + true + $(OutDir)zlibwapi.map + Windows + $(OutDir)zlibwapi.lib + MachineX64 + + + cd ..\..\masmx64 +bld_ml64.bat + + + + + NDEBUG;%(PreprocessorDefinitions) + true + true + Itanium + $(OutDir)zlibvc.tlb + + + OnlyExplicitInline + ..\..\..;..\..\masmx86;%(AdditionalIncludeDirectories) + _CRT_NONSTDC_NO_DEPRECATE;_CRT_SECURE_NO_DEPRECATE;_CRT_NONSTDC_NO_WARNINGS;ZLIB_WINAPI;WIN64;%(PreprocessorDefinitions) + true + + + MultiThreadedDLL + false + true + $(IntDir)zlibvc.pch + All + $(IntDir) + $(IntDir) + $(OutDir) + + + Level3 + true + + + NDEBUG;%(PreprocessorDefinitions) + 0x040c + + + $(OutDir)zlibwapi.dll + true + false + .\zlibvc.def + $(OutDir)zlibwapi.pdb + true + $(OutDir)zlibwapi.map + Windows + $(OutDir)zlibwapi.lib + MachineIA64 + + + + + + + + + + + + + + true + true + true + true + true + true + + + + + + + + + + %(AdditionalIncludeDirectories) + ZLIB_INTERNAL;%(PreprocessorDefinitions) + %(AdditionalIncludeDirectories) + ZLIB_INTERNAL;%(PreprocessorDefinitions) + %(AdditionalIncludeDirectories) + ZLIB_INTERNAL;%(PreprocessorDefinitions) + + + %(AdditionalIncludeDirectories) + ZLIB_INTERNAL;%(PreprocessorDefinitions) + %(AdditionalIncludeDirectories) + ZLIB_INTERNAL;%(PreprocessorDefinitions) + %(AdditionalIncludeDirectories) + ZLIB_INTERNAL;%(PreprocessorDefinitions) + + + + + + + + + + + + + + + + + + + + + + + + ADDED compat/zlib/contrib/vstudio/vc12/miniunz.vcxproj Index: compat/zlib/contrib/vstudio/vc12/miniunz.vcxproj ================================================================== --- /dev/null +++ compat/zlib/contrib/vstudio/vc12/miniunz.vcxproj @@ -0,0 +1,316 @@ + + + + + Debug + Itanium + + + Debug + Win32 + + + Debug + x64 + + + Release + Itanium + + + Release + Win32 + + + Release + x64 + + + + {C52F9E7B-498A-42BE-8DB4-85A15694382A} + Win32Proj + + + + Application + MultiByte + v120 + + + Application + Unicode + v120 + + + Application + MultiByte + v120 + + + Application + MultiByte + v120 + + + Application + MultiByte + v120 + + + Application + MultiByte + v120 + + + + + + + + + + + + + + + + + + + + + + + + + <_ProjectFileVersion>10.0.30128.1 + x86\MiniUnzip$(Configuration)\ + x86\MiniUnzip$(Configuration)\Tmp\ + true + false + x86\MiniUnzip$(Configuration)\ + x86\MiniUnzip$(Configuration)\Tmp\ + false + false + x64\MiniUnzip$(Configuration)\ + x64\MiniUnzip$(Configuration)\Tmp\ + true + false + ia64\MiniUnzip$(Configuration)\ + ia64\MiniUnzip$(Configuration)\Tmp\ + true + false + x64\MiniUnzip$(Configuration)\ + x64\MiniUnzip$(Configuration)\Tmp\ + false + false + ia64\MiniUnzip$(Configuration)\ + ia64\MiniUnzip$(Configuration)\Tmp\ + false + false + AllRules.ruleset + + + AllRules.ruleset + + + AllRules.ruleset + + + AllRules.ruleset + + + AllRules.ruleset + + + AllRules.ruleset + + + + + + Disabled + ..\..\..;..\..\minizip;%(AdditionalIncludeDirectories) + WIN32;_CRT_NONSTDC_NO_DEPRECATE;_CRT_SECURE_NO_DEPRECATE;ZLIB_WINAPI;_DEBUG;_CONSOLE;%(PreprocessorDefinitions) + true + Default + MultiThreadedDebugDLL + false + + + $(IntDir) + Level3 + ProgramDatabase + + + x86\ZlibDllDebug\zlibwapi.lib;%(AdditionalDependencies) + $(OutDir)miniunz.exe + true + $(OutDir)miniunz.pdb + Console + false + + + MachineX86 + + + + + MaxSpeed + OnlyExplicitInline + true + ..\..\..;..\..\minizip;%(AdditionalIncludeDirectories) + WIN32;_CRT_NONSTDC_NO_DEPRECATE;_CRT_SECURE_NO_DEPRECATE;ZLIB_WINAPI;NDEBUG;_CONSOLE;%(PreprocessorDefinitions) + true + Default + MultiThreaded + false + true + + + $(IntDir) + Level3 + ProgramDatabase + + + x86\ZlibDllRelease\zlibwapi.lib;%(AdditionalDependencies) + $(OutDir)miniunz.exe + true + Console + true + true + false + + + MachineX86 + + + + + X64 + + + Disabled + ..\..\..;..\..\minizip;%(AdditionalIncludeDirectories) + _CRT_NONSTDC_NO_DEPRECATE;_CRT_SECURE_NO_DEPRECATE;ZLIB_WINAPI;_DEBUG;_CONSOLE;WIN64;%(PreprocessorDefinitions) + true + Default + MultiThreadedDebugDLL + false + + + $(IntDir) + Level3 + ProgramDatabase + + + x64\ZlibDllDebug\zlibwapi.lib;%(AdditionalDependencies) + $(OutDir)miniunz.exe + true + $(OutDir)miniunz.pdb + Console + MachineX64 + + + + + Itanium + + + Disabled + ..\..\..;..\..\minizip;%(AdditionalIncludeDirectories) + _CRT_NONSTDC_NO_DEPRECATE;_CRT_SECURE_NO_DEPRECATE;ZLIB_WINAPI;_DEBUG;_CONSOLE;WIN64;%(PreprocessorDefinitions) + true + Default + MultiThreadedDebugDLL + false + + + $(IntDir) + Level3 + ProgramDatabase + + + ia64\ZlibDllDebug\zlibwapi.lib;%(AdditionalDependencies) + $(OutDir)miniunz.exe + true + $(OutDir)miniunz.pdb + Console + MachineIA64 + + + + + X64 + + + MaxSpeed + OnlyExplicitInline + true + ..\..\..;..\..\minizip;%(AdditionalIncludeDirectories) + _CRT_NONSTDC_NO_DEPRECATE;_CRT_SECURE_NO_DEPRECATE;ZLIB_WINAPI;NDEBUG;_CONSOLE;WIN64;%(PreprocessorDefinitions) + true + Default + MultiThreadedDLL + false + true + + + $(IntDir) + Level3 + ProgramDatabase + + + x64\ZlibDllRelease\zlibwapi.lib;%(AdditionalDependencies) + $(OutDir)miniunz.exe + true + Console + true + true + MachineX64 + + + + + Itanium + + + MaxSpeed + OnlyExplicitInline + true + ..\..\..;..\..\minizip;%(AdditionalIncludeDirectories) + _CRT_NONSTDC_NO_DEPRECATE;_CRT_SECURE_NO_DEPRECATE;ZLIB_WINAPI;NDEBUG;_CONSOLE;WIN64;%(PreprocessorDefinitions) + true + Default + MultiThreadedDLL + false + true + + + $(IntDir) + Level3 + ProgramDatabase + + + ia64\ZlibDllRelease\zlibwapi.lib;%(AdditionalDependencies) + $(OutDir)miniunz.exe + true + Console + true + true + MachineIA64 + + + + + + + + {8fd826f8-3739-44e6-8cc8-997122e53b8d} + + + + + + ADDED compat/zlib/contrib/vstudio/vc12/minizip.vcxproj Index: compat/zlib/contrib/vstudio/vc12/minizip.vcxproj ================================================================== --- /dev/null +++ compat/zlib/contrib/vstudio/vc12/minizip.vcxproj @@ -0,0 +1,313 @@ + + + + + Debug + Itanium + + + Debug + Win32 + + + Debug + x64 + + + Release + Itanium + + + Release + Win32 + + + Release + x64 + + + + {48CDD9DC-E09F-4135-9C0C-4FE50C3C654B} + Win32Proj + + + + Application + MultiByte + v120 + + + Application + Unicode + v120 + + + Application + MultiByte + v120 + + + Application + MultiByte + v120 + + + Application + MultiByte + v120 + + + Application + MultiByte + v120 + + + + + + + + + + + + + + + + + + + + + + + + + <_ProjectFileVersion>10.0.30128.1 + x86\MiniZip$(Configuration)\ + x86\MiniZip$(Configuration)\Tmp\ + true + false + x86\MiniZip$(Configuration)\ + x86\MiniZip$(Configuration)\Tmp\ + false + x64\$(Configuration)\ + x64\$(Configuration)\ + true + false + ia64\$(Configuration)\ + ia64\$(Configuration)\ + true + false + x64\$(Configuration)\ + x64\$(Configuration)\ + false + ia64\$(Configuration)\ + ia64\$(Configuration)\ + false + AllRules.ruleset + + + AllRules.ruleset + + + AllRules.ruleset + + + AllRules.ruleset + + + AllRules.ruleset + + + AllRules.ruleset + + + + + + Disabled + ..\..\..;..\..\minizip;%(AdditionalIncludeDirectories) + WIN32;_CRT_NONSTDC_NO_DEPRECATE;_CRT_SECURE_NO_DEPRECATE;ZLIB_WINAPI;_DEBUG;_CONSOLE;%(PreprocessorDefinitions) + true + Default + MultiThreadedDebugDLL + false + + + $(IntDir) + Level3 + ProgramDatabase + + + x86\ZlibDllDebug\zlibwapi.lib;%(AdditionalDependencies) + $(OutDir)minizip.exe + true + $(OutDir)minizip.pdb + Console + false + + + MachineX86 + + + + + MaxSpeed + OnlyExplicitInline + true + ..\..\..;..\..\minizip;%(AdditionalIncludeDirectories) + WIN32;_CRT_NONSTDC_NO_DEPRECATE;_CRT_SECURE_NO_DEPRECATE;ZLIB_WINAPI;NDEBUG;_CONSOLE;%(PreprocessorDefinitions) + true + Default + MultiThreaded + false + true + + + $(IntDir) + Level3 + ProgramDatabase + + + x86\ZlibDllRelease\zlibwapi.lib;%(AdditionalDependencies) + $(OutDir)minizip.exe + true + Console + true + true + false + + + MachineX86 + + + + + X64 + + + Disabled + ..\..\..;..\..\minizip;%(AdditionalIncludeDirectories) + _CRT_NONSTDC_NO_DEPRECATE;_CRT_SECURE_NO_DEPRECATE;ZLIB_WINAPI;_DEBUG;_CONSOLE;WIN64;%(PreprocessorDefinitions) + true + Default + MultiThreadedDebugDLL + false + + + $(IntDir) + Level3 + ProgramDatabase + + + x64\ZlibDllDebug\zlibwapi.lib;%(AdditionalDependencies) + $(OutDir)minizip.exe + true + $(OutDir)minizip.pdb + Console + MachineX64 + + + + + Itanium + + + Disabled + ..\..\..;..\..\minizip;%(AdditionalIncludeDirectories) + _CRT_NONSTDC_NO_DEPRECATE;_CRT_SECURE_NO_DEPRECATE;ZLIB_WINAPI;_DEBUG;_CONSOLE;WIN64;%(PreprocessorDefinitions) + true + Default + MultiThreadedDebugDLL + false + + + $(IntDir) + Level3 + ProgramDatabase + + + ia64\ZlibDllDebug\zlibwapi.lib;%(AdditionalDependencies) + $(OutDir)minizip.exe + true + $(OutDir)minizip.pdb + Console + MachineIA64 + + + + + X64 + + + MaxSpeed + OnlyExplicitInline + true + ..\..\..;..\..\minizip;%(AdditionalIncludeDirectories) + _CRT_NONSTDC_NO_DEPRECATE;_CRT_SECURE_NO_DEPRECATE;ZLIB_WINAPI;NDEBUG;_CONSOLE;WIN64;%(PreprocessorDefinitions) + true + Default + MultiThreadedDLL + false + true + + + $(IntDir) + Level3 + ProgramDatabase + + + x64\ZlibDllRelease\zlibwapi.lib;%(AdditionalDependencies) + $(OutDir)minizip.exe + true + Console + true + true + MachineX64 + + + + + Itanium + + + MaxSpeed + OnlyExplicitInline + true + ..\..\..;..\..\minizip;%(AdditionalIncludeDirectories) + _CRT_NONSTDC_NO_DEPRECATE;_CRT_SECURE_NO_DEPRECATE;ZLIB_WINAPI;NDEBUG;_CONSOLE;WIN64;%(PreprocessorDefinitions) + true + Default + MultiThreadedDLL + false + true + + + $(IntDir) + Level3 + ProgramDatabase + + + ia64\ZlibDllRelease\zlibwapi.lib;%(AdditionalDependencies) + $(OutDir)minizip.exe + true + Console + true + true + MachineIA64 + + + + + + + + {8fd826f8-3739-44e6-8cc8-997122e53b8d} + + + + + + ADDED compat/zlib/contrib/vstudio/vc12/testzlib.vcxproj Index: compat/zlib/contrib/vstudio/vc12/testzlib.vcxproj ================================================================== --- /dev/null +++ compat/zlib/contrib/vstudio/vc12/testzlib.vcxproj @@ -0,0 +1,430 @@ + + + + + Debug + Itanium + + + Debug + Win32 + + + Debug + x64 + + + ReleaseWithoutAsm + Itanium + + + ReleaseWithoutAsm + Win32 + + + ReleaseWithoutAsm + x64 + + + Release + Itanium + + + Release + Win32 + + + Release + x64 + + + + {AA6666AA-E09F-4135-9C0C-4FE50C3C654B} + testzlib + Win32Proj + + + + Application + MultiByte + true + v120 + + + Application + MultiByte + true + v120 + + + Application + Unicode + v120 + + + Application + MultiByte + true + v120 + + + Application + MultiByte + true + v120 + + + Application + MultiByte + v120 + + + Application + true + v120 + + + Application + true + v120 + + + Application + v120 + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + <_ProjectFileVersion>10.0.30128.1 + x86\TestZlib$(Configuration)\ + x86\TestZlib$(Configuration)\Tmp\ + true + false + x86\TestZlib$(Configuration)\ + x86\TestZlib$(Configuration)\Tmp\ + false + false + x86\TestZlib$(Configuration)\ + x86\TestZlib$(Configuration)\Tmp\ + false + false + x64\TestZlib$(Configuration)\ + x64\TestZlib$(Configuration)\Tmp\ + false + ia64\TestZlib$(Configuration)\ + ia64\TestZlib$(Configuration)\Tmp\ + true + false + x64\TestZlib$(Configuration)\ + x64\TestZlib$(Configuration)\Tmp\ + false + ia64\TestZlib$(Configuration)\ + ia64\TestZlib$(Configuration)\Tmp\ + false + false + x64\TestZlib$(Configuration)\ + x64\TestZlib$(Configuration)\Tmp\ + false + ia64\TestZlib$(Configuration)\ + ia64\TestZlib$(Configuration)\Tmp\ + false + false + AllRules.ruleset + + + AllRules.ruleset + + + AllRules.ruleset + + + AllRules.ruleset + + + AllRules.ruleset + + + AllRules.ruleset + + + AllRules.ruleset + + + AllRules.ruleset + + + AllRules.ruleset + + + + + + Disabled + ..\..\..;%(AdditionalIncludeDirectories) + ASMV;ASMINF;WIN32;ZLIB_WINAPI;_DEBUG;_CONSOLE;_CRT_NONSTDC_NO_DEPRECATE;_CRT_SECURE_NO_DEPRECATE;_CRT_NONSTDC_NO_WARNINGS;%(PreprocessorDefinitions) + true + Default + MultiThreadedDebugDLL + false + + + AssemblyAndSourceCode + $(IntDir) + Level3 + ProgramDatabase + + + ..\..\masmx86\match686.obj;..\..\masmx86\inffas32.obj;%(AdditionalDependencies) + $(OutDir)testzlib.exe + true + $(OutDir)testzlib.pdb + Console + false + + + MachineX86 + + + + + MaxSpeed + OnlyExplicitInline + true + ..\..\..;%(AdditionalIncludeDirectories) + WIN32;ZLIB_WINAPI;NDEBUG;_CONSOLE;_CRT_NONSTDC_NO_DEPRECATE;_CRT_SECURE_NO_DEPRECATE;_CRT_NONSTDC_NO_WARNINGS;%(PreprocessorDefinitions) + true + Default + MultiThreaded + false + true + + + $(IntDir) + Level3 + ProgramDatabase + + + $(OutDir)testzlib.exe + true + Console + true + true + false + + + MachineX86 + + + + + MaxSpeed + OnlyExplicitInline + true + ..\..\..;%(AdditionalIncludeDirectories) + ASMV;ASMINF;WIN32;ZLIB_WINAPI;NDEBUG;_CONSOLE;_CRT_NONSTDC_NO_DEPRECATE;_CRT_SECURE_NO_DEPRECATE;_CRT_NONSTDC_NO_WARNINGS;%(PreprocessorDefinitions) + true + Default + MultiThreaded + false + true + + + $(IntDir) + Level3 + ProgramDatabase + + + ..\..\masmx86\match686.obj;..\..\masmx86\inffas32.obj;%(AdditionalDependencies) + $(OutDir)testzlib.exe + true + Console + true + true + false + + + MachineX86 + false + + + + + ..\..\..;%(AdditionalIncludeDirectories) + ASMV;ASMINF;WIN32;ZLIB_WINAPI;_DEBUG;_CONSOLE;_CRT_NONSTDC_NO_DEPRECATE;_CRT_SECURE_NO_DEPRECATE;_CRT_NONSTDC_NO_WARNINGS;%(PreprocessorDefinitions) + Default + MultiThreadedDebugDLL + false + $(IntDir) + + + ..\..\masmx64\gvmat64.obj;..\..\masmx64\inffasx64.obj;%(AdditionalDependencies) + + + + + Itanium + + + Disabled + ..\..\..;%(AdditionalIncludeDirectories) + ZLIB_WINAPI;_DEBUG;_CONSOLE;_CRT_NONSTDC_NO_DEPRECATE;_CRT_SECURE_NO_DEPRECATE;_CRT_NONSTDC_NO_WARNINGS;WIN64;%(PreprocessorDefinitions) + true + Default + MultiThreadedDebugDLL + false + + + AssemblyAndSourceCode + $(IntDir) + Level3 + ProgramDatabase + + + $(OutDir)testzlib.exe + true + $(OutDir)testzlib.pdb + Console + MachineIA64 + + + + + ..\..\..;%(AdditionalIncludeDirectories) + WIN32;ZLIB_WINAPI;NDEBUG;_CONSOLE;_CRT_NONSTDC_NO_DEPRECATE;_CRT_SECURE_NO_DEPRECATE;_CRT_NONSTDC_NO_WARNINGS;%(PreprocessorDefinitions) + Default + MultiThreadedDLL + false + $(IntDir) + + + %(AdditionalDependencies) + + + + + Itanium + + + MaxSpeed + OnlyExplicitInline + true + ..\..\..;%(AdditionalIncludeDirectories) + ZLIB_WINAPI;NDEBUG;_CONSOLE;_CRT_NONSTDC_NO_DEPRECATE;_CRT_SECURE_NO_DEPRECATE;_CRT_NONSTDC_NO_WARNINGS;WIN64;%(PreprocessorDefinitions) + true + Default + MultiThreadedDLL + false + true + + + $(IntDir) + Level3 + ProgramDatabase + + + $(OutDir)testzlib.exe + true + Console + true + true + MachineIA64 + + + + + ..\..\..;%(AdditionalIncludeDirectories) + ASMV;ASMINF;WIN32;ZLIB_WINAPI;NDEBUG;_CONSOLE;_CRT_NONSTDC_NO_DEPRECATE;_CRT_SECURE_NO_DEPRECATE;_CRT_NONSTDC_NO_WARNINGS;%(PreprocessorDefinitions) + Default + MultiThreadedDLL + false + $(IntDir) + + + ..\..\masmx64\gvmat64.obj;..\..\masmx64\inffasx64.obj;%(AdditionalDependencies) + + + + + Itanium + + + MaxSpeed + OnlyExplicitInline + true + ..\..\..;%(AdditionalIncludeDirectories) + ZLIB_WINAPI;NDEBUG;_CONSOLE;_CRT_NONSTDC_NO_DEPRECATE;_CRT_SECURE_NO_DEPRECATE;_CRT_NONSTDC_NO_WARNINGS;WIN64;%(PreprocessorDefinitions) + true + Default + MultiThreadedDLL + false + true + + + $(IntDir) + Level3 + ProgramDatabase + + + $(OutDir)testzlib.exe + true + Console + true + true + MachineIA64 + + + + + + + + + + true + true + true + true + true + true + + + + + + + + + + + + + ADDED compat/zlib/contrib/vstudio/vc12/testzlibdll.vcxproj Index: compat/zlib/contrib/vstudio/vc12/testzlibdll.vcxproj ================================================================== --- /dev/null +++ compat/zlib/contrib/vstudio/vc12/testzlibdll.vcxproj @@ -0,0 +1,316 @@ + + + + + Debug + Itanium + + + Debug + Win32 + + + Debug + x64 + + + Release + Itanium + + + Release + Win32 + + + Release + x64 + + + + {C52F9E7B-498A-42BE-8DB4-85A15694366A} + Win32Proj + + + + Application + MultiByte + v120 + + + Application + Unicode + v120 + + + Application + MultiByte + v120 + + + Application + MultiByte + v120 + + + Application + MultiByte + v120 + + + Application + MultiByte + v120 + + + + + + + + + + + + + + + + + + + + + + + + + <_ProjectFileVersion>10.0.30128.1 + x86\TestZlibDll$(Configuration)\ + x86\TestZlibDll$(Configuration)\Tmp\ + true + false + x86\TestZlibDll$(Configuration)\ + x86\TestZlibDll$(Configuration)\Tmp\ + false + false + x64\TestZlibDll$(Configuration)\ + x64\TestZlibDll$(Configuration)\Tmp\ + true + false + ia64\TestZlibDll$(Configuration)\ + ia64\TestZlibDll$(Configuration)\Tmp\ + true + false + x64\TestZlibDll$(Configuration)\ + x64\TestZlibDll$(Configuration)\Tmp\ + false + false + ia64\TestZlibDll$(Configuration)\ + ia64\TestZlibDll$(Configuration)\Tmp\ + false + false + AllRules.ruleset + + + AllRules.ruleset + + + AllRules.ruleset + + + AllRules.ruleset + + + AllRules.ruleset + + + AllRules.ruleset + + + + + + Disabled + ..\..\..;..\..\minizip;%(AdditionalIncludeDirectories) + WIN32;_CRT_NONSTDC_NO_DEPRECATE;_CRT_SECURE_NO_DEPRECATE;ZLIB_WINAPI;_DEBUG;_CONSOLE;%(PreprocessorDefinitions) + true + Default + MultiThreadedDebugDLL + false + + + $(IntDir) + Level3 + ProgramDatabase + + + x86\ZlibDllDebug\zlibwapi.lib;%(AdditionalDependencies) + $(OutDir)testzlibdll.exe + true + $(OutDir)testzlib.pdb + Console + false + + + MachineX86 + + + + + MaxSpeed + OnlyExplicitInline + true + ..\..\..;..\..\minizip;%(AdditionalIncludeDirectories) + WIN32;_CRT_NONSTDC_NO_DEPRECATE;_CRT_SECURE_NO_DEPRECATE;ZLIB_WINAPI;NDEBUG;_CONSOLE;%(PreprocessorDefinitions) + true + Default + MultiThreaded + false + true + + + $(IntDir) + Level3 + ProgramDatabase + + + x86\ZlibDllRelease\zlibwapi.lib;%(AdditionalDependencies) + $(OutDir)testzlibdll.exe + true + Console + true + true + false + + + MachineX86 + + + + + X64 + + + Disabled + ..\..\..;..\..\minizip;%(AdditionalIncludeDirectories) + _CRT_NONSTDC_NO_DEPRECATE;_CRT_SECURE_NO_DEPRECATE;ZLIB_WINAPI;_DEBUG;_CONSOLE;WIN64;%(PreprocessorDefinitions) + true + Default + MultiThreadedDebugDLL + false + + + $(IntDir) + Level3 + ProgramDatabase + + + x64\ZlibDllDebug\zlibwapi.lib;%(AdditionalDependencies) + $(OutDir)testzlibdll.exe + true + $(OutDir)testzlib.pdb + Console + MachineX64 + + + + + Itanium + + + Disabled + ..\..\..;..\..\minizip;%(AdditionalIncludeDirectories) + _CRT_NONSTDC_NO_DEPRECATE;_CRT_SECURE_NO_DEPRECATE;ZLIB_WINAPI;_DEBUG;_CONSOLE;WIN64;%(PreprocessorDefinitions) + true + Default + MultiThreadedDebugDLL + false + + + $(IntDir) + Level3 + ProgramDatabase + + + ia64\ZlibDllDebug\zlibwapi.lib;%(AdditionalDependencies) + $(OutDir)testzlibdll.exe + true + $(OutDir)testzlib.pdb + Console + MachineIA64 + + + + + X64 + + + MaxSpeed + OnlyExplicitInline + true + ..\..\..;..\..\minizip;%(AdditionalIncludeDirectories) + _CRT_NONSTDC_NO_DEPRECATE;_CRT_SECURE_NO_DEPRECATE;ZLIB_WINAPI;NDEBUG;_CONSOLE;WIN64;%(PreprocessorDefinitions) + true + Default + MultiThreadedDLL + false + true + + + $(IntDir) + Level3 + ProgramDatabase + + + x64\ZlibDllRelease\zlibwapi.lib;%(AdditionalDependencies) + $(OutDir)testzlibdll.exe + true + Console + true + true + MachineX64 + + + + + Itanium + + + MaxSpeed + OnlyExplicitInline + true + ..\..\..;..\..\minizip;%(AdditionalIncludeDirectories) + _CRT_NONSTDC_NO_DEPRECATE;_CRT_SECURE_NO_DEPRECATE;ZLIB_WINAPI;NDEBUG;_CONSOLE;WIN64;%(PreprocessorDefinitions) + true + Default + MultiThreadedDLL + false + true + + + $(IntDir) + Level3 + ProgramDatabase + + + ia64\ZlibDllRelease\zlibwapi.lib;%(AdditionalDependencies) + $(OutDir)testzlibdll.exe + true + Console + true + true + MachineIA64 + + + + + + + + {8fd826f8-3739-44e6-8cc8-997122e53b8d} + + + + + + ADDED compat/zlib/contrib/vstudio/vc12/zlib.rc Index: compat/zlib/contrib/vstudio/vc12/zlib.rc ================================================================== --- /dev/null +++ compat/zlib/contrib/vstudio/vc12/zlib.rc @@ -0,0 +1,32 @@ +#include + +#define IDR_VERSION1 1 +IDR_VERSION1 VERSIONINFO MOVEABLE IMPURE LOADONCALL DISCARDABLE + FILEVERSION 1, 2, 11, 0 + PRODUCTVERSION 1, 2, 11, 0 + FILEFLAGSMASK VS_FFI_FILEFLAGSMASK + FILEFLAGS 0 + FILEOS VOS_DOS_WINDOWS32 + FILETYPE VFT_DLL + FILESUBTYPE 0 // not used +BEGIN + BLOCK "StringFileInfo" + BEGIN + BLOCK "040904E4" + //language ID = U.S. English, char set = Windows, Multilingual + + BEGIN + VALUE "FileDescription", "zlib data compression and ZIP file I/O library\0" + VALUE "FileVersion", "1.2.11\0" + VALUE "InternalName", "zlib\0" + VALUE "OriginalFilename", "zlibwapi.dll\0" + VALUE "ProductName", "ZLib.DLL\0" + VALUE "Comments","DLL support by Alessandro Iacopetti & Gilles Vollant\0" + VALUE "LegalCopyright", "(C) 1995-2017 Jean-loup Gailly & Mark Adler\0" + END + END + BLOCK "VarFileInfo" + BEGIN + VALUE "Translation", 0x0409, 1252 + END +END ADDED compat/zlib/contrib/vstudio/vc12/zlibstat.vcxproj Index: compat/zlib/contrib/vstudio/vc12/zlibstat.vcxproj ================================================================== --- /dev/null +++ compat/zlib/contrib/vstudio/vc12/zlibstat.vcxproj @@ -0,0 +1,467 @@ + + + + + Debug + Itanium + + + Debug + Win32 + + + Debug + x64 + + + ReleaseWithoutAsm + Itanium + + + ReleaseWithoutAsm + Win32 + + + ReleaseWithoutAsm + x64 + + + Release + Itanium + + + Release + Win32 + + + Release + x64 + + + + {745DEC58-EBB3-47A9-A9B8-4C6627C01BF8} + + + + StaticLibrary + false + v120 + + + StaticLibrary + false + v120 + + + StaticLibrary + false + v120 + Unicode + + + StaticLibrary + false + v120 + + + StaticLibrary + false + v120 + + + StaticLibrary + false + v120 + + + StaticLibrary + false + v120 + + + StaticLibrary + false + v120 + + + StaticLibrary + false + v120 + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + <_ProjectFileVersion>10.0.30128.1 + x86\ZlibStat$(Configuration)\ + x86\ZlibStat$(Configuration)\Tmp\ + x86\ZlibStat$(Configuration)\ + x86\ZlibStat$(Configuration)\Tmp\ + x86\ZlibStat$(Configuration)\ + x86\ZlibStat$(Configuration)\Tmp\ + x64\ZlibStat$(Configuration)\ + x64\ZlibStat$(Configuration)\Tmp\ + ia64\ZlibStat$(Configuration)\ + ia64\ZlibStat$(Configuration)\Tmp\ + x64\ZlibStat$(Configuration)\ + x64\ZlibStat$(Configuration)\Tmp\ + ia64\ZlibStat$(Configuration)\ + ia64\ZlibStat$(Configuration)\Tmp\ + x64\ZlibStat$(Configuration)\ + x64\ZlibStat$(Configuration)\Tmp\ + ia64\ZlibStat$(Configuration)\ + ia64\ZlibStat$(Configuration)\Tmp\ + AllRules.ruleset + + + AllRules.ruleset + + + AllRules.ruleset + + + AllRules.ruleset + + + AllRules.ruleset + + + AllRules.ruleset + + + AllRules.ruleset + + + AllRules.ruleset + + + AllRules.ruleset + + + + + + Disabled + ..\..\..;..\..\masmx86;%(AdditionalIncludeDirectories) + WIN32;ZLIB_WINAPI;_CRT_NONSTDC_NO_DEPRECATE;_CRT_SECURE_NO_DEPRECATE;_CRT_NONSTDC_NO_WARNINGS;%(PreprocessorDefinitions) + + + MultiThreadedDebugDLL + false + $(IntDir)zlibstat.pch + $(IntDir) + $(IntDir) + $(OutDir) + Level3 + true + OldStyle + + + 0x040c + + + /MACHINE:X86 /NODEFAULTLIB %(AdditionalOptions) + $(OutDir)zlibstat.lib + true + + + + + OnlyExplicitInline + ..\..\..;..\..\masmx86;%(AdditionalIncludeDirectories) + WIN32;ZLIB_WINAPI;_CRT_NONSTDC_NO_DEPRECATE;_CRT_SECURE_NO_DEPRECATE;_CRT_NONSTDC_NO_WARNINGS;ASMV;ASMINF;%(PreprocessorDefinitions) + true + + + MultiThreaded + false + true + $(IntDir)zlibstat.pch + $(IntDir) + $(IntDir) + $(OutDir) + Level3 + true + + + 0x040c + + + /MACHINE:X86 /NODEFAULTLIB %(AdditionalOptions) + ..\..\masmx86\match686.obj;..\..\masmx86\inffas32.obj;%(AdditionalDependencies) + $(OutDir)zlibstat.lib + true + + + + + OnlyExplicitInline + ..\..\..;..\..\masmx86;%(AdditionalIncludeDirectories) + WIN32;ZLIB_WINAPI;_CRT_NONSTDC_NO_DEPRECATE;_CRT_SECURE_NO_DEPRECATE;_CRT_NONSTDC_NO_WARNINGS;%(PreprocessorDefinitions) + true + + + MultiThreaded + false + true + $(IntDir)zlibstat.pch + $(IntDir) + $(IntDir) + $(OutDir) + Level3 + true + + + 0x040c + + + /MACHINE:X86 /NODEFAULTLIB %(AdditionalOptions) + $(OutDir)zlibstat.lib + true + + + + + X64 + + + Disabled + ..\..\..;..\..\masmx86;%(AdditionalIncludeDirectories) + ZLIB_WINAPI;_CRT_NONSTDC_NO_DEPRECATE;_CRT_SECURE_NO_DEPRECATE;_CRT_NONSTDC_NO_WARNINGS;WIN64;%(PreprocessorDefinitions) + + + MultiThreadedDebugDLL + false + $(IntDir)zlibstat.pch + $(IntDir) + $(IntDir) + $(OutDir) + Level3 + true + OldStyle + + + 0x040c + + + /MACHINE:AMD64 /NODEFAULTLIB %(AdditionalOptions) + $(OutDir)zlibstat.lib + true + + + + + Itanium + + + Disabled + ..\..\..;..\..\masmx86;%(AdditionalIncludeDirectories) + ZLIB_WINAPI;_CRT_NONSTDC_NO_DEPRECATE;_CRT_SECURE_NO_DEPRECATE;_CRT_NONSTDC_NO_WARNINGS;WIN64;%(PreprocessorDefinitions) + + + MultiThreadedDebugDLL + false + $(IntDir)zlibstat.pch + $(IntDir) + $(IntDir) + $(OutDir) + Level3 + true + OldStyle + + + 0x040c + + + /MACHINE:IA64 /NODEFAULTLIB %(AdditionalOptions) + $(OutDir)zlibstat.lib + true + + + + + X64 + + + OnlyExplicitInline + ..\..\..;..\..\masmx86;%(AdditionalIncludeDirectories) + ZLIB_WINAPI;_CRT_NONSTDC_NO_DEPRECATE;_CRT_SECURE_NO_DEPRECATE;_CRT_NONSTDC_NO_WARNINGS;ASMV;ASMINF;WIN64;%(PreprocessorDefinitions) + true + + + MultiThreadedDLL + false + true + $(IntDir)zlibstat.pch + $(IntDir) + $(IntDir) + $(OutDir) + Level3 + true + + + 0x040c + + + /MACHINE:AMD64 /NODEFAULTLIB %(AdditionalOptions) + ..\..\masmx64\gvmat64.obj;..\..\masmx64\inffasx64.obj;%(AdditionalDependencies) + $(OutDir)zlibstat.lib + true + + + + + Itanium + + + OnlyExplicitInline + ..\..\..;..\..\masmx86;%(AdditionalIncludeDirectories) + ZLIB_WINAPI;_CRT_NONSTDC_NO_DEPRECATE;_CRT_SECURE_NO_DEPRECATE;_CRT_NONSTDC_NO_WARNINGS;WIN64;%(PreprocessorDefinitions) + true + + + MultiThreadedDLL + false + true + $(IntDir)zlibstat.pch + $(IntDir) + $(IntDir) + $(OutDir) + Level3 + true + + + 0x040c + + + /MACHINE:IA64 /NODEFAULTLIB %(AdditionalOptions) + $(OutDir)zlibstat.lib + true + + + + + X64 + + + OnlyExplicitInline + ..\..\..;..\..\masmx86;%(AdditionalIncludeDirectories) + ZLIB_WINAPI;_CRT_NONSTDC_NO_DEPRECATE;_CRT_SECURE_NO_DEPRECATE;_CRT_NONSTDC_NO_WARNINGS;WIN64;%(PreprocessorDefinitions) + true + + + MultiThreadedDLL + false + true + $(IntDir)zlibstat.pch + $(IntDir) + $(IntDir) + $(OutDir) + Level3 + true + + + 0x040c + + + /MACHINE:AMD64 /NODEFAULTLIB %(AdditionalOptions) + $(OutDir)zlibstat.lib + true + + + + + Itanium + + + OnlyExplicitInline + ..\..\..;..\..\masmx86;%(AdditionalIncludeDirectories) + ZLIB_WINAPI;_CRT_NONSTDC_NO_DEPRECATE;_CRT_SECURE_NO_DEPRECATE;_CRT_NONSTDC_NO_WARNINGS;WIN64;%(PreprocessorDefinitions) + true + + + MultiThreadedDLL + false + true + $(IntDir)zlibstat.pch + $(IntDir) + $(IntDir) + $(OutDir) + Level3 + true + + + 0x040c + + + /MACHINE:IA64 /NODEFAULTLIB %(AdditionalOptions) + $(OutDir)zlibstat.lib + true + + + + + + + + + + + + + + true + true + true + true + true + true + + + + + + + + + + + + + + + + + + + + + ADDED compat/zlib/contrib/vstudio/vc12/zlibvc.def Index: compat/zlib/contrib/vstudio/vc12/zlibvc.def ================================================================== --- /dev/null +++ compat/zlib/contrib/vstudio/vc12/zlibvc.def @@ -0,0 +1,153 @@ +LIBRARY +; zlib data compression and ZIP file I/O library + +VERSION 1.2 + +EXPORTS + adler32 @1 + compress @2 + crc32 @3 + deflate @4 + deflateCopy @5 + deflateEnd @6 + deflateInit2_ @7 + deflateInit_ @8 + deflateParams @9 + deflateReset @10 + deflateSetDictionary @11 + gzclose @12 + gzdopen @13 + gzerror @14 + gzflush @15 + gzopen @16 + gzread @17 + gzwrite @18 + inflate @19 + inflateEnd @20 + inflateInit2_ @21 + inflateInit_ @22 + inflateReset @23 + inflateSetDictionary @24 + inflateSync @25 + uncompress @26 + zlibVersion @27 + gzprintf @28 + gzputc @29 + gzgetc @30 + gzseek @31 + gzrewind @32 + gztell @33 + gzeof @34 + gzsetparams @35 + zError @36 + inflateSyncPoint @37 + get_crc_table @38 + compress2 @39 + gzputs @40 + gzgets @41 + inflateCopy @42 + inflateBackInit_ @43 + inflateBack @44 + inflateBackEnd @45 + compressBound @46 + deflateBound @47 + gzclearerr @48 + gzungetc @49 + zlibCompileFlags @50 + deflatePrime @51 + deflatePending @52 + + unzOpen @61 + unzClose @62 + unzGetGlobalInfo @63 + unzGetCurrentFileInfo @64 + unzGoToFirstFile @65 + unzGoToNextFile @66 + unzOpenCurrentFile @67 + unzReadCurrentFile @68 + unzOpenCurrentFile3 @69 + unztell @70 + unzeof @71 + unzCloseCurrentFile @72 + unzGetGlobalComment @73 + unzStringFileNameCompare @74 + unzLocateFile @75 + unzGetLocalExtrafield @76 + unzOpen2 @77 + unzOpenCurrentFile2 @78 + unzOpenCurrentFilePassword @79 + + zipOpen @80 + zipOpenNewFileInZip @81 + zipWriteInFileInZip @82 + zipCloseFileInZip @83 + zipClose @84 + zipOpenNewFileInZip2 @86 + zipCloseFileInZipRaw @87 + zipOpen2 @88 + zipOpenNewFileInZip3 @89 + + unzGetFilePos @100 + unzGoToFilePos @101 + + fill_win32_filefunc @110 + +; zlibwapi v1.2.4 added: + fill_win32_filefunc64 @111 + fill_win32_filefunc64A @112 + fill_win32_filefunc64W @113 + + unzOpen64 @120 + unzOpen2_64 @121 + unzGetGlobalInfo64 @122 + unzGetCurrentFileInfo64 @124 + unzGetCurrentFileZStreamPos64 @125 + unztell64 @126 + unzGetFilePos64 @127 + unzGoToFilePos64 @128 + + zipOpen64 @130 + zipOpen2_64 @131 + zipOpenNewFileInZip64 @132 + zipOpenNewFileInZip2_64 @133 + zipOpenNewFileInZip3_64 @134 + zipOpenNewFileInZip4_64 @135 + zipCloseFileInZipRaw64 @136 + +; zlib1 v1.2.4 added: + adler32_combine @140 + crc32_combine @142 + deflateSetHeader @144 + deflateTune @145 + gzbuffer @146 + gzclose_r @147 + gzclose_w @148 + gzdirect @149 + gzoffset @150 + inflateGetHeader @156 + inflateMark @157 + inflatePrime @158 + inflateReset2 @159 + inflateUndermine @160 + +; zlib1 v1.2.6 added: + gzgetc_ @161 + inflateResetKeep @163 + deflateResetKeep @164 + +; zlib1 v1.2.7 added: + gzopen_w @165 + +; zlib1 v1.2.8 added: + inflateGetDictionary @166 + gzvprintf @167 + +; zlib1 v1.2.9 added: + inflateCodesUsed @168 + inflateValidate @169 + uncompress2 @170 + gzfread @171 + gzfwrite @172 + deflateGetDictionary @173 + adler32_z @174 + crc32_z @175 ADDED compat/zlib/contrib/vstudio/vc12/zlibvc.sln Index: compat/zlib/contrib/vstudio/vc12/zlibvc.sln ================================================================== --- /dev/null +++ compat/zlib/contrib/vstudio/vc12/zlibvc.sln @@ -0,0 +1,119 @@ + +Microsoft Visual Studio Solution File, Format Version 12.00 +# Visual Studio 2013 +VisualStudioVersion = 12.0.40629.0 +MinimumVisualStudioVersion = 10.0.40219.1 +Project("{8BC9CEB8-8B4A-11D0-8D11-00A0C91BC942}") = "zlibvc", "zlibvc.vcxproj", "{8FD826F8-3739-44E6-8CC8-997122E53B8D}" +EndProject +Project("{8BC9CEB8-8B4A-11D0-8D11-00A0C91BC942}") = "zlibstat", "zlibstat.vcxproj", "{745DEC58-EBB3-47A9-A9B8-4C6627C01BF8}" +EndProject +Project("{8BC9CEB8-8B4A-11D0-8D11-00A0C91BC942}") = "testzlib", "testzlib.vcxproj", "{AA6666AA-E09F-4135-9C0C-4FE50C3C654B}" +EndProject +Project("{8BC9CEB8-8B4A-11D0-8D11-00A0C91BC942}") = "testzlibdll", "testzlibdll.vcxproj", "{C52F9E7B-498A-42BE-8DB4-85A15694366A}" +EndProject +Project("{8BC9CEB8-8B4A-11D0-8D11-00A0C91BC942}") = "minizip", "minizip.vcxproj", "{48CDD9DC-E09F-4135-9C0C-4FE50C3C654B}" +EndProject +Project("{8BC9CEB8-8B4A-11D0-8D11-00A0C91BC942}") = "miniunz", "miniunz.vcxproj", "{C52F9E7B-498A-42BE-8DB4-85A15694382A}" +EndProject +Global + GlobalSection(SolutionConfigurationPlatforms) = preSolution + Debug|Itanium = Debug|Itanium + Debug|Win32 = Debug|Win32 + Debug|x64 = Debug|x64 + Release|Itanium = Release|Itanium + Release|Win32 = Release|Win32 + Release|x64 = Release|x64 + ReleaseWithoutAsm|Itanium = ReleaseWithoutAsm|Itanium + ReleaseWithoutAsm|Win32 = ReleaseWithoutAsm|Win32 + ReleaseWithoutAsm|x64 = ReleaseWithoutAsm|x64 + EndGlobalSection + GlobalSection(ProjectConfigurationPlatforms) = postSolution + {8FD826F8-3739-44E6-8CC8-997122E53B8D}.Debug|Itanium.ActiveCfg = Debug|Win32 + {8FD826F8-3739-44E6-8CC8-997122E53B8D}.Debug|Win32.ActiveCfg = Debug|Win32 + {8FD826F8-3739-44E6-8CC8-997122E53B8D}.Debug|Win32.Build.0 = Debug|Win32 + {8FD826F8-3739-44E6-8CC8-997122E53B8D}.Debug|x64.ActiveCfg = Debug|x64 + {8FD826F8-3739-44E6-8CC8-997122E53B8D}.Debug|x64.Build.0 = Debug|x64 + {8FD826F8-3739-44E6-8CC8-997122E53B8D}.Release|Itanium.ActiveCfg = Release|Win32 + {8FD826F8-3739-44E6-8CC8-997122E53B8D}.Release|Win32.ActiveCfg = Release|Win32 + {8FD826F8-3739-44E6-8CC8-997122E53B8D}.Release|Win32.Build.0 = Release|Win32 + {8FD826F8-3739-44E6-8CC8-997122E53B8D}.Release|x64.ActiveCfg = Release|x64 + {8FD826F8-3739-44E6-8CC8-997122E53B8D}.Release|x64.Build.0 = Release|x64 + {8FD826F8-3739-44E6-8CC8-997122E53B8D}.ReleaseWithoutAsm|Itanium.ActiveCfg = ReleaseWithoutAsm|Win32 + {8FD826F8-3739-44E6-8CC8-997122E53B8D}.ReleaseWithoutAsm|Win32.ActiveCfg = ReleaseWithoutAsm|Win32 + {8FD826F8-3739-44E6-8CC8-997122E53B8D}.ReleaseWithoutAsm|Win32.Build.0 = ReleaseWithoutAsm|Win32 + {8FD826F8-3739-44E6-8CC8-997122E53B8D}.ReleaseWithoutAsm|x64.ActiveCfg = ReleaseWithoutAsm|x64 + {8FD826F8-3739-44E6-8CC8-997122E53B8D}.ReleaseWithoutAsm|x64.Build.0 = ReleaseWithoutAsm|x64 + {745DEC58-EBB3-47A9-A9B8-4C6627C01BF8}.Debug|Itanium.ActiveCfg = Debug|Win32 + {745DEC58-EBB3-47A9-A9B8-4C6627C01BF8}.Debug|Win32.ActiveCfg = Debug|Win32 + {745DEC58-EBB3-47A9-A9B8-4C6627C01BF8}.Debug|Win32.Build.0 = Debug|Win32 + {745DEC58-EBB3-47A9-A9B8-4C6627C01BF8}.Debug|x64.ActiveCfg = Debug|x64 + {745DEC58-EBB3-47A9-A9B8-4C6627C01BF8}.Debug|x64.Build.0 = Debug|x64 + {745DEC58-EBB3-47A9-A9B8-4C6627C01BF8}.Release|Itanium.ActiveCfg = Release|Win32 + {745DEC58-EBB3-47A9-A9B8-4C6627C01BF8}.Release|Win32.ActiveCfg = Release|Win32 + {745DEC58-EBB3-47A9-A9B8-4C6627C01BF8}.Release|Win32.Build.0 = Release|Win32 + {745DEC58-EBB3-47A9-A9B8-4C6627C01BF8}.Release|x64.ActiveCfg = Release|x64 + {745DEC58-EBB3-47A9-A9B8-4C6627C01BF8}.Release|x64.Build.0 = Release|x64 + {745DEC58-EBB3-47A9-A9B8-4C6627C01BF8}.ReleaseWithoutAsm|Itanium.ActiveCfg = ReleaseWithoutAsm|Win32 + {745DEC58-EBB3-47A9-A9B8-4C6627C01BF8}.ReleaseWithoutAsm|Win32.ActiveCfg = ReleaseWithoutAsm|Win32 + {745DEC58-EBB3-47A9-A9B8-4C6627C01BF8}.ReleaseWithoutAsm|Win32.Build.0 = ReleaseWithoutAsm|Win32 + {745DEC58-EBB3-47A9-A9B8-4C6627C01BF8}.ReleaseWithoutAsm|x64.ActiveCfg = ReleaseWithoutAsm|x64 + {745DEC58-EBB3-47A9-A9B8-4C6627C01BF8}.ReleaseWithoutAsm|x64.Build.0 = ReleaseWithoutAsm|x64 + {AA6666AA-E09F-4135-9C0C-4FE50C3C654B}.Debug|Itanium.ActiveCfg = Debug|Win32 + {AA6666AA-E09F-4135-9C0C-4FE50C3C654B}.Debug|Win32.ActiveCfg = Debug|Win32 + {AA6666AA-E09F-4135-9C0C-4FE50C3C654B}.Debug|Win32.Build.0 = Debug|Win32 + {AA6666AA-E09F-4135-9C0C-4FE50C3C654B}.Debug|x64.ActiveCfg = Debug|x64 + {AA6666AA-E09F-4135-9C0C-4FE50C3C654B}.Debug|x64.Build.0 = Debug|x64 + {AA6666AA-E09F-4135-9C0C-4FE50C3C654B}.Release|Itanium.ActiveCfg = Release|Win32 + {AA6666AA-E09F-4135-9C0C-4FE50C3C654B}.Release|Win32.ActiveCfg = Release|Win32 + {AA6666AA-E09F-4135-9C0C-4FE50C3C654B}.Release|Win32.Build.0 = Release|Win32 + {AA6666AA-E09F-4135-9C0C-4FE50C3C654B}.Release|x64.ActiveCfg = Release|x64 + {AA6666AA-E09F-4135-9C0C-4FE50C3C654B}.Release|x64.Build.0 = Release|x64 + {AA6666AA-E09F-4135-9C0C-4FE50C3C654B}.ReleaseWithoutAsm|Itanium.ActiveCfg = ReleaseWithoutAsm|Win32 + {AA6666AA-E09F-4135-9C0C-4FE50C3C654B}.ReleaseWithoutAsm|Win32.ActiveCfg = ReleaseWithoutAsm|Win32 + {AA6666AA-E09F-4135-9C0C-4FE50C3C654B}.ReleaseWithoutAsm|Win32.Build.0 = ReleaseWithoutAsm|Win32 + {AA6666AA-E09F-4135-9C0C-4FE50C3C654B}.ReleaseWithoutAsm|x64.ActiveCfg = ReleaseWithoutAsm|x64 + {AA6666AA-E09F-4135-9C0C-4FE50C3C654B}.ReleaseWithoutAsm|x64.Build.0 = ReleaseWithoutAsm|x64 + {C52F9E7B-498A-42BE-8DB4-85A15694366A}.Debug|Itanium.ActiveCfg = Debug|Win32 + {C52F9E7B-498A-42BE-8DB4-85A15694366A}.Debug|Win32.ActiveCfg = Debug|Win32 + {C52F9E7B-498A-42BE-8DB4-85A15694366A}.Debug|Win32.Build.0 = Debug|Win32 + {C52F9E7B-498A-42BE-8DB4-85A15694366A}.Debug|x64.ActiveCfg = Debug|x64 + {C52F9E7B-498A-42BE-8DB4-85A15694366A}.Debug|x64.Build.0 = Debug|x64 + {C52F9E7B-498A-42BE-8DB4-85A15694366A}.Release|Itanium.ActiveCfg = Release|Win32 + {C52F9E7B-498A-42BE-8DB4-85A15694366A}.Release|Win32.ActiveCfg = Release|Win32 + {C52F9E7B-498A-42BE-8DB4-85A15694366A}.Release|Win32.Build.0 = Release|Win32 + {C52F9E7B-498A-42BE-8DB4-85A15694366A}.Release|x64.ActiveCfg = Release|x64 + {C52F9E7B-498A-42BE-8DB4-85A15694366A}.Release|x64.Build.0 = Release|x64 + {C52F9E7B-498A-42BE-8DB4-85A15694366A}.ReleaseWithoutAsm|Itanium.ActiveCfg = Release|Win32 + {C52F9E7B-498A-42BE-8DB4-85A15694366A}.ReleaseWithoutAsm|Win32.ActiveCfg = Release|Win32 + {C52F9E7B-498A-42BE-8DB4-85A15694366A}.ReleaseWithoutAsm|x64.ActiveCfg = Release|x64 + {48CDD9DC-E09F-4135-9C0C-4FE50C3C654B}.Debug|Itanium.ActiveCfg = Debug|Win32 + {48CDD9DC-E09F-4135-9C0C-4FE50C3C654B}.Debug|Win32.ActiveCfg = Debug|Win32 + {48CDD9DC-E09F-4135-9C0C-4FE50C3C654B}.Debug|Win32.Build.0 = Debug|Win32 + {48CDD9DC-E09F-4135-9C0C-4FE50C3C654B}.Debug|x64.ActiveCfg = Debug|x64 + {48CDD9DC-E09F-4135-9C0C-4FE50C3C654B}.Debug|x64.Build.0 = Debug|x64 + {48CDD9DC-E09F-4135-9C0C-4FE50C3C654B}.Release|Itanium.ActiveCfg = Release|Win32 + {48CDD9DC-E09F-4135-9C0C-4FE50C3C654B}.Release|Win32.ActiveCfg = Release|Win32 + {48CDD9DC-E09F-4135-9C0C-4FE50C3C654B}.Release|Win32.Build.0 = Release|Win32 + {48CDD9DC-E09F-4135-9C0C-4FE50C3C654B}.Release|x64.ActiveCfg = Release|x64 + {48CDD9DC-E09F-4135-9C0C-4FE50C3C654B}.Release|x64.Build.0 = Release|x64 + {48CDD9DC-E09F-4135-9C0C-4FE50C3C654B}.ReleaseWithoutAsm|Itanium.ActiveCfg = Release|Win32 + {48CDD9DC-E09F-4135-9C0C-4FE50C3C654B}.ReleaseWithoutAsm|Win32.ActiveCfg = Release|Win32 + {48CDD9DC-E09F-4135-9C0C-4FE50C3C654B}.ReleaseWithoutAsm|x64.ActiveCfg = Release|x64 + {C52F9E7B-498A-42BE-8DB4-85A15694382A}.Debug|Itanium.ActiveCfg = Debug|Win32 + {C52F9E7B-498A-42BE-8DB4-85A15694382A}.Debug|Win32.ActiveCfg = Debug|Win32 + {C52F9E7B-498A-42BE-8DB4-85A15694382A}.Debug|Win32.Build.0 = Debug|Win32 + {C52F9E7B-498A-42BE-8DB4-85A15694382A}.Debug|x64.ActiveCfg = Debug|x64 + {C52F9E7B-498A-42BE-8DB4-85A15694382A}.Debug|x64.Build.0 = Debug|x64 + {C52F9E7B-498A-42BE-8DB4-85A15694382A}.Release|Itanium.ActiveCfg = Release|Win32 + {C52F9E7B-498A-42BE-8DB4-85A15694382A}.Release|Win32.ActiveCfg = Release|Win32 + {C52F9E7B-498A-42BE-8DB4-85A15694382A}.Release|Win32.Build.0 = Release|Win32 + {C52F9E7B-498A-42BE-8DB4-85A15694382A}.Release|x64.ActiveCfg = Release|x64 + {C52F9E7B-498A-42BE-8DB4-85A15694382A}.Release|x64.Build.0 = Release|x64 + {C52F9E7B-498A-42BE-8DB4-85A15694382A}.ReleaseWithoutAsm|Itanium.ActiveCfg = Release|Win32 + {C52F9E7B-498A-42BE-8DB4-85A15694382A}.ReleaseWithoutAsm|Win32.ActiveCfg = Release|Win32 + {C52F9E7B-498A-42BE-8DB4-85A15694382A}.ReleaseWithoutAsm|x64.ActiveCfg = Release|x64 + EndGlobalSection + GlobalSection(SolutionProperties) = preSolution + HideSolutionNode = FALSE + EndGlobalSection +EndGlobal ADDED compat/zlib/contrib/vstudio/vc12/zlibvc.vcxproj Index: compat/zlib/contrib/vstudio/vc12/zlibvc.vcxproj ================================================================== --- /dev/null +++ compat/zlib/contrib/vstudio/vc12/zlibvc.vcxproj @@ -0,0 +1,692 @@ + + + + + Debug + Itanium + + + Debug + Win32 + + + Debug + x64 + + + ReleaseWithoutAsm + Itanium + + + ReleaseWithoutAsm + Win32 + + + ReleaseWithoutAsm + x64 + + + Release + Itanium + + + Release + Win32 + + + Release + x64 + + + + {8FD826F8-3739-44E6-8CC8-997122E53B8D} + + + + DynamicLibrary + false + true + v120 + + + DynamicLibrary + false + true + v120 + + + DynamicLibrary + false + v120 + Unicode + + + DynamicLibrary + false + true + v120 + + + DynamicLibrary + false + true + v120 + + + DynamicLibrary + false + v120 + + + DynamicLibrary + false + true + v120 + + + DynamicLibrary + false + true + v120 + + + DynamicLibrary + false + v120 + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + <_ProjectFileVersion>10.0.30128.1 + x86\ZlibDll$(Configuration)\ + x86\ZlibDll$(Configuration)\Tmp\ + true + false + x86\ZlibDll$(Configuration)\ + x86\ZlibDll$(Configuration)\Tmp\ + false + false + x86\ZlibDll$(Configuration)\ + x86\ZlibDll$(Configuration)\Tmp\ + false + false + x64\ZlibDll$(Configuration)\ + x64\ZlibDll$(Configuration)\Tmp\ + true + false + ia64\ZlibDll$(Configuration)\ + ia64\ZlibDll$(Configuration)\Tmp\ + true + false + x64\ZlibDll$(Configuration)\ + x64\ZlibDll$(Configuration)\Tmp\ + false + false + ia64\ZlibDll$(Configuration)\ + ia64\ZlibDll$(Configuration)\Tmp\ + false + false + x64\ZlibDll$(Configuration)\ + x64\ZlibDll$(Configuration)\Tmp\ + false + false + ia64\ZlibDll$(Configuration)\ + ia64\ZlibDll$(Configuration)\Tmp\ + false + false + AllRules.ruleset + + + AllRules.ruleset + + + AllRules.ruleset + + + AllRules.ruleset + + + AllRules.ruleset + + + AllRules.ruleset + + + AllRules.ruleset + + + AllRules.ruleset + + + AllRules.ruleset + + + zlibwapi + zlibwapi + zlibwapi + zlibwapi + zlibwapi + zlibwapi + + + + _DEBUG;%(PreprocessorDefinitions) + true + true + Win32 + $(OutDir)zlibvc.tlb + + + Disabled + ..\..\..;..\..\masmx86;%(AdditionalIncludeDirectories) + WIN32;_CRT_NONSTDC_NO_DEPRECATE;_CRT_SECURE_NO_DEPRECATE;_CRT_NONSTDC_NO_WARNINGS;ZLIB_WINAPI;ASMV;ASMINF;%(PreprocessorDefinitions) + + + MultiThreadedDebugDLL + false + $(IntDir)zlibvc.pch + $(IntDir) + $(IntDir) + $(OutDir) + + + Level3 + true + ProgramDatabase + + + _DEBUG;%(PreprocessorDefinitions) + 0x040c + + + /MACHINE:I386 %(AdditionalOptions) + ..\..\masmx86\match686.obj;..\..\masmx86\inffas32.obj;%(AdditionalDependencies) + $(OutDir)zlibwapi.dll + true + .\zlibvc.def + true + $(OutDir)zlibwapi.pdb + true + $(OutDir)zlibwapi.map + Windows + false + + + $(OutDir)zlibwapi.lib + + + cd ..\..\masmx86 +bld_ml32.bat + + + + + NDEBUG;%(PreprocessorDefinitions) + true + true + Win32 + $(OutDir)zlibvc.tlb + + + OnlyExplicitInline + ..\..\..;..\..\masmx86;%(AdditionalIncludeDirectories) + WIN32;_CRT_NONSTDC_NO_DEPRECATE;_CRT_SECURE_NO_DEPRECATE;_CRT_NONSTDC_NO_WARNINGS;ZLIB_WINAPI;%(PreprocessorDefinitions) + true + + + MultiThreadedDLL + false + true + $(IntDir)zlibvc.pch + All + $(IntDir) + $(IntDir) + $(OutDir) + + + Level3 + true + + + NDEBUG;%(PreprocessorDefinitions) + 0x040c + + + /MACHINE:I386 %(AdditionalOptions) + $(OutDir)zlibwapi.dll + true + false + .\zlibvc.def + $(OutDir)zlibwapi.pdb + true + $(OutDir)zlibwapi.map + Windows + false + + + $(OutDir)zlibwapi.lib + + + + + NDEBUG;%(PreprocessorDefinitions) + true + true + Win32 + $(OutDir)zlibvc.tlb + + + OnlyExplicitInline + ..\..\..;..\..\masmx86;%(AdditionalIncludeDirectories) + WIN32;_CRT_NONSTDC_NO_DEPRECATE;_CRT_SECURE_NO_DEPRECATE;_CRT_NONSTDC_NO_WARNINGS;ZLIB_WINAPI;ASMV;ASMINF;%(PreprocessorDefinitions) + true + + + MultiThreaded + false + true + $(IntDir)zlibvc.pch + All + $(IntDir) + $(IntDir) + $(OutDir) + + + Level3 + true + + + NDEBUG;%(PreprocessorDefinitions) + 0x040c + + + /MACHINE:I386 %(AdditionalOptions) + ..\..\masmx86\match686.obj;..\..\masmx86\inffas32.obj;%(AdditionalDependencies) + $(OutDir)zlibwapi.dll + true + false + .\zlibvc.def + $(OutDir)zlibwapi.pdb + true + $(OutDir)zlibwapi.map + Windows + false + + + $(OutDir)zlibwapi.lib + false + + + cd ..\..\masmx86 +bld_ml32.bat + + + + + _DEBUG;%(PreprocessorDefinitions) + true + true + X64 + $(OutDir)zlibvc.tlb + + + Disabled + ..\..\..;..\..\masmx86;%(AdditionalIncludeDirectories) + WIN32;_CRT_NONSTDC_NO_DEPRECATE;_CRT_SECURE_NO_DEPRECATE;_CRT_NONSTDC_NO_WARNINGS;ZLIB_WINAPI;ASMV;ASMINF;WIN64;%(PreprocessorDefinitions) + + + MultiThreadedDebugDLL + false + $(IntDir)zlibvc.pch + $(IntDir) + $(IntDir) + $(OutDir) + + + Level3 + true + ProgramDatabase + + + _DEBUG;%(PreprocessorDefinitions) + 0x040c + + + ..\..\masmx64\gvmat64.obj;..\..\masmx64\inffasx64.obj;%(AdditionalDependencies) + $(OutDir)zlibwapi.dll + true + .\zlibvc.def + true + $(OutDir)zlibwapi.pdb + true + $(OutDir)zlibwapi.map + Windows + $(OutDir)zlibwapi.lib + MachineX64 + + + cd ..\..\contrib\masmx64 +bld_ml64.bat + + + + + _DEBUG;%(PreprocessorDefinitions) + true + true + Itanium + $(OutDir)zlibvc.tlb + + + Disabled + ..\..\..;..\..\masmx86;%(AdditionalIncludeDirectories) + WIN32;_CRT_NONSTDC_NO_DEPRECATE;_CRT_SECURE_NO_DEPRECATE;_CRT_NONSTDC_NO_WARNINGS;ZLIB_WINAPI;WIN64;%(PreprocessorDefinitions) + + + MultiThreadedDebugDLL + false + $(IntDir)zlibvc.pch + $(IntDir) + $(IntDir) + $(OutDir) + + + Level3 + true + ProgramDatabase + + + _DEBUG;%(PreprocessorDefinitions) + 0x040c + + + $(OutDir)zlibwapi.dll + true + .\zlibvc.def + true + $(OutDir)zlibwapi.pdb + true + $(OutDir)zlibwapi.map + Windows + $(OutDir)zlibwapi.lib + MachineIA64 + + + + + NDEBUG;%(PreprocessorDefinitions) + true + true + X64 + $(OutDir)zlibvc.tlb + + + OnlyExplicitInline + ..\..\..;..\..\masmx86;%(AdditionalIncludeDirectories) + WIN32;_CRT_NONSTDC_NO_DEPRECATE;_CRT_SECURE_NO_DEPRECATE;_CRT_NONSTDC_NO_WARNINGS;ZLIB_WINAPI;WIN64;%(PreprocessorDefinitions) + true + + + MultiThreadedDLL + false + true + $(IntDir)zlibvc.pch + All + $(IntDir) + $(IntDir) + $(OutDir) + + + Level3 + true + + + NDEBUG;%(PreprocessorDefinitions) + 0x040c + + + $(OutDir)zlibwapi.dll + true + false + .\zlibvc.def + $(OutDir)zlibwapi.pdb + true + $(OutDir)zlibwapi.map + Windows + $(OutDir)zlibwapi.lib + MachineX64 + + + + + NDEBUG;%(PreprocessorDefinitions) + true + true + Itanium + $(OutDir)zlibvc.tlb + + + OnlyExplicitInline + ..\..\..;..\..\masmx86;%(AdditionalIncludeDirectories) + WIN32;_CRT_NONSTDC_NO_DEPRECATE;_CRT_SECURE_NO_DEPRECATE;_CRT_NONSTDC_NO_WARNINGS;ZLIB_WINAPI;WIN64;%(PreprocessorDefinitions) + true + + + MultiThreadedDLL + false + true + $(IntDir)zlibvc.pch + All + $(IntDir) + $(IntDir) + $(OutDir) + + + Level3 + true + + + NDEBUG;%(PreprocessorDefinitions) + 0x040c + + + $(OutDir)zlibwapi.dll + true + false + .\zlibvc.def + $(OutDir)zlibwapi.pdb + true + $(OutDir)zlibwapi.map + Windows + $(OutDir)zlibwapi.lib + MachineIA64 + + + + + NDEBUG;%(PreprocessorDefinitions) + true + true + X64 + $(OutDir)zlibvc.tlb + + + OnlyExplicitInline + ..\..\..;..\..\masmx86;%(AdditionalIncludeDirectories) + _CRT_NONSTDC_NO_DEPRECATE;_CRT_SECURE_NO_DEPRECATE;_CRT_NONSTDC_NO_WARNINGS;ZLIB_WINAPI;ASMV;ASMINF;WIN64;%(PreprocessorDefinitions) + true + + + MultiThreadedDLL + false + true + $(IntDir)zlibvc.pch + All + $(IntDir) + $(IntDir) + $(OutDir) + + + Level3 + true + + + NDEBUG;%(PreprocessorDefinitions) + 0x040c + + + ..\..\masmx64\gvmat64.obj;..\..\masmx64\inffasx64.obj;%(AdditionalDependencies) + $(OutDir)zlibwapi.dll + true + false + .\zlibvc.def + $(OutDir)zlibwapi.pdb + true + $(OutDir)zlibwapi.map + Windows + $(OutDir)zlibwapi.lib + MachineX64 + + + cd ..\..\masmx64 +bld_ml64.bat + + + + + NDEBUG;%(PreprocessorDefinitions) + true + true + Itanium + $(OutDir)zlibvc.tlb + + + OnlyExplicitInline + ..\..\..;..\..\masmx86;%(AdditionalIncludeDirectories) + _CRT_NONSTDC_NO_DEPRECATE;_CRT_SECURE_NO_DEPRECATE;_CRT_NONSTDC_NO_WARNINGS;ZLIB_WINAPI;WIN64;%(PreprocessorDefinitions) + true + + + MultiThreadedDLL + false + true + $(IntDir)zlibvc.pch + All + $(IntDir) + $(IntDir) + $(OutDir) + + + Level3 + true + + + NDEBUG;%(PreprocessorDefinitions) + 0x040c + + + $(OutDir)zlibwapi.dll + true + false + .\zlibvc.def + $(OutDir)zlibwapi.pdb + true + $(OutDir)zlibwapi.map + Windows + $(OutDir)zlibwapi.lib + MachineIA64 + + + + + + + + + + + + + + true + true + true + true + true + true + + + + + + + + + + %(AdditionalIncludeDirectories) + ZLIB_INTERNAL;%(PreprocessorDefinitions) + %(AdditionalIncludeDirectories) + ZLIB_INTERNAL;%(PreprocessorDefinitions) + %(AdditionalIncludeDirectories) + ZLIB_INTERNAL;%(PreprocessorDefinitions) + + + %(AdditionalIncludeDirectories) + ZLIB_INTERNAL;%(PreprocessorDefinitions) + %(AdditionalIncludeDirectories) + ZLIB_INTERNAL;%(PreprocessorDefinitions) + %(AdditionalIncludeDirectories) + ZLIB_INTERNAL;%(PreprocessorDefinitions) + + + + + + + + + + + + + + + + + + + + + + + + ADDED compat/zlib/contrib/vstudio/vc14/miniunz.vcxproj Index: compat/zlib/contrib/vstudio/vc14/miniunz.vcxproj ================================================================== --- /dev/null +++ compat/zlib/contrib/vstudio/vc14/miniunz.vcxproj @@ -0,0 +1,316 @@ + + + + + Debug + Itanium + + + Debug + Win32 + + + Debug + x64 + + + Release + Itanium + + + Release + Win32 + + + Release + x64 + + + + {C52F9E7B-498A-42BE-8DB4-85A15694382A} + Win32Proj + + + + Application + MultiByte + v140 + + + Application + Unicode + v140 + + + Application + MultiByte + v140 + + + Application + MultiByte + v140 + + + Application + MultiByte + v140 + + + Application + MultiByte + v140 + + + + + + + + + + + + + + + + + + + + + + + + + <_ProjectFileVersion>10.0.30128.1 + x86\MiniUnzip$(Configuration)\ + x86\MiniUnzip$(Configuration)\Tmp\ + true + false + x86\MiniUnzip$(Configuration)\ + x86\MiniUnzip$(Configuration)\Tmp\ + false + false + x64\MiniUnzip$(Configuration)\ + x64\MiniUnzip$(Configuration)\Tmp\ + true + false + ia64\MiniUnzip$(Configuration)\ + ia64\MiniUnzip$(Configuration)\Tmp\ + true + false + x64\MiniUnzip$(Configuration)\ + x64\MiniUnzip$(Configuration)\Tmp\ + false + false + ia64\MiniUnzip$(Configuration)\ + ia64\MiniUnzip$(Configuration)\Tmp\ + false + false + AllRules.ruleset + + + AllRules.ruleset + + + AllRules.ruleset + + + AllRules.ruleset + + + AllRules.ruleset + + + AllRules.ruleset + + + + + + Disabled + ..\..\..;..\..\minizip;%(AdditionalIncludeDirectories) + WIN32;_CRT_NONSTDC_NO_DEPRECATE;_CRT_SECURE_NO_DEPRECATE;ZLIB_WINAPI;_DEBUG;_CONSOLE;%(PreprocessorDefinitions) + true + Default + MultiThreadedDebugDLL + false + + + $(IntDir) + Level3 + ProgramDatabase + + + x86\ZlibDllDebug\zlibwapi.lib;%(AdditionalDependencies) + $(OutDir)miniunz.exe + true + $(OutDir)miniunz.pdb + Console + false + + + MachineX86 + + + + + MaxSpeed + OnlyExplicitInline + true + ..\..\..;..\..\minizip;%(AdditionalIncludeDirectories) + WIN32;_CRT_NONSTDC_NO_DEPRECATE;_CRT_SECURE_NO_DEPRECATE;ZLIB_WINAPI;NDEBUG;_CONSOLE;%(PreprocessorDefinitions) + true + Default + MultiThreaded + false + true + + + $(IntDir) + Level3 + ProgramDatabase + + + x86\ZlibDllRelease\zlibwapi.lib;%(AdditionalDependencies) + $(OutDir)miniunz.exe + true + Console + true + true + false + + + MachineX86 + + + + + X64 + + + Disabled + ..\..\..;..\..\minizip;%(AdditionalIncludeDirectories) + _CRT_NONSTDC_NO_DEPRECATE;_CRT_SECURE_NO_DEPRECATE;ZLIB_WINAPI;_DEBUG;_CONSOLE;WIN64;%(PreprocessorDefinitions) + true + Default + MultiThreadedDebugDLL + false + + + $(IntDir) + Level3 + ProgramDatabase + + + x64\ZlibDllDebug\zlibwapi.lib;%(AdditionalDependencies) + $(OutDir)miniunz.exe + true + $(OutDir)miniunz.pdb + Console + MachineX64 + + + + + Itanium + + + Disabled + ..\..\..;..\..\minizip;%(AdditionalIncludeDirectories) + _CRT_NONSTDC_NO_DEPRECATE;_CRT_SECURE_NO_DEPRECATE;ZLIB_WINAPI;_DEBUG;_CONSOLE;WIN64;%(PreprocessorDefinitions) + true + Default + MultiThreadedDebugDLL + false + + + $(IntDir) + Level3 + ProgramDatabase + + + ia64\ZlibDllDebug\zlibwapi.lib;%(AdditionalDependencies) + $(OutDir)miniunz.exe + true + $(OutDir)miniunz.pdb + Console + MachineIA64 + + + + + X64 + + + MaxSpeed + OnlyExplicitInline + true + ..\..\..;..\..\minizip;%(AdditionalIncludeDirectories) + _CRT_NONSTDC_NO_DEPRECATE;_CRT_SECURE_NO_DEPRECATE;ZLIB_WINAPI;NDEBUG;_CONSOLE;WIN64;%(PreprocessorDefinitions) + true + Default + MultiThreadedDLL + false + true + + + $(IntDir) + Level3 + ProgramDatabase + + + x64\ZlibDllRelease\zlibwapi.lib;%(AdditionalDependencies) + $(OutDir)miniunz.exe + true + Console + true + true + MachineX64 + + + + + Itanium + + + MaxSpeed + OnlyExplicitInline + true + ..\..\..;..\..\minizip;%(AdditionalIncludeDirectories) + _CRT_NONSTDC_NO_DEPRECATE;_CRT_SECURE_NO_DEPRECATE;ZLIB_WINAPI;NDEBUG;_CONSOLE;WIN64;%(PreprocessorDefinitions) + true + Default + MultiThreadedDLL + false + true + + + $(IntDir) + Level3 + ProgramDatabase + + + ia64\ZlibDllRelease\zlibwapi.lib;%(AdditionalDependencies) + $(OutDir)miniunz.exe + true + Console + true + true + MachineIA64 + + + + + + + + {8fd826f8-3739-44e6-8cc8-997122e53b8d} + + + + + + ADDED compat/zlib/contrib/vstudio/vc14/minizip.vcxproj Index: compat/zlib/contrib/vstudio/vc14/minizip.vcxproj ================================================================== --- /dev/null +++ compat/zlib/contrib/vstudio/vc14/minizip.vcxproj @@ -0,0 +1,313 @@ + + + + + Debug + Itanium + + + Debug + Win32 + + + Debug + x64 + + + Release + Itanium + + + Release + Win32 + + + Release + x64 + + + + {48CDD9DC-E09F-4135-9C0C-4FE50C3C654B} + Win32Proj + + + + Application + MultiByte + v140 + + + Application + Unicode + v140 + + + Application + MultiByte + v140 + + + Application + MultiByte + v140 + + + Application + MultiByte + v140 + + + Application + MultiByte + v140 + + + + + + + + + + + + + + + + + + + + + + + + + <_ProjectFileVersion>10.0.30128.1 + x86\MiniZip$(Configuration)\ + x86\MiniZip$(Configuration)\Tmp\ + true + false + x86\MiniZip$(Configuration)\ + x86\MiniZip$(Configuration)\Tmp\ + false + x64\$(Configuration)\ + x64\$(Configuration)\ + true + false + ia64\$(Configuration)\ + ia64\$(Configuration)\ + true + false + x64\$(Configuration)\ + x64\$(Configuration)\ + false + ia64\$(Configuration)\ + ia64\$(Configuration)\ + false + AllRules.ruleset + + + AllRules.ruleset + + + AllRules.ruleset + + + AllRules.ruleset + + + AllRules.ruleset + + + AllRules.ruleset + + + + + + Disabled + ..\..\..;..\..\minizip;%(AdditionalIncludeDirectories) + WIN32;_CRT_NONSTDC_NO_DEPRECATE;_CRT_SECURE_NO_DEPRECATE;ZLIB_WINAPI;_DEBUG;_CONSOLE;%(PreprocessorDefinitions) + true + Default + MultiThreadedDebugDLL + false + + + $(IntDir) + Level3 + ProgramDatabase + + + x86\ZlibDllDebug\zlibwapi.lib;%(AdditionalDependencies) + $(OutDir)minizip.exe + true + $(OutDir)minizip.pdb + Console + false + + + MachineX86 + + + + + MaxSpeed + OnlyExplicitInline + true + ..\..\..;..\..\minizip;%(AdditionalIncludeDirectories) + WIN32;_CRT_NONSTDC_NO_DEPRECATE;_CRT_SECURE_NO_DEPRECATE;ZLIB_WINAPI;NDEBUG;_CONSOLE;%(PreprocessorDefinitions) + true + Default + MultiThreaded + false + true + + + $(IntDir) + Level3 + ProgramDatabase + + + x86\ZlibDllRelease\zlibwapi.lib;%(AdditionalDependencies) + $(OutDir)minizip.exe + true + Console + true + true + false + + + MachineX86 + + + + + X64 + + + Disabled + ..\..\..;..\..\minizip;%(AdditionalIncludeDirectories) + _CRT_NONSTDC_NO_DEPRECATE;_CRT_SECURE_NO_DEPRECATE;ZLIB_WINAPI;_DEBUG;_CONSOLE;WIN64;%(PreprocessorDefinitions) + true + Default + MultiThreadedDebugDLL + false + + + $(IntDir) + Level3 + ProgramDatabase + + + x64\ZlibDllDebug\zlibwapi.lib;%(AdditionalDependencies) + $(OutDir)minizip.exe + true + $(OutDir)minizip.pdb + Console + MachineX64 + + + + + Itanium + + + Disabled + ..\..\..;..\..\minizip;%(AdditionalIncludeDirectories) + _CRT_NONSTDC_NO_DEPRECATE;_CRT_SECURE_NO_DEPRECATE;ZLIB_WINAPI;_DEBUG;_CONSOLE;WIN64;%(PreprocessorDefinitions) + true + Default + MultiThreadedDebugDLL + false + + + $(IntDir) + Level3 + ProgramDatabase + + + ia64\ZlibDllDebug\zlibwapi.lib;%(AdditionalDependencies) + $(OutDir)minizip.exe + true + $(OutDir)minizip.pdb + Console + MachineIA64 + + + + + X64 + + + MaxSpeed + OnlyExplicitInline + true + ..\..\..;..\..\minizip;%(AdditionalIncludeDirectories) + _CRT_NONSTDC_NO_DEPRECATE;_CRT_SECURE_NO_DEPRECATE;ZLIB_WINAPI;NDEBUG;_CONSOLE;WIN64;%(PreprocessorDefinitions) + true + Default + MultiThreadedDLL + false + true + + + $(IntDir) + Level3 + ProgramDatabase + + + x64\ZlibDllRelease\zlibwapi.lib;%(AdditionalDependencies) + $(OutDir)minizip.exe + true + Console + true + true + MachineX64 + + + + + Itanium + + + MaxSpeed + OnlyExplicitInline + true + ..\..\..;..\..\minizip;%(AdditionalIncludeDirectories) + _CRT_NONSTDC_NO_DEPRECATE;_CRT_SECURE_NO_DEPRECATE;ZLIB_WINAPI;NDEBUG;_CONSOLE;WIN64;%(PreprocessorDefinitions) + true + Default + MultiThreadedDLL + false + true + + + $(IntDir) + Level3 + ProgramDatabase + + + ia64\ZlibDllRelease\zlibwapi.lib;%(AdditionalDependencies) + $(OutDir)minizip.exe + true + Console + true + true + MachineIA64 + + + + + + + + {8fd826f8-3739-44e6-8cc8-997122e53b8d} + + + + + + ADDED compat/zlib/contrib/vstudio/vc14/testzlib.vcxproj Index: compat/zlib/contrib/vstudio/vc14/testzlib.vcxproj ================================================================== --- /dev/null +++ compat/zlib/contrib/vstudio/vc14/testzlib.vcxproj @@ -0,0 +1,430 @@ + + + + + Debug + Itanium + + + Debug + Win32 + + + Debug + x64 + + + ReleaseWithoutAsm + Itanium + + + ReleaseWithoutAsm + Win32 + + + ReleaseWithoutAsm + x64 + + + Release + Itanium + + + Release + Win32 + + + Release + x64 + + + + {AA6666AA-E09F-4135-9C0C-4FE50C3C654B} + testzlib + Win32Proj + + + + Application + MultiByte + true + v140 + + + Application + MultiByte + true + v140 + + + Application + Unicode + v140 + + + Application + MultiByte + true + v140 + + + Application + MultiByte + true + v140 + + + Application + MultiByte + v140 + + + Application + true + v140 + + + Application + true + v140 + + + Application + v140 + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + <_ProjectFileVersion>10.0.30128.1 + x86\TestZlib$(Configuration)\ + x86\TestZlib$(Configuration)\Tmp\ + true + false + x86\TestZlib$(Configuration)\ + x86\TestZlib$(Configuration)\Tmp\ + false + false + x86\TestZlib$(Configuration)\ + x86\TestZlib$(Configuration)\Tmp\ + false + false + x64\TestZlib$(Configuration)\ + x64\TestZlib$(Configuration)\Tmp\ + false + ia64\TestZlib$(Configuration)\ + ia64\TestZlib$(Configuration)\Tmp\ + true + false + x64\TestZlib$(Configuration)\ + x64\TestZlib$(Configuration)\Tmp\ + false + ia64\TestZlib$(Configuration)\ + ia64\TestZlib$(Configuration)\Tmp\ + false + false + x64\TestZlib$(Configuration)\ + x64\TestZlib$(Configuration)\Tmp\ + false + ia64\TestZlib$(Configuration)\ + ia64\TestZlib$(Configuration)\Tmp\ + false + false + AllRules.ruleset + + + AllRules.ruleset + + + AllRules.ruleset + + + AllRules.ruleset + + + AllRules.ruleset + + + AllRules.ruleset + + + AllRules.ruleset + + + AllRules.ruleset + + + AllRules.ruleset + + + + + + Disabled + ..\..\..;%(AdditionalIncludeDirectories) + ASMV;ASMINF;WIN32;ZLIB_WINAPI;_DEBUG;_CONSOLE;_CRT_NONSTDC_NO_DEPRECATE;_CRT_SECURE_NO_DEPRECATE;_CRT_NONSTDC_NO_WARNINGS;%(PreprocessorDefinitions) + true + Default + MultiThreadedDebugDLL + false + + + AssemblyAndSourceCode + $(IntDir) + Level3 + ProgramDatabase + + + ..\..\masmx86\match686.obj;..\..\masmx86\inffas32.obj;%(AdditionalDependencies) + $(OutDir)testzlib.exe + true + $(OutDir)testzlib.pdb + Console + false + + + MachineX86 + + + + + MaxSpeed + OnlyExplicitInline + true + ..\..\..;%(AdditionalIncludeDirectories) + WIN32;ZLIB_WINAPI;NDEBUG;_CONSOLE;_CRT_NONSTDC_NO_DEPRECATE;_CRT_SECURE_NO_DEPRECATE;_CRT_NONSTDC_NO_WARNINGS;%(PreprocessorDefinitions) + true + Default + MultiThreaded + false + true + + + $(IntDir) + Level3 + ProgramDatabase + + + $(OutDir)testzlib.exe + true + Console + true + true + false + + + MachineX86 + + + + + MaxSpeed + OnlyExplicitInline + true + ..\..\..;%(AdditionalIncludeDirectories) + ASMV;ASMINF;WIN32;ZLIB_WINAPI;NDEBUG;_CONSOLE;_CRT_NONSTDC_NO_DEPRECATE;_CRT_SECURE_NO_DEPRECATE;_CRT_NONSTDC_NO_WARNINGS;%(PreprocessorDefinitions) + true + Default + MultiThreaded + false + true + + + $(IntDir) + Level3 + ProgramDatabase + + + ..\..\masmx86\match686.obj;..\..\masmx86\inffas32.obj;%(AdditionalDependencies) + $(OutDir)testzlib.exe + true + Console + true + true + false + + + MachineX86 + false + + + + + ..\..\..;%(AdditionalIncludeDirectories) + ASMV;ASMINF;WIN32;ZLIB_WINAPI;_DEBUG;_CONSOLE;_CRT_NONSTDC_NO_DEPRECATE;_CRT_SECURE_NO_DEPRECATE;_CRT_NONSTDC_NO_WARNINGS;%(PreprocessorDefinitions) + Default + MultiThreadedDebugDLL + false + $(IntDir) + + + ..\..\masmx64\gvmat64.obj;..\..\masmx64\inffasx64.obj;%(AdditionalDependencies) + + + + + Itanium + + + Disabled + ..\..\..;%(AdditionalIncludeDirectories) + ZLIB_WINAPI;_DEBUG;_CONSOLE;_CRT_NONSTDC_NO_DEPRECATE;_CRT_SECURE_NO_DEPRECATE;_CRT_NONSTDC_NO_WARNINGS;WIN64;%(PreprocessorDefinitions) + true + Default + MultiThreadedDebugDLL + false + + + AssemblyAndSourceCode + $(IntDir) + Level3 + ProgramDatabase + + + $(OutDir)testzlib.exe + true + $(OutDir)testzlib.pdb + Console + MachineIA64 + + + + + ..\..\..;%(AdditionalIncludeDirectories) + WIN32;ZLIB_WINAPI;NDEBUG;_CONSOLE;_CRT_NONSTDC_NO_DEPRECATE;_CRT_SECURE_NO_DEPRECATE;_CRT_NONSTDC_NO_WARNINGS;%(PreprocessorDefinitions) + Default + MultiThreadedDLL + false + $(IntDir) + + + %(AdditionalDependencies) + + + + + Itanium + + + MaxSpeed + OnlyExplicitInline + true + ..\..\..;%(AdditionalIncludeDirectories) + ZLIB_WINAPI;NDEBUG;_CONSOLE;_CRT_NONSTDC_NO_DEPRECATE;_CRT_SECURE_NO_DEPRECATE;_CRT_NONSTDC_NO_WARNINGS;WIN64;%(PreprocessorDefinitions) + true + Default + MultiThreadedDLL + false + true + + + $(IntDir) + Level3 + ProgramDatabase + + + $(OutDir)testzlib.exe + true + Console + true + true + MachineIA64 + + + + + ..\..\..;%(AdditionalIncludeDirectories) + ASMV;ASMINF;WIN32;ZLIB_WINAPI;NDEBUG;_CONSOLE;_CRT_NONSTDC_NO_DEPRECATE;_CRT_SECURE_NO_DEPRECATE;_CRT_NONSTDC_NO_WARNINGS;%(PreprocessorDefinitions) + Default + MultiThreadedDLL + false + $(IntDir) + + + ..\..\masmx64\gvmat64.obj;..\..\masmx64\inffasx64.obj;%(AdditionalDependencies) + + + + + Itanium + + + MaxSpeed + OnlyExplicitInline + true + ..\..\..;%(AdditionalIncludeDirectories) + ZLIB_WINAPI;NDEBUG;_CONSOLE;_CRT_NONSTDC_NO_DEPRECATE;_CRT_SECURE_NO_DEPRECATE;_CRT_NONSTDC_NO_WARNINGS;WIN64;%(PreprocessorDefinitions) + true + Default + MultiThreadedDLL + false + true + + + $(IntDir) + Level3 + ProgramDatabase + + + $(OutDir)testzlib.exe + true + Console + true + true + MachineIA64 + + + + + + + + + + true + true + true + true + true + true + + + + + + + + + + + + + ADDED compat/zlib/contrib/vstudio/vc14/testzlibdll.vcxproj Index: compat/zlib/contrib/vstudio/vc14/testzlibdll.vcxproj ================================================================== --- /dev/null +++ compat/zlib/contrib/vstudio/vc14/testzlibdll.vcxproj @@ -0,0 +1,316 @@ + + + + + Debug + Itanium + + + Debug + Win32 + + + Debug + x64 + + + Release + Itanium + + + Release + Win32 + + + Release + x64 + + + + {C52F9E7B-498A-42BE-8DB4-85A15694366A} + Win32Proj + + + + Application + MultiByte + v140 + + + Application + Unicode + v140 + + + Application + MultiByte + v140 + + + Application + MultiByte + v140 + + + Application + MultiByte + v140 + + + Application + MultiByte + v140 + + + + + + + + + + + + + + + + + + + + + + + + + <_ProjectFileVersion>10.0.30128.1 + x86\TestZlibDll$(Configuration)\ + x86\TestZlibDll$(Configuration)\Tmp\ + true + false + x86\TestZlibDll$(Configuration)\ + x86\TestZlibDll$(Configuration)\Tmp\ + false + false + x64\TestZlibDll$(Configuration)\ + x64\TestZlibDll$(Configuration)\Tmp\ + true + false + ia64\TestZlibDll$(Configuration)\ + ia64\TestZlibDll$(Configuration)\Tmp\ + true + false + x64\TestZlibDll$(Configuration)\ + x64\TestZlibDll$(Configuration)\Tmp\ + false + false + ia64\TestZlibDll$(Configuration)\ + ia64\TestZlibDll$(Configuration)\Tmp\ + false + false + AllRules.ruleset + + + AllRules.ruleset + + + AllRules.ruleset + + + AllRules.ruleset + + + AllRules.ruleset + + + AllRules.ruleset + + + + + + Disabled + ..\..\..;..\..\minizip;%(AdditionalIncludeDirectories) + WIN32;_CRT_NONSTDC_NO_DEPRECATE;_CRT_SECURE_NO_DEPRECATE;ZLIB_WINAPI;_DEBUG;_CONSOLE;%(PreprocessorDefinitions) + true + Default + MultiThreadedDebugDLL + false + + + $(IntDir) + Level3 + ProgramDatabase + + + x86\ZlibDllDebug\zlibwapi.lib;%(AdditionalDependencies) + $(OutDir)testzlibdll.exe + true + $(OutDir)testzlib.pdb + Console + false + + + MachineX86 + + + + + MaxSpeed + OnlyExplicitInline + true + ..\..\..;..\..\minizip;%(AdditionalIncludeDirectories) + WIN32;_CRT_NONSTDC_NO_DEPRECATE;_CRT_SECURE_NO_DEPRECATE;ZLIB_WINAPI;NDEBUG;_CONSOLE;%(PreprocessorDefinitions) + true + Default + MultiThreaded + false + true + + + $(IntDir) + Level3 + ProgramDatabase + + + x86\ZlibDllRelease\zlibwapi.lib;%(AdditionalDependencies) + $(OutDir)testzlibdll.exe + true + Console + true + true + false + + + MachineX86 + + + + + X64 + + + Disabled + ..\..\..;..\..\minizip;%(AdditionalIncludeDirectories) + _CRT_NONSTDC_NO_DEPRECATE;_CRT_SECURE_NO_DEPRECATE;ZLIB_WINAPI;_DEBUG;_CONSOLE;WIN64;%(PreprocessorDefinitions) + true + Default + MultiThreadedDebugDLL + false + + + $(IntDir) + Level3 + ProgramDatabase + + + x64\ZlibDllDebug\zlibwapi.lib;%(AdditionalDependencies) + $(OutDir)testzlibdll.exe + true + $(OutDir)testzlib.pdb + Console + MachineX64 + + + + + Itanium + + + Disabled + ..\..\..;..\..\minizip;%(AdditionalIncludeDirectories) + _CRT_NONSTDC_NO_DEPRECATE;_CRT_SECURE_NO_DEPRECATE;ZLIB_WINAPI;_DEBUG;_CONSOLE;WIN64;%(PreprocessorDefinitions) + true + Default + MultiThreadedDebugDLL + false + + + $(IntDir) + Level3 + ProgramDatabase + + + ia64\ZlibDllDebug\zlibwapi.lib;%(AdditionalDependencies) + $(OutDir)testzlibdll.exe + true + $(OutDir)testzlib.pdb + Console + MachineIA64 + + + + + X64 + + + MaxSpeed + OnlyExplicitInline + true + ..\..\..;..\..\minizip;%(AdditionalIncludeDirectories) + _CRT_NONSTDC_NO_DEPRECATE;_CRT_SECURE_NO_DEPRECATE;ZLIB_WINAPI;NDEBUG;_CONSOLE;WIN64;%(PreprocessorDefinitions) + true + Default + MultiThreadedDLL + false + true + + + $(IntDir) + Level3 + ProgramDatabase + + + x64\ZlibDllRelease\zlibwapi.lib;%(AdditionalDependencies) + $(OutDir)testzlibdll.exe + true + Console + true + true + MachineX64 + + + + + Itanium + + + MaxSpeed + OnlyExplicitInline + true + ..\..\..;..\..\minizip;%(AdditionalIncludeDirectories) + _CRT_NONSTDC_NO_DEPRECATE;_CRT_SECURE_NO_DEPRECATE;ZLIB_WINAPI;NDEBUG;_CONSOLE;WIN64;%(PreprocessorDefinitions) + true + Default + MultiThreadedDLL + false + true + + + $(IntDir) + Level3 + ProgramDatabase + + + ia64\ZlibDllRelease\zlibwapi.lib;%(AdditionalDependencies) + $(OutDir)testzlibdll.exe + true + Console + true + true + MachineIA64 + + + + + + + + {8fd826f8-3739-44e6-8cc8-997122e53b8d} + + + + + + ADDED compat/zlib/contrib/vstudio/vc14/zlib.rc Index: compat/zlib/contrib/vstudio/vc14/zlib.rc ================================================================== --- /dev/null +++ compat/zlib/contrib/vstudio/vc14/zlib.rc @@ -0,0 +1,32 @@ +#include + +#define IDR_VERSION1 1 +IDR_VERSION1 VERSIONINFO MOVEABLE IMPURE LOADONCALL DISCARDABLE + FILEVERSION 1, 2, 11, 0 + PRODUCTVERSION 1, 2, 11, 0 + FILEFLAGSMASK VS_FFI_FILEFLAGSMASK + FILEFLAGS 0 + FILEOS VOS_DOS_WINDOWS32 + FILETYPE VFT_DLL + FILESUBTYPE 0 // not used +BEGIN + BLOCK "StringFileInfo" + BEGIN + BLOCK "040904E4" + //language ID = U.S. English, char set = Windows, Multilingual + + BEGIN + VALUE "FileDescription", "zlib data compression and ZIP file I/O library\0" + VALUE "FileVersion", "1.2.11\0" + VALUE "InternalName", "zlib\0" + VALUE "OriginalFilename", "zlibwapi.dll\0" + VALUE "ProductName", "ZLib.DLL\0" + VALUE "Comments","DLL support by Alessandro Iacopetti & Gilles Vollant\0" + VALUE "LegalCopyright", "(C) 1995-2017 Jean-loup Gailly & Mark Adler\0" + END + END + BLOCK "VarFileInfo" + BEGIN + VALUE "Translation", 0x0409, 1252 + END +END ADDED compat/zlib/contrib/vstudio/vc14/zlibstat.vcxproj Index: compat/zlib/contrib/vstudio/vc14/zlibstat.vcxproj ================================================================== --- /dev/null +++ compat/zlib/contrib/vstudio/vc14/zlibstat.vcxproj @@ -0,0 +1,467 @@ + + + + + Debug + Itanium + + + Debug + Win32 + + + Debug + x64 + + + ReleaseWithoutAsm + Itanium + + + ReleaseWithoutAsm + Win32 + + + ReleaseWithoutAsm + x64 + + + Release + Itanium + + + Release + Win32 + + + Release + x64 + + + + {745DEC58-EBB3-47A9-A9B8-4C6627C01BF8} + + + + StaticLibrary + false + v140 + + + StaticLibrary + false + v140 + + + StaticLibrary + false + v140 + Unicode + + + StaticLibrary + false + v140 + + + StaticLibrary + false + v140 + + + StaticLibrary + false + v140 + + + StaticLibrary + false + v140 + + + StaticLibrary + false + v140 + + + StaticLibrary + false + v140 + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + <_ProjectFileVersion>10.0.30128.1 + x86\ZlibStat$(Configuration)\ + x86\ZlibStat$(Configuration)\Tmp\ + x86\ZlibStat$(Configuration)\ + x86\ZlibStat$(Configuration)\Tmp\ + x86\ZlibStat$(Configuration)\ + x86\ZlibStat$(Configuration)\Tmp\ + x64\ZlibStat$(Configuration)\ + x64\ZlibStat$(Configuration)\Tmp\ + ia64\ZlibStat$(Configuration)\ + ia64\ZlibStat$(Configuration)\Tmp\ + x64\ZlibStat$(Configuration)\ + x64\ZlibStat$(Configuration)\Tmp\ + ia64\ZlibStat$(Configuration)\ + ia64\ZlibStat$(Configuration)\Tmp\ + x64\ZlibStat$(Configuration)\ + x64\ZlibStat$(Configuration)\Tmp\ + ia64\ZlibStat$(Configuration)\ + ia64\ZlibStat$(Configuration)\Tmp\ + AllRules.ruleset + + + AllRules.ruleset + + + AllRules.ruleset + + + AllRules.ruleset + + + AllRules.ruleset + + + AllRules.ruleset + + + AllRules.ruleset + + + AllRules.ruleset + + + AllRules.ruleset + + + + + + Disabled + ..\..\..;..\..\masmx86;%(AdditionalIncludeDirectories) + WIN32;ZLIB_WINAPI;_CRT_NONSTDC_NO_DEPRECATE;_CRT_SECURE_NO_DEPRECATE;_CRT_NONSTDC_NO_WARNINGS;%(PreprocessorDefinitions) + + + MultiThreadedDebugDLL + false + $(IntDir)zlibstat.pch + $(IntDir) + $(IntDir) + $(OutDir) + Level3 + true + OldStyle + + + 0x040c + + + /MACHINE:X86 /NODEFAULTLIB %(AdditionalOptions) + $(OutDir)zlibstat.lib + true + + + + + OnlyExplicitInline + ..\..\..;..\..\masmx86;%(AdditionalIncludeDirectories) + WIN32;ZLIB_WINAPI;_CRT_NONSTDC_NO_DEPRECATE;_CRT_SECURE_NO_DEPRECATE;_CRT_NONSTDC_NO_WARNINGS;ASMV;ASMINF;%(PreprocessorDefinitions) + true + + + MultiThreaded + false + true + $(IntDir)zlibstat.pch + $(IntDir) + $(IntDir) + $(OutDir) + Level3 + true + + + 0x040c + + + /MACHINE:X86 /NODEFAULTLIB %(AdditionalOptions) + ..\..\masmx86\match686.obj;..\..\masmx86\inffas32.obj;%(AdditionalDependencies) + $(OutDir)zlibstat.lib + true + + + + + OnlyExplicitInline + ..\..\..;..\..\masmx86;%(AdditionalIncludeDirectories) + WIN32;ZLIB_WINAPI;_CRT_NONSTDC_NO_DEPRECATE;_CRT_SECURE_NO_DEPRECATE;_CRT_NONSTDC_NO_WARNINGS;%(PreprocessorDefinitions) + true + + + MultiThreaded + false + true + $(IntDir)zlibstat.pch + $(IntDir) + $(IntDir) + $(OutDir) + Level3 + true + + + 0x040c + + + /MACHINE:X86 /NODEFAULTLIB %(AdditionalOptions) + $(OutDir)zlibstat.lib + true + + + + + X64 + + + Disabled + ..\..\..;..\..\masmx86;%(AdditionalIncludeDirectories) + ZLIB_WINAPI;_CRT_NONSTDC_NO_DEPRECATE;_CRT_SECURE_NO_DEPRECATE;_CRT_NONSTDC_NO_WARNINGS;WIN64;%(PreprocessorDefinitions) + + + MultiThreadedDebugDLL + false + $(IntDir)zlibstat.pch + $(IntDir) + $(IntDir) + $(OutDir) + Level3 + true + OldStyle + + + 0x040c + + + /MACHINE:AMD64 /NODEFAULTLIB %(AdditionalOptions) + $(OutDir)zlibstat.lib + true + + + + + Itanium + + + Disabled + ..\..\..;..\..\masmx86;%(AdditionalIncludeDirectories) + ZLIB_WINAPI;_CRT_NONSTDC_NO_DEPRECATE;_CRT_SECURE_NO_DEPRECATE;_CRT_NONSTDC_NO_WARNINGS;WIN64;%(PreprocessorDefinitions) + + + MultiThreadedDebugDLL + false + $(IntDir)zlibstat.pch + $(IntDir) + $(IntDir) + $(OutDir) + Level3 + true + OldStyle + + + 0x040c + + + /MACHINE:IA64 /NODEFAULTLIB %(AdditionalOptions) + $(OutDir)zlibstat.lib + true + + + + + X64 + + + OnlyExplicitInline + ..\..\..;..\..\masmx86;%(AdditionalIncludeDirectories) + ZLIB_WINAPI;_CRT_NONSTDC_NO_DEPRECATE;_CRT_SECURE_NO_DEPRECATE;_CRT_NONSTDC_NO_WARNINGS;ASMV;ASMINF;WIN64;%(PreprocessorDefinitions) + true + + + MultiThreadedDLL + false + true + $(IntDir)zlibstat.pch + $(IntDir) + $(IntDir) + $(OutDir) + Level3 + true + + + 0x040c + + + /MACHINE:AMD64 /NODEFAULTLIB %(AdditionalOptions) + ..\..\masmx64\gvmat64.obj;..\..\masmx64\inffasx64.obj;%(AdditionalDependencies) + $(OutDir)zlibstat.lib + true + + + + + Itanium + + + OnlyExplicitInline + ..\..\..;..\..\masmx86;%(AdditionalIncludeDirectories) + ZLIB_WINAPI;_CRT_NONSTDC_NO_DEPRECATE;_CRT_SECURE_NO_DEPRECATE;_CRT_NONSTDC_NO_WARNINGS;WIN64;%(PreprocessorDefinitions) + true + + + MultiThreadedDLL + false + true + $(IntDir)zlibstat.pch + $(IntDir) + $(IntDir) + $(OutDir) + Level3 + true + + + 0x040c + + + /MACHINE:IA64 /NODEFAULTLIB %(AdditionalOptions) + $(OutDir)zlibstat.lib + true + + + + + X64 + + + OnlyExplicitInline + ..\..\..;..\..\masmx86;%(AdditionalIncludeDirectories) + ZLIB_WINAPI;_CRT_NONSTDC_NO_DEPRECATE;_CRT_SECURE_NO_DEPRECATE;_CRT_NONSTDC_NO_WARNINGS;WIN64;%(PreprocessorDefinitions) + true + + + MultiThreadedDLL + false + true + $(IntDir)zlibstat.pch + $(IntDir) + $(IntDir) + $(OutDir) + Level3 + true + + + 0x040c + + + /MACHINE:AMD64 /NODEFAULTLIB %(AdditionalOptions) + $(OutDir)zlibstat.lib + true + + + + + Itanium + + + OnlyExplicitInline + ..\..\..;..\..\masmx86;%(AdditionalIncludeDirectories) + ZLIB_WINAPI;_CRT_NONSTDC_NO_DEPRECATE;_CRT_SECURE_NO_DEPRECATE;_CRT_NONSTDC_NO_WARNINGS;WIN64;%(PreprocessorDefinitions) + true + + + MultiThreadedDLL + false + true + $(IntDir)zlibstat.pch + $(IntDir) + $(IntDir) + $(OutDir) + Level3 + true + + + 0x040c + + + /MACHINE:IA64 /NODEFAULTLIB %(AdditionalOptions) + $(OutDir)zlibstat.lib + true + + + + + + + + + + + + + + true + true + true + true + true + true + + + + + + + + + + + + + + + + + + + + + ADDED compat/zlib/contrib/vstudio/vc14/zlibvc.def Index: compat/zlib/contrib/vstudio/vc14/zlibvc.def ================================================================== --- /dev/null +++ compat/zlib/contrib/vstudio/vc14/zlibvc.def @@ -0,0 +1,153 @@ +LIBRARY +; zlib data compression and ZIP file I/O library + +VERSION 1.2 + +EXPORTS + adler32 @1 + compress @2 + crc32 @3 + deflate @4 + deflateCopy @5 + deflateEnd @6 + deflateInit2_ @7 + deflateInit_ @8 + deflateParams @9 + deflateReset @10 + deflateSetDictionary @11 + gzclose @12 + gzdopen @13 + gzerror @14 + gzflush @15 + gzopen @16 + gzread @17 + gzwrite @18 + inflate @19 + inflateEnd @20 + inflateInit2_ @21 + inflateInit_ @22 + inflateReset @23 + inflateSetDictionary @24 + inflateSync @25 + uncompress @26 + zlibVersion @27 + gzprintf @28 + gzputc @29 + gzgetc @30 + gzseek @31 + gzrewind @32 + gztell @33 + gzeof @34 + gzsetparams @35 + zError @36 + inflateSyncPoint @37 + get_crc_table @38 + compress2 @39 + gzputs @40 + gzgets @41 + inflateCopy @42 + inflateBackInit_ @43 + inflateBack @44 + inflateBackEnd @45 + compressBound @46 + deflateBound @47 + gzclearerr @48 + gzungetc @49 + zlibCompileFlags @50 + deflatePrime @51 + deflatePending @52 + + unzOpen @61 + unzClose @62 + unzGetGlobalInfo @63 + unzGetCurrentFileInfo @64 + unzGoToFirstFile @65 + unzGoToNextFile @66 + unzOpenCurrentFile @67 + unzReadCurrentFile @68 + unzOpenCurrentFile3 @69 + unztell @70 + unzeof @71 + unzCloseCurrentFile @72 + unzGetGlobalComment @73 + unzStringFileNameCompare @74 + unzLocateFile @75 + unzGetLocalExtrafield @76 + unzOpen2 @77 + unzOpenCurrentFile2 @78 + unzOpenCurrentFilePassword @79 + + zipOpen @80 + zipOpenNewFileInZip @81 + zipWriteInFileInZip @82 + zipCloseFileInZip @83 + zipClose @84 + zipOpenNewFileInZip2 @86 + zipCloseFileInZipRaw @87 + zipOpen2 @88 + zipOpenNewFileInZip3 @89 + + unzGetFilePos @100 + unzGoToFilePos @101 + + fill_win32_filefunc @110 + +; zlibwapi v1.2.4 added: + fill_win32_filefunc64 @111 + fill_win32_filefunc64A @112 + fill_win32_filefunc64W @113 + + unzOpen64 @120 + unzOpen2_64 @121 + unzGetGlobalInfo64 @122 + unzGetCurrentFileInfo64 @124 + unzGetCurrentFileZStreamPos64 @125 + unztell64 @126 + unzGetFilePos64 @127 + unzGoToFilePos64 @128 + + zipOpen64 @130 + zipOpen2_64 @131 + zipOpenNewFileInZip64 @132 + zipOpenNewFileInZip2_64 @133 + zipOpenNewFileInZip3_64 @134 + zipOpenNewFileInZip4_64 @135 + zipCloseFileInZipRaw64 @136 + +; zlib1 v1.2.4 added: + adler32_combine @140 + crc32_combine @142 + deflateSetHeader @144 + deflateTune @145 + gzbuffer @146 + gzclose_r @147 + gzclose_w @148 + gzdirect @149 + gzoffset @150 + inflateGetHeader @156 + inflateMark @157 + inflatePrime @158 + inflateReset2 @159 + inflateUndermine @160 + +; zlib1 v1.2.6 added: + gzgetc_ @161 + inflateResetKeep @163 + deflateResetKeep @164 + +; zlib1 v1.2.7 added: + gzopen_w @165 + +; zlib1 v1.2.8 added: + inflateGetDictionary @166 + gzvprintf @167 + +; zlib1 v1.2.9 added: + inflateCodesUsed @168 + inflateValidate @169 + uncompress2 @170 + gzfread @171 + gzfwrite @172 + deflateGetDictionary @173 + adler32_z @174 + crc32_z @175 ADDED compat/zlib/contrib/vstudio/vc14/zlibvc.sln Index: compat/zlib/contrib/vstudio/vc14/zlibvc.sln ================================================================== --- /dev/null +++ compat/zlib/contrib/vstudio/vc14/zlibvc.sln @@ -0,0 +1,119 @@ + +Microsoft Visual Studio Solution File, Format Version 12.00 +# Visual Studio 14 +VisualStudioVersion = 14.0.25420.1 +MinimumVisualStudioVersion = 10.0.40219.1 +Project("{8BC9CEB8-8B4A-11D0-8D11-00A0C91BC942}") = "zlibvc", "zlibvc.vcxproj", "{8FD826F8-3739-44E6-8CC8-997122E53B8D}" +EndProject +Project("{8BC9CEB8-8B4A-11D0-8D11-00A0C91BC942}") = "zlibstat", "zlibstat.vcxproj", "{745DEC58-EBB3-47A9-A9B8-4C6627C01BF8}" +EndProject +Project("{8BC9CEB8-8B4A-11D0-8D11-00A0C91BC942}") = "testzlib", "testzlib.vcxproj", "{AA6666AA-E09F-4135-9C0C-4FE50C3C654B}" +EndProject +Project("{8BC9CEB8-8B4A-11D0-8D11-00A0C91BC942}") = "testzlibdll", "testzlibdll.vcxproj", "{C52F9E7B-498A-42BE-8DB4-85A15694366A}" +EndProject +Project("{8BC9CEB8-8B4A-11D0-8D11-00A0C91BC942}") = "minizip", "minizip.vcxproj", "{48CDD9DC-E09F-4135-9C0C-4FE50C3C654B}" +EndProject +Project("{8BC9CEB8-8B4A-11D0-8D11-00A0C91BC942}") = "miniunz", "miniunz.vcxproj", "{C52F9E7B-498A-42BE-8DB4-85A15694382A}" +EndProject +Global + GlobalSection(SolutionConfigurationPlatforms) = preSolution + Debug|Itanium = Debug|Itanium + Debug|Win32 = Debug|Win32 + Debug|x64 = Debug|x64 + Release|Itanium = Release|Itanium + Release|Win32 = Release|Win32 + Release|x64 = Release|x64 + ReleaseWithoutAsm|Itanium = ReleaseWithoutAsm|Itanium + ReleaseWithoutAsm|Win32 = ReleaseWithoutAsm|Win32 + ReleaseWithoutAsm|x64 = ReleaseWithoutAsm|x64 + EndGlobalSection + GlobalSection(ProjectConfigurationPlatforms) = postSolution + {8FD826F8-3739-44E6-8CC8-997122E53B8D}.Debug|Itanium.ActiveCfg = Debug|Win32 + {8FD826F8-3739-44E6-8CC8-997122E53B8D}.Debug|Win32.ActiveCfg = Debug|Win32 + {8FD826F8-3739-44E6-8CC8-997122E53B8D}.Debug|Win32.Build.0 = Debug|Win32 + {8FD826F8-3739-44E6-8CC8-997122E53B8D}.Debug|x64.ActiveCfg = Debug|x64 + {8FD826F8-3739-44E6-8CC8-997122E53B8D}.Debug|x64.Build.0 = Debug|x64 + {8FD826F8-3739-44E6-8CC8-997122E53B8D}.Release|Itanium.ActiveCfg = Release|Win32 + {8FD826F8-3739-44E6-8CC8-997122E53B8D}.Release|Win32.ActiveCfg = Release|Win32 + {8FD826F8-3739-44E6-8CC8-997122E53B8D}.Release|Win32.Build.0 = Release|Win32 + {8FD826F8-3739-44E6-8CC8-997122E53B8D}.Release|x64.ActiveCfg = Release|x64 + {8FD826F8-3739-44E6-8CC8-997122E53B8D}.Release|x64.Build.0 = Release|x64 + {8FD826F8-3739-44E6-8CC8-997122E53B8D}.ReleaseWithoutAsm|Itanium.ActiveCfg = ReleaseWithoutAsm|Win32 + {8FD826F8-3739-44E6-8CC8-997122E53B8D}.ReleaseWithoutAsm|Win32.ActiveCfg = ReleaseWithoutAsm|Win32 + {8FD826F8-3739-44E6-8CC8-997122E53B8D}.ReleaseWithoutAsm|Win32.Build.0 = ReleaseWithoutAsm|Win32 + {8FD826F8-3739-44E6-8CC8-997122E53B8D}.ReleaseWithoutAsm|x64.ActiveCfg = ReleaseWithoutAsm|x64 + {8FD826F8-3739-44E6-8CC8-997122E53B8D}.ReleaseWithoutAsm|x64.Build.0 = ReleaseWithoutAsm|x64 + {745DEC58-EBB3-47A9-A9B8-4C6627C01BF8}.Debug|Itanium.ActiveCfg = Debug|Win32 + {745DEC58-EBB3-47A9-A9B8-4C6627C01BF8}.Debug|Win32.ActiveCfg = Debug|Win32 + {745DEC58-EBB3-47A9-A9B8-4C6627C01BF8}.Debug|Win32.Build.0 = Debug|Win32 + {745DEC58-EBB3-47A9-A9B8-4C6627C01BF8}.Debug|x64.ActiveCfg = Debug|x64 + {745DEC58-EBB3-47A9-A9B8-4C6627C01BF8}.Debug|x64.Build.0 = Debug|x64 + {745DEC58-EBB3-47A9-A9B8-4C6627C01BF8}.Release|Itanium.ActiveCfg = Release|Win32 + {745DEC58-EBB3-47A9-A9B8-4C6627C01BF8}.Release|Win32.ActiveCfg = Release|Win32 + {745DEC58-EBB3-47A9-A9B8-4C6627C01BF8}.Release|Win32.Build.0 = Release|Win32 + {745DEC58-EBB3-47A9-A9B8-4C6627C01BF8}.Release|x64.ActiveCfg = Release|x64 + {745DEC58-EBB3-47A9-A9B8-4C6627C01BF8}.Release|x64.Build.0 = Release|x64 + {745DEC58-EBB3-47A9-A9B8-4C6627C01BF8}.ReleaseWithoutAsm|Itanium.ActiveCfg = ReleaseWithoutAsm|Win32 + {745DEC58-EBB3-47A9-A9B8-4C6627C01BF8}.ReleaseWithoutAsm|Win32.ActiveCfg = ReleaseWithoutAsm|Win32 + {745DEC58-EBB3-47A9-A9B8-4C6627C01BF8}.ReleaseWithoutAsm|Win32.Build.0 = ReleaseWithoutAsm|Win32 + {745DEC58-EBB3-47A9-A9B8-4C6627C01BF8}.ReleaseWithoutAsm|x64.ActiveCfg = ReleaseWithoutAsm|x64 + {745DEC58-EBB3-47A9-A9B8-4C6627C01BF8}.ReleaseWithoutAsm|x64.Build.0 = ReleaseWithoutAsm|x64 + {AA6666AA-E09F-4135-9C0C-4FE50C3C654B}.Debug|Itanium.ActiveCfg = Debug|Win32 + {AA6666AA-E09F-4135-9C0C-4FE50C3C654B}.Debug|Win32.ActiveCfg = Debug|Win32 + {AA6666AA-E09F-4135-9C0C-4FE50C3C654B}.Debug|Win32.Build.0 = Debug|Win32 + {AA6666AA-E09F-4135-9C0C-4FE50C3C654B}.Debug|x64.ActiveCfg = Debug|x64 + {AA6666AA-E09F-4135-9C0C-4FE50C3C654B}.Debug|x64.Build.0 = Debug|x64 + {AA6666AA-E09F-4135-9C0C-4FE50C3C654B}.Release|Itanium.ActiveCfg = Release|Win32 + {AA6666AA-E09F-4135-9C0C-4FE50C3C654B}.Release|Win32.ActiveCfg = Release|Win32 + {AA6666AA-E09F-4135-9C0C-4FE50C3C654B}.Release|Win32.Build.0 = Release|Win32 + {AA6666AA-E09F-4135-9C0C-4FE50C3C654B}.Release|x64.ActiveCfg = Release|x64 + {AA6666AA-E09F-4135-9C0C-4FE50C3C654B}.Release|x64.Build.0 = Release|x64 + {AA6666AA-E09F-4135-9C0C-4FE50C3C654B}.ReleaseWithoutAsm|Itanium.ActiveCfg = ReleaseWithoutAsm|Win32 + {AA6666AA-E09F-4135-9C0C-4FE50C3C654B}.ReleaseWithoutAsm|Win32.ActiveCfg = ReleaseWithoutAsm|Win32 + {AA6666AA-E09F-4135-9C0C-4FE50C3C654B}.ReleaseWithoutAsm|Win32.Build.0 = ReleaseWithoutAsm|Win32 + {AA6666AA-E09F-4135-9C0C-4FE50C3C654B}.ReleaseWithoutAsm|x64.ActiveCfg = ReleaseWithoutAsm|x64 + {AA6666AA-E09F-4135-9C0C-4FE50C3C654B}.ReleaseWithoutAsm|x64.Build.0 = ReleaseWithoutAsm|x64 + {C52F9E7B-498A-42BE-8DB4-85A15694366A}.Debug|Itanium.ActiveCfg = Debug|Win32 + {C52F9E7B-498A-42BE-8DB4-85A15694366A}.Debug|Win32.ActiveCfg = Debug|Win32 + {C52F9E7B-498A-42BE-8DB4-85A15694366A}.Debug|Win32.Build.0 = Debug|Win32 + {C52F9E7B-498A-42BE-8DB4-85A15694366A}.Debug|x64.ActiveCfg = Debug|x64 + {C52F9E7B-498A-42BE-8DB4-85A15694366A}.Debug|x64.Build.0 = Debug|x64 + {C52F9E7B-498A-42BE-8DB4-85A15694366A}.Release|Itanium.ActiveCfg = Release|Win32 + {C52F9E7B-498A-42BE-8DB4-85A15694366A}.Release|Win32.ActiveCfg = Release|Win32 + {C52F9E7B-498A-42BE-8DB4-85A15694366A}.Release|Win32.Build.0 = Release|Win32 + {C52F9E7B-498A-42BE-8DB4-85A15694366A}.Release|x64.ActiveCfg = Release|x64 + {C52F9E7B-498A-42BE-8DB4-85A15694366A}.Release|x64.Build.0 = Release|x64 + {C52F9E7B-498A-42BE-8DB4-85A15694366A}.ReleaseWithoutAsm|Itanium.ActiveCfg = Release|Win32 + {C52F9E7B-498A-42BE-8DB4-85A15694366A}.ReleaseWithoutAsm|Win32.ActiveCfg = Release|Win32 + {C52F9E7B-498A-42BE-8DB4-85A15694366A}.ReleaseWithoutAsm|x64.ActiveCfg = Release|x64 + {48CDD9DC-E09F-4135-9C0C-4FE50C3C654B}.Debug|Itanium.ActiveCfg = Debug|Win32 + {48CDD9DC-E09F-4135-9C0C-4FE50C3C654B}.Debug|Win32.ActiveCfg = Debug|Win32 + {48CDD9DC-E09F-4135-9C0C-4FE50C3C654B}.Debug|Win32.Build.0 = Debug|Win32 + {48CDD9DC-E09F-4135-9C0C-4FE50C3C654B}.Debug|x64.ActiveCfg = Debug|x64 + {48CDD9DC-E09F-4135-9C0C-4FE50C3C654B}.Debug|x64.Build.0 = Debug|x64 + {48CDD9DC-E09F-4135-9C0C-4FE50C3C654B}.Release|Itanium.ActiveCfg = Release|Win32 + {48CDD9DC-E09F-4135-9C0C-4FE50C3C654B}.Release|Win32.ActiveCfg = Release|Win32 + {48CDD9DC-E09F-4135-9C0C-4FE50C3C654B}.Release|Win32.Build.0 = Release|Win32 + {48CDD9DC-E09F-4135-9C0C-4FE50C3C654B}.Release|x64.ActiveCfg = Release|x64 + {48CDD9DC-E09F-4135-9C0C-4FE50C3C654B}.Release|x64.Build.0 = Release|x64 + {48CDD9DC-E09F-4135-9C0C-4FE50C3C654B}.ReleaseWithoutAsm|Itanium.ActiveCfg = Release|Win32 + {48CDD9DC-E09F-4135-9C0C-4FE50C3C654B}.ReleaseWithoutAsm|Win32.ActiveCfg = Release|Win32 + {48CDD9DC-E09F-4135-9C0C-4FE50C3C654B}.ReleaseWithoutAsm|x64.ActiveCfg = Release|x64 + {C52F9E7B-498A-42BE-8DB4-85A15694382A}.Debug|Itanium.ActiveCfg = Debug|Win32 + {C52F9E7B-498A-42BE-8DB4-85A15694382A}.Debug|Win32.ActiveCfg = Debug|Win32 + {C52F9E7B-498A-42BE-8DB4-85A15694382A}.Debug|Win32.Build.0 = Debug|Win32 + {C52F9E7B-498A-42BE-8DB4-85A15694382A}.Debug|x64.ActiveCfg = Debug|x64 + {C52F9E7B-498A-42BE-8DB4-85A15694382A}.Debug|x64.Build.0 = Debug|x64 + {C52F9E7B-498A-42BE-8DB4-85A15694382A}.Release|Itanium.ActiveCfg = Release|Win32 + {C52F9E7B-498A-42BE-8DB4-85A15694382A}.Release|Win32.ActiveCfg = Release|Win32 + {C52F9E7B-498A-42BE-8DB4-85A15694382A}.Release|Win32.Build.0 = Release|Win32 + {C52F9E7B-498A-42BE-8DB4-85A15694382A}.Release|x64.ActiveCfg = Release|x64 + {C52F9E7B-498A-42BE-8DB4-85A15694382A}.Release|x64.Build.0 = Release|x64 + {C52F9E7B-498A-42BE-8DB4-85A15694382A}.ReleaseWithoutAsm|Itanium.ActiveCfg = Release|Win32 + {C52F9E7B-498A-42BE-8DB4-85A15694382A}.ReleaseWithoutAsm|Win32.ActiveCfg = Release|Win32 + {C52F9E7B-498A-42BE-8DB4-85A15694382A}.ReleaseWithoutAsm|x64.ActiveCfg = Release|x64 + EndGlobalSection + GlobalSection(SolutionProperties) = preSolution + HideSolutionNode = FALSE + EndGlobalSection +EndGlobal ADDED compat/zlib/contrib/vstudio/vc14/zlibvc.vcxproj Index: compat/zlib/contrib/vstudio/vc14/zlibvc.vcxproj ================================================================== --- /dev/null +++ compat/zlib/contrib/vstudio/vc14/zlibvc.vcxproj @@ -0,0 +1,692 @@ + + + + + Debug + Itanium + + + Debug + Win32 + + + Debug + x64 + + + ReleaseWithoutAsm + Itanium + + + ReleaseWithoutAsm + Win32 + + + ReleaseWithoutAsm + x64 + + + Release + Itanium + + + Release + Win32 + + + Release + x64 + + + + {8FD826F8-3739-44E6-8CC8-997122E53B8D} + + + + DynamicLibrary + false + true + v140 + + + DynamicLibrary + false + true + v140 + + + DynamicLibrary + false + v140 + Unicode + + + DynamicLibrary + false + true + v140 + + + DynamicLibrary + false + true + v140 + + + DynamicLibrary + false + v140 + + + DynamicLibrary + false + true + v140 + + + DynamicLibrary + false + true + v140 + + + DynamicLibrary + false + v140 + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + <_ProjectFileVersion>10.0.30128.1 + x86\ZlibDll$(Configuration)\ + x86\ZlibDll$(Configuration)\Tmp\ + true + false + x86\ZlibDll$(Configuration)\ + x86\ZlibDll$(Configuration)\Tmp\ + false + false + x86\ZlibDll$(Configuration)\ + x86\ZlibDll$(Configuration)\Tmp\ + false + false + x64\ZlibDll$(Configuration)\ + x64\ZlibDll$(Configuration)\Tmp\ + true + false + ia64\ZlibDll$(Configuration)\ + ia64\ZlibDll$(Configuration)\Tmp\ + true + false + x64\ZlibDll$(Configuration)\ + x64\ZlibDll$(Configuration)\Tmp\ + false + false + ia64\ZlibDll$(Configuration)\ + ia64\ZlibDll$(Configuration)\Tmp\ + false + false + x64\ZlibDll$(Configuration)\ + x64\ZlibDll$(Configuration)\Tmp\ + false + false + ia64\ZlibDll$(Configuration)\ + ia64\ZlibDll$(Configuration)\Tmp\ + false + false + AllRules.ruleset + + + AllRules.ruleset + + + AllRules.ruleset + + + AllRules.ruleset + + + AllRules.ruleset + + + AllRules.ruleset + + + AllRules.ruleset + + + AllRules.ruleset + + + AllRules.ruleset + + + zlibwapi + zlibwapi + zlibwapi + zlibwapi + zlibwapi + zlibwapi + + + + _DEBUG;%(PreprocessorDefinitions) + true + true + Win32 + $(OutDir)zlibvc.tlb + + + Disabled + ..\..\..;..\..\masmx86;%(AdditionalIncludeDirectories) + WIN32;_CRT_NONSTDC_NO_DEPRECATE;_CRT_SECURE_NO_DEPRECATE;_CRT_NONSTDC_NO_WARNINGS;ZLIB_WINAPI;ASMV;ASMINF;%(PreprocessorDefinitions) + + + MultiThreadedDebugDLL + false + $(IntDir)zlibvc.pch + $(IntDir) + $(IntDir) + $(OutDir) + + + Level3 + true + ProgramDatabase + + + _DEBUG;%(PreprocessorDefinitions) + 0x040c + + + /MACHINE:I386 %(AdditionalOptions) + ..\..\masmx86\match686.obj;..\..\masmx86\inffas32.obj;%(AdditionalDependencies) + $(OutDir)zlibwapi.dll + true + .\zlibvc.def + true + $(OutDir)zlibwapi.pdb + true + $(OutDir)zlibwapi.map + Windows + false + + + $(OutDir)zlibwapi.lib + + + cd ..\..\masmx86 +bld_ml32.bat + + + + + NDEBUG;%(PreprocessorDefinitions) + true + true + Win32 + $(OutDir)zlibvc.tlb + + + OnlyExplicitInline + ..\..\..;..\..\masmx86;%(AdditionalIncludeDirectories) + WIN32;_CRT_NONSTDC_NO_DEPRECATE;_CRT_SECURE_NO_DEPRECATE;_CRT_NONSTDC_NO_WARNINGS;ZLIB_WINAPI;%(PreprocessorDefinitions) + true + + + MultiThreadedDLL + false + true + $(IntDir)zlibvc.pch + All + $(IntDir) + $(IntDir) + $(OutDir) + + + Level3 + true + + + NDEBUG;%(PreprocessorDefinitions) + 0x040c + + + /MACHINE:I386 %(AdditionalOptions) + $(OutDir)zlibwapi.dll + true + false + .\zlibvc.def + $(OutDir)zlibwapi.pdb + true + $(OutDir)zlibwapi.map + Windows + false + + + $(OutDir)zlibwapi.lib + + + + + NDEBUG;%(PreprocessorDefinitions) + true + true + Win32 + $(OutDir)zlibvc.tlb + + + OnlyExplicitInline + ..\..\..;..\..\masmx86;%(AdditionalIncludeDirectories) + WIN32;_CRT_NONSTDC_NO_DEPRECATE;_CRT_SECURE_NO_DEPRECATE;_CRT_NONSTDC_NO_WARNINGS;ZLIB_WINAPI;ASMV;ASMINF;%(PreprocessorDefinitions) + true + + + MultiThreaded + false + true + $(IntDir)zlibvc.pch + All + $(IntDir) + $(IntDir) + $(OutDir) + + + Level3 + true + + + NDEBUG;%(PreprocessorDefinitions) + 0x040c + + + /MACHINE:I386 %(AdditionalOptions) + ..\..\masmx86\match686.obj;..\..\masmx86\inffas32.obj;%(AdditionalDependencies) + $(OutDir)zlibwapi.dll + true + false + .\zlibvc.def + $(OutDir)zlibwapi.pdb + true + $(OutDir)zlibwapi.map + Windows + false + + + $(OutDir)zlibwapi.lib + false + + + cd ..\..\masmx86 +bld_ml32.bat + + + + + _DEBUG;%(PreprocessorDefinitions) + true + true + X64 + $(OutDir)zlibvc.tlb + + + Disabled + ..\..\..;..\..\masmx86;%(AdditionalIncludeDirectories) + WIN32;_CRT_NONSTDC_NO_DEPRECATE;_CRT_SECURE_NO_DEPRECATE;_CRT_NONSTDC_NO_WARNINGS;ZLIB_WINAPI;ASMV;ASMINF;WIN64;%(PreprocessorDefinitions) + + + MultiThreadedDebugDLL + false + $(IntDir)zlibvc.pch + $(IntDir) + $(IntDir) + $(OutDir) + + + Level3 + true + ProgramDatabase + + + _DEBUG;%(PreprocessorDefinitions) + 0x040c + + + ..\..\masmx64\gvmat64.obj;..\..\masmx64\inffasx64.obj;%(AdditionalDependencies) + $(OutDir)zlibwapi.dll + true + .\zlibvc.def + true + $(OutDir)zlibwapi.pdb + true + $(OutDir)zlibwapi.map + Windows + $(OutDir)zlibwapi.lib + MachineX64 + + + cd ..\..\contrib\masmx64 +bld_ml64.bat + + + + + _DEBUG;%(PreprocessorDefinitions) + true + true + Itanium + $(OutDir)zlibvc.tlb + + + Disabled + ..\..\..;..\..\masmx86;%(AdditionalIncludeDirectories) + WIN32;_CRT_NONSTDC_NO_DEPRECATE;_CRT_SECURE_NO_DEPRECATE;_CRT_NONSTDC_NO_WARNINGS;ZLIB_WINAPI;WIN64;%(PreprocessorDefinitions) + + + MultiThreadedDebugDLL + false + $(IntDir)zlibvc.pch + $(IntDir) + $(IntDir) + $(OutDir) + + + Level3 + true + ProgramDatabase + + + _DEBUG;%(PreprocessorDefinitions) + 0x040c + + + $(OutDir)zlibwapi.dll + true + .\zlibvc.def + true + $(OutDir)zlibwapi.pdb + true + $(OutDir)zlibwapi.map + Windows + $(OutDir)zlibwapi.lib + MachineIA64 + + + + + NDEBUG;%(PreprocessorDefinitions) + true + true + X64 + $(OutDir)zlibvc.tlb + + + OnlyExplicitInline + ..\..\..;..\..\masmx86;%(AdditionalIncludeDirectories) + WIN32;_CRT_NONSTDC_NO_DEPRECATE;_CRT_SECURE_NO_DEPRECATE;_CRT_NONSTDC_NO_WARNINGS;ZLIB_WINAPI;WIN64;%(PreprocessorDefinitions) + true + + + MultiThreadedDLL + false + true + $(IntDir)zlibvc.pch + All + $(IntDir) + $(IntDir) + $(OutDir) + + + Level3 + true + + + NDEBUG;%(PreprocessorDefinitions) + 0x040c + + + $(OutDir)zlibwapi.dll + true + false + .\zlibvc.def + $(OutDir)zlibwapi.pdb + true + $(OutDir)zlibwapi.map + Windows + $(OutDir)zlibwapi.lib + MachineX64 + + + + + NDEBUG;%(PreprocessorDefinitions) + true + true + Itanium + $(OutDir)zlibvc.tlb + + + OnlyExplicitInline + ..\..\..;..\..\masmx86;%(AdditionalIncludeDirectories) + WIN32;_CRT_NONSTDC_NO_DEPRECATE;_CRT_SECURE_NO_DEPRECATE;_CRT_NONSTDC_NO_WARNINGS;ZLIB_WINAPI;WIN64;%(PreprocessorDefinitions) + true + + + MultiThreadedDLL + false + true + $(IntDir)zlibvc.pch + All + $(IntDir) + $(IntDir) + $(OutDir) + + + Level3 + true + + + NDEBUG;%(PreprocessorDefinitions) + 0x040c + + + $(OutDir)zlibwapi.dll + true + false + .\zlibvc.def + $(OutDir)zlibwapi.pdb + true + $(OutDir)zlibwapi.map + Windows + $(OutDir)zlibwapi.lib + MachineIA64 + + + + + NDEBUG;%(PreprocessorDefinitions) + true + true + X64 + $(OutDir)zlibvc.tlb + + + OnlyExplicitInline + ..\..\..;..\..\masmx86;%(AdditionalIncludeDirectories) + _CRT_NONSTDC_NO_DEPRECATE;_CRT_SECURE_NO_DEPRECATE;_CRT_NONSTDC_NO_WARNINGS;ZLIB_WINAPI;ASMV;ASMINF;WIN64;%(PreprocessorDefinitions) + true + + + MultiThreadedDLL + false + true + $(IntDir)zlibvc.pch + All + $(IntDir) + $(IntDir) + $(OutDir) + + + Level3 + true + + + NDEBUG;%(PreprocessorDefinitions) + 0x040c + + + ..\..\masmx64\gvmat64.obj;..\..\masmx64\inffasx64.obj;%(AdditionalDependencies) + $(OutDir)zlibwapi.dll + true + false + .\zlibvc.def + $(OutDir)zlibwapi.pdb + true + $(OutDir)zlibwapi.map + Windows + $(OutDir)zlibwapi.lib + MachineX64 + + + cd ..\..\masmx64 +bld_ml64.bat + + + + + NDEBUG;%(PreprocessorDefinitions) + true + true + Itanium + $(OutDir)zlibvc.tlb + + + OnlyExplicitInline + ..\..\..;..\..\masmx86;%(AdditionalIncludeDirectories) + _CRT_NONSTDC_NO_DEPRECATE;_CRT_SECURE_NO_DEPRECATE;_CRT_NONSTDC_NO_WARNINGS;ZLIB_WINAPI;WIN64;%(PreprocessorDefinitions) + true + + + MultiThreadedDLL + false + true + $(IntDir)zlibvc.pch + All + $(IntDir) + $(IntDir) + $(OutDir) + + + Level3 + true + + + NDEBUG;%(PreprocessorDefinitions) + 0x040c + + + $(OutDir)zlibwapi.dll + true + false + .\zlibvc.def + $(OutDir)zlibwapi.pdb + true + $(OutDir)zlibwapi.map + Windows + $(OutDir)zlibwapi.lib + MachineIA64 + + + + + + + + + + + + + + true + true + true + true + true + true + + + + + + + + + + %(AdditionalIncludeDirectories) + ZLIB_INTERNAL;%(PreprocessorDefinitions) + %(AdditionalIncludeDirectories) + ZLIB_INTERNAL;%(PreprocessorDefinitions) + %(AdditionalIncludeDirectories) + ZLIB_INTERNAL;%(PreprocessorDefinitions) + + + %(AdditionalIncludeDirectories) + ZLIB_INTERNAL;%(PreprocessorDefinitions) + %(AdditionalIncludeDirectories) + ZLIB_INTERNAL;%(PreprocessorDefinitions) + %(AdditionalIncludeDirectories) + ZLIB_INTERNAL;%(PreprocessorDefinitions) + + + + + + + + + + + + + + + + + + + + + + + + ADDED compat/zlib/contrib/vstudio/vc9/miniunz.vcproj Index: compat/zlib/contrib/vstudio/vc9/miniunz.vcproj ================================================================== --- /dev/null +++ compat/zlib/contrib/vstudio/vc9/miniunz.vcproj @@ -0,0 +1,565 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + ADDED compat/zlib/contrib/vstudio/vc9/minizip.vcproj Index: compat/zlib/contrib/vstudio/vc9/minizip.vcproj ================================================================== --- /dev/null +++ compat/zlib/contrib/vstudio/vc9/minizip.vcproj @@ -0,0 +1,562 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + ADDED compat/zlib/contrib/vstudio/vc9/testzlib.vcproj Index: compat/zlib/contrib/vstudio/vc9/testzlib.vcproj ================================================================== --- /dev/null +++ compat/zlib/contrib/vstudio/vc9/testzlib.vcproj @@ -0,0 +1,852 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + ADDED compat/zlib/contrib/vstudio/vc9/testzlibdll.vcproj Index: compat/zlib/contrib/vstudio/vc9/testzlibdll.vcproj ================================================================== --- /dev/null +++ compat/zlib/contrib/vstudio/vc9/testzlibdll.vcproj @@ -0,0 +1,565 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + ADDED compat/zlib/contrib/vstudio/vc9/zlib.rc Index: compat/zlib/contrib/vstudio/vc9/zlib.rc ================================================================== --- /dev/null +++ compat/zlib/contrib/vstudio/vc9/zlib.rc @@ -0,0 +1,32 @@ +#include + +#define IDR_VERSION1 1 +IDR_VERSION1 VERSIONINFO MOVEABLE IMPURE LOADONCALL DISCARDABLE + FILEVERSION 1, 2, 11, 0 + PRODUCTVERSION 1, 2, 11, 0 + FILEFLAGSMASK VS_FFI_FILEFLAGSMASK + FILEFLAGS 0 + FILEOS VOS_DOS_WINDOWS32 + FILETYPE VFT_DLL + FILESUBTYPE 0 // not used +BEGIN + BLOCK "StringFileInfo" + BEGIN + BLOCK "040904E4" + //language ID = U.S. English, char set = Windows, Multilingual + + BEGIN + VALUE "FileDescription", "zlib data compression and ZIP file I/O library\0" + VALUE "FileVersion", "1.2.11\0" + VALUE "InternalName", "zlib\0" + VALUE "OriginalFilename", "zlibwapi.dll\0" + VALUE "ProductName", "ZLib.DLL\0" + VALUE "Comments","DLL support by Alessandro Iacopetti & Gilles Vollant\0" + VALUE "LegalCopyright", "(C) 1995-2017 Jean-loup Gailly & Mark Adler\0" + END + END + BLOCK "VarFileInfo" + BEGIN + VALUE "Translation", 0x0409, 1252 + END +END ADDED compat/zlib/contrib/vstudio/vc9/zlibstat.vcproj Index: compat/zlib/contrib/vstudio/vc9/zlibstat.vcproj ================================================================== --- /dev/null +++ compat/zlib/contrib/vstudio/vc9/zlibstat.vcproj @@ -0,0 +1,835 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + ADDED compat/zlib/contrib/vstudio/vc9/zlibvc.def Index: compat/zlib/contrib/vstudio/vc9/zlibvc.def ================================================================== --- /dev/null +++ compat/zlib/contrib/vstudio/vc9/zlibvc.def @@ -0,0 +1,153 @@ +LIBRARY +; zlib data compression and ZIP file I/O library + +VERSION 1.2 + +EXPORTS + adler32 @1 + compress @2 + crc32 @3 + deflate @4 + deflateCopy @5 + deflateEnd @6 + deflateInit2_ @7 + deflateInit_ @8 + deflateParams @9 + deflateReset @10 + deflateSetDictionary @11 + gzclose @12 + gzdopen @13 + gzerror @14 + gzflush @15 + gzopen @16 + gzread @17 + gzwrite @18 + inflate @19 + inflateEnd @20 + inflateInit2_ @21 + inflateInit_ @22 + inflateReset @23 + inflateSetDictionary @24 + inflateSync @25 + uncompress @26 + zlibVersion @27 + gzprintf @28 + gzputc @29 + gzgetc @30 + gzseek @31 + gzrewind @32 + gztell @33 + gzeof @34 + gzsetparams @35 + zError @36 + inflateSyncPoint @37 + get_crc_table @38 + compress2 @39 + gzputs @40 + gzgets @41 + inflateCopy @42 + inflateBackInit_ @43 + inflateBack @44 + inflateBackEnd @45 + compressBound @46 + deflateBound @47 + gzclearerr @48 + gzungetc @49 + zlibCompileFlags @50 + deflatePrime @51 + deflatePending @52 + + unzOpen @61 + unzClose @62 + unzGetGlobalInfo @63 + unzGetCurrentFileInfo @64 + unzGoToFirstFile @65 + unzGoToNextFile @66 + unzOpenCurrentFile @67 + unzReadCurrentFile @68 + unzOpenCurrentFile3 @69 + unztell @70 + unzeof @71 + unzCloseCurrentFile @72 + unzGetGlobalComment @73 + unzStringFileNameCompare @74 + unzLocateFile @75 + unzGetLocalExtrafield @76 + unzOpen2 @77 + unzOpenCurrentFile2 @78 + unzOpenCurrentFilePassword @79 + + zipOpen @80 + zipOpenNewFileInZip @81 + zipWriteInFileInZip @82 + zipCloseFileInZip @83 + zipClose @84 + zipOpenNewFileInZip2 @86 + zipCloseFileInZipRaw @87 + zipOpen2 @88 + zipOpenNewFileInZip3 @89 + + unzGetFilePos @100 + unzGoToFilePos @101 + + fill_win32_filefunc @110 + +; zlibwapi v1.2.4 added: + fill_win32_filefunc64 @111 + fill_win32_filefunc64A @112 + fill_win32_filefunc64W @113 + + unzOpen64 @120 + unzOpen2_64 @121 + unzGetGlobalInfo64 @122 + unzGetCurrentFileInfo64 @124 + unzGetCurrentFileZStreamPos64 @125 + unztell64 @126 + unzGetFilePos64 @127 + unzGoToFilePos64 @128 + + zipOpen64 @130 + zipOpen2_64 @131 + zipOpenNewFileInZip64 @132 + zipOpenNewFileInZip2_64 @133 + zipOpenNewFileInZip3_64 @134 + zipOpenNewFileInZip4_64 @135 + zipCloseFileInZipRaw64 @136 + +; zlib1 v1.2.4 added: + adler32_combine @140 + crc32_combine @142 + deflateSetHeader @144 + deflateTune @145 + gzbuffer @146 + gzclose_r @147 + gzclose_w @148 + gzdirect @149 + gzoffset @150 + inflateGetHeader @156 + inflateMark @157 + inflatePrime @158 + inflateReset2 @159 + inflateUndermine @160 + +; zlib1 v1.2.6 added: + gzgetc_ @161 + inflateResetKeep @163 + deflateResetKeep @164 + +; zlib1 v1.2.7 added: + gzopen_w @165 + +; zlib1 v1.2.8 added: + inflateGetDictionary @166 + gzvprintf @167 + +; zlib1 v1.2.9 added: + inflateCodesUsed @168 + inflateValidate @169 + uncompress2 @170 + gzfread @171 + gzfwrite @172 + deflateGetDictionary @173 + adler32_z @174 + crc32_z @175 ADDED compat/zlib/contrib/vstudio/vc9/zlibvc.sln Index: compat/zlib/contrib/vstudio/vc9/zlibvc.sln ================================================================== --- /dev/null +++ compat/zlib/contrib/vstudio/vc9/zlibvc.sln @@ -0,0 +1,144 @@ + +Microsoft Visual Studio Solution File, Format Version 10.00 +# Visual Studio 2008 +Project("{8BC9CEB8-8B4A-11D0-8D11-00A0C91BC942}") = "zlibvc", "zlibvc.vcproj", "{8FD826F8-3739-44E6-8CC8-997122E53B8D}" +EndProject +Project("{8BC9CEB8-8B4A-11D0-8D11-00A0C91BC942}") = "zlibstat", "zlibstat.vcproj", "{745DEC58-EBB3-47A9-A9B8-4C6627C01BF8}" +EndProject +Project("{8BC9CEB8-8B4A-11D0-8D11-00A0C91BC942}") = "testzlib", "testzlib.vcproj", "{AA6666AA-E09F-4135-9C0C-4FE50C3C654B}" +EndProject +Project("{8BC9CEB8-8B4A-11D0-8D11-00A0C91BC942}") = "TestZlibDll", "testzlibdll.vcproj", "{C52F9E7B-498A-42BE-8DB4-85A15694366A}" + ProjectSection(ProjectDependencies) = postProject + {8FD826F8-3739-44E6-8CC8-997122E53B8D} = {8FD826F8-3739-44E6-8CC8-997122E53B8D} + EndProjectSection +EndProject +Project("{8BC9CEB8-8B4A-11D0-8D11-00A0C91BC942}") = "minizip", "minizip.vcproj", "{48CDD9DC-E09F-4135-9C0C-4FE50C3C654B}" + ProjectSection(ProjectDependencies) = postProject + {8FD826F8-3739-44E6-8CC8-997122E53B8D} = {8FD826F8-3739-44E6-8CC8-997122E53B8D} + EndProjectSection +EndProject +Project("{8BC9CEB8-8B4A-11D0-8D11-00A0C91BC942}") = "miniunz", "miniunz.vcproj", "{C52F9E7B-498A-42BE-8DB4-85A15694382A}" + ProjectSection(ProjectDependencies) = postProject + {8FD826F8-3739-44E6-8CC8-997122E53B8D} = {8FD826F8-3739-44E6-8CC8-997122E53B8D} + EndProjectSection +EndProject +Global + GlobalSection(SolutionConfigurationPlatforms) = preSolution + Debug|Itanium = Debug|Itanium + Debug|Win32 = Debug|Win32 + Debug|x64 = Debug|x64 + Release|Itanium = Release|Itanium + Release|Win32 = Release|Win32 + Release|x64 = Release|x64 + ReleaseWithoutAsm|Itanium = ReleaseWithoutAsm|Itanium + ReleaseWithoutAsm|Win32 = ReleaseWithoutAsm|Win32 + ReleaseWithoutAsm|x64 = ReleaseWithoutAsm|x64 + EndGlobalSection + GlobalSection(ProjectConfigurationPlatforms) = postSolution + {8FD826F8-3739-44E6-8CC8-997122E53B8D}.Debug|Itanium.ActiveCfg = Debug|Itanium + {8FD826F8-3739-44E6-8CC8-997122E53B8D}.Debug|Itanium.Build.0 = Debug|Itanium + {8FD826F8-3739-44E6-8CC8-997122E53B8D}.Debug|Win32.ActiveCfg = Debug|Win32 + {8FD826F8-3739-44E6-8CC8-997122E53B8D}.Debug|Win32.Build.0 = Debug|Win32 + {8FD826F8-3739-44E6-8CC8-997122E53B8D}.Debug|x64.ActiveCfg = Debug|x64 + {8FD826F8-3739-44E6-8CC8-997122E53B8D}.Debug|x64.Build.0 = Debug|x64 + {8FD826F8-3739-44E6-8CC8-997122E53B8D}.Release|Itanium.ActiveCfg = Release|Itanium + {8FD826F8-3739-44E6-8CC8-997122E53B8D}.Release|Itanium.Build.0 = Release|Itanium + {8FD826F8-3739-44E6-8CC8-997122E53B8D}.Release|Win32.ActiveCfg = Release|Win32 + {8FD826F8-3739-44E6-8CC8-997122E53B8D}.Release|Win32.Build.0 = Release|Win32 + {8FD826F8-3739-44E6-8CC8-997122E53B8D}.Release|x64.ActiveCfg = Release|x64 + {8FD826F8-3739-44E6-8CC8-997122E53B8D}.Release|x64.Build.0 = Release|x64 + {8FD826F8-3739-44E6-8CC8-997122E53B8D}.ReleaseWithoutAsm|Itanium.ActiveCfg = ReleaseWithoutAsm|Itanium + {8FD826F8-3739-44E6-8CC8-997122E53B8D}.ReleaseWithoutAsm|Itanium.Build.0 = ReleaseWithoutAsm|Itanium + {8FD826F8-3739-44E6-8CC8-997122E53B8D}.ReleaseWithoutAsm|Win32.ActiveCfg = ReleaseWithoutAsm|Win32 + {8FD826F8-3739-44E6-8CC8-997122E53B8D}.ReleaseWithoutAsm|Win32.Build.0 = ReleaseWithoutAsm|Win32 + {8FD826F8-3739-44E6-8CC8-997122E53B8D}.ReleaseWithoutAsm|x64.ActiveCfg = ReleaseWithoutAsm|x64 + {8FD826F8-3739-44E6-8CC8-997122E53B8D}.ReleaseWithoutAsm|x64.Build.0 = ReleaseWithoutAsm|x64 + {745DEC58-EBB3-47A9-A9B8-4C6627C01BF8}.Debug|Itanium.ActiveCfg = Debug|Itanium + {745DEC58-EBB3-47A9-A9B8-4C6627C01BF8}.Debug|Itanium.Build.0 = Debug|Itanium + {745DEC58-EBB3-47A9-A9B8-4C6627C01BF8}.Debug|Win32.ActiveCfg = Debug|Win32 + {745DEC58-EBB3-47A9-A9B8-4C6627C01BF8}.Debug|Win32.Build.0 = Debug|Win32 + {745DEC58-EBB3-47A9-A9B8-4C6627C01BF8}.Debug|x64.ActiveCfg = Debug|x64 + {745DEC58-EBB3-47A9-A9B8-4C6627C01BF8}.Debug|x64.Build.0 = Debug|x64 + {745DEC58-EBB3-47A9-A9B8-4C6627C01BF8}.Release|Itanium.ActiveCfg = Release|Itanium + {745DEC58-EBB3-47A9-A9B8-4C6627C01BF8}.Release|Itanium.Build.0 = Release|Itanium + {745DEC58-EBB3-47A9-A9B8-4C6627C01BF8}.Release|Win32.ActiveCfg = Release|Win32 + {745DEC58-EBB3-47A9-A9B8-4C6627C01BF8}.Release|Win32.Build.0 = Release|Win32 + {745DEC58-EBB3-47A9-A9B8-4C6627C01BF8}.Release|x64.ActiveCfg = Release|x64 + {745DEC58-EBB3-47A9-A9B8-4C6627C01BF8}.Release|x64.Build.0 = Release|x64 + {745DEC58-EBB3-47A9-A9B8-4C6627C01BF8}.ReleaseWithoutAsm|Itanium.ActiveCfg = ReleaseWithoutAsm|Itanium + {745DEC58-EBB3-47A9-A9B8-4C6627C01BF8}.ReleaseWithoutAsm|Itanium.Build.0 = ReleaseWithoutAsm|Itanium + {745DEC58-EBB3-47A9-A9B8-4C6627C01BF8}.ReleaseWithoutAsm|Win32.ActiveCfg = ReleaseWithoutAsm|Win32 + {745DEC58-EBB3-47A9-A9B8-4C6627C01BF8}.ReleaseWithoutAsm|Win32.Build.0 = ReleaseWithoutAsm|Win32 + {745DEC58-EBB3-47A9-A9B8-4C6627C01BF8}.ReleaseWithoutAsm|x64.ActiveCfg = ReleaseWithoutAsm|x64 + {745DEC58-EBB3-47A9-A9B8-4C6627C01BF8}.ReleaseWithoutAsm|x64.Build.0 = ReleaseWithoutAsm|x64 + {AA6666AA-E09F-4135-9C0C-4FE50C3C654B}.Debug|Itanium.ActiveCfg = Debug|Itanium + {AA6666AA-E09F-4135-9C0C-4FE50C3C654B}.Debug|Itanium.Build.0 = Debug|Itanium + {AA6666AA-E09F-4135-9C0C-4FE50C3C654B}.Debug|Win32.ActiveCfg = Debug|Win32 + {AA6666AA-E09F-4135-9C0C-4FE50C3C654B}.Debug|Win32.Build.0 = Debug|Win32 + {AA6666AA-E09F-4135-9C0C-4FE50C3C654B}.Debug|x64.ActiveCfg = Debug|x64 + {AA6666AA-E09F-4135-9C0C-4FE50C3C654B}.Debug|x64.Build.0 = Debug|x64 + {AA6666AA-E09F-4135-9C0C-4FE50C3C654B}.Release|Itanium.ActiveCfg = Release|Itanium + {AA6666AA-E09F-4135-9C0C-4FE50C3C654B}.Release|Itanium.Build.0 = Release|Itanium + {AA6666AA-E09F-4135-9C0C-4FE50C3C654B}.Release|Win32.ActiveCfg = Release|Win32 + {AA6666AA-E09F-4135-9C0C-4FE50C3C654B}.Release|Win32.Build.0 = Release|Win32 + {AA6666AA-E09F-4135-9C0C-4FE50C3C654B}.Release|x64.ActiveCfg = Release|x64 + {AA6666AA-E09F-4135-9C0C-4FE50C3C654B}.Release|x64.Build.0 = Release|x64 + {AA6666AA-E09F-4135-9C0C-4FE50C3C654B}.ReleaseWithoutAsm|Itanium.ActiveCfg = ReleaseWithoutAsm|Itanium + {AA6666AA-E09F-4135-9C0C-4FE50C3C654B}.ReleaseWithoutAsm|Itanium.Build.0 = ReleaseWithoutAsm|Itanium + {AA6666AA-E09F-4135-9C0C-4FE50C3C654B}.ReleaseWithoutAsm|Win32.ActiveCfg = ReleaseWithoutAsm|Win32 + {AA6666AA-E09F-4135-9C0C-4FE50C3C654B}.ReleaseWithoutAsm|Win32.Build.0 = ReleaseWithoutAsm|Win32 + {AA6666AA-E09F-4135-9C0C-4FE50C3C654B}.ReleaseWithoutAsm|x64.ActiveCfg = ReleaseWithoutAsm|x64 + {AA6666AA-E09F-4135-9C0C-4FE50C3C654B}.ReleaseWithoutAsm|x64.Build.0 = ReleaseWithoutAsm|x64 + {C52F9E7B-498A-42BE-8DB4-85A15694366A}.Debug|Itanium.ActiveCfg = Debug|Itanium + {C52F9E7B-498A-42BE-8DB4-85A15694366A}.Debug|Itanium.Build.0 = Debug|Itanium + {C52F9E7B-498A-42BE-8DB4-85A15694366A}.Debug|Win32.ActiveCfg = Debug|Win32 + {C52F9E7B-498A-42BE-8DB4-85A15694366A}.Debug|Win32.Build.0 = Debug|Win32 + {C52F9E7B-498A-42BE-8DB4-85A15694366A}.Debug|x64.ActiveCfg = Debug|x64 + {C52F9E7B-498A-42BE-8DB4-85A15694366A}.Debug|x64.Build.0 = Debug|x64 + {C52F9E7B-498A-42BE-8DB4-85A15694366A}.Release|Itanium.ActiveCfg = Release|Itanium + {C52F9E7B-498A-42BE-8DB4-85A15694366A}.Release|Itanium.Build.0 = Release|Itanium + {C52F9E7B-498A-42BE-8DB4-85A15694366A}.Release|Win32.ActiveCfg = Release|Win32 + {C52F9E7B-498A-42BE-8DB4-85A15694366A}.Release|Win32.Build.0 = Release|Win32 + {C52F9E7B-498A-42BE-8DB4-85A15694366A}.Release|x64.ActiveCfg = Release|x64 + {C52F9E7B-498A-42BE-8DB4-85A15694366A}.Release|x64.Build.0 = Release|x64 + {C52F9E7B-498A-42BE-8DB4-85A15694366A}.ReleaseWithoutAsm|Itanium.ActiveCfg = Release|Itanium + {C52F9E7B-498A-42BE-8DB4-85A15694366A}.ReleaseWithoutAsm|Itanium.Build.0 = Release|Itanium + {C52F9E7B-498A-42BE-8DB4-85A15694366A}.ReleaseWithoutAsm|Win32.ActiveCfg = Release|Win32 + {C52F9E7B-498A-42BE-8DB4-85A15694366A}.ReleaseWithoutAsm|x64.ActiveCfg = Release|x64 + {48CDD9DC-E09F-4135-9C0C-4FE50C3C654B}.Debug|Itanium.ActiveCfg = Debug|Itanium + {48CDD9DC-E09F-4135-9C0C-4FE50C3C654B}.Debug|Itanium.Build.0 = Debug|Itanium + {48CDD9DC-E09F-4135-9C0C-4FE50C3C654B}.Debug|Win32.ActiveCfg = Debug|Win32 + {48CDD9DC-E09F-4135-9C0C-4FE50C3C654B}.Debug|Win32.Build.0 = Debug|Win32 + {48CDD9DC-E09F-4135-9C0C-4FE50C3C654B}.Debug|x64.ActiveCfg = Debug|x64 + {48CDD9DC-E09F-4135-9C0C-4FE50C3C654B}.Debug|x64.Build.0 = Debug|x64 + {48CDD9DC-E09F-4135-9C0C-4FE50C3C654B}.Release|Itanium.ActiveCfg = Release|Itanium + {48CDD9DC-E09F-4135-9C0C-4FE50C3C654B}.Release|Itanium.Build.0 = Release|Itanium + {48CDD9DC-E09F-4135-9C0C-4FE50C3C654B}.Release|Win32.ActiveCfg = Release|Win32 + {48CDD9DC-E09F-4135-9C0C-4FE50C3C654B}.Release|Win32.Build.0 = Release|Win32 + {48CDD9DC-E09F-4135-9C0C-4FE50C3C654B}.Release|x64.ActiveCfg = Release|x64 + {48CDD9DC-E09F-4135-9C0C-4FE50C3C654B}.Release|x64.Build.0 = Release|x64 + {48CDD9DC-E09F-4135-9C0C-4FE50C3C654B}.ReleaseWithoutAsm|Itanium.ActiveCfg = Release|Itanium + {48CDD9DC-E09F-4135-9C0C-4FE50C3C654B}.ReleaseWithoutAsm|Itanium.Build.0 = Release|Itanium + {48CDD9DC-E09F-4135-9C0C-4FE50C3C654B}.ReleaseWithoutAsm|Win32.ActiveCfg = Release|Win32 + {48CDD9DC-E09F-4135-9C0C-4FE50C3C654B}.ReleaseWithoutAsm|x64.ActiveCfg = Release|x64 + {C52F9E7B-498A-42BE-8DB4-85A15694382A}.Debug|Itanium.ActiveCfg = Debug|Itanium + {C52F9E7B-498A-42BE-8DB4-85A15694382A}.Debug|Itanium.Build.0 = Debug|Itanium + {C52F9E7B-498A-42BE-8DB4-85A15694382A}.Debug|Win32.ActiveCfg = Debug|Win32 + {C52F9E7B-498A-42BE-8DB4-85A15694382A}.Debug|Win32.Build.0 = Debug|Win32 + {C52F9E7B-498A-42BE-8DB4-85A15694382A}.Debug|x64.ActiveCfg = Debug|x64 + {C52F9E7B-498A-42BE-8DB4-85A15694382A}.Debug|x64.Build.0 = Debug|x64 + {C52F9E7B-498A-42BE-8DB4-85A15694382A}.Release|Itanium.ActiveCfg = Release|Itanium + {C52F9E7B-498A-42BE-8DB4-85A15694382A}.Release|Itanium.Build.0 = Release|Itanium + {C52F9E7B-498A-42BE-8DB4-85A15694382A}.Release|Win32.ActiveCfg = Release|Win32 + {C52F9E7B-498A-42BE-8DB4-85A15694382A}.Release|Win32.Build.0 = Release|Win32 + {C52F9E7B-498A-42BE-8DB4-85A15694382A}.Release|x64.ActiveCfg = Release|x64 + {C52F9E7B-498A-42BE-8DB4-85A15694382A}.Release|x64.Build.0 = Release|x64 + {C52F9E7B-498A-42BE-8DB4-85A15694382A}.ReleaseWithoutAsm|Itanium.ActiveCfg = Release|Itanium + {C52F9E7B-498A-42BE-8DB4-85A15694382A}.ReleaseWithoutAsm|Itanium.Build.0 = Release|Itanium + {C52F9E7B-498A-42BE-8DB4-85A15694382A}.ReleaseWithoutAsm|Win32.ActiveCfg = Release|Win32 + {C52F9E7B-498A-42BE-8DB4-85A15694382A}.ReleaseWithoutAsm|x64.ActiveCfg = Release|x64 + EndGlobalSection + GlobalSection(SolutionProperties) = preSolution + HideSolutionNode = FALSE + EndGlobalSection +EndGlobal ADDED compat/zlib/contrib/vstudio/vc9/zlibvc.vcproj Index: compat/zlib/contrib/vstudio/vc9/zlibvc.vcproj ================================================================== --- /dev/null +++ compat/zlib/contrib/vstudio/vc9/zlibvc.vcproj @@ -0,0 +1,1156 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + ADDED compat/zlib/crc32.c Index: compat/zlib/crc32.c ================================================================== --- /dev/null +++ compat/zlib/crc32.c @@ -0,0 +1,442 @@ +/* crc32.c -- compute the CRC-32 of a data stream + * Copyright (C) 1995-2006, 2010, 2011, 2012, 2016 Mark Adler + * For conditions of distribution and use, see copyright notice in zlib.h + * + * Thanks to Rodney Brown for his contribution of faster + * CRC methods: exclusive-oring 32 bits of data at a time, and pre-computing + * tables for updating the shift register in one step with three exclusive-ors + * instead of four steps with four exclusive-ors. This results in about a + * factor of two increase in speed on a Power PC G4 (PPC7455) using gcc -O3. + */ + +/* @(#) $Id$ */ + +/* + Note on the use of DYNAMIC_CRC_TABLE: there is no mutex or semaphore + protection on the static variables used to control the first-use generation + of the crc tables. Therefore, if you #define DYNAMIC_CRC_TABLE, you should + first call get_crc_table() to initialize the tables before allowing more than + one thread to use crc32(). + + DYNAMIC_CRC_TABLE and MAKECRCH can be #defined to write out crc32.h. + */ + +#ifdef MAKECRCH +# include +# ifndef DYNAMIC_CRC_TABLE +# define DYNAMIC_CRC_TABLE +# endif /* !DYNAMIC_CRC_TABLE */ +#endif /* MAKECRCH */ + +#include "zutil.h" /* for STDC and FAR definitions */ + +/* Definitions for doing the crc four data bytes at a time. */ +#if !defined(NOBYFOUR) && defined(Z_U4) +# define BYFOUR +#endif +#ifdef BYFOUR + local unsigned long crc32_little OF((unsigned long, + const unsigned char FAR *, z_size_t)); + local unsigned long crc32_big OF((unsigned long, + const unsigned char FAR *, z_size_t)); +# define TBLS 8 +#else +# define TBLS 1 +#endif /* BYFOUR */ + +/* Local functions for crc concatenation */ +local unsigned long gf2_matrix_times OF((unsigned long *mat, + unsigned long vec)); +local void gf2_matrix_square OF((unsigned long *square, unsigned long *mat)); +local uLong crc32_combine_ OF((uLong crc1, uLong crc2, z_off64_t len2)); + + +#ifdef DYNAMIC_CRC_TABLE + +local volatile int crc_table_empty = 1; +local z_crc_t FAR crc_table[TBLS][256]; +local void make_crc_table OF((void)); +#ifdef MAKECRCH + local void write_table OF((FILE *, const z_crc_t FAR *)); +#endif /* MAKECRCH */ +/* + Generate tables for a byte-wise 32-bit CRC calculation on the polynomial: + x^32+x^26+x^23+x^22+x^16+x^12+x^11+x^10+x^8+x^7+x^5+x^4+x^2+x+1. + + Polynomials over GF(2) are represented in binary, one bit per coefficient, + with the lowest powers in the most significant bit. Then adding polynomials + is just exclusive-or, and multiplying a polynomial by x is a right shift by + one. If we call the above polynomial p, and represent a byte as the + polynomial q, also with the lowest power in the most significant bit (so the + byte 0xb1 is the polynomial x^7+x^3+x+1), then the CRC is (q*x^32) mod p, + where a mod b means the remainder after dividing a by b. + + This calculation is done using the shift-register method of multiplying and + taking the remainder. The register is initialized to zero, and for each + incoming bit, x^32 is added mod p to the register if the bit is a one (where + x^32 mod p is p+x^32 = x^26+...+1), and the register is multiplied mod p by + x (which is shifting right by one and adding x^32 mod p if the bit shifted + out is a one). We start with the highest power (least significant bit) of + q and repeat for all eight bits of q. + + The first table is simply the CRC of all possible eight bit values. This is + all the information needed to generate CRCs on data a byte at a time for all + combinations of CRC register values and incoming bytes. The remaining tables + allow for word-at-a-time CRC calculation for both big-endian and little- + endian machines, where a word is four bytes. +*/ +local void make_crc_table() +{ + z_crc_t c; + int n, k; + z_crc_t poly; /* polynomial exclusive-or pattern */ + /* terms of polynomial defining this crc (except x^32): */ + static volatile int first = 1; /* flag to limit concurrent making */ + static const unsigned char p[] = {0,1,2,4,5,7,8,10,11,12,16,22,23,26}; + + /* See if another task is already doing this (not thread-safe, but better + than nothing -- significantly reduces duration of vulnerability in + case the advice about DYNAMIC_CRC_TABLE is ignored) */ + if (first) { + first = 0; + + /* make exclusive-or pattern from polynomial (0xedb88320UL) */ + poly = 0; + for (n = 0; n < (int)(sizeof(p)/sizeof(unsigned char)); n++) + poly |= (z_crc_t)1 << (31 - p[n]); + + /* generate a crc for every 8-bit value */ + for (n = 0; n < 256; n++) { + c = (z_crc_t)n; + for (k = 0; k < 8; k++) + c = c & 1 ? poly ^ (c >> 1) : c >> 1; + crc_table[0][n] = c; + } + +#ifdef BYFOUR + /* generate crc for each value followed by one, two, and three zeros, + and then the byte reversal of those as well as the first table */ + for (n = 0; n < 256; n++) { + c = crc_table[0][n]; + crc_table[4][n] = ZSWAP32(c); + for (k = 1; k < 4; k++) { + c = crc_table[0][c & 0xff] ^ (c >> 8); + crc_table[k][n] = c; + crc_table[k + 4][n] = ZSWAP32(c); + } + } +#endif /* BYFOUR */ + + crc_table_empty = 0; + } + else { /* not first */ + /* wait for the other guy to finish (not efficient, but rare) */ + while (crc_table_empty) + ; + } + +#ifdef MAKECRCH + /* write out CRC tables to crc32.h */ + { + FILE *out; + + out = fopen("crc32.h", "w"); + if (out == NULL) return; + fprintf(out, "/* crc32.h -- tables for rapid CRC calculation\n"); + fprintf(out, " * Generated automatically by crc32.c\n */\n\n"); + fprintf(out, "local const z_crc_t FAR "); + fprintf(out, "crc_table[TBLS][256] =\n{\n {\n"); + write_table(out, crc_table[0]); +# ifdef BYFOUR + fprintf(out, "#ifdef BYFOUR\n"); + for (k = 1; k < 8; k++) { + fprintf(out, " },\n {\n"); + write_table(out, crc_table[k]); + } + fprintf(out, "#endif\n"); +# endif /* BYFOUR */ + fprintf(out, " }\n};\n"); + fclose(out); + } +#endif /* MAKECRCH */ +} + +#ifdef MAKECRCH +local void write_table(out, table) + FILE *out; + const z_crc_t FAR *table; +{ + int n; + + for (n = 0; n < 256; n++) + fprintf(out, "%s0x%08lxUL%s", n % 5 ? "" : " ", + (unsigned long)(table[n]), + n == 255 ? "\n" : (n % 5 == 4 ? ",\n" : ", ")); +} +#endif /* MAKECRCH */ + +#else /* !DYNAMIC_CRC_TABLE */ +/* ======================================================================== + * Tables of CRC-32s of all single-byte values, made by make_crc_table(). + */ +#include "crc32.h" +#endif /* DYNAMIC_CRC_TABLE */ + +/* ========================================================================= + * This function can be used by asm versions of crc32() + */ +const z_crc_t FAR * ZEXPORT get_crc_table() +{ +#ifdef DYNAMIC_CRC_TABLE + if (crc_table_empty) + make_crc_table(); +#endif /* DYNAMIC_CRC_TABLE */ + return (const z_crc_t FAR *)crc_table; +} + +/* ========================================================================= */ +#define DO1 crc = crc_table[0][((int)crc ^ (*buf++)) & 0xff] ^ (crc >> 8) +#define DO8 DO1; DO1; DO1; DO1; DO1; DO1; DO1; DO1 + +/* ========================================================================= */ +unsigned long ZEXPORT crc32_z(crc, buf, len) + unsigned long crc; + const unsigned char FAR *buf; + z_size_t len; +{ + if (buf == Z_NULL) return 0UL; + +#ifdef DYNAMIC_CRC_TABLE + if (crc_table_empty) + make_crc_table(); +#endif /* DYNAMIC_CRC_TABLE */ + +#ifdef BYFOUR + if (sizeof(void *) == sizeof(ptrdiff_t)) { + z_crc_t endian; + + endian = 1; + if (*((unsigned char *)(&endian))) + return crc32_little(crc, buf, len); + else + return crc32_big(crc, buf, len); + } +#endif /* BYFOUR */ + crc = crc ^ 0xffffffffUL; + while (len >= 8) { + DO8; + len -= 8; + } + if (len) do { + DO1; + } while (--len); + return crc ^ 0xffffffffUL; +} + +/* ========================================================================= */ +unsigned long ZEXPORT crc32(crc, buf, len) + unsigned long crc; + const unsigned char FAR *buf; + uInt len; +{ + return crc32_z(crc, buf, len); +} + +#ifdef BYFOUR + +/* + This BYFOUR code accesses the passed unsigned char * buffer with a 32-bit + integer pointer type. This violates the strict aliasing rule, where a + compiler can assume, for optimization purposes, that two pointers to + fundamentally different types won't ever point to the same memory. This can + manifest as a problem only if one of the pointers is written to. This code + only reads from those pointers. So long as this code remains isolated in + this compilation unit, there won't be a problem. For this reason, this code + should not be copied and pasted into a compilation unit in which other code + writes to the buffer that is passed to these routines. + */ + +/* ========================================================================= */ +#define DOLIT4 c ^= *buf4++; \ + c = crc_table[3][c & 0xff] ^ crc_table[2][(c >> 8) & 0xff] ^ \ + crc_table[1][(c >> 16) & 0xff] ^ crc_table[0][c >> 24] +#define DOLIT32 DOLIT4; DOLIT4; DOLIT4; DOLIT4; DOLIT4; DOLIT4; DOLIT4; DOLIT4 + +/* ========================================================================= */ +local unsigned long crc32_little(crc, buf, len) + unsigned long crc; + const unsigned char FAR *buf; + z_size_t len; +{ + register z_crc_t c; + register const z_crc_t FAR *buf4; + + c = (z_crc_t)crc; + c = ~c; + while (len && ((ptrdiff_t)buf & 3)) { + c = crc_table[0][(c ^ *buf++) & 0xff] ^ (c >> 8); + len--; + } + + buf4 = (const z_crc_t FAR *)(const void FAR *)buf; + while (len >= 32) { + DOLIT32; + len -= 32; + } + while (len >= 4) { + DOLIT4; + len -= 4; + } + buf = (const unsigned char FAR *)buf4; + + if (len) do { + c = crc_table[0][(c ^ *buf++) & 0xff] ^ (c >> 8); + } while (--len); + c = ~c; + return (unsigned long)c; +} + +/* ========================================================================= */ +#define DOBIG4 c ^= *buf4++; \ + c = crc_table[4][c & 0xff] ^ crc_table[5][(c >> 8) & 0xff] ^ \ + crc_table[6][(c >> 16) & 0xff] ^ crc_table[7][c >> 24] +#define DOBIG32 DOBIG4; DOBIG4; DOBIG4; DOBIG4; DOBIG4; DOBIG4; DOBIG4; DOBIG4 + +/* ========================================================================= */ +local unsigned long crc32_big(crc, buf, len) + unsigned long crc; + const unsigned char FAR *buf; + z_size_t len; +{ + register z_crc_t c; + register const z_crc_t FAR *buf4; + + c = ZSWAP32((z_crc_t)crc); + c = ~c; + while (len && ((ptrdiff_t)buf & 3)) { + c = crc_table[4][(c >> 24) ^ *buf++] ^ (c << 8); + len--; + } + + buf4 = (const z_crc_t FAR *)(const void FAR *)buf; + while (len >= 32) { + DOBIG32; + len -= 32; + } + while (len >= 4) { + DOBIG4; + len -= 4; + } + buf = (const unsigned char FAR *)buf4; + + if (len) do { + c = crc_table[4][(c >> 24) ^ *buf++] ^ (c << 8); + } while (--len); + c = ~c; + return (unsigned long)(ZSWAP32(c)); +} + +#endif /* BYFOUR */ + +#define GF2_DIM 32 /* dimension of GF(2) vectors (length of CRC) */ + +/* ========================================================================= */ +local unsigned long gf2_matrix_times(mat, vec) + unsigned long *mat; + unsigned long vec; +{ + unsigned long sum; + + sum = 0; + while (vec) { + if (vec & 1) + sum ^= *mat; + vec >>= 1; + mat++; + } + return sum; +} + +/* ========================================================================= */ +local void gf2_matrix_square(square, mat) + unsigned long *square; + unsigned long *mat; +{ + int n; + + for (n = 0; n < GF2_DIM; n++) + square[n] = gf2_matrix_times(mat, mat[n]); +} + +/* ========================================================================= */ +local uLong crc32_combine_(crc1, crc2, len2) + uLong crc1; + uLong crc2; + z_off64_t len2; +{ + int n; + unsigned long row; + unsigned long even[GF2_DIM]; /* even-power-of-two zeros operator */ + unsigned long odd[GF2_DIM]; /* odd-power-of-two zeros operator */ + + /* degenerate case (also disallow negative lengths) */ + if (len2 <= 0) + return crc1; + + /* put operator for one zero bit in odd */ + odd[0] = 0xedb88320UL; /* CRC-32 polynomial */ + row = 1; + for (n = 1; n < GF2_DIM; n++) { + odd[n] = row; + row <<= 1; + } + + /* put operator for two zero bits in even */ + gf2_matrix_square(even, odd); + + /* put operator for four zero bits in odd */ + gf2_matrix_square(odd, even); + + /* apply len2 zeros to crc1 (first square will put the operator for one + zero byte, eight zero bits, in even) */ + do { + /* apply zeros operator for this bit of len2 */ + gf2_matrix_square(even, odd); + if (len2 & 1) + crc1 = gf2_matrix_times(even, crc1); + len2 >>= 1; + + /* if no more bits set, then done */ + if (len2 == 0) + break; + + /* another iteration of the loop with odd and even swapped */ + gf2_matrix_square(odd, even); + if (len2 & 1) + crc1 = gf2_matrix_times(odd, crc1); + len2 >>= 1; + + /* if no more bits set, then done */ + } while (len2 != 0); + + /* return combined crc */ + crc1 ^= crc2; + return crc1; +} + +/* ========================================================================= */ +uLong ZEXPORT crc32_combine(crc1, crc2, len2) + uLong crc1; + uLong crc2; + z_off_t len2; +{ + return crc32_combine_(crc1, crc2, len2); +} + +uLong ZEXPORT crc32_combine64(crc1, crc2, len2) + uLong crc1; + uLong crc2; + z_off64_t len2; +{ + return crc32_combine_(crc1, crc2, len2); +} ADDED compat/zlib/crc32.h Index: compat/zlib/crc32.h ================================================================== --- /dev/null +++ compat/zlib/crc32.h @@ -0,0 +1,441 @@ +/* crc32.h -- tables for rapid CRC calculation + * Generated automatically by crc32.c + */ + +local const z_crc_t FAR crc_table[TBLS][256] = +{ + { + 0x00000000UL, 0x77073096UL, 0xee0e612cUL, 0x990951baUL, 0x076dc419UL, + 0x706af48fUL, 0xe963a535UL, 0x9e6495a3UL, 0x0edb8832UL, 0x79dcb8a4UL, + 0xe0d5e91eUL, 0x97d2d988UL, 0x09b64c2bUL, 0x7eb17cbdUL, 0xe7b82d07UL, + 0x90bf1d91UL, 0x1db71064UL, 0x6ab020f2UL, 0xf3b97148UL, 0x84be41deUL, + 0x1adad47dUL, 0x6ddde4ebUL, 0xf4d4b551UL, 0x83d385c7UL, 0x136c9856UL, + 0x646ba8c0UL, 0xfd62f97aUL, 0x8a65c9ecUL, 0x14015c4fUL, 0x63066cd9UL, + 0xfa0f3d63UL, 0x8d080df5UL, 0x3b6e20c8UL, 0x4c69105eUL, 0xd56041e4UL, + 0xa2677172UL, 0x3c03e4d1UL, 0x4b04d447UL, 0xd20d85fdUL, 0xa50ab56bUL, + 0x35b5a8faUL, 0x42b2986cUL, 0xdbbbc9d6UL, 0xacbcf940UL, 0x32d86ce3UL, + 0x45df5c75UL, 0xdcd60dcfUL, 0xabd13d59UL, 0x26d930acUL, 0x51de003aUL, + 0xc8d75180UL, 0xbfd06116UL, 0x21b4f4b5UL, 0x56b3c423UL, 0xcfba9599UL, + 0xb8bda50fUL, 0x2802b89eUL, 0x5f058808UL, 0xc60cd9b2UL, 0xb10be924UL, + 0x2f6f7c87UL, 0x58684c11UL, 0xc1611dabUL, 0xb6662d3dUL, 0x76dc4190UL, + 0x01db7106UL, 0x98d220bcUL, 0xefd5102aUL, 0x71b18589UL, 0x06b6b51fUL, + 0x9fbfe4a5UL, 0xe8b8d433UL, 0x7807c9a2UL, 0x0f00f934UL, 0x9609a88eUL, + 0xe10e9818UL, 0x7f6a0dbbUL, 0x086d3d2dUL, 0x91646c97UL, 0xe6635c01UL, + 0x6b6b51f4UL, 0x1c6c6162UL, 0x856530d8UL, 0xf262004eUL, 0x6c0695edUL, + 0x1b01a57bUL, 0x8208f4c1UL, 0xf50fc457UL, 0x65b0d9c6UL, 0x12b7e950UL, + 0x8bbeb8eaUL, 0xfcb9887cUL, 0x62dd1ddfUL, 0x15da2d49UL, 0x8cd37cf3UL, + 0xfbd44c65UL, 0x4db26158UL, 0x3ab551ceUL, 0xa3bc0074UL, 0xd4bb30e2UL, + 0x4adfa541UL, 0x3dd895d7UL, 0xa4d1c46dUL, 0xd3d6f4fbUL, 0x4369e96aUL, + 0x346ed9fcUL, 0xad678846UL, 0xda60b8d0UL, 0x44042d73UL, 0x33031de5UL, + 0xaa0a4c5fUL, 0xdd0d7cc9UL, 0x5005713cUL, 0x270241aaUL, 0xbe0b1010UL, + 0xc90c2086UL, 0x5768b525UL, 0x206f85b3UL, 0xb966d409UL, 0xce61e49fUL, + 0x5edef90eUL, 0x29d9c998UL, 0xb0d09822UL, 0xc7d7a8b4UL, 0x59b33d17UL, + 0x2eb40d81UL, 0xb7bd5c3bUL, 0xc0ba6cadUL, 0xedb88320UL, 0x9abfb3b6UL, + 0x03b6e20cUL, 0x74b1d29aUL, 0xead54739UL, 0x9dd277afUL, 0x04db2615UL, + 0x73dc1683UL, 0xe3630b12UL, 0x94643b84UL, 0x0d6d6a3eUL, 0x7a6a5aa8UL, + 0xe40ecf0bUL, 0x9309ff9dUL, 0x0a00ae27UL, 0x7d079eb1UL, 0xf00f9344UL, + 0x8708a3d2UL, 0x1e01f268UL, 0x6906c2feUL, 0xf762575dUL, 0x806567cbUL, + 0x196c3671UL, 0x6e6b06e7UL, 0xfed41b76UL, 0x89d32be0UL, 0x10da7a5aUL, + 0x67dd4accUL, 0xf9b9df6fUL, 0x8ebeeff9UL, 0x17b7be43UL, 0x60b08ed5UL, + 0xd6d6a3e8UL, 0xa1d1937eUL, 0x38d8c2c4UL, 0x4fdff252UL, 0xd1bb67f1UL, + 0xa6bc5767UL, 0x3fb506ddUL, 0x48b2364bUL, 0xd80d2bdaUL, 0xaf0a1b4cUL, + 0x36034af6UL, 0x41047a60UL, 0xdf60efc3UL, 0xa867df55UL, 0x316e8eefUL, + 0x4669be79UL, 0xcb61b38cUL, 0xbc66831aUL, 0x256fd2a0UL, 0x5268e236UL, + 0xcc0c7795UL, 0xbb0b4703UL, 0x220216b9UL, 0x5505262fUL, 0xc5ba3bbeUL, + 0xb2bd0b28UL, 0x2bb45a92UL, 0x5cb36a04UL, 0xc2d7ffa7UL, 0xb5d0cf31UL, + 0x2cd99e8bUL, 0x5bdeae1dUL, 0x9b64c2b0UL, 0xec63f226UL, 0x756aa39cUL, + 0x026d930aUL, 0x9c0906a9UL, 0xeb0e363fUL, 0x72076785UL, 0x05005713UL, + 0x95bf4a82UL, 0xe2b87a14UL, 0x7bb12baeUL, 0x0cb61b38UL, 0x92d28e9bUL, + 0xe5d5be0dUL, 0x7cdcefb7UL, 0x0bdbdf21UL, 0x86d3d2d4UL, 0xf1d4e242UL, + 0x68ddb3f8UL, 0x1fda836eUL, 0x81be16cdUL, 0xf6b9265bUL, 0x6fb077e1UL, + 0x18b74777UL, 0x88085ae6UL, 0xff0f6a70UL, 0x66063bcaUL, 0x11010b5cUL, + 0x8f659effUL, 0xf862ae69UL, 0x616bffd3UL, 0x166ccf45UL, 0xa00ae278UL, + 0xd70dd2eeUL, 0x4e048354UL, 0x3903b3c2UL, 0xa7672661UL, 0xd06016f7UL, + 0x4969474dUL, 0x3e6e77dbUL, 0xaed16a4aUL, 0xd9d65adcUL, 0x40df0b66UL, + 0x37d83bf0UL, 0xa9bcae53UL, 0xdebb9ec5UL, 0x47b2cf7fUL, 0x30b5ffe9UL, + 0xbdbdf21cUL, 0xcabac28aUL, 0x53b39330UL, 0x24b4a3a6UL, 0xbad03605UL, + 0xcdd70693UL, 0x54de5729UL, 0x23d967bfUL, 0xb3667a2eUL, 0xc4614ab8UL, + 0x5d681b02UL, 0x2a6f2b94UL, 0xb40bbe37UL, 0xc30c8ea1UL, 0x5a05df1bUL, + 0x2d02ef8dUL +#ifdef BYFOUR + }, + { + 0x00000000UL, 0x191b3141UL, 0x32366282UL, 0x2b2d53c3UL, 0x646cc504UL, + 0x7d77f445UL, 0x565aa786UL, 0x4f4196c7UL, 0xc8d98a08UL, 0xd1c2bb49UL, + 0xfaefe88aUL, 0xe3f4d9cbUL, 0xacb54f0cUL, 0xb5ae7e4dUL, 0x9e832d8eUL, + 0x87981ccfUL, 0x4ac21251UL, 0x53d92310UL, 0x78f470d3UL, 0x61ef4192UL, + 0x2eaed755UL, 0x37b5e614UL, 0x1c98b5d7UL, 0x05838496UL, 0x821b9859UL, + 0x9b00a918UL, 0xb02dfadbUL, 0xa936cb9aUL, 0xe6775d5dUL, 0xff6c6c1cUL, + 0xd4413fdfUL, 0xcd5a0e9eUL, 0x958424a2UL, 0x8c9f15e3UL, 0xa7b24620UL, + 0xbea97761UL, 0xf1e8e1a6UL, 0xe8f3d0e7UL, 0xc3de8324UL, 0xdac5b265UL, + 0x5d5daeaaUL, 0x44469febUL, 0x6f6bcc28UL, 0x7670fd69UL, 0x39316baeUL, + 0x202a5aefUL, 0x0b07092cUL, 0x121c386dUL, 0xdf4636f3UL, 0xc65d07b2UL, + 0xed705471UL, 0xf46b6530UL, 0xbb2af3f7UL, 0xa231c2b6UL, 0x891c9175UL, + 0x9007a034UL, 0x179fbcfbUL, 0x0e848dbaUL, 0x25a9de79UL, 0x3cb2ef38UL, + 0x73f379ffUL, 0x6ae848beUL, 0x41c51b7dUL, 0x58de2a3cUL, 0xf0794f05UL, + 0xe9627e44UL, 0xc24f2d87UL, 0xdb541cc6UL, 0x94158a01UL, 0x8d0ebb40UL, + 0xa623e883UL, 0xbf38d9c2UL, 0x38a0c50dUL, 0x21bbf44cUL, 0x0a96a78fUL, + 0x138d96ceUL, 0x5ccc0009UL, 0x45d73148UL, 0x6efa628bUL, 0x77e153caUL, + 0xbabb5d54UL, 0xa3a06c15UL, 0x888d3fd6UL, 0x91960e97UL, 0xded79850UL, + 0xc7cca911UL, 0xece1fad2UL, 0xf5facb93UL, 0x7262d75cUL, 0x6b79e61dUL, + 0x4054b5deUL, 0x594f849fUL, 0x160e1258UL, 0x0f152319UL, 0x243870daUL, + 0x3d23419bUL, 0x65fd6ba7UL, 0x7ce65ae6UL, 0x57cb0925UL, 0x4ed03864UL, + 0x0191aea3UL, 0x188a9fe2UL, 0x33a7cc21UL, 0x2abcfd60UL, 0xad24e1afUL, + 0xb43fd0eeUL, 0x9f12832dUL, 0x8609b26cUL, 0xc94824abUL, 0xd05315eaUL, + 0xfb7e4629UL, 0xe2657768UL, 0x2f3f79f6UL, 0x362448b7UL, 0x1d091b74UL, + 0x04122a35UL, 0x4b53bcf2UL, 0x52488db3UL, 0x7965de70UL, 0x607eef31UL, + 0xe7e6f3feUL, 0xfefdc2bfUL, 0xd5d0917cUL, 0xcccba03dUL, 0x838a36faUL, + 0x9a9107bbUL, 0xb1bc5478UL, 0xa8a76539UL, 0x3b83984bUL, 0x2298a90aUL, + 0x09b5fac9UL, 0x10aecb88UL, 0x5fef5d4fUL, 0x46f46c0eUL, 0x6dd93fcdUL, + 0x74c20e8cUL, 0xf35a1243UL, 0xea412302UL, 0xc16c70c1UL, 0xd8774180UL, + 0x9736d747UL, 0x8e2de606UL, 0xa500b5c5UL, 0xbc1b8484UL, 0x71418a1aUL, + 0x685abb5bUL, 0x4377e898UL, 0x5a6cd9d9UL, 0x152d4f1eUL, 0x0c367e5fUL, + 0x271b2d9cUL, 0x3e001cddUL, 0xb9980012UL, 0xa0833153UL, 0x8bae6290UL, + 0x92b553d1UL, 0xddf4c516UL, 0xc4eff457UL, 0xefc2a794UL, 0xf6d996d5UL, + 0xae07bce9UL, 0xb71c8da8UL, 0x9c31de6bUL, 0x852aef2aUL, 0xca6b79edUL, + 0xd37048acUL, 0xf85d1b6fUL, 0xe1462a2eUL, 0x66de36e1UL, 0x7fc507a0UL, + 0x54e85463UL, 0x4df36522UL, 0x02b2f3e5UL, 0x1ba9c2a4UL, 0x30849167UL, + 0x299fa026UL, 0xe4c5aeb8UL, 0xfdde9ff9UL, 0xd6f3cc3aUL, 0xcfe8fd7bUL, + 0x80a96bbcUL, 0x99b25afdUL, 0xb29f093eUL, 0xab84387fUL, 0x2c1c24b0UL, + 0x350715f1UL, 0x1e2a4632UL, 0x07317773UL, 0x4870e1b4UL, 0x516bd0f5UL, + 0x7a468336UL, 0x635db277UL, 0xcbfad74eUL, 0xd2e1e60fUL, 0xf9ccb5ccUL, + 0xe0d7848dUL, 0xaf96124aUL, 0xb68d230bUL, 0x9da070c8UL, 0x84bb4189UL, + 0x03235d46UL, 0x1a386c07UL, 0x31153fc4UL, 0x280e0e85UL, 0x674f9842UL, + 0x7e54a903UL, 0x5579fac0UL, 0x4c62cb81UL, 0x8138c51fUL, 0x9823f45eUL, + 0xb30ea79dUL, 0xaa1596dcUL, 0xe554001bUL, 0xfc4f315aUL, 0xd7626299UL, + 0xce7953d8UL, 0x49e14f17UL, 0x50fa7e56UL, 0x7bd72d95UL, 0x62cc1cd4UL, + 0x2d8d8a13UL, 0x3496bb52UL, 0x1fbbe891UL, 0x06a0d9d0UL, 0x5e7ef3ecUL, + 0x4765c2adUL, 0x6c48916eUL, 0x7553a02fUL, 0x3a1236e8UL, 0x230907a9UL, + 0x0824546aUL, 0x113f652bUL, 0x96a779e4UL, 0x8fbc48a5UL, 0xa4911b66UL, + 0xbd8a2a27UL, 0xf2cbbce0UL, 0xebd08da1UL, 0xc0fdde62UL, 0xd9e6ef23UL, + 0x14bce1bdUL, 0x0da7d0fcUL, 0x268a833fUL, 0x3f91b27eUL, 0x70d024b9UL, + 0x69cb15f8UL, 0x42e6463bUL, 0x5bfd777aUL, 0xdc656bb5UL, 0xc57e5af4UL, + 0xee530937UL, 0xf7483876UL, 0xb809aeb1UL, 0xa1129ff0UL, 0x8a3fcc33UL, + 0x9324fd72UL + }, + { + 0x00000000UL, 0x01c26a37UL, 0x0384d46eUL, 0x0246be59UL, 0x0709a8dcUL, + 0x06cbc2ebUL, 0x048d7cb2UL, 0x054f1685UL, 0x0e1351b8UL, 0x0fd13b8fUL, + 0x0d9785d6UL, 0x0c55efe1UL, 0x091af964UL, 0x08d89353UL, 0x0a9e2d0aUL, + 0x0b5c473dUL, 0x1c26a370UL, 0x1de4c947UL, 0x1fa2771eUL, 0x1e601d29UL, + 0x1b2f0bacUL, 0x1aed619bUL, 0x18abdfc2UL, 0x1969b5f5UL, 0x1235f2c8UL, + 0x13f798ffUL, 0x11b126a6UL, 0x10734c91UL, 0x153c5a14UL, 0x14fe3023UL, + 0x16b88e7aUL, 0x177ae44dUL, 0x384d46e0UL, 0x398f2cd7UL, 0x3bc9928eUL, + 0x3a0bf8b9UL, 0x3f44ee3cUL, 0x3e86840bUL, 0x3cc03a52UL, 0x3d025065UL, + 0x365e1758UL, 0x379c7d6fUL, 0x35dac336UL, 0x3418a901UL, 0x3157bf84UL, + 0x3095d5b3UL, 0x32d36beaUL, 0x331101ddUL, 0x246be590UL, 0x25a98fa7UL, + 0x27ef31feUL, 0x262d5bc9UL, 0x23624d4cUL, 0x22a0277bUL, 0x20e69922UL, + 0x2124f315UL, 0x2a78b428UL, 0x2bbade1fUL, 0x29fc6046UL, 0x283e0a71UL, + 0x2d711cf4UL, 0x2cb376c3UL, 0x2ef5c89aUL, 0x2f37a2adUL, 0x709a8dc0UL, + 0x7158e7f7UL, 0x731e59aeUL, 0x72dc3399UL, 0x7793251cUL, 0x76514f2bUL, + 0x7417f172UL, 0x75d59b45UL, 0x7e89dc78UL, 0x7f4bb64fUL, 0x7d0d0816UL, + 0x7ccf6221UL, 0x798074a4UL, 0x78421e93UL, 0x7a04a0caUL, 0x7bc6cafdUL, + 0x6cbc2eb0UL, 0x6d7e4487UL, 0x6f38fadeUL, 0x6efa90e9UL, 0x6bb5866cUL, + 0x6a77ec5bUL, 0x68315202UL, 0x69f33835UL, 0x62af7f08UL, 0x636d153fUL, + 0x612bab66UL, 0x60e9c151UL, 0x65a6d7d4UL, 0x6464bde3UL, 0x662203baUL, + 0x67e0698dUL, 0x48d7cb20UL, 0x4915a117UL, 0x4b531f4eUL, 0x4a917579UL, + 0x4fde63fcUL, 0x4e1c09cbUL, 0x4c5ab792UL, 0x4d98dda5UL, 0x46c49a98UL, + 0x4706f0afUL, 0x45404ef6UL, 0x448224c1UL, 0x41cd3244UL, 0x400f5873UL, + 0x4249e62aUL, 0x438b8c1dUL, 0x54f16850UL, 0x55330267UL, 0x5775bc3eUL, + 0x56b7d609UL, 0x53f8c08cUL, 0x523aaabbUL, 0x507c14e2UL, 0x51be7ed5UL, + 0x5ae239e8UL, 0x5b2053dfUL, 0x5966ed86UL, 0x58a487b1UL, 0x5deb9134UL, + 0x5c29fb03UL, 0x5e6f455aUL, 0x5fad2f6dUL, 0xe1351b80UL, 0xe0f771b7UL, + 0xe2b1cfeeUL, 0xe373a5d9UL, 0xe63cb35cUL, 0xe7fed96bUL, 0xe5b86732UL, + 0xe47a0d05UL, 0xef264a38UL, 0xeee4200fUL, 0xeca29e56UL, 0xed60f461UL, + 0xe82fe2e4UL, 0xe9ed88d3UL, 0xebab368aUL, 0xea695cbdUL, 0xfd13b8f0UL, + 0xfcd1d2c7UL, 0xfe976c9eUL, 0xff5506a9UL, 0xfa1a102cUL, 0xfbd87a1bUL, + 0xf99ec442UL, 0xf85cae75UL, 0xf300e948UL, 0xf2c2837fUL, 0xf0843d26UL, + 0xf1465711UL, 0xf4094194UL, 0xf5cb2ba3UL, 0xf78d95faUL, 0xf64fffcdUL, + 0xd9785d60UL, 0xd8ba3757UL, 0xdafc890eUL, 0xdb3ee339UL, 0xde71f5bcUL, + 0xdfb39f8bUL, 0xddf521d2UL, 0xdc374be5UL, 0xd76b0cd8UL, 0xd6a966efUL, + 0xd4efd8b6UL, 0xd52db281UL, 0xd062a404UL, 0xd1a0ce33UL, 0xd3e6706aUL, + 0xd2241a5dUL, 0xc55efe10UL, 0xc49c9427UL, 0xc6da2a7eUL, 0xc7184049UL, + 0xc25756ccUL, 0xc3953cfbUL, 0xc1d382a2UL, 0xc011e895UL, 0xcb4dafa8UL, + 0xca8fc59fUL, 0xc8c97bc6UL, 0xc90b11f1UL, 0xcc440774UL, 0xcd866d43UL, + 0xcfc0d31aUL, 0xce02b92dUL, 0x91af9640UL, 0x906dfc77UL, 0x922b422eUL, + 0x93e92819UL, 0x96a63e9cUL, 0x976454abUL, 0x9522eaf2UL, 0x94e080c5UL, + 0x9fbcc7f8UL, 0x9e7eadcfUL, 0x9c381396UL, 0x9dfa79a1UL, 0x98b56f24UL, + 0x99770513UL, 0x9b31bb4aUL, 0x9af3d17dUL, 0x8d893530UL, 0x8c4b5f07UL, + 0x8e0de15eUL, 0x8fcf8b69UL, 0x8a809decUL, 0x8b42f7dbUL, 0x89044982UL, + 0x88c623b5UL, 0x839a6488UL, 0x82580ebfUL, 0x801eb0e6UL, 0x81dcdad1UL, + 0x8493cc54UL, 0x8551a663UL, 0x8717183aUL, 0x86d5720dUL, 0xa9e2d0a0UL, + 0xa820ba97UL, 0xaa6604ceUL, 0xaba46ef9UL, 0xaeeb787cUL, 0xaf29124bUL, + 0xad6fac12UL, 0xacadc625UL, 0xa7f18118UL, 0xa633eb2fUL, 0xa4755576UL, + 0xa5b73f41UL, 0xa0f829c4UL, 0xa13a43f3UL, 0xa37cfdaaUL, 0xa2be979dUL, + 0xb5c473d0UL, 0xb40619e7UL, 0xb640a7beUL, 0xb782cd89UL, 0xb2cddb0cUL, + 0xb30fb13bUL, 0xb1490f62UL, 0xb08b6555UL, 0xbbd72268UL, 0xba15485fUL, + 0xb853f606UL, 0xb9919c31UL, 0xbcde8ab4UL, 0xbd1ce083UL, 0xbf5a5edaUL, + 0xbe9834edUL + }, + { + 0x00000000UL, 0xb8bc6765UL, 0xaa09c88bUL, 0x12b5afeeUL, 0x8f629757UL, + 0x37def032UL, 0x256b5fdcUL, 0x9dd738b9UL, 0xc5b428efUL, 0x7d084f8aUL, + 0x6fbde064UL, 0xd7018701UL, 0x4ad6bfb8UL, 0xf26ad8ddUL, 0xe0df7733UL, + 0x58631056UL, 0x5019579fUL, 0xe8a530faUL, 0xfa109f14UL, 0x42acf871UL, + 0xdf7bc0c8UL, 0x67c7a7adUL, 0x75720843UL, 0xcdce6f26UL, 0x95ad7f70UL, + 0x2d111815UL, 0x3fa4b7fbUL, 0x8718d09eUL, 0x1acfe827UL, 0xa2738f42UL, + 0xb0c620acUL, 0x087a47c9UL, 0xa032af3eUL, 0x188ec85bUL, 0x0a3b67b5UL, + 0xb28700d0UL, 0x2f503869UL, 0x97ec5f0cUL, 0x8559f0e2UL, 0x3de59787UL, + 0x658687d1UL, 0xdd3ae0b4UL, 0xcf8f4f5aUL, 0x7733283fUL, 0xeae41086UL, + 0x525877e3UL, 0x40edd80dUL, 0xf851bf68UL, 0xf02bf8a1UL, 0x48979fc4UL, + 0x5a22302aUL, 0xe29e574fUL, 0x7f496ff6UL, 0xc7f50893UL, 0xd540a77dUL, + 0x6dfcc018UL, 0x359fd04eUL, 0x8d23b72bUL, 0x9f9618c5UL, 0x272a7fa0UL, + 0xbafd4719UL, 0x0241207cUL, 0x10f48f92UL, 0xa848e8f7UL, 0x9b14583dUL, + 0x23a83f58UL, 0x311d90b6UL, 0x89a1f7d3UL, 0x1476cf6aUL, 0xaccaa80fUL, + 0xbe7f07e1UL, 0x06c36084UL, 0x5ea070d2UL, 0xe61c17b7UL, 0xf4a9b859UL, + 0x4c15df3cUL, 0xd1c2e785UL, 0x697e80e0UL, 0x7bcb2f0eUL, 0xc377486bUL, + 0xcb0d0fa2UL, 0x73b168c7UL, 0x6104c729UL, 0xd9b8a04cUL, 0x446f98f5UL, + 0xfcd3ff90UL, 0xee66507eUL, 0x56da371bUL, 0x0eb9274dUL, 0xb6054028UL, + 0xa4b0efc6UL, 0x1c0c88a3UL, 0x81dbb01aUL, 0x3967d77fUL, 0x2bd27891UL, + 0x936e1ff4UL, 0x3b26f703UL, 0x839a9066UL, 0x912f3f88UL, 0x299358edUL, + 0xb4446054UL, 0x0cf80731UL, 0x1e4da8dfUL, 0xa6f1cfbaUL, 0xfe92dfecUL, + 0x462eb889UL, 0x549b1767UL, 0xec277002UL, 0x71f048bbUL, 0xc94c2fdeUL, + 0xdbf98030UL, 0x6345e755UL, 0x6b3fa09cUL, 0xd383c7f9UL, 0xc1366817UL, + 0x798a0f72UL, 0xe45d37cbUL, 0x5ce150aeUL, 0x4e54ff40UL, 0xf6e89825UL, + 0xae8b8873UL, 0x1637ef16UL, 0x048240f8UL, 0xbc3e279dUL, 0x21e91f24UL, + 0x99557841UL, 0x8be0d7afUL, 0x335cb0caUL, 0xed59b63bUL, 0x55e5d15eUL, + 0x47507eb0UL, 0xffec19d5UL, 0x623b216cUL, 0xda874609UL, 0xc832e9e7UL, + 0x708e8e82UL, 0x28ed9ed4UL, 0x9051f9b1UL, 0x82e4565fUL, 0x3a58313aUL, + 0xa78f0983UL, 0x1f336ee6UL, 0x0d86c108UL, 0xb53aa66dUL, 0xbd40e1a4UL, + 0x05fc86c1UL, 0x1749292fUL, 0xaff54e4aUL, 0x322276f3UL, 0x8a9e1196UL, + 0x982bbe78UL, 0x2097d91dUL, 0x78f4c94bUL, 0xc048ae2eUL, 0xd2fd01c0UL, + 0x6a4166a5UL, 0xf7965e1cUL, 0x4f2a3979UL, 0x5d9f9697UL, 0xe523f1f2UL, + 0x4d6b1905UL, 0xf5d77e60UL, 0xe762d18eUL, 0x5fdeb6ebUL, 0xc2098e52UL, + 0x7ab5e937UL, 0x680046d9UL, 0xd0bc21bcUL, 0x88df31eaUL, 0x3063568fUL, + 0x22d6f961UL, 0x9a6a9e04UL, 0x07bda6bdUL, 0xbf01c1d8UL, 0xadb46e36UL, + 0x15080953UL, 0x1d724e9aUL, 0xa5ce29ffUL, 0xb77b8611UL, 0x0fc7e174UL, + 0x9210d9cdUL, 0x2aacbea8UL, 0x38191146UL, 0x80a57623UL, 0xd8c66675UL, + 0x607a0110UL, 0x72cfaefeUL, 0xca73c99bUL, 0x57a4f122UL, 0xef189647UL, + 0xfdad39a9UL, 0x45115eccUL, 0x764dee06UL, 0xcef18963UL, 0xdc44268dUL, + 0x64f841e8UL, 0xf92f7951UL, 0x41931e34UL, 0x5326b1daUL, 0xeb9ad6bfUL, + 0xb3f9c6e9UL, 0x0b45a18cUL, 0x19f00e62UL, 0xa14c6907UL, 0x3c9b51beUL, + 0x842736dbUL, 0x96929935UL, 0x2e2efe50UL, 0x2654b999UL, 0x9ee8defcUL, + 0x8c5d7112UL, 0x34e11677UL, 0xa9362eceUL, 0x118a49abUL, 0x033fe645UL, + 0xbb838120UL, 0xe3e09176UL, 0x5b5cf613UL, 0x49e959fdUL, 0xf1553e98UL, + 0x6c820621UL, 0xd43e6144UL, 0xc68bceaaUL, 0x7e37a9cfUL, 0xd67f4138UL, + 0x6ec3265dUL, 0x7c7689b3UL, 0xc4caeed6UL, 0x591dd66fUL, 0xe1a1b10aUL, + 0xf3141ee4UL, 0x4ba87981UL, 0x13cb69d7UL, 0xab770eb2UL, 0xb9c2a15cUL, + 0x017ec639UL, 0x9ca9fe80UL, 0x241599e5UL, 0x36a0360bUL, 0x8e1c516eUL, + 0x866616a7UL, 0x3eda71c2UL, 0x2c6fde2cUL, 0x94d3b949UL, 0x090481f0UL, + 0xb1b8e695UL, 0xa30d497bUL, 0x1bb12e1eUL, 0x43d23e48UL, 0xfb6e592dUL, + 0xe9dbf6c3UL, 0x516791a6UL, 0xccb0a91fUL, 0x740cce7aUL, 0x66b96194UL, + 0xde0506f1UL + }, + { + 0x00000000UL, 0x96300777UL, 0x2c610eeeUL, 0xba510999UL, 0x19c46d07UL, + 0x8ff46a70UL, 0x35a563e9UL, 0xa395649eUL, 0x3288db0eUL, 0xa4b8dc79UL, + 0x1ee9d5e0UL, 0x88d9d297UL, 0x2b4cb609UL, 0xbd7cb17eUL, 0x072db8e7UL, + 0x911dbf90UL, 0x6410b71dUL, 0xf220b06aUL, 0x4871b9f3UL, 0xde41be84UL, + 0x7dd4da1aUL, 0xebe4dd6dUL, 0x51b5d4f4UL, 0xc785d383UL, 0x56986c13UL, + 0xc0a86b64UL, 0x7af962fdUL, 0xecc9658aUL, 0x4f5c0114UL, 0xd96c0663UL, + 0x633d0ffaUL, 0xf50d088dUL, 0xc8206e3bUL, 0x5e10694cUL, 0xe44160d5UL, + 0x727167a2UL, 0xd1e4033cUL, 0x47d4044bUL, 0xfd850dd2UL, 0x6bb50aa5UL, + 0xfaa8b535UL, 0x6c98b242UL, 0xd6c9bbdbUL, 0x40f9bcacUL, 0xe36cd832UL, + 0x755cdf45UL, 0xcf0dd6dcUL, 0x593dd1abUL, 0xac30d926UL, 0x3a00de51UL, + 0x8051d7c8UL, 0x1661d0bfUL, 0xb5f4b421UL, 0x23c4b356UL, 0x9995bacfUL, + 0x0fa5bdb8UL, 0x9eb80228UL, 0x0888055fUL, 0xb2d90cc6UL, 0x24e90bb1UL, + 0x877c6f2fUL, 0x114c6858UL, 0xab1d61c1UL, 0x3d2d66b6UL, 0x9041dc76UL, + 0x0671db01UL, 0xbc20d298UL, 0x2a10d5efUL, 0x8985b171UL, 0x1fb5b606UL, + 0xa5e4bf9fUL, 0x33d4b8e8UL, 0xa2c90778UL, 0x34f9000fUL, 0x8ea80996UL, + 0x18980ee1UL, 0xbb0d6a7fUL, 0x2d3d6d08UL, 0x976c6491UL, 0x015c63e6UL, + 0xf4516b6bUL, 0x62616c1cUL, 0xd8306585UL, 0x4e0062f2UL, 0xed95066cUL, + 0x7ba5011bUL, 0xc1f40882UL, 0x57c40ff5UL, 0xc6d9b065UL, 0x50e9b712UL, + 0xeab8be8bUL, 0x7c88b9fcUL, 0xdf1ddd62UL, 0x492dda15UL, 0xf37cd38cUL, + 0x654cd4fbUL, 0x5861b24dUL, 0xce51b53aUL, 0x7400bca3UL, 0xe230bbd4UL, + 0x41a5df4aUL, 0xd795d83dUL, 0x6dc4d1a4UL, 0xfbf4d6d3UL, 0x6ae96943UL, + 0xfcd96e34UL, 0x468867adUL, 0xd0b860daUL, 0x732d0444UL, 0xe51d0333UL, + 0x5f4c0aaaUL, 0xc97c0dddUL, 0x3c710550UL, 0xaa410227UL, 0x10100bbeUL, + 0x86200cc9UL, 0x25b56857UL, 0xb3856f20UL, 0x09d466b9UL, 0x9fe461ceUL, + 0x0ef9de5eUL, 0x98c9d929UL, 0x2298d0b0UL, 0xb4a8d7c7UL, 0x173db359UL, + 0x810db42eUL, 0x3b5cbdb7UL, 0xad6cbac0UL, 0x2083b8edUL, 0xb6b3bf9aUL, + 0x0ce2b603UL, 0x9ad2b174UL, 0x3947d5eaUL, 0xaf77d29dUL, 0x1526db04UL, + 0x8316dc73UL, 0x120b63e3UL, 0x843b6494UL, 0x3e6a6d0dUL, 0xa85a6a7aUL, + 0x0bcf0ee4UL, 0x9dff0993UL, 0x27ae000aUL, 0xb19e077dUL, 0x44930ff0UL, + 0xd2a30887UL, 0x68f2011eUL, 0xfec20669UL, 0x5d5762f7UL, 0xcb676580UL, + 0x71366c19UL, 0xe7066b6eUL, 0x761bd4feUL, 0xe02bd389UL, 0x5a7ada10UL, + 0xcc4add67UL, 0x6fdfb9f9UL, 0xf9efbe8eUL, 0x43beb717UL, 0xd58eb060UL, + 0xe8a3d6d6UL, 0x7e93d1a1UL, 0xc4c2d838UL, 0x52f2df4fUL, 0xf167bbd1UL, + 0x6757bca6UL, 0xdd06b53fUL, 0x4b36b248UL, 0xda2b0dd8UL, 0x4c1b0aafUL, + 0xf64a0336UL, 0x607a0441UL, 0xc3ef60dfUL, 0x55df67a8UL, 0xef8e6e31UL, + 0x79be6946UL, 0x8cb361cbUL, 0x1a8366bcUL, 0xa0d26f25UL, 0x36e26852UL, + 0x95770cccUL, 0x03470bbbUL, 0xb9160222UL, 0x2f260555UL, 0xbe3bbac5UL, + 0x280bbdb2UL, 0x925ab42bUL, 0x046ab35cUL, 0xa7ffd7c2UL, 0x31cfd0b5UL, + 0x8b9ed92cUL, 0x1daede5bUL, 0xb0c2649bUL, 0x26f263ecUL, 0x9ca36a75UL, + 0x0a936d02UL, 0xa906099cUL, 0x3f360eebUL, 0x85670772UL, 0x13570005UL, + 0x824abf95UL, 0x147ab8e2UL, 0xae2bb17bUL, 0x381bb60cUL, 0x9b8ed292UL, + 0x0dbed5e5UL, 0xb7efdc7cUL, 0x21dfdb0bUL, 0xd4d2d386UL, 0x42e2d4f1UL, + 0xf8b3dd68UL, 0x6e83da1fUL, 0xcd16be81UL, 0x5b26b9f6UL, 0xe177b06fUL, + 0x7747b718UL, 0xe65a0888UL, 0x706a0fffUL, 0xca3b0666UL, 0x5c0b0111UL, + 0xff9e658fUL, 0x69ae62f8UL, 0xd3ff6b61UL, 0x45cf6c16UL, 0x78e20aa0UL, + 0xeed20dd7UL, 0x5483044eUL, 0xc2b30339UL, 0x612667a7UL, 0xf71660d0UL, + 0x4d476949UL, 0xdb776e3eUL, 0x4a6ad1aeUL, 0xdc5ad6d9UL, 0x660bdf40UL, + 0xf03bd837UL, 0x53aebca9UL, 0xc59ebbdeUL, 0x7fcfb247UL, 0xe9ffb530UL, + 0x1cf2bdbdUL, 0x8ac2bacaUL, 0x3093b353UL, 0xa6a3b424UL, 0x0536d0baUL, + 0x9306d7cdUL, 0x2957de54UL, 0xbf67d923UL, 0x2e7a66b3UL, 0xb84a61c4UL, + 0x021b685dUL, 0x942b6f2aUL, 0x37be0bb4UL, 0xa18e0cc3UL, 0x1bdf055aUL, + 0x8def022dUL + }, + { + 0x00000000UL, 0x41311b19UL, 0x82623632UL, 0xc3532d2bUL, 0x04c56c64UL, + 0x45f4777dUL, 0x86a75a56UL, 0xc796414fUL, 0x088ad9c8UL, 0x49bbc2d1UL, + 0x8ae8effaUL, 0xcbd9f4e3UL, 0x0c4fb5acUL, 0x4d7eaeb5UL, 0x8e2d839eUL, + 0xcf1c9887UL, 0x5112c24aUL, 0x1023d953UL, 0xd370f478UL, 0x9241ef61UL, + 0x55d7ae2eUL, 0x14e6b537UL, 0xd7b5981cUL, 0x96848305UL, 0x59981b82UL, + 0x18a9009bUL, 0xdbfa2db0UL, 0x9acb36a9UL, 0x5d5d77e6UL, 0x1c6c6cffUL, + 0xdf3f41d4UL, 0x9e0e5acdUL, 0xa2248495UL, 0xe3159f8cUL, 0x2046b2a7UL, + 0x6177a9beUL, 0xa6e1e8f1UL, 0xe7d0f3e8UL, 0x2483dec3UL, 0x65b2c5daUL, + 0xaaae5d5dUL, 0xeb9f4644UL, 0x28cc6b6fUL, 0x69fd7076UL, 0xae6b3139UL, + 0xef5a2a20UL, 0x2c09070bUL, 0x6d381c12UL, 0xf33646dfUL, 0xb2075dc6UL, + 0x715470edUL, 0x30656bf4UL, 0xf7f32abbUL, 0xb6c231a2UL, 0x75911c89UL, + 0x34a00790UL, 0xfbbc9f17UL, 0xba8d840eUL, 0x79dea925UL, 0x38efb23cUL, + 0xff79f373UL, 0xbe48e86aUL, 0x7d1bc541UL, 0x3c2ade58UL, 0x054f79f0UL, + 0x447e62e9UL, 0x872d4fc2UL, 0xc61c54dbUL, 0x018a1594UL, 0x40bb0e8dUL, + 0x83e823a6UL, 0xc2d938bfUL, 0x0dc5a038UL, 0x4cf4bb21UL, 0x8fa7960aUL, + 0xce968d13UL, 0x0900cc5cUL, 0x4831d745UL, 0x8b62fa6eUL, 0xca53e177UL, + 0x545dbbbaUL, 0x156ca0a3UL, 0xd63f8d88UL, 0x970e9691UL, 0x5098d7deUL, + 0x11a9ccc7UL, 0xd2fae1ecUL, 0x93cbfaf5UL, 0x5cd76272UL, 0x1de6796bUL, + 0xdeb55440UL, 0x9f844f59UL, 0x58120e16UL, 0x1923150fUL, 0xda703824UL, + 0x9b41233dUL, 0xa76bfd65UL, 0xe65ae67cUL, 0x2509cb57UL, 0x6438d04eUL, + 0xa3ae9101UL, 0xe29f8a18UL, 0x21cca733UL, 0x60fdbc2aUL, 0xafe124adUL, + 0xeed03fb4UL, 0x2d83129fUL, 0x6cb20986UL, 0xab2448c9UL, 0xea1553d0UL, + 0x29467efbUL, 0x687765e2UL, 0xf6793f2fUL, 0xb7482436UL, 0x741b091dUL, + 0x352a1204UL, 0xf2bc534bUL, 0xb38d4852UL, 0x70de6579UL, 0x31ef7e60UL, + 0xfef3e6e7UL, 0xbfc2fdfeUL, 0x7c91d0d5UL, 0x3da0cbccUL, 0xfa368a83UL, + 0xbb07919aUL, 0x7854bcb1UL, 0x3965a7a8UL, 0x4b98833bUL, 0x0aa99822UL, + 0xc9fab509UL, 0x88cbae10UL, 0x4f5def5fUL, 0x0e6cf446UL, 0xcd3fd96dUL, + 0x8c0ec274UL, 0x43125af3UL, 0x022341eaUL, 0xc1706cc1UL, 0x804177d8UL, + 0x47d73697UL, 0x06e62d8eUL, 0xc5b500a5UL, 0x84841bbcUL, 0x1a8a4171UL, + 0x5bbb5a68UL, 0x98e87743UL, 0xd9d96c5aUL, 0x1e4f2d15UL, 0x5f7e360cUL, + 0x9c2d1b27UL, 0xdd1c003eUL, 0x120098b9UL, 0x533183a0UL, 0x9062ae8bUL, + 0xd153b592UL, 0x16c5f4ddUL, 0x57f4efc4UL, 0x94a7c2efUL, 0xd596d9f6UL, + 0xe9bc07aeUL, 0xa88d1cb7UL, 0x6bde319cUL, 0x2aef2a85UL, 0xed796bcaUL, + 0xac4870d3UL, 0x6f1b5df8UL, 0x2e2a46e1UL, 0xe136de66UL, 0xa007c57fUL, + 0x6354e854UL, 0x2265f34dUL, 0xe5f3b202UL, 0xa4c2a91bUL, 0x67918430UL, + 0x26a09f29UL, 0xb8aec5e4UL, 0xf99fdefdUL, 0x3accf3d6UL, 0x7bfde8cfUL, + 0xbc6ba980UL, 0xfd5ab299UL, 0x3e099fb2UL, 0x7f3884abUL, 0xb0241c2cUL, + 0xf1150735UL, 0x32462a1eUL, 0x73773107UL, 0xb4e17048UL, 0xf5d06b51UL, + 0x3683467aUL, 0x77b25d63UL, 0x4ed7facbUL, 0x0fe6e1d2UL, 0xccb5ccf9UL, + 0x8d84d7e0UL, 0x4a1296afUL, 0x0b238db6UL, 0xc870a09dUL, 0x8941bb84UL, + 0x465d2303UL, 0x076c381aUL, 0xc43f1531UL, 0x850e0e28UL, 0x42984f67UL, + 0x03a9547eUL, 0xc0fa7955UL, 0x81cb624cUL, 0x1fc53881UL, 0x5ef42398UL, + 0x9da70eb3UL, 0xdc9615aaUL, 0x1b0054e5UL, 0x5a314ffcUL, 0x996262d7UL, + 0xd85379ceUL, 0x174fe149UL, 0x567efa50UL, 0x952dd77bUL, 0xd41ccc62UL, + 0x138a8d2dUL, 0x52bb9634UL, 0x91e8bb1fUL, 0xd0d9a006UL, 0xecf37e5eUL, + 0xadc26547UL, 0x6e91486cUL, 0x2fa05375UL, 0xe836123aUL, 0xa9070923UL, + 0x6a542408UL, 0x2b653f11UL, 0xe479a796UL, 0xa548bc8fUL, 0x661b91a4UL, + 0x272a8abdUL, 0xe0bccbf2UL, 0xa18dd0ebUL, 0x62defdc0UL, 0x23efe6d9UL, + 0xbde1bc14UL, 0xfcd0a70dUL, 0x3f838a26UL, 0x7eb2913fUL, 0xb924d070UL, + 0xf815cb69UL, 0x3b46e642UL, 0x7a77fd5bUL, 0xb56b65dcUL, 0xf45a7ec5UL, + 0x370953eeUL, 0x763848f7UL, 0xb1ae09b8UL, 0xf09f12a1UL, 0x33cc3f8aUL, + 0x72fd2493UL + }, + { + 0x00000000UL, 0x376ac201UL, 0x6ed48403UL, 0x59be4602UL, 0xdca80907UL, + 0xebc2cb06UL, 0xb27c8d04UL, 0x85164f05UL, 0xb851130eUL, 0x8f3bd10fUL, + 0xd685970dUL, 0xe1ef550cUL, 0x64f91a09UL, 0x5393d808UL, 0x0a2d9e0aUL, + 0x3d475c0bUL, 0x70a3261cUL, 0x47c9e41dUL, 0x1e77a21fUL, 0x291d601eUL, + 0xac0b2f1bUL, 0x9b61ed1aUL, 0xc2dfab18UL, 0xf5b56919UL, 0xc8f23512UL, + 0xff98f713UL, 0xa626b111UL, 0x914c7310UL, 0x145a3c15UL, 0x2330fe14UL, + 0x7a8eb816UL, 0x4de47a17UL, 0xe0464d38UL, 0xd72c8f39UL, 0x8e92c93bUL, + 0xb9f80b3aUL, 0x3cee443fUL, 0x0b84863eUL, 0x523ac03cUL, 0x6550023dUL, + 0x58175e36UL, 0x6f7d9c37UL, 0x36c3da35UL, 0x01a91834UL, 0x84bf5731UL, + 0xb3d59530UL, 0xea6bd332UL, 0xdd011133UL, 0x90e56b24UL, 0xa78fa925UL, + 0xfe31ef27UL, 0xc95b2d26UL, 0x4c4d6223UL, 0x7b27a022UL, 0x2299e620UL, + 0x15f32421UL, 0x28b4782aUL, 0x1fdeba2bUL, 0x4660fc29UL, 0x710a3e28UL, + 0xf41c712dUL, 0xc376b32cUL, 0x9ac8f52eUL, 0xada2372fUL, 0xc08d9a70UL, + 0xf7e75871UL, 0xae591e73UL, 0x9933dc72UL, 0x1c259377UL, 0x2b4f5176UL, + 0x72f11774UL, 0x459bd575UL, 0x78dc897eUL, 0x4fb64b7fUL, 0x16080d7dUL, + 0x2162cf7cUL, 0xa4748079UL, 0x931e4278UL, 0xcaa0047aUL, 0xfdcac67bUL, + 0xb02ebc6cUL, 0x87447e6dUL, 0xdefa386fUL, 0xe990fa6eUL, 0x6c86b56bUL, + 0x5bec776aUL, 0x02523168UL, 0x3538f369UL, 0x087faf62UL, 0x3f156d63UL, + 0x66ab2b61UL, 0x51c1e960UL, 0xd4d7a665UL, 0xe3bd6464UL, 0xba032266UL, + 0x8d69e067UL, 0x20cbd748UL, 0x17a11549UL, 0x4e1f534bUL, 0x7975914aUL, + 0xfc63de4fUL, 0xcb091c4eUL, 0x92b75a4cUL, 0xa5dd984dUL, 0x989ac446UL, + 0xaff00647UL, 0xf64e4045UL, 0xc1248244UL, 0x4432cd41UL, 0x73580f40UL, + 0x2ae64942UL, 0x1d8c8b43UL, 0x5068f154UL, 0x67023355UL, 0x3ebc7557UL, + 0x09d6b756UL, 0x8cc0f853UL, 0xbbaa3a52UL, 0xe2147c50UL, 0xd57ebe51UL, + 0xe839e25aUL, 0xdf53205bUL, 0x86ed6659UL, 0xb187a458UL, 0x3491eb5dUL, + 0x03fb295cUL, 0x5a456f5eUL, 0x6d2fad5fUL, 0x801b35e1UL, 0xb771f7e0UL, + 0xeecfb1e2UL, 0xd9a573e3UL, 0x5cb33ce6UL, 0x6bd9fee7UL, 0x3267b8e5UL, + 0x050d7ae4UL, 0x384a26efUL, 0x0f20e4eeUL, 0x569ea2ecUL, 0x61f460edUL, + 0xe4e22fe8UL, 0xd388ede9UL, 0x8a36abebUL, 0xbd5c69eaUL, 0xf0b813fdUL, + 0xc7d2d1fcUL, 0x9e6c97feUL, 0xa90655ffUL, 0x2c101afaUL, 0x1b7ad8fbUL, + 0x42c49ef9UL, 0x75ae5cf8UL, 0x48e900f3UL, 0x7f83c2f2UL, 0x263d84f0UL, + 0x115746f1UL, 0x944109f4UL, 0xa32bcbf5UL, 0xfa958df7UL, 0xcdff4ff6UL, + 0x605d78d9UL, 0x5737bad8UL, 0x0e89fcdaUL, 0x39e33edbUL, 0xbcf571deUL, + 0x8b9fb3dfUL, 0xd221f5ddUL, 0xe54b37dcUL, 0xd80c6bd7UL, 0xef66a9d6UL, + 0xb6d8efd4UL, 0x81b22dd5UL, 0x04a462d0UL, 0x33cea0d1UL, 0x6a70e6d3UL, + 0x5d1a24d2UL, 0x10fe5ec5UL, 0x27949cc4UL, 0x7e2adac6UL, 0x494018c7UL, + 0xcc5657c2UL, 0xfb3c95c3UL, 0xa282d3c1UL, 0x95e811c0UL, 0xa8af4dcbUL, + 0x9fc58fcaUL, 0xc67bc9c8UL, 0xf1110bc9UL, 0x740744ccUL, 0x436d86cdUL, + 0x1ad3c0cfUL, 0x2db902ceUL, 0x4096af91UL, 0x77fc6d90UL, 0x2e422b92UL, + 0x1928e993UL, 0x9c3ea696UL, 0xab546497UL, 0xf2ea2295UL, 0xc580e094UL, + 0xf8c7bc9fUL, 0xcfad7e9eUL, 0x9613389cUL, 0xa179fa9dUL, 0x246fb598UL, + 0x13057799UL, 0x4abb319bUL, 0x7dd1f39aUL, 0x3035898dUL, 0x075f4b8cUL, + 0x5ee10d8eUL, 0x698bcf8fUL, 0xec9d808aUL, 0xdbf7428bUL, 0x82490489UL, + 0xb523c688UL, 0x88649a83UL, 0xbf0e5882UL, 0xe6b01e80UL, 0xd1dadc81UL, + 0x54cc9384UL, 0x63a65185UL, 0x3a181787UL, 0x0d72d586UL, 0xa0d0e2a9UL, + 0x97ba20a8UL, 0xce0466aaUL, 0xf96ea4abUL, 0x7c78ebaeUL, 0x4b1229afUL, + 0x12ac6fadUL, 0x25c6adacUL, 0x1881f1a7UL, 0x2feb33a6UL, 0x765575a4UL, + 0x413fb7a5UL, 0xc429f8a0UL, 0xf3433aa1UL, 0xaafd7ca3UL, 0x9d97bea2UL, + 0xd073c4b5UL, 0xe71906b4UL, 0xbea740b6UL, 0x89cd82b7UL, 0x0cdbcdb2UL, + 0x3bb10fb3UL, 0x620f49b1UL, 0x55658bb0UL, 0x6822d7bbUL, 0x5f4815baUL, + 0x06f653b8UL, 0x319c91b9UL, 0xb48adebcUL, 0x83e01cbdUL, 0xda5e5abfUL, + 0xed3498beUL + }, + { + 0x00000000UL, 0x6567bcb8UL, 0x8bc809aaUL, 0xeeafb512UL, 0x5797628fUL, + 0x32f0de37UL, 0xdc5f6b25UL, 0xb938d79dUL, 0xef28b4c5UL, 0x8a4f087dUL, + 0x64e0bd6fUL, 0x018701d7UL, 0xb8bfd64aUL, 0xddd86af2UL, 0x3377dfe0UL, + 0x56106358UL, 0x9f571950UL, 0xfa30a5e8UL, 0x149f10faUL, 0x71f8ac42UL, + 0xc8c07bdfUL, 0xada7c767UL, 0x43087275UL, 0x266fcecdUL, 0x707fad95UL, + 0x1518112dUL, 0xfbb7a43fUL, 0x9ed01887UL, 0x27e8cf1aUL, 0x428f73a2UL, + 0xac20c6b0UL, 0xc9477a08UL, 0x3eaf32a0UL, 0x5bc88e18UL, 0xb5673b0aUL, + 0xd00087b2UL, 0x6938502fUL, 0x0c5fec97UL, 0xe2f05985UL, 0x8797e53dUL, + 0xd1878665UL, 0xb4e03addUL, 0x5a4f8fcfUL, 0x3f283377UL, 0x8610e4eaUL, + 0xe3775852UL, 0x0dd8ed40UL, 0x68bf51f8UL, 0xa1f82bf0UL, 0xc49f9748UL, + 0x2a30225aUL, 0x4f579ee2UL, 0xf66f497fUL, 0x9308f5c7UL, 0x7da740d5UL, + 0x18c0fc6dUL, 0x4ed09f35UL, 0x2bb7238dUL, 0xc518969fUL, 0xa07f2a27UL, + 0x1947fdbaUL, 0x7c204102UL, 0x928ff410UL, 0xf7e848a8UL, 0x3d58149bUL, + 0x583fa823UL, 0xb6901d31UL, 0xd3f7a189UL, 0x6acf7614UL, 0x0fa8caacUL, + 0xe1077fbeUL, 0x8460c306UL, 0xd270a05eUL, 0xb7171ce6UL, 0x59b8a9f4UL, + 0x3cdf154cUL, 0x85e7c2d1UL, 0xe0807e69UL, 0x0e2fcb7bUL, 0x6b4877c3UL, + 0xa20f0dcbUL, 0xc768b173UL, 0x29c70461UL, 0x4ca0b8d9UL, 0xf5986f44UL, + 0x90ffd3fcUL, 0x7e5066eeUL, 0x1b37da56UL, 0x4d27b90eUL, 0x284005b6UL, + 0xc6efb0a4UL, 0xa3880c1cUL, 0x1ab0db81UL, 0x7fd76739UL, 0x9178d22bUL, + 0xf41f6e93UL, 0x03f7263bUL, 0x66909a83UL, 0x883f2f91UL, 0xed589329UL, + 0x546044b4UL, 0x3107f80cUL, 0xdfa84d1eUL, 0xbacff1a6UL, 0xecdf92feUL, + 0x89b82e46UL, 0x67179b54UL, 0x027027ecUL, 0xbb48f071UL, 0xde2f4cc9UL, + 0x3080f9dbUL, 0x55e74563UL, 0x9ca03f6bUL, 0xf9c783d3UL, 0x176836c1UL, + 0x720f8a79UL, 0xcb375de4UL, 0xae50e15cUL, 0x40ff544eUL, 0x2598e8f6UL, + 0x73888baeUL, 0x16ef3716UL, 0xf8408204UL, 0x9d273ebcUL, 0x241fe921UL, + 0x41785599UL, 0xafd7e08bUL, 0xcab05c33UL, 0x3bb659edUL, 0x5ed1e555UL, + 0xb07e5047UL, 0xd519ecffUL, 0x6c213b62UL, 0x094687daUL, 0xe7e932c8UL, + 0x828e8e70UL, 0xd49eed28UL, 0xb1f95190UL, 0x5f56e482UL, 0x3a31583aUL, + 0x83098fa7UL, 0xe66e331fUL, 0x08c1860dUL, 0x6da63ab5UL, 0xa4e140bdUL, + 0xc186fc05UL, 0x2f294917UL, 0x4a4ef5afUL, 0xf3762232UL, 0x96119e8aUL, + 0x78be2b98UL, 0x1dd99720UL, 0x4bc9f478UL, 0x2eae48c0UL, 0xc001fdd2UL, + 0xa566416aUL, 0x1c5e96f7UL, 0x79392a4fUL, 0x97969f5dUL, 0xf2f123e5UL, + 0x05196b4dUL, 0x607ed7f5UL, 0x8ed162e7UL, 0xebb6de5fUL, 0x528e09c2UL, + 0x37e9b57aUL, 0xd9460068UL, 0xbc21bcd0UL, 0xea31df88UL, 0x8f566330UL, + 0x61f9d622UL, 0x049e6a9aUL, 0xbda6bd07UL, 0xd8c101bfUL, 0x366eb4adUL, + 0x53090815UL, 0x9a4e721dUL, 0xff29cea5UL, 0x11867bb7UL, 0x74e1c70fUL, + 0xcdd91092UL, 0xa8beac2aUL, 0x46111938UL, 0x2376a580UL, 0x7566c6d8UL, + 0x10017a60UL, 0xfeaecf72UL, 0x9bc973caUL, 0x22f1a457UL, 0x479618efUL, + 0xa939adfdUL, 0xcc5e1145UL, 0x06ee4d76UL, 0x6389f1ceUL, 0x8d2644dcUL, + 0xe841f864UL, 0x51792ff9UL, 0x341e9341UL, 0xdab12653UL, 0xbfd69aebUL, + 0xe9c6f9b3UL, 0x8ca1450bUL, 0x620ef019UL, 0x07694ca1UL, 0xbe519b3cUL, + 0xdb362784UL, 0x35999296UL, 0x50fe2e2eUL, 0x99b95426UL, 0xfcdee89eUL, + 0x12715d8cUL, 0x7716e134UL, 0xce2e36a9UL, 0xab498a11UL, 0x45e63f03UL, + 0x208183bbUL, 0x7691e0e3UL, 0x13f65c5bUL, 0xfd59e949UL, 0x983e55f1UL, + 0x2106826cUL, 0x44613ed4UL, 0xaace8bc6UL, 0xcfa9377eUL, 0x38417fd6UL, + 0x5d26c36eUL, 0xb389767cUL, 0xd6eecac4UL, 0x6fd61d59UL, 0x0ab1a1e1UL, + 0xe41e14f3UL, 0x8179a84bUL, 0xd769cb13UL, 0xb20e77abUL, 0x5ca1c2b9UL, + 0x39c67e01UL, 0x80fea99cUL, 0xe5991524UL, 0x0b36a036UL, 0x6e511c8eUL, + 0xa7166686UL, 0xc271da3eUL, 0x2cde6f2cUL, 0x49b9d394UL, 0xf0810409UL, + 0x95e6b8b1UL, 0x7b490da3UL, 0x1e2eb11bUL, 0x483ed243UL, 0x2d596efbUL, + 0xc3f6dbe9UL, 0xa6916751UL, 0x1fa9b0ccUL, 0x7ace0c74UL, 0x9461b966UL, + 0xf10605deUL +#endif + } +}; ADDED compat/zlib/deflate.c Index: compat/zlib/deflate.c ================================================================== --- /dev/null +++ compat/zlib/deflate.c @@ -0,0 +1,2163 @@ +/* deflate.c -- compress data using the deflation algorithm + * Copyright (C) 1995-2017 Jean-loup Gailly and Mark Adler + * For conditions of distribution and use, see copyright notice in zlib.h + */ + +/* + * ALGORITHM + * + * The "deflation" process depends on being able to identify portions + * of the input text which are identical to earlier input (within a + * sliding window trailing behind the input currently being processed). + * + * The most straightforward technique turns out to be the fastest for + * most input files: try all possible matches and select the longest. + * The key feature of this algorithm is that insertions into the string + * dictionary are very simple and thus fast, and deletions are avoided + * completely. Insertions are performed at each input character, whereas + * string matches are performed only when the previous match ends. So it + * is preferable to spend more time in matches to allow very fast string + * insertions and avoid deletions. The matching algorithm for small + * strings is inspired from that of Rabin & Karp. A brute force approach + * is used to find longer strings when a small match has been found. + * A similar algorithm is used in comic (by Jan-Mark Wams) and freeze + * (by Leonid Broukhis). + * A previous version of this file used a more sophisticated algorithm + * (by Fiala and Greene) which is guaranteed to run in linear amortized + * time, but has a larger average cost, uses more memory and is patented. + * However the F&G algorithm may be faster for some highly redundant + * files if the parameter max_chain_length (described below) is too large. + * + * ACKNOWLEDGEMENTS + * + * The idea of lazy evaluation of matches is due to Jan-Mark Wams, and + * I found it in 'freeze' written by Leonid Broukhis. + * Thanks to many people for bug reports and testing. + * + * REFERENCES + * + * Deutsch, L.P.,"DEFLATE Compressed Data Format Specification". + * Available in http://tools.ietf.org/html/rfc1951 + * + * A description of the Rabin and Karp algorithm is given in the book + * "Algorithms" by R. Sedgewick, Addison-Wesley, p252. + * + * Fiala,E.R., and Greene,D.H. + * Data Compression with Finite Windows, Comm.ACM, 32,4 (1989) 490-595 + * + */ + +/* @(#) $Id$ */ + +#include "deflate.h" + +const char deflate_copyright[] = + " deflate 1.2.11 Copyright 1995-2017 Jean-loup Gailly and Mark Adler "; +/* + If you use the zlib library in a product, an acknowledgment is welcome + in the documentation of your product. If for some reason you cannot + include such an acknowledgment, I would appreciate that you keep this + copyright string in the executable of your product. + */ + +/* =========================================================================== + * Function prototypes. + */ +typedef enum { + need_more, /* block not completed, need more input or more output */ + block_done, /* block flush performed */ + finish_started, /* finish started, need only more output at next deflate */ + finish_done /* finish done, accept no more input or output */ +} block_state; + +typedef block_state (*compress_func) OF((deflate_state *s, int flush)); +/* Compression function. Returns the block state after the call. */ + +local int deflateStateCheck OF((z_streamp strm)); +local void slide_hash OF((deflate_state *s)); +local void fill_window OF((deflate_state *s)); +local block_state deflate_stored OF((deflate_state *s, int flush)); +local block_state deflate_fast OF((deflate_state *s, int flush)); +#ifndef FASTEST +local block_state deflate_slow OF((deflate_state *s, int flush)); +#endif +local block_state deflate_rle OF((deflate_state *s, int flush)); +local block_state deflate_huff OF((deflate_state *s, int flush)); +local void lm_init OF((deflate_state *s)); +local void putShortMSB OF((deflate_state *s, uInt b)); +local void flush_pending OF((z_streamp strm)); +local unsigned read_buf OF((z_streamp strm, Bytef *buf, unsigned size)); +#ifdef ASMV +# pragma message("Assembler code may have bugs -- use at your own risk") + void match_init OF((void)); /* asm code initialization */ + uInt longest_match OF((deflate_state *s, IPos cur_match)); +#else +local uInt longest_match OF((deflate_state *s, IPos cur_match)); +#endif + +#ifdef ZLIB_DEBUG +local void check_match OF((deflate_state *s, IPos start, IPos match, + int length)); +#endif + +/* =========================================================================== + * Local data + */ + +#define NIL 0 +/* Tail of hash chains */ + +#ifndef TOO_FAR +# define TOO_FAR 4096 +#endif +/* Matches of length 3 are discarded if their distance exceeds TOO_FAR */ + +/* Values for max_lazy_match, good_match and max_chain_length, depending on + * the desired pack level (0..9). The values given below have been tuned to + * exclude worst case performance for pathological files. Better values may be + * found for specific files. + */ +typedef struct config_s { + ush good_length; /* reduce lazy search above this match length */ + ush max_lazy; /* do not perform lazy search above this match length */ + ush nice_length; /* quit search above this match length */ + ush max_chain; + compress_func func; +} config; + +#ifdef FASTEST +local const config configuration_table[2] = { +/* good lazy nice chain */ +/* 0 */ {0, 0, 0, 0, deflate_stored}, /* store only */ +/* 1 */ {4, 4, 8, 4, deflate_fast}}; /* max speed, no lazy matches */ +#else +local const config configuration_table[10] = { +/* good lazy nice chain */ +/* 0 */ {0, 0, 0, 0, deflate_stored}, /* store only */ +/* 1 */ {4, 4, 8, 4, deflate_fast}, /* max speed, no lazy matches */ +/* 2 */ {4, 5, 16, 8, deflate_fast}, +/* 3 */ {4, 6, 32, 32, deflate_fast}, + +/* 4 */ {4, 4, 16, 16, deflate_slow}, /* lazy matches */ +/* 5 */ {8, 16, 32, 32, deflate_slow}, +/* 6 */ {8, 16, 128, 128, deflate_slow}, +/* 7 */ {8, 32, 128, 256, deflate_slow}, +/* 8 */ {32, 128, 258, 1024, deflate_slow}, +/* 9 */ {32, 258, 258, 4096, deflate_slow}}; /* max compression */ +#endif + +/* Note: the deflate() code requires max_lazy >= MIN_MATCH and max_chain >= 4 + * For deflate_fast() (levels <= 3) good is ignored and lazy has a different + * meaning. + */ + +/* rank Z_BLOCK between Z_NO_FLUSH and Z_PARTIAL_FLUSH */ +#define RANK(f) (((f) * 2) - ((f) > 4 ? 9 : 0)) + +/* =========================================================================== + * Update a hash value with the given input byte + * IN assertion: all calls to UPDATE_HASH are made with consecutive input + * characters, so that a running hash key can be computed from the previous + * key instead of complete recalculation each time. + */ +#define UPDATE_HASH(s,h,c) (h = (((h)<hash_shift) ^ (c)) & s->hash_mask) + + +/* =========================================================================== + * Insert string str in the dictionary and set match_head to the previous head + * of the hash chain (the most recent string with same hash key). Return + * the previous length of the hash chain. + * If this file is compiled with -DFASTEST, the compression level is forced + * to 1, and no hash chains are maintained. + * IN assertion: all calls to INSERT_STRING are made with consecutive input + * characters and the first MIN_MATCH bytes of str are valid (except for + * the last MIN_MATCH-1 bytes of the input file). + */ +#ifdef FASTEST +#define INSERT_STRING(s, str, match_head) \ + (UPDATE_HASH(s, s->ins_h, s->window[(str) + (MIN_MATCH-1)]), \ + match_head = s->head[s->ins_h], \ + s->head[s->ins_h] = (Pos)(str)) +#else +#define INSERT_STRING(s, str, match_head) \ + (UPDATE_HASH(s, s->ins_h, s->window[(str) + (MIN_MATCH-1)]), \ + match_head = s->prev[(str) & s->w_mask] = s->head[s->ins_h], \ + s->head[s->ins_h] = (Pos)(str)) +#endif + +/* =========================================================================== + * Initialize the hash table (avoiding 64K overflow for 16 bit systems). + * prev[] will be initialized on the fly. + */ +#define CLEAR_HASH(s) \ + s->head[s->hash_size-1] = NIL; \ + zmemzero((Bytef *)s->head, (unsigned)(s->hash_size-1)*sizeof(*s->head)); + +/* =========================================================================== + * Slide the hash table when sliding the window down (could be avoided with 32 + * bit values at the expense of memory usage). We slide even when level == 0 to + * keep the hash table consistent if we switch back to level > 0 later. + */ +local void slide_hash(s) + deflate_state *s; +{ + unsigned n, m; + Posf *p; + uInt wsize = s->w_size; + + n = s->hash_size; + p = &s->head[n]; + do { + m = *--p; + *p = (Pos)(m >= wsize ? m - wsize : NIL); + } while (--n); + n = wsize; +#ifndef FASTEST + p = &s->prev[n]; + do { + m = *--p; + *p = (Pos)(m >= wsize ? m - wsize : NIL); + /* If n is not on any hash chain, prev[n] is garbage but + * its value will never be used. + */ + } while (--n); +#endif +} + +/* ========================================================================= */ +int ZEXPORT deflateInit_(strm, level, version, stream_size) + z_streamp strm; + int level; + const char *version; + int stream_size; +{ + return deflateInit2_(strm, level, Z_DEFLATED, MAX_WBITS, DEF_MEM_LEVEL, + Z_DEFAULT_STRATEGY, version, stream_size); + /* To do: ignore strm->next_in if we use it as window */ +} + +/* ========================================================================= */ +int ZEXPORT deflateInit2_(strm, level, method, windowBits, memLevel, strategy, + version, stream_size) + z_streamp strm; + int level; + int method; + int windowBits; + int memLevel; + int strategy; + const char *version; + int stream_size; +{ + deflate_state *s; + int wrap = 1; + static const char my_version[] = ZLIB_VERSION; + + ushf *overlay; + /* We overlay pending_buf and d_buf+l_buf. This works since the average + * output size for (length,distance) codes is <= 24 bits. + */ + + if (version == Z_NULL || version[0] != my_version[0] || + stream_size != sizeof(z_stream)) { + return Z_VERSION_ERROR; + } + if (strm == Z_NULL) return Z_STREAM_ERROR; + + strm->msg = Z_NULL; + if (strm->zalloc == (alloc_func)0) { +#ifdef Z_SOLO + return Z_STREAM_ERROR; +#else + strm->zalloc = zcalloc; + strm->opaque = (voidpf)0; +#endif + } + if (strm->zfree == (free_func)0) +#ifdef Z_SOLO + return Z_STREAM_ERROR; +#else + strm->zfree = zcfree; +#endif + +#ifdef FASTEST + if (level != 0) level = 1; +#else + if (level == Z_DEFAULT_COMPRESSION) level = 6; +#endif + + if (windowBits < 0) { /* suppress zlib wrapper */ + wrap = 0; + windowBits = -windowBits; + } +#ifdef GZIP + else if (windowBits > 15) { + wrap = 2; /* write gzip wrapper instead */ + windowBits -= 16; + } +#endif + if (memLevel < 1 || memLevel > MAX_MEM_LEVEL || method != Z_DEFLATED || + windowBits < 8 || windowBits > 15 || level < 0 || level > 9 || + strategy < 0 || strategy > Z_FIXED || (windowBits == 8 && wrap != 1)) { + return Z_STREAM_ERROR; + } + if (windowBits == 8) windowBits = 9; /* until 256-byte window bug fixed */ + s = (deflate_state *) ZALLOC(strm, 1, sizeof(deflate_state)); + if (s == Z_NULL) return Z_MEM_ERROR; + strm->state = (struct internal_state FAR *)s; + s->strm = strm; + s->status = INIT_STATE; /* to pass state test in deflateReset() */ + + s->wrap = wrap; + s->gzhead = Z_NULL; + s->w_bits = (uInt)windowBits; + s->w_size = 1 << s->w_bits; + s->w_mask = s->w_size - 1; + + s->hash_bits = (uInt)memLevel + 7; + s->hash_size = 1 << s->hash_bits; + s->hash_mask = s->hash_size - 1; + s->hash_shift = ((s->hash_bits+MIN_MATCH-1)/MIN_MATCH); + + s->window = (Bytef *) ZALLOC(strm, s->w_size, 2*sizeof(Byte)); + s->prev = (Posf *) ZALLOC(strm, s->w_size, sizeof(Pos)); + s->head = (Posf *) ZALLOC(strm, s->hash_size, sizeof(Pos)); + + s->high_water = 0; /* nothing written to s->window yet */ + + s->lit_bufsize = 1 << (memLevel + 6); /* 16K elements by default */ + + overlay = (ushf *) ZALLOC(strm, s->lit_bufsize, sizeof(ush)+2); + s->pending_buf = (uchf *) overlay; + s->pending_buf_size = (ulg)s->lit_bufsize * (sizeof(ush)+2L); + + if (s->window == Z_NULL || s->prev == Z_NULL || s->head == Z_NULL || + s->pending_buf == Z_NULL) { + s->status = FINISH_STATE; + strm->msg = ERR_MSG(Z_MEM_ERROR); + deflateEnd (strm); + return Z_MEM_ERROR; + } + s->d_buf = overlay + s->lit_bufsize/sizeof(ush); + s->l_buf = s->pending_buf + (1+sizeof(ush))*s->lit_bufsize; + + s->level = level; + s->strategy = strategy; + s->method = (Byte)method; + + return deflateReset(strm); +} + +/* ========================================================================= + * Check for a valid deflate stream state. Return 0 if ok, 1 if not. + */ +local int deflateStateCheck (strm) + z_streamp strm; +{ + deflate_state *s; + if (strm == Z_NULL || + strm->zalloc == (alloc_func)0 || strm->zfree == (free_func)0) + return 1; + s = strm->state; + if (s == Z_NULL || s->strm != strm || (s->status != INIT_STATE && +#ifdef GZIP + s->status != GZIP_STATE && +#endif + s->status != EXTRA_STATE && + s->status != NAME_STATE && + s->status != COMMENT_STATE && + s->status != HCRC_STATE && + s->status != BUSY_STATE && + s->status != FINISH_STATE)) + return 1; + return 0; +} + +/* ========================================================================= */ +int ZEXPORT deflateSetDictionary (strm, dictionary, dictLength) + z_streamp strm; + const Bytef *dictionary; + uInt dictLength; +{ + deflate_state *s; + uInt str, n; + int wrap; + unsigned avail; + z_const unsigned char *next; + + if (deflateStateCheck(strm) || dictionary == Z_NULL) + return Z_STREAM_ERROR; + s = strm->state; + wrap = s->wrap; + if (wrap == 2 || (wrap == 1 && s->status != INIT_STATE) || s->lookahead) + return Z_STREAM_ERROR; + + /* when using zlib wrappers, compute Adler-32 for provided dictionary */ + if (wrap == 1) + strm->adler = adler32(strm->adler, dictionary, dictLength); + s->wrap = 0; /* avoid computing Adler-32 in read_buf */ + + /* if dictionary would fill window, just replace the history */ + if (dictLength >= s->w_size) { + if (wrap == 0) { /* already empty otherwise */ + CLEAR_HASH(s); + s->strstart = 0; + s->block_start = 0L; + s->insert = 0; + } + dictionary += dictLength - s->w_size; /* use the tail */ + dictLength = s->w_size; + } + + /* insert dictionary into window and hash */ + avail = strm->avail_in; + next = strm->next_in; + strm->avail_in = dictLength; + strm->next_in = (z_const Bytef *)dictionary; + fill_window(s); + while (s->lookahead >= MIN_MATCH) { + str = s->strstart; + n = s->lookahead - (MIN_MATCH-1); + do { + UPDATE_HASH(s, s->ins_h, s->window[str + MIN_MATCH-1]); +#ifndef FASTEST + s->prev[str & s->w_mask] = s->head[s->ins_h]; +#endif + s->head[s->ins_h] = (Pos)str; + str++; + } while (--n); + s->strstart = str; + s->lookahead = MIN_MATCH-1; + fill_window(s); + } + s->strstart += s->lookahead; + s->block_start = (long)s->strstart; + s->insert = s->lookahead; + s->lookahead = 0; + s->match_length = s->prev_length = MIN_MATCH-1; + s->match_available = 0; + strm->next_in = next; + strm->avail_in = avail; + s->wrap = wrap; + return Z_OK; +} + +/* ========================================================================= */ +int ZEXPORT deflateGetDictionary (strm, dictionary, dictLength) + z_streamp strm; + Bytef *dictionary; + uInt *dictLength; +{ + deflate_state *s; + uInt len; + + if (deflateStateCheck(strm)) + return Z_STREAM_ERROR; + s = strm->state; + len = s->strstart + s->lookahead; + if (len > s->w_size) + len = s->w_size; + if (dictionary != Z_NULL && len) + zmemcpy(dictionary, s->window + s->strstart + s->lookahead - len, len); + if (dictLength != Z_NULL) + *dictLength = len; + return Z_OK; +} + +/* ========================================================================= */ +int ZEXPORT deflateResetKeep (strm) + z_streamp strm; +{ + deflate_state *s; + + if (deflateStateCheck(strm)) { + return Z_STREAM_ERROR; + } + + strm->total_in = strm->total_out = 0; + strm->msg = Z_NULL; /* use zfree if we ever allocate msg dynamically */ + strm->data_type = Z_UNKNOWN; + + s = (deflate_state *)strm->state; + s->pending = 0; + s->pending_out = s->pending_buf; + + if (s->wrap < 0) { + s->wrap = -s->wrap; /* was made negative by deflate(..., Z_FINISH); */ + } + s->status = +#ifdef GZIP + s->wrap == 2 ? GZIP_STATE : +#endif + s->wrap ? INIT_STATE : BUSY_STATE; + strm->adler = +#ifdef GZIP + s->wrap == 2 ? crc32(0L, Z_NULL, 0) : +#endif + adler32(0L, Z_NULL, 0); + s->last_flush = Z_NO_FLUSH; + + _tr_init(s); + + return Z_OK; +} + +/* ========================================================================= */ +int ZEXPORT deflateReset (strm) + z_streamp strm; +{ + int ret; + + ret = deflateResetKeep(strm); + if (ret == Z_OK) + lm_init(strm->state); + return ret; +} + +/* ========================================================================= */ +int ZEXPORT deflateSetHeader (strm, head) + z_streamp strm; + gz_headerp head; +{ + if (deflateStateCheck(strm) || strm->state->wrap != 2) + return Z_STREAM_ERROR; + strm->state->gzhead = head; + return Z_OK; +} + +/* ========================================================================= */ +int ZEXPORT deflatePending (strm, pending, bits) + unsigned *pending; + int *bits; + z_streamp strm; +{ + if (deflateStateCheck(strm)) return Z_STREAM_ERROR; + if (pending != Z_NULL) + *pending = strm->state->pending; + if (bits != Z_NULL) + *bits = strm->state->bi_valid; + return Z_OK; +} + +/* ========================================================================= */ +int ZEXPORT deflatePrime (strm, bits, value) + z_streamp strm; + int bits; + int value; +{ + deflate_state *s; + int put; + + if (deflateStateCheck(strm)) return Z_STREAM_ERROR; + s = strm->state; + if ((Bytef *)(s->d_buf) < s->pending_out + ((Buf_size + 7) >> 3)) + return Z_BUF_ERROR; + do { + put = Buf_size - s->bi_valid; + if (put > bits) + put = bits; + s->bi_buf |= (ush)((value & ((1 << put) - 1)) << s->bi_valid); + s->bi_valid += put; + _tr_flush_bits(s); + value >>= put; + bits -= put; + } while (bits); + return Z_OK; +} + +/* ========================================================================= */ +int ZEXPORT deflateParams(strm, level, strategy) + z_streamp strm; + int level; + int strategy; +{ + deflate_state *s; + compress_func func; + + if (deflateStateCheck(strm)) return Z_STREAM_ERROR; + s = strm->state; + +#ifdef FASTEST + if (level != 0) level = 1; +#else + if (level == Z_DEFAULT_COMPRESSION) level = 6; +#endif + if (level < 0 || level > 9 || strategy < 0 || strategy > Z_FIXED) { + return Z_STREAM_ERROR; + } + func = configuration_table[s->level].func; + + if ((strategy != s->strategy || func != configuration_table[level].func) && + s->high_water) { + /* Flush the last buffer: */ + int err = deflate(strm, Z_BLOCK); + if (err == Z_STREAM_ERROR) + return err; + if (strm->avail_out == 0) + return Z_BUF_ERROR; + } + if (s->level != level) { + if (s->level == 0 && s->matches != 0) { + if (s->matches == 1) + slide_hash(s); + else + CLEAR_HASH(s); + s->matches = 0; + } + s->level = level; + s->max_lazy_match = configuration_table[level].max_lazy; + s->good_match = configuration_table[level].good_length; + s->nice_match = configuration_table[level].nice_length; + s->max_chain_length = configuration_table[level].max_chain; + } + s->strategy = strategy; + return Z_OK; +} + +/* ========================================================================= */ +int ZEXPORT deflateTune(strm, good_length, max_lazy, nice_length, max_chain) + z_streamp strm; + int good_length; + int max_lazy; + int nice_length; + int max_chain; +{ + deflate_state *s; + + if (deflateStateCheck(strm)) return Z_STREAM_ERROR; + s = strm->state; + s->good_match = (uInt)good_length; + s->max_lazy_match = (uInt)max_lazy; + s->nice_match = nice_length; + s->max_chain_length = (uInt)max_chain; + return Z_OK; +} + +/* ========================================================================= + * For the default windowBits of 15 and memLevel of 8, this function returns + * a close to exact, as well as small, upper bound on the compressed size. + * They are coded as constants here for a reason--if the #define's are + * changed, then this function needs to be changed as well. The return + * value for 15 and 8 only works for those exact settings. + * + * For any setting other than those defaults for windowBits and memLevel, + * the value returned is a conservative worst case for the maximum expansion + * resulting from using fixed blocks instead of stored blocks, which deflate + * can emit on compressed data for some combinations of the parameters. + * + * This function could be more sophisticated to provide closer upper bounds for + * every combination of windowBits and memLevel. But even the conservative + * upper bound of about 14% expansion does not seem onerous for output buffer + * allocation. + */ +uLong ZEXPORT deflateBound(strm, sourceLen) + z_streamp strm; + uLong sourceLen; +{ + deflate_state *s; + uLong complen, wraplen; + + /* conservative upper bound for compressed data */ + complen = sourceLen + + ((sourceLen + 7) >> 3) + ((sourceLen + 63) >> 6) + 5; + + /* if can't get parameters, return conservative bound plus zlib wrapper */ + if (deflateStateCheck(strm)) + return complen + 6; + + /* compute wrapper length */ + s = strm->state; + switch (s->wrap) { + case 0: /* raw deflate */ + wraplen = 0; + break; + case 1: /* zlib wrapper */ + wraplen = 6 + (s->strstart ? 4 : 0); + break; +#ifdef GZIP + case 2: /* gzip wrapper */ + wraplen = 18; + if (s->gzhead != Z_NULL) { /* user-supplied gzip header */ + Bytef *str; + if (s->gzhead->extra != Z_NULL) + wraplen += 2 + s->gzhead->extra_len; + str = s->gzhead->name; + if (str != Z_NULL) + do { + wraplen++; + } while (*str++); + str = s->gzhead->comment; + if (str != Z_NULL) + do { + wraplen++; + } while (*str++); + if (s->gzhead->hcrc) + wraplen += 2; + } + break; +#endif + default: /* for compiler happiness */ + wraplen = 6; + } + + /* if not default parameters, return conservative bound */ + if (s->w_bits != 15 || s->hash_bits != 8 + 7) + return complen + wraplen; + + /* default settings: return tight bound for that case */ + return sourceLen + (sourceLen >> 12) + (sourceLen >> 14) + + (sourceLen >> 25) + 13 - 6 + wraplen; +} + +/* ========================================================================= + * Put a short in the pending buffer. The 16-bit value is put in MSB order. + * IN assertion: the stream state is correct and there is enough room in + * pending_buf. + */ +local void putShortMSB (s, b) + deflate_state *s; + uInt b; +{ + put_byte(s, (Byte)(b >> 8)); + put_byte(s, (Byte)(b & 0xff)); +} + +/* ========================================================================= + * Flush as much pending output as possible. All deflate() output, except for + * some deflate_stored() output, goes through this function so some + * applications may wish to modify it to avoid allocating a large + * strm->next_out buffer and copying into it. (See also read_buf()). + */ +local void flush_pending(strm) + z_streamp strm; +{ + unsigned len; + deflate_state *s = strm->state; + + _tr_flush_bits(s); + len = s->pending; + if (len > strm->avail_out) len = strm->avail_out; + if (len == 0) return; + + zmemcpy(strm->next_out, s->pending_out, len); + strm->next_out += len; + s->pending_out += len; + strm->total_out += len; + strm->avail_out -= len; + s->pending -= len; + if (s->pending == 0) { + s->pending_out = s->pending_buf; + } +} + +/* =========================================================================== + * Update the header CRC with the bytes s->pending_buf[beg..s->pending - 1]. + */ +#define HCRC_UPDATE(beg) \ + do { \ + if (s->gzhead->hcrc && s->pending > (beg)) \ + strm->adler = crc32(strm->adler, s->pending_buf + (beg), \ + s->pending - (beg)); \ + } while (0) + +/* ========================================================================= */ +int ZEXPORT deflate (strm, flush) + z_streamp strm; + int flush; +{ + int old_flush; /* value of flush param for previous deflate call */ + deflate_state *s; + + if (deflateStateCheck(strm) || flush > Z_BLOCK || flush < 0) { + return Z_STREAM_ERROR; + } + s = strm->state; + + if (strm->next_out == Z_NULL || + (strm->avail_in != 0 && strm->next_in == Z_NULL) || + (s->status == FINISH_STATE && flush != Z_FINISH)) { + ERR_RETURN(strm, Z_STREAM_ERROR); + } + if (strm->avail_out == 0) ERR_RETURN(strm, Z_BUF_ERROR); + + old_flush = s->last_flush; + s->last_flush = flush; + + /* Flush as much pending output as possible */ + if (s->pending != 0) { + flush_pending(strm); + if (strm->avail_out == 0) { + /* Since avail_out is 0, deflate will be called again with + * more output space, but possibly with both pending and + * avail_in equal to zero. There won't be anything to do, + * but this is not an error situation so make sure we + * return OK instead of BUF_ERROR at next call of deflate: + */ + s->last_flush = -1; + return Z_OK; + } + + /* Make sure there is something to do and avoid duplicate consecutive + * flushes. For repeated and useless calls with Z_FINISH, we keep + * returning Z_STREAM_END instead of Z_BUF_ERROR. + */ + } else if (strm->avail_in == 0 && RANK(flush) <= RANK(old_flush) && + flush != Z_FINISH) { + ERR_RETURN(strm, Z_BUF_ERROR); + } + + /* User must not provide more input after the first FINISH: */ + if (s->status == FINISH_STATE && strm->avail_in != 0) { + ERR_RETURN(strm, Z_BUF_ERROR); + } + + /* Write the header */ + if (s->status == INIT_STATE) { + /* zlib header */ + uInt header = (Z_DEFLATED + ((s->w_bits-8)<<4)) << 8; + uInt level_flags; + + if (s->strategy >= Z_HUFFMAN_ONLY || s->level < 2) + level_flags = 0; + else if (s->level < 6) + level_flags = 1; + else if (s->level == 6) + level_flags = 2; + else + level_flags = 3; + header |= (level_flags << 6); + if (s->strstart != 0) header |= PRESET_DICT; + header += 31 - (header % 31); + + putShortMSB(s, header); + + /* Save the adler32 of the preset dictionary: */ + if (s->strstart != 0) { + putShortMSB(s, (uInt)(strm->adler >> 16)); + putShortMSB(s, (uInt)(strm->adler & 0xffff)); + } + strm->adler = adler32(0L, Z_NULL, 0); + s->status = BUSY_STATE; + + /* Compression must start with an empty pending buffer */ + flush_pending(strm); + if (s->pending != 0) { + s->last_flush = -1; + return Z_OK; + } + } +#ifdef GZIP + if (s->status == GZIP_STATE) { + /* gzip header */ + strm->adler = crc32(0L, Z_NULL, 0); + put_byte(s, 31); + put_byte(s, 139); + put_byte(s, 8); + if (s->gzhead == Z_NULL) { + put_byte(s, 0); + put_byte(s, 0); + put_byte(s, 0); + put_byte(s, 0); + put_byte(s, 0); + put_byte(s, s->level == 9 ? 2 : + (s->strategy >= Z_HUFFMAN_ONLY || s->level < 2 ? + 4 : 0)); + put_byte(s, OS_CODE); + s->status = BUSY_STATE; + + /* Compression must start with an empty pending buffer */ + flush_pending(strm); + if (s->pending != 0) { + s->last_flush = -1; + return Z_OK; + } + } + else { + put_byte(s, (s->gzhead->text ? 1 : 0) + + (s->gzhead->hcrc ? 2 : 0) + + (s->gzhead->extra == Z_NULL ? 0 : 4) + + (s->gzhead->name == Z_NULL ? 0 : 8) + + (s->gzhead->comment == Z_NULL ? 0 : 16) + ); + put_byte(s, (Byte)(s->gzhead->time & 0xff)); + put_byte(s, (Byte)((s->gzhead->time >> 8) & 0xff)); + put_byte(s, (Byte)((s->gzhead->time >> 16) & 0xff)); + put_byte(s, (Byte)((s->gzhead->time >> 24) & 0xff)); + put_byte(s, s->level == 9 ? 2 : + (s->strategy >= Z_HUFFMAN_ONLY || s->level < 2 ? + 4 : 0)); + put_byte(s, s->gzhead->os & 0xff); + if (s->gzhead->extra != Z_NULL) { + put_byte(s, s->gzhead->extra_len & 0xff); + put_byte(s, (s->gzhead->extra_len >> 8) & 0xff); + } + if (s->gzhead->hcrc) + strm->adler = crc32(strm->adler, s->pending_buf, + s->pending); + s->gzindex = 0; + s->status = EXTRA_STATE; + } + } + if (s->status == EXTRA_STATE) { + if (s->gzhead->extra != Z_NULL) { + ulg beg = s->pending; /* start of bytes to update crc */ + uInt left = (s->gzhead->extra_len & 0xffff) - s->gzindex; + while (s->pending + left > s->pending_buf_size) { + uInt copy = s->pending_buf_size - s->pending; + zmemcpy(s->pending_buf + s->pending, + s->gzhead->extra + s->gzindex, copy); + s->pending = s->pending_buf_size; + HCRC_UPDATE(beg); + s->gzindex += copy; + flush_pending(strm); + if (s->pending != 0) { + s->last_flush = -1; + return Z_OK; + } + beg = 0; + left -= copy; + } + zmemcpy(s->pending_buf + s->pending, + s->gzhead->extra + s->gzindex, left); + s->pending += left; + HCRC_UPDATE(beg); + s->gzindex = 0; + } + s->status = NAME_STATE; + } + if (s->status == NAME_STATE) { + if (s->gzhead->name != Z_NULL) { + ulg beg = s->pending; /* start of bytes to update crc */ + int val; + do { + if (s->pending == s->pending_buf_size) { + HCRC_UPDATE(beg); + flush_pending(strm); + if (s->pending != 0) { + s->last_flush = -1; + return Z_OK; + } + beg = 0; + } + val = s->gzhead->name[s->gzindex++]; + put_byte(s, val); + } while (val != 0); + HCRC_UPDATE(beg); + s->gzindex = 0; + } + s->status = COMMENT_STATE; + } + if (s->status == COMMENT_STATE) { + if (s->gzhead->comment != Z_NULL) { + ulg beg = s->pending; /* start of bytes to update crc */ + int val; + do { + if (s->pending == s->pending_buf_size) { + HCRC_UPDATE(beg); + flush_pending(strm); + if (s->pending != 0) { + s->last_flush = -1; + return Z_OK; + } + beg = 0; + } + val = s->gzhead->comment[s->gzindex++]; + put_byte(s, val); + } while (val != 0); + HCRC_UPDATE(beg); + } + s->status = HCRC_STATE; + } + if (s->status == HCRC_STATE) { + if (s->gzhead->hcrc) { + if (s->pending + 2 > s->pending_buf_size) { + flush_pending(strm); + if (s->pending != 0) { + s->last_flush = -1; + return Z_OK; + } + } + put_byte(s, (Byte)(strm->adler & 0xff)); + put_byte(s, (Byte)((strm->adler >> 8) & 0xff)); + strm->adler = crc32(0L, Z_NULL, 0); + } + s->status = BUSY_STATE; + + /* Compression must start with an empty pending buffer */ + flush_pending(strm); + if (s->pending != 0) { + s->last_flush = -1; + return Z_OK; + } + } +#endif + + /* Start a new block or continue the current one. + */ + if (strm->avail_in != 0 || s->lookahead != 0 || + (flush != Z_NO_FLUSH && s->status != FINISH_STATE)) { + block_state bstate; + + bstate = s->level == 0 ? deflate_stored(s, flush) : + s->strategy == Z_HUFFMAN_ONLY ? deflate_huff(s, flush) : + s->strategy == Z_RLE ? deflate_rle(s, flush) : + (*(configuration_table[s->level].func))(s, flush); + + if (bstate == finish_started || bstate == finish_done) { + s->status = FINISH_STATE; + } + if (bstate == need_more || bstate == finish_started) { + if (strm->avail_out == 0) { + s->last_flush = -1; /* avoid BUF_ERROR next call, see above */ + } + return Z_OK; + /* If flush != Z_NO_FLUSH && avail_out == 0, the next call + * of deflate should use the same flush parameter to make sure + * that the flush is complete. So we don't have to output an + * empty block here, this will be done at next call. This also + * ensures that for a very small output buffer, we emit at most + * one empty block. + */ + } + if (bstate == block_done) { + if (flush == Z_PARTIAL_FLUSH) { + _tr_align(s); + } else if (flush != Z_BLOCK) { /* FULL_FLUSH or SYNC_FLUSH */ + _tr_stored_block(s, (char*)0, 0L, 0); + /* For a full flush, this empty block will be recognized + * as a special marker by inflate_sync(). + */ + if (flush == Z_FULL_FLUSH) { + CLEAR_HASH(s); /* forget history */ + if (s->lookahead == 0) { + s->strstart = 0; + s->block_start = 0L; + s->insert = 0; + } + } + } + flush_pending(strm); + if (strm->avail_out == 0) { + s->last_flush = -1; /* avoid BUF_ERROR at next call, see above */ + return Z_OK; + } + } + } + + if (flush != Z_FINISH) return Z_OK; + if (s->wrap <= 0) return Z_STREAM_END; + + /* Write the trailer */ +#ifdef GZIP + if (s->wrap == 2) { + put_byte(s, (Byte)(strm->adler & 0xff)); + put_byte(s, (Byte)((strm->adler >> 8) & 0xff)); + put_byte(s, (Byte)((strm->adler >> 16) & 0xff)); + put_byte(s, (Byte)((strm->adler >> 24) & 0xff)); + put_byte(s, (Byte)(strm->total_in & 0xff)); + put_byte(s, (Byte)((strm->total_in >> 8) & 0xff)); + put_byte(s, (Byte)((strm->total_in >> 16) & 0xff)); + put_byte(s, (Byte)((strm->total_in >> 24) & 0xff)); + } + else +#endif + { + putShortMSB(s, (uInt)(strm->adler >> 16)); + putShortMSB(s, (uInt)(strm->adler & 0xffff)); + } + flush_pending(strm); + /* If avail_out is zero, the application will call deflate again + * to flush the rest. + */ + if (s->wrap > 0) s->wrap = -s->wrap; /* write the trailer only once! */ + return s->pending != 0 ? Z_OK : Z_STREAM_END; +} + +/* ========================================================================= */ +int ZEXPORT deflateEnd (strm) + z_streamp strm; +{ + int status; + + if (deflateStateCheck(strm)) return Z_STREAM_ERROR; + + status = strm->state->status; + + /* Deallocate in reverse order of allocations: */ + TRY_FREE(strm, strm->state->pending_buf); + TRY_FREE(strm, strm->state->head); + TRY_FREE(strm, strm->state->prev); + TRY_FREE(strm, strm->state->window); + + ZFREE(strm, strm->state); + strm->state = Z_NULL; + + return status == BUSY_STATE ? Z_DATA_ERROR : Z_OK; +} + +/* ========================================================================= + * Copy the source state to the destination state. + * To simplify the source, this is not supported for 16-bit MSDOS (which + * doesn't have enough memory anyway to duplicate compression states). + */ +int ZEXPORT deflateCopy (dest, source) + z_streamp dest; + z_streamp source; +{ +#ifdef MAXSEG_64K + return Z_STREAM_ERROR; +#else + deflate_state *ds; + deflate_state *ss; + ushf *overlay; + + + if (deflateStateCheck(source) || dest == Z_NULL) { + return Z_STREAM_ERROR; + } + + ss = source->state; + + zmemcpy((voidpf)dest, (voidpf)source, sizeof(z_stream)); + + ds = (deflate_state *) ZALLOC(dest, 1, sizeof(deflate_state)); + if (ds == Z_NULL) return Z_MEM_ERROR; + dest->state = (struct internal_state FAR *) ds; + zmemcpy((voidpf)ds, (voidpf)ss, sizeof(deflate_state)); + ds->strm = dest; + + ds->window = (Bytef *) ZALLOC(dest, ds->w_size, 2*sizeof(Byte)); + ds->prev = (Posf *) ZALLOC(dest, ds->w_size, sizeof(Pos)); + ds->head = (Posf *) ZALLOC(dest, ds->hash_size, sizeof(Pos)); + overlay = (ushf *) ZALLOC(dest, ds->lit_bufsize, sizeof(ush)+2); + ds->pending_buf = (uchf *) overlay; + + if (ds->window == Z_NULL || ds->prev == Z_NULL || ds->head == Z_NULL || + ds->pending_buf == Z_NULL) { + deflateEnd (dest); + return Z_MEM_ERROR; + } + /* following zmemcpy do not work for 16-bit MSDOS */ + zmemcpy(ds->window, ss->window, ds->w_size * 2 * sizeof(Byte)); + zmemcpy((voidpf)ds->prev, (voidpf)ss->prev, ds->w_size * sizeof(Pos)); + zmemcpy((voidpf)ds->head, (voidpf)ss->head, ds->hash_size * sizeof(Pos)); + zmemcpy(ds->pending_buf, ss->pending_buf, (uInt)ds->pending_buf_size); + + ds->pending_out = ds->pending_buf + (ss->pending_out - ss->pending_buf); + ds->d_buf = overlay + ds->lit_bufsize/sizeof(ush); + ds->l_buf = ds->pending_buf + (1+sizeof(ush))*ds->lit_bufsize; + + ds->l_desc.dyn_tree = ds->dyn_ltree; + ds->d_desc.dyn_tree = ds->dyn_dtree; + ds->bl_desc.dyn_tree = ds->bl_tree; + + return Z_OK; +#endif /* MAXSEG_64K */ +} + +/* =========================================================================== + * Read a new buffer from the current input stream, update the adler32 + * and total number of bytes read. All deflate() input goes through + * this function so some applications may wish to modify it to avoid + * allocating a large strm->next_in buffer and copying from it. + * (See also flush_pending()). + */ +local unsigned read_buf(strm, buf, size) + z_streamp strm; + Bytef *buf; + unsigned size; +{ + unsigned len = strm->avail_in; + + if (len > size) len = size; + if (len == 0) return 0; + + strm->avail_in -= len; + + zmemcpy(buf, strm->next_in, len); + if (strm->state->wrap == 1) { + strm->adler = adler32(strm->adler, buf, len); + } +#ifdef GZIP + else if (strm->state->wrap == 2) { + strm->adler = crc32(strm->adler, buf, len); + } +#endif + strm->next_in += len; + strm->total_in += len; + + return len; +} + +/* =========================================================================== + * Initialize the "longest match" routines for a new zlib stream + */ +local void lm_init (s) + deflate_state *s; +{ + s->window_size = (ulg)2L*s->w_size; + + CLEAR_HASH(s); + + /* Set the default configuration parameters: + */ + s->max_lazy_match = configuration_table[s->level].max_lazy; + s->good_match = configuration_table[s->level].good_length; + s->nice_match = configuration_table[s->level].nice_length; + s->max_chain_length = configuration_table[s->level].max_chain; + + s->strstart = 0; + s->block_start = 0L; + s->lookahead = 0; + s->insert = 0; + s->match_length = s->prev_length = MIN_MATCH-1; + s->match_available = 0; + s->ins_h = 0; +#ifndef FASTEST +#ifdef ASMV + match_init(); /* initialize the asm code */ +#endif +#endif +} + +#ifndef FASTEST +/* =========================================================================== + * Set match_start to the longest match starting at the given string and + * return its length. Matches shorter or equal to prev_length are discarded, + * in which case the result is equal to prev_length and match_start is + * garbage. + * IN assertions: cur_match is the head of the hash chain for the current + * string (strstart) and its distance is <= MAX_DIST, and prev_length >= 1 + * OUT assertion: the match length is not greater than s->lookahead. + */ +#ifndef ASMV +/* For 80x86 and 680x0, an optimized version will be provided in match.asm or + * match.S. The code will be functionally equivalent. + */ +local uInt longest_match(s, cur_match) + deflate_state *s; + IPos cur_match; /* current match */ +{ + unsigned chain_length = s->max_chain_length;/* max hash chain length */ + register Bytef *scan = s->window + s->strstart; /* current string */ + register Bytef *match; /* matched string */ + register int len; /* length of current match */ + int best_len = (int)s->prev_length; /* best match length so far */ + int nice_match = s->nice_match; /* stop if match long enough */ + IPos limit = s->strstart > (IPos)MAX_DIST(s) ? + s->strstart - (IPos)MAX_DIST(s) : NIL; + /* Stop when cur_match becomes <= limit. To simplify the code, + * we prevent matches with the string of window index 0. + */ + Posf *prev = s->prev; + uInt wmask = s->w_mask; + +#ifdef UNALIGNED_OK + /* Compare two bytes at a time. Note: this is not always beneficial. + * Try with and without -DUNALIGNED_OK to check. + */ + register Bytef *strend = s->window + s->strstart + MAX_MATCH - 1; + register ush scan_start = *(ushf*)scan; + register ush scan_end = *(ushf*)(scan+best_len-1); +#else + register Bytef *strend = s->window + s->strstart + MAX_MATCH; + register Byte scan_end1 = scan[best_len-1]; + register Byte scan_end = scan[best_len]; +#endif + + /* The code is optimized for HASH_BITS >= 8 and MAX_MATCH-2 multiple of 16. + * It is easy to get rid of this optimization if necessary. + */ + Assert(s->hash_bits >= 8 && MAX_MATCH == 258, "Code too clever"); + + /* Do not waste too much time if we already have a good match: */ + if (s->prev_length >= s->good_match) { + chain_length >>= 2; + } + /* Do not look for matches beyond the end of the input. This is necessary + * to make deflate deterministic. + */ + if ((uInt)nice_match > s->lookahead) nice_match = (int)s->lookahead; + + Assert((ulg)s->strstart <= s->window_size-MIN_LOOKAHEAD, "need lookahead"); + + do { + Assert(cur_match < s->strstart, "no future"); + match = s->window + cur_match; + + /* Skip to next match if the match length cannot increase + * or if the match length is less than 2. Note that the checks below + * for insufficient lookahead only occur occasionally for performance + * reasons. Therefore uninitialized memory will be accessed, and + * conditional jumps will be made that depend on those values. + * However the length of the match is limited to the lookahead, so + * the output of deflate is not affected by the uninitialized values. + */ +#if (defined(UNALIGNED_OK) && MAX_MATCH == 258) + /* This code assumes sizeof(unsigned short) == 2. Do not use + * UNALIGNED_OK if your compiler uses a different size. + */ + if (*(ushf*)(match+best_len-1) != scan_end || + *(ushf*)match != scan_start) continue; + + /* It is not necessary to compare scan[2] and match[2] since they are + * always equal when the other bytes match, given that the hash keys + * are equal and that HASH_BITS >= 8. Compare 2 bytes at a time at + * strstart+3, +5, ... up to strstart+257. We check for insufficient + * lookahead only every 4th comparison; the 128th check will be made + * at strstart+257. If MAX_MATCH-2 is not a multiple of 8, it is + * necessary to put more guard bytes at the end of the window, or + * to check more often for insufficient lookahead. + */ + Assert(scan[2] == match[2], "scan[2]?"); + scan++, match++; + do { + } while (*(ushf*)(scan+=2) == *(ushf*)(match+=2) && + *(ushf*)(scan+=2) == *(ushf*)(match+=2) && + *(ushf*)(scan+=2) == *(ushf*)(match+=2) && + *(ushf*)(scan+=2) == *(ushf*)(match+=2) && + scan < strend); + /* The funny "do {}" generates better code on most compilers */ + + /* Here, scan <= window+strstart+257 */ + Assert(scan <= s->window+(unsigned)(s->window_size-1), "wild scan"); + if (*scan == *match) scan++; + + len = (MAX_MATCH - 1) - (int)(strend-scan); + scan = strend - (MAX_MATCH-1); + +#else /* UNALIGNED_OK */ + + if (match[best_len] != scan_end || + match[best_len-1] != scan_end1 || + *match != *scan || + *++match != scan[1]) continue; + + /* The check at best_len-1 can be removed because it will be made + * again later. (This heuristic is not always a win.) + * It is not necessary to compare scan[2] and match[2] since they + * are always equal when the other bytes match, given that + * the hash keys are equal and that HASH_BITS >= 8. + */ + scan += 2, match++; + Assert(*scan == *match, "match[2]?"); + + /* We check for insufficient lookahead only every 8th comparison; + * the 256th check will be made at strstart+258. + */ + do { + } while (*++scan == *++match && *++scan == *++match && + *++scan == *++match && *++scan == *++match && + *++scan == *++match && *++scan == *++match && + *++scan == *++match && *++scan == *++match && + scan < strend); + + Assert(scan <= s->window+(unsigned)(s->window_size-1), "wild scan"); + + len = MAX_MATCH - (int)(strend - scan); + scan = strend - MAX_MATCH; + +#endif /* UNALIGNED_OK */ + + if (len > best_len) { + s->match_start = cur_match; + best_len = len; + if (len >= nice_match) break; +#ifdef UNALIGNED_OK + scan_end = *(ushf*)(scan+best_len-1); +#else + scan_end1 = scan[best_len-1]; + scan_end = scan[best_len]; +#endif + } + } while ((cur_match = prev[cur_match & wmask]) > limit + && --chain_length != 0); + + if ((uInt)best_len <= s->lookahead) return (uInt)best_len; + return s->lookahead; +} +#endif /* ASMV */ + +#else /* FASTEST */ + +/* --------------------------------------------------------------------------- + * Optimized version for FASTEST only + */ +local uInt longest_match(s, cur_match) + deflate_state *s; + IPos cur_match; /* current match */ +{ + register Bytef *scan = s->window + s->strstart; /* current string */ + register Bytef *match; /* matched string */ + register int len; /* length of current match */ + register Bytef *strend = s->window + s->strstart + MAX_MATCH; + + /* The code is optimized for HASH_BITS >= 8 and MAX_MATCH-2 multiple of 16. + * It is easy to get rid of this optimization if necessary. + */ + Assert(s->hash_bits >= 8 && MAX_MATCH == 258, "Code too clever"); + + Assert((ulg)s->strstart <= s->window_size-MIN_LOOKAHEAD, "need lookahead"); + + Assert(cur_match < s->strstart, "no future"); + + match = s->window + cur_match; + + /* Return failure if the match length is less than 2: + */ + if (match[0] != scan[0] || match[1] != scan[1]) return MIN_MATCH-1; + + /* The check at best_len-1 can be removed because it will be made + * again later. (This heuristic is not always a win.) + * It is not necessary to compare scan[2] and match[2] since they + * are always equal when the other bytes match, given that + * the hash keys are equal and that HASH_BITS >= 8. + */ + scan += 2, match += 2; + Assert(*scan == *match, "match[2]?"); + + /* We check for insufficient lookahead only every 8th comparison; + * the 256th check will be made at strstart+258. + */ + do { + } while (*++scan == *++match && *++scan == *++match && + *++scan == *++match && *++scan == *++match && + *++scan == *++match && *++scan == *++match && + *++scan == *++match && *++scan == *++match && + scan < strend); + + Assert(scan <= s->window+(unsigned)(s->window_size-1), "wild scan"); + + len = MAX_MATCH - (int)(strend - scan); + + if (len < MIN_MATCH) return MIN_MATCH - 1; + + s->match_start = cur_match; + return (uInt)len <= s->lookahead ? (uInt)len : s->lookahead; +} + +#endif /* FASTEST */ + +#ifdef ZLIB_DEBUG + +#define EQUAL 0 +/* result of memcmp for equal strings */ + +/* =========================================================================== + * Check that the match at match_start is indeed a match. + */ +local void check_match(s, start, match, length) + deflate_state *s; + IPos start, match; + int length; +{ + /* check that the match is indeed a match */ + if (zmemcmp(s->window + match, + s->window + start, length) != EQUAL) { + fprintf(stderr, " start %u, match %u, length %d\n", + start, match, length); + do { + fprintf(stderr, "%c%c", s->window[match++], s->window[start++]); + } while (--length != 0); + z_error("invalid match"); + } + if (z_verbose > 1) { + fprintf(stderr,"\\[%d,%d]", start-match, length); + do { putc(s->window[start++], stderr); } while (--length != 0); + } +} +#else +# define check_match(s, start, match, length) +#endif /* ZLIB_DEBUG */ + +/* =========================================================================== + * Fill the window when the lookahead becomes insufficient. + * Updates strstart and lookahead. + * + * IN assertion: lookahead < MIN_LOOKAHEAD + * OUT assertions: strstart <= window_size-MIN_LOOKAHEAD + * At least one byte has been read, or avail_in == 0; reads are + * performed for at least two bytes (required for the zip translate_eol + * option -- not supported here). + */ +local void fill_window(s) + deflate_state *s; +{ + unsigned n; + unsigned more; /* Amount of free space at the end of the window. */ + uInt wsize = s->w_size; + + Assert(s->lookahead < MIN_LOOKAHEAD, "already enough lookahead"); + + do { + more = (unsigned)(s->window_size -(ulg)s->lookahead -(ulg)s->strstart); + + /* Deal with !@#$% 64K limit: */ + if (sizeof(int) <= 2) { + if (more == 0 && s->strstart == 0 && s->lookahead == 0) { + more = wsize; + + } else if (more == (unsigned)(-1)) { + /* Very unlikely, but possible on 16 bit machine if + * strstart == 0 && lookahead == 1 (input done a byte at time) + */ + more--; + } + } + + /* If the window is almost full and there is insufficient lookahead, + * move the upper half to the lower one to make room in the upper half. + */ + if (s->strstart >= wsize+MAX_DIST(s)) { + + zmemcpy(s->window, s->window+wsize, (unsigned)wsize - more); + s->match_start -= wsize; + s->strstart -= wsize; /* we now have strstart >= MAX_DIST */ + s->block_start -= (long) wsize; + slide_hash(s); + more += wsize; + } + if (s->strm->avail_in == 0) break; + + /* If there was no sliding: + * strstart <= WSIZE+MAX_DIST-1 && lookahead <= MIN_LOOKAHEAD - 1 && + * more == window_size - lookahead - strstart + * => more >= window_size - (MIN_LOOKAHEAD-1 + WSIZE + MAX_DIST-1) + * => more >= window_size - 2*WSIZE + 2 + * In the BIG_MEM or MMAP case (not yet supported), + * window_size == input_size + MIN_LOOKAHEAD && + * strstart + s->lookahead <= input_size => more >= MIN_LOOKAHEAD. + * Otherwise, window_size == 2*WSIZE so more >= 2. + * If there was sliding, more >= WSIZE. So in all cases, more >= 2. + */ + Assert(more >= 2, "more < 2"); + + n = read_buf(s->strm, s->window + s->strstart + s->lookahead, more); + s->lookahead += n; + + /* Initialize the hash value now that we have some input: */ + if (s->lookahead + s->insert >= MIN_MATCH) { + uInt str = s->strstart - s->insert; + s->ins_h = s->window[str]; + UPDATE_HASH(s, s->ins_h, s->window[str + 1]); +#if MIN_MATCH != 3 + Call UPDATE_HASH() MIN_MATCH-3 more times +#endif + while (s->insert) { + UPDATE_HASH(s, s->ins_h, s->window[str + MIN_MATCH-1]); +#ifndef FASTEST + s->prev[str & s->w_mask] = s->head[s->ins_h]; +#endif + s->head[s->ins_h] = (Pos)str; + str++; + s->insert--; + if (s->lookahead + s->insert < MIN_MATCH) + break; + } + } + /* If the whole input has less than MIN_MATCH bytes, ins_h is garbage, + * but this is not important since only literal bytes will be emitted. + */ + + } while (s->lookahead < MIN_LOOKAHEAD && s->strm->avail_in != 0); + + /* If the WIN_INIT bytes after the end of the current data have never been + * written, then zero those bytes in order to avoid memory check reports of + * the use of uninitialized (or uninitialised as Julian writes) bytes by + * the longest match routines. Update the high water mark for the next + * time through here. WIN_INIT is set to MAX_MATCH since the longest match + * routines allow scanning to strstart + MAX_MATCH, ignoring lookahead. + */ + if (s->high_water < s->window_size) { + ulg curr = s->strstart + (ulg)(s->lookahead); + ulg init; + + if (s->high_water < curr) { + /* Previous high water mark below current data -- zero WIN_INIT + * bytes or up to end of window, whichever is less. + */ + init = s->window_size - curr; + if (init > WIN_INIT) + init = WIN_INIT; + zmemzero(s->window + curr, (unsigned)init); + s->high_water = curr + init; + } + else if (s->high_water < (ulg)curr + WIN_INIT) { + /* High water mark at or above current data, but below current data + * plus WIN_INIT -- zero out to current data plus WIN_INIT, or up + * to end of window, whichever is less. + */ + init = (ulg)curr + WIN_INIT - s->high_water; + if (init > s->window_size - s->high_water) + init = s->window_size - s->high_water; + zmemzero(s->window + s->high_water, (unsigned)init); + s->high_water += init; + } + } + + Assert((ulg)s->strstart <= s->window_size - MIN_LOOKAHEAD, + "not enough room for search"); +} + +/* =========================================================================== + * Flush the current block, with given end-of-file flag. + * IN assertion: strstart is set to the end of the current match. + */ +#define FLUSH_BLOCK_ONLY(s, last) { \ + _tr_flush_block(s, (s->block_start >= 0L ? \ + (charf *)&s->window[(unsigned)s->block_start] : \ + (charf *)Z_NULL), \ + (ulg)((long)s->strstart - s->block_start), \ + (last)); \ + s->block_start = s->strstart; \ + flush_pending(s->strm); \ + Tracev((stderr,"[FLUSH]")); \ +} + +/* Same but force premature exit if necessary. */ +#define FLUSH_BLOCK(s, last) { \ + FLUSH_BLOCK_ONLY(s, last); \ + if (s->strm->avail_out == 0) return (last) ? finish_started : need_more; \ +} + +/* Maximum stored block length in deflate format (not including header). */ +#define MAX_STORED 65535 + +/* Minimum of a and b. */ +#define MIN(a, b) ((a) > (b) ? (b) : (a)) + +/* =========================================================================== + * Copy without compression as much as possible from the input stream, return + * the current block state. + * + * In case deflateParams() is used to later switch to a non-zero compression + * level, s->matches (otherwise unused when storing) keeps track of the number + * of hash table slides to perform. If s->matches is 1, then one hash table + * slide will be done when switching. If s->matches is 2, the maximum value + * allowed here, then the hash table will be cleared, since two or more slides + * is the same as a clear. + * + * deflate_stored() is written to minimize the number of times an input byte is + * copied. It is most efficient with large input and output buffers, which + * maximizes the opportunites to have a single copy from next_in to next_out. + */ +local block_state deflate_stored(s, flush) + deflate_state *s; + int flush; +{ + /* Smallest worthy block size when not flushing or finishing. By default + * this is 32K. This can be as small as 507 bytes for memLevel == 1. For + * large input and output buffers, the stored block size will be larger. + */ + unsigned min_block = MIN(s->pending_buf_size - 5, s->w_size); + + /* Copy as many min_block or larger stored blocks directly to next_out as + * possible. If flushing, copy the remaining available input to next_out as + * stored blocks, if there is enough space. + */ + unsigned len, left, have, last = 0; + unsigned used = s->strm->avail_in; + do { + /* Set len to the maximum size block that we can copy directly with the + * available input data and output space. Set left to how much of that + * would be copied from what's left in the window. + */ + len = MAX_STORED; /* maximum deflate stored block length */ + have = (s->bi_valid + 42) >> 3; /* number of header bytes */ + if (s->strm->avail_out < have) /* need room for header */ + break; + /* maximum stored block length that will fit in avail_out: */ + have = s->strm->avail_out - have; + left = s->strstart - s->block_start; /* bytes left in window */ + if (len > (ulg)left + s->strm->avail_in) + len = left + s->strm->avail_in; /* limit len to the input */ + if (len > have) + len = have; /* limit len to the output */ + + /* If the stored block would be less than min_block in length, or if + * unable to copy all of the available input when flushing, then try + * copying to the window and the pending buffer instead. Also don't + * write an empty block when flushing -- deflate() does that. + */ + if (len < min_block && ((len == 0 && flush != Z_FINISH) || + flush == Z_NO_FLUSH || + len != left + s->strm->avail_in)) + break; + + /* Make a dummy stored block in pending to get the header bytes, + * including any pending bits. This also updates the debugging counts. + */ + last = flush == Z_FINISH && len == left + s->strm->avail_in ? 1 : 0; + _tr_stored_block(s, (char *)0, 0L, last); + + /* Replace the lengths in the dummy stored block with len. */ + s->pending_buf[s->pending - 4] = len; + s->pending_buf[s->pending - 3] = len >> 8; + s->pending_buf[s->pending - 2] = ~len; + s->pending_buf[s->pending - 1] = ~len >> 8; + + /* Write the stored block header bytes. */ + flush_pending(s->strm); + +#ifdef ZLIB_DEBUG + /* Update debugging counts for the data about to be copied. */ + s->compressed_len += len << 3; + s->bits_sent += len << 3; +#endif + + /* Copy uncompressed bytes from the window to next_out. */ + if (left) { + if (left > len) + left = len; + zmemcpy(s->strm->next_out, s->window + s->block_start, left); + s->strm->next_out += left; + s->strm->avail_out -= left; + s->strm->total_out += left; + s->block_start += left; + len -= left; + } + + /* Copy uncompressed bytes directly from next_in to next_out, updating + * the check value. + */ + if (len) { + read_buf(s->strm, s->strm->next_out, len); + s->strm->next_out += len; + s->strm->avail_out -= len; + s->strm->total_out += len; + } + } while (last == 0); + + /* Update the sliding window with the last s->w_size bytes of the copied + * data, or append all of the copied data to the existing window if less + * than s->w_size bytes were copied. Also update the number of bytes to + * insert in the hash tables, in the event that deflateParams() switches to + * a non-zero compression level. + */ + used -= s->strm->avail_in; /* number of input bytes directly copied */ + if (used) { + /* If any input was used, then no unused input remains in the window, + * therefore s->block_start == s->strstart. + */ + if (used >= s->w_size) { /* supplant the previous history */ + s->matches = 2; /* clear hash */ + zmemcpy(s->window, s->strm->next_in - s->w_size, s->w_size); + s->strstart = s->w_size; + } + else { + if (s->window_size - s->strstart <= used) { + /* Slide the window down. */ + s->strstart -= s->w_size; + zmemcpy(s->window, s->window + s->w_size, s->strstart); + if (s->matches < 2) + s->matches++; /* add a pending slide_hash() */ + } + zmemcpy(s->window + s->strstart, s->strm->next_in - used, used); + s->strstart += used; + } + s->block_start = s->strstart; + s->insert += MIN(used, s->w_size - s->insert); + } + if (s->high_water < s->strstart) + s->high_water = s->strstart; + + /* If the last block was written to next_out, then done. */ + if (last) + return finish_done; + + /* If flushing and all input has been consumed, then done. */ + if (flush != Z_NO_FLUSH && flush != Z_FINISH && + s->strm->avail_in == 0 && (long)s->strstart == s->block_start) + return block_done; + + /* Fill the window with any remaining input. */ + have = s->window_size - s->strstart - 1; + if (s->strm->avail_in > have && s->block_start >= (long)s->w_size) { + /* Slide the window down. */ + s->block_start -= s->w_size; + s->strstart -= s->w_size; + zmemcpy(s->window, s->window + s->w_size, s->strstart); + if (s->matches < 2) + s->matches++; /* add a pending slide_hash() */ + have += s->w_size; /* more space now */ + } + if (have > s->strm->avail_in) + have = s->strm->avail_in; + if (have) { + read_buf(s->strm, s->window + s->strstart, have); + s->strstart += have; + } + if (s->high_water < s->strstart) + s->high_water = s->strstart; + + /* There was not enough avail_out to write a complete worthy or flushed + * stored block to next_out. Write a stored block to pending instead, if we + * have enough input for a worthy block, or if flushing and there is enough + * room for the remaining input as a stored block in the pending buffer. + */ + have = (s->bi_valid + 42) >> 3; /* number of header bytes */ + /* maximum stored block length that will fit in pending: */ + have = MIN(s->pending_buf_size - have, MAX_STORED); + min_block = MIN(have, s->w_size); + left = s->strstart - s->block_start; + if (left >= min_block || + ((left || flush == Z_FINISH) && flush != Z_NO_FLUSH && + s->strm->avail_in == 0 && left <= have)) { + len = MIN(left, have); + last = flush == Z_FINISH && s->strm->avail_in == 0 && + len == left ? 1 : 0; + _tr_stored_block(s, (charf *)s->window + s->block_start, len, last); + s->block_start += len; + flush_pending(s->strm); + } + + /* We've done all we can with the available input and output. */ + return last ? finish_started : need_more; +} + +/* =========================================================================== + * Compress as much as possible from the input stream, return the current + * block state. + * This function does not perform lazy evaluation of matches and inserts + * new strings in the dictionary only for unmatched strings or for short + * matches. It is used only for the fast compression options. + */ +local block_state deflate_fast(s, flush) + deflate_state *s; + int flush; +{ + IPos hash_head; /* head of the hash chain */ + int bflush; /* set if current block must be flushed */ + + for (;;) { + /* Make sure that we always have enough lookahead, except + * at the end of the input file. We need MAX_MATCH bytes + * for the next match, plus MIN_MATCH bytes to insert the + * string following the next match. + */ + if (s->lookahead < MIN_LOOKAHEAD) { + fill_window(s); + if (s->lookahead < MIN_LOOKAHEAD && flush == Z_NO_FLUSH) { + return need_more; + } + if (s->lookahead == 0) break; /* flush the current block */ + } + + /* Insert the string window[strstart .. strstart+2] in the + * dictionary, and set hash_head to the head of the hash chain: + */ + hash_head = NIL; + if (s->lookahead >= MIN_MATCH) { + INSERT_STRING(s, s->strstart, hash_head); + } + + /* Find the longest match, discarding those <= prev_length. + * At this point we have always match_length < MIN_MATCH + */ + if (hash_head != NIL && s->strstart - hash_head <= MAX_DIST(s)) { + /* To simplify the code, we prevent matches with the string + * of window index 0 (in particular we have to avoid a match + * of the string with itself at the start of the input file). + */ + s->match_length = longest_match (s, hash_head); + /* longest_match() sets match_start */ + } + if (s->match_length >= MIN_MATCH) { + check_match(s, s->strstart, s->match_start, s->match_length); + + _tr_tally_dist(s, s->strstart - s->match_start, + s->match_length - MIN_MATCH, bflush); + + s->lookahead -= s->match_length; + + /* Insert new strings in the hash table only if the match length + * is not too large. This saves time but degrades compression. + */ +#ifndef FASTEST + if (s->match_length <= s->max_insert_length && + s->lookahead >= MIN_MATCH) { + s->match_length--; /* string at strstart already in table */ + do { + s->strstart++; + INSERT_STRING(s, s->strstart, hash_head); + /* strstart never exceeds WSIZE-MAX_MATCH, so there are + * always MIN_MATCH bytes ahead. + */ + } while (--s->match_length != 0); + s->strstart++; + } else +#endif + { + s->strstart += s->match_length; + s->match_length = 0; + s->ins_h = s->window[s->strstart]; + UPDATE_HASH(s, s->ins_h, s->window[s->strstart+1]); +#if MIN_MATCH != 3 + Call UPDATE_HASH() MIN_MATCH-3 more times +#endif + /* If lookahead < MIN_MATCH, ins_h is garbage, but it does not + * matter since it will be recomputed at next deflate call. + */ + } + } else { + /* No match, output a literal byte */ + Tracevv((stderr,"%c", s->window[s->strstart])); + _tr_tally_lit (s, s->window[s->strstart], bflush); + s->lookahead--; + s->strstart++; + } + if (bflush) FLUSH_BLOCK(s, 0); + } + s->insert = s->strstart < MIN_MATCH-1 ? s->strstart : MIN_MATCH-1; + if (flush == Z_FINISH) { + FLUSH_BLOCK(s, 1); + return finish_done; + } + if (s->last_lit) + FLUSH_BLOCK(s, 0); + return block_done; +} + +#ifndef FASTEST +/* =========================================================================== + * Same as above, but achieves better compression. We use a lazy + * evaluation for matches: a match is finally adopted only if there is + * no better match at the next window position. + */ +local block_state deflate_slow(s, flush) + deflate_state *s; + int flush; +{ + IPos hash_head; /* head of hash chain */ + int bflush; /* set if current block must be flushed */ + + /* Process the input block. */ + for (;;) { + /* Make sure that we always have enough lookahead, except + * at the end of the input file. We need MAX_MATCH bytes + * for the next match, plus MIN_MATCH bytes to insert the + * string following the next match. + */ + if (s->lookahead < MIN_LOOKAHEAD) { + fill_window(s); + if (s->lookahead < MIN_LOOKAHEAD && flush == Z_NO_FLUSH) { + return need_more; + } + if (s->lookahead == 0) break; /* flush the current block */ + } + + /* Insert the string window[strstart .. strstart+2] in the + * dictionary, and set hash_head to the head of the hash chain: + */ + hash_head = NIL; + if (s->lookahead >= MIN_MATCH) { + INSERT_STRING(s, s->strstart, hash_head); + } + + /* Find the longest match, discarding those <= prev_length. + */ + s->prev_length = s->match_length, s->prev_match = s->match_start; + s->match_length = MIN_MATCH-1; + + if (hash_head != NIL && s->prev_length < s->max_lazy_match && + s->strstart - hash_head <= MAX_DIST(s)) { + /* To simplify the code, we prevent matches with the string + * of window index 0 (in particular we have to avoid a match + * of the string with itself at the start of the input file). + */ + s->match_length = longest_match (s, hash_head); + /* longest_match() sets match_start */ + + if (s->match_length <= 5 && (s->strategy == Z_FILTERED +#if TOO_FAR <= 32767 + || (s->match_length == MIN_MATCH && + s->strstart - s->match_start > TOO_FAR) +#endif + )) { + + /* If prev_match is also MIN_MATCH, match_start is garbage + * but we will ignore the current match anyway. + */ + s->match_length = MIN_MATCH-1; + } + } + /* If there was a match at the previous step and the current + * match is not better, output the previous match: + */ + if (s->prev_length >= MIN_MATCH && s->match_length <= s->prev_length) { + uInt max_insert = s->strstart + s->lookahead - MIN_MATCH; + /* Do not insert strings in hash table beyond this. */ + + check_match(s, s->strstart-1, s->prev_match, s->prev_length); + + _tr_tally_dist(s, s->strstart -1 - s->prev_match, + s->prev_length - MIN_MATCH, bflush); + + /* Insert in hash table all strings up to the end of the match. + * strstart-1 and strstart are already inserted. If there is not + * enough lookahead, the last two strings are not inserted in + * the hash table. + */ + s->lookahead -= s->prev_length-1; + s->prev_length -= 2; + do { + if (++s->strstart <= max_insert) { + INSERT_STRING(s, s->strstart, hash_head); + } + } while (--s->prev_length != 0); + s->match_available = 0; + s->match_length = MIN_MATCH-1; + s->strstart++; + + if (bflush) FLUSH_BLOCK(s, 0); + + } else if (s->match_available) { + /* If there was no match at the previous position, output a + * single literal. If there was a match but the current match + * is longer, truncate the previous match to a single literal. + */ + Tracevv((stderr,"%c", s->window[s->strstart-1])); + _tr_tally_lit(s, s->window[s->strstart-1], bflush); + if (bflush) { + FLUSH_BLOCK_ONLY(s, 0); + } + s->strstart++; + s->lookahead--; + if (s->strm->avail_out == 0) return need_more; + } else { + /* There is no previous match to compare with, wait for + * the next step to decide. + */ + s->match_available = 1; + s->strstart++; + s->lookahead--; + } + } + Assert (flush != Z_NO_FLUSH, "no flush?"); + if (s->match_available) { + Tracevv((stderr,"%c", s->window[s->strstart-1])); + _tr_tally_lit(s, s->window[s->strstart-1], bflush); + s->match_available = 0; + } + s->insert = s->strstart < MIN_MATCH-1 ? s->strstart : MIN_MATCH-1; + if (flush == Z_FINISH) { + FLUSH_BLOCK(s, 1); + return finish_done; + } + if (s->last_lit) + FLUSH_BLOCK(s, 0); + return block_done; +} +#endif /* FASTEST */ + +/* =========================================================================== + * For Z_RLE, simply look for runs of bytes, generate matches only of distance + * one. Do not maintain a hash table. (It will be regenerated if this run of + * deflate switches away from Z_RLE.) + */ +local block_state deflate_rle(s, flush) + deflate_state *s; + int flush; +{ + int bflush; /* set if current block must be flushed */ + uInt prev; /* byte at distance one to match */ + Bytef *scan, *strend; /* scan goes up to strend for length of run */ + + for (;;) { + /* Make sure that we always have enough lookahead, except + * at the end of the input file. We need MAX_MATCH bytes + * for the longest run, plus one for the unrolled loop. + */ + if (s->lookahead <= MAX_MATCH) { + fill_window(s); + if (s->lookahead <= MAX_MATCH && flush == Z_NO_FLUSH) { + return need_more; + } + if (s->lookahead == 0) break; /* flush the current block */ + } + + /* See how many times the previous byte repeats */ + s->match_length = 0; + if (s->lookahead >= MIN_MATCH && s->strstart > 0) { + scan = s->window + s->strstart - 1; + prev = *scan; + if (prev == *++scan && prev == *++scan && prev == *++scan) { + strend = s->window + s->strstart + MAX_MATCH; + do { + } while (prev == *++scan && prev == *++scan && + prev == *++scan && prev == *++scan && + prev == *++scan && prev == *++scan && + prev == *++scan && prev == *++scan && + scan < strend); + s->match_length = MAX_MATCH - (uInt)(strend - scan); + if (s->match_length > s->lookahead) + s->match_length = s->lookahead; + } + Assert(scan <= s->window+(uInt)(s->window_size-1), "wild scan"); + } + + /* Emit match if have run of MIN_MATCH or longer, else emit literal */ + if (s->match_length >= MIN_MATCH) { + check_match(s, s->strstart, s->strstart - 1, s->match_length); + + _tr_tally_dist(s, 1, s->match_length - MIN_MATCH, bflush); + + s->lookahead -= s->match_length; + s->strstart += s->match_length; + s->match_length = 0; + } else { + /* No match, output a literal byte */ + Tracevv((stderr,"%c", s->window[s->strstart])); + _tr_tally_lit (s, s->window[s->strstart], bflush); + s->lookahead--; + s->strstart++; + } + if (bflush) FLUSH_BLOCK(s, 0); + } + s->insert = 0; + if (flush == Z_FINISH) { + FLUSH_BLOCK(s, 1); + return finish_done; + } + if (s->last_lit) + FLUSH_BLOCK(s, 0); + return block_done; +} + +/* =========================================================================== + * For Z_HUFFMAN_ONLY, do not look for matches. Do not maintain a hash table. + * (It will be regenerated if this run of deflate switches away from Huffman.) + */ +local block_state deflate_huff(s, flush) + deflate_state *s; + int flush; +{ + int bflush; /* set if current block must be flushed */ + + for (;;) { + /* Make sure that we have a literal to write. */ + if (s->lookahead == 0) { + fill_window(s); + if (s->lookahead == 0) { + if (flush == Z_NO_FLUSH) + return need_more; + break; /* flush the current block */ + } + } + + /* Output a literal byte */ + s->match_length = 0; + Tracevv((stderr,"%c", s->window[s->strstart])); + _tr_tally_lit (s, s->window[s->strstart], bflush); + s->lookahead--; + s->strstart++; + if (bflush) FLUSH_BLOCK(s, 0); + } + s->insert = 0; + if (flush == Z_FINISH) { + FLUSH_BLOCK(s, 1); + return finish_done; + } + if (s->last_lit) + FLUSH_BLOCK(s, 0); + return block_done; +} ADDED compat/zlib/deflate.h Index: compat/zlib/deflate.h ================================================================== --- /dev/null +++ compat/zlib/deflate.h @@ -0,0 +1,349 @@ +/* deflate.h -- internal compression state + * Copyright (C) 1995-2016 Jean-loup Gailly + * For conditions of distribution and use, see copyright notice in zlib.h + */ + +/* WARNING: this file should *not* be used by applications. It is + part of the implementation of the compression library and is + subject to change. Applications should only use zlib.h. + */ + +/* @(#) $Id$ */ + +#ifndef DEFLATE_H +#define DEFLATE_H + +#include "zutil.h" + +/* define NO_GZIP when compiling if you want to disable gzip header and + trailer creation by deflate(). NO_GZIP would be used to avoid linking in + the crc code when it is not needed. For shared libraries, gzip encoding + should be left enabled. */ +#ifndef NO_GZIP +# define GZIP +#endif + +/* =========================================================================== + * Internal compression state. + */ + +#define LENGTH_CODES 29 +/* number of length codes, not counting the special END_BLOCK code */ + +#define LITERALS 256 +/* number of literal bytes 0..255 */ + +#define L_CODES (LITERALS+1+LENGTH_CODES) +/* number of Literal or Length codes, including the END_BLOCK code */ + +#define D_CODES 30 +/* number of distance codes */ + +#define BL_CODES 19 +/* number of codes used to transfer the bit lengths */ + +#define HEAP_SIZE (2*L_CODES+1) +/* maximum heap size */ + +#define MAX_BITS 15 +/* All codes must not exceed MAX_BITS bits */ + +#define Buf_size 16 +/* size of bit buffer in bi_buf */ + +#define INIT_STATE 42 /* zlib header -> BUSY_STATE */ +#ifdef GZIP +# define GZIP_STATE 57 /* gzip header -> BUSY_STATE | EXTRA_STATE */ +#endif +#define EXTRA_STATE 69 /* gzip extra block -> NAME_STATE */ +#define NAME_STATE 73 /* gzip file name -> COMMENT_STATE */ +#define COMMENT_STATE 91 /* gzip comment -> HCRC_STATE */ +#define HCRC_STATE 103 /* gzip header CRC -> BUSY_STATE */ +#define BUSY_STATE 113 /* deflate -> FINISH_STATE */ +#define FINISH_STATE 666 /* stream complete */ +/* Stream status */ + + +/* Data structure describing a single value and its code string. */ +typedef struct ct_data_s { + union { + ush freq; /* frequency count */ + ush code; /* bit string */ + } fc; + union { + ush dad; /* father node in Huffman tree */ + ush len; /* length of bit string */ + } dl; +} FAR ct_data; + +#define Freq fc.freq +#define Code fc.code +#define Dad dl.dad +#define Len dl.len + +typedef struct static_tree_desc_s static_tree_desc; + +typedef struct tree_desc_s { + ct_data *dyn_tree; /* the dynamic tree */ + int max_code; /* largest code with non zero frequency */ + const static_tree_desc *stat_desc; /* the corresponding static tree */ +} FAR tree_desc; + +typedef ush Pos; +typedef Pos FAR Posf; +typedef unsigned IPos; + +/* A Pos is an index in the character window. We use short instead of int to + * save space in the various tables. IPos is used only for parameter passing. + */ + +typedef struct internal_state { + z_streamp strm; /* pointer back to this zlib stream */ + int status; /* as the name implies */ + Bytef *pending_buf; /* output still pending */ + ulg pending_buf_size; /* size of pending_buf */ + Bytef *pending_out; /* next pending byte to output to the stream */ + ulg pending; /* nb of bytes in the pending buffer */ + int wrap; /* bit 0 true for zlib, bit 1 true for gzip */ + gz_headerp gzhead; /* gzip header information to write */ + ulg gzindex; /* where in extra, name, or comment */ + Byte method; /* can only be DEFLATED */ + int last_flush; /* value of flush param for previous deflate call */ + + /* used by deflate.c: */ + + uInt w_size; /* LZ77 window size (32K by default) */ + uInt w_bits; /* log2(w_size) (8..16) */ + uInt w_mask; /* w_size - 1 */ + + Bytef *window; + /* Sliding window. Input bytes are read into the second half of the window, + * and move to the first half later to keep a dictionary of at least wSize + * bytes. With this organization, matches are limited to a distance of + * wSize-MAX_MATCH bytes, but this ensures that IO is always + * performed with a length multiple of the block size. Also, it limits + * the window size to 64K, which is quite useful on MSDOS. + * To do: use the user input buffer as sliding window. + */ + + ulg window_size; + /* Actual size of window: 2*wSize, except when the user input buffer + * is directly used as sliding window. + */ + + Posf *prev; + /* Link to older string with same hash index. To limit the size of this + * array to 64K, this link is maintained only for the last 32K strings. + * An index in this array is thus a window index modulo 32K. + */ + + Posf *head; /* Heads of the hash chains or NIL. */ + + uInt ins_h; /* hash index of string to be inserted */ + uInt hash_size; /* number of elements in hash table */ + uInt hash_bits; /* log2(hash_size) */ + uInt hash_mask; /* hash_size-1 */ + + uInt hash_shift; + /* Number of bits by which ins_h must be shifted at each input + * step. It must be such that after MIN_MATCH steps, the oldest + * byte no longer takes part in the hash key, that is: + * hash_shift * MIN_MATCH >= hash_bits + */ + + long block_start; + /* Window position at the beginning of the current output block. Gets + * negative when the window is moved backwards. + */ + + uInt match_length; /* length of best match */ + IPos prev_match; /* previous match */ + int match_available; /* set if previous match exists */ + uInt strstart; /* start of string to insert */ + uInt match_start; /* start of matching string */ + uInt lookahead; /* number of valid bytes ahead in window */ + + uInt prev_length; + /* Length of the best match at previous step. Matches not greater than this + * are discarded. This is used in the lazy match evaluation. + */ + + uInt max_chain_length; + /* To speed up deflation, hash chains are never searched beyond this + * length. A higher limit improves compression ratio but degrades the + * speed. + */ + + uInt max_lazy_match; + /* Attempt to find a better match only when the current match is strictly + * smaller than this value. This mechanism is used only for compression + * levels >= 4. + */ +# define max_insert_length max_lazy_match + /* Insert new strings in the hash table only if the match length is not + * greater than this length. This saves time but degrades compression. + * max_insert_length is used only for compression levels <= 3. + */ + + int level; /* compression level (1..9) */ + int strategy; /* favor or force Huffman coding*/ + + uInt good_match; + /* Use a faster search when the previous match is longer than this */ + + int nice_match; /* Stop searching when current match exceeds this */ + + /* used by trees.c: */ + /* Didn't use ct_data typedef below to suppress compiler warning */ + struct ct_data_s dyn_ltree[HEAP_SIZE]; /* literal and length tree */ + struct ct_data_s dyn_dtree[2*D_CODES+1]; /* distance tree */ + struct ct_data_s bl_tree[2*BL_CODES+1]; /* Huffman tree for bit lengths */ + + struct tree_desc_s l_desc; /* desc. for literal tree */ + struct tree_desc_s d_desc; /* desc. for distance tree */ + struct tree_desc_s bl_desc; /* desc. for bit length tree */ + + ush bl_count[MAX_BITS+1]; + /* number of codes at each bit length for an optimal tree */ + + int heap[2*L_CODES+1]; /* heap used to build the Huffman trees */ + int heap_len; /* number of elements in the heap */ + int heap_max; /* element of largest frequency */ + /* The sons of heap[n] are heap[2*n] and heap[2*n+1]. heap[0] is not used. + * The same heap array is used to build all trees. + */ + + uch depth[2*L_CODES+1]; + /* Depth of each subtree used as tie breaker for trees of equal frequency + */ + + uchf *l_buf; /* buffer for literals or lengths */ + + uInt lit_bufsize; + /* Size of match buffer for literals/lengths. There are 4 reasons for + * limiting lit_bufsize to 64K: + * - frequencies can be kept in 16 bit counters + * - if compression is not successful for the first block, all input + * data is still in the window so we can still emit a stored block even + * when input comes from standard input. (This can also be done for + * all blocks if lit_bufsize is not greater than 32K.) + * - if compression is not successful for a file smaller than 64K, we can + * even emit a stored file instead of a stored block (saving 5 bytes). + * This is applicable only for zip (not gzip or zlib). + * - creating new Huffman trees less frequently may not provide fast + * adaptation to changes in the input data statistics. (Take for + * example a binary file with poorly compressible code followed by + * a highly compressible string table.) Smaller buffer sizes give + * fast adaptation but have of course the overhead of transmitting + * trees more frequently. + * - I can't count above 4 + */ + + uInt last_lit; /* running index in l_buf */ + + ushf *d_buf; + /* Buffer for distances. To simplify the code, d_buf and l_buf have + * the same number of elements. To use different lengths, an extra flag + * array would be necessary. + */ + + ulg opt_len; /* bit length of current block with optimal trees */ + ulg static_len; /* bit length of current block with static trees */ + uInt matches; /* number of string matches in current block */ + uInt insert; /* bytes at end of window left to insert */ + +#ifdef ZLIB_DEBUG + ulg compressed_len; /* total bit length of compressed file mod 2^32 */ + ulg bits_sent; /* bit length of compressed data sent mod 2^32 */ +#endif + + ush bi_buf; + /* Output buffer. bits are inserted starting at the bottom (least + * significant bits). + */ + int bi_valid; + /* Number of valid bits in bi_buf. All bits above the last valid bit + * are always zero. + */ + + ulg high_water; + /* High water mark offset in window for initialized bytes -- bytes above + * this are set to zero in order to avoid memory check warnings when + * longest match routines access bytes past the input. This is then + * updated to the new high water mark. + */ + +} FAR deflate_state; + +/* Output a byte on the stream. + * IN assertion: there is enough room in pending_buf. + */ +#define put_byte(s, c) {s->pending_buf[s->pending++] = (Bytef)(c);} + + +#define MIN_LOOKAHEAD (MAX_MATCH+MIN_MATCH+1) +/* Minimum amount of lookahead, except at the end of the input file. + * See deflate.c for comments about the MIN_MATCH+1. + */ + +#define MAX_DIST(s) ((s)->w_size-MIN_LOOKAHEAD) +/* In order to simplify the code, particularly on 16 bit machines, match + * distances are limited to MAX_DIST instead of WSIZE. + */ + +#define WIN_INIT MAX_MATCH +/* Number of bytes after end of data in window to initialize in order to avoid + memory checker errors from longest match routines */ + + /* in trees.c */ +void ZLIB_INTERNAL _tr_init OF((deflate_state *s)); +int ZLIB_INTERNAL _tr_tally OF((deflate_state *s, unsigned dist, unsigned lc)); +void ZLIB_INTERNAL _tr_flush_block OF((deflate_state *s, charf *buf, + ulg stored_len, int last)); +void ZLIB_INTERNAL _tr_flush_bits OF((deflate_state *s)); +void ZLIB_INTERNAL _tr_align OF((deflate_state *s)); +void ZLIB_INTERNAL _tr_stored_block OF((deflate_state *s, charf *buf, + ulg stored_len, int last)); + +#define d_code(dist) \ + ((dist) < 256 ? _dist_code[dist] : _dist_code[256+((dist)>>7)]) +/* Mapping from a distance to a distance code. dist is the distance - 1 and + * must not have side effects. _dist_code[256] and _dist_code[257] are never + * used. + */ + +#ifndef ZLIB_DEBUG +/* Inline versions of _tr_tally for speed: */ + +#if defined(GEN_TREES_H) || !defined(STDC) + extern uch ZLIB_INTERNAL _length_code[]; + extern uch ZLIB_INTERNAL _dist_code[]; +#else + extern const uch ZLIB_INTERNAL _length_code[]; + extern const uch ZLIB_INTERNAL _dist_code[]; +#endif + +# define _tr_tally_lit(s, c, flush) \ + { uch cc = (c); \ + s->d_buf[s->last_lit] = 0; \ + s->l_buf[s->last_lit++] = cc; \ + s->dyn_ltree[cc].Freq++; \ + flush = (s->last_lit == s->lit_bufsize-1); \ + } +# define _tr_tally_dist(s, distance, length, flush) \ + { uch len = (uch)(length); \ + ush dist = (ush)(distance); \ + s->d_buf[s->last_lit] = dist; \ + s->l_buf[s->last_lit++] = len; \ + dist--; \ + s->dyn_ltree[_length_code[len]+LITERALS+1].Freq++; \ + s->dyn_dtree[d_code(dist)].Freq++; \ + flush = (s->last_lit == s->lit_bufsize-1); \ + } +#else +# define _tr_tally_lit(s, c, flush) flush = _tr_tally(s, 0, c) +# define _tr_tally_dist(s, distance, length, flush) \ + flush = _tr_tally(s, distance, length) +#endif + +#endif /* DEFLATE_H */ ADDED compat/zlib/examples/README.examples Index: compat/zlib/examples/README.examples ================================================================== --- /dev/null +++ compat/zlib/examples/README.examples @@ -0,0 +1,49 @@ +This directory contains examples of the use of zlib and other relevant +programs and documentation. + +enough.c + calculation and justification of ENOUGH parameter in inftrees.h + - calculates the maximum table space used in inflate tree + construction over all possible Huffman codes + +fitblk.c + compress just enough input to nearly fill a requested output size + - zlib isn't designed to do this, but fitblk does it anyway + +gun.c + uncompress a gzip file + - illustrates the use of inflateBack() for high speed file-to-file + decompression using call-back functions + - is approximately twice as fast as gzip -d + - also provides Unix uncompress functionality, again twice as fast + +gzappend.c + append to a gzip file + - illustrates the use of the Z_BLOCK flush parameter for inflate() + - illustrates the use of deflatePrime() to start at any bit + +gzjoin.c + join gzip files without recalculating the crc or recompressing + - illustrates the use of the Z_BLOCK flush parameter for inflate() + - illustrates the use of crc32_combine() + +gzlog.c +gzlog.h + efficiently and robustly maintain a message log file in gzip format + - illustrates use of raw deflate, Z_PARTIAL_FLUSH, deflatePrime(), + and deflateSetDictionary() + - illustrates use of a gzip header extra field + +zlib_how.html + painfully comprehensive description of zpipe.c (see below) + - describes in excruciating detail the use of deflate() and inflate() + +zpipe.c + reads and writes zlib streams from stdin to stdout + - illustrates the proper use of deflate() and inflate() + - deeply commented in zlib_how.html (see above) + +zran.c + index a zlib or gzip stream and randomly access it + - illustrates the use of Z_BLOCK, inflatePrime(), and + inflateSetDictionary() to provide random access ADDED compat/zlib/examples/enough.c Index: compat/zlib/examples/enough.c ================================================================== --- /dev/null +++ compat/zlib/examples/enough.c @@ -0,0 +1,572 @@ +/* enough.c -- determine the maximum size of inflate's Huffman code tables over + * all possible valid and complete Huffman codes, subject to a length limit. + * Copyright (C) 2007, 2008, 2012 Mark Adler + * Version 1.4 18 August 2012 Mark Adler + */ + +/* Version history: + 1.0 3 Jan 2007 First version (derived from codecount.c version 1.4) + 1.1 4 Jan 2007 Use faster incremental table usage computation + Prune examine() search on previously visited states + 1.2 5 Jan 2007 Comments clean up + As inflate does, decrease root for short codes + Refuse cases where inflate would increase root + 1.3 17 Feb 2008 Add argument for initial root table size + Fix bug for initial root table size == max - 1 + Use a macro to compute the history index + 1.4 18 Aug 2012 Avoid shifts more than bits in type (caused endless loop!) + Clean up comparisons of different types + Clean up code indentation + */ + +/* + Examine all possible Huffman codes for a given number of symbols and a + maximum code length in bits to determine the maximum table size for zilb's + inflate. Only complete Huffman codes are counted. + + Two codes are considered distinct if the vectors of the number of codes per + length are not identical. So permutations of the symbol assignments result + in the same code for the counting, as do permutations of the assignments of + the bit values to the codes (i.e. only canonical codes are counted). + + We build a code from shorter to longer lengths, determining how many symbols + are coded at each length. At each step, we have how many symbols remain to + be coded, what the last code length used was, and how many bit patterns of + that length remain unused. Then we add one to the code length and double the + number of unused patterns to graduate to the next code length. We then + assign all portions of the remaining symbols to that code length that + preserve the properties of a correct and eventually complete code. Those + properties are: we cannot use more bit patterns than are available; and when + all the symbols are used, there are exactly zero possible bit patterns + remaining. + + The inflate Huffman decoding algorithm uses two-level lookup tables for + speed. There is a single first-level table to decode codes up to root bits + in length (root == 9 in the current inflate implementation). The table + has 1 << root entries and is indexed by the next root bits of input. Codes + shorter than root bits have replicated table entries, so that the correct + entry is pointed to regardless of the bits that follow the short code. If + the code is longer than root bits, then the table entry points to a second- + level table. The size of that table is determined by the longest code with + that root-bit prefix. If that longest code has length len, then the table + has size 1 << (len - root), to index the remaining bits in that set of + codes. Each subsequent root-bit prefix then has its own sub-table. The + total number of table entries required by the code is calculated + incrementally as the number of codes at each bit length is populated. When + all of the codes are shorter than root bits, then root is reduced to the + longest code length, resulting in a single, smaller, one-level table. + + The inflate algorithm also provides for small values of root (relative to + the log2 of the number of symbols), where the shortest code has more bits + than root. In that case, root is increased to the length of the shortest + code. This program, by design, does not handle that case, so it is verified + that the number of symbols is less than 2^(root + 1). + + In order to speed up the examination (by about ten orders of magnitude for + the default arguments), the intermediate states in the build-up of a code + are remembered and previously visited branches are pruned. The memory + required for this will increase rapidly with the total number of symbols and + the maximum code length in bits. However this is a very small price to pay + for the vast speedup. + + First, all of the possible Huffman codes are counted, and reachable + intermediate states are noted by a non-zero count in a saved-results array. + Second, the intermediate states that lead to (root + 1) bit or longer codes + are used to look at all sub-codes from those junctures for their inflate + memory usage. (The amount of memory used is not affected by the number of + codes of root bits or less in length.) Third, the visited states in the + construction of those sub-codes and the associated calculation of the table + size is recalled in order to avoid recalculating from the same juncture. + Beginning the code examination at (root + 1) bit codes, which is enabled by + identifying the reachable nodes, accounts for about six of the orders of + magnitude of improvement for the default arguments. About another four + orders of magnitude come from not revisiting previous states. Out of + approximately 2x10^16 possible Huffman codes, only about 2x10^6 sub-codes + need to be examined to cover all of the possible table memory usage cases + for the default arguments of 286 symbols limited to 15-bit codes. + + Note that an unsigned long long type is used for counting. It is quite easy + to exceed the capacity of an eight-byte integer with a large number of + symbols and a large maximum code length, so multiple-precision arithmetic + would need to replace the unsigned long long arithmetic in that case. This + program will abort if an overflow occurs. The big_t type identifies where + the counting takes place. + + An unsigned long long type is also used for calculating the number of + possible codes remaining at the maximum length. This limits the maximum + code length to the number of bits in a long long minus the number of bits + needed to represent the symbols in a flat code. The code_t type identifies + where the bit pattern counting takes place. + */ + +#include +#include +#include +#include + +#define local static + +/* special data types */ +typedef unsigned long long big_t; /* type for code counting */ +typedef unsigned long long code_t; /* type for bit pattern counting */ +struct tab { /* type for been here check */ + size_t len; /* length of bit vector in char's */ + char *vec; /* allocated bit vector */ +}; + +/* The array for saving results, num[], is indexed with this triplet: + + syms: number of symbols remaining to code + left: number of available bit patterns at length len + len: number of bits in the codes currently being assigned + + Those indices are constrained thusly when saving results: + + syms: 3..totsym (totsym == total symbols to code) + left: 2..syms - 1, but only the evens (so syms == 8 -> 2, 4, 6) + len: 1..max - 1 (max == maximum code length in bits) + + syms == 2 is not saved since that immediately leads to a single code. left + must be even, since it represents the number of available bit patterns at + the current length, which is double the number at the previous length. + left ends at syms-1 since left == syms immediately results in a single code. + (left > sym is not allowed since that would result in an incomplete code.) + len is less than max, since the code completes immediately when len == max. + + The offset into the array is calculated for the three indices with the + first one (syms) being outermost, and the last one (len) being innermost. + We build the array with length max-1 lists for the len index, with syms-3 + of those for each symbol. There are totsym-2 of those, with each one + varying in length as a function of sym. See the calculation of index in + count() for the index, and the calculation of size in main() for the size + of the array. + + For the deflate example of 286 symbols limited to 15-bit codes, the array + has 284,284 entries, taking up 2.17 MB for an 8-byte big_t. More than + half of the space allocated for saved results is actually used -- not all + possible triplets are reached in the generation of valid Huffman codes. + */ + +/* The array for tracking visited states, done[], is itself indexed identically + to the num[] array as described above for the (syms, left, len) triplet. + Each element in the array is further indexed by the (mem, rem) doublet, + where mem is the amount of inflate table space used so far, and rem is the + remaining unused entries in the current inflate sub-table. Each indexed + element is simply one bit indicating whether the state has been visited or + not. Since the ranges for mem and rem are not known a priori, each bit + vector is of a variable size, and grows as needed to accommodate the visited + states. mem and rem are used to calculate a single index in a triangular + array. Since the range of mem is expected in the default case to be about + ten times larger than the range of rem, the array is skewed to reduce the + memory usage, with eight times the range for mem than for rem. See the + calculations for offset and bit in beenhere() for the details. + + For the deflate example of 286 symbols limited to 15-bit codes, the bit + vectors grow to total approximately 21 MB, in addition to the 4.3 MB done[] + array itself. + */ + +/* Globals to avoid propagating constants or constant pointers recursively */ +local int max; /* maximum allowed bit length for the codes */ +local int root; /* size of base code table in bits */ +local int large; /* largest code table so far */ +local size_t size; /* number of elements in num and done */ +local int *code; /* number of symbols assigned to each bit length */ +local big_t *num; /* saved results array for code counting */ +local struct tab *done; /* states already evaluated array */ + +/* Index function for num[] and done[] */ +#define INDEX(i,j,k) (((size_t)((i-1)>>1)*((i-2)>>1)+(j>>1)-1)*(max-1)+k-1) + +/* Free allocated space. Uses globals code, num, and done. */ +local void cleanup(void) +{ + size_t n; + + if (done != NULL) { + for (n = 0; n < size; n++) + if (done[n].len) + free(done[n].vec); + free(done); + } + if (num != NULL) + free(num); + if (code != NULL) + free(code); +} + +/* Return the number of possible Huffman codes using bit patterns of lengths + len through max inclusive, coding syms symbols, with left bit patterns of + length len unused -- return -1 if there is an overflow in the counting. + Keep a record of previous results in num to prevent repeating the same + calculation. Uses the globals max and num. */ +local big_t count(int syms, int len, int left) +{ + big_t sum; /* number of possible codes from this juncture */ + big_t got; /* value returned from count() */ + int least; /* least number of syms to use at this juncture */ + int most; /* most number of syms to use at this juncture */ + int use; /* number of bit patterns to use in next call */ + size_t index; /* index of this case in *num */ + + /* see if only one possible code */ + if (syms == left) + return 1; + + /* note and verify the expected state */ + assert(syms > left && left > 0 && len < max); + + /* see if we've done this one already */ + index = INDEX(syms, left, len); + got = num[index]; + if (got) + return got; /* we have -- return the saved result */ + + /* we need to use at least this many bit patterns so that the code won't be + incomplete at the next length (more bit patterns than symbols) */ + least = (left << 1) - syms; + if (least < 0) + least = 0; + + /* we can use at most this many bit patterns, lest there not be enough + available for the remaining symbols at the maximum length (if there were + no limit to the code length, this would become: most = left - 1) */ + most = (((code_t)left << (max - len)) - syms) / + (((code_t)1 << (max - len)) - 1); + + /* count all possible codes from this juncture and add them up */ + sum = 0; + for (use = least; use <= most; use++) { + got = count(syms - use, len + 1, (left - use) << 1); + sum += got; + if (got == (big_t)0 - 1 || sum < got) /* overflow */ + return (big_t)0 - 1; + } + + /* verify that all recursive calls are productive */ + assert(sum != 0); + + /* save the result and return it */ + num[index] = sum; + return sum; +} + +/* Return true if we've been here before, set to true if not. Set a bit in a + bit vector to indicate visiting this state. Each (syms,len,left) state + has a variable size bit vector indexed by (mem,rem). The bit vector is + lengthened if needed to allow setting the (mem,rem) bit. */ +local int beenhere(int syms, int len, int left, int mem, int rem) +{ + size_t index; /* index for this state's bit vector */ + size_t offset; /* offset in this state's bit vector */ + int bit; /* mask for this state's bit */ + size_t length; /* length of the bit vector in bytes */ + char *vector; /* new or enlarged bit vector */ + + /* point to vector for (syms,left,len), bit in vector for (mem,rem) */ + index = INDEX(syms, left, len); + mem -= 1 << root; + offset = (mem >> 3) + rem; + offset = ((offset * (offset + 1)) >> 1) + rem; + bit = 1 << (mem & 7); + + /* see if we've been here */ + length = done[index].len; + if (offset < length && (done[index].vec[offset] & bit) != 0) + return 1; /* done this! */ + + /* we haven't been here before -- set the bit to show we have now */ + + /* see if we need to lengthen the vector in order to set the bit */ + if (length <= offset) { + /* if we have one already, enlarge it, zero out the appended space */ + if (length) { + do { + length <<= 1; + } while (length <= offset); + vector = realloc(done[index].vec, length); + if (vector != NULL) + memset(vector + done[index].len, 0, length - done[index].len); + } + + /* otherwise we need to make a new vector and zero it out */ + else { + length = 1 << (len - root); + while (length <= offset) + length <<= 1; + vector = calloc(length, sizeof(char)); + } + + /* in either case, bail if we can't get the memory */ + if (vector == NULL) { + fputs("abort: unable to allocate enough memory\n", stderr); + cleanup(); + exit(1); + } + + /* install the new vector */ + done[index].len = length; + done[index].vec = vector; + } + + /* set the bit */ + done[index].vec[offset] |= bit; + return 0; +} + +/* Examine all possible codes from the given node (syms, len, left). Compute + the amount of memory required to build inflate's decoding tables, where the + number of code structures used so far is mem, and the number remaining in + the current sub-table is rem. Uses the globals max, code, root, large, and + done. */ +local void examine(int syms, int len, int left, int mem, int rem) +{ + int least; /* least number of syms to use at this juncture */ + int most; /* most number of syms to use at this juncture */ + int use; /* number of bit patterns to use in next call */ + + /* see if we have a complete code */ + if (syms == left) { + /* set the last code entry */ + code[len] = left; + + /* complete computation of memory used by this code */ + while (rem < left) { + left -= rem; + rem = 1 << (len - root); + mem += rem; + } + assert(rem == left); + + /* if this is a new maximum, show the entries used and the sub-code */ + if (mem > large) { + large = mem; + printf("max %d: ", mem); + for (use = root + 1; use <= max; use++) + if (code[use]) + printf("%d[%d] ", code[use], use); + putchar('\n'); + fflush(stdout); + } + + /* remove entries as we drop back down in the recursion */ + code[len] = 0; + return; + } + + /* prune the tree if we can */ + if (beenhere(syms, len, left, mem, rem)) + return; + + /* we need to use at least this many bit patterns so that the code won't be + incomplete at the next length (more bit patterns than symbols) */ + least = (left << 1) - syms; + if (least < 0) + least = 0; + + /* we can use at most this many bit patterns, lest there not be enough + available for the remaining symbols at the maximum length (if there were + no limit to the code length, this would become: most = left - 1) */ + most = (((code_t)left << (max - len)) - syms) / + (((code_t)1 << (max - len)) - 1); + + /* occupy least table spaces, creating new sub-tables as needed */ + use = least; + while (rem < use) { + use -= rem; + rem = 1 << (len - root); + mem += rem; + } + rem -= use; + + /* examine codes from here, updating table space as we go */ + for (use = least; use <= most; use++) { + code[len] = use; + examine(syms - use, len + 1, (left - use) << 1, + mem + (rem ? 1 << (len - root) : 0), rem << 1); + if (rem == 0) { + rem = 1 << (len - root); + mem += rem; + } + rem--; + } + + /* remove entries as we drop back down in the recursion */ + code[len] = 0; +} + +/* Look at all sub-codes starting with root + 1 bits. Look at only the valid + intermediate code states (syms, left, len). For each completed code, + calculate the amount of memory required by inflate to build the decoding + tables. Find the maximum amount of memory required and show the code that + requires that maximum. Uses the globals max, root, and num. */ +local void enough(int syms) +{ + int n; /* number of remaing symbols for this node */ + int left; /* number of unused bit patterns at this length */ + size_t index; /* index of this case in *num */ + + /* clear code */ + for (n = 0; n <= max; n++) + code[n] = 0; + + /* look at all (root + 1) bit and longer codes */ + large = 1 << root; /* base table */ + if (root < max) /* otherwise, there's only a base table */ + for (n = 3; n <= syms; n++) + for (left = 2; left < n; left += 2) + { + /* look at all reachable (root + 1) bit nodes, and the + resulting codes (complete at root + 2 or more) */ + index = INDEX(n, left, root + 1); + if (root + 1 < max && num[index]) /* reachable node */ + examine(n, root + 1, left, 1 << root, 0); + + /* also look at root bit codes with completions at root + 1 + bits (not saved in num, since complete), just in case */ + if (num[index - 1] && n <= left << 1) + examine((n - left) << 1, root + 1, (n - left) << 1, + 1 << root, 0); + } + + /* done */ + printf("done: maximum of %d table entries\n", large); +} + +/* + Examine and show the total number of possible Huffman codes for a given + maximum number of symbols, initial root table size, and maximum code length + in bits -- those are the command arguments in that order. The default + values are 286, 9, and 15 respectively, for the deflate literal/length code. + The possible codes are counted for each number of coded symbols from two to + the maximum. The counts for each of those and the total number of codes are + shown. The maximum number of inflate table entires is then calculated + across all possible codes. Each new maximum number of table entries and the + associated sub-code (starting at root + 1 == 10 bits) is shown. + + To count and examine Huffman codes that are not length-limited, provide a + maximum length equal to the number of symbols minus one. + + For the deflate literal/length code, use "enough". For the deflate distance + code, use "enough 30 6". + + This uses the %llu printf format to print big_t numbers, which assumes that + big_t is an unsigned long long. If the big_t type is changed (for example + to a multiple precision type), the method of printing will also need to be + updated. + */ +int main(int argc, char **argv) +{ + int syms; /* total number of symbols to code */ + int n; /* number of symbols to code for this run */ + big_t got; /* return value of count() */ + big_t sum; /* accumulated number of codes over n */ + code_t word; /* for counting bits in code_t */ + + /* set up globals for cleanup() */ + code = NULL; + num = NULL; + done = NULL; + + /* get arguments -- default to the deflate literal/length code */ + syms = 286; + root = 9; + max = 15; + if (argc > 1) { + syms = atoi(argv[1]); + if (argc > 2) { + root = atoi(argv[2]); + if (argc > 3) + max = atoi(argv[3]); + } + } + if (argc > 4 || syms < 2 || root < 1 || max < 1) { + fputs("invalid arguments, need: [sym >= 2 [root >= 1 [max >= 1]]]\n", + stderr); + return 1; + } + + /* if not restricting the code length, the longest is syms - 1 */ + if (max > syms - 1) + max = syms - 1; + + /* determine the number of bits in a code_t */ + for (n = 0, word = 1; word; n++, word <<= 1) + ; + + /* make sure that the calculation of most will not overflow */ + if (max > n || (code_t)(syms - 2) >= (((code_t)0 - 1) >> (max - 1))) { + fputs("abort: code length too long for internal types\n", stderr); + return 1; + } + + /* reject impossible code requests */ + if ((code_t)(syms - 1) > ((code_t)1 << max) - 1) { + fprintf(stderr, "%d symbols cannot be coded in %d bits\n", + syms, max); + return 1; + } + + /* allocate code vector */ + code = calloc(max + 1, sizeof(int)); + if (code == NULL) { + fputs("abort: unable to allocate enough memory\n", stderr); + return 1; + } + + /* determine size of saved results array, checking for overflows, + allocate and clear the array (set all to zero with calloc()) */ + if (syms == 2) /* iff max == 1 */ + num = NULL; /* won't be saving any results */ + else { + size = syms >> 1; + if (size > ((size_t)0 - 1) / (n = (syms - 1) >> 1) || + (size *= n, size > ((size_t)0 - 1) / (n = max - 1)) || + (size *= n, size > ((size_t)0 - 1) / sizeof(big_t)) || + (num = calloc(size, sizeof(big_t))) == NULL) { + fputs("abort: unable to allocate enough memory\n", stderr); + cleanup(); + return 1; + } + } + + /* count possible codes for all numbers of symbols, add up counts */ + sum = 0; + for (n = 2; n <= syms; n++) { + got = count(n, 1, 2); + sum += got; + if (got == (big_t)0 - 1 || sum < got) { /* overflow */ + fputs("abort: can't count that high!\n", stderr); + cleanup(); + return 1; + } + printf("%llu %d-codes\n", got, n); + } + printf("%llu total codes for 2 to %d symbols", sum, syms); + if (max < syms - 1) + printf(" (%d-bit length limit)\n", max); + else + puts(" (no length limit)"); + + /* allocate and clear done array for beenhere() */ + if (syms == 2) + done = NULL; + else if (size > ((size_t)0 - 1) / sizeof(struct tab) || + (done = calloc(size, sizeof(struct tab))) == NULL) { + fputs("abort: unable to allocate enough memory\n", stderr); + cleanup(); + return 1; + } + + /* find and show maximum inflate table usage */ + if (root > max) /* reduce root to max length */ + root = max; + if ((code_t)syms < ((code_t)1 << (root + 1))) + enough(syms); + else + puts("cannot handle minimum code lengths > root"); + + /* done */ + cleanup(); + return 0; +} ADDED compat/zlib/examples/fitblk.c Index: compat/zlib/examples/fitblk.c ================================================================== --- /dev/null +++ compat/zlib/examples/fitblk.c @@ -0,0 +1,233 @@ +/* fitblk.c: example of fitting compressed output to a specified size + Not copyrighted -- provided to the public domain + Version 1.1 25 November 2004 Mark Adler */ + +/* Version history: + 1.0 24 Nov 2004 First version + 1.1 25 Nov 2004 Change deflateInit2() to deflateInit() + Use fixed-size, stack-allocated raw buffers + Simplify code moving compression to subroutines + Use assert() for internal errors + Add detailed description of approach + */ + +/* Approach to just fitting a requested compressed size: + + fitblk performs three compression passes on a portion of the input + data in order to determine how much of that input will compress to + nearly the requested output block size. The first pass generates + enough deflate blocks to produce output to fill the requested + output size plus a specfied excess amount (see the EXCESS define + below). The last deflate block may go quite a bit past that, but + is discarded. The second pass decompresses and recompresses just + the compressed data that fit in the requested plus excess sized + buffer. The deflate process is terminated after that amount of + input, which is less than the amount consumed on the first pass. + The last deflate block of the result will be of a comparable size + to the final product, so that the header for that deflate block and + the compression ratio for that block will be about the same as in + the final product. The third compression pass decompresses the + result of the second step, but only the compressed data up to the + requested size minus an amount to allow the compressed stream to + complete (see the MARGIN define below). That will result in a + final compressed stream whose length is less than or equal to the + requested size. Assuming sufficient input and a requested size + greater than a few hundred bytes, the shortfall will typically be + less than ten bytes. + + If the input is short enough that the first compression completes + before filling the requested output size, then that compressed + stream is return with no recompression. + + EXCESS is chosen to be just greater than the shortfall seen in a + two pass approach similar to the above. That shortfall is due to + the last deflate block compressing more efficiently with a smaller + header on the second pass. EXCESS is set to be large enough so + that there is enough uncompressed data for the second pass to fill + out the requested size, and small enough so that the final deflate + block of the second pass will be close in size to the final deflate + block of the third and final pass. MARGIN is chosen to be just + large enough to assure that the final compression has enough room + to complete in all cases. + */ + +#include +#include +#include +#include "zlib.h" + +#define local static + +/* print nastygram and leave */ +local void quit(char *why) +{ + fprintf(stderr, "fitblk abort: %s\n", why); + exit(1); +} + +#define RAWLEN 4096 /* intermediate uncompressed buffer size */ + +/* compress from file to def until provided buffer is full or end of + input reached; return last deflate() return value, or Z_ERRNO if + there was read error on the file */ +local int partcompress(FILE *in, z_streamp def) +{ + int ret, flush; + unsigned char raw[RAWLEN]; + + flush = Z_NO_FLUSH; + do { + def->avail_in = fread(raw, 1, RAWLEN, in); + if (ferror(in)) + return Z_ERRNO; + def->next_in = raw; + if (feof(in)) + flush = Z_FINISH; + ret = deflate(def, flush); + assert(ret != Z_STREAM_ERROR); + } while (def->avail_out != 0 && flush == Z_NO_FLUSH); + return ret; +} + +/* recompress from inf's input to def's output; the input for inf and + the output for def are set in those structures before calling; + return last deflate() return value, or Z_MEM_ERROR if inflate() + was not able to allocate enough memory when it needed to */ +local int recompress(z_streamp inf, z_streamp def) +{ + int ret, flush; + unsigned char raw[RAWLEN]; + + flush = Z_NO_FLUSH; + do { + /* decompress */ + inf->avail_out = RAWLEN; + inf->next_out = raw; + ret = inflate(inf, Z_NO_FLUSH); + assert(ret != Z_STREAM_ERROR && ret != Z_DATA_ERROR && + ret != Z_NEED_DICT); + if (ret == Z_MEM_ERROR) + return ret; + + /* compress what was decompresed until done or no room */ + def->avail_in = RAWLEN - inf->avail_out; + def->next_in = raw; + if (inf->avail_out != 0) + flush = Z_FINISH; + ret = deflate(def, flush); + assert(ret != Z_STREAM_ERROR); + } while (ret != Z_STREAM_END && def->avail_out != 0); + return ret; +} + +#define EXCESS 256 /* empirically determined stream overage */ +#define MARGIN 8 /* amount to back off for completion */ + +/* compress from stdin to fixed-size block on stdout */ +int main(int argc, char **argv) +{ + int ret; /* return code */ + unsigned size; /* requested fixed output block size */ + unsigned have; /* bytes written by deflate() call */ + unsigned char *blk; /* intermediate and final stream */ + unsigned char *tmp; /* close to desired size stream */ + z_stream def, inf; /* zlib deflate and inflate states */ + + /* get requested output size */ + if (argc != 2) + quit("need one argument: size of output block"); + ret = strtol(argv[1], argv + 1, 10); + if (argv[1][0] != 0) + quit("argument must be a number"); + if (ret < 8) /* 8 is minimum zlib stream size */ + quit("need positive size of 8 or greater"); + size = (unsigned)ret; + + /* allocate memory for buffers and compression engine */ + blk = malloc(size + EXCESS); + def.zalloc = Z_NULL; + def.zfree = Z_NULL; + def.opaque = Z_NULL; + ret = deflateInit(&def, Z_DEFAULT_COMPRESSION); + if (ret != Z_OK || blk == NULL) + quit("out of memory"); + + /* compress from stdin until output full, or no more input */ + def.avail_out = size + EXCESS; + def.next_out = blk; + ret = partcompress(stdin, &def); + if (ret == Z_ERRNO) + quit("error reading input"); + + /* if it all fit, then size was undersubscribed -- done! */ + if (ret == Z_STREAM_END && def.avail_out >= EXCESS) { + /* write block to stdout */ + have = size + EXCESS - def.avail_out; + if (fwrite(blk, 1, have, stdout) != have || ferror(stdout)) + quit("error writing output"); + + /* clean up and print results to stderr */ + ret = deflateEnd(&def); + assert(ret != Z_STREAM_ERROR); + free(blk); + fprintf(stderr, + "%u bytes unused out of %u requested (all input)\n", + size - have, size); + return 0; + } + + /* it didn't all fit -- set up for recompression */ + inf.zalloc = Z_NULL; + inf.zfree = Z_NULL; + inf.opaque = Z_NULL; + inf.avail_in = 0; + inf.next_in = Z_NULL; + ret = inflateInit(&inf); + tmp = malloc(size + EXCESS); + if (ret != Z_OK || tmp == NULL) + quit("out of memory"); + ret = deflateReset(&def); + assert(ret != Z_STREAM_ERROR); + + /* do first recompression close to the right amount */ + inf.avail_in = size + EXCESS; + inf.next_in = blk; + def.avail_out = size + EXCESS; + def.next_out = tmp; + ret = recompress(&inf, &def); + if (ret == Z_MEM_ERROR) + quit("out of memory"); + + /* set up for next reocmpression */ + ret = inflateReset(&inf); + assert(ret != Z_STREAM_ERROR); + ret = deflateReset(&def); + assert(ret != Z_STREAM_ERROR); + + /* do second and final recompression (third compression) */ + inf.avail_in = size - MARGIN; /* assure stream will complete */ + inf.next_in = tmp; + def.avail_out = size; + def.next_out = blk; + ret = recompress(&inf, &def); + if (ret == Z_MEM_ERROR) + quit("out of memory"); + assert(ret == Z_STREAM_END); /* otherwise MARGIN too small */ + + /* done -- write block to stdout */ + have = size - def.avail_out; + if (fwrite(blk, 1, have, stdout) != have || ferror(stdout)) + quit("error writing output"); + + /* clean up and print results to stderr */ + free(tmp); + ret = inflateEnd(&inf); + assert(ret != Z_STREAM_ERROR); + ret = deflateEnd(&def); + assert(ret != Z_STREAM_ERROR); + free(blk); + fprintf(stderr, + "%u bytes unused out of %u requested (%lu input)\n", + size - have, size, def.total_in); + return 0; +} ADDED compat/zlib/examples/gun.c Index: compat/zlib/examples/gun.c ================================================================== --- /dev/null +++ compat/zlib/examples/gun.c @@ -0,0 +1,702 @@ +/* gun.c -- simple gunzip to give an example of the use of inflateBack() + * Copyright (C) 2003, 2005, 2008, 2010, 2012 Mark Adler + * For conditions of distribution and use, see copyright notice in zlib.h + Version 1.7 12 August 2012 Mark Adler */ + +/* Version history: + 1.0 16 Feb 2003 First version for testing of inflateBack() + 1.1 21 Feb 2005 Decompress concatenated gzip streams + Remove use of "this" variable (C++ keyword) + Fix return value for in() + Improve allocation failure checking + Add typecasting for void * structures + Add -h option for command version and usage + Add a bunch of comments + 1.2 20 Mar 2005 Add Unix compress (LZW) decompression + Copy file attributes from input file to output file + 1.3 12 Jun 2005 Add casts for error messages [Oberhumer] + 1.4 8 Dec 2006 LZW decompression speed improvements + 1.5 9 Feb 2008 Avoid warning in latest version of gcc + 1.6 17 Jan 2010 Avoid signed/unsigned comparison warnings + 1.7 12 Aug 2012 Update for z_const usage in zlib 1.2.8 + */ + +/* + gun [ -t ] [ name ... ] + + decompresses the data in the named gzip files. If no arguments are given, + gun will decompress from stdin to stdout. The names must end in .gz, -gz, + .z, -z, _z, or .Z. The uncompressed data will be written to a file name + with the suffix stripped. On success, the original file is deleted. On + failure, the output file is deleted. For most failures, the command will + continue to process the remaining names on the command line. A memory + allocation failure will abort the command. If -t is specified, then the + listed files or stdin will be tested as gzip files for integrity (without + checking for a proper suffix), no output will be written, and no files + will be deleted. + + Like gzip, gun allows concatenated gzip streams and will decompress them, + writing all of the uncompressed data to the output. Unlike gzip, gun allows + an empty file on input, and will produce no error writing an empty output + file. + + gun will also decompress files made by Unix compress, which uses LZW + compression. These files are automatically detected by virtue of their + magic header bytes. Since the end of Unix compress stream is marked by the + end-of-file, they cannot be concantenated. If a Unix compress stream is + encountered in an input file, it is the last stream in that file. + + Like gunzip and uncompress, the file attributes of the original compressed + file are maintained in the final uncompressed file, to the extent that the + user permissions allow it. + + On my Mac OS X PowerPC G4, gun is almost twice as fast as gunzip (version + 1.2.4) is on the same file, when gun is linked with zlib 1.2.2. Also the + LZW decompression provided by gun is about twice as fast as the standard + Unix uncompress command. + */ + +/* external functions and related types and constants */ +#include /* fprintf() */ +#include /* malloc(), free() */ +#include /* strerror(), strcmp(), strlen(), memcpy() */ +#include /* errno */ +#include /* open() */ +#include /* read(), write(), close(), chown(), unlink() */ +#include +#include /* stat(), chmod() */ +#include /* utime() */ +#include "zlib.h" /* inflateBackInit(), inflateBack(), */ + /* inflateBackEnd(), crc32() */ + +/* function declaration */ +#define local static + +/* buffer constants */ +#define SIZE 32768U /* input and output buffer sizes */ +#define PIECE 16384 /* limits i/o chunks for 16-bit int case */ + +/* structure for infback() to pass to input function in() -- it maintains the + input file and a buffer of size SIZE */ +struct ind { + int infile; + unsigned char *inbuf; +}; + +/* Load input buffer, assumed to be empty, and return bytes loaded and a + pointer to them. read() is called until the buffer is full, or until it + returns end-of-file or error. Return 0 on error. */ +local unsigned in(void *in_desc, z_const unsigned char **buf) +{ + int ret; + unsigned len; + unsigned char *next; + struct ind *me = (struct ind *)in_desc; + + next = me->inbuf; + *buf = next; + len = 0; + do { + ret = PIECE; + if ((unsigned)ret > SIZE - len) + ret = (int)(SIZE - len); + ret = (int)read(me->infile, next, ret); + if (ret == -1) { + len = 0; + break; + } + next += ret; + len += ret; + } while (ret != 0 && len < SIZE); + return len; +} + +/* structure for infback() to pass to output function out() -- it maintains the + output file, a running CRC-32 check on the output and the total number of + bytes output, both for checking against the gzip trailer. (The length in + the gzip trailer is stored modulo 2^32, so it's ok if a long is 32 bits and + the output is greater than 4 GB.) */ +struct outd { + int outfile; + int check; /* true if checking crc and total */ + unsigned long crc; + unsigned long total; +}; + +/* Write output buffer and update the CRC-32 and total bytes written. write() + is called until all of the output is written or an error is encountered. + On success out() returns 0. For a write failure, out() returns 1. If the + output file descriptor is -1, then nothing is written. + */ +local int out(void *out_desc, unsigned char *buf, unsigned len) +{ + int ret; + struct outd *me = (struct outd *)out_desc; + + if (me->check) { + me->crc = crc32(me->crc, buf, len); + me->total += len; + } + if (me->outfile != -1) + do { + ret = PIECE; + if ((unsigned)ret > len) + ret = (int)len; + ret = (int)write(me->outfile, buf, ret); + if (ret == -1) + return 1; + buf += ret; + len -= ret; + } while (len != 0); + return 0; +} + +/* next input byte macro for use inside lunpipe() and gunpipe() */ +#define NEXT() (have ? 0 : (have = in(indp, &next)), \ + last = have ? (have--, (int)(*next++)) : -1) + +/* memory for gunpipe() and lunpipe() -- + the first 256 entries of prefix[] and suffix[] are never used, could + have offset the index, but it's faster to waste the memory */ +unsigned char inbuf[SIZE]; /* input buffer */ +unsigned char outbuf[SIZE]; /* output buffer */ +unsigned short prefix[65536]; /* index to LZW prefix string */ +unsigned char suffix[65536]; /* one-character LZW suffix */ +unsigned char match[65280 + 2]; /* buffer for reversed match or gzip + 32K sliding window */ + +/* throw out what's left in the current bits byte buffer (this is a vestigial + aspect of the compressed data format derived from an implementation that + made use of a special VAX machine instruction!) */ +#define FLUSHCODE() \ + do { \ + left = 0; \ + rem = 0; \ + if (chunk > have) { \ + chunk -= have; \ + have = 0; \ + if (NEXT() == -1) \ + break; \ + chunk--; \ + if (chunk > have) { \ + chunk = have = 0; \ + break; \ + } \ + } \ + have -= chunk; \ + next += chunk; \ + chunk = 0; \ + } while (0) + +/* Decompress a compress (LZW) file from indp to outfile. The compress magic + header (two bytes) has already been read and verified. There are have bytes + of buffered input at next. strm is used for passing error information back + to gunpipe(). + + lunpipe() will return Z_OK on success, Z_BUF_ERROR for an unexpected end of + file, read error, or write error (a write error indicated by strm->next_in + not equal to Z_NULL), or Z_DATA_ERROR for invalid input. + */ +local int lunpipe(unsigned have, z_const unsigned char *next, struct ind *indp, + int outfile, z_stream *strm) +{ + int last; /* last byte read by NEXT(), or -1 if EOF */ + unsigned chunk; /* bytes left in current chunk */ + int left; /* bits left in rem */ + unsigned rem; /* unused bits from input */ + int bits; /* current bits per code */ + unsigned code; /* code, table traversal index */ + unsigned mask; /* mask for current bits codes */ + int max; /* maximum bits per code for this stream */ + unsigned flags; /* compress flags, then block compress flag */ + unsigned end; /* last valid entry in prefix/suffix tables */ + unsigned temp; /* current code */ + unsigned prev; /* previous code */ + unsigned final; /* last character written for previous code */ + unsigned stack; /* next position for reversed string */ + unsigned outcnt; /* bytes in output buffer */ + struct outd outd; /* output structure */ + unsigned char *p; + + /* set up output */ + outd.outfile = outfile; + outd.check = 0; + + /* process remainder of compress header -- a flags byte */ + flags = NEXT(); + if (last == -1) + return Z_BUF_ERROR; + if (flags & 0x60) { + strm->msg = (char *)"unknown lzw flags set"; + return Z_DATA_ERROR; + } + max = flags & 0x1f; + if (max < 9 || max > 16) { + strm->msg = (char *)"lzw bits out of range"; + return Z_DATA_ERROR; + } + if (max == 9) /* 9 doesn't really mean 9 */ + max = 10; + flags &= 0x80; /* true if block compress */ + + /* clear table */ + bits = 9; + mask = 0x1ff; + end = flags ? 256 : 255; + + /* set up: get first 9-bit code, which is the first decompressed byte, but + don't create a table entry until the next code */ + if (NEXT() == -1) /* no compressed data is ok */ + return Z_OK; + final = prev = (unsigned)last; /* low 8 bits of code */ + if (NEXT() == -1) /* missing a bit */ + return Z_BUF_ERROR; + if (last & 1) { /* code must be < 256 */ + strm->msg = (char *)"invalid lzw code"; + return Z_DATA_ERROR; + } + rem = (unsigned)last >> 1; /* remaining 7 bits */ + left = 7; + chunk = bits - 2; /* 7 bytes left in this chunk */ + outbuf[0] = (unsigned char)final; /* write first decompressed byte */ + outcnt = 1; + + /* decode codes */ + stack = 0; + for (;;) { + /* if the table will be full after this, increment the code size */ + if (end >= mask && bits < max) { + FLUSHCODE(); + bits++; + mask <<= 1; + mask++; + } + + /* get a code of length bits */ + if (chunk == 0) /* decrement chunk modulo bits */ + chunk = bits; + code = rem; /* low bits of code */ + if (NEXT() == -1) { /* EOF is end of compressed data */ + /* write remaining buffered output */ + if (outcnt && out(&outd, outbuf, outcnt)) { + strm->next_in = outbuf; /* signal write error */ + return Z_BUF_ERROR; + } + return Z_OK; + } + code += (unsigned)last << left; /* middle (or high) bits of code */ + left += 8; + chunk--; + if (bits > left) { /* need more bits */ + if (NEXT() == -1) /* can't end in middle of code */ + return Z_BUF_ERROR; + code += (unsigned)last << left; /* high bits of code */ + left += 8; + chunk--; + } + code &= mask; /* mask to current code length */ + left -= bits; /* number of unused bits */ + rem = (unsigned)last >> (8 - left); /* unused bits from last byte */ + + /* process clear code (256) */ + if (code == 256 && flags) { + FLUSHCODE(); + bits = 9; /* initialize bits and mask */ + mask = 0x1ff; + end = 255; /* empty table */ + continue; /* get next code */ + } + + /* special code to reuse last match */ + temp = code; /* save the current code */ + if (code > end) { + /* Be picky on the allowed code here, and make sure that the code + we drop through (prev) will be a valid index so that random + input does not cause an exception. The code != end + 1 check is + empirically derived, and not checked in the original uncompress + code. If this ever causes a problem, that check could be safely + removed. Leaving this check in greatly improves gun's ability + to detect random or corrupted input after a compress header. + In any case, the prev > end check must be retained. */ + if (code != end + 1 || prev > end) { + strm->msg = (char *)"invalid lzw code"; + return Z_DATA_ERROR; + } + match[stack++] = (unsigned char)final; + code = prev; + } + + /* walk through linked list to generate output in reverse order */ + p = match + stack; + while (code >= 256) { + *p++ = suffix[code]; + code = prefix[code]; + } + stack = p - match; + match[stack++] = (unsigned char)code; + final = code; + + /* link new table entry */ + if (end < mask) { + end++; + prefix[end] = (unsigned short)prev; + suffix[end] = (unsigned char)final; + } + + /* set previous code for next iteration */ + prev = temp; + + /* write output in forward order */ + while (stack > SIZE - outcnt) { + while (outcnt < SIZE) + outbuf[outcnt++] = match[--stack]; + if (out(&outd, outbuf, outcnt)) { + strm->next_in = outbuf; /* signal write error */ + return Z_BUF_ERROR; + } + outcnt = 0; + } + p = match + stack; + do { + outbuf[outcnt++] = *--p; + } while (p > match); + stack = 0; + + /* loop for next code with final and prev as the last match, rem and + left provide the first 0..7 bits of the next code, end is the last + valid table entry */ + } +} + +/* Decompress a gzip file from infile to outfile. strm is assumed to have been + successfully initialized with inflateBackInit(). The input file may consist + of a series of gzip streams, in which case all of them will be decompressed + to the output file. If outfile is -1, then the gzip stream(s) integrity is + checked and nothing is written. + + The return value is a zlib error code: Z_MEM_ERROR if out of memory, + Z_DATA_ERROR if the header or the compressed data is invalid, or if the + trailer CRC-32 check or length doesn't match, Z_BUF_ERROR if the input ends + prematurely or a write error occurs, or Z_ERRNO if junk (not a another gzip + stream) follows a valid gzip stream. + */ +local int gunpipe(z_stream *strm, int infile, int outfile) +{ + int ret, first, last; + unsigned have, flags, len; + z_const unsigned char *next = NULL; + struct ind ind, *indp; + struct outd outd; + + /* setup input buffer */ + ind.infile = infile; + ind.inbuf = inbuf; + indp = &ind; + + /* decompress concatenated gzip streams */ + have = 0; /* no input data read in yet */ + first = 1; /* looking for first gzip header */ + strm->next_in = Z_NULL; /* so Z_BUF_ERROR means EOF */ + for (;;) { + /* look for the two magic header bytes for a gzip stream */ + if (NEXT() == -1) { + ret = Z_OK; + break; /* empty gzip stream is ok */ + } + if (last != 31 || (NEXT() != 139 && last != 157)) { + strm->msg = (char *)"incorrect header check"; + ret = first ? Z_DATA_ERROR : Z_ERRNO; + break; /* not a gzip or compress header */ + } + first = 0; /* next non-header is junk */ + + /* process a compress (LZW) file -- can't be concatenated after this */ + if (last == 157) { + ret = lunpipe(have, next, indp, outfile, strm); + break; + } + + /* process remainder of gzip header */ + ret = Z_BUF_ERROR; + if (NEXT() != 8) { /* only deflate method allowed */ + if (last == -1) break; + strm->msg = (char *)"unknown compression method"; + ret = Z_DATA_ERROR; + break; + } + flags = NEXT(); /* header flags */ + NEXT(); /* discard mod time, xflgs, os */ + NEXT(); + NEXT(); + NEXT(); + NEXT(); + NEXT(); + if (last == -1) break; + if (flags & 0xe0) { + strm->msg = (char *)"unknown header flags set"; + ret = Z_DATA_ERROR; + break; + } + if (flags & 4) { /* extra field */ + len = NEXT(); + len += (unsigned)(NEXT()) << 8; + if (last == -1) break; + while (len > have) { + len -= have; + have = 0; + if (NEXT() == -1) break; + len--; + } + if (last == -1) break; + have -= len; + next += len; + } + if (flags & 8) /* file name */ + while (NEXT() != 0 && last != -1) + ; + if (flags & 16) /* comment */ + while (NEXT() != 0 && last != -1) + ; + if (flags & 2) { /* header crc */ + NEXT(); + NEXT(); + } + if (last == -1) break; + + /* set up output */ + outd.outfile = outfile; + outd.check = 1; + outd.crc = crc32(0L, Z_NULL, 0); + outd.total = 0; + + /* decompress data to output */ + strm->next_in = next; + strm->avail_in = have; + ret = inflateBack(strm, in, indp, out, &outd); + if (ret != Z_STREAM_END) break; + next = strm->next_in; + have = strm->avail_in; + strm->next_in = Z_NULL; /* so Z_BUF_ERROR means EOF */ + + /* check trailer */ + ret = Z_BUF_ERROR; + if (NEXT() != (int)(outd.crc & 0xff) || + NEXT() != (int)((outd.crc >> 8) & 0xff) || + NEXT() != (int)((outd.crc >> 16) & 0xff) || + NEXT() != (int)((outd.crc >> 24) & 0xff)) { + /* crc error */ + if (last != -1) { + strm->msg = (char *)"incorrect data check"; + ret = Z_DATA_ERROR; + } + break; + } + if (NEXT() != (int)(outd.total & 0xff) || + NEXT() != (int)((outd.total >> 8) & 0xff) || + NEXT() != (int)((outd.total >> 16) & 0xff) || + NEXT() != (int)((outd.total >> 24) & 0xff)) { + /* length error */ + if (last != -1) { + strm->msg = (char *)"incorrect length check"; + ret = Z_DATA_ERROR; + } + break; + } + + /* go back and look for another gzip stream */ + } + + /* clean up and return */ + return ret; +} + +/* Copy file attributes, from -> to, as best we can. This is best effort, so + no errors are reported. The mode bits, including suid, sgid, and the sticky + bit are copied (if allowed), the owner's user id and group id are copied + (again if allowed), and the access and modify times are copied. */ +local void copymeta(char *from, char *to) +{ + struct stat was; + struct utimbuf when; + + /* get all of from's Unix meta data, return if not a regular file */ + if (stat(from, &was) != 0 || (was.st_mode & S_IFMT) != S_IFREG) + return; + + /* set to's mode bits, ignore errors */ + (void)chmod(to, was.st_mode & 07777); + + /* copy owner's user and group, ignore errors */ + (void)chown(to, was.st_uid, was.st_gid); + + /* copy access and modify times, ignore errors */ + when.actime = was.st_atime; + when.modtime = was.st_mtime; + (void)utime(to, &when); +} + +/* Decompress the file inname to the file outnname, of if test is true, just + decompress without writing and check the gzip trailer for integrity. If + inname is NULL or an empty string, read from stdin. If outname is NULL or + an empty string, write to stdout. strm is a pre-initialized inflateBack + structure. When appropriate, copy the file attributes from inname to + outname. + + gunzip() returns 1 if there is an out-of-memory error or an unexpected + return code from gunpipe(). Otherwise it returns 0. + */ +local int gunzip(z_stream *strm, char *inname, char *outname, int test) +{ + int ret; + int infile, outfile; + + /* open files */ + if (inname == NULL || *inname == 0) { + inname = "-"; + infile = 0; /* stdin */ + } + else { + infile = open(inname, O_RDONLY, 0); + if (infile == -1) { + fprintf(stderr, "gun cannot open %s\n", inname); + return 0; + } + } + if (test) + outfile = -1; + else if (outname == NULL || *outname == 0) { + outname = "-"; + outfile = 1; /* stdout */ + } + else { + outfile = open(outname, O_CREAT | O_TRUNC | O_WRONLY, 0666); + if (outfile == -1) { + close(infile); + fprintf(stderr, "gun cannot create %s\n", outname); + return 0; + } + } + errno = 0; + + /* decompress */ + ret = gunpipe(strm, infile, outfile); + if (outfile > 2) close(outfile); + if (infile > 2) close(infile); + + /* interpret result */ + switch (ret) { + case Z_OK: + case Z_ERRNO: + if (infile > 2 && outfile > 2) { + copymeta(inname, outname); /* copy attributes */ + unlink(inname); + } + if (ret == Z_ERRNO) + fprintf(stderr, "gun warning: trailing garbage ignored in %s\n", + inname); + break; + case Z_DATA_ERROR: + if (outfile > 2) unlink(outname); + fprintf(stderr, "gun data error on %s: %s\n", inname, strm->msg); + break; + case Z_MEM_ERROR: + if (outfile > 2) unlink(outname); + fprintf(stderr, "gun out of memory error--aborting\n"); + return 1; + case Z_BUF_ERROR: + if (outfile > 2) unlink(outname); + if (strm->next_in != Z_NULL) { + fprintf(stderr, "gun write error on %s: %s\n", + outname, strerror(errno)); + } + else if (errno) { + fprintf(stderr, "gun read error on %s: %s\n", + inname, strerror(errno)); + } + else { + fprintf(stderr, "gun unexpected end of file on %s\n", + inname); + } + break; + default: + if (outfile > 2) unlink(outname); + fprintf(stderr, "gun internal error--aborting\n"); + return 1; + } + return 0; +} + +/* Process the gun command line arguments. See the command syntax near the + beginning of this source file. */ +int main(int argc, char **argv) +{ + int ret, len, test; + char *outname; + unsigned char *window; + z_stream strm; + + /* initialize inflateBack state for repeated use */ + window = match; /* reuse LZW match buffer */ + strm.zalloc = Z_NULL; + strm.zfree = Z_NULL; + strm.opaque = Z_NULL; + ret = inflateBackInit(&strm, 15, window); + if (ret != Z_OK) { + fprintf(stderr, "gun out of memory error--aborting\n"); + return 1; + } + + /* decompress each file to the same name with the suffix removed */ + argc--; + argv++; + test = 0; + if (argc && strcmp(*argv, "-h") == 0) { + fprintf(stderr, "gun 1.6 (17 Jan 2010)\n"); + fprintf(stderr, "Copyright (C) 2003-2010 Mark Adler\n"); + fprintf(stderr, "usage: gun [-t] [file1.gz [file2.Z ...]]\n"); + return 0; + } + if (argc && strcmp(*argv, "-t") == 0) { + test = 1; + argc--; + argv++; + } + if (argc) + do { + if (test) + outname = NULL; + else { + len = (int)strlen(*argv); + if (strcmp(*argv + len - 3, ".gz") == 0 || + strcmp(*argv + len - 3, "-gz") == 0) + len -= 3; + else if (strcmp(*argv + len - 2, ".z") == 0 || + strcmp(*argv + len - 2, "-z") == 0 || + strcmp(*argv + len - 2, "_z") == 0 || + strcmp(*argv + len - 2, ".Z") == 0) + len -= 2; + else { + fprintf(stderr, "gun error: no gz type on %s--skipping\n", + *argv); + continue; + } + outname = malloc(len + 1); + if (outname == NULL) { + fprintf(stderr, "gun out of memory error--aborting\n"); + ret = 1; + break; + } + memcpy(outname, *argv, len); + outname[len] = 0; + } + ret = gunzip(&strm, *argv, outname, test); + if (outname != NULL) free(outname); + if (ret) break; + } while (argv++, --argc); + else + ret = gunzip(&strm, NULL, NULL, test); + + /* clean up */ + inflateBackEnd(&strm); + return ret; +} ADDED compat/zlib/examples/gzappend.c Index: compat/zlib/examples/gzappend.c ================================================================== --- /dev/null +++ compat/zlib/examples/gzappend.c @@ -0,0 +1,504 @@ +/* gzappend -- command to append to a gzip file + + Copyright (C) 2003, 2012 Mark Adler, all rights reserved + version 1.2, 11 Oct 2012 + + This software is provided 'as-is', without any express or implied + warranty. In no event will the author be held liable for any damages + arising from the use of this software. + + Permission is granted to anyone to use this software for any purpose, + including commercial applications, and to alter it and redistribute it + freely, subject to the following restrictions: + + 1. The origin of this software must not be misrepresented; you must not + claim that you wrote the original software. If you use this software + in a product, an acknowledgment in the product documentation would be + appreciated but is not required. + 2. Altered source versions must be plainly marked as such, and must not be + misrepresented as being the original software. + 3. This notice may not be removed or altered from any source distribution. + + Mark Adler madler@alumni.caltech.edu + */ + +/* + * Change history: + * + * 1.0 19 Oct 2003 - First version + * 1.1 4 Nov 2003 - Expand and clarify some comments and notes + * - Add version and copyright to help + * - Send help to stdout instead of stderr + * - Add some preemptive typecasts + * - Add L to constants in lseek() calls + * - Remove some debugging information in error messages + * - Use new data_type definition for zlib 1.2.1 + * - Simplfy and unify file operations + * - Finish off gzip file in gztack() + * - Use deflatePrime() instead of adding empty blocks + * - Keep gzip file clean on appended file read errors + * - Use in-place rotate instead of auxiliary buffer + * (Why you ask? Because it was fun to write!) + * 1.2 11 Oct 2012 - Fix for proper z_const usage + * - Check for input buffer malloc failure + */ + +/* + gzappend takes a gzip file and appends to it, compressing files from the + command line or data from stdin. The gzip file is written to directly, to + avoid copying that file, in case it's large. Note that this results in the + unfriendly behavior that if gzappend fails, the gzip file is corrupted. + + This program was written to illustrate the use of the new Z_BLOCK option of + zlib 1.2.x's inflate() function. This option returns from inflate() at each + block boundary to facilitate locating and modifying the last block bit at + the start of the final deflate block. Also whether using Z_BLOCK or not, + another required feature of zlib 1.2.x is that inflate() now provides the + number of unusued bits in the last input byte used. gzappend will not work + with versions of zlib earlier than 1.2.1. + + gzappend first decompresses the gzip file internally, discarding all but + the last 32K of uncompressed data, and noting the location of the last block + bit and the number of unused bits in the last byte of the compressed data. + The gzip trailer containing the CRC-32 and length of the uncompressed data + is verified. This trailer will be later overwritten. + + Then the last block bit is cleared by seeking back in the file and rewriting + the byte that contains it. Seeking forward, the last byte of the compressed + data is saved along with the number of unused bits to initialize deflate. + + A deflate process is initialized, using the last 32K of the uncompressed + data from the gzip file to initialize the dictionary. If the total + uncompressed data was less than 32K, then all of it is used to initialize + the dictionary. The deflate output bit buffer is also initialized with the + last bits from the original deflate stream. From here on, the data to + append is simply compressed using deflate, and written to the gzip file. + When that is complete, the new CRC-32 and uncompressed length are written + as the trailer of the gzip file. + */ + +#include +#include +#include +#include +#include +#include "zlib.h" + +#define local static +#define LGCHUNK 14 +#define CHUNK (1U << LGCHUNK) +#define DSIZE 32768U + +/* print an error message and terminate with extreme prejudice */ +local void bye(char *msg1, char *msg2) +{ + fprintf(stderr, "gzappend error: %s%s\n", msg1, msg2); + exit(1); +} + +/* return the greatest common divisor of a and b using Euclid's algorithm, + modified to be fast when one argument much greater than the other, and + coded to avoid unnecessary swapping */ +local unsigned gcd(unsigned a, unsigned b) +{ + unsigned c; + + while (a && b) + if (a > b) { + c = b; + while (a - c >= c) + c <<= 1; + a -= c; + } + else { + c = a; + while (b - c >= c) + c <<= 1; + b -= c; + } + return a + b; +} + +/* rotate list[0..len-1] left by rot positions, in place */ +local void rotate(unsigned char *list, unsigned len, unsigned rot) +{ + unsigned char tmp; + unsigned cycles; + unsigned char *start, *last, *to, *from; + + /* normalize rot and handle degenerate cases */ + if (len < 2) return; + if (rot >= len) rot %= len; + if (rot == 0) return; + + /* pointer to last entry in list */ + last = list + (len - 1); + + /* do simple left shift by one */ + if (rot == 1) { + tmp = *list; + memcpy(list, list + 1, len - 1); + *last = tmp; + return; + } + + /* do simple right shift by one */ + if (rot == len - 1) { + tmp = *last; + memmove(list + 1, list, len - 1); + *list = tmp; + return; + } + + /* otherwise do rotate as a set of cycles in place */ + cycles = gcd(len, rot); /* number of cycles */ + do { + start = from = list + cycles; /* start index is arbitrary */ + tmp = *from; /* save entry to be overwritten */ + for (;;) { + to = from; /* next step in cycle */ + from += rot; /* go right rot positions */ + if (from > last) from -= len; /* (pointer better not wrap) */ + if (from == start) break; /* all but one shifted */ + *to = *from; /* shift left */ + } + *to = tmp; /* complete the circle */ + } while (--cycles); +} + +/* structure for gzip file read operations */ +typedef struct { + int fd; /* file descriptor */ + int size; /* 1 << size is bytes in buf */ + unsigned left; /* bytes available at next */ + unsigned char *buf; /* buffer */ + z_const unsigned char *next; /* next byte in buffer */ + char *name; /* file name for error messages */ +} file; + +/* reload buffer */ +local int readin(file *in) +{ + int len; + + len = read(in->fd, in->buf, 1 << in->size); + if (len == -1) bye("error reading ", in->name); + in->left = (unsigned)len; + in->next = in->buf; + return len; +} + +/* read from file in, exit if end-of-file */ +local int readmore(file *in) +{ + if (readin(in) == 0) bye("unexpected end of ", in->name); + return 0; +} + +#define read1(in) (in->left == 0 ? readmore(in) : 0, \ + in->left--, *(in->next)++) + +/* skip over n bytes of in */ +local void skip(file *in, unsigned n) +{ + unsigned bypass; + + if (n > in->left) { + n -= in->left; + bypass = n & ~((1U << in->size) - 1); + if (bypass) { + if (lseek(in->fd, (off_t)bypass, SEEK_CUR) == -1) + bye("seeking ", in->name); + n -= bypass; + } + readmore(in); + if (n > in->left) + bye("unexpected end of ", in->name); + } + in->left -= n; + in->next += n; +} + +/* read a four-byte unsigned integer, little-endian, from in */ +unsigned long read4(file *in) +{ + unsigned long val; + + val = read1(in); + val += (unsigned)read1(in) << 8; + val += (unsigned long)read1(in) << 16; + val += (unsigned long)read1(in) << 24; + return val; +} + +/* skip over gzip header */ +local void gzheader(file *in) +{ + int flags; + unsigned n; + + if (read1(in) != 31 || read1(in) != 139) bye(in->name, " not a gzip file"); + if (read1(in) != 8) bye("unknown compression method in", in->name); + flags = read1(in); + if (flags & 0xe0) bye("unknown header flags set in", in->name); + skip(in, 6); + if (flags & 4) { + n = read1(in); + n += (unsigned)(read1(in)) << 8; + skip(in, n); + } + if (flags & 8) while (read1(in) != 0) ; + if (flags & 16) while (read1(in) != 0) ; + if (flags & 2) skip(in, 2); +} + +/* decompress gzip file "name", return strm with a deflate stream ready to + continue compression of the data in the gzip file, and return a file + descriptor pointing to where to write the compressed data -- the deflate + stream is initialized to compress using level "level" */ +local int gzscan(char *name, z_stream *strm, int level) +{ + int ret, lastbit, left, full; + unsigned have; + unsigned long crc, tot; + unsigned char *window; + off_t lastoff, end; + file gz; + + /* open gzip file */ + gz.name = name; + gz.fd = open(name, O_RDWR, 0); + if (gz.fd == -1) bye("cannot open ", name); + gz.buf = malloc(CHUNK); + if (gz.buf == NULL) bye("out of memory", ""); + gz.size = LGCHUNK; + gz.left = 0; + + /* skip gzip header */ + gzheader(&gz); + + /* prepare to decompress */ + window = malloc(DSIZE); + if (window == NULL) bye("out of memory", ""); + strm->zalloc = Z_NULL; + strm->zfree = Z_NULL; + strm->opaque = Z_NULL; + ret = inflateInit2(strm, -15); + if (ret != Z_OK) bye("out of memory", " or library mismatch"); + + /* decompress the deflate stream, saving append information */ + lastbit = 0; + lastoff = lseek(gz.fd, 0L, SEEK_CUR) - gz.left; + left = 0; + strm->avail_in = gz.left; + strm->next_in = gz.next; + crc = crc32(0L, Z_NULL, 0); + have = full = 0; + do { + /* if needed, get more input */ + if (strm->avail_in == 0) { + readmore(&gz); + strm->avail_in = gz.left; + strm->next_in = gz.next; + } + + /* set up output to next available section of sliding window */ + strm->avail_out = DSIZE - have; + strm->next_out = window + have; + + /* inflate and check for errors */ + ret = inflate(strm, Z_BLOCK); + if (ret == Z_STREAM_ERROR) bye("internal stream error!", ""); + if (ret == Z_MEM_ERROR) bye("out of memory", ""); + if (ret == Z_DATA_ERROR) + bye("invalid compressed data--format violated in", name); + + /* update crc and sliding window pointer */ + crc = crc32(crc, window + have, DSIZE - have - strm->avail_out); + if (strm->avail_out) + have = DSIZE - strm->avail_out; + else { + have = 0; + full = 1; + } + + /* process end of block */ + if (strm->data_type & 128) { + if (strm->data_type & 64) + left = strm->data_type & 0x1f; + else { + lastbit = strm->data_type & 0x1f; + lastoff = lseek(gz.fd, 0L, SEEK_CUR) - strm->avail_in; + } + } + } while (ret != Z_STREAM_END); + inflateEnd(strm); + gz.left = strm->avail_in; + gz.next = strm->next_in; + + /* save the location of the end of the compressed data */ + end = lseek(gz.fd, 0L, SEEK_CUR) - gz.left; + + /* check gzip trailer and save total for deflate */ + if (crc != read4(&gz)) + bye("invalid compressed data--crc mismatch in ", name); + tot = strm->total_out; + if ((tot & 0xffffffffUL) != read4(&gz)) + bye("invalid compressed data--length mismatch in", name); + + /* if not at end of file, warn */ + if (gz.left || readin(&gz)) + fprintf(stderr, + "gzappend warning: junk at end of gzip file overwritten\n"); + + /* clear last block bit */ + lseek(gz.fd, lastoff - (lastbit != 0), SEEK_SET); + if (read(gz.fd, gz.buf, 1) != 1) bye("reading after seek on ", name); + *gz.buf = (unsigned char)(*gz.buf ^ (1 << ((8 - lastbit) & 7))); + lseek(gz.fd, -1L, SEEK_CUR); + if (write(gz.fd, gz.buf, 1) != 1) bye("writing after seek to ", name); + + /* if window wrapped, build dictionary from window by rotating */ + if (full) { + rotate(window, DSIZE, have); + have = DSIZE; + } + + /* set up deflate stream with window, crc, total_in, and leftover bits */ + ret = deflateInit2(strm, level, Z_DEFLATED, -15, 8, Z_DEFAULT_STRATEGY); + if (ret != Z_OK) bye("out of memory", ""); + deflateSetDictionary(strm, window, have); + strm->adler = crc; + strm->total_in = tot; + if (left) { + lseek(gz.fd, --end, SEEK_SET); + if (read(gz.fd, gz.buf, 1) != 1) bye("reading after seek on ", name); + deflatePrime(strm, 8 - left, *gz.buf); + } + lseek(gz.fd, end, SEEK_SET); + + /* clean up and return */ + free(window); + free(gz.buf); + return gz.fd; +} + +/* append file "name" to gzip file gd using deflate stream strm -- if last + is true, then finish off the deflate stream at the end */ +local void gztack(char *name, int gd, z_stream *strm, int last) +{ + int fd, len, ret; + unsigned left; + unsigned char *in, *out; + + /* open file to compress and append */ + fd = 0; + if (name != NULL) { + fd = open(name, O_RDONLY, 0); + if (fd == -1) + fprintf(stderr, "gzappend warning: %s not found, skipping ...\n", + name); + } + + /* allocate buffers */ + in = malloc(CHUNK); + out = malloc(CHUNK); + if (in == NULL || out == NULL) bye("out of memory", ""); + + /* compress input file and append to gzip file */ + do { + /* get more input */ + len = read(fd, in, CHUNK); + if (len == -1) { + fprintf(stderr, + "gzappend warning: error reading %s, skipping rest ...\n", + name); + len = 0; + } + strm->avail_in = (unsigned)len; + strm->next_in = in; + if (len) strm->adler = crc32(strm->adler, in, (unsigned)len); + + /* compress and write all available output */ + do { + strm->avail_out = CHUNK; + strm->next_out = out; + ret = deflate(strm, last && len == 0 ? Z_FINISH : Z_NO_FLUSH); + left = CHUNK - strm->avail_out; + while (left) { + len = write(gd, out + CHUNK - strm->avail_out - left, left); + if (len == -1) bye("writing gzip file", ""); + left -= (unsigned)len; + } + } while (strm->avail_out == 0 && ret != Z_STREAM_END); + } while (len != 0); + + /* write trailer after last entry */ + if (last) { + deflateEnd(strm); + out[0] = (unsigned char)(strm->adler); + out[1] = (unsigned char)(strm->adler >> 8); + out[2] = (unsigned char)(strm->adler >> 16); + out[3] = (unsigned char)(strm->adler >> 24); + out[4] = (unsigned char)(strm->total_in); + out[5] = (unsigned char)(strm->total_in >> 8); + out[6] = (unsigned char)(strm->total_in >> 16); + out[7] = (unsigned char)(strm->total_in >> 24); + len = 8; + do { + ret = write(gd, out + 8 - len, len); + if (ret == -1) bye("writing gzip file", ""); + len -= ret; + } while (len); + close(gd); + } + + /* clean up and return */ + free(out); + free(in); + if (fd > 0) close(fd); +} + +/* process the compression level option if present, scan the gzip file, and + append the specified files, or append the data from stdin if no other file + names are provided on the command line -- the gzip file must be writable + and seekable */ +int main(int argc, char **argv) +{ + int gd, level; + z_stream strm; + + /* ignore command name */ + argc--; argv++; + + /* provide usage if no arguments */ + if (*argv == NULL) { + printf( + "gzappend 1.2 (11 Oct 2012) Copyright (C) 2003, 2012 Mark Adler\n" + ); + printf( + "usage: gzappend [-level] file.gz [ addthis [ andthis ... ]]\n"); + return 0; + } + + /* set compression level */ + level = Z_DEFAULT_COMPRESSION; + if (argv[0][0] == '-') { + if (argv[0][1] < '0' || argv[0][1] > '9' || argv[0][2] != 0) + bye("invalid compression level", ""); + level = argv[0][1] - '0'; + if (*++argv == NULL) bye("no gzip file name after options", ""); + } + + /* prepare to append to gzip file */ + gd = gzscan(*argv++, &strm, level); + + /* append files on command line, or from stdin if none */ + if (*argv == NULL) + gztack(NULL, gd, &strm, 1); + else + do { + gztack(*argv, gd, &strm, argv[1] == NULL); + } while (*++argv != NULL); + return 0; +} ADDED compat/zlib/examples/gzjoin.c Index: compat/zlib/examples/gzjoin.c ================================================================== --- /dev/null +++ compat/zlib/examples/gzjoin.c @@ -0,0 +1,449 @@ +/* gzjoin -- command to join gzip files into one gzip file + + Copyright (C) 2004, 2005, 2012 Mark Adler, all rights reserved + version 1.2, 14 Aug 2012 + + This software is provided 'as-is', without any express or implied + warranty. In no event will the author be held liable for any damages + arising from the use of this software. + + Permission is granted to anyone to use this software for any purpose, + including commercial applications, and to alter it and redistribute it + freely, subject to the following restrictions: + + 1. The origin of this software must not be misrepresented; you must not + claim that you wrote the original software. If you use this software + in a product, an acknowledgment in the product documentation would be + appreciated but is not required. + 2. Altered source versions must be plainly marked as such, and must not be + misrepresented as being the original software. + 3. This notice may not be removed or altered from any source distribution. + + Mark Adler madler@alumni.caltech.edu + */ + +/* + * Change history: + * + * 1.0 11 Dec 2004 - First version + * 1.1 12 Jun 2005 - Changed ssize_t to long for portability + * 1.2 14 Aug 2012 - Clean up for z_const usage + */ + +/* + gzjoin takes one or more gzip files on the command line and writes out a + single gzip file that will uncompress to the concatenation of the + uncompressed data from the individual gzip files. gzjoin does this without + having to recompress any of the data and without having to calculate a new + crc32 for the concatenated uncompressed data. gzjoin does however have to + decompress all of the input data in order to find the bits in the compressed + data that need to be modified to concatenate the streams. + + gzjoin does not do an integrity check on the input gzip files other than + checking the gzip header and decompressing the compressed data. They are + otherwise assumed to be complete and correct. + + Each joint between gzip files removes at least 18 bytes of previous trailer + and subsequent header, and inserts an average of about three bytes to the + compressed data in order to connect the streams. The output gzip file + has a minimal ten-byte gzip header with no file name or modification time. + + This program was written to illustrate the use of the Z_BLOCK option of + inflate() and the crc32_combine() function. gzjoin will not compile with + versions of zlib earlier than 1.2.3. + */ + +#include /* fputs(), fprintf(), fwrite(), putc() */ +#include /* exit(), malloc(), free() */ +#include /* open() */ +#include /* close(), read(), lseek() */ +#include "zlib.h" + /* crc32(), crc32_combine(), inflateInit2(), inflate(), inflateEnd() */ + +#define local static + +/* exit with an error (return a value to allow use in an expression) */ +local int bail(char *why1, char *why2) +{ + fprintf(stderr, "gzjoin error: %s%s, output incomplete\n", why1, why2); + exit(1); + return 0; +} + +/* -- simple buffered file input with access to the buffer -- */ + +#define CHUNK 32768 /* must be a power of two and fit in unsigned */ + +/* bin buffered input file type */ +typedef struct { + char *name; /* name of file for error messages */ + int fd; /* file descriptor */ + unsigned left; /* bytes remaining at next */ + unsigned char *next; /* next byte to read */ + unsigned char *buf; /* allocated buffer of length CHUNK */ +} bin; + +/* close a buffered file and free allocated memory */ +local void bclose(bin *in) +{ + if (in != NULL) { + if (in->fd != -1) + close(in->fd); + if (in->buf != NULL) + free(in->buf); + free(in); + } +} + +/* open a buffered file for input, return a pointer to type bin, or NULL on + failure */ +local bin *bopen(char *name) +{ + bin *in; + + in = malloc(sizeof(bin)); + if (in == NULL) + return NULL; + in->buf = malloc(CHUNK); + in->fd = open(name, O_RDONLY, 0); + if (in->buf == NULL || in->fd == -1) { + bclose(in); + return NULL; + } + in->left = 0; + in->next = in->buf; + in->name = name; + return in; +} + +/* load buffer from file, return -1 on read error, 0 or 1 on success, with + 1 indicating that end-of-file was reached */ +local int bload(bin *in) +{ + long len; + + if (in == NULL) + return -1; + if (in->left != 0) + return 0; + in->next = in->buf; + do { + len = (long)read(in->fd, in->buf + in->left, CHUNK - in->left); + if (len < 0) + return -1; + in->left += (unsigned)len; + } while (len != 0 && in->left < CHUNK); + return len == 0 ? 1 : 0; +} + +/* get a byte from the file, bail if end of file */ +#define bget(in) (in->left ? 0 : bload(in), \ + in->left ? (in->left--, *(in->next)++) : \ + bail("unexpected end of file on ", in->name)) + +/* get a four-byte little-endian unsigned integer from file */ +local unsigned long bget4(bin *in) +{ + unsigned long val; + + val = bget(in); + val += (unsigned long)(bget(in)) << 8; + val += (unsigned long)(bget(in)) << 16; + val += (unsigned long)(bget(in)) << 24; + return val; +} + +/* skip bytes in file */ +local void bskip(bin *in, unsigned skip) +{ + /* check pointer */ + if (in == NULL) + return; + + /* easy case -- skip bytes in buffer */ + if (skip <= in->left) { + in->left -= skip; + in->next += skip; + return; + } + + /* skip what's in buffer, discard buffer contents */ + skip -= in->left; + in->left = 0; + + /* seek past multiples of CHUNK bytes */ + if (skip > CHUNK) { + unsigned left; + + left = skip & (CHUNK - 1); + if (left == 0) { + /* exact number of chunks: seek all the way minus one byte to check + for end-of-file with a read */ + lseek(in->fd, skip - 1, SEEK_CUR); + if (read(in->fd, in->buf, 1) != 1) + bail("unexpected end of file on ", in->name); + return; + } + + /* skip the integral chunks, update skip with remainder */ + lseek(in->fd, skip - left, SEEK_CUR); + skip = left; + } + + /* read more input and skip remainder */ + bload(in); + if (skip > in->left) + bail("unexpected end of file on ", in->name); + in->left -= skip; + in->next += skip; +} + +/* -- end of buffered input functions -- */ + +/* skip the gzip header from file in */ +local void gzhead(bin *in) +{ + int flags; + + /* verify gzip magic header and compression method */ + if (bget(in) != 0x1f || bget(in) != 0x8b || bget(in) != 8) + bail(in->name, " is not a valid gzip file"); + + /* get and verify flags */ + flags = bget(in); + if ((flags & 0xe0) != 0) + bail("unknown reserved bits set in ", in->name); + + /* skip modification time, extra flags, and os */ + bskip(in, 6); + + /* skip extra field if present */ + if (flags & 4) { + unsigned len; + + len = bget(in); + len += (unsigned)(bget(in)) << 8; + bskip(in, len); + } + + /* skip file name if present */ + if (flags & 8) + while (bget(in) != 0) + ; + + /* skip comment if present */ + if (flags & 16) + while (bget(in) != 0) + ; + + /* skip header crc if present */ + if (flags & 2) + bskip(in, 2); +} + +/* write a four-byte little-endian unsigned integer to out */ +local void put4(unsigned long val, FILE *out) +{ + putc(val & 0xff, out); + putc((val >> 8) & 0xff, out); + putc((val >> 16) & 0xff, out); + putc((val >> 24) & 0xff, out); +} + +/* Load up zlib stream from buffered input, bail if end of file */ +local void zpull(z_streamp strm, bin *in) +{ + if (in->left == 0) + bload(in); + if (in->left == 0) + bail("unexpected end of file on ", in->name); + strm->avail_in = in->left; + strm->next_in = in->next; +} + +/* Write header for gzip file to out and initialize trailer. */ +local void gzinit(unsigned long *crc, unsigned long *tot, FILE *out) +{ + fwrite("\x1f\x8b\x08\0\0\0\0\0\0\xff", 1, 10, out); + *crc = crc32(0L, Z_NULL, 0); + *tot = 0; +} + +/* Copy the compressed data from name, zeroing the last block bit of the last + block if clr is true, and adding empty blocks as needed to get to a byte + boundary. If clr is false, then the last block becomes the last block of + the output, and the gzip trailer is written. crc and tot maintains the + crc and length (modulo 2^32) of the output for the trailer. The resulting + gzip file is written to out. gzinit() must be called before the first call + of gzcopy() to write the gzip header and to initialize crc and tot. */ +local void gzcopy(char *name, int clr, unsigned long *crc, unsigned long *tot, + FILE *out) +{ + int ret; /* return value from zlib functions */ + int pos; /* where the "last block" bit is in byte */ + int last; /* true if processing the last block */ + bin *in; /* buffered input file */ + unsigned char *start; /* start of compressed data in buffer */ + unsigned char *junk; /* buffer for uncompressed data -- discarded */ + z_off_t len; /* length of uncompressed data (support > 4 GB) */ + z_stream strm; /* zlib inflate stream */ + + /* open gzip file and skip header */ + in = bopen(name); + if (in == NULL) + bail("could not open ", name); + gzhead(in); + + /* allocate buffer for uncompressed data and initialize raw inflate + stream */ + junk = malloc(CHUNK); + strm.zalloc = Z_NULL; + strm.zfree = Z_NULL; + strm.opaque = Z_NULL; + strm.avail_in = 0; + strm.next_in = Z_NULL; + ret = inflateInit2(&strm, -15); + if (junk == NULL || ret != Z_OK) + bail("out of memory", ""); + + /* inflate and copy compressed data, clear last-block bit if requested */ + len = 0; + zpull(&strm, in); + start = in->next; + last = start[0] & 1; + if (last && clr) + start[0] &= ~1; + strm.avail_out = 0; + for (;;) { + /* if input used and output done, write used input and get more */ + if (strm.avail_in == 0 && strm.avail_out != 0) { + fwrite(start, 1, strm.next_in - start, out); + start = in->buf; + in->left = 0; + zpull(&strm, in); + } + + /* decompress -- return early when end-of-block reached */ + strm.avail_out = CHUNK; + strm.next_out = junk; + ret = inflate(&strm, Z_BLOCK); + switch (ret) { + case Z_MEM_ERROR: + bail("out of memory", ""); + case Z_DATA_ERROR: + bail("invalid compressed data in ", in->name); + } + + /* update length of uncompressed data */ + len += CHUNK - strm.avail_out; + + /* check for block boundary (only get this when block copied out) */ + if (strm.data_type & 128) { + /* if that was the last block, then done */ + if (last) + break; + + /* number of unused bits in last byte */ + pos = strm.data_type & 7; + + /* find the next last-block bit */ + if (pos != 0) { + /* next last-block bit is in last used byte */ + pos = 0x100 >> pos; + last = strm.next_in[-1] & pos; + if (last && clr) + in->buf[strm.next_in - in->buf - 1] &= ~pos; + } + else { + /* next last-block bit is in next unused byte */ + if (strm.avail_in == 0) { + /* don't have that byte yet -- get it */ + fwrite(start, 1, strm.next_in - start, out); + start = in->buf; + in->left = 0; + zpull(&strm, in); + } + last = strm.next_in[0] & 1; + if (last && clr) + in->buf[strm.next_in - in->buf] &= ~1; + } + } + } + + /* update buffer with unused input */ + in->left = strm.avail_in; + in->next = in->buf + (strm.next_in - in->buf); + + /* copy used input, write empty blocks to get to byte boundary */ + pos = strm.data_type & 7; + fwrite(start, 1, in->next - start - 1, out); + last = in->next[-1]; + if (pos == 0 || !clr) + /* already at byte boundary, or last file: write last byte */ + putc(last, out); + else { + /* append empty blocks to last byte */ + last &= ((0x100 >> pos) - 1); /* assure unused bits are zero */ + if (pos & 1) { + /* odd -- append an empty stored block */ + putc(last, out); + if (pos == 1) + putc(0, out); /* two more bits in block header */ + fwrite("\0\0\xff\xff", 1, 4, out); + } + else { + /* even -- append 1, 2, or 3 empty fixed blocks */ + switch (pos) { + case 6: + putc(last | 8, out); + last = 0; + case 4: + putc(last | 0x20, out); + last = 0; + case 2: + putc(last | 0x80, out); + putc(0, out); + } + } + } + + /* update crc and tot */ + *crc = crc32_combine(*crc, bget4(in), len); + *tot += (unsigned long)len; + + /* clean up */ + inflateEnd(&strm); + free(junk); + bclose(in); + + /* write trailer if this is the last gzip file */ + if (!clr) { + put4(*crc, out); + put4(*tot, out); + } +} + +/* join the gzip files on the command line, write result to stdout */ +int main(int argc, char **argv) +{ + unsigned long crc, tot; /* running crc and total uncompressed length */ + + /* skip command name */ + argc--; + argv++; + + /* show usage if no arguments */ + if (argc == 0) { + fputs("gzjoin usage: gzjoin f1.gz [f2.gz [f3.gz ...]] > fjoin.gz\n", + stderr); + return 0; + } + + /* join gzip files on command line and write to stdout */ + gzinit(&crc, &tot, stdout); + while (argc--) + gzcopy(*argv++, argc, &crc, &tot, stdout); + + /* done */ + return 0; +} ADDED compat/zlib/examples/gzlog.c Index: compat/zlib/examples/gzlog.c ================================================================== --- /dev/null +++ compat/zlib/examples/gzlog.c @@ -0,0 +1,1059 @@ +/* + * gzlog.c + * Copyright (C) 2004, 2008, 2012, 2016 Mark Adler, all rights reserved + * For conditions of distribution and use, see copyright notice in gzlog.h + * version 2.2, 14 Aug 2012 + */ + +/* + gzlog provides a mechanism for frequently appending short strings to a gzip + file that is efficient both in execution time and compression ratio. The + strategy is to write the short strings in an uncompressed form to the end of + the gzip file, only compressing when the amount of uncompressed data has + reached a given threshold. + + gzlog also provides protection against interruptions in the process due to + system crashes. The status of the operation is recorded in an extra field + in the gzip file, and is only updated once the gzip file is brought to a + valid state. The last data to be appended or compressed is saved in an + auxiliary file, so that if the operation is interrupted, it can be completed + the next time an append operation is attempted. + + gzlog maintains another auxiliary file with the last 32K of data from the + compressed portion, which is preloaded for the compression of the subsequent + data. This minimizes the impact to the compression ratio of appending. + */ + +/* + Operations Concept: + + Files (log name "foo"): + foo.gz -- gzip file with the complete log + foo.add -- last message to append or last data to compress + foo.dict -- dictionary of the last 32K of data for next compression + foo.temp -- temporary dictionary file for compression after this one + foo.lock -- lock file for reading and writing the other files + foo.repairs -- log file for log file recovery operations (not compressed) + + gzip file structure: + - fixed-length (no file name) header with extra field (see below) + - compressed data ending initially with empty stored block + - uncompressed data filling out originally empty stored block and + subsequent stored blocks as needed (16K max each) + - gzip trailer + - no junk at end (no other gzip streams) + + When appending data, the information in the first three items above plus the + foo.add file are sufficient to recover an interrupted append operation. The + extra field has the necessary information to restore the start of the last + stored block and determine where to append the data in the foo.add file, as + well as the crc and length of the gzip data before the append operation. + + The foo.add file is created before the gzip file is marked for append, and + deleted after the gzip file is marked as complete. So if the append + operation is interrupted, the data to add will still be there. If due to + some external force, the foo.add file gets deleted between when the append + operation was interrupted and when recovery is attempted, the gzip file will + still be restored, but without the appended data. + + When compressing data, the information in the first two items above plus the + foo.add file are sufficient to recover an interrupted compress operation. + The extra field has the necessary information to find the end of the + compressed data, and contains both the crc and length of just the compressed + data and of the complete set of data including the contents of the foo.add + file. + + Again, the foo.add file is maintained during the compress operation in case + of an interruption. If in the unlikely event the foo.add file with the data + to be compressed is missing due to some external force, a gzip file with + just the previous compressed data will be reconstructed. In this case, all + of the data that was to be compressed is lost (approximately one megabyte). + This will not occur if all that happened was an interruption of the compress + operation. + + The third state that is marked is the replacement of the old dictionary with + the new dictionary after a compress operation. Once compression is + complete, the gzip file is marked as being in the replace state. This + completes the gzip file, so an interrupt after being so marked does not + result in recompression. Then the dictionary file is replaced, and the gzip + file is marked as completed. This state prevents the possibility of + restarting compression with the wrong dictionary file. + + All three operations are wrapped by a lock/unlock procedure. In order to + gain exclusive access to the log files, first a foo.lock file must be + exclusively created. When all operations are complete, the lock is + released by deleting the foo.lock file. If when attempting to create the + lock file, it already exists and the modify time of the lock file is more + than five minutes old (set by the PATIENCE define below), then the old + lock file is considered stale and deleted, and the exclusive creation of + the lock file is retried. To assure that there are no false assessments + of the staleness of the lock file, the operations periodically touch the + lock file to update the modified date. + + Following is the definition of the extra field with all of the information + required to enable the above append and compress operations and their + recovery if interrupted. Multi-byte values are stored little endian + (consistent with the gzip format). File pointers are eight bytes long. + The crc's and lengths for the gzip trailer are four bytes long. (Note that + the length at the end of a gzip file is used for error checking only, and + for large files is actually the length modulo 2^32.) The stored block + length is two bytes long. The gzip extra field two-byte identification is + "ap" for append. It is assumed that writing the extra field to the file is + an "atomic" operation. That is, either all of the extra field is written + to the file, or none of it is, if the operation is interrupted right at the + point of updating the extra field. This is a reasonable assumption, since + the extra field is within the first 52 bytes of the file, which is smaller + than any expected block size for a mass storage device (usually 512 bytes or + larger). + + Extra field (35 bytes): + - Pointer to first stored block length -- this points to the two-byte length + of the first stored block, which is followed by the two-byte, one's + complement of that length. The stored block length is preceded by the + three-bit header of the stored block, which is the actual start of the + stored block in the deflate format. See the bit offset field below. + - Pointer to the last stored block length. This is the same as above, but + for the last stored block of the uncompressed data in the gzip file. + Initially this is the same as the first stored block length pointer. + When the stored block gets to 16K (see the MAX_STORE define), then a new + stored block as added, at which point the last stored block length pointer + is different from the first stored block length pointer. When they are + different, the first bit of the last stored block header is eight bits, or + one byte back from the block length. + - Compressed data crc and length. This is the crc and length of the data + that is in the compressed portion of the deflate stream. These are used + only in the event that the foo.add file containing the data to compress is + lost after a compress operation is interrupted. + - Total data crc and length. This is the crc and length of all of the data + stored in the gzip file, compressed and uncompressed. It is used to + reconstruct the gzip trailer when compressing, as well as when recovering + interrupted operations. + - Final stored block length. This is used to quickly find where to append, + and allows the restoration of the original final stored block state when + an append operation is interrupted. + - First stored block start as the number of bits back from the final stored + block first length byte. This value is in the range of 3..10, and is + stored as the low three bits of the final byte of the extra field after + subtracting three (0..7). This allows the last-block bit of the stored + block header to be updated when a new stored block is added, for the case + when the first stored block and the last stored block are the same. (When + they are different, the numbers of bits back is known to be eight.) This + also allows for new compressed data to be appended to the old compressed + data in the compress operation, overwriting the previous first stored + block, or for the compressed data to be terminated and a valid gzip file + reconstructed on the off chance that a compression operation was + interrupted and the data to compress in the foo.add file was deleted. + - The operation in process. This is the next two bits in the last byte (the + bits under the mask 0x18). The are interpreted as 0: nothing in process, + 1: append in process, 2: compress in process, 3: replace in process. + - The top three bits of the last byte in the extra field are reserved and + are currently set to zero. + + Main procedure: + - Exclusively create the foo.lock file using the O_CREAT and O_EXCL modes of + the system open() call. If the modify time of an existing lock file is + more than PATIENCE seconds old, then the lock file is deleted and the + exclusive create is retried. + - Load the extra field from the foo.gz file, and see if an operation was in + progress but not completed. If so, apply the recovery procedure below. + - Perform the append procedure with the provided data. + - If the uncompressed data in the foo.gz file is 1MB or more, apply the + compress procedure. + - Delete the foo.lock file. + + Append procedure: + - Put what to append in the foo.add file so that the operation can be + restarted if this procedure is interrupted. + - Mark the foo.gz extra field with the append operation in progress. + + Restore the original last-block bit and stored block length of the last + stored block from the information in the extra field, in case a previous + append operation was interrupted. + - Append the provided data to the last stored block, creating new stored + blocks as needed and updating the stored blocks last-block bits and + lengths. + - Update the crc and length with the new data, and write the gzip trailer. + - Write over the extra field (with a single write operation) with the new + pointers, lengths, and crc's, and mark the gzip file as not in process. + Though there is still a foo.add file, it will be ignored since nothing + is in process. If a foo.add file is leftover from a previously + completed operation, it is truncated when writing new data to it. + - Delete the foo.add file. + + Compress and replace procedures: + - Read all of the uncompressed data in the stored blocks in foo.gz and write + it to foo.add. Also write foo.temp with the last 32K of that data to + provide a dictionary for the next invocation of this procedure. + - Rewrite the extra field marking foo.gz with a compression in process. + * If there is no data provided to compress (due to a missing foo.add file + when recovering), reconstruct and truncate the foo.gz file to contain + only the previous compressed data and proceed to the step after the next + one. Otherwise ... + - Compress the data with the dictionary in foo.dict, and write to the + foo.gz file starting at the bit immediately following the last previously + compressed block. If there is no foo.dict, proceed anyway with the + compression at slightly reduced efficiency. (For the foo.dict file to be + missing requires some external failure beyond simply the interruption of + a compress operation.) During this process, the foo.lock file is + periodically touched to assure that that file is not considered stale by + another process before we're done. The deflation is terminated with a + non-last empty static block (10 bits long), that is then located and + written over by a last-bit-set empty stored block. + - Append the crc and length of the data in the gzip file (previously + calculated during the append operations). + - Write over the extra field with the updated stored block offsets, bits + back, crc's, and lengths, and mark foo.gz as in process for a replacement + of the dictionary. + @ Delete the foo.add file. + - Replace foo.dict with foo.temp. + - Write over the extra field, marking foo.gz as complete. + + Recovery procedure: + - If not a replace recovery, read in the foo.add file, and provide that data + to the appropriate recovery below. If there is no foo.add file, provide + a zero data length to the recovery. In that case, the append recovery + restores the foo.gz to the previous compressed + uncompressed data state. + For the the compress recovery, a missing foo.add file results in foo.gz + being restored to the previous compressed-only data state. + - Append recovery: + - Pick up append at + step above + - Compress recovery: + - Pick up compress at * step above + - Replace recovery: + - Pick up compress at @ step above + - Log the repair with a date stamp in foo.repairs + */ + +#include +#include /* rename, fopen, fprintf, fclose */ +#include /* malloc, free */ +#include /* strlen, strrchr, strcpy, strncpy, strcmp */ +#include /* open */ +#include /* lseek, read, write, close, unlink, sleep, */ + /* ftruncate, fsync */ +#include /* errno */ +#include /* time, ctime */ +#include /* stat */ +#include /* utimes */ +#include "zlib.h" /* crc32 */ + +#include "gzlog.h" /* header for external access */ + +#define local static +typedef unsigned int uint; +typedef unsigned long ulong; + +/* Macro for debugging to deterministically force recovery operations */ +#ifdef GZLOG_DEBUG + #include /* longjmp */ + jmp_buf gzlog_jump; /* where to go back to */ + int gzlog_bail = 0; /* which point to bail at (1..8) */ + int gzlog_count = -1; /* number of times through to wait */ +# define BAIL(n) do { if (n == gzlog_bail && gzlog_count-- == 0) \ + longjmp(gzlog_jump, gzlog_bail); } while (0) +#else +# define BAIL(n) +#endif + +/* how old the lock file can be in seconds before considering it stale */ +#define PATIENCE 300 + +/* maximum stored block size in Kbytes -- must be in 1..63 */ +#define MAX_STORE 16 + +/* number of stored Kbytes to trigger compression (must be >= 32 to allow + dictionary construction, and <= 204 * MAX_STORE, in order for >> 10 to + discard the stored block headers contribution of five bytes each) */ +#define TRIGGER 1024 + +/* size of a deflate dictionary (this cannot be changed) */ +#define DICT 32768U + +/* values for the operation (2 bits) */ +#define NO_OP 0 +#define APPEND_OP 1 +#define COMPRESS_OP 2 +#define REPLACE_OP 3 + +/* macros to extract little-endian integers from an unsigned byte buffer */ +#define PULL2(p) ((p)[0]+((uint)((p)[1])<<8)) +#define PULL4(p) (PULL2(p)+((ulong)PULL2(p+2)<<16)) +#define PULL8(p) (PULL4(p)+((off_t)PULL4(p+4)<<32)) + +/* macros to store integers into a byte buffer in little-endian order */ +#define PUT2(p,a) do {(p)[0]=a;(p)[1]=(a)>>8;} while(0) +#define PUT4(p,a) do {PUT2(p,a);PUT2(p+2,a>>16);} while(0) +#define PUT8(p,a) do {PUT4(p,a);PUT4(p+4,a>>32);} while(0) + +/* internal structure for log information */ +#define LOGID "\106\035\172" /* should be three non-zero characters */ +struct log { + char id[4]; /* contains LOGID to detect inadvertent overwrites */ + int fd; /* file descriptor for .gz file, opened read/write */ + char *path; /* allocated path, e.g. "/var/log/foo" or "foo" */ + char *end; /* end of path, for appending suffices such as ".gz" */ + off_t first; /* offset of first stored block first length byte */ + int back; /* location of first block id in bits back from first */ + uint stored; /* bytes currently in last stored block */ + off_t last; /* offset of last stored block first length byte */ + ulong ccrc; /* crc of compressed data */ + ulong clen; /* length (modulo 2^32) of compressed data */ + ulong tcrc; /* crc of total data */ + ulong tlen; /* length (modulo 2^32) of total data */ + time_t lock; /* last modify time of our lock file */ +}; + +/* gzip header for gzlog */ +local unsigned char log_gzhead[] = { + 0x1f, 0x8b, /* magic gzip id */ + 8, /* compression method is deflate */ + 4, /* there is an extra field (no file name) */ + 0, 0, 0, 0, /* no modification time provided */ + 0, 0xff, /* no extra flags, no OS specified */ + 39, 0, 'a', 'p', 35, 0 /* extra field with "ap" subfield */ + /* 35 is EXTRA, 39 is EXTRA + 4 */ +}; + +#define HEAD sizeof(log_gzhead) /* should be 16 */ + +/* initial gzip extra field content (52 == HEAD + EXTRA + 1) */ +local unsigned char log_gzext[] = { + 52, 0, 0, 0, 0, 0, 0, 0, /* offset of first stored block length */ + 52, 0, 0, 0, 0, 0, 0, 0, /* offset of last stored block length */ + 0, 0, 0, 0, 0, 0, 0, 0, /* compressed data crc and length */ + 0, 0, 0, 0, 0, 0, 0, 0, /* total data crc and length */ + 0, 0, /* final stored block data length */ + 5 /* op is NO_OP, last bit 8 bits back */ +}; + +#define EXTRA sizeof(log_gzext) /* should be 35 */ + +/* initial gzip data and trailer */ +local unsigned char log_gzbody[] = { + 1, 0, 0, 0xff, 0xff, /* empty stored block (last) */ + 0, 0, 0, 0, /* crc */ + 0, 0, 0, 0 /* uncompressed length */ +}; + +#define BODY sizeof(log_gzbody) + +/* Exclusively create foo.lock in order to negotiate exclusive access to the + foo.* files. If the modify time of an existing lock file is greater than + PATIENCE seconds in the past, then consider the lock file to have been + abandoned, delete it, and try the exclusive create again. Save the lock + file modify time for verification of ownership. Return 0 on success, or -1 + on failure, usually due to an access restriction or invalid path. Note that + if stat() or unlink() fails, it may be due to another process noticing the + abandoned lock file a smidge sooner and deleting it, so those are not + flagged as an error. */ +local int log_lock(struct log *log) +{ + int fd; + struct stat st; + + strcpy(log->end, ".lock"); + while ((fd = open(log->path, O_CREAT | O_EXCL, 0644)) < 0) { + if (errno != EEXIST) + return -1; + if (stat(log->path, &st) == 0 && time(NULL) - st.st_mtime > PATIENCE) { + unlink(log->path); + continue; + } + sleep(2); /* relinquish the CPU for two seconds while waiting */ + } + close(fd); + if (stat(log->path, &st) == 0) + log->lock = st.st_mtime; + return 0; +} + +/* Update the modify time of the lock file to now, in order to prevent another + task from thinking that the lock is stale. Save the lock file modify time + for verification of ownership. */ +local void log_touch(struct log *log) +{ + struct stat st; + + strcpy(log->end, ".lock"); + utimes(log->path, NULL); + if (stat(log->path, &st) == 0) + log->lock = st.st_mtime; +} + +/* Check the log file modify time against what is expected. Return true if + this is not our lock. If it is our lock, touch it to keep it. */ +local int log_check(struct log *log) +{ + struct stat st; + + strcpy(log->end, ".lock"); + if (stat(log->path, &st) || st.st_mtime != log->lock) + return 1; + log_touch(log); + return 0; +} + +/* Unlock a previously acquired lock, but only if it's ours. */ +local void log_unlock(struct log *log) +{ + if (log_check(log)) + return; + strcpy(log->end, ".lock"); + unlink(log->path); + log->lock = 0; +} + +/* Check the gzip header and read in the extra field, filling in the values in + the log structure. Return op on success or -1 if the gzip header was not as + expected. op is the current operation in progress last written to the extra + field. This assumes that the gzip file has already been opened, with the + file descriptor log->fd. */ +local int log_head(struct log *log) +{ + int op; + unsigned char buf[HEAD + EXTRA]; + + if (lseek(log->fd, 0, SEEK_SET) < 0 || + read(log->fd, buf, HEAD + EXTRA) != HEAD + EXTRA || + memcmp(buf, log_gzhead, HEAD)) { + return -1; + } + log->first = PULL8(buf + HEAD); + log->last = PULL8(buf + HEAD + 8); + log->ccrc = PULL4(buf + HEAD + 16); + log->clen = PULL4(buf + HEAD + 20); + log->tcrc = PULL4(buf + HEAD + 24); + log->tlen = PULL4(buf + HEAD + 28); + log->stored = PULL2(buf + HEAD + 32); + log->back = 3 + (buf[HEAD + 34] & 7); + op = (buf[HEAD + 34] >> 3) & 3; + return op; +} + +/* Write over the extra field contents, marking the operation as op. Use fsync + to assure that the device is written to, and in the requested order. This + operation, and only this operation, is assumed to be atomic in order to + assure that the log is recoverable in the event of an interruption at any + point in the process. Return -1 if the write to foo.gz failed. */ +local int log_mark(struct log *log, int op) +{ + int ret; + unsigned char ext[EXTRA]; + + PUT8(ext, log->first); + PUT8(ext + 8, log->last); + PUT4(ext + 16, log->ccrc); + PUT4(ext + 20, log->clen); + PUT4(ext + 24, log->tcrc); + PUT4(ext + 28, log->tlen); + PUT2(ext + 32, log->stored); + ext[34] = log->back - 3 + (op << 3); + fsync(log->fd); + ret = lseek(log->fd, HEAD, SEEK_SET) < 0 || + write(log->fd, ext, EXTRA) != EXTRA ? -1 : 0; + fsync(log->fd); + return ret; +} + +/* Rewrite the last block header bits and subsequent zero bits to get to a byte + boundary, setting the last block bit if last is true, and then write the + remainder of the stored block header (length and one's complement). Leave + the file pointer after the end of the last stored block data. Return -1 if + there is a read or write failure on the foo.gz file */ +local int log_last(struct log *log, int last) +{ + int back, len, mask; + unsigned char buf[6]; + + /* determine the locations of the bytes and bits to modify */ + back = log->last == log->first ? log->back : 8; + len = back > 8 ? 2 : 1; /* bytes back from log->last */ + mask = 0x80 >> ((back - 1) & 7); /* mask for block last-bit */ + + /* get the byte to modify (one or two back) into buf[0] -- don't need to + read the byte if the last-bit is eight bits back, since in that case + the entire byte will be modified */ + buf[0] = 0; + if (back != 8 && (lseek(log->fd, log->last - len, SEEK_SET) < 0 || + read(log->fd, buf, 1) != 1)) + return -1; + + /* change the last-bit of the last stored block as requested -- note + that all bits above the last-bit are set to zero, per the type bits + of a stored block being 00 and per the convention that the bits to + bring the stream to a byte boundary are also zeros */ + buf[1] = 0; + buf[2 - len] = (*buf & (mask - 1)) + (last ? mask : 0); + + /* write the modified stored block header and lengths, move the file + pointer to after the last stored block data */ + PUT2(buf + 2, log->stored); + PUT2(buf + 4, log->stored ^ 0xffff); + return lseek(log->fd, log->last - len, SEEK_SET) < 0 || + write(log->fd, buf + 2 - len, len + 4) != len + 4 || + lseek(log->fd, log->stored, SEEK_CUR) < 0 ? -1 : 0; +} + +/* Append len bytes from data to the locked and open log file. len may be zero + if recovering and no .add file was found. In that case, the previous state + of the foo.gz file is restored. The data is appended uncompressed in + deflate stored blocks. Return -1 if there was an error reading or writing + the foo.gz file. */ +local int log_append(struct log *log, unsigned char *data, size_t len) +{ + uint put; + off_t end; + unsigned char buf[8]; + + /* set the last block last-bit and length, in case recovering an + interrupted append, then position the file pointer to append to the + block */ + if (log_last(log, 1)) + return -1; + + /* append, adding stored blocks and updating the offset of the last stored + block as needed, and update the total crc and length */ + while (len) { + /* append as much as we can to the last block */ + put = (MAX_STORE << 10) - log->stored; + if (put > len) + put = (uint)len; + if (put) { + if (write(log->fd, data, put) != put) + return -1; + BAIL(1); + log->tcrc = crc32(log->tcrc, data, put); + log->tlen += put; + log->stored += put; + data += put; + len -= put; + } + + /* if we need to, add a new empty stored block */ + if (len) { + /* mark current block as not last */ + if (log_last(log, 0)) + return -1; + + /* point to new, empty stored block */ + log->last += 4 + log->stored + 1; + log->stored = 0; + } + + /* mark last block as last, update its length */ + if (log_last(log, 1)) + return -1; + BAIL(2); + } + + /* write the new crc and length trailer, and truncate just in case (could + be recovering from partial append with a missing foo.add file) */ + PUT4(buf, log->tcrc); + PUT4(buf + 4, log->tlen); + if (write(log->fd, buf, 8) != 8 || + (end = lseek(log->fd, 0, SEEK_CUR)) < 0 || ftruncate(log->fd, end)) + return -1; + + /* write the extra field, marking the log file as done, delete .add file */ + if (log_mark(log, NO_OP)) + return -1; + strcpy(log->end, ".add"); + unlink(log->path); /* ignore error, since may not exist */ + return 0; +} + +/* Replace the foo.dict file with the foo.temp file. Also delete the foo.add + file, since the compress operation may have been interrupted before that was + done. Returns 1 if memory could not be allocated, or -1 if reading or + writing foo.gz fails, or if the rename fails for some reason other than + foo.temp not existing. foo.temp not existing is a permitted error, since + the replace operation may have been interrupted after the rename is done, + but before foo.gz is marked as complete. */ +local int log_replace(struct log *log) +{ + int ret; + char *dest; + + /* delete foo.add file */ + strcpy(log->end, ".add"); + unlink(log->path); /* ignore error, since may not exist */ + BAIL(3); + + /* rename foo.name to foo.dict, replacing foo.dict if it exists */ + strcpy(log->end, ".dict"); + dest = malloc(strlen(log->path) + 1); + if (dest == NULL) + return -2; + strcpy(dest, log->path); + strcpy(log->end, ".temp"); + ret = rename(log->path, dest); + free(dest); + if (ret && errno != ENOENT) + return -1; + BAIL(4); + + /* mark the foo.gz file as done */ + return log_mark(log, NO_OP); +} + +/* Compress the len bytes at data and append the compressed data to the + foo.gz deflate data immediately after the previous compressed data. This + overwrites the previous uncompressed data, which was stored in foo.add + and is the data provided in data[0..len-1]. If this operation is + interrupted, it picks up at the start of this routine, with the foo.add + file read in again. If there is no data to compress (len == 0), then we + simply terminate the foo.gz file after the previously compressed data, + appending a final empty stored block and the gzip trailer. Return -1 if + reading or writing the log.gz file failed, or -2 if there was a memory + allocation failure. */ +local int log_compress(struct log *log, unsigned char *data, size_t len) +{ + int fd; + uint got, max; + ssize_t dict; + off_t end; + z_stream strm; + unsigned char buf[DICT]; + + /* compress and append compressed data */ + if (len) { + /* set up for deflate, allocating memory */ + strm.zalloc = Z_NULL; + strm.zfree = Z_NULL; + strm.opaque = Z_NULL; + if (deflateInit2(&strm, Z_DEFAULT_COMPRESSION, Z_DEFLATED, -15, 8, + Z_DEFAULT_STRATEGY) != Z_OK) + return -2; + + /* read in dictionary (last 32K of data that was compressed) */ + strcpy(log->end, ".dict"); + fd = open(log->path, O_RDONLY, 0); + if (fd >= 0) { + dict = read(fd, buf, DICT); + close(fd); + if (dict < 0) { + deflateEnd(&strm); + return -1; + } + if (dict) + deflateSetDictionary(&strm, buf, (uint)dict); + } + log_touch(log); + + /* prime deflate with last bits of previous block, position write + pointer to write those bits and overwrite what follows */ + if (lseek(log->fd, log->first - (log->back > 8 ? 2 : 1), + SEEK_SET) < 0 || + read(log->fd, buf, 1) != 1 || lseek(log->fd, -1, SEEK_CUR) < 0) { + deflateEnd(&strm); + return -1; + } + deflatePrime(&strm, (8 - log->back) & 7, *buf); + + /* compress, finishing with a partial non-last empty static block */ + strm.next_in = data; + max = (((uint)0 - 1) >> 1) + 1; /* in case int smaller than size_t */ + do { + strm.avail_in = len > max ? max : (uint)len; + len -= strm.avail_in; + do { + strm.avail_out = DICT; + strm.next_out = buf; + deflate(&strm, len ? Z_NO_FLUSH : Z_PARTIAL_FLUSH); + got = DICT - strm.avail_out; + if (got && write(log->fd, buf, got) != got) { + deflateEnd(&strm); + return -1; + } + log_touch(log); + } while (strm.avail_out == 0); + } while (len); + deflateEnd(&strm); + BAIL(5); + + /* find start of empty static block -- scanning backwards the first one + bit is the second bit of the block, if the last byte is zero, then + we know the byte before that has a one in the top bit, since an + empty static block is ten bits long */ + if ((log->first = lseek(log->fd, -1, SEEK_CUR)) < 0 || + read(log->fd, buf, 1) != 1) + return -1; + log->first++; + if (*buf) { + log->back = 1; + while ((*buf & ((uint)1 << (8 - log->back++))) == 0) + ; /* guaranteed to terminate, since *buf != 0 */ + } + else + log->back = 10; + + /* update compressed crc and length */ + log->ccrc = log->tcrc; + log->clen = log->tlen; + } + else { + /* no data to compress -- fix up existing gzip stream */ + log->tcrc = log->ccrc; + log->tlen = log->clen; + } + + /* complete and truncate gzip stream */ + log->last = log->first; + log->stored = 0; + PUT4(buf, log->tcrc); + PUT4(buf + 4, log->tlen); + if (log_last(log, 1) || write(log->fd, buf, 8) != 8 || + (end = lseek(log->fd, 0, SEEK_CUR)) < 0 || ftruncate(log->fd, end)) + return -1; + BAIL(6); + + /* mark as being in the replace operation */ + if (log_mark(log, REPLACE_OP)) + return -1; + + /* execute the replace operation and mark the file as done */ + return log_replace(log); +} + +/* log a repair record to the .repairs file */ +local void log_log(struct log *log, int op, char *record) +{ + time_t now; + FILE *rec; + + now = time(NULL); + strcpy(log->end, ".repairs"); + rec = fopen(log->path, "a"); + if (rec == NULL) + return; + fprintf(rec, "%.24s %s recovery: %s\n", ctime(&now), op == APPEND_OP ? + "append" : (op == COMPRESS_OP ? "compress" : "replace"), record); + fclose(rec); + return; +} + +/* Recover the interrupted operation op. First read foo.add for recovering an + append or compress operation. Return -1 if there was an error reading or + writing foo.gz or reading an existing foo.add, or -2 if there was a memory + allocation failure. */ +local int log_recover(struct log *log, int op) +{ + int fd, ret = 0; + unsigned char *data = NULL; + size_t len = 0; + struct stat st; + + /* log recovery */ + log_log(log, op, "start"); + + /* load foo.add file if expected and present */ + if (op == APPEND_OP || op == COMPRESS_OP) { + strcpy(log->end, ".add"); + if (stat(log->path, &st) == 0 && st.st_size) { + len = (size_t)(st.st_size); + if ((off_t)len != st.st_size || + (data = malloc(st.st_size)) == NULL) { + log_log(log, op, "allocation failure"); + return -2; + } + if ((fd = open(log->path, O_RDONLY, 0)) < 0) { + log_log(log, op, ".add file read failure"); + return -1; + } + ret = (size_t)read(fd, data, len) != len; + close(fd); + if (ret) { + log_log(log, op, ".add file read failure"); + return -1; + } + log_log(log, op, "loaded .add file"); + } + else + log_log(log, op, "missing .add file!"); + } + + /* recover the interrupted operation */ + switch (op) { + case APPEND_OP: + ret = log_append(log, data, len); + break; + case COMPRESS_OP: + ret = log_compress(log, data, len); + break; + case REPLACE_OP: + ret = log_replace(log); + } + + /* log status */ + log_log(log, op, ret ? "failure" : "complete"); + + /* clean up */ + if (data != NULL) + free(data); + return ret; +} + +/* Close the foo.gz file (if open) and release the lock. */ +local void log_close(struct log *log) +{ + if (log->fd >= 0) + close(log->fd); + log->fd = -1; + log_unlock(log); +} + +/* Open foo.gz, verify the header, and load the extra field contents, after + first creating the foo.lock file to gain exclusive access to the foo.* + files. If foo.gz does not exist or is empty, then write the initial header, + extra, and body content of an empty foo.gz log file. If there is an error + creating the lock file due to access restrictions, or an error reading or + writing the foo.gz file, or if the foo.gz file is not a proper log file for + this object (e.g. not a gzip file or does not contain the expected extra + field), then return true. If there is an error, the lock is released. + Otherwise, the lock is left in place. */ +local int log_open(struct log *log) +{ + int op; + + /* release open file resource if left over -- can occur if lock lost + between gzlog_open() and gzlog_write() */ + if (log->fd >= 0) + close(log->fd); + log->fd = -1; + + /* negotiate exclusive access */ + if (log_lock(log) < 0) + return -1; + + /* open the log file, foo.gz */ + strcpy(log->end, ".gz"); + log->fd = open(log->path, O_RDWR | O_CREAT, 0644); + if (log->fd < 0) { + log_close(log); + return -1; + } + + /* if new, initialize foo.gz with an empty log, delete old dictionary */ + if (lseek(log->fd, 0, SEEK_END) == 0) { + if (write(log->fd, log_gzhead, HEAD) != HEAD || + write(log->fd, log_gzext, EXTRA) != EXTRA || + write(log->fd, log_gzbody, BODY) != BODY) { + log_close(log); + return -1; + } + strcpy(log->end, ".dict"); + unlink(log->path); + } + + /* verify log file and load extra field information */ + if ((op = log_head(log)) < 0) { + log_close(log); + return -1; + } + + /* check for interrupted process and if so, recover */ + if (op != NO_OP && log_recover(log, op)) { + log_close(log); + return -1; + } + + /* touch the lock file to prevent another process from grabbing it */ + log_touch(log); + return 0; +} + +/* See gzlog.h for the description of the external methods below */ +gzlog *gzlog_open(char *path) +{ + size_t n; + struct log *log; + + /* check arguments */ + if (path == NULL || *path == 0) + return NULL; + + /* allocate and initialize log structure */ + log = malloc(sizeof(struct log)); + if (log == NULL) + return NULL; + strcpy(log->id, LOGID); + log->fd = -1; + + /* save path and end of path for name construction */ + n = strlen(path); + log->path = malloc(n + 9); /* allow for ".repairs" */ + if (log->path == NULL) { + free(log); + return NULL; + } + strcpy(log->path, path); + log->end = log->path + n; + + /* gain exclusive access and verify log file -- may perform a + recovery operation if needed */ + if (log_open(log)) { + free(log->path); + free(log); + return NULL; + } + + /* return pointer to log structure */ + return log; +} + +/* gzlog_compress() return values: + 0: all good + -1: file i/o error (usually access issue) + -2: memory allocation failure + -3: invalid log pointer argument */ +int gzlog_compress(gzlog *logd) +{ + int fd, ret; + uint block; + size_t len, next; + unsigned char *data, buf[5]; + struct log *log = logd; + + /* check arguments */ + if (log == NULL || strcmp(log->id, LOGID)) + return -3; + + /* see if we lost the lock -- if so get it again and reload the extra + field information (it probably changed), recover last operation if + necessary */ + if (log_check(log) && log_open(log)) + return -1; + + /* create space for uncompressed data */ + len = ((size_t)(log->last - log->first) & ~(((size_t)1 << 10) - 1)) + + log->stored; + if ((data = malloc(len)) == NULL) + return -2; + + /* do statement here is just a cheap trick for error handling */ + do { + /* read in the uncompressed data */ + if (lseek(log->fd, log->first - 1, SEEK_SET) < 0) + break; + next = 0; + while (next < len) { + if (read(log->fd, buf, 5) != 5) + break; + block = PULL2(buf + 1); + if (next + block > len || + read(log->fd, (char *)data + next, block) != block) + break; + next += block; + } + if (lseek(log->fd, 0, SEEK_CUR) != log->last + 4 + log->stored) + break; + log_touch(log); + + /* write the uncompressed data to the .add file */ + strcpy(log->end, ".add"); + fd = open(log->path, O_WRONLY | O_CREAT | O_TRUNC, 0644); + if (fd < 0) + break; + ret = (size_t)write(fd, data, len) != len; + if (ret | close(fd)) + break; + log_touch(log); + + /* write the dictionary for the next compress to the .temp file */ + strcpy(log->end, ".temp"); + fd = open(log->path, O_WRONLY | O_CREAT | O_TRUNC, 0644); + if (fd < 0) + break; + next = DICT > len ? len : DICT; + ret = (size_t)write(fd, (char *)data + len - next, next) != next; + if (ret | close(fd)) + break; + log_touch(log); + + /* roll back to compressed data, mark the compress in progress */ + log->last = log->first; + log->stored = 0; + if (log_mark(log, COMPRESS_OP)) + break; + BAIL(7); + + /* compress and append the data (clears mark) */ + ret = log_compress(log, data, len); + free(data); + return ret; + } while (0); + + /* broke out of do above on i/o error */ + free(data); + return -1; +} + +/* gzlog_write() return values: + 0: all good + -1: file i/o error (usually access issue) + -2: memory allocation failure + -3: invalid log pointer argument */ +int gzlog_write(gzlog *logd, void *data, size_t len) +{ + int fd, ret; + struct log *log = logd; + + /* check arguments */ + if (log == NULL || strcmp(log->id, LOGID)) + return -3; + if (data == NULL || len <= 0) + return 0; + + /* see if we lost the lock -- if so get it again and reload the extra + field information (it probably changed), recover last operation if + necessary */ + if (log_check(log) && log_open(log)) + return -1; + + /* create and write .add file */ + strcpy(log->end, ".add"); + fd = open(log->path, O_WRONLY | O_CREAT | O_TRUNC, 0644); + if (fd < 0) + return -1; + ret = (size_t)write(fd, data, len) != len; + if (ret | close(fd)) + return -1; + log_touch(log); + + /* mark log file with append in progress */ + if (log_mark(log, APPEND_OP)) + return -1; + BAIL(8); + + /* append data (clears mark) */ + if (log_append(log, data, len)) + return -1; + + /* check to see if it's time to compress -- if not, then done */ + if (((log->last - log->first) >> 10) + (log->stored >> 10) < TRIGGER) + return 0; + + /* time to compress */ + return gzlog_compress(log); +} + +/* gzlog_close() return values: + 0: ok + -3: invalid log pointer argument */ +int gzlog_close(gzlog *logd) +{ + struct log *log = logd; + + /* check arguments */ + if (log == NULL || strcmp(log->id, LOGID)) + return -3; + + /* close the log file and release the lock */ + log_close(log); + + /* free structure and return */ + if (log->path != NULL) + free(log->path); + strcpy(log->id, "bad"); + free(log); + return 0; +} ADDED compat/zlib/examples/gzlog.h Index: compat/zlib/examples/gzlog.h ================================================================== --- /dev/null +++ compat/zlib/examples/gzlog.h @@ -0,0 +1,91 @@ +/* gzlog.h + Copyright (C) 2004, 2008, 2012 Mark Adler, all rights reserved + version 2.2, 14 Aug 2012 + + This software is provided 'as-is', without any express or implied + warranty. In no event will the author be held liable for any damages + arising from the use of this software. + + Permission is granted to anyone to use this software for any purpose, + including commercial applications, and to alter it and redistribute it + freely, subject to the following restrictions: + + 1. The origin of this software must not be misrepresented; you must not + claim that you wrote the original software. If you use this software + in a product, an acknowledgment in the product documentation would be + appreciated but is not required. + 2. Altered source versions must be plainly marked as such, and must not be + misrepresented as being the original software. + 3. This notice may not be removed or altered from any source distribution. + + Mark Adler madler@alumni.caltech.edu + */ + +/* Version History: + 1.0 26 Nov 2004 First version + 2.0 25 Apr 2008 Complete redesign for recovery of interrupted operations + Interface changed slightly in that now path is a prefix + Compression now occurs as needed during gzlog_write() + gzlog_write() now always leaves the log file as valid gzip + 2.1 8 Jul 2012 Fix argument checks in gzlog_compress() and gzlog_write() + 2.2 14 Aug 2012 Clean up signed comparisons + */ + +/* + The gzlog object allows writing short messages to a gzipped log file, + opening the log file locked for small bursts, and then closing it. The log + object works by appending stored (uncompressed) data to the gzip file until + 1 MB has been accumulated. At that time, the stored data is compressed, and + replaces the uncompressed data in the file. The log file is truncated to + its new size at that time. After each write operation, the log file is a + valid gzip file that can decompressed to recover what was written. + + The gzlog operations can be interupted at any point due to an application or + system crash, and the log file will be recovered the next time the log is + opened with gzlog_open(). + */ + +#ifndef GZLOG_H +#define GZLOG_H + +/* gzlog object type */ +typedef void gzlog; + +/* Open a gzlog object, creating the log file if it does not exist. Return + NULL on error. Note that gzlog_open() could take a while to complete if it + has to wait to verify that a lock is stale (possibly for five minutes), or + if there is significant contention with other instantiations of this object + when locking the resource. path is the prefix of the file names created by + this object. If path is "foo", then the log file will be "foo.gz", and + other auxiliary files will be created and destroyed during the process: + "foo.dict" for a compression dictionary, "foo.temp" for a temporary (next) + dictionary, "foo.add" for data being added or compressed, "foo.lock" for the + lock file, and "foo.repairs" to log recovery operations performed due to + interrupted gzlog operations. A gzlog_open() followed by a gzlog_close() + will recover a previously interrupted operation, if any. */ +gzlog *gzlog_open(char *path); + +/* Write to a gzlog object. Return zero on success, -1 if there is a file i/o + error on any of the gzlog files (this should not happen if gzlog_open() + succeeded, unless the device has run out of space or leftover auxiliary + files have permissions or ownership that prevent their use), -2 if there is + a memory allocation failure, or -3 if the log argument is invalid (e.g. if + it was not created by gzlog_open()). This function will write data to the + file uncompressed, until 1 MB has been accumulated, at which time that data + will be compressed. The log file will be a valid gzip file upon successful + return. */ +int gzlog_write(gzlog *log, void *data, size_t len); + +/* Force compression of any uncompressed data in the log. This should be used + sparingly, if at all. The main application would be when a log file will + not be appended to again. If this is used to compress frequently while + appending, it will both significantly increase the execution time and + reduce the compression ratio. The return codes are the same as for + gzlog_write(). */ +int gzlog_compress(gzlog *log); + +/* Close a gzlog object. Return zero on success, -3 if the log argument is + invalid. The log object is freed, and so cannot be referenced again. */ +int gzlog_close(gzlog *log); + +#endif ADDED compat/zlib/examples/zlib_how.html Index: compat/zlib/examples/zlib_how.html ================================================================== --- /dev/null +++ compat/zlib/examples/zlib_how.html @@ -0,0 +1,545 @@ + + + + +zlib Usage Example + + + +

zlib Usage Example

+We often get questions about how the deflate() and inflate() functions should be used. +Users wonder when they should provide more input, when they should use more output, +what to do with a Z_BUF_ERROR, how to make sure the process terminates properly, and +so on. So for those who have read zlib.h (a few times), and +would like further edification, below is an annotated example in C of simple routines to compress and decompress +from an input file to an output file using deflate() and inflate() respectively. The +annotations are interspersed between lines of the code. So please read between the lines. +We hope this helps explain some of the intricacies of zlib. +

+Without further adieu, here is the program zpipe.c: +


+/* zpipe.c: example of proper use of zlib's inflate() and deflate()
+   Not copyrighted -- provided to the public domain
+   Version 1.4  11 December 2005  Mark Adler */
+
+/* Version history:
+   1.0  30 Oct 2004  First version
+   1.1   8 Nov 2004  Add void casting for unused return values
+                     Use switch statement for inflate() return values
+   1.2   9 Nov 2004  Add assertions to document zlib guarantees
+   1.3   6 Apr 2005  Remove incorrect assertion in inf()
+   1.4  11 Dec 2005  Add hack to avoid MSDOS end-of-line conversions
+                     Avoid some compiler warnings for input and output buffers
+ */
+
+We now include the header files for the required definitions. From +stdio.h we use fopen(), fread(), fwrite(), +feof(), ferror(), and fclose() for file i/o, and +fputs() for error messages. From string.h we use +strcmp() for command line argument processing. +From assert.h we use the assert() macro. +From zlib.h +we use the basic compression functions deflateInit(), +deflate(), and deflateEnd(), and the basic decompression +functions inflateInit(), inflate(), and +inflateEnd(). +

+#include <stdio.h>
+#include <string.h>
+#include <assert.h>
+#include "zlib.h"
+
+This is an ugly hack required to avoid corruption of the input and output data on +Windows/MS-DOS systems. Without this, those systems would assume that the input and output +files are text, and try to convert the end-of-line characters from one standard to +another. That would corrupt binary data, and in particular would render the compressed data unusable. +This sets the input and output to binary which suppresses the end-of-line conversions. +SET_BINARY_MODE() will be used later on stdin and stdout, at the beginning of main(). +

+#if defined(MSDOS) || defined(OS2) || defined(WIN32) || defined(__CYGWIN__)
+#  include <fcntl.h>
+#  include <io.h>
+#  define SET_BINARY_MODE(file) setmode(fileno(file), O_BINARY)
+#else
+#  define SET_BINARY_MODE(file)
+#endif
+
+CHUNK is simply the buffer size for feeding data to and pulling data +from the zlib routines. Larger buffer sizes would be more efficient, +especially for inflate(). If the memory is available, buffers sizes +on the order of 128K or 256K bytes should be used. +

+#define CHUNK 16384
+
+The def() routine compresses data from an input file to an output file. The output data +will be in the zlib format, which is different from the gzip or zip +formats. The zlib format has a very small header of only two bytes to identify it as +a zlib stream and to provide decoding information, and a four-byte trailer with a fast +check value to verify the integrity of the uncompressed data after decoding. +

+/* Compress from file source to file dest until EOF on source.
+   def() returns Z_OK on success, Z_MEM_ERROR if memory could not be
+   allocated for processing, Z_STREAM_ERROR if an invalid compression
+   level is supplied, Z_VERSION_ERROR if the version of zlib.h and the
+   version of the library linked do not match, or Z_ERRNO if there is
+   an error reading or writing the files. */
+int def(FILE *source, FILE *dest, int level)
+{
+
+Here are the local variables for def(). ret will be used for zlib +return codes. flush will keep track of the current flushing state for deflate(), +which is either no flushing, or flush to completion after the end of the input file is reached. +have is the amount of data returned from deflate(). The strm structure +is used to pass information to and from the zlib routines, and to maintain the +deflate() state. in and out are the input and output buffers for +deflate(). +

+    int ret, flush;
+    unsigned have;
+    z_stream strm;
+    unsigned char in[CHUNK];
+    unsigned char out[CHUNK];
+
+The first thing we do is to initialize the zlib state for compression using +deflateInit(). This must be done before the first use of deflate(). +The zalloc, zfree, and opaque fields in the strm +structure must be initialized before calling deflateInit(). Here they are +set to the zlib constant Z_NULL to request that zlib use +the default memory allocation routines. An application may also choose to provide +custom memory allocation routines here. deflateInit() will allocate on the +order of 256K bytes for the internal state. +(See zlib Technical Details.) +

+deflateInit() is called with a pointer to the structure to be initialized and +the compression level, which is an integer in the range of -1 to 9. Lower compression +levels result in faster execution, but less compression. Higher levels result in +greater compression, but slower execution. The zlib constant Z_DEFAULT_COMPRESSION, +equal to -1, +provides a good compromise between compression and speed and is equivalent to level 6. +Level 0 actually does no compression at all, and in fact expands the data slightly to produce +the zlib format (it is not a byte-for-byte copy of the input). +More advanced applications of zlib +may use deflateInit2() here instead. Such an application may want to reduce how +much memory will be used, at some price in compression. Or it may need to request a +gzip header and trailer instead of a zlib header and trailer, or raw +encoding with no header or trailer at all. +

+We must check the return value of deflateInit() against the zlib constant +Z_OK to make sure that it was able to +allocate memory for the internal state, and that the provided arguments were valid. +deflateInit() will also check that the version of zlib that the zlib.h +file came from matches the version of zlib actually linked with the program. This +is especially important for environments in which zlib is a shared library. +

+Note that an application can initialize multiple, independent zlib streams, which can +operate in parallel. The state information maintained in the structure allows the zlib +routines to be reentrant. +


+    /* allocate deflate state */
+    strm.zalloc = Z_NULL;
+    strm.zfree = Z_NULL;
+    strm.opaque = Z_NULL;
+    ret = deflateInit(&strm, level);
+    if (ret != Z_OK)
+        return ret;
+
+With the pleasantries out of the way, now we can get down to business. The outer do-loop +reads all of the input file and exits at the bottom of the loop once end-of-file is reached. +This loop contains the only call of deflate(). So we must make sure that all of the +input data has been processed and that all of the output data has been generated and consumed +before we fall out of the loop at the bottom. +

+    /* compress until end of file */
+    do {
+
+We start off by reading data from the input file. The number of bytes read is put directly +into avail_in, and a pointer to those bytes is put into next_in. We also +check to see if end-of-file on the input has been reached. If we are at the end of file, then flush is set to the +zlib constant Z_FINISH, which is later passed to deflate() to +indicate that this is the last chunk of input data to compress. We need to use feof() +to check for end-of-file as opposed to seeing if fewer than CHUNK bytes have been read. The +reason is that if the input file length is an exact multiple of CHUNK, we will miss +the fact that we got to the end-of-file, and not know to tell deflate() to finish +up the compressed stream. If we are not yet at the end of the input, then the zlib +constant Z_NO_FLUSH will be passed to deflate to indicate that we are still +in the middle of the uncompressed data. +

+If there is an error in reading from the input file, the process is aborted with +deflateEnd() being called to free the allocated zlib state before returning +the error. We wouldn't want a memory leak, now would we? deflateEnd() can be called +at any time after the state has been initialized. Once that's done, deflateInit() (or +deflateInit2()) would have to be called to start a new compression process. There is +no point here in checking the deflateEnd() return code. The deallocation can't fail. +


+        strm.avail_in = fread(in, 1, CHUNK, source);
+        if (ferror(source)) {
+            (void)deflateEnd(&strm);
+            return Z_ERRNO;
+        }
+        flush = feof(source) ? Z_FINISH : Z_NO_FLUSH;
+        strm.next_in = in;
+
+The inner do-loop passes our chunk of input data to deflate(), and then +keeps calling deflate() until it is done producing output. Once there is no more +new output, deflate() is guaranteed to have consumed all of the input, i.e., +avail_in will be zero. +

+        /* run deflate() on input until output buffer not full, finish
+           compression if all of source has been read in */
+        do {
+
+Output space is provided to deflate() by setting avail_out to the number +of available output bytes and next_out to a pointer to that space. +

+            strm.avail_out = CHUNK;
+            strm.next_out = out;
+
+Now we call the compression engine itself, deflate(). It takes as many of the +avail_in bytes at next_in as it can process, and writes as many as +avail_out bytes to next_out. Those counters and pointers are then +updated past the input data consumed and the output data written. It is the amount of +output space available that may limit how much input is consumed. +Hence the inner loop to make sure that +all of the input is consumed by providing more output space each time. Since avail_in +and next_in are updated by deflate(), we don't have to mess with those +between deflate() calls until it's all used up. +

+The parameters to deflate() are a pointer to the strm structure containing +the input and output information and the internal compression engine state, and a parameter +indicating whether and how to flush data to the output. Normally deflate will consume +several K bytes of input data before producing any output (except for the header), in order +to accumulate statistics on the data for optimum compression. It will then put out a burst of +compressed data, and proceed to consume more input before the next burst. Eventually, +deflate() +must be told to terminate the stream, complete the compression with provided input data, and +write out the trailer check value. deflate() will continue to compress normally as long +as the flush parameter is Z_NO_FLUSH. Once the Z_FINISH parameter is provided, +deflate() will begin to complete the compressed output stream. However depending on how +much output space is provided, deflate() may have to be called several times until it +has provided the complete compressed stream, even after it has consumed all of the input. The flush +parameter must continue to be Z_FINISH for those subsequent calls. +

+There are other values of the flush parameter that are used in more advanced applications. You can +force deflate() to produce a burst of output that encodes all of the input data provided +so far, even if it wouldn't have otherwise, for example to control data latency on a link with +compressed data. You can also ask that deflate() do that as well as erase any history up to +that point so that what follows can be decompressed independently, for example for random access +applications. Both requests will degrade compression by an amount depending on how often such +requests are made. +

+deflate() has a return value that can indicate errors, yet we do not check it here. Why +not? Well, it turns out that deflate() can do no wrong here. Let's go through +deflate()'s return values and dispense with them one by one. The possible values are +Z_OK, Z_STREAM_END, Z_STREAM_ERROR, or Z_BUF_ERROR. Z_OK +is, well, ok. Z_STREAM_END is also ok and will be returned for the last call of +deflate(). This is already guaranteed by calling deflate() with Z_FINISH +until it has no more output. Z_STREAM_ERROR is only possible if the stream is not +initialized properly, but we did initialize it properly. There is no harm in checking for +Z_STREAM_ERROR here, for example to check for the possibility that some +other part of the application inadvertently clobbered the memory containing the zlib state. +Z_BUF_ERROR will be explained further below, but +suffice it to say that this is simply an indication that deflate() could not consume +more input or produce more output. deflate() can be called again with more output space +or more available input, which it will be in this code. +


+            ret = deflate(&strm, flush);    /* no bad return value */
+            assert(ret != Z_STREAM_ERROR);  /* state not clobbered */
+
+Now we compute how much output deflate() provided on the last call, which is the +difference between how much space was provided before the call, and how much output space +is still available after the call. Then that data, if any, is written to the output file. +We can then reuse the output buffer for the next call of deflate(). Again if there +is a file i/o error, we call deflateEnd() before returning to avoid a memory leak. +

+            have = CHUNK - strm.avail_out;
+            if (fwrite(out, 1, have, dest) != have || ferror(dest)) {
+                (void)deflateEnd(&strm);
+                return Z_ERRNO;
+            }
+
+The inner do-loop is repeated until the last deflate() call fails to fill the +provided output buffer. Then we know that deflate() has done as much as it can with +the provided input, and that all of that input has been consumed. We can then fall out of this +loop and reuse the input buffer. +

+The way we tell that deflate() has no more output is by seeing that it did not fill +the output buffer, leaving avail_out greater than zero. However suppose that +deflate() has no more output, but just so happened to exactly fill the output buffer! +avail_out is zero, and we can't tell that deflate() has done all it can. +As far as we know, deflate() +has more output for us. So we call it again. But now deflate() produces no output +at all, and avail_out remains unchanged as CHUNK. That deflate() call +wasn't able to do anything, either consume input or produce output, and so it returns +Z_BUF_ERROR. (See, I told you I'd cover this later.) However this is not a problem at +all. Now we finally have the desired indication that deflate() is really done, +and so we drop out of the inner loop to provide more input to deflate(). +

+With flush set to Z_FINISH, this final set of deflate() calls will +complete the output stream. Once that is done, subsequent calls of deflate() would return +Z_STREAM_ERROR if the flush parameter is not Z_FINISH, and do no more processing +until the state is reinitialized. +

+Some applications of zlib have two loops that call deflate() +instead of the single inner loop we have here. The first loop would call +without flushing and feed all of the data to deflate(). The second loop would call +deflate() with no more +data and the Z_FINISH parameter to complete the process. As you can see from this +example, that can be avoided by simply keeping track of the current flush state. +


+        } while (strm.avail_out == 0);
+        assert(strm.avail_in == 0);     /* all input will be used */
+
+Now we check to see if we have already processed all of the input file. That information was +saved in the flush variable, so we see if that was set to Z_FINISH. If so, +then we're done and we fall out of the outer loop. We're guaranteed to get Z_STREAM_END +from the last deflate() call, since we ran it until the last chunk of input was +consumed and all of the output was generated. +

+        /* done when last data in file processed */
+    } while (flush != Z_FINISH);
+    assert(ret == Z_STREAM_END);        /* stream will be complete */
+
+The process is complete, but we still need to deallocate the state to avoid a memory leak +(or rather more like a memory hemorrhage if you didn't do this). Then +finally we can return with a happy return value. +

+    /* clean up and return */
+    (void)deflateEnd(&strm);
+    return Z_OK;
+}
+
+Now we do the same thing for decompression in the inf() routine. inf() +decompresses what is hopefully a valid zlib stream from the input file and writes the +uncompressed data to the output file. Much of the discussion above for def() +applies to inf() as well, so the discussion here will focus on the differences between +the two. +

+/* Decompress from file source to file dest until stream ends or EOF.
+   inf() returns Z_OK on success, Z_MEM_ERROR if memory could not be
+   allocated for processing, Z_DATA_ERROR if the deflate data is
+   invalid or incomplete, Z_VERSION_ERROR if the version of zlib.h and
+   the version of the library linked do not match, or Z_ERRNO if there
+   is an error reading or writing the files. */
+int inf(FILE *source, FILE *dest)
+{
+
+The local variables have the same functionality as they do for def(). The +only difference is that there is no flush variable, since inflate() +can tell from the zlib stream itself when the stream is complete. +

+    int ret;
+    unsigned have;
+    z_stream strm;
+    unsigned char in[CHUNK];
+    unsigned char out[CHUNK];
+
+The initialization of the state is the same, except that there is no compression level, +of course, and two more elements of the structure are initialized. avail_in +and next_in must be initialized before calling inflateInit(). This +is because the application has the option to provide the start of the zlib stream in +order for inflateInit() to have access to information about the compression +method to aid in memory allocation. In the current implementation of zlib +(up through versions 1.2.x), the method-dependent memory allocations are deferred to the first call of +inflate() anyway. However those fields must be initialized since later versions +of zlib that provide more compression methods may take advantage of this interface. +In any case, no decompression is performed by inflateInit(), so the +avail_out and next_out fields do not need to be initialized before calling. +

+Here avail_in is set to zero and next_in is set to Z_NULL to +indicate that no input data is being provided. +


+    /* allocate inflate state */
+    strm.zalloc = Z_NULL;
+    strm.zfree = Z_NULL;
+    strm.opaque = Z_NULL;
+    strm.avail_in = 0;
+    strm.next_in = Z_NULL;
+    ret = inflateInit(&strm);
+    if (ret != Z_OK)
+        return ret;
+
+The outer do-loop decompresses input until inflate() indicates +that it has reached the end of the compressed data and has produced all of the uncompressed +output. This is in contrast to def() which processes all of the input file. +If end-of-file is reached before the compressed data self-terminates, then the compressed +data is incomplete and an error is returned. +

+    /* decompress until deflate stream ends or end of file */
+    do {
+
+We read input data and set the strm structure accordingly. If we've reached the +end of the input file, then we leave the outer loop and report an error, since the +compressed data is incomplete. Note that we may read more data than is eventually consumed +by inflate(), if the input file continues past the zlib stream. +For applications where zlib streams are embedded in other data, this routine would +need to be modified to return the unused data, or at least indicate how much of the input +data was not used, so the application would know where to pick up after the zlib stream. +

+        strm.avail_in = fread(in, 1, CHUNK, source);
+        if (ferror(source)) {
+            (void)inflateEnd(&strm);
+            return Z_ERRNO;
+        }
+        if (strm.avail_in == 0)
+            break;
+        strm.next_in = in;
+
+The inner do-loop has the same function it did in def(), which is to +keep calling inflate() until has generated all of the output it can with the +provided input. +

+        /* run inflate() on input until output buffer not full */
+        do {
+
+Just like in def(), the same output space is provided for each call of inflate(). +

+            strm.avail_out = CHUNK;
+            strm.next_out = out;
+
+Now we run the decompression engine itself. There is no need to adjust the flush parameter, since +the zlib format is self-terminating. The main difference here is that there are +return values that we need to pay attention to. Z_DATA_ERROR +indicates that inflate() detected an error in the zlib compressed data format, +which means that either the data is not a zlib stream to begin with, or that the data was +corrupted somewhere along the way since it was compressed. The other error to be processed is +Z_MEM_ERROR, which can occur since memory allocation is deferred until inflate() +needs it, unlike deflate(), whose memory is allocated at the start by deflateInit(). +

+Advanced applications may use +deflateSetDictionary() to prime deflate() with a set of likely data to improve the +first 32K or so of compression. This is noted in the zlib header, so inflate() +requests that that dictionary be provided before it can start to decompress. Without the dictionary, +correct decompression is not possible. For this routine, we have no idea what the dictionary is, +so the Z_NEED_DICT indication is converted to a Z_DATA_ERROR. +

+inflate() can also return Z_STREAM_ERROR, which should not be possible here, +but could be checked for as noted above for def(). Z_BUF_ERROR does not need to be +checked for here, for the same reasons noted for def(). Z_STREAM_END will be +checked for later. +


+            ret = inflate(&strm, Z_NO_FLUSH);
+            assert(ret != Z_STREAM_ERROR);  /* state not clobbered */
+            switch (ret) {
+            case Z_NEED_DICT:
+                ret = Z_DATA_ERROR;     /* and fall through */
+            case Z_DATA_ERROR:
+            case Z_MEM_ERROR:
+                (void)inflateEnd(&strm);
+                return ret;
+            }
+
+The output of inflate() is handled identically to that of deflate(). +

+            have = CHUNK - strm.avail_out;
+            if (fwrite(out, 1, have, dest) != have || ferror(dest)) {
+                (void)inflateEnd(&strm);
+                return Z_ERRNO;
+            }
+
+The inner do-loop ends when inflate() has no more output as indicated +by not filling the output buffer, just as for deflate(). In this case, we cannot +assert that strm.avail_in will be zero, since the deflate stream may end before the file +does. +

+        } while (strm.avail_out == 0);
+
+The outer do-loop ends when inflate() reports that it has reached the +end of the input zlib stream, has completed the decompression and integrity +check, and has provided all of the output. This is indicated by the inflate() +return value Z_STREAM_END. The inner loop is guaranteed to leave ret +equal to Z_STREAM_END if the last chunk of the input file read contained the end +of the zlib stream. So if the return value is not Z_STREAM_END, the +loop continues to read more input. +

+        /* done when inflate() says it's done */
+    } while (ret != Z_STREAM_END);
+
+At this point, decompression successfully completed, or we broke out of the loop due to no +more data being available from the input file. If the last inflate() return value +is not Z_STREAM_END, then the zlib stream was incomplete and a data error +is returned. Otherwise, we return with a happy return value. Of course, inflateEnd() +is called first to avoid a memory leak. +

+    /* clean up and return */
+    (void)inflateEnd(&strm);
+    return ret == Z_STREAM_END ? Z_OK : Z_DATA_ERROR;
+}
+
+That ends the routines that directly use zlib. The following routines make this +a command-line program by running data through the above routines from stdin to +stdout, and handling any errors reported by def() or inf(). +

+zerr() is used to interpret the possible error codes from def() +and inf(), as detailed in their comments above, and print out an error message. +Note that these are only a subset of the possible return values from deflate() +and inflate(). +


+/* report a zlib or i/o error */
+void zerr(int ret)
+{
+    fputs("zpipe: ", stderr);
+    switch (ret) {
+    case Z_ERRNO:
+        if (ferror(stdin))
+            fputs("error reading stdin\n", stderr);
+        if (ferror(stdout))
+            fputs("error writing stdout\n", stderr);
+        break;
+    case Z_STREAM_ERROR:
+        fputs("invalid compression level\n", stderr);
+        break;
+    case Z_DATA_ERROR:
+        fputs("invalid or incomplete deflate data\n", stderr);
+        break;
+    case Z_MEM_ERROR:
+        fputs("out of memory\n", stderr);
+        break;
+    case Z_VERSION_ERROR:
+        fputs("zlib version mismatch!\n", stderr);
+    }
+}
+
+Here is the main() routine used to test def() and inf(). The +zpipe command is simply a compression pipe from stdin to stdout, if +no arguments are given, or it is a decompression pipe if zpipe -d is used. If any other +arguments are provided, no compression or decompression is performed. Instead a usage +message is displayed. Examples are zpipe < foo.txt > foo.txt.z to compress, and +zpipe -d < foo.txt.z > foo.txt to decompress. +

+/* compress or decompress from stdin to stdout */
+int main(int argc, char **argv)
+{
+    int ret;
+
+    /* avoid end-of-line conversions */
+    SET_BINARY_MODE(stdin);
+    SET_BINARY_MODE(stdout);
+
+    /* do compression if no arguments */
+    if (argc == 1) {
+        ret = def(stdin, stdout, Z_DEFAULT_COMPRESSION);
+        if (ret != Z_OK)
+            zerr(ret);
+        return ret;
+    }
+
+    /* do decompression if -d specified */
+    else if (argc == 2 && strcmp(argv[1], "-d") == 0) {
+        ret = inf(stdin, stdout);
+        if (ret != Z_OK)
+            zerr(ret);
+        return ret;
+    }
+
+    /* otherwise, report usage */
+    else {
+        fputs("zpipe usage: zpipe [-d] < source > dest\n", stderr);
+        return 1;
+    }
+}
+
+
+Copyright (c) 2004, 2005 by Mark Adler
Last modified 11 December 2005
+ + ADDED compat/zlib/examples/zpipe.c Index: compat/zlib/examples/zpipe.c ================================================================== --- /dev/null +++ compat/zlib/examples/zpipe.c @@ -0,0 +1,205 @@ +/* zpipe.c: example of proper use of zlib's inflate() and deflate() + Not copyrighted -- provided to the public domain + Version 1.4 11 December 2005 Mark Adler */ + +/* Version history: + 1.0 30 Oct 2004 First version + 1.1 8 Nov 2004 Add void casting for unused return values + Use switch statement for inflate() return values + 1.2 9 Nov 2004 Add assertions to document zlib guarantees + 1.3 6 Apr 2005 Remove incorrect assertion in inf() + 1.4 11 Dec 2005 Add hack to avoid MSDOS end-of-line conversions + Avoid some compiler warnings for input and output buffers + */ + +#include +#include +#include +#include "zlib.h" + +#if defined(MSDOS) || defined(OS2) || defined(WIN32) || defined(__CYGWIN__) +# include +# include +# define SET_BINARY_MODE(file) setmode(fileno(file), O_BINARY) +#else +# define SET_BINARY_MODE(file) +#endif + +#define CHUNK 16384 + +/* Compress from file source to file dest until EOF on source. + def() returns Z_OK on success, Z_MEM_ERROR if memory could not be + allocated for processing, Z_STREAM_ERROR if an invalid compression + level is supplied, Z_VERSION_ERROR if the version of zlib.h and the + version of the library linked do not match, or Z_ERRNO if there is + an error reading or writing the files. */ +int def(FILE *source, FILE *dest, int level) +{ + int ret, flush; + unsigned have; + z_stream strm; + unsigned char in[CHUNK]; + unsigned char out[CHUNK]; + + /* allocate deflate state */ + strm.zalloc = Z_NULL; + strm.zfree = Z_NULL; + strm.opaque = Z_NULL; + ret = deflateInit(&strm, level); + if (ret != Z_OK) + return ret; + + /* compress until end of file */ + do { + strm.avail_in = fread(in, 1, CHUNK, source); + if (ferror(source)) { + (void)deflateEnd(&strm); + return Z_ERRNO; + } + flush = feof(source) ? Z_FINISH : Z_NO_FLUSH; + strm.next_in = in; + + /* run deflate() on input until output buffer not full, finish + compression if all of source has been read in */ + do { + strm.avail_out = CHUNK; + strm.next_out = out; + ret = deflate(&strm, flush); /* no bad return value */ + assert(ret != Z_STREAM_ERROR); /* state not clobbered */ + have = CHUNK - strm.avail_out; + if (fwrite(out, 1, have, dest) != have || ferror(dest)) { + (void)deflateEnd(&strm); + return Z_ERRNO; + } + } while (strm.avail_out == 0); + assert(strm.avail_in == 0); /* all input will be used */ + + /* done when last data in file processed */ + } while (flush != Z_FINISH); + assert(ret == Z_STREAM_END); /* stream will be complete */ + + /* clean up and return */ + (void)deflateEnd(&strm); + return Z_OK; +} + +/* Decompress from file source to file dest until stream ends or EOF. + inf() returns Z_OK on success, Z_MEM_ERROR if memory could not be + allocated for processing, Z_DATA_ERROR if the deflate data is + invalid or incomplete, Z_VERSION_ERROR if the version of zlib.h and + the version of the library linked do not match, or Z_ERRNO if there + is an error reading or writing the files. */ +int inf(FILE *source, FILE *dest) +{ + int ret; + unsigned have; + z_stream strm; + unsigned char in[CHUNK]; + unsigned char out[CHUNK]; + + /* allocate inflate state */ + strm.zalloc = Z_NULL; + strm.zfree = Z_NULL; + strm.opaque = Z_NULL; + strm.avail_in = 0; + strm.next_in = Z_NULL; + ret = inflateInit(&strm); + if (ret != Z_OK) + return ret; + + /* decompress until deflate stream ends or end of file */ + do { + strm.avail_in = fread(in, 1, CHUNK, source); + if (ferror(source)) { + (void)inflateEnd(&strm); + return Z_ERRNO; + } + if (strm.avail_in == 0) + break; + strm.next_in = in; + + /* run inflate() on input until output buffer not full */ + do { + strm.avail_out = CHUNK; + strm.next_out = out; + ret = inflate(&strm, Z_NO_FLUSH); + assert(ret != Z_STREAM_ERROR); /* state not clobbered */ + switch (ret) { + case Z_NEED_DICT: + ret = Z_DATA_ERROR; /* and fall through */ + case Z_DATA_ERROR: + case Z_MEM_ERROR: + (void)inflateEnd(&strm); + return ret; + } + have = CHUNK - strm.avail_out; + if (fwrite(out, 1, have, dest) != have || ferror(dest)) { + (void)inflateEnd(&strm); + return Z_ERRNO; + } + } while (strm.avail_out == 0); + + /* done when inflate() says it's done */ + } while (ret != Z_STREAM_END); + + /* clean up and return */ + (void)inflateEnd(&strm); + return ret == Z_STREAM_END ? Z_OK : Z_DATA_ERROR; +} + +/* report a zlib or i/o error */ +void zerr(int ret) +{ + fputs("zpipe: ", stderr); + switch (ret) { + case Z_ERRNO: + if (ferror(stdin)) + fputs("error reading stdin\n", stderr); + if (ferror(stdout)) + fputs("error writing stdout\n", stderr); + break; + case Z_STREAM_ERROR: + fputs("invalid compression level\n", stderr); + break; + case Z_DATA_ERROR: + fputs("invalid or incomplete deflate data\n", stderr); + break; + case Z_MEM_ERROR: + fputs("out of memory\n", stderr); + break; + case Z_VERSION_ERROR: + fputs("zlib version mismatch!\n", stderr); + } +} + +/* compress or decompress from stdin to stdout */ +int main(int argc, char **argv) +{ + int ret; + + /* avoid end-of-line conversions */ + SET_BINARY_MODE(stdin); + SET_BINARY_MODE(stdout); + + /* do compression if no arguments */ + if (argc == 1) { + ret = def(stdin, stdout, Z_DEFAULT_COMPRESSION); + if (ret != Z_OK) + zerr(ret); + return ret; + } + + /* do decompression if -d specified */ + else if (argc == 2 && strcmp(argv[1], "-d") == 0) { + ret = inf(stdin, stdout); + if (ret != Z_OK) + zerr(ret); + return ret; + } + + /* otherwise, report usage */ + else { + fputs("zpipe usage: zpipe [-d] < source > dest\n", stderr); + return 1; + } +} ADDED compat/zlib/examples/zran.c Index: compat/zlib/examples/zran.c ================================================================== --- /dev/null +++ compat/zlib/examples/zran.c @@ -0,0 +1,409 @@ +/* zran.c -- example of zlib/gzip stream indexing and random access + * Copyright (C) 2005, 2012 Mark Adler + * For conditions of distribution and use, see copyright notice in zlib.h + Version 1.1 29 Sep 2012 Mark Adler */ + +/* Version History: + 1.0 29 May 2005 First version + 1.1 29 Sep 2012 Fix memory reallocation error + */ + +/* Illustrate the use of Z_BLOCK, inflatePrime(), and inflateSetDictionary() + for random access of a compressed file. A file containing a zlib or gzip + stream is provided on the command line. The compressed stream is decoded in + its entirety, and an index built with access points about every SPAN bytes + in the uncompressed output. The compressed file is left open, and can then + be read randomly, having to decompress on the average SPAN/2 uncompressed + bytes before getting to the desired block of data. + + An access point can be created at the start of any deflate block, by saving + the starting file offset and bit of that block, and the 32K bytes of + uncompressed data that precede that block. Also the uncompressed offset of + that block is saved to provide a referece for locating a desired starting + point in the uncompressed stream. build_index() works by decompressing the + input zlib or gzip stream a block at a time, and at the end of each block + deciding if enough uncompressed data has gone by to justify the creation of + a new access point. If so, that point is saved in a data structure that + grows as needed to accommodate the points. + + To use the index, an offset in the uncompressed data is provided, for which + the latest access point at or preceding that offset is located in the index. + The input file is positioned to the specified location in the index, and if + necessary the first few bits of the compressed data is read from the file. + inflate is initialized with those bits and the 32K of uncompressed data, and + the decompression then proceeds until the desired offset in the file is + reached. Then the decompression continues to read the desired uncompressed + data from the file. + + Another approach would be to generate the index on demand. In that case, + requests for random access reads from the compressed data would try to use + the index, but if a read far enough past the end of the index is required, + then further index entries would be generated and added. + + There is some fair bit of overhead to starting inflation for the random + access, mainly copying the 32K byte dictionary. So if small pieces of the + file are being accessed, it would make sense to implement a cache to hold + some lookahead and avoid many calls to extract() for small lengths. + + Another way to build an index would be to use inflateCopy(). That would + not be constrained to have access points at block boundaries, but requires + more memory per access point, and also cannot be saved to file due to the + use of pointers in the state. The approach here allows for storage of the + index in a file. + */ + +#include +#include +#include +#include "zlib.h" + +#define local static + +#define SPAN 1048576L /* desired distance between access points */ +#define WINSIZE 32768U /* sliding window size */ +#define CHUNK 16384 /* file input buffer size */ + +/* access point entry */ +struct point { + off_t out; /* corresponding offset in uncompressed data */ + off_t in; /* offset in input file of first full byte */ + int bits; /* number of bits (1-7) from byte at in - 1, or 0 */ + unsigned char window[WINSIZE]; /* preceding 32K of uncompressed data */ +}; + +/* access point list */ +struct access { + int have; /* number of list entries filled in */ + int size; /* number of list entries allocated */ + struct point *list; /* allocated list */ +}; + +/* Deallocate an index built by build_index() */ +local void free_index(struct access *index) +{ + if (index != NULL) { + free(index->list); + free(index); + } +} + +/* Add an entry to the access point list. If out of memory, deallocate the + existing list and return NULL. */ +local struct access *addpoint(struct access *index, int bits, + off_t in, off_t out, unsigned left, unsigned char *window) +{ + struct point *next; + + /* if list is empty, create it (start with eight points) */ + if (index == NULL) { + index = malloc(sizeof(struct access)); + if (index == NULL) return NULL; + index->list = malloc(sizeof(struct point) << 3); + if (index->list == NULL) { + free(index); + return NULL; + } + index->size = 8; + index->have = 0; + } + + /* if list is full, make it bigger */ + else if (index->have == index->size) { + index->size <<= 1; + next = realloc(index->list, sizeof(struct point) * index->size); + if (next == NULL) { + free_index(index); + return NULL; + } + index->list = next; + } + + /* fill in entry and increment how many we have */ + next = index->list + index->have; + next->bits = bits; + next->in = in; + next->out = out; + if (left) + memcpy(next->window, window + WINSIZE - left, left); + if (left < WINSIZE) + memcpy(next->window + left, window, WINSIZE - left); + index->have++; + + /* return list, possibly reallocated */ + return index; +} + +/* Make one entire pass through the compressed stream and build an index, with + access points about every span bytes of uncompressed output -- span is + chosen to balance the speed of random access against the memory requirements + of the list, about 32K bytes per access point. Note that data after the end + of the first zlib or gzip stream in the file is ignored. build_index() + returns the number of access points on success (>= 1), Z_MEM_ERROR for out + of memory, Z_DATA_ERROR for an error in the input file, or Z_ERRNO for a + file read error. On success, *built points to the resulting index. */ +local int build_index(FILE *in, off_t span, struct access **built) +{ + int ret; + off_t totin, totout; /* our own total counters to avoid 4GB limit */ + off_t last; /* totout value of last access point */ + struct access *index; /* access points being generated */ + z_stream strm; + unsigned char input[CHUNK]; + unsigned char window[WINSIZE]; + + /* initialize inflate */ + strm.zalloc = Z_NULL; + strm.zfree = Z_NULL; + strm.opaque = Z_NULL; + strm.avail_in = 0; + strm.next_in = Z_NULL; + ret = inflateInit2(&strm, 47); /* automatic zlib or gzip decoding */ + if (ret != Z_OK) + return ret; + + /* inflate the input, maintain a sliding window, and build an index -- this + also validates the integrity of the compressed data using the check + information at the end of the gzip or zlib stream */ + totin = totout = last = 0; + index = NULL; /* will be allocated by first addpoint() */ + strm.avail_out = 0; + do { + /* get some compressed data from input file */ + strm.avail_in = fread(input, 1, CHUNK, in); + if (ferror(in)) { + ret = Z_ERRNO; + goto build_index_error; + } + if (strm.avail_in == 0) { + ret = Z_DATA_ERROR; + goto build_index_error; + } + strm.next_in = input; + + /* process all of that, or until end of stream */ + do { + /* reset sliding window if necessary */ + if (strm.avail_out == 0) { + strm.avail_out = WINSIZE; + strm.next_out = window; + } + + /* inflate until out of input, output, or at end of block -- + update the total input and output counters */ + totin += strm.avail_in; + totout += strm.avail_out; + ret = inflate(&strm, Z_BLOCK); /* return at end of block */ + totin -= strm.avail_in; + totout -= strm.avail_out; + if (ret == Z_NEED_DICT) + ret = Z_DATA_ERROR; + if (ret == Z_MEM_ERROR || ret == Z_DATA_ERROR) + goto build_index_error; + if (ret == Z_STREAM_END) + break; + + /* if at end of block, consider adding an index entry (note that if + data_type indicates an end-of-block, then all of the + uncompressed data from that block has been delivered, and none + of the compressed data after that block has been consumed, + except for up to seven bits) -- the totout == 0 provides an + entry point after the zlib or gzip header, and assures that the + index always has at least one access point; we avoid creating an + access point after the last block by checking bit 6 of data_type + */ + if ((strm.data_type & 128) && !(strm.data_type & 64) && + (totout == 0 || totout - last > span)) { + index = addpoint(index, strm.data_type & 7, totin, + totout, strm.avail_out, window); + if (index == NULL) { + ret = Z_MEM_ERROR; + goto build_index_error; + } + last = totout; + } + } while (strm.avail_in != 0); + } while (ret != Z_STREAM_END); + + /* clean up and return index (release unused entries in list) */ + (void)inflateEnd(&strm); + index->list = realloc(index->list, sizeof(struct point) * index->have); + index->size = index->have; + *built = index; + return index->size; + + /* return error */ + build_index_error: + (void)inflateEnd(&strm); + if (index != NULL) + free_index(index); + return ret; +} + +/* Use the index to read len bytes from offset into buf, return bytes read or + negative for error (Z_DATA_ERROR or Z_MEM_ERROR). If data is requested past + the end of the uncompressed data, then extract() will return a value less + than len, indicating how much as actually read into buf. This function + should not return a data error unless the file was modified since the index + was generated. extract() may also return Z_ERRNO if there is an error on + reading or seeking the input file. */ +local int extract(FILE *in, struct access *index, off_t offset, + unsigned char *buf, int len) +{ + int ret, skip; + z_stream strm; + struct point *here; + unsigned char input[CHUNK]; + unsigned char discard[WINSIZE]; + + /* proceed only if something reasonable to do */ + if (len < 0) + return 0; + + /* find where in stream to start */ + here = index->list; + ret = index->have; + while (--ret && here[1].out <= offset) + here++; + + /* initialize file and inflate state to start there */ + strm.zalloc = Z_NULL; + strm.zfree = Z_NULL; + strm.opaque = Z_NULL; + strm.avail_in = 0; + strm.next_in = Z_NULL; + ret = inflateInit2(&strm, -15); /* raw inflate */ + if (ret != Z_OK) + return ret; + ret = fseeko(in, here->in - (here->bits ? 1 : 0), SEEK_SET); + if (ret == -1) + goto extract_ret; + if (here->bits) { + ret = getc(in); + if (ret == -1) { + ret = ferror(in) ? Z_ERRNO : Z_DATA_ERROR; + goto extract_ret; + } + (void)inflatePrime(&strm, here->bits, ret >> (8 - here->bits)); + } + (void)inflateSetDictionary(&strm, here->window, WINSIZE); + + /* skip uncompressed bytes until offset reached, then satisfy request */ + offset -= here->out; + strm.avail_in = 0; + skip = 1; /* while skipping to offset */ + do { + /* define where to put uncompressed data, and how much */ + if (offset == 0 && skip) { /* at offset now */ + strm.avail_out = len; + strm.next_out = buf; + skip = 0; /* only do this once */ + } + if (offset > WINSIZE) { /* skip WINSIZE bytes */ + strm.avail_out = WINSIZE; + strm.next_out = discard; + offset -= WINSIZE; + } + else if (offset != 0) { /* last skip */ + strm.avail_out = (unsigned)offset; + strm.next_out = discard; + offset = 0; + } + + /* uncompress until avail_out filled, or end of stream */ + do { + if (strm.avail_in == 0) { + strm.avail_in = fread(input, 1, CHUNK, in); + if (ferror(in)) { + ret = Z_ERRNO; + goto extract_ret; + } + if (strm.avail_in == 0) { + ret = Z_DATA_ERROR; + goto extract_ret; + } + strm.next_in = input; + } + ret = inflate(&strm, Z_NO_FLUSH); /* normal inflate */ + if (ret == Z_NEED_DICT) + ret = Z_DATA_ERROR; + if (ret == Z_MEM_ERROR || ret == Z_DATA_ERROR) + goto extract_ret; + if (ret == Z_STREAM_END) + break; + } while (strm.avail_out != 0); + + /* if reach end of stream, then don't keep trying to get more */ + if (ret == Z_STREAM_END) + break; + + /* do until offset reached and requested data read, or stream ends */ + } while (skip); + + /* compute number of uncompressed bytes read after offset */ + ret = skip ? 0 : len - strm.avail_out; + + /* clean up and return bytes read or error */ + extract_ret: + (void)inflateEnd(&strm); + return ret; +} + +/* Demonstrate the use of build_index() and extract() by processing the file + provided on the command line, and the extracting 16K from about 2/3rds of + the way through the uncompressed output, and writing that to stdout. */ +int main(int argc, char **argv) +{ + int len; + off_t offset; + FILE *in; + struct access *index = NULL; + unsigned char buf[CHUNK]; + + /* open input file */ + if (argc != 2) { + fprintf(stderr, "usage: zran file.gz\n"); + return 1; + } + in = fopen(argv[1], "rb"); + if (in == NULL) { + fprintf(stderr, "zran: could not open %s for reading\n", argv[1]); + return 1; + } + + /* build index */ + len = build_index(in, SPAN, &index); + if (len < 0) { + fclose(in); + switch (len) { + case Z_MEM_ERROR: + fprintf(stderr, "zran: out of memory\n"); + break; + case Z_DATA_ERROR: + fprintf(stderr, "zran: compressed data error in %s\n", argv[1]); + break; + case Z_ERRNO: + fprintf(stderr, "zran: read error on %s\n", argv[1]); + break; + default: + fprintf(stderr, "zran: error %d while building index\n", len); + } + return 1; + } + fprintf(stderr, "zran: built index with %d access points\n", len); + + /* use index by reading some bytes from an arbitrary offset */ + offset = (index->list[index->have - 1].out << 1) / 3; + len = extract(in, index, offset, buf, CHUNK); + if (len < 0) + fprintf(stderr, "zran: extraction failed: %s error\n", + len == Z_MEM_ERROR ? "out of memory" : "input corrupted"); + else { + fwrite(buf, 1, len, stdout); + fprintf(stderr, "zran: extracted %d bytes at %llu\n", len, offset); + } + + /* clean up and exit */ + free_index(index); + fclose(in); + return 0; +} ADDED compat/zlib/gzclose.c Index: compat/zlib/gzclose.c ================================================================== --- /dev/null +++ compat/zlib/gzclose.c @@ -0,0 +1,25 @@ +/* gzclose.c -- zlib gzclose() function + * Copyright (C) 2004, 2010 Mark Adler + * For conditions of distribution and use, see copyright notice in zlib.h + */ + +#include "gzguts.h" + +/* gzclose() is in a separate file so that it is linked in only if it is used. + That way the other gzclose functions can be used instead to avoid linking in + unneeded compression or decompression routines. */ +int ZEXPORT gzclose(file) + gzFile file; +{ +#ifndef NO_GZCOMPRESS + gz_statep state; + + if (file == NULL) + return Z_STREAM_ERROR; + state = (gz_statep)file; + + return state->mode == GZ_READ ? gzclose_r(file) : gzclose_w(file); +#else + return gzclose_r(file); +#endif +} ADDED compat/zlib/gzguts.h Index: compat/zlib/gzguts.h ================================================================== --- /dev/null +++ compat/zlib/gzguts.h @@ -0,0 +1,218 @@ +/* gzguts.h -- zlib internal header definitions for gz* operations + * Copyright (C) 2004, 2005, 2010, 2011, 2012, 2013, 2016 Mark Adler + * For conditions of distribution and use, see copyright notice in zlib.h + */ + +#ifdef _LARGEFILE64_SOURCE +# ifndef _LARGEFILE_SOURCE +# define _LARGEFILE_SOURCE 1 +# endif +# ifdef _FILE_OFFSET_BITS +# undef _FILE_OFFSET_BITS +# endif +#endif + +#ifdef HAVE_HIDDEN +# define ZLIB_INTERNAL __attribute__((visibility ("hidden"))) +#else +# define ZLIB_INTERNAL +#endif + +#include +#include "zlib.h" +#ifdef STDC +# include +# include +# include +#endif + +#ifndef _POSIX_SOURCE +# define _POSIX_SOURCE +#endif +#include + +#ifdef _WIN32 +# include +#endif + +#if defined(__TURBOC__) || defined(_MSC_VER) || defined(_WIN32) +# include +#endif + +#if defined(_WIN32) || defined(__CYGWIN__) +# define WIDECHAR +#endif + +#ifdef WINAPI_FAMILY +# define open _open +# define read _read +# define write _write +# define close _close +#endif + +#ifdef NO_DEFLATE /* for compatibility with old definition */ +# define NO_GZCOMPRESS +#endif + +#if defined(STDC99) || (defined(__TURBOC__) && __TURBOC__ >= 0x550) +# ifndef HAVE_VSNPRINTF +# define HAVE_VSNPRINTF +# endif +#endif + +#if defined(__CYGWIN__) +# ifndef HAVE_VSNPRINTF +# define HAVE_VSNPRINTF +# endif +#endif + +#if defined(MSDOS) && defined(__BORLANDC__) && (BORLANDC > 0x410) +# ifndef HAVE_VSNPRINTF +# define HAVE_VSNPRINTF +# endif +#endif + +#ifndef HAVE_VSNPRINTF +# ifdef MSDOS +/* vsnprintf may exist on some MS-DOS compilers (DJGPP?), + but for now we just assume it doesn't. */ +# define NO_vsnprintf +# endif +# ifdef __TURBOC__ +# define NO_vsnprintf +# endif +# ifdef WIN32 +/* In Win32, vsnprintf is available as the "non-ANSI" _vsnprintf. */ +# if !defined(vsnprintf) && !defined(NO_vsnprintf) +# if !defined(_MSC_VER) || ( defined(_MSC_VER) && _MSC_VER < 1500 ) +# define vsnprintf _vsnprintf +# endif +# endif +# endif +# ifdef __SASC +# define NO_vsnprintf +# endif +# ifdef VMS +# define NO_vsnprintf +# endif +# ifdef __OS400__ +# define NO_vsnprintf +# endif +# ifdef __MVS__ +# define NO_vsnprintf +# endif +#endif + +/* unlike snprintf (which is required in C99), _snprintf does not guarantee + null termination of the result -- however this is only used in gzlib.c where + the result is assured to fit in the space provided */ +#if defined(_MSC_VER) && _MSC_VER < 1900 +# define snprintf _snprintf +#endif + +#ifndef local +# define local static +#endif +/* since "static" is used to mean two completely different things in C, we + define "local" for the non-static meaning of "static", for readability + (compile with -Dlocal if your debugger can't find static symbols) */ + +/* gz* functions always use library allocation functions */ +#ifndef STDC + extern voidp malloc OF((uInt size)); + extern void free OF((voidpf ptr)); +#endif + +/* get errno and strerror definition */ +#if defined UNDER_CE +# include +# define zstrerror() gz_strwinerror((DWORD)GetLastError()) +#else +# ifndef NO_STRERROR +# include +# define zstrerror() strerror(errno) +# else +# define zstrerror() "stdio error (consult errno)" +# endif +#endif + +/* provide prototypes for these when building zlib without LFS */ +#if !defined(_LARGEFILE64_SOURCE) || _LFS64_LARGEFILE-0 == 0 + ZEXTERN gzFile ZEXPORT gzopen64 OF((const char *, const char *)); + ZEXTERN z_off64_t ZEXPORT gzseek64 OF((gzFile, z_off64_t, int)); + ZEXTERN z_off64_t ZEXPORT gztell64 OF((gzFile)); + ZEXTERN z_off64_t ZEXPORT gzoffset64 OF((gzFile)); +#endif + +/* default memLevel */ +#if MAX_MEM_LEVEL >= 8 +# define DEF_MEM_LEVEL 8 +#else +# define DEF_MEM_LEVEL MAX_MEM_LEVEL +#endif + +/* default i/o buffer size -- double this for output when reading (this and + twice this must be able to fit in an unsigned type) */ +#define GZBUFSIZE 8192 + +/* gzip modes, also provide a little integrity check on the passed structure */ +#define GZ_NONE 0 +#define GZ_READ 7247 +#define GZ_WRITE 31153 +#define GZ_APPEND 1 /* mode set to GZ_WRITE after the file is opened */ + +/* values for gz_state how */ +#define LOOK 0 /* look for a gzip header */ +#define COPY 1 /* copy input directly */ +#define GZIP 2 /* decompress a gzip stream */ + +/* internal gzip file state data structure */ +typedef struct { + /* exposed contents for gzgetc() macro */ + struct gzFile_s x; /* "x" for exposed */ + /* x.have: number of bytes available at x.next */ + /* x.next: next output data to deliver or write */ + /* x.pos: current position in uncompressed data */ + /* used for both reading and writing */ + int mode; /* see gzip modes above */ + int fd; /* file descriptor */ + char *path; /* path or fd for error messages */ + unsigned size; /* buffer size, zero if not allocated yet */ + unsigned want; /* requested buffer size, default is GZBUFSIZE */ + unsigned char *in; /* input buffer (double-sized when writing) */ + unsigned char *out; /* output buffer (double-sized when reading) */ + int direct; /* 0 if processing gzip, 1 if transparent */ + /* just for reading */ + int how; /* 0: get header, 1: copy, 2: decompress */ + z_off64_t start; /* where the gzip data started, for rewinding */ + int eof; /* true if end of input file reached */ + int past; /* true if read requested past end */ + /* just for writing */ + int level; /* compression level */ + int strategy; /* compression strategy */ + /* seek request */ + z_off64_t skip; /* amount to skip (already rewound if backwards) */ + int seek; /* true if seek request pending */ + /* error information */ + int err; /* error code */ + char *msg; /* error message */ + /* zlib inflate or deflate stream */ + z_stream strm; /* stream structure in-place (not a pointer) */ +} gz_state; +typedef gz_state FAR *gz_statep; + +/* shared functions */ +void ZLIB_INTERNAL gz_error OF((gz_statep, int, const char *)); +#if defined UNDER_CE +char ZLIB_INTERNAL *gz_strwinerror OF((DWORD error)); +#endif + +/* GT_OFF(x), where x is an unsigned value, is true if x > maximum z_off64_t + value -- needed when comparing unsigned to z_off64_t, which is signed + (possible z_off64_t types off_t, off64_t, and long are all signed) */ +#ifdef INT_MAX +# define GT_OFF(x) (sizeof(int) == sizeof(z_off64_t) && (x) > INT_MAX) +#else +unsigned ZLIB_INTERNAL gz_intmax OF((void)); +# define GT_OFF(x) (sizeof(int) == sizeof(z_off64_t) && (x) > gz_intmax()) +#endif ADDED compat/zlib/gzlib.c Index: compat/zlib/gzlib.c ================================================================== --- /dev/null +++ compat/zlib/gzlib.c @@ -0,0 +1,637 @@ +/* gzlib.c -- zlib functions common to reading and writing gzip files + * Copyright (C) 2004-2017 Mark Adler + * For conditions of distribution and use, see copyright notice in zlib.h + */ + +#include "gzguts.h" + +#if defined(_WIN32) && !defined(__BORLANDC__) && !defined(__MINGW32__) +# define LSEEK _lseeki64 +#else +#if defined(_LARGEFILE64_SOURCE) && _LFS64_LARGEFILE-0 +# define LSEEK lseek64 +#else +# define LSEEK lseek +#endif +#endif + +/* Local functions */ +local void gz_reset OF((gz_statep)); +local gzFile gz_open OF((const void *, int, const char *)); + +#if defined UNDER_CE + +/* Map the Windows error number in ERROR to a locale-dependent error message + string and return a pointer to it. Typically, the values for ERROR come + from GetLastError. + + The string pointed to shall not be modified by the application, but may be + overwritten by a subsequent call to gz_strwinerror + + The gz_strwinerror function does not change the current setting of + GetLastError. */ +char ZLIB_INTERNAL *gz_strwinerror (error) + DWORD error; +{ + static char buf[1024]; + + wchar_t *msgbuf; + DWORD lasterr = GetLastError(); + DWORD chars = FormatMessage(FORMAT_MESSAGE_FROM_SYSTEM + | FORMAT_MESSAGE_ALLOCATE_BUFFER, + NULL, + error, + 0, /* Default language */ + (LPVOID)&msgbuf, + 0, + NULL); + if (chars != 0) { + /* If there is an \r\n appended, zap it. */ + if (chars >= 2 + && msgbuf[chars - 2] == '\r' && msgbuf[chars - 1] == '\n') { + chars -= 2; + msgbuf[chars] = 0; + } + + if (chars > sizeof (buf) - 1) { + chars = sizeof (buf) - 1; + msgbuf[chars] = 0; + } + + wcstombs(buf, msgbuf, chars + 1); + LocalFree(msgbuf); + } + else { + sprintf(buf, "unknown win32 error (%ld)", error); + } + + SetLastError(lasterr); + return buf; +} + +#endif /* UNDER_CE */ + +/* Reset gzip file state */ +local void gz_reset(state) + gz_statep state; +{ + state->x.have = 0; /* no output data available */ + if (state->mode == GZ_READ) { /* for reading ... */ + state->eof = 0; /* not at end of file */ + state->past = 0; /* have not read past end yet */ + state->how = LOOK; /* look for gzip header */ + } + state->seek = 0; /* no seek request pending */ + gz_error(state, Z_OK, NULL); /* clear error */ + state->x.pos = 0; /* no uncompressed data yet */ + state->strm.avail_in = 0; /* no input data yet */ +} + +/* Open a gzip file either by name or file descriptor. */ +local gzFile gz_open(path, fd, mode) + const void *path; + int fd; + const char *mode; +{ + gz_statep state; + z_size_t len; + int oflag; +#ifdef O_CLOEXEC + int cloexec = 0; +#endif +#ifdef O_EXCL + int exclusive = 0; +#endif + + /* check input */ + if (path == NULL) + return NULL; + + /* allocate gzFile structure to return */ + state = (gz_statep)malloc(sizeof(gz_state)); + if (state == NULL) + return NULL; + state->size = 0; /* no buffers allocated yet */ + state->want = GZBUFSIZE; /* requested buffer size */ + state->msg = NULL; /* no error message yet */ + + /* interpret mode */ + state->mode = GZ_NONE; + state->level = Z_DEFAULT_COMPRESSION; + state->strategy = Z_DEFAULT_STRATEGY; + state->direct = 0; + while (*mode) { + if (*mode >= '0' && *mode <= '9') + state->level = *mode - '0'; + else + switch (*mode) { + case 'r': + state->mode = GZ_READ; + break; +#ifndef NO_GZCOMPRESS + case 'w': + state->mode = GZ_WRITE; + break; + case 'a': + state->mode = GZ_APPEND; + break; +#endif + case '+': /* can't read and write at the same time */ + free(state); + return NULL; + case 'b': /* ignore -- will request binary anyway */ + break; +#ifdef O_CLOEXEC + case 'e': + cloexec = 1; + break; +#endif +#ifdef O_EXCL + case 'x': + exclusive = 1; + break; +#endif + case 'f': + state->strategy = Z_FILTERED; + break; + case 'h': + state->strategy = Z_HUFFMAN_ONLY; + break; + case 'R': + state->strategy = Z_RLE; + break; + case 'F': + state->strategy = Z_FIXED; + break; + case 'T': + state->direct = 1; + break; + default: /* could consider as an error, but just ignore */ + ; + } + mode++; + } + + /* must provide an "r", "w", or "a" */ + if (state->mode == GZ_NONE) { + free(state); + return NULL; + } + + /* can't force transparent read */ + if (state->mode == GZ_READ) { + if (state->direct) { + free(state); + return NULL; + } + state->direct = 1; /* for empty file */ + } + + /* save the path name for error messages */ +#ifdef WIDECHAR + if (fd == -2) { + len = wcstombs(NULL, path, 0); + if (len == (z_size_t)-1) + len = 0; + } + else +#endif + len = strlen((const char *)path); + state->path = (char *)malloc(len + 1); + if (state->path == NULL) { + free(state); + return NULL; + } +#ifdef WIDECHAR + if (fd == -2) + if (len) + wcstombs(state->path, path, len + 1); + else + *(state->path) = 0; + else +#endif +#if !defined(NO_snprintf) && !defined(NO_vsnprintf) + (void)snprintf(state->path, len + 1, "%s", (const char *)path); +#else + strcpy(state->path, path); +#endif + + /* compute the flags for open() */ + oflag = +#ifdef O_LARGEFILE + O_LARGEFILE | +#endif +#ifdef O_BINARY + O_BINARY | +#endif +#ifdef O_CLOEXEC + (cloexec ? O_CLOEXEC : 0) | +#endif + (state->mode == GZ_READ ? + O_RDONLY : + (O_WRONLY | O_CREAT | +#ifdef O_EXCL + (exclusive ? O_EXCL : 0) | +#endif + (state->mode == GZ_WRITE ? + O_TRUNC : + O_APPEND))); + + /* open the file with the appropriate flags (or just use fd) */ + state->fd = fd > -1 ? fd : ( +#ifdef WIDECHAR + fd == -2 ? _wopen(path, oflag, 0666) : +#endif + open((const char *)path, oflag, 0666)); + if (state->fd == -1) { + free(state->path); + free(state); + return NULL; + } + if (state->mode == GZ_APPEND) { + LSEEK(state->fd, 0, SEEK_END); /* so gzoffset() is correct */ + state->mode = GZ_WRITE; /* simplify later checks */ + } + + /* save the current position for rewinding (only if reading) */ + if (state->mode == GZ_READ) { + state->start = LSEEK(state->fd, 0, SEEK_CUR); + if (state->start == -1) state->start = 0; + } + + /* initialize stream */ + gz_reset(state); + + /* return stream */ + return (gzFile)state; +} + +/* -- see zlib.h -- */ +gzFile ZEXPORT gzopen(path, mode) + const char *path; + const char *mode; +{ + return gz_open(path, -1, mode); +} + +/* -- see zlib.h -- */ +gzFile ZEXPORT gzopen64(path, mode) + const char *path; + const char *mode; +{ + return gz_open(path, -1, mode); +} + +/* -- see zlib.h -- */ +gzFile ZEXPORT gzdopen(fd, mode) + int fd; + const char *mode; +{ + char *path; /* identifier for error messages */ + gzFile gz; + + if (fd == -1 || (path = (char *)malloc(7 + 3 * sizeof(int))) == NULL) + return NULL; +#if !defined(NO_snprintf) && !defined(NO_vsnprintf) + (void)snprintf(path, 7 + 3 * sizeof(int), "", fd); +#else + sprintf(path, "", fd); /* for debugging */ +#endif + gz = gz_open(path, fd, mode); + free(path); + return gz; +} + +/* -- see zlib.h -- */ +#ifdef WIDECHAR +gzFile ZEXPORT gzopen_w(path, mode) + const wchar_t *path; + const char *mode; +{ + return gz_open(path, -2, mode); +} +#endif + +/* -- see zlib.h -- */ +int ZEXPORT gzbuffer(file, size) + gzFile file; + unsigned size; +{ + gz_statep state; + + /* get internal structure and check integrity */ + if (file == NULL) + return -1; + state = (gz_statep)file; + if (state->mode != GZ_READ && state->mode != GZ_WRITE) + return -1; + + /* make sure we haven't already allocated memory */ + if (state->size != 0) + return -1; + + /* check and set requested size */ + if ((size << 1) < size) + return -1; /* need to be able to double it */ + if (size < 2) + size = 2; /* need two bytes to check magic header */ + state->want = size; + return 0; +} + +/* -- see zlib.h -- */ +int ZEXPORT gzrewind(file) + gzFile file; +{ + gz_statep state; + + /* get internal structure */ + if (file == NULL) + return -1; + state = (gz_statep)file; + + /* check that we're reading and that there's no error */ + if (state->mode != GZ_READ || + (state->err != Z_OK && state->err != Z_BUF_ERROR)) + return -1; + + /* back up and start over */ + if (LSEEK(state->fd, state->start, SEEK_SET) == -1) + return -1; + gz_reset(state); + return 0; +} + +/* -- see zlib.h -- */ +z_off64_t ZEXPORT gzseek64(file, offset, whence) + gzFile file; + z_off64_t offset; + int whence; +{ + unsigned n; + z_off64_t ret; + gz_statep state; + + /* get internal structure and check integrity */ + if (file == NULL) + return -1; + state = (gz_statep)file; + if (state->mode != GZ_READ && state->mode != GZ_WRITE) + return -1; + + /* check that there's no error */ + if (state->err != Z_OK && state->err != Z_BUF_ERROR) + return -1; + + /* can only seek from start or relative to current position */ + if (whence != SEEK_SET && whence != SEEK_CUR) + return -1; + + /* normalize offset to a SEEK_CUR specification */ + if (whence == SEEK_SET) + offset -= state->x.pos; + else if (state->seek) + offset += state->skip; + state->seek = 0; + + /* if within raw area while reading, just go there */ + if (state->mode == GZ_READ && state->how == COPY && + state->x.pos + offset >= 0) { + ret = LSEEK(state->fd, offset - state->x.have, SEEK_CUR); + if (ret == -1) + return -1; + state->x.have = 0; + state->eof = 0; + state->past = 0; + state->seek = 0; + gz_error(state, Z_OK, NULL); + state->strm.avail_in = 0; + state->x.pos += offset; + return state->x.pos; + } + + /* calculate skip amount, rewinding if needed for back seek when reading */ + if (offset < 0) { + if (state->mode != GZ_READ) /* writing -- can't go backwards */ + return -1; + offset += state->x.pos; + if (offset < 0) /* before start of file! */ + return -1; + if (gzrewind(file) == -1) /* rewind, then skip to offset */ + return -1; + } + + /* if reading, skip what's in output buffer (one less gzgetc() check) */ + if (state->mode == GZ_READ) { + n = GT_OFF(state->x.have) || (z_off64_t)state->x.have > offset ? + (unsigned)offset : state->x.have; + state->x.have -= n; + state->x.next += n; + state->x.pos += n; + offset -= n; + } + + /* request skip (if not zero) */ + if (offset) { + state->seek = 1; + state->skip = offset; + } + return state->x.pos + offset; +} + +/* -- see zlib.h -- */ +z_off_t ZEXPORT gzseek(file, offset, whence) + gzFile file; + z_off_t offset; + int whence; +{ + z_off64_t ret; + + ret = gzseek64(file, (z_off64_t)offset, whence); + return ret == (z_off_t)ret ? (z_off_t)ret : -1; +} + +/* -- see zlib.h -- */ +z_off64_t ZEXPORT gztell64(file) + gzFile file; +{ + gz_statep state; + + /* get internal structure and check integrity */ + if (file == NULL) + return -1; + state = (gz_statep)file; + if (state->mode != GZ_READ && state->mode != GZ_WRITE) + return -1; + + /* return position */ + return state->x.pos + (state->seek ? state->skip : 0); +} + +/* -- see zlib.h -- */ +z_off_t ZEXPORT gztell(file) + gzFile file; +{ + z_off64_t ret; + + ret = gztell64(file); + return ret == (z_off_t)ret ? (z_off_t)ret : -1; +} + +/* -- see zlib.h -- */ +z_off64_t ZEXPORT gzoffset64(file) + gzFile file; +{ + z_off64_t offset; + gz_statep state; + + /* get internal structure and check integrity */ + if (file == NULL) + return -1; + state = (gz_statep)file; + if (state->mode != GZ_READ && state->mode != GZ_WRITE) + return -1; + + /* compute and return effective offset in file */ + offset = LSEEK(state->fd, 0, SEEK_CUR); + if (offset == -1) + return -1; + if (state->mode == GZ_READ) /* reading */ + offset -= state->strm.avail_in; /* don't count buffered input */ + return offset; +} + +/* -- see zlib.h -- */ +z_off_t ZEXPORT gzoffset(file) + gzFile file; +{ + z_off64_t ret; + + ret = gzoffset64(file); + return ret == (z_off_t)ret ? (z_off_t)ret : -1; +} + +/* -- see zlib.h -- */ +int ZEXPORT gzeof(file) + gzFile file; +{ + gz_statep state; + + /* get internal structure and check integrity */ + if (file == NULL) + return 0; + state = (gz_statep)file; + if (state->mode != GZ_READ && state->mode != GZ_WRITE) + return 0; + + /* return end-of-file state */ + return state->mode == GZ_READ ? state->past : 0; +} + +/* -- see zlib.h -- */ +const char * ZEXPORT gzerror(file, errnum) + gzFile file; + int *errnum; +{ + gz_statep state; + + /* get internal structure and check integrity */ + if (file == NULL) + return NULL; + state = (gz_statep)file; + if (state->mode != GZ_READ && state->mode != GZ_WRITE) + return NULL; + + /* return error information */ + if (errnum != NULL) + *errnum = state->err; + return state->err == Z_MEM_ERROR ? "out of memory" : + (state->msg == NULL ? "" : state->msg); +} + +/* -- see zlib.h -- */ +void ZEXPORT gzclearerr(file) + gzFile file; +{ + gz_statep state; + + /* get internal structure and check integrity */ + if (file == NULL) + return; + state = (gz_statep)file; + if (state->mode != GZ_READ && state->mode != GZ_WRITE) + return; + + /* clear error and end-of-file */ + if (state->mode == GZ_READ) { + state->eof = 0; + state->past = 0; + } + gz_error(state, Z_OK, NULL); +} + +/* Create an error message in allocated memory and set state->err and + state->msg accordingly. Free any previous error message already there. Do + not try to free or allocate space if the error is Z_MEM_ERROR (out of + memory). Simply save the error message as a static string. If there is an + allocation failure constructing the error message, then convert the error to + out of memory. */ +void ZLIB_INTERNAL gz_error(state, err, msg) + gz_statep state; + int err; + const char *msg; +{ + /* free previously allocated message and clear */ + if (state->msg != NULL) { + if (state->err != Z_MEM_ERROR) + free(state->msg); + state->msg = NULL; + } + + /* if fatal, set state->x.have to 0 so that the gzgetc() macro fails */ + if (err != Z_OK && err != Z_BUF_ERROR) + state->x.have = 0; + + /* set error code, and if no message, then done */ + state->err = err; + if (msg == NULL) + return; + + /* for an out of memory error, return literal string when requested */ + if (err == Z_MEM_ERROR) + return; + + /* construct error message with path */ + if ((state->msg = (char *)malloc(strlen(state->path) + strlen(msg) + 3)) == + NULL) { + state->err = Z_MEM_ERROR; + return; + } +#if !defined(NO_snprintf) && !defined(NO_vsnprintf) + (void)snprintf(state->msg, strlen(state->path) + strlen(msg) + 3, + "%s%s%s", state->path, ": ", msg); +#else + strcpy(state->msg, state->path); + strcat(state->msg, ": "); + strcat(state->msg, msg); +#endif +} + +#ifndef INT_MAX +/* portably return maximum value for an int (when limits.h presumed not + available) -- we need to do this to cover cases where 2's complement not + used, since C standard permits 1's complement and sign-bit representations, + otherwise we could just use ((unsigned)-1) >> 1 */ +unsigned ZLIB_INTERNAL gz_intmax() +{ + unsigned p, q; + + p = 1; + do { + q = p; + p <<= 1; + p++; + } while (p > q); + return q >> 1; +} +#endif ADDED compat/zlib/gzread.c Index: compat/zlib/gzread.c ================================================================== --- /dev/null +++ compat/zlib/gzread.c @@ -0,0 +1,654 @@ +/* gzread.c -- zlib functions for reading gzip files + * Copyright (C) 2004, 2005, 2010, 2011, 2012, 2013, 2016 Mark Adler + * For conditions of distribution and use, see copyright notice in zlib.h + */ + +#include "gzguts.h" + +/* Local functions */ +local int gz_load OF((gz_statep, unsigned char *, unsigned, unsigned *)); +local int gz_avail OF((gz_statep)); +local int gz_look OF((gz_statep)); +local int gz_decomp OF((gz_statep)); +local int gz_fetch OF((gz_statep)); +local int gz_skip OF((gz_statep, z_off64_t)); +local z_size_t gz_read OF((gz_statep, voidp, z_size_t)); + +/* Use read() to load a buffer -- return -1 on error, otherwise 0. Read from + state->fd, and update state->eof, state->err, and state->msg as appropriate. + This function needs to loop on read(), since read() is not guaranteed to + read the number of bytes requested, depending on the type of descriptor. */ +local int gz_load(state, buf, len, have) + gz_statep state; + unsigned char *buf; + unsigned len; + unsigned *have; +{ + int ret; + unsigned get, max = ((unsigned)-1 >> 2) + 1; + + *have = 0; + do { + get = len - *have; + if (get > max) + get = max; + ret = read(state->fd, buf + *have, get); + if (ret <= 0) + break; + *have += (unsigned)ret; + } while (*have < len); + if (ret < 0) { + gz_error(state, Z_ERRNO, zstrerror()); + return -1; + } + if (ret == 0) + state->eof = 1; + return 0; +} + +/* Load up input buffer and set eof flag if last data loaded -- return -1 on + error, 0 otherwise. Note that the eof flag is set when the end of the input + file is reached, even though there may be unused data in the buffer. Once + that data has been used, no more attempts will be made to read the file. + If strm->avail_in != 0, then the current data is moved to the beginning of + the input buffer, and then the remainder of the buffer is loaded with the + available data from the input file. */ +local int gz_avail(state) + gz_statep state; +{ + unsigned got; + z_streamp strm = &(state->strm); + + if (state->err != Z_OK && state->err != Z_BUF_ERROR) + return -1; + if (state->eof == 0) { + if (strm->avail_in) { /* copy what's there to the start */ + unsigned char *p = state->in; + unsigned const char *q = strm->next_in; + unsigned n = strm->avail_in; + do { + *p++ = *q++; + } while (--n); + } + if (gz_load(state, state->in + strm->avail_in, + state->size - strm->avail_in, &got) == -1) + return -1; + strm->avail_in += got; + strm->next_in = state->in; + } + return 0; +} + +/* Look for gzip header, set up for inflate or copy. state->x.have must be 0. + If this is the first time in, allocate required memory. state->how will be + left unchanged if there is no more input data available, will be set to COPY + if there is no gzip header and direct copying will be performed, or it will + be set to GZIP for decompression. If direct copying, then leftover input + data from the input buffer will be copied to the output buffer. In that + case, all further file reads will be directly to either the output buffer or + a user buffer. If decompressing, the inflate state will be initialized. + gz_look() will return 0 on success or -1 on failure. */ +local int gz_look(state) + gz_statep state; +{ + z_streamp strm = &(state->strm); + + /* allocate read buffers and inflate memory */ + if (state->size == 0) { + /* allocate buffers */ + state->in = (unsigned char *)malloc(state->want); + state->out = (unsigned char *)malloc(state->want << 1); + if (state->in == NULL || state->out == NULL) { + free(state->out); + free(state->in); + gz_error(state, Z_MEM_ERROR, "out of memory"); + return -1; + } + state->size = state->want; + + /* allocate inflate memory */ + state->strm.zalloc = Z_NULL; + state->strm.zfree = Z_NULL; + state->strm.opaque = Z_NULL; + state->strm.avail_in = 0; + state->strm.next_in = Z_NULL; + if (inflateInit2(&(state->strm), 15 + 16) != Z_OK) { /* gunzip */ + free(state->out); + free(state->in); + state->size = 0; + gz_error(state, Z_MEM_ERROR, "out of memory"); + return -1; + } + } + + /* get at least the magic bytes in the input buffer */ + if (strm->avail_in < 2) { + if (gz_avail(state) == -1) + return -1; + if (strm->avail_in == 0) + return 0; + } + + /* look for gzip magic bytes -- if there, do gzip decoding (note: there is + a logical dilemma here when considering the case of a partially written + gzip file, to wit, if a single 31 byte is written, then we cannot tell + whether this is a single-byte file, or just a partially written gzip + file -- for here we assume that if a gzip file is being written, then + the header will be written in a single operation, so that reading a + single byte is sufficient indication that it is not a gzip file) */ + if (strm->avail_in > 1 && + strm->next_in[0] == 31 && strm->next_in[1] == 139) { + inflateReset(strm); + state->how = GZIP; + state->direct = 0; + return 0; + } + + /* no gzip header -- if we were decoding gzip before, then this is trailing + garbage. Ignore the trailing garbage and finish. */ + if (state->direct == 0) { + strm->avail_in = 0; + state->eof = 1; + state->x.have = 0; + return 0; + } + + /* doing raw i/o, copy any leftover input to output -- this assumes that + the output buffer is larger than the input buffer, which also assures + space for gzungetc() */ + state->x.next = state->out; + if (strm->avail_in) { + memcpy(state->x.next, strm->next_in, strm->avail_in); + state->x.have = strm->avail_in; + strm->avail_in = 0; + } + state->how = COPY; + state->direct = 1; + return 0; +} + +/* Decompress from input to the provided next_out and avail_out in the state. + On return, state->x.have and state->x.next point to the just decompressed + data. If the gzip stream completes, state->how is reset to LOOK to look for + the next gzip stream or raw data, once state->x.have is depleted. Returns 0 + on success, -1 on failure. */ +local int gz_decomp(state) + gz_statep state; +{ + int ret = Z_OK; + unsigned had; + z_streamp strm = &(state->strm); + + /* fill output buffer up to end of deflate stream */ + had = strm->avail_out; + do { + /* get more input for inflate() */ + if (strm->avail_in == 0 && gz_avail(state) == -1) + return -1; + if (strm->avail_in == 0) { + gz_error(state, Z_BUF_ERROR, "unexpected end of file"); + break; + } + + /* decompress and handle errors */ + ret = inflate(strm, Z_NO_FLUSH); + if (ret == Z_STREAM_ERROR || ret == Z_NEED_DICT) { + gz_error(state, Z_STREAM_ERROR, + "internal error: inflate stream corrupt"); + return -1; + } + if (ret == Z_MEM_ERROR) { + gz_error(state, Z_MEM_ERROR, "out of memory"); + return -1; + } + if (ret == Z_DATA_ERROR) { /* deflate stream invalid */ + gz_error(state, Z_DATA_ERROR, + strm->msg == NULL ? "compressed data error" : strm->msg); + return -1; + } + } while (strm->avail_out && ret != Z_STREAM_END); + + /* update available output */ + state->x.have = had - strm->avail_out; + state->x.next = strm->next_out - state->x.have; + + /* if the gzip stream completed successfully, look for another */ + if (ret == Z_STREAM_END) + state->how = LOOK; + + /* good decompression */ + return 0; +} + +/* Fetch data and put it in the output buffer. Assumes state->x.have is 0. + Data is either copied from the input file or decompressed from the input + file depending on state->how. If state->how is LOOK, then a gzip header is + looked for to determine whether to copy or decompress. Returns -1 on error, + otherwise 0. gz_fetch() will leave state->how as COPY or GZIP unless the + end of the input file has been reached and all data has been processed. */ +local int gz_fetch(state) + gz_statep state; +{ + z_streamp strm = &(state->strm); + + do { + switch(state->how) { + case LOOK: /* -> LOOK, COPY (only if never GZIP), or GZIP */ + if (gz_look(state) == -1) + return -1; + if (state->how == LOOK) + return 0; + break; + case COPY: /* -> COPY */ + if (gz_load(state, state->out, state->size << 1, &(state->x.have)) + == -1) + return -1; + state->x.next = state->out; + return 0; + case GZIP: /* -> GZIP or LOOK (if end of gzip stream) */ + strm->avail_out = state->size << 1; + strm->next_out = state->out; + if (gz_decomp(state) == -1) + return -1; + } + } while (state->x.have == 0 && (!state->eof || strm->avail_in)); + return 0; +} + +/* Skip len uncompressed bytes of output. Return -1 on error, 0 on success. */ +local int gz_skip(state, len) + gz_statep state; + z_off64_t len; +{ + unsigned n; + + /* skip over len bytes or reach end-of-file, whichever comes first */ + while (len) + /* skip over whatever is in output buffer */ + if (state->x.have) { + n = GT_OFF(state->x.have) || (z_off64_t)state->x.have > len ? + (unsigned)len : state->x.have; + state->x.have -= n; + state->x.next += n; + state->x.pos += n; + len -= n; + } + + /* output buffer empty -- return if we're at the end of the input */ + else if (state->eof && state->strm.avail_in == 0) + break; + + /* need more data to skip -- load up output buffer */ + else { + /* get more output, looking for header if required */ + if (gz_fetch(state) == -1) + return -1; + } + return 0; +} + +/* Read len bytes into buf from file, or less than len up to the end of the + input. Return the number of bytes read. If zero is returned, either the + end of file was reached, or there was an error. state->err must be + consulted in that case to determine which. */ +local z_size_t gz_read(state, buf, len) + gz_statep state; + voidp buf; + z_size_t len; +{ + z_size_t got; + unsigned n; + + /* if len is zero, avoid unnecessary operations */ + if (len == 0) + return 0; + + /* process a skip request */ + if (state->seek) { + state->seek = 0; + if (gz_skip(state, state->skip) == -1) + return 0; + } + + /* get len bytes to buf, or less than len if at the end */ + got = 0; + do { + /* set n to the maximum amount of len that fits in an unsigned int */ + n = -1; + if (n > len) + n = len; + + /* first just try copying data from the output buffer */ + if (state->x.have) { + if (state->x.have < n) + n = state->x.have; + memcpy(buf, state->x.next, n); + state->x.next += n; + state->x.have -= n; + } + + /* output buffer empty -- return if we're at the end of the input */ + else if (state->eof && state->strm.avail_in == 0) { + state->past = 1; /* tried to read past end */ + break; + } + + /* need output data -- for small len or new stream load up our output + buffer */ + else if (state->how == LOOK || n < (state->size << 1)) { + /* get more output, looking for header if required */ + if (gz_fetch(state) == -1) + return 0; + continue; /* no progress yet -- go back to copy above */ + /* the copy above assures that we will leave with space in the + output buffer, allowing at least one gzungetc() to succeed */ + } + + /* large len -- read directly into user buffer */ + else if (state->how == COPY) { /* read directly */ + if (gz_load(state, (unsigned char *)buf, n, &n) == -1) + return 0; + } + + /* large len -- decompress directly into user buffer */ + else { /* state->how == GZIP */ + state->strm.avail_out = n; + state->strm.next_out = (unsigned char *)buf; + if (gz_decomp(state) == -1) + return 0; + n = state->x.have; + state->x.have = 0; + } + + /* update progress */ + len -= n; + buf = (char *)buf + n; + got += n; + state->x.pos += n; + } while (len); + + /* return number of bytes read into user buffer */ + return got; +} + +/* -- see zlib.h -- */ +int ZEXPORT gzread(file, buf, len) + gzFile file; + voidp buf; + unsigned len; +{ + gz_statep state; + + /* get internal structure */ + if (file == NULL) + return -1; + state = (gz_statep)file; + + /* check that we're reading and that there's no (serious) error */ + if (state->mode != GZ_READ || + (state->err != Z_OK && state->err != Z_BUF_ERROR)) + return -1; + + /* since an int is returned, make sure len fits in one, otherwise return + with an error (this avoids a flaw in the interface) */ + if ((int)len < 0) { + gz_error(state, Z_STREAM_ERROR, "request does not fit in an int"); + return -1; + } + + /* read len or fewer bytes to buf */ + len = gz_read(state, buf, len); + + /* check for an error */ + if (len == 0 && state->err != Z_OK && state->err != Z_BUF_ERROR) + return -1; + + /* return the number of bytes read (this is assured to fit in an int) */ + return (int)len; +} + +/* -- see zlib.h -- */ +z_size_t ZEXPORT gzfread(buf, size, nitems, file) + voidp buf; + z_size_t size; + z_size_t nitems; + gzFile file; +{ + z_size_t len; + gz_statep state; + + /* get internal structure */ + if (file == NULL) + return 0; + state = (gz_statep)file; + + /* check that we're reading and that there's no (serious) error */ + if (state->mode != GZ_READ || + (state->err != Z_OK && state->err != Z_BUF_ERROR)) + return 0; + + /* compute bytes to read -- error on overflow */ + len = nitems * size; + if (size && len / size != nitems) { + gz_error(state, Z_STREAM_ERROR, "request does not fit in a size_t"); + return 0; + } + + /* read len or fewer bytes to buf, return the number of full items read */ + return len ? gz_read(state, buf, len) / size : 0; +} + +/* -- see zlib.h -- */ +#ifdef Z_PREFIX_SET +# undef z_gzgetc +#else +# undef gzgetc +#endif +int ZEXPORT gzgetc(file) + gzFile file; +{ + int ret; + unsigned char buf[1]; + gz_statep state; + + /* get internal structure */ + if (file == NULL) + return -1; + state = (gz_statep)file; + + /* check that we're reading and that there's no (serious) error */ + if (state->mode != GZ_READ || + (state->err != Z_OK && state->err != Z_BUF_ERROR)) + return -1; + + /* try output buffer (no need to check for skip request) */ + if (state->x.have) { + state->x.have--; + state->x.pos++; + return *(state->x.next)++; + } + + /* nothing there -- try gz_read() */ + ret = gz_read(state, buf, 1); + return ret < 1 ? -1 : buf[0]; +} + +int ZEXPORT gzgetc_(file) +gzFile file; +{ + return gzgetc(file); +} + +/* -- see zlib.h -- */ +int ZEXPORT gzungetc(c, file) + int c; + gzFile file; +{ + gz_statep state; + + /* get internal structure */ + if (file == NULL) + return -1; + state = (gz_statep)file; + + /* check that we're reading and that there's no (serious) error */ + if (state->mode != GZ_READ || + (state->err != Z_OK && state->err != Z_BUF_ERROR)) + return -1; + + /* process a skip request */ + if (state->seek) { + state->seek = 0; + if (gz_skip(state, state->skip) == -1) + return -1; + } + + /* can't push EOF */ + if (c < 0) + return -1; + + /* if output buffer empty, put byte at end (allows more pushing) */ + if (state->x.have == 0) { + state->x.have = 1; + state->x.next = state->out + (state->size << 1) - 1; + state->x.next[0] = (unsigned char)c; + state->x.pos--; + state->past = 0; + return c; + } + + /* if no room, give up (must have already done a gzungetc()) */ + if (state->x.have == (state->size << 1)) { + gz_error(state, Z_DATA_ERROR, "out of room to push characters"); + return -1; + } + + /* slide output data if needed and insert byte before existing data */ + if (state->x.next == state->out) { + unsigned char *src = state->out + state->x.have; + unsigned char *dest = state->out + (state->size << 1); + while (src > state->out) + *--dest = *--src; + state->x.next = dest; + } + state->x.have++; + state->x.next--; + state->x.next[0] = (unsigned char)c; + state->x.pos--; + state->past = 0; + return c; +} + +/* -- see zlib.h -- */ +char * ZEXPORT gzgets(file, buf, len) + gzFile file; + char *buf; + int len; +{ + unsigned left, n; + char *str; + unsigned char *eol; + gz_statep state; + + /* check parameters and get internal structure */ + if (file == NULL || buf == NULL || len < 1) + return NULL; + state = (gz_statep)file; + + /* check that we're reading and that there's no (serious) error */ + if (state->mode != GZ_READ || + (state->err != Z_OK && state->err != Z_BUF_ERROR)) + return NULL; + + /* process a skip request */ + if (state->seek) { + state->seek = 0; + if (gz_skip(state, state->skip) == -1) + return NULL; + } + + /* copy output bytes up to new line or len - 1, whichever comes first -- + append a terminating zero to the string (we don't check for a zero in + the contents, let the user worry about that) */ + str = buf; + left = (unsigned)len - 1; + if (left) do { + /* assure that something is in the output buffer */ + if (state->x.have == 0 && gz_fetch(state) == -1) + return NULL; /* error */ + if (state->x.have == 0) { /* end of file */ + state->past = 1; /* read past end */ + break; /* return what we have */ + } + + /* look for end-of-line in current output buffer */ + n = state->x.have > left ? left : state->x.have; + eol = (unsigned char *)memchr(state->x.next, '\n', n); + if (eol != NULL) + n = (unsigned)(eol - state->x.next) + 1; + + /* copy through end-of-line, or remainder if not found */ + memcpy(buf, state->x.next, n); + state->x.have -= n; + state->x.next += n; + state->x.pos += n; + left -= n; + buf += n; + } while (left && eol == NULL); + + /* return terminated string, or if nothing, end of file */ + if (buf == str) + return NULL; + buf[0] = 0; + return str; +} + +/* -- see zlib.h -- */ +int ZEXPORT gzdirect(file) + gzFile file; +{ + gz_statep state; + + /* get internal structure */ + if (file == NULL) + return 0; + state = (gz_statep)file; + + /* if the state is not known, but we can find out, then do so (this is + mainly for right after a gzopen() or gzdopen()) */ + if (state->mode == GZ_READ && state->how == LOOK && state->x.have == 0) + (void)gz_look(state); + + /* return 1 if transparent, 0 if processing a gzip stream */ + return state->direct; +} + +/* -- see zlib.h -- */ +int ZEXPORT gzclose_r(file) + gzFile file; +{ + int ret, err; + gz_statep state; + + /* get internal structure */ + if (file == NULL) + return Z_STREAM_ERROR; + state = (gz_statep)file; + + /* check that we're reading */ + if (state->mode != GZ_READ) + return Z_STREAM_ERROR; + + /* free memory and close file */ + if (state->size) { + inflateEnd(&(state->strm)); + free(state->out); + free(state->in); + } + err = state->err == Z_BUF_ERROR ? Z_BUF_ERROR : Z_OK; + gz_error(state, Z_OK, NULL); + free(state->path); + ret = close(state->fd); + free(state); + return ret ? Z_ERRNO : err; +} ADDED compat/zlib/gzwrite.c Index: compat/zlib/gzwrite.c ================================================================== --- /dev/null +++ compat/zlib/gzwrite.c @@ -0,0 +1,665 @@ +/* gzwrite.c -- zlib functions for writing gzip files + * Copyright (C) 2004-2017 Mark Adler + * For conditions of distribution and use, see copyright notice in zlib.h + */ + +#include "gzguts.h" + +/* Local functions */ +local int gz_init OF((gz_statep)); +local int gz_comp OF((gz_statep, int)); +local int gz_zero OF((gz_statep, z_off64_t)); +local z_size_t gz_write OF((gz_statep, voidpc, z_size_t)); + +/* Initialize state for writing a gzip file. Mark initialization by setting + state->size to non-zero. Return -1 on a memory allocation failure, or 0 on + success. */ +local int gz_init(state) + gz_statep state; +{ + int ret; + z_streamp strm = &(state->strm); + + /* allocate input buffer (double size for gzprintf) */ + state->in = (unsigned char *)malloc(state->want << 1); + if (state->in == NULL) { + gz_error(state, Z_MEM_ERROR, "out of memory"); + return -1; + } + + /* only need output buffer and deflate state if compressing */ + if (!state->direct) { + /* allocate output buffer */ + state->out = (unsigned char *)malloc(state->want); + if (state->out == NULL) { + free(state->in); + gz_error(state, Z_MEM_ERROR, "out of memory"); + return -1; + } + + /* allocate deflate memory, set up for gzip compression */ + strm->zalloc = Z_NULL; + strm->zfree = Z_NULL; + strm->opaque = Z_NULL; + ret = deflateInit2(strm, state->level, Z_DEFLATED, + MAX_WBITS + 16, DEF_MEM_LEVEL, state->strategy); + if (ret != Z_OK) { + free(state->out); + free(state->in); + gz_error(state, Z_MEM_ERROR, "out of memory"); + return -1; + } + strm->next_in = NULL; + } + + /* mark state as initialized */ + state->size = state->want; + + /* initialize write buffer if compressing */ + if (!state->direct) { + strm->avail_out = state->size; + strm->next_out = state->out; + state->x.next = strm->next_out; + } + return 0; +} + +/* Compress whatever is at avail_in and next_in and write to the output file. + Return -1 if there is an error writing to the output file or if gz_init() + fails to allocate memory, otherwise 0. flush is assumed to be a valid + deflate() flush value. If flush is Z_FINISH, then the deflate() state is + reset to start a new gzip stream. If gz->direct is true, then simply write + to the output file without compressing, and ignore flush. */ +local int gz_comp(state, flush) + gz_statep state; + int flush; +{ + int ret, writ; + unsigned have, put, max = ((unsigned)-1 >> 2) + 1; + z_streamp strm = &(state->strm); + + /* allocate memory if this is the first time through */ + if (state->size == 0 && gz_init(state) == -1) + return -1; + + /* write directly if requested */ + if (state->direct) { + while (strm->avail_in) { + put = strm->avail_in > max ? max : strm->avail_in; + writ = write(state->fd, strm->next_in, put); + if (writ < 0) { + gz_error(state, Z_ERRNO, zstrerror()); + return -1; + } + strm->avail_in -= (unsigned)writ; + strm->next_in += writ; + } + return 0; + } + + /* run deflate() on provided input until it produces no more output */ + ret = Z_OK; + do { + /* write out current buffer contents if full, or if flushing, but if + doing Z_FINISH then don't write until we get to Z_STREAM_END */ + if (strm->avail_out == 0 || (flush != Z_NO_FLUSH && + (flush != Z_FINISH || ret == Z_STREAM_END))) { + while (strm->next_out > state->x.next) { + put = strm->next_out - state->x.next > (int)max ? max : + (unsigned)(strm->next_out - state->x.next); + writ = write(state->fd, state->x.next, put); + if (writ < 0) { + gz_error(state, Z_ERRNO, zstrerror()); + return -1; + } + state->x.next += writ; + } + if (strm->avail_out == 0) { + strm->avail_out = state->size; + strm->next_out = state->out; + state->x.next = state->out; + } + } + + /* compress */ + have = strm->avail_out; + ret = deflate(strm, flush); + if (ret == Z_STREAM_ERROR) { + gz_error(state, Z_STREAM_ERROR, + "internal error: deflate stream corrupt"); + return -1; + } + have -= strm->avail_out; + } while (have); + + /* if that completed a deflate stream, allow another to start */ + if (flush == Z_FINISH) + deflateReset(strm); + + /* all done, no errors */ + return 0; +} + +/* Compress len zeros to output. Return -1 on a write error or memory + allocation failure by gz_comp(), or 0 on success. */ +local int gz_zero(state, len) + gz_statep state; + z_off64_t len; +{ + int first; + unsigned n; + z_streamp strm = &(state->strm); + + /* consume whatever's left in the input buffer */ + if (strm->avail_in && gz_comp(state, Z_NO_FLUSH) == -1) + return -1; + + /* compress len zeros (len guaranteed > 0) */ + first = 1; + while (len) { + n = GT_OFF(state->size) || (z_off64_t)state->size > len ? + (unsigned)len : state->size; + if (first) { + memset(state->in, 0, n); + first = 0; + } + strm->avail_in = n; + strm->next_in = state->in; + state->x.pos += n; + if (gz_comp(state, Z_NO_FLUSH) == -1) + return -1; + len -= n; + } + return 0; +} + +/* Write len bytes from buf to file. Return the number of bytes written. If + the returned value is less than len, then there was an error. */ +local z_size_t gz_write(state, buf, len) + gz_statep state; + voidpc buf; + z_size_t len; +{ + z_size_t put = len; + + /* if len is zero, avoid unnecessary operations */ + if (len == 0) + return 0; + + /* allocate memory if this is the first time through */ + if (state->size == 0 && gz_init(state) == -1) + return 0; + + /* check for seek request */ + if (state->seek) { + state->seek = 0; + if (gz_zero(state, state->skip) == -1) + return 0; + } + + /* for small len, copy to input buffer, otherwise compress directly */ + if (len < state->size) { + /* copy to input buffer, compress when full */ + do { + unsigned have, copy; + + if (state->strm.avail_in == 0) + state->strm.next_in = state->in; + have = (unsigned)((state->strm.next_in + state->strm.avail_in) - + state->in); + copy = state->size - have; + if (copy > len) + copy = len; + memcpy(state->in + have, buf, copy); + state->strm.avail_in += copy; + state->x.pos += copy; + buf = (const char *)buf + copy; + len -= copy; + if (len && gz_comp(state, Z_NO_FLUSH) == -1) + return 0; + } while (len); + } + else { + /* consume whatever's left in the input buffer */ + if (state->strm.avail_in && gz_comp(state, Z_NO_FLUSH) == -1) + return 0; + + /* directly compress user buffer to file */ + state->strm.next_in = (z_const Bytef *)buf; + do { + unsigned n = (unsigned)-1; + if (n > len) + n = len; + state->strm.avail_in = n; + state->x.pos += n; + if (gz_comp(state, Z_NO_FLUSH) == -1) + return 0; + len -= n; + } while (len); + } + + /* input was all buffered or compressed */ + return put; +} + +/* -- see zlib.h -- */ +int ZEXPORT gzwrite(file, buf, len) + gzFile file; + voidpc buf; + unsigned len; +{ + gz_statep state; + + /* get internal structure */ + if (file == NULL) + return 0; + state = (gz_statep)file; + + /* check that we're writing and that there's no error */ + if (state->mode != GZ_WRITE || state->err != Z_OK) + return 0; + + /* since an int is returned, make sure len fits in one, otherwise return + with an error (this avoids a flaw in the interface) */ + if ((int)len < 0) { + gz_error(state, Z_DATA_ERROR, "requested length does not fit in int"); + return 0; + } + + /* write len bytes from buf (the return value will fit in an int) */ + return (int)gz_write(state, buf, len); +} + +/* -- see zlib.h -- */ +z_size_t ZEXPORT gzfwrite(buf, size, nitems, file) + voidpc buf; + z_size_t size; + z_size_t nitems; + gzFile file; +{ + z_size_t len; + gz_statep state; + + /* get internal structure */ + if (file == NULL) + return 0; + state = (gz_statep)file; + + /* check that we're writing and that there's no error */ + if (state->mode != GZ_WRITE || state->err != Z_OK) + return 0; + + /* compute bytes to read -- error on overflow */ + len = nitems * size; + if (size && len / size != nitems) { + gz_error(state, Z_STREAM_ERROR, "request does not fit in a size_t"); + return 0; + } + + /* write len bytes to buf, return the number of full items written */ + return len ? gz_write(state, buf, len) / size : 0; +} + +/* -- see zlib.h -- */ +int ZEXPORT gzputc(file, c) + gzFile file; + int c; +{ + unsigned have; + unsigned char buf[1]; + gz_statep state; + z_streamp strm; + + /* get internal structure */ + if (file == NULL) + return -1; + state = (gz_statep)file; + strm = &(state->strm); + + /* check that we're writing and that there's no error */ + if (state->mode != GZ_WRITE || state->err != Z_OK) + return -1; + + /* check for seek request */ + if (state->seek) { + state->seek = 0; + if (gz_zero(state, state->skip) == -1) + return -1; + } + + /* try writing to input buffer for speed (state->size == 0 if buffer not + initialized) */ + if (state->size) { + if (strm->avail_in == 0) + strm->next_in = state->in; + have = (unsigned)((strm->next_in + strm->avail_in) - state->in); + if (have < state->size) { + state->in[have] = (unsigned char)c; + strm->avail_in++; + state->x.pos++; + return c & 0xff; + } + } + + /* no room in buffer or not initialized, use gz_write() */ + buf[0] = (unsigned char)c; + if (gz_write(state, buf, 1) != 1) + return -1; + return c & 0xff; +} + +/* -- see zlib.h -- */ +int ZEXPORT gzputs(file, str) + gzFile file; + const char *str; +{ + int ret; + z_size_t len; + gz_statep state; + + /* get internal structure */ + if (file == NULL) + return -1; + state = (gz_statep)file; + + /* check that we're writing and that there's no error */ + if (state->mode != GZ_WRITE || state->err != Z_OK) + return -1; + + /* write string */ + len = strlen(str); + ret = gz_write(state, str, len); + return ret == 0 && len != 0 ? -1 : ret; +} + +#if defined(STDC) || defined(Z_HAVE_STDARG_H) +#include + +/* -- see zlib.h -- */ +int ZEXPORTVA gzvprintf(gzFile file, const char *format, va_list va) +{ + int len; + unsigned left; + char *next; + gz_statep state; + z_streamp strm; + + /* get internal structure */ + if (file == NULL) + return Z_STREAM_ERROR; + state = (gz_statep)file; + strm = &(state->strm); + + /* check that we're writing and that there's no error */ + if (state->mode != GZ_WRITE || state->err != Z_OK) + return Z_STREAM_ERROR; + + /* make sure we have some buffer space */ + if (state->size == 0 && gz_init(state) == -1) + return state->err; + + /* check for seek request */ + if (state->seek) { + state->seek = 0; + if (gz_zero(state, state->skip) == -1) + return state->err; + } + + /* do the printf() into the input buffer, put length in len -- the input + buffer is double-sized just for this function, so there is guaranteed to + be state->size bytes available after the current contents */ + if (strm->avail_in == 0) + strm->next_in = state->in; + next = (char *)(state->in + (strm->next_in - state->in) + strm->avail_in); + next[state->size - 1] = 0; +#ifdef NO_vsnprintf +# ifdef HAS_vsprintf_void + (void)vsprintf(next, format, va); + for (len = 0; len < state->size; len++) + if (next[len] == 0) break; +# else + len = vsprintf(next, format, va); +# endif +#else +# ifdef HAS_vsnprintf_void + (void)vsnprintf(next, state->size, format, va); + len = strlen(next); +# else + len = vsnprintf(next, state->size, format, va); +# endif +#endif + + /* check that printf() results fit in buffer */ + if (len == 0 || (unsigned)len >= state->size || next[state->size - 1] != 0) + return 0; + + /* update buffer and position, compress first half if past that */ + strm->avail_in += (unsigned)len; + state->x.pos += len; + if (strm->avail_in >= state->size) { + left = strm->avail_in - state->size; + strm->avail_in = state->size; + if (gz_comp(state, Z_NO_FLUSH) == -1) + return state->err; + memcpy(state->in, state->in + state->size, left); + strm->next_in = state->in; + strm->avail_in = left; + } + return len; +} + +int ZEXPORTVA gzprintf(gzFile file, const char *format, ...) +{ + va_list va; + int ret; + + va_start(va, format); + ret = gzvprintf(file, format, va); + va_end(va); + return ret; +} + +#else /* !STDC && !Z_HAVE_STDARG_H */ + +/* -- see zlib.h -- */ +int ZEXPORTVA gzprintf (file, format, a1, a2, a3, a4, a5, a6, a7, a8, a9, a10, + a11, a12, a13, a14, a15, a16, a17, a18, a19, a20) + gzFile file; + const char *format; + int a1, a2, a3, a4, a5, a6, a7, a8, a9, a10, + a11, a12, a13, a14, a15, a16, a17, a18, a19, a20; +{ + unsigned len, left; + char *next; + gz_statep state; + z_streamp strm; + + /* get internal structure */ + if (file == NULL) + return Z_STREAM_ERROR; + state = (gz_statep)file; + strm = &(state->strm); + + /* check that can really pass pointer in ints */ + if (sizeof(int) != sizeof(void *)) + return Z_STREAM_ERROR; + + /* check that we're writing and that there's no error */ + if (state->mode != GZ_WRITE || state->err != Z_OK) + return Z_STREAM_ERROR; + + /* make sure we have some buffer space */ + if (state->size == 0 && gz_init(state) == -1) + return state->error; + + /* check for seek request */ + if (state->seek) { + state->seek = 0; + if (gz_zero(state, state->skip) == -1) + return state->error; + } + + /* do the printf() into the input buffer, put length in len -- the input + buffer is double-sized just for this function, so there is guaranteed to + be state->size bytes available after the current contents */ + if (strm->avail_in == 0) + strm->next_in = state->in; + next = (char *)(strm->next_in + strm->avail_in); + next[state->size - 1] = 0; +#ifdef NO_snprintf +# ifdef HAS_sprintf_void + sprintf(next, format, a1, a2, a3, a4, a5, a6, a7, a8, a9, a10, a11, a12, + a13, a14, a15, a16, a17, a18, a19, a20); + for (len = 0; len < size; len++) + if (next[len] == 0) + break; +# else + len = sprintf(next, format, a1, a2, a3, a4, a5, a6, a7, a8, a9, a10, a11, + a12, a13, a14, a15, a16, a17, a18, a19, a20); +# endif +#else +# ifdef HAS_snprintf_void + snprintf(next, state->size, format, a1, a2, a3, a4, a5, a6, a7, a8, a9, + a10, a11, a12, a13, a14, a15, a16, a17, a18, a19, a20); + len = strlen(next); +# else + len = snprintf(next, state->size, format, a1, a2, a3, a4, a5, a6, a7, a8, + a9, a10, a11, a12, a13, a14, a15, a16, a17, a18, a19, a20); +# endif +#endif + + /* check that printf() results fit in buffer */ + if (len == 0 || len >= state->size || next[state->size - 1] != 0) + return 0; + + /* update buffer and position, compress first half if past that */ + strm->avail_in += len; + state->x.pos += len; + if (strm->avail_in >= state->size) { + left = strm->avail_in - state->size; + strm->avail_in = state->size; + if (gz_comp(state, Z_NO_FLUSH) == -1) + return state->err; + memcpy(state->in, state->in + state->size, left); + strm->next_in = state->in; + strm->avail_in = left; + } + return (int)len; +} + +#endif + +/* -- see zlib.h -- */ +int ZEXPORT gzflush(file, flush) + gzFile file; + int flush; +{ + gz_statep state; + + /* get internal structure */ + if (file == NULL) + return Z_STREAM_ERROR; + state = (gz_statep)file; + + /* check that we're writing and that there's no error */ + if (state->mode != GZ_WRITE || state->err != Z_OK) + return Z_STREAM_ERROR; + + /* check flush parameter */ + if (flush < 0 || flush > Z_FINISH) + return Z_STREAM_ERROR; + + /* check for seek request */ + if (state->seek) { + state->seek = 0; + if (gz_zero(state, state->skip) == -1) + return state->err; + } + + /* compress remaining data with requested flush */ + (void)gz_comp(state, flush); + return state->err; +} + +/* -- see zlib.h -- */ +int ZEXPORT gzsetparams(file, level, strategy) + gzFile file; + int level; + int strategy; +{ + gz_statep state; + z_streamp strm; + + /* get internal structure */ + if (file == NULL) + return Z_STREAM_ERROR; + state = (gz_statep)file; + strm = &(state->strm); + + /* check that we're writing and that there's no error */ + if (state->mode != GZ_WRITE || state->err != Z_OK) + return Z_STREAM_ERROR; + + /* if no change is requested, then do nothing */ + if (level == state->level && strategy == state->strategy) + return Z_OK; + + /* check for seek request */ + if (state->seek) { + state->seek = 0; + if (gz_zero(state, state->skip) == -1) + return state->err; + } + + /* change compression parameters for subsequent input */ + if (state->size) { + /* flush previous input with previous parameters before changing */ + if (strm->avail_in && gz_comp(state, Z_BLOCK) == -1) + return state->err; + deflateParams(strm, level, strategy); + } + state->level = level; + state->strategy = strategy; + return Z_OK; +} + +/* -- see zlib.h -- */ +int ZEXPORT gzclose_w(file) + gzFile file; +{ + int ret = Z_OK; + gz_statep state; + + /* get internal structure */ + if (file == NULL) + return Z_STREAM_ERROR; + state = (gz_statep)file; + + /* check that we're writing */ + if (state->mode != GZ_WRITE) + return Z_STREAM_ERROR; + + /* check for seek request */ + if (state->seek) { + state->seek = 0; + if (gz_zero(state, state->skip) == -1) + ret = state->err; + } + + /* flush, free memory, and close file */ + if (gz_comp(state, Z_FINISH) == -1) + ret = state->err; + if (state->size) { + if (!state->direct) { + (void)deflateEnd(&(state->strm)); + free(state->out); + } + free(state->in); + } + gz_error(state, Z_OK, NULL); + free(state->path); + if (close(state->fd) == -1) + ret = Z_ERRNO; + free(state); + return ret; +} ADDED compat/zlib/infback.c Index: compat/zlib/infback.c ================================================================== --- /dev/null +++ compat/zlib/infback.c @@ -0,0 +1,640 @@ +/* infback.c -- inflate using a call-back interface + * Copyright (C) 1995-2016 Mark Adler + * For conditions of distribution and use, see copyright notice in zlib.h + */ + +/* + This code is largely copied from inflate.c. Normally either infback.o or + inflate.o would be linked into an application--not both. The interface + with inffast.c is retained so that optimized assembler-coded versions of + inflate_fast() can be used with either inflate.c or infback.c. + */ + +#include "zutil.h" +#include "inftrees.h" +#include "inflate.h" +#include "inffast.h" + +/* function prototypes */ +local void fixedtables OF((struct inflate_state FAR *state)); + +/* + strm provides memory allocation functions in zalloc and zfree, or + Z_NULL to use the library memory allocation functions. + + windowBits is in the range 8..15, and window is a user-supplied + window and output buffer that is 2**windowBits bytes. + */ +int ZEXPORT inflateBackInit_(strm, windowBits, window, version, stream_size) +z_streamp strm; +int windowBits; +unsigned char FAR *window; +const char *version; +int stream_size; +{ + struct inflate_state FAR *state; + + if (version == Z_NULL || version[0] != ZLIB_VERSION[0] || + stream_size != (int)(sizeof(z_stream))) + return Z_VERSION_ERROR; + if (strm == Z_NULL || window == Z_NULL || + windowBits < 8 || windowBits > 15) + return Z_STREAM_ERROR; + strm->msg = Z_NULL; /* in case we return an error */ + if (strm->zalloc == (alloc_func)0) { +#ifdef Z_SOLO + return Z_STREAM_ERROR; +#else + strm->zalloc = zcalloc; + strm->opaque = (voidpf)0; +#endif + } + if (strm->zfree == (free_func)0) +#ifdef Z_SOLO + return Z_STREAM_ERROR; +#else + strm->zfree = zcfree; +#endif + state = (struct inflate_state FAR *)ZALLOC(strm, 1, + sizeof(struct inflate_state)); + if (state == Z_NULL) return Z_MEM_ERROR; + Tracev((stderr, "inflate: allocated\n")); + strm->state = (struct internal_state FAR *)state; + state->dmax = 32768U; + state->wbits = (uInt)windowBits; + state->wsize = 1U << windowBits; + state->window = window; + state->wnext = 0; + state->whave = 0; + return Z_OK; +} + +/* + Return state with length and distance decoding tables and index sizes set to + fixed code decoding. Normally this returns fixed tables from inffixed.h. + If BUILDFIXED is defined, then instead this routine builds the tables the + first time it's called, and returns those tables the first time and + thereafter. This reduces the size of the code by about 2K bytes, in + exchange for a little execution time. However, BUILDFIXED should not be + used for threaded applications, since the rewriting of the tables and virgin + may not be thread-safe. + */ +local void fixedtables(state) +struct inflate_state FAR *state; +{ +#ifdef BUILDFIXED + static int virgin = 1; + static code *lenfix, *distfix; + static code fixed[544]; + + /* build fixed huffman tables if first call (may not be thread safe) */ + if (virgin) { + unsigned sym, bits; + static code *next; + + /* literal/length table */ + sym = 0; + while (sym < 144) state->lens[sym++] = 8; + while (sym < 256) state->lens[sym++] = 9; + while (sym < 280) state->lens[sym++] = 7; + while (sym < 288) state->lens[sym++] = 8; + next = fixed; + lenfix = next; + bits = 9; + inflate_table(LENS, state->lens, 288, &(next), &(bits), state->work); + + /* distance table */ + sym = 0; + while (sym < 32) state->lens[sym++] = 5; + distfix = next; + bits = 5; + inflate_table(DISTS, state->lens, 32, &(next), &(bits), state->work); + + /* do this just once */ + virgin = 0; + } +#else /* !BUILDFIXED */ +# include "inffixed.h" +#endif /* BUILDFIXED */ + state->lencode = lenfix; + state->lenbits = 9; + state->distcode = distfix; + state->distbits = 5; +} + +/* Macros for inflateBack(): */ + +/* Load returned state from inflate_fast() */ +#define LOAD() \ + do { \ + put = strm->next_out; \ + left = strm->avail_out; \ + next = strm->next_in; \ + have = strm->avail_in; \ + hold = state->hold; \ + bits = state->bits; \ + } while (0) + +/* Set state from registers for inflate_fast() */ +#define RESTORE() \ + do { \ + strm->next_out = put; \ + strm->avail_out = left; \ + strm->next_in = next; \ + strm->avail_in = have; \ + state->hold = hold; \ + state->bits = bits; \ + } while (0) + +/* Clear the input bit accumulator */ +#define INITBITS() \ + do { \ + hold = 0; \ + bits = 0; \ + } while (0) + +/* Assure that some input is available. If input is requested, but denied, + then return a Z_BUF_ERROR from inflateBack(). */ +#define PULL() \ + do { \ + if (have == 0) { \ + have = in(in_desc, &next); \ + if (have == 0) { \ + next = Z_NULL; \ + ret = Z_BUF_ERROR; \ + goto inf_leave; \ + } \ + } \ + } while (0) + +/* Get a byte of input into the bit accumulator, or return from inflateBack() + with an error if there is no input available. */ +#define PULLBYTE() \ + do { \ + PULL(); \ + have--; \ + hold += (unsigned long)(*next++) << bits; \ + bits += 8; \ + } while (0) + +/* Assure that there are at least n bits in the bit accumulator. If there is + not enough available input to do that, then return from inflateBack() with + an error. */ +#define NEEDBITS(n) \ + do { \ + while (bits < (unsigned)(n)) \ + PULLBYTE(); \ + } while (0) + +/* Return the low n bits of the bit accumulator (n < 16) */ +#define BITS(n) \ + ((unsigned)hold & ((1U << (n)) - 1)) + +/* Remove n bits from the bit accumulator */ +#define DROPBITS(n) \ + do { \ + hold >>= (n); \ + bits -= (unsigned)(n); \ + } while (0) + +/* Remove zero to seven bits as needed to go to a byte boundary */ +#define BYTEBITS() \ + do { \ + hold >>= bits & 7; \ + bits -= bits & 7; \ + } while (0) + +/* Assure that some output space is available, by writing out the window + if it's full. If the write fails, return from inflateBack() with a + Z_BUF_ERROR. */ +#define ROOM() \ + do { \ + if (left == 0) { \ + put = state->window; \ + left = state->wsize; \ + state->whave = left; \ + if (out(out_desc, put, left)) { \ + ret = Z_BUF_ERROR; \ + goto inf_leave; \ + } \ + } \ + } while (0) + +/* + strm provides the memory allocation functions and window buffer on input, + and provides information on the unused input on return. For Z_DATA_ERROR + returns, strm will also provide an error message. + + in() and out() are the call-back input and output functions. When + inflateBack() needs more input, it calls in(). When inflateBack() has + filled the window with output, or when it completes with data in the + window, it calls out() to write out the data. The application must not + change the provided input until in() is called again or inflateBack() + returns. The application must not change the window/output buffer until + inflateBack() returns. + + in() and out() are called with a descriptor parameter provided in the + inflateBack() call. This parameter can be a structure that provides the + information required to do the read or write, as well as accumulated + information on the input and output such as totals and check values. + + in() should return zero on failure. out() should return non-zero on + failure. If either in() or out() fails, than inflateBack() returns a + Z_BUF_ERROR. strm->next_in can be checked for Z_NULL to see whether it + was in() or out() that caused in the error. Otherwise, inflateBack() + returns Z_STREAM_END on success, Z_DATA_ERROR for an deflate format + error, or Z_MEM_ERROR if it could not allocate memory for the state. + inflateBack() can also return Z_STREAM_ERROR if the input parameters + are not correct, i.e. strm is Z_NULL or the state was not initialized. + */ +int ZEXPORT inflateBack(strm, in, in_desc, out, out_desc) +z_streamp strm; +in_func in; +void FAR *in_desc; +out_func out; +void FAR *out_desc; +{ + struct inflate_state FAR *state; + z_const unsigned char FAR *next; /* next input */ + unsigned char FAR *put; /* next output */ + unsigned have, left; /* available input and output */ + unsigned long hold; /* bit buffer */ + unsigned bits; /* bits in bit buffer */ + unsigned copy; /* number of stored or match bytes to copy */ + unsigned char FAR *from; /* where to copy match bytes from */ + code here; /* current decoding table entry */ + code last; /* parent table entry */ + unsigned len; /* length to copy for repeats, bits to drop */ + int ret; /* return code */ + static const unsigned short order[19] = /* permutation of code lengths */ + {16, 17, 18, 0, 8, 7, 9, 6, 10, 5, 11, 4, 12, 3, 13, 2, 14, 1, 15}; + + /* Check that the strm exists and that the state was initialized */ + if (strm == Z_NULL || strm->state == Z_NULL) + return Z_STREAM_ERROR; + state = (struct inflate_state FAR *)strm->state; + + /* Reset the state */ + strm->msg = Z_NULL; + state->mode = TYPE; + state->last = 0; + state->whave = 0; + next = strm->next_in; + have = next != Z_NULL ? strm->avail_in : 0; + hold = 0; + bits = 0; + put = state->window; + left = state->wsize; + + /* Inflate until end of block marked as last */ + for (;;) + switch (state->mode) { + case TYPE: + /* determine and dispatch block type */ + if (state->last) { + BYTEBITS(); + state->mode = DONE; + break; + } + NEEDBITS(3); + state->last = BITS(1); + DROPBITS(1); + switch (BITS(2)) { + case 0: /* stored block */ + Tracev((stderr, "inflate: stored block%s\n", + state->last ? " (last)" : "")); + state->mode = STORED; + break; + case 1: /* fixed block */ + fixedtables(state); + Tracev((stderr, "inflate: fixed codes block%s\n", + state->last ? " (last)" : "")); + state->mode = LEN; /* decode codes */ + break; + case 2: /* dynamic block */ + Tracev((stderr, "inflate: dynamic codes block%s\n", + state->last ? " (last)" : "")); + state->mode = TABLE; + break; + case 3: + strm->msg = (char *)"invalid block type"; + state->mode = BAD; + } + DROPBITS(2); + break; + + case STORED: + /* get and verify stored block length */ + BYTEBITS(); /* go to byte boundary */ + NEEDBITS(32); + if ((hold & 0xffff) != ((hold >> 16) ^ 0xffff)) { + strm->msg = (char *)"invalid stored block lengths"; + state->mode = BAD; + break; + } + state->length = (unsigned)hold & 0xffff; + Tracev((stderr, "inflate: stored length %u\n", + state->length)); + INITBITS(); + + /* copy stored block from input to output */ + while (state->length != 0) { + copy = state->length; + PULL(); + ROOM(); + if (copy > have) copy = have; + if (copy > left) copy = left; + zmemcpy(put, next, copy); + have -= copy; + next += copy; + left -= copy; + put += copy; + state->length -= copy; + } + Tracev((stderr, "inflate: stored end\n")); + state->mode = TYPE; + break; + + case TABLE: + /* get dynamic table entries descriptor */ + NEEDBITS(14); + state->nlen = BITS(5) + 257; + DROPBITS(5); + state->ndist = BITS(5) + 1; + DROPBITS(5); + state->ncode = BITS(4) + 4; + DROPBITS(4); +#ifndef PKZIP_BUG_WORKAROUND + if (state->nlen > 286 || state->ndist > 30) { + strm->msg = (char *)"too many length or distance symbols"; + state->mode = BAD; + break; + } +#endif + Tracev((stderr, "inflate: table sizes ok\n")); + + /* get code length code lengths (not a typo) */ + state->have = 0; + while (state->have < state->ncode) { + NEEDBITS(3); + state->lens[order[state->have++]] = (unsigned short)BITS(3); + DROPBITS(3); + } + while (state->have < 19) + state->lens[order[state->have++]] = 0; + state->next = state->codes; + state->lencode = (code const FAR *)(state->next); + state->lenbits = 7; + ret = inflate_table(CODES, state->lens, 19, &(state->next), + &(state->lenbits), state->work); + if (ret) { + strm->msg = (char *)"invalid code lengths set"; + state->mode = BAD; + break; + } + Tracev((stderr, "inflate: code lengths ok\n")); + + /* get length and distance code code lengths */ + state->have = 0; + while (state->have < state->nlen + state->ndist) { + for (;;) { + here = state->lencode[BITS(state->lenbits)]; + if ((unsigned)(here.bits) <= bits) break; + PULLBYTE(); + } + if (here.val < 16) { + DROPBITS(here.bits); + state->lens[state->have++] = here.val; + } + else { + if (here.val == 16) { + NEEDBITS(here.bits + 2); + DROPBITS(here.bits); + if (state->have == 0) { + strm->msg = (char *)"invalid bit length repeat"; + state->mode = BAD; + break; + } + len = (unsigned)(state->lens[state->have - 1]); + copy = 3 + BITS(2); + DROPBITS(2); + } + else if (here.val == 17) { + NEEDBITS(here.bits + 3); + DROPBITS(here.bits); + len = 0; + copy = 3 + BITS(3); + DROPBITS(3); + } + else { + NEEDBITS(here.bits + 7); + DROPBITS(here.bits); + len = 0; + copy = 11 + BITS(7); + DROPBITS(7); + } + if (state->have + copy > state->nlen + state->ndist) { + strm->msg = (char *)"invalid bit length repeat"; + state->mode = BAD; + break; + } + while (copy--) + state->lens[state->have++] = (unsigned short)len; + } + } + + /* handle error breaks in while */ + if (state->mode == BAD) break; + + /* check for end-of-block code (better have one) */ + if (state->lens[256] == 0) { + strm->msg = (char *)"invalid code -- missing end-of-block"; + state->mode = BAD; + break; + } + + /* build code tables -- note: do not change the lenbits or distbits + values here (9 and 6) without reading the comments in inftrees.h + concerning the ENOUGH constants, which depend on those values */ + state->next = state->codes; + state->lencode = (code const FAR *)(state->next); + state->lenbits = 9; + ret = inflate_table(LENS, state->lens, state->nlen, &(state->next), + &(state->lenbits), state->work); + if (ret) { + strm->msg = (char *)"invalid literal/lengths set"; + state->mode = BAD; + break; + } + state->distcode = (code const FAR *)(state->next); + state->distbits = 6; + ret = inflate_table(DISTS, state->lens + state->nlen, state->ndist, + &(state->next), &(state->distbits), state->work); + if (ret) { + strm->msg = (char *)"invalid distances set"; + state->mode = BAD; + break; + } + Tracev((stderr, "inflate: codes ok\n")); + state->mode = LEN; + + case LEN: + /* use inflate_fast() if we have enough input and output */ + if (have >= 6 && left >= 258) { + RESTORE(); + if (state->whave < state->wsize) + state->whave = state->wsize - left; + inflate_fast(strm, state->wsize); + LOAD(); + break; + } + + /* get a literal, length, or end-of-block code */ + for (;;) { + here = state->lencode[BITS(state->lenbits)]; + if ((unsigned)(here.bits) <= bits) break; + PULLBYTE(); + } + if (here.op && (here.op & 0xf0) == 0) { + last = here; + for (;;) { + here = state->lencode[last.val + + (BITS(last.bits + last.op) >> last.bits)]; + if ((unsigned)(last.bits + here.bits) <= bits) break; + PULLBYTE(); + } + DROPBITS(last.bits); + } + DROPBITS(here.bits); + state->length = (unsigned)here.val; + + /* process literal */ + if (here.op == 0) { + Tracevv((stderr, here.val >= 0x20 && here.val < 0x7f ? + "inflate: literal '%c'\n" : + "inflate: literal 0x%02x\n", here.val)); + ROOM(); + *put++ = (unsigned char)(state->length); + left--; + state->mode = LEN; + break; + } + + /* process end of block */ + if (here.op & 32) { + Tracevv((stderr, "inflate: end of block\n")); + state->mode = TYPE; + break; + } + + /* invalid code */ + if (here.op & 64) { + strm->msg = (char *)"invalid literal/length code"; + state->mode = BAD; + break; + } + + /* length code -- get extra bits, if any */ + state->extra = (unsigned)(here.op) & 15; + if (state->extra != 0) { + NEEDBITS(state->extra); + state->length += BITS(state->extra); + DROPBITS(state->extra); + } + Tracevv((stderr, "inflate: length %u\n", state->length)); + + /* get distance code */ + for (;;) { + here = state->distcode[BITS(state->distbits)]; + if ((unsigned)(here.bits) <= bits) break; + PULLBYTE(); + } + if ((here.op & 0xf0) == 0) { + last = here; + for (;;) { + here = state->distcode[last.val + + (BITS(last.bits + last.op) >> last.bits)]; + if ((unsigned)(last.bits + here.bits) <= bits) break; + PULLBYTE(); + } + DROPBITS(last.bits); + } + DROPBITS(here.bits); + if (here.op & 64) { + strm->msg = (char *)"invalid distance code"; + state->mode = BAD; + break; + } + state->offset = (unsigned)here.val; + + /* get distance extra bits, if any */ + state->extra = (unsigned)(here.op) & 15; + if (state->extra != 0) { + NEEDBITS(state->extra); + state->offset += BITS(state->extra); + DROPBITS(state->extra); + } + if (state->offset > state->wsize - (state->whave < state->wsize ? + left : 0)) { + strm->msg = (char *)"invalid distance too far back"; + state->mode = BAD; + break; + } + Tracevv((stderr, "inflate: distance %u\n", state->offset)); + + /* copy match from window to output */ + do { + ROOM(); + copy = state->wsize - state->offset; + if (copy < left) { + from = put + copy; + copy = left - copy; + } + else { + from = put - state->offset; + copy = left; + } + if (copy > state->length) copy = state->length; + state->length -= copy; + left -= copy; + do { + *put++ = *from++; + } while (--copy); + } while (state->length != 0); + break; + + case DONE: + /* inflate stream terminated properly -- write leftover output */ + ret = Z_STREAM_END; + if (left < state->wsize) { + if (out(out_desc, state->window, state->wsize - left)) + ret = Z_BUF_ERROR; + } + goto inf_leave; + + case BAD: + ret = Z_DATA_ERROR; + goto inf_leave; + + default: /* can't happen, but makes compilers happy */ + ret = Z_STREAM_ERROR; + goto inf_leave; + } + + /* Return unused input */ + inf_leave: + strm->next_in = next; + strm->avail_in = have; + return ret; +} + +int ZEXPORT inflateBackEnd(strm) +z_streamp strm; +{ + if (strm == Z_NULL || strm->state == Z_NULL || strm->zfree == (free_func)0) + return Z_STREAM_ERROR; + ZFREE(strm, strm->state); + strm->state = Z_NULL; + Tracev((stderr, "inflate: end\n")); + return Z_OK; +} ADDED compat/zlib/inffast.c Index: compat/zlib/inffast.c ================================================================== --- /dev/null +++ compat/zlib/inffast.c @@ -0,0 +1,323 @@ +/* inffast.c -- fast decoding + * Copyright (C) 1995-2017 Mark Adler + * For conditions of distribution and use, see copyright notice in zlib.h + */ + +#include "zutil.h" +#include "inftrees.h" +#include "inflate.h" +#include "inffast.h" + +#ifdef ASMINF +# pragma message("Assembler code may have bugs -- use at your own risk") +#else + +/* + Decode literal, length, and distance codes and write out the resulting + literal and match bytes until either not enough input or output is + available, an end-of-block is encountered, or a data error is encountered. + When large enough input and output buffers are supplied to inflate(), for + example, a 16K input buffer and a 64K output buffer, more than 95% of the + inflate execution time is spent in this routine. + + Entry assumptions: + + state->mode == LEN + strm->avail_in >= 6 + strm->avail_out >= 258 + start >= strm->avail_out + state->bits < 8 + + On return, state->mode is one of: + + LEN -- ran out of enough output space or enough available input + TYPE -- reached end of block code, inflate() to interpret next block + BAD -- error in block data + + Notes: + + - The maximum input bits used by a length/distance pair is 15 bits for the + length code, 5 bits for the length extra, 15 bits for the distance code, + and 13 bits for the distance extra. This totals 48 bits, or six bytes. + Therefore if strm->avail_in >= 6, then there is enough input to avoid + checking for available input while decoding. + + - The maximum bytes that a single length/distance pair can output is 258 + bytes, which is the maximum length that can be coded. inflate_fast() + requires strm->avail_out >= 258 for each loop to avoid checking for + output space. + */ +void ZLIB_INTERNAL inflate_fast(strm, start) +z_streamp strm; +unsigned start; /* inflate()'s starting value for strm->avail_out */ +{ + struct inflate_state FAR *state; + z_const unsigned char FAR *in; /* local strm->next_in */ + z_const unsigned char FAR *last; /* have enough input while in < last */ + unsigned char FAR *out; /* local strm->next_out */ + unsigned char FAR *beg; /* inflate()'s initial strm->next_out */ + unsigned char FAR *end; /* while out < end, enough space available */ +#ifdef INFLATE_STRICT + unsigned dmax; /* maximum distance from zlib header */ +#endif + unsigned wsize; /* window size or zero if not using window */ + unsigned whave; /* valid bytes in the window */ + unsigned wnext; /* window write index */ + unsigned char FAR *window; /* allocated sliding window, if wsize != 0 */ + unsigned long hold; /* local strm->hold */ + unsigned bits; /* local strm->bits */ + code const FAR *lcode; /* local strm->lencode */ + code const FAR *dcode; /* local strm->distcode */ + unsigned lmask; /* mask for first level of length codes */ + unsigned dmask; /* mask for first level of distance codes */ + code here; /* retrieved table entry */ + unsigned op; /* code bits, operation, extra bits, or */ + /* window position, window bytes to copy */ + unsigned len; /* match length, unused bytes */ + unsigned dist; /* match distance */ + unsigned char FAR *from; /* where to copy match from */ + + /* copy state to local variables */ + state = (struct inflate_state FAR *)strm->state; + in = strm->next_in; + last = in + (strm->avail_in - 5); + out = strm->next_out; + beg = out - (start - strm->avail_out); + end = out + (strm->avail_out - 257); +#ifdef INFLATE_STRICT + dmax = state->dmax; +#endif + wsize = state->wsize; + whave = state->whave; + wnext = state->wnext; + window = state->window; + hold = state->hold; + bits = state->bits; + lcode = state->lencode; + dcode = state->distcode; + lmask = (1U << state->lenbits) - 1; + dmask = (1U << state->distbits) - 1; + + /* decode literals and length/distances until end-of-block or not enough + input data or output space */ + do { + if (bits < 15) { + hold += (unsigned long)(*in++) << bits; + bits += 8; + hold += (unsigned long)(*in++) << bits; + bits += 8; + } + here = lcode[hold & lmask]; + dolen: + op = (unsigned)(here.bits); + hold >>= op; + bits -= op; + op = (unsigned)(here.op); + if (op == 0) { /* literal */ + Tracevv((stderr, here.val >= 0x20 && here.val < 0x7f ? + "inflate: literal '%c'\n" : + "inflate: literal 0x%02x\n", here.val)); + *out++ = (unsigned char)(here.val); + } + else if (op & 16) { /* length base */ + len = (unsigned)(here.val); + op &= 15; /* number of extra bits */ + if (op) { + if (bits < op) { + hold += (unsigned long)(*in++) << bits; + bits += 8; + } + len += (unsigned)hold & ((1U << op) - 1); + hold >>= op; + bits -= op; + } + Tracevv((stderr, "inflate: length %u\n", len)); + if (bits < 15) { + hold += (unsigned long)(*in++) << bits; + bits += 8; + hold += (unsigned long)(*in++) << bits; + bits += 8; + } + here = dcode[hold & dmask]; + dodist: + op = (unsigned)(here.bits); + hold >>= op; + bits -= op; + op = (unsigned)(here.op); + if (op & 16) { /* distance base */ + dist = (unsigned)(here.val); + op &= 15; /* number of extra bits */ + if (bits < op) { + hold += (unsigned long)(*in++) << bits; + bits += 8; + if (bits < op) { + hold += (unsigned long)(*in++) << bits; + bits += 8; + } + } + dist += (unsigned)hold & ((1U << op) - 1); +#ifdef INFLATE_STRICT + if (dist > dmax) { + strm->msg = (char *)"invalid distance too far back"; + state->mode = BAD; + break; + } +#endif + hold >>= op; + bits -= op; + Tracevv((stderr, "inflate: distance %u\n", dist)); + op = (unsigned)(out - beg); /* max distance in output */ + if (dist > op) { /* see if copy from window */ + op = dist - op; /* distance back in window */ + if (op > whave) { + if (state->sane) { + strm->msg = + (char *)"invalid distance too far back"; + state->mode = BAD; + break; + } +#ifdef INFLATE_ALLOW_INVALID_DISTANCE_TOOFAR_ARRR + if (len <= op - whave) { + do { + *out++ = 0; + } while (--len); + continue; + } + len -= op - whave; + do { + *out++ = 0; + } while (--op > whave); + if (op == 0) { + from = out - dist; + do { + *out++ = *from++; + } while (--len); + continue; + } +#endif + } + from = window; + if (wnext == 0) { /* very common case */ + from += wsize - op; + if (op < len) { /* some from window */ + len -= op; + do { + *out++ = *from++; + } while (--op); + from = out - dist; /* rest from output */ + } + } + else if (wnext < op) { /* wrap around window */ + from += wsize + wnext - op; + op -= wnext; + if (op < len) { /* some from end of window */ + len -= op; + do { + *out++ = *from++; + } while (--op); + from = window; + if (wnext < len) { /* some from start of window */ + op = wnext; + len -= op; + do { + *out++ = *from++; + } while (--op); + from = out - dist; /* rest from output */ + } + } + } + else { /* contiguous in window */ + from += wnext - op; + if (op < len) { /* some from window */ + len -= op; + do { + *out++ = *from++; + } while (--op); + from = out - dist; /* rest from output */ + } + } + while (len > 2) { + *out++ = *from++; + *out++ = *from++; + *out++ = *from++; + len -= 3; + } + if (len) { + *out++ = *from++; + if (len > 1) + *out++ = *from++; + } + } + else { + from = out - dist; /* copy direct from output */ + do { /* minimum length is three */ + *out++ = *from++; + *out++ = *from++; + *out++ = *from++; + len -= 3; + } while (len > 2); + if (len) { + *out++ = *from++; + if (len > 1) + *out++ = *from++; + } + } + } + else if ((op & 64) == 0) { /* 2nd level distance code */ + here = dcode[here.val + (hold & ((1U << op) - 1))]; + goto dodist; + } + else { + strm->msg = (char *)"invalid distance code"; + state->mode = BAD; + break; + } + } + else if ((op & 64) == 0) { /* 2nd level length code */ + here = lcode[here.val + (hold & ((1U << op) - 1))]; + goto dolen; + } + else if (op & 32) { /* end-of-block */ + Tracevv((stderr, "inflate: end of block\n")); + state->mode = TYPE; + break; + } + else { + strm->msg = (char *)"invalid literal/length code"; + state->mode = BAD; + break; + } + } while (in < last && out < end); + + /* return unused bytes (on entry, bits < 8, so in won't go too far back) */ + len = bits >> 3; + in -= len; + bits -= len << 3; + hold &= (1U << bits) - 1; + + /* update state and return */ + strm->next_in = in; + strm->next_out = out; + strm->avail_in = (unsigned)(in < last ? 5 + (last - in) : 5 - (in - last)); + strm->avail_out = (unsigned)(out < end ? + 257 + (end - out) : 257 - (out - end)); + state->hold = hold; + state->bits = bits; + return; +} + +/* + inflate_fast() speedups that turned out slower (on a PowerPC G3 750CXe): + - Using bit fields for code structure + - Different op definition to avoid & for extra bits (do & for table bits) + - Three separate decoding do-loops for direct, window, and wnext == 0 + - Special case for distance > 1 copies to do overlapped load and store copy + - Explicit branch predictions (based on measured branch probabilities) + - Deferring match copy and interspersed it with decoding subsequent codes + - Swapping literal/length else + - Swapping window/direct else + - Larger unrolled copy loops (three is about right) + - Moving len -= 3 statement into middle of loop + */ + +#endif /* !ASMINF */ ADDED compat/zlib/inffast.h Index: compat/zlib/inffast.h ================================================================== --- /dev/null +++ compat/zlib/inffast.h @@ -0,0 +1,11 @@ +/* inffast.h -- header to use inffast.c + * Copyright (C) 1995-2003, 2010 Mark Adler + * For conditions of distribution and use, see copyright notice in zlib.h + */ + +/* WARNING: this file should *not* be used by applications. It is + part of the implementation of the compression library and is + subject to change. Applications should only use zlib.h. + */ + +void ZLIB_INTERNAL inflate_fast OF((z_streamp strm, unsigned start)); ADDED compat/zlib/inffixed.h Index: compat/zlib/inffixed.h ================================================================== --- /dev/null +++ compat/zlib/inffixed.h @@ -0,0 +1,94 @@ + /* inffixed.h -- table for decoding fixed codes + * Generated automatically by makefixed(). + */ + + /* WARNING: this file should *not* be used by applications. + It is part of the implementation of this library and is + subject to change. Applications should only use zlib.h. + */ + + static const code lenfix[512] = { + {96,7,0},{0,8,80},{0,8,16},{20,8,115},{18,7,31},{0,8,112},{0,8,48}, + {0,9,192},{16,7,10},{0,8,96},{0,8,32},{0,9,160},{0,8,0},{0,8,128}, + {0,8,64},{0,9,224},{16,7,6},{0,8,88},{0,8,24},{0,9,144},{19,7,59}, + {0,8,120},{0,8,56},{0,9,208},{17,7,17},{0,8,104},{0,8,40},{0,9,176}, + {0,8,8},{0,8,136},{0,8,72},{0,9,240},{16,7,4},{0,8,84},{0,8,20}, + {21,8,227},{19,7,43},{0,8,116},{0,8,52},{0,9,200},{17,7,13},{0,8,100}, + {0,8,36},{0,9,168},{0,8,4},{0,8,132},{0,8,68},{0,9,232},{16,7,8}, + {0,8,92},{0,8,28},{0,9,152},{20,7,83},{0,8,124},{0,8,60},{0,9,216}, + {18,7,23},{0,8,108},{0,8,44},{0,9,184},{0,8,12},{0,8,140},{0,8,76}, + {0,9,248},{16,7,3},{0,8,82},{0,8,18},{21,8,163},{19,7,35},{0,8,114}, + {0,8,50},{0,9,196},{17,7,11},{0,8,98},{0,8,34},{0,9,164},{0,8,2}, + {0,8,130},{0,8,66},{0,9,228},{16,7,7},{0,8,90},{0,8,26},{0,9,148}, + {20,7,67},{0,8,122},{0,8,58},{0,9,212},{18,7,19},{0,8,106},{0,8,42}, + {0,9,180},{0,8,10},{0,8,138},{0,8,74},{0,9,244},{16,7,5},{0,8,86}, + {0,8,22},{64,8,0},{19,7,51},{0,8,118},{0,8,54},{0,9,204},{17,7,15}, + {0,8,102},{0,8,38},{0,9,172},{0,8,6},{0,8,134},{0,8,70},{0,9,236}, + {16,7,9},{0,8,94},{0,8,30},{0,9,156},{20,7,99},{0,8,126},{0,8,62}, + {0,9,220},{18,7,27},{0,8,110},{0,8,46},{0,9,188},{0,8,14},{0,8,142}, + {0,8,78},{0,9,252},{96,7,0},{0,8,81},{0,8,17},{21,8,131},{18,7,31}, + {0,8,113},{0,8,49},{0,9,194},{16,7,10},{0,8,97},{0,8,33},{0,9,162}, + {0,8,1},{0,8,129},{0,8,65},{0,9,226},{16,7,6},{0,8,89},{0,8,25}, + {0,9,146},{19,7,59},{0,8,121},{0,8,57},{0,9,210},{17,7,17},{0,8,105}, + {0,8,41},{0,9,178},{0,8,9},{0,8,137},{0,8,73},{0,9,242},{16,7,4}, + {0,8,85},{0,8,21},{16,8,258},{19,7,43},{0,8,117},{0,8,53},{0,9,202}, + {17,7,13},{0,8,101},{0,8,37},{0,9,170},{0,8,5},{0,8,133},{0,8,69}, + {0,9,234},{16,7,8},{0,8,93},{0,8,29},{0,9,154},{20,7,83},{0,8,125}, + {0,8,61},{0,9,218},{18,7,23},{0,8,109},{0,8,45},{0,9,186},{0,8,13}, + {0,8,141},{0,8,77},{0,9,250},{16,7,3},{0,8,83},{0,8,19},{21,8,195}, + {19,7,35},{0,8,115},{0,8,51},{0,9,198},{17,7,11},{0,8,99},{0,8,35}, + {0,9,166},{0,8,3},{0,8,131},{0,8,67},{0,9,230},{16,7,7},{0,8,91}, + {0,8,27},{0,9,150},{20,7,67},{0,8,123},{0,8,59},{0,9,214},{18,7,19}, + {0,8,107},{0,8,43},{0,9,182},{0,8,11},{0,8,139},{0,8,75},{0,9,246}, + {16,7,5},{0,8,87},{0,8,23},{64,8,0},{19,7,51},{0,8,119},{0,8,55}, + {0,9,206},{17,7,15},{0,8,103},{0,8,39},{0,9,174},{0,8,7},{0,8,135}, + {0,8,71},{0,9,238},{16,7,9},{0,8,95},{0,8,31},{0,9,158},{20,7,99}, + {0,8,127},{0,8,63},{0,9,222},{18,7,27},{0,8,111},{0,8,47},{0,9,190}, + {0,8,15},{0,8,143},{0,8,79},{0,9,254},{96,7,0},{0,8,80},{0,8,16}, + {20,8,115},{18,7,31},{0,8,112},{0,8,48},{0,9,193},{16,7,10},{0,8,96}, + {0,8,32},{0,9,161},{0,8,0},{0,8,128},{0,8,64},{0,9,225},{16,7,6}, + {0,8,88},{0,8,24},{0,9,145},{19,7,59},{0,8,120},{0,8,56},{0,9,209}, + {17,7,17},{0,8,104},{0,8,40},{0,9,177},{0,8,8},{0,8,136},{0,8,72}, + {0,9,241},{16,7,4},{0,8,84},{0,8,20},{21,8,227},{19,7,43},{0,8,116}, + {0,8,52},{0,9,201},{17,7,13},{0,8,100},{0,8,36},{0,9,169},{0,8,4}, + {0,8,132},{0,8,68},{0,9,233},{16,7,8},{0,8,92},{0,8,28},{0,9,153}, + {20,7,83},{0,8,124},{0,8,60},{0,9,217},{18,7,23},{0,8,108},{0,8,44}, + {0,9,185},{0,8,12},{0,8,140},{0,8,76},{0,9,249},{16,7,3},{0,8,82}, + {0,8,18},{21,8,163},{19,7,35},{0,8,114},{0,8,50},{0,9,197},{17,7,11}, + {0,8,98},{0,8,34},{0,9,165},{0,8,2},{0,8,130},{0,8,66},{0,9,229}, + {16,7,7},{0,8,90},{0,8,26},{0,9,149},{20,7,67},{0,8,122},{0,8,58}, + {0,9,213},{18,7,19},{0,8,106},{0,8,42},{0,9,181},{0,8,10},{0,8,138}, + {0,8,74},{0,9,245},{16,7,5},{0,8,86},{0,8,22},{64,8,0},{19,7,51}, + {0,8,118},{0,8,54},{0,9,205},{17,7,15},{0,8,102},{0,8,38},{0,9,173}, + {0,8,6},{0,8,134},{0,8,70},{0,9,237},{16,7,9},{0,8,94},{0,8,30}, + {0,9,157},{20,7,99},{0,8,126},{0,8,62},{0,9,221},{18,7,27},{0,8,110}, + {0,8,46},{0,9,189},{0,8,14},{0,8,142},{0,8,78},{0,9,253},{96,7,0}, + {0,8,81},{0,8,17},{21,8,131},{18,7,31},{0,8,113},{0,8,49},{0,9,195}, + {16,7,10},{0,8,97},{0,8,33},{0,9,163},{0,8,1},{0,8,129},{0,8,65}, + {0,9,227},{16,7,6},{0,8,89},{0,8,25},{0,9,147},{19,7,59},{0,8,121}, + {0,8,57},{0,9,211},{17,7,17},{0,8,105},{0,8,41},{0,9,179},{0,8,9}, + {0,8,137},{0,8,73},{0,9,243},{16,7,4},{0,8,85},{0,8,21},{16,8,258}, + {19,7,43},{0,8,117},{0,8,53},{0,9,203},{17,7,13},{0,8,101},{0,8,37}, + {0,9,171},{0,8,5},{0,8,133},{0,8,69},{0,9,235},{16,7,8},{0,8,93}, + {0,8,29},{0,9,155},{20,7,83},{0,8,125},{0,8,61},{0,9,219},{18,7,23}, + {0,8,109},{0,8,45},{0,9,187},{0,8,13},{0,8,141},{0,8,77},{0,9,251}, + {16,7,3},{0,8,83},{0,8,19},{21,8,195},{19,7,35},{0,8,115},{0,8,51}, + {0,9,199},{17,7,11},{0,8,99},{0,8,35},{0,9,167},{0,8,3},{0,8,131}, + {0,8,67},{0,9,231},{16,7,7},{0,8,91},{0,8,27},{0,9,151},{20,7,67}, + {0,8,123},{0,8,59},{0,9,215},{18,7,19},{0,8,107},{0,8,43},{0,9,183}, + {0,8,11},{0,8,139},{0,8,75},{0,9,247},{16,7,5},{0,8,87},{0,8,23}, + {64,8,0},{19,7,51},{0,8,119},{0,8,55},{0,9,207},{17,7,15},{0,8,103}, + {0,8,39},{0,9,175},{0,8,7},{0,8,135},{0,8,71},{0,9,239},{16,7,9}, + {0,8,95},{0,8,31},{0,9,159},{20,7,99},{0,8,127},{0,8,63},{0,9,223}, + {18,7,27},{0,8,111},{0,8,47},{0,9,191},{0,8,15},{0,8,143},{0,8,79}, + {0,9,255} + }; + + static const code distfix[32] = { + {16,5,1},{23,5,257},{19,5,17},{27,5,4097},{17,5,5},{25,5,1025}, + {21,5,65},{29,5,16385},{16,5,3},{24,5,513},{20,5,33},{28,5,8193}, + {18,5,9},{26,5,2049},{22,5,129},{64,5,0},{16,5,2},{23,5,385}, + {19,5,25},{27,5,6145},{17,5,7},{25,5,1537},{21,5,97},{29,5,24577}, + {16,5,4},{24,5,769},{20,5,49},{28,5,12289},{18,5,13},{26,5,3073}, + {22,5,193},{64,5,0} + }; ADDED compat/zlib/inflate.c Index: compat/zlib/inflate.c ================================================================== --- /dev/null +++ compat/zlib/inflate.c @@ -0,0 +1,1561 @@ +/* inflate.c -- zlib decompression + * Copyright (C) 1995-2016 Mark Adler + * For conditions of distribution and use, see copyright notice in zlib.h + */ + +/* + * Change history: + * + * 1.2.beta0 24 Nov 2002 + * - First version -- complete rewrite of inflate to simplify code, avoid + * creation of window when not needed, minimize use of window when it is + * needed, make inffast.c even faster, implement gzip decoding, and to + * improve code readability and style over the previous zlib inflate code + * + * 1.2.beta1 25 Nov 2002 + * - Use pointers for available input and output checking in inffast.c + * - Remove input and output counters in inffast.c + * - Change inffast.c entry and loop from avail_in >= 7 to >= 6 + * - Remove unnecessary second byte pull from length extra in inffast.c + * - Unroll direct copy to three copies per loop in inffast.c + * + * 1.2.beta2 4 Dec 2002 + * - Change external routine names to reduce potential conflicts + * - Correct filename to inffixed.h for fixed tables in inflate.c + * - Make hbuf[] unsigned char to match parameter type in inflate.c + * - Change strm->next_out[-state->offset] to *(strm->next_out - state->offset) + * to avoid negation problem on Alphas (64 bit) in inflate.c + * + * 1.2.beta3 22 Dec 2002 + * - Add comments on state->bits assertion in inffast.c + * - Add comments on op field in inftrees.h + * - Fix bug in reuse of allocated window after inflateReset() + * - Remove bit fields--back to byte structure for speed + * - Remove distance extra == 0 check in inflate_fast()--only helps for lengths + * - Change post-increments to pre-increments in inflate_fast(), PPC biased? + * - Add compile time option, POSTINC, to use post-increments instead (Intel?) + * - Make MATCH copy in inflate() much faster for when inflate_fast() not used + * - Use local copies of stream next and avail values, as well as local bit + * buffer and bit count in inflate()--for speed when inflate_fast() not used + * + * 1.2.beta4 1 Jan 2003 + * - Split ptr - 257 statements in inflate_table() to avoid compiler warnings + * - Move a comment on output buffer sizes from inffast.c to inflate.c + * - Add comments in inffast.c to introduce the inflate_fast() routine + * - Rearrange window copies in inflate_fast() for speed and simplification + * - Unroll last copy for window match in inflate_fast() + * - Use local copies of window variables in inflate_fast() for speed + * - Pull out common wnext == 0 case for speed in inflate_fast() + * - Make op and len in inflate_fast() unsigned for consistency + * - Add FAR to lcode and dcode declarations in inflate_fast() + * - Simplified bad distance check in inflate_fast() + * - Added inflateBackInit(), inflateBack(), and inflateBackEnd() in new + * source file infback.c to provide a call-back interface to inflate for + * programs like gzip and unzip -- uses window as output buffer to avoid + * window copying + * + * 1.2.beta5 1 Jan 2003 + * - Improved inflateBack() interface to allow the caller to provide initial + * input in strm. + * - Fixed stored blocks bug in inflateBack() + * + * 1.2.beta6 4 Jan 2003 + * - Added comments in inffast.c on effectiveness of POSTINC + * - Typecasting all around to reduce compiler warnings + * - Changed loops from while (1) or do {} while (1) to for (;;), again to + * make compilers happy + * - Changed type of window in inflateBackInit() to unsigned char * + * + * 1.2.beta7 27 Jan 2003 + * - Changed many types to unsigned or unsigned short to avoid warnings + * - Added inflateCopy() function + * + * 1.2.0 9 Mar 2003 + * - Changed inflateBack() interface to provide separate opaque descriptors + * for the in() and out() functions + * - Changed inflateBack() argument and in_func typedef to swap the length + * and buffer address return values for the input function + * - Check next_in and next_out for Z_NULL on entry to inflate() + * + * The history for versions after 1.2.0 are in ChangeLog in zlib distribution. + */ + +#include "zutil.h" +#include "inftrees.h" +#include "inflate.h" +#include "inffast.h" + +#ifdef MAKEFIXED +# ifndef BUILDFIXED +# define BUILDFIXED +# endif +#endif + +/* function prototypes */ +local int inflateStateCheck OF((z_streamp strm)); +local void fixedtables OF((struct inflate_state FAR *state)); +local int updatewindow OF((z_streamp strm, const unsigned char FAR *end, + unsigned copy)); +#ifdef BUILDFIXED + void makefixed OF((void)); +#endif +local unsigned syncsearch OF((unsigned FAR *have, const unsigned char FAR *buf, + unsigned len)); + +local int inflateStateCheck(strm) +z_streamp strm; +{ + struct inflate_state FAR *state; + if (strm == Z_NULL || + strm->zalloc == (alloc_func)0 || strm->zfree == (free_func)0) + return 1; + state = (struct inflate_state FAR *)strm->state; + if (state == Z_NULL || state->strm != strm || + state->mode < HEAD || state->mode > SYNC) + return 1; + return 0; +} + +int ZEXPORT inflateResetKeep(strm) +z_streamp strm; +{ + struct inflate_state FAR *state; + + if (inflateStateCheck(strm)) return Z_STREAM_ERROR; + state = (struct inflate_state FAR *)strm->state; + strm->total_in = strm->total_out = state->total = 0; + strm->msg = Z_NULL; + if (state->wrap) /* to support ill-conceived Java test suite */ + strm->adler = state->wrap & 1; + state->mode = HEAD; + state->last = 0; + state->havedict = 0; + state->dmax = 32768U; + state->head = Z_NULL; + state->hold = 0; + state->bits = 0; + state->lencode = state->distcode = state->next = state->codes; + state->sane = 1; + state->back = -1; + Tracev((stderr, "inflate: reset\n")); + return Z_OK; +} + +int ZEXPORT inflateReset(strm) +z_streamp strm; +{ + struct inflate_state FAR *state; + + if (inflateStateCheck(strm)) return Z_STREAM_ERROR; + state = (struct inflate_state FAR *)strm->state; + state->wsize = 0; + state->whave = 0; + state->wnext = 0; + return inflateResetKeep(strm); +} + +int ZEXPORT inflateReset2(strm, windowBits) +z_streamp strm; +int windowBits; +{ + int wrap; + struct inflate_state FAR *state; + + /* get the state */ + if (inflateStateCheck(strm)) return Z_STREAM_ERROR; + state = (struct inflate_state FAR *)strm->state; + + /* extract wrap request from windowBits parameter */ + if (windowBits < 0) { + wrap = 0; + windowBits = -windowBits; + } + else { + wrap = (windowBits >> 4) + 5; +#ifdef GUNZIP + if (windowBits < 48) + windowBits &= 15; +#endif + } + + /* set number of window bits, free window if different */ + if (windowBits && (windowBits < 8 || windowBits > 15)) + return Z_STREAM_ERROR; + if (state->window != Z_NULL && state->wbits != (unsigned)windowBits) { + ZFREE(strm, state->window); + state->window = Z_NULL; + } + + /* update state and reset the rest of it */ + state->wrap = wrap; + state->wbits = (unsigned)windowBits; + return inflateReset(strm); +} + +int ZEXPORT inflateInit2_(strm, windowBits, version, stream_size) +z_streamp strm; +int windowBits; +const char *version; +int stream_size; +{ + int ret; + struct inflate_state FAR *state; + + if (version == Z_NULL || version[0] != ZLIB_VERSION[0] || + stream_size != (int)(sizeof(z_stream))) + return Z_VERSION_ERROR; + if (strm == Z_NULL) return Z_STREAM_ERROR; + strm->msg = Z_NULL; /* in case we return an error */ + if (strm->zalloc == (alloc_func)0) { +#ifdef Z_SOLO + return Z_STREAM_ERROR; +#else + strm->zalloc = zcalloc; + strm->opaque = (voidpf)0; +#endif + } + if (strm->zfree == (free_func)0) +#ifdef Z_SOLO + return Z_STREAM_ERROR; +#else + strm->zfree = zcfree; +#endif + state = (struct inflate_state FAR *) + ZALLOC(strm, 1, sizeof(struct inflate_state)); + if (state == Z_NULL) return Z_MEM_ERROR; + Tracev((stderr, "inflate: allocated\n")); + strm->state = (struct internal_state FAR *)state; + state->strm = strm; + state->window = Z_NULL; + state->mode = HEAD; /* to pass state test in inflateReset2() */ + ret = inflateReset2(strm, windowBits); + if (ret != Z_OK) { + ZFREE(strm, state); + strm->state = Z_NULL; + } + return ret; +} + +int ZEXPORT inflateInit_(strm, version, stream_size) +z_streamp strm; +const char *version; +int stream_size; +{ + return inflateInit2_(strm, DEF_WBITS, version, stream_size); +} + +int ZEXPORT inflatePrime(strm, bits, value) +z_streamp strm; +int bits; +int value; +{ + struct inflate_state FAR *state; + + if (inflateStateCheck(strm)) return Z_STREAM_ERROR; + state = (struct inflate_state FAR *)strm->state; + if (bits < 0) { + state->hold = 0; + state->bits = 0; + return Z_OK; + } + if (bits > 16 || state->bits + (uInt)bits > 32) return Z_STREAM_ERROR; + value &= (1L << bits) - 1; + state->hold += (unsigned)value << state->bits; + state->bits += (uInt)bits; + return Z_OK; +} + +/* + Return state with length and distance decoding tables and index sizes set to + fixed code decoding. Normally this returns fixed tables from inffixed.h. + If BUILDFIXED is defined, then instead this routine builds the tables the + first time it's called, and returns those tables the first time and + thereafter. This reduces the size of the code by about 2K bytes, in + exchange for a little execution time. However, BUILDFIXED should not be + used for threaded applications, since the rewriting of the tables and virgin + may not be thread-safe. + */ +local void fixedtables(state) +struct inflate_state FAR *state; +{ +#ifdef BUILDFIXED + static int virgin = 1; + static code *lenfix, *distfix; + static code fixed[544]; + + /* build fixed huffman tables if first call (may not be thread safe) */ + if (virgin) { + unsigned sym, bits; + static code *next; + + /* literal/length table */ + sym = 0; + while (sym < 144) state->lens[sym++] = 8; + while (sym < 256) state->lens[sym++] = 9; + while (sym < 280) state->lens[sym++] = 7; + while (sym < 288) state->lens[sym++] = 8; + next = fixed; + lenfix = next; + bits = 9; + inflate_table(LENS, state->lens, 288, &(next), &(bits), state->work); + + /* distance table */ + sym = 0; + while (sym < 32) state->lens[sym++] = 5; + distfix = next; + bits = 5; + inflate_table(DISTS, state->lens, 32, &(next), &(bits), state->work); + + /* do this just once */ + virgin = 0; + } +#else /* !BUILDFIXED */ +# include "inffixed.h" +#endif /* BUILDFIXED */ + state->lencode = lenfix; + state->lenbits = 9; + state->distcode = distfix; + state->distbits = 5; +} + +#ifdef MAKEFIXED +#include + +/* + Write out the inffixed.h that is #include'd above. Defining MAKEFIXED also + defines BUILDFIXED, so the tables are built on the fly. makefixed() writes + those tables to stdout, which would be piped to inffixed.h. A small program + can simply call makefixed to do this: + + void makefixed(void); + + int main(void) + { + makefixed(); + return 0; + } + + Then that can be linked with zlib built with MAKEFIXED defined and run: + + a.out > inffixed.h + */ +void makefixed() +{ + unsigned low, size; + struct inflate_state state; + + fixedtables(&state); + puts(" /* inffixed.h -- table for decoding fixed codes"); + puts(" * Generated automatically by makefixed()."); + puts(" */"); + puts(""); + puts(" /* WARNING: this file should *not* be used by applications."); + puts(" It is part of the implementation of this library and is"); + puts(" subject to change. Applications should only use zlib.h."); + puts(" */"); + puts(""); + size = 1U << 9; + printf(" static const code lenfix[%u] = {", size); + low = 0; + for (;;) { + if ((low % 7) == 0) printf("\n "); + printf("{%u,%u,%d}", (low & 127) == 99 ? 64 : state.lencode[low].op, + state.lencode[low].bits, state.lencode[low].val); + if (++low == size) break; + putchar(','); + } + puts("\n };"); + size = 1U << 5; + printf("\n static const code distfix[%u] = {", size); + low = 0; + for (;;) { + if ((low % 6) == 0) printf("\n "); + printf("{%u,%u,%d}", state.distcode[low].op, state.distcode[low].bits, + state.distcode[low].val); + if (++low == size) break; + putchar(','); + } + puts("\n };"); +} +#endif /* MAKEFIXED */ + +/* + Update the window with the last wsize (normally 32K) bytes written before + returning. If window does not exist yet, create it. This is only called + when a window is already in use, or when output has been written during this + inflate call, but the end of the deflate stream has not been reached yet. + It is also called to create a window for dictionary data when a dictionary + is loaded. + + Providing output buffers larger than 32K to inflate() should provide a speed + advantage, since only the last 32K of output is copied to the sliding window + upon return from inflate(), and since all distances after the first 32K of + output will fall in the output data, making match copies simpler and faster. + The advantage may be dependent on the size of the processor's data caches. + */ +local int updatewindow(strm, end, copy) +z_streamp strm; +const Bytef *end; +unsigned copy; +{ + struct inflate_state FAR *state; + unsigned dist; + + state = (struct inflate_state FAR *)strm->state; + + /* if it hasn't been done already, allocate space for the window */ + if (state->window == Z_NULL) { + state->window = (unsigned char FAR *) + ZALLOC(strm, 1U << state->wbits, + sizeof(unsigned char)); + if (state->window == Z_NULL) return 1; + } + + /* if window not in use yet, initialize */ + if (state->wsize == 0) { + state->wsize = 1U << state->wbits; + state->wnext = 0; + state->whave = 0; + } + + /* copy state->wsize or less output bytes into the circular window */ + if (copy >= state->wsize) { + zmemcpy(state->window, end - state->wsize, state->wsize); + state->wnext = 0; + state->whave = state->wsize; + } + else { + dist = state->wsize - state->wnext; + if (dist > copy) dist = copy; + zmemcpy(state->window + state->wnext, end - copy, dist); + copy -= dist; + if (copy) { + zmemcpy(state->window, end - copy, copy); + state->wnext = copy; + state->whave = state->wsize; + } + else { + state->wnext += dist; + if (state->wnext == state->wsize) state->wnext = 0; + if (state->whave < state->wsize) state->whave += dist; + } + } + return 0; +} + +/* Macros for inflate(): */ + +/* check function to use adler32() for zlib or crc32() for gzip */ +#ifdef GUNZIP +# define UPDATE(check, buf, len) \ + (state->flags ? crc32(check, buf, len) : adler32(check, buf, len)) +#else +# define UPDATE(check, buf, len) adler32(check, buf, len) +#endif + +/* check macros for header crc */ +#ifdef GUNZIP +# define CRC2(check, word) \ + do { \ + hbuf[0] = (unsigned char)(word); \ + hbuf[1] = (unsigned char)((word) >> 8); \ + check = crc32(check, hbuf, 2); \ + } while (0) + +# define CRC4(check, word) \ + do { \ + hbuf[0] = (unsigned char)(word); \ + hbuf[1] = (unsigned char)((word) >> 8); \ + hbuf[2] = (unsigned char)((word) >> 16); \ + hbuf[3] = (unsigned char)((word) >> 24); \ + check = crc32(check, hbuf, 4); \ + } while (0) +#endif + +/* Load registers with state in inflate() for speed */ +#define LOAD() \ + do { \ + put = strm->next_out; \ + left = strm->avail_out; \ + next = strm->next_in; \ + have = strm->avail_in; \ + hold = state->hold; \ + bits = state->bits; \ + } while (0) + +/* Restore state from registers in inflate() */ +#define RESTORE() \ + do { \ + strm->next_out = put; \ + strm->avail_out = left; \ + strm->next_in = next; \ + strm->avail_in = have; \ + state->hold = hold; \ + state->bits = bits; \ + } while (0) + +/* Clear the input bit accumulator */ +#define INITBITS() \ + do { \ + hold = 0; \ + bits = 0; \ + } while (0) + +/* Get a byte of input into the bit accumulator, or return from inflate() + if there is no input available. */ +#define PULLBYTE() \ + do { \ + if (have == 0) goto inf_leave; \ + have--; \ + hold += (unsigned long)(*next++) << bits; \ + bits += 8; \ + } while (0) + +/* Assure that there are at least n bits in the bit accumulator. If there is + not enough available input to do that, then return from inflate(). */ +#define NEEDBITS(n) \ + do { \ + while (bits < (unsigned)(n)) \ + PULLBYTE(); \ + } while (0) + +/* Return the low n bits of the bit accumulator (n < 16) */ +#define BITS(n) \ + ((unsigned)hold & ((1U << (n)) - 1)) + +/* Remove n bits from the bit accumulator */ +#define DROPBITS(n) \ + do { \ + hold >>= (n); \ + bits -= (unsigned)(n); \ + } while (0) + +/* Remove zero to seven bits as needed to go to a byte boundary */ +#define BYTEBITS() \ + do { \ + hold >>= bits & 7; \ + bits -= bits & 7; \ + } while (0) + +/* + inflate() uses a state machine to process as much input data and generate as + much output data as possible before returning. The state machine is + structured roughly as follows: + + for (;;) switch (state) { + ... + case STATEn: + if (not enough input data or output space to make progress) + return; + ... make progress ... + state = STATEm; + break; + ... + } + + so when inflate() is called again, the same case is attempted again, and + if the appropriate resources are provided, the machine proceeds to the + next state. The NEEDBITS() macro is usually the way the state evaluates + whether it can proceed or should return. NEEDBITS() does the return if + the requested bits are not available. The typical use of the BITS macros + is: + + NEEDBITS(n); + ... do something with BITS(n) ... + DROPBITS(n); + + where NEEDBITS(n) either returns from inflate() if there isn't enough + input left to load n bits into the accumulator, or it continues. BITS(n) + gives the low n bits in the accumulator. When done, DROPBITS(n) drops + the low n bits off the accumulator. INITBITS() clears the accumulator + and sets the number of available bits to zero. BYTEBITS() discards just + enough bits to put the accumulator on a byte boundary. After BYTEBITS() + and a NEEDBITS(8), then BITS(8) would return the next byte in the stream. + + NEEDBITS(n) uses PULLBYTE() to get an available byte of input, or to return + if there is no input available. The decoding of variable length codes uses + PULLBYTE() directly in order to pull just enough bytes to decode the next + code, and no more. + + Some states loop until they get enough input, making sure that enough + state information is maintained to continue the loop where it left off + if NEEDBITS() returns in the loop. For example, want, need, and keep + would all have to actually be part of the saved state in case NEEDBITS() + returns: + + case STATEw: + while (want < need) { + NEEDBITS(n); + keep[want++] = BITS(n); + DROPBITS(n); + } + state = STATEx; + case STATEx: + + As shown above, if the next state is also the next case, then the break + is omitted. + + A state may also return if there is not enough output space available to + complete that state. Those states are copying stored data, writing a + literal byte, and copying a matching string. + + When returning, a "goto inf_leave" is used to update the total counters, + update the check value, and determine whether any progress has been made + during that inflate() call in order to return the proper return code. + Progress is defined as a change in either strm->avail_in or strm->avail_out. + When there is a window, goto inf_leave will update the window with the last + output written. If a goto inf_leave occurs in the middle of decompression + and there is no window currently, goto inf_leave will create one and copy + output to the window for the next call of inflate(). + + In this implementation, the flush parameter of inflate() only affects the + return code (per zlib.h). inflate() always writes as much as possible to + strm->next_out, given the space available and the provided input--the effect + documented in zlib.h of Z_SYNC_FLUSH. Furthermore, inflate() always defers + the allocation of and copying into a sliding window until necessary, which + provides the effect documented in zlib.h for Z_FINISH when the entire input + stream available. So the only thing the flush parameter actually does is: + when flush is set to Z_FINISH, inflate() cannot return Z_OK. Instead it + will return Z_BUF_ERROR if it has not reached the end of the stream. + */ + +int ZEXPORT inflate(strm, flush) +z_streamp strm; +int flush; +{ + struct inflate_state FAR *state; + z_const unsigned char FAR *next; /* next input */ + unsigned char FAR *put; /* next output */ + unsigned have, left; /* available input and output */ + unsigned long hold; /* bit buffer */ + unsigned bits; /* bits in bit buffer */ + unsigned in, out; /* save starting available input and output */ + unsigned copy; /* number of stored or match bytes to copy */ + unsigned char FAR *from; /* where to copy match bytes from */ + code here; /* current decoding table entry */ + code last; /* parent table entry */ + unsigned len; /* length to copy for repeats, bits to drop */ + int ret; /* return code */ +#ifdef GUNZIP + unsigned char hbuf[4]; /* buffer for gzip header crc calculation */ +#endif + static const unsigned short order[19] = /* permutation of code lengths */ + {16, 17, 18, 0, 8, 7, 9, 6, 10, 5, 11, 4, 12, 3, 13, 2, 14, 1, 15}; + + if (inflateStateCheck(strm) || strm->next_out == Z_NULL || + (strm->next_in == Z_NULL && strm->avail_in != 0)) + return Z_STREAM_ERROR; + + state = (struct inflate_state FAR *)strm->state; + if (state->mode == TYPE) state->mode = TYPEDO; /* skip check */ + LOAD(); + in = have; + out = left; + ret = Z_OK; + for (;;) + switch (state->mode) { + case HEAD: + if (state->wrap == 0) { + state->mode = TYPEDO; + break; + } + NEEDBITS(16); +#ifdef GUNZIP + if ((state->wrap & 2) && hold == 0x8b1f) { /* gzip header */ + if (state->wbits == 0) + state->wbits = 15; + state->check = crc32(0L, Z_NULL, 0); + CRC2(state->check, hold); + INITBITS(); + state->mode = FLAGS; + break; + } + state->flags = 0; /* expect zlib header */ + if (state->head != Z_NULL) + state->head->done = -1; + if (!(state->wrap & 1) || /* check if zlib header allowed */ +#else + if ( +#endif + ((BITS(8) << 8) + (hold >> 8)) % 31) { + strm->msg = (char *)"incorrect header check"; + state->mode = BAD; + break; + } + if (BITS(4) != Z_DEFLATED) { + strm->msg = (char *)"unknown compression method"; + state->mode = BAD; + break; + } + DROPBITS(4); + len = BITS(4) + 8; + if (state->wbits == 0) + state->wbits = len; + if (len > 15 || len > state->wbits) { + strm->msg = (char *)"invalid window size"; + state->mode = BAD; + break; + } + state->dmax = 1U << len; + Tracev((stderr, "inflate: zlib header ok\n")); + strm->adler = state->check = adler32(0L, Z_NULL, 0); + state->mode = hold & 0x200 ? DICTID : TYPE; + INITBITS(); + break; +#ifdef GUNZIP + case FLAGS: + NEEDBITS(16); + state->flags = (int)(hold); + if ((state->flags & 0xff) != Z_DEFLATED) { + strm->msg = (char *)"unknown compression method"; + state->mode = BAD; + break; + } + if (state->flags & 0xe000) { + strm->msg = (char *)"unknown header flags set"; + state->mode = BAD; + break; + } + if (state->head != Z_NULL) + state->head->text = (int)((hold >> 8) & 1); + if ((state->flags & 0x0200) && (state->wrap & 4)) + CRC2(state->check, hold); + INITBITS(); + state->mode = TIME; + case TIME: + NEEDBITS(32); + if (state->head != Z_NULL) + state->head->time = hold; + if ((state->flags & 0x0200) && (state->wrap & 4)) + CRC4(state->check, hold); + INITBITS(); + state->mode = OS; + case OS: + NEEDBITS(16); + if (state->head != Z_NULL) { + state->head->xflags = (int)(hold & 0xff); + state->head->os = (int)(hold >> 8); + } + if ((state->flags & 0x0200) && (state->wrap & 4)) + CRC2(state->check, hold); + INITBITS(); + state->mode = EXLEN; + case EXLEN: + if (state->flags & 0x0400) { + NEEDBITS(16); + state->length = (unsigned)(hold); + if (state->head != Z_NULL) + state->head->extra_len = (unsigned)hold; + if ((state->flags & 0x0200) && (state->wrap & 4)) + CRC2(state->check, hold); + INITBITS(); + } + else if (state->head != Z_NULL) + state->head->extra = Z_NULL; + state->mode = EXTRA; + case EXTRA: + if (state->flags & 0x0400) { + copy = state->length; + if (copy > have) copy = have; + if (copy) { + if (state->head != Z_NULL && + state->head->extra != Z_NULL) { + len = state->head->extra_len - state->length; + zmemcpy(state->head->extra + len, next, + len + copy > state->head->extra_max ? + state->head->extra_max - len : copy); + } + if ((state->flags & 0x0200) && (state->wrap & 4)) + state->check = crc32(state->check, next, copy); + have -= copy; + next += copy; + state->length -= copy; + } + if (state->length) goto inf_leave; + } + state->length = 0; + state->mode = NAME; + case NAME: + if (state->flags & 0x0800) { + if (have == 0) goto inf_leave; + copy = 0; + do { + len = (unsigned)(next[copy++]); + if (state->head != Z_NULL && + state->head->name != Z_NULL && + state->length < state->head->name_max) + state->head->name[state->length++] = (Bytef)len; + } while (len && copy < have); + if ((state->flags & 0x0200) && (state->wrap & 4)) + state->check = crc32(state->check, next, copy); + have -= copy; + next += copy; + if (len) goto inf_leave; + } + else if (state->head != Z_NULL) + state->head->name = Z_NULL; + state->length = 0; + state->mode = COMMENT; + case COMMENT: + if (state->flags & 0x1000) { + if (have == 0) goto inf_leave; + copy = 0; + do { + len = (unsigned)(next[copy++]); + if (state->head != Z_NULL && + state->head->comment != Z_NULL && + state->length < state->head->comm_max) + state->head->comment[state->length++] = (Bytef)len; + } while (len && copy < have); + if ((state->flags & 0x0200) && (state->wrap & 4)) + state->check = crc32(state->check, next, copy); + have -= copy; + next += copy; + if (len) goto inf_leave; + } + else if (state->head != Z_NULL) + state->head->comment = Z_NULL; + state->mode = HCRC; + case HCRC: + if (state->flags & 0x0200) { + NEEDBITS(16); + if ((state->wrap & 4) && hold != (state->check & 0xffff)) { + strm->msg = (char *)"header crc mismatch"; + state->mode = BAD; + break; + } + INITBITS(); + } + if (state->head != Z_NULL) { + state->head->hcrc = (int)((state->flags >> 9) & 1); + state->head->done = 1; + } + strm->adler = state->check = crc32(0L, Z_NULL, 0); + state->mode = TYPE; + break; +#endif + case DICTID: + NEEDBITS(32); + strm->adler = state->check = ZSWAP32(hold); + INITBITS(); + state->mode = DICT; + case DICT: + if (state->havedict == 0) { + RESTORE(); + return Z_NEED_DICT; + } + strm->adler = state->check = adler32(0L, Z_NULL, 0); + state->mode = TYPE; + case TYPE: + if (flush == Z_BLOCK || flush == Z_TREES) goto inf_leave; + case TYPEDO: + if (state->last) { + BYTEBITS(); + state->mode = CHECK; + break; + } + NEEDBITS(3); + state->last = BITS(1); + DROPBITS(1); + switch (BITS(2)) { + case 0: /* stored block */ + Tracev((stderr, "inflate: stored block%s\n", + state->last ? " (last)" : "")); + state->mode = STORED; + break; + case 1: /* fixed block */ + fixedtables(state); + Tracev((stderr, "inflate: fixed codes block%s\n", + state->last ? " (last)" : "")); + state->mode = LEN_; /* decode codes */ + if (flush == Z_TREES) { + DROPBITS(2); + goto inf_leave; + } + break; + case 2: /* dynamic block */ + Tracev((stderr, "inflate: dynamic codes block%s\n", + state->last ? " (last)" : "")); + state->mode = TABLE; + break; + case 3: + strm->msg = (char *)"invalid block type"; + state->mode = BAD; + } + DROPBITS(2); + break; + case STORED: + BYTEBITS(); /* go to byte boundary */ + NEEDBITS(32); + if ((hold & 0xffff) != ((hold >> 16) ^ 0xffff)) { + strm->msg = (char *)"invalid stored block lengths"; + state->mode = BAD; + break; + } + state->length = (unsigned)hold & 0xffff; + Tracev((stderr, "inflate: stored length %u\n", + state->length)); + INITBITS(); + state->mode = COPY_; + if (flush == Z_TREES) goto inf_leave; + case COPY_: + state->mode = COPY; + case COPY: + copy = state->length; + if (copy) { + if (copy > have) copy = have; + if (copy > left) copy = left; + if (copy == 0) goto inf_leave; + zmemcpy(put, next, copy); + have -= copy; + next += copy; + left -= copy; + put += copy; + state->length -= copy; + break; + } + Tracev((stderr, "inflate: stored end\n")); + state->mode = TYPE; + break; + case TABLE: + NEEDBITS(14); + state->nlen = BITS(5) + 257; + DROPBITS(5); + state->ndist = BITS(5) + 1; + DROPBITS(5); + state->ncode = BITS(4) + 4; + DROPBITS(4); +#ifndef PKZIP_BUG_WORKAROUND + if (state->nlen > 286 || state->ndist > 30) { + strm->msg = (char *)"too many length or distance symbols"; + state->mode = BAD; + break; + } +#endif + Tracev((stderr, "inflate: table sizes ok\n")); + state->have = 0; + state->mode = LENLENS; + case LENLENS: + while (state->have < state->ncode) { + NEEDBITS(3); + state->lens[order[state->have++]] = (unsigned short)BITS(3); + DROPBITS(3); + } + while (state->have < 19) + state->lens[order[state->have++]] = 0; + state->next = state->codes; + state->lencode = (const code FAR *)(state->next); + state->lenbits = 7; + ret = inflate_table(CODES, state->lens, 19, &(state->next), + &(state->lenbits), state->work); + if (ret) { + strm->msg = (char *)"invalid code lengths set"; + state->mode = BAD; + break; + } + Tracev((stderr, "inflate: code lengths ok\n")); + state->have = 0; + state->mode = CODELENS; + case CODELENS: + while (state->have < state->nlen + state->ndist) { + for (;;) { + here = state->lencode[BITS(state->lenbits)]; + if ((unsigned)(here.bits) <= bits) break; + PULLBYTE(); + } + if (here.val < 16) { + DROPBITS(here.bits); + state->lens[state->have++] = here.val; + } + else { + if (here.val == 16) { + NEEDBITS(here.bits + 2); + DROPBITS(here.bits); + if (state->have == 0) { + strm->msg = (char *)"invalid bit length repeat"; + state->mode = BAD; + break; + } + len = state->lens[state->have - 1]; + copy = 3 + BITS(2); + DROPBITS(2); + } + else if (here.val == 17) { + NEEDBITS(here.bits + 3); + DROPBITS(here.bits); + len = 0; + copy = 3 + BITS(3); + DROPBITS(3); + } + else { + NEEDBITS(here.bits + 7); + DROPBITS(here.bits); + len = 0; + copy = 11 + BITS(7); + DROPBITS(7); + } + if (state->have + copy > state->nlen + state->ndist) { + strm->msg = (char *)"invalid bit length repeat"; + state->mode = BAD; + break; + } + while (copy--) + state->lens[state->have++] = (unsigned short)len; + } + } + + /* handle error breaks in while */ + if (state->mode == BAD) break; + + /* check for end-of-block code (better have one) */ + if (state->lens[256] == 0) { + strm->msg = (char *)"invalid code -- missing end-of-block"; + state->mode = BAD; + break; + } + + /* build code tables -- note: do not change the lenbits or distbits + values here (9 and 6) without reading the comments in inftrees.h + concerning the ENOUGH constants, which depend on those values */ + state->next = state->codes; + state->lencode = (const code FAR *)(state->next); + state->lenbits = 9; + ret = inflate_table(LENS, state->lens, state->nlen, &(state->next), + &(state->lenbits), state->work); + if (ret) { + strm->msg = (char *)"invalid literal/lengths set"; + state->mode = BAD; + break; + } + state->distcode = (const code FAR *)(state->next); + state->distbits = 6; + ret = inflate_table(DISTS, state->lens + state->nlen, state->ndist, + &(state->next), &(state->distbits), state->work); + if (ret) { + strm->msg = (char *)"invalid distances set"; + state->mode = BAD; + break; + } + Tracev((stderr, "inflate: codes ok\n")); + state->mode = LEN_; + if (flush == Z_TREES) goto inf_leave; + case LEN_: + state->mode = LEN; + case LEN: + if (have >= 6 && left >= 258) { + RESTORE(); + inflate_fast(strm, out); + LOAD(); + if (state->mode == TYPE) + state->back = -1; + break; + } + state->back = 0; + for (;;) { + here = state->lencode[BITS(state->lenbits)]; + if ((unsigned)(here.bits) <= bits) break; + PULLBYTE(); + } + if (here.op && (here.op & 0xf0) == 0) { + last = here; + for (;;) { + here = state->lencode[last.val + + (BITS(last.bits + last.op) >> last.bits)]; + if ((unsigned)(last.bits + here.bits) <= bits) break; + PULLBYTE(); + } + DROPBITS(last.bits); + state->back += last.bits; + } + DROPBITS(here.bits); + state->back += here.bits; + state->length = (unsigned)here.val; + if ((int)(here.op) == 0) { + Tracevv((stderr, here.val >= 0x20 && here.val < 0x7f ? + "inflate: literal '%c'\n" : + "inflate: literal 0x%02x\n", here.val)); + state->mode = LIT; + break; + } + if (here.op & 32) { + Tracevv((stderr, "inflate: end of block\n")); + state->back = -1; + state->mode = TYPE; + break; + } + if (here.op & 64) { + strm->msg = (char *)"invalid literal/length code"; + state->mode = BAD; + break; + } + state->extra = (unsigned)(here.op) & 15; + state->mode = LENEXT; + case LENEXT: + if (state->extra) { + NEEDBITS(state->extra); + state->length += BITS(state->extra); + DROPBITS(state->extra); + state->back += state->extra; + } + Tracevv((stderr, "inflate: length %u\n", state->length)); + state->was = state->length; + state->mode = DIST; + case DIST: + for (;;) { + here = state->distcode[BITS(state->distbits)]; + if ((unsigned)(here.bits) <= bits) break; + PULLBYTE(); + } + if ((here.op & 0xf0) == 0) { + last = here; + for (;;) { + here = state->distcode[last.val + + (BITS(last.bits + last.op) >> last.bits)]; + if ((unsigned)(last.bits + here.bits) <= bits) break; + PULLBYTE(); + } + DROPBITS(last.bits); + state->back += last.bits; + } + DROPBITS(here.bits); + state->back += here.bits; + if (here.op & 64) { + strm->msg = (char *)"invalid distance code"; + state->mode = BAD; + break; + } + state->offset = (unsigned)here.val; + state->extra = (unsigned)(here.op) & 15; + state->mode = DISTEXT; + case DISTEXT: + if (state->extra) { + NEEDBITS(state->extra); + state->offset += BITS(state->extra); + DROPBITS(state->extra); + state->back += state->extra; + } +#ifdef INFLATE_STRICT + if (state->offset > state->dmax) { + strm->msg = (char *)"invalid distance too far back"; + state->mode = BAD; + break; + } +#endif + Tracevv((stderr, "inflate: distance %u\n", state->offset)); + state->mode = MATCH; + case MATCH: + if (left == 0) goto inf_leave; + copy = out - left; + if (state->offset > copy) { /* copy from window */ + copy = state->offset - copy; + if (copy > state->whave) { + if (state->sane) { + strm->msg = (char *)"invalid distance too far back"; + state->mode = BAD; + break; + } +#ifdef INFLATE_ALLOW_INVALID_DISTANCE_TOOFAR_ARRR + Trace((stderr, "inflate.c too far\n")); + copy -= state->whave; + if (copy > state->length) copy = state->length; + if (copy > left) copy = left; + left -= copy; + state->length -= copy; + do { + *put++ = 0; + } while (--copy); + if (state->length == 0) state->mode = LEN; + break; +#endif + } + if (copy > state->wnext) { + copy -= state->wnext; + from = state->window + (state->wsize - copy); + } + else + from = state->window + (state->wnext - copy); + if (copy > state->length) copy = state->length; + } + else { /* copy from output */ + from = put - state->offset; + copy = state->length; + } + if (copy > left) copy = left; + left -= copy; + state->length -= copy; + do { + *put++ = *from++; + } while (--copy); + if (state->length == 0) state->mode = LEN; + break; + case LIT: + if (left == 0) goto inf_leave; + *put++ = (unsigned char)(state->length); + left--; + state->mode = LEN; + break; + case CHECK: + if (state->wrap) { + NEEDBITS(32); + out -= left; + strm->total_out += out; + state->total += out; + if ((state->wrap & 4) && out) + strm->adler = state->check = + UPDATE(state->check, put - out, out); + out = left; + if ((state->wrap & 4) && ( +#ifdef GUNZIP + state->flags ? hold : +#endif + ZSWAP32(hold)) != state->check) { + strm->msg = (char *)"incorrect data check"; + state->mode = BAD; + break; + } + INITBITS(); + Tracev((stderr, "inflate: check matches trailer\n")); + } +#ifdef GUNZIP + state->mode = LENGTH; + case LENGTH: + if (state->wrap && state->flags) { + NEEDBITS(32); + if (hold != (state->total & 0xffffffffUL)) { + strm->msg = (char *)"incorrect length check"; + state->mode = BAD; + break; + } + INITBITS(); + Tracev((stderr, "inflate: length matches trailer\n")); + } +#endif + state->mode = DONE; + case DONE: + ret = Z_STREAM_END; + goto inf_leave; + case BAD: + ret = Z_DATA_ERROR; + goto inf_leave; + case MEM: + return Z_MEM_ERROR; + case SYNC: + default: + return Z_STREAM_ERROR; + } + + /* + Return from inflate(), updating the total counts and the check value. + If there was no progress during the inflate() call, return a buffer + error. Call updatewindow() to create and/or update the window state. + Note: a memory error from inflate() is non-recoverable. + */ + inf_leave: + RESTORE(); + if (state->wsize || (out != strm->avail_out && state->mode < BAD && + (state->mode < CHECK || flush != Z_FINISH))) + if (updatewindow(strm, strm->next_out, out - strm->avail_out)) { + state->mode = MEM; + return Z_MEM_ERROR; + } + in -= strm->avail_in; + out -= strm->avail_out; + strm->total_in += in; + strm->total_out += out; + state->total += out; + if ((state->wrap & 4) && out) + strm->adler = state->check = + UPDATE(state->check, strm->next_out - out, out); + strm->data_type = (int)state->bits + (state->last ? 64 : 0) + + (state->mode == TYPE ? 128 : 0) + + (state->mode == LEN_ || state->mode == COPY_ ? 256 : 0); + if (((in == 0 && out == 0) || flush == Z_FINISH) && ret == Z_OK) + ret = Z_BUF_ERROR; + return ret; +} + +int ZEXPORT inflateEnd(strm) +z_streamp strm; +{ + struct inflate_state FAR *state; + if (inflateStateCheck(strm)) + return Z_STREAM_ERROR; + state = (struct inflate_state FAR *)strm->state; + if (state->window != Z_NULL) ZFREE(strm, state->window); + ZFREE(strm, strm->state); + strm->state = Z_NULL; + Tracev((stderr, "inflate: end\n")); + return Z_OK; +} + +int ZEXPORT inflateGetDictionary(strm, dictionary, dictLength) +z_streamp strm; +Bytef *dictionary; +uInt *dictLength; +{ + struct inflate_state FAR *state; + + /* check state */ + if (inflateStateCheck(strm)) return Z_STREAM_ERROR; + state = (struct inflate_state FAR *)strm->state; + + /* copy dictionary */ + if (state->whave && dictionary != Z_NULL) { + zmemcpy(dictionary, state->window + state->wnext, + state->whave - state->wnext); + zmemcpy(dictionary + state->whave - state->wnext, + state->window, state->wnext); + } + if (dictLength != Z_NULL) + *dictLength = state->whave; + return Z_OK; +} + +int ZEXPORT inflateSetDictionary(strm, dictionary, dictLength) +z_streamp strm; +const Bytef *dictionary; +uInt dictLength; +{ + struct inflate_state FAR *state; + unsigned long dictid; + int ret; + + /* check state */ + if (inflateStateCheck(strm)) return Z_STREAM_ERROR; + state = (struct inflate_state FAR *)strm->state; + if (state->wrap != 0 && state->mode != DICT) + return Z_STREAM_ERROR; + + /* check for correct dictionary identifier */ + if (state->mode == DICT) { + dictid = adler32(0L, Z_NULL, 0); + dictid = adler32(dictid, dictionary, dictLength); + if (dictid != state->check) + return Z_DATA_ERROR; + } + + /* copy dictionary to window using updatewindow(), which will amend the + existing dictionary if appropriate */ + ret = updatewindow(strm, dictionary + dictLength, dictLength); + if (ret) { + state->mode = MEM; + return Z_MEM_ERROR; + } + state->havedict = 1; + Tracev((stderr, "inflate: dictionary set\n")); + return Z_OK; +} + +int ZEXPORT inflateGetHeader(strm, head) +z_streamp strm; +gz_headerp head; +{ + struct inflate_state FAR *state; + + /* check state */ + if (inflateStateCheck(strm)) return Z_STREAM_ERROR; + state = (struct inflate_state FAR *)strm->state; + if ((state->wrap & 2) == 0) return Z_STREAM_ERROR; + + /* save header structure */ + state->head = head; + head->done = 0; + return Z_OK; +} + +/* + Search buf[0..len-1] for the pattern: 0, 0, 0xff, 0xff. Return when found + or when out of input. When called, *have is the number of pattern bytes + found in order so far, in 0..3. On return *have is updated to the new + state. If on return *have equals four, then the pattern was found and the + return value is how many bytes were read including the last byte of the + pattern. If *have is less than four, then the pattern has not been found + yet and the return value is len. In the latter case, syncsearch() can be + called again with more data and the *have state. *have is initialized to + zero for the first call. + */ +local unsigned syncsearch(have, buf, len) +unsigned FAR *have; +const unsigned char FAR *buf; +unsigned len; +{ + unsigned got; + unsigned next; + + got = *have; + next = 0; + while (next < len && got < 4) { + if ((int)(buf[next]) == (got < 2 ? 0 : 0xff)) + got++; + else if (buf[next]) + got = 0; + else + got = 4 - got; + next++; + } + *have = got; + return next; +} + +int ZEXPORT inflateSync(strm) +z_streamp strm; +{ + unsigned len; /* number of bytes to look at or looked at */ + unsigned long in, out; /* temporary to save total_in and total_out */ + unsigned char buf[4]; /* to restore bit buffer to byte string */ + struct inflate_state FAR *state; + + /* check parameters */ + if (inflateStateCheck(strm)) return Z_STREAM_ERROR; + state = (struct inflate_state FAR *)strm->state; + if (strm->avail_in == 0 && state->bits < 8) return Z_BUF_ERROR; + + /* if first time, start search in bit buffer */ + if (state->mode != SYNC) { + state->mode = SYNC; + state->hold <<= state->bits & 7; + state->bits -= state->bits & 7; + len = 0; + while (state->bits >= 8) { + buf[len++] = (unsigned char)(state->hold); + state->hold >>= 8; + state->bits -= 8; + } + state->have = 0; + syncsearch(&(state->have), buf, len); + } + + /* search available input */ + len = syncsearch(&(state->have), strm->next_in, strm->avail_in); + strm->avail_in -= len; + strm->next_in += len; + strm->total_in += len; + + /* return no joy or set up to restart inflate() on a new block */ + if (state->have != 4) return Z_DATA_ERROR; + in = strm->total_in; out = strm->total_out; + inflateReset(strm); + strm->total_in = in; strm->total_out = out; + state->mode = TYPE; + return Z_OK; +} + +/* + Returns true if inflate is currently at the end of a block generated by + Z_SYNC_FLUSH or Z_FULL_FLUSH. This function is used by one PPP + implementation to provide an additional safety check. PPP uses + Z_SYNC_FLUSH but removes the length bytes of the resulting empty stored + block. When decompressing, PPP checks that at the end of input packet, + inflate is waiting for these length bytes. + */ +int ZEXPORT inflateSyncPoint(strm) +z_streamp strm; +{ + struct inflate_state FAR *state; + + if (inflateStateCheck(strm)) return Z_STREAM_ERROR; + state = (struct inflate_state FAR *)strm->state; + return state->mode == STORED && state->bits == 0; +} + +int ZEXPORT inflateCopy(dest, source) +z_streamp dest; +z_streamp source; +{ + struct inflate_state FAR *state; + struct inflate_state FAR *copy; + unsigned char FAR *window; + unsigned wsize; + + /* check input */ + if (inflateStateCheck(source) || dest == Z_NULL) + return Z_STREAM_ERROR; + state = (struct inflate_state FAR *)source->state; + + /* allocate space */ + copy = (struct inflate_state FAR *) + ZALLOC(source, 1, sizeof(struct inflate_state)); + if (copy == Z_NULL) return Z_MEM_ERROR; + window = Z_NULL; + if (state->window != Z_NULL) { + window = (unsigned char FAR *) + ZALLOC(source, 1U << state->wbits, sizeof(unsigned char)); + if (window == Z_NULL) { + ZFREE(source, copy); + return Z_MEM_ERROR; + } + } + + /* copy state */ + zmemcpy((voidpf)dest, (voidpf)source, sizeof(z_stream)); + zmemcpy((voidpf)copy, (voidpf)state, sizeof(struct inflate_state)); + copy->strm = dest; + if (state->lencode >= state->codes && + state->lencode <= state->codes + ENOUGH - 1) { + copy->lencode = copy->codes + (state->lencode - state->codes); + copy->distcode = copy->codes + (state->distcode - state->codes); + } + copy->next = copy->codes + (state->next - state->codes); + if (window != Z_NULL) { + wsize = 1U << state->wbits; + zmemcpy(window, state->window, wsize); + } + copy->window = window; + dest->state = (struct internal_state FAR *)copy; + return Z_OK; +} + +int ZEXPORT inflateUndermine(strm, subvert) +z_streamp strm; +int subvert; +{ + struct inflate_state FAR *state; + + if (inflateStateCheck(strm)) return Z_STREAM_ERROR; + state = (struct inflate_state FAR *)strm->state; +#ifdef INFLATE_ALLOW_INVALID_DISTANCE_TOOFAR_ARRR + state->sane = !subvert; + return Z_OK; +#else + (void)subvert; + state->sane = 1; + return Z_DATA_ERROR; +#endif +} + +int ZEXPORT inflateValidate(strm, check) +z_streamp strm; +int check; +{ + struct inflate_state FAR *state; + + if (inflateStateCheck(strm)) return Z_STREAM_ERROR; + state = (struct inflate_state FAR *)strm->state; + if (check) + state->wrap |= 4; + else + state->wrap &= ~4; + return Z_OK; +} + +long ZEXPORT inflateMark(strm) +z_streamp strm; +{ + struct inflate_state FAR *state; + + if (inflateStateCheck(strm)) + return -(1L << 16); + state = (struct inflate_state FAR *)strm->state; + return (long)(((unsigned long)((long)state->back)) << 16) + + (state->mode == COPY ? state->length : + (state->mode == MATCH ? state->was - state->length : 0)); +} + +unsigned long ZEXPORT inflateCodesUsed(strm) +z_streamp strm; +{ + struct inflate_state FAR *state; + if (inflateStateCheck(strm)) return (unsigned long)-1; + state = (struct inflate_state FAR *)strm->state; + return (unsigned long)(state->next - state->codes); +} ADDED compat/zlib/inflate.h Index: compat/zlib/inflate.h ================================================================== --- /dev/null +++ compat/zlib/inflate.h @@ -0,0 +1,125 @@ +/* inflate.h -- internal inflate state definition + * Copyright (C) 1995-2016 Mark Adler + * For conditions of distribution and use, see copyright notice in zlib.h + */ + +/* WARNING: this file should *not* be used by applications. It is + part of the implementation of the compression library and is + subject to change. Applications should only use zlib.h. + */ + +/* define NO_GZIP when compiling if you want to disable gzip header and + trailer decoding by inflate(). NO_GZIP would be used to avoid linking in + the crc code when it is not needed. For shared libraries, gzip decoding + should be left enabled. */ +#ifndef NO_GZIP +# define GUNZIP +#endif + +/* Possible inflate modes between inflate() calls */ +typedef enum { + HEAD = 16180, /* i: waiting for magic header */ + FLAGS, /* i: waiting for method and flags (gzip) */ + TIME, /* i: waiting for modification time (gzip) */ + OS, /* i: waiting for extra flags and operating system (gzip) */ + EXLEN, /* i: waiting for extra length (gzip) */ + EXTRA, /* i: waiting for extra bytes (gzip) */ + NAME, /* i: waiting for end of file name (gzip) */ + COMMENT, /* i: waiting for end of comment (gzip) */ + HCRC, /* i: waiting for header crc (gzip) */ + DICTID, /* i: waiting for dictionary check value */ + DICT, /* waiting for inflateSetDictionary() call */ + TYPE, /* i: waiting for type bits, including last-flag bit */ + TYPEDO, /* i: same, but skip check to exit inflate on new block */ + STORED, /* i: waiting for stored size (length and complement) */ + COPY_, /* i/o: same as COPY below, but only first time in */ + COPY, /* i/o: waiting for input or output to copy stored block */ + TABLE, /* i: waiting for dynamic block table lengths */ + LENLENS, /* i: waiting for code length code lengths */ + CODELENS, /* i: waiting for length/lit and distance code lengths */ + LEN_, /* i: same as LEN below, but only first time in */ + LEN, /* i: waiting for length/lit/eob code */ + LENEXT, /* i: waiting for length extra bits */ + DIST, /* i: waiting for distance code */ + DISTEXT, /* i: waiting for distance extra bits */ + MATCH, /* o: waiting for output space to copy string */ + LIT, /* o: waiting for output space to write literal */ + CHECK, /* i: waiting for 32-bit check value */ + LENGTH, /* i: waiting for 32-bit length (gzip) */ + DONE, /* finished check, done -- remain here until reset */ + BAD, /* got a data error -- remain here until reset */ + MEM, /* got an inflate() memory error -- remain here until reset */ + SYNC /* looking for synchronization bytes to restart inflate() */ +} inflate_mode; + +/* + State transitions between above modes - + + (most modes can go to BAD or MEM on error -- not shown for clarity) + + Process header: + HEAD -> (gzip) or (zlib) or (raw) + (gzip) -> FLAGS -> TIME -> OS -> EXLEN -> EXTRA -> NAME -> COMMENT -> + HCRC -> TYPE + (zlib) -> DICTID or TYPE + DICTID -> DICT -> TYPE + (raw) -> TYPEDO + Read deflate blocks: + TYPE -> TYPEDO -> STORED or TABLE or LEN_ or CHECK + STORED -> COPY_ -> COPY -> TYPE + TABLE -> LENLENS -> CODELENS -> LEN_ + LEN_ -> LEN + Read deflate codes in fixed or dynamic block: + LEN -> LENEXT or LIT or TYPE + LENEXT -> DIST -> DISTEXT -> MATCH -> LEN + LIT -> LEN + Process trailer: + CHECK -> LENGTH -> DONE + */ + +/* State maintained between inflate() calls -- approximately 7K bytes, not + including the allocated sliding window, which is up to 32K bytes. */ +struct inflate_state { + z_streamp strm; /* pointer back to this zlib stream */ + inflate_mode mode; /* current inflate mode */ + int last; /* true if processing last block */ + int wrap; /* bit 0 true for zlib, bit 1 true for gzip, + bit 2 true to validate check value */ + int havedict; /* true if dictionary provided */ + int flags; /* gzip header method and flags (0 if zlib) */ + unsigned dmax; /* zlib header max distance (INFLATE_STRICT) */ + unsigned long check; /* protected copy of check value */ + unsigned long total; /* protected copy of output count */ + gz_headerp head; /* where to save gzip header information */ + /* sliding window */ + unsigned wbits; /* log base 2 of requested window size */ + unsigned wsize; /* window size or zero if not using window */ + unsigned whave; /* valid bytes in the window */ + unsigned wnext; /* window write index */ + unsigned char FAR *window; /* allocated sliding window, if needed */ + /* bit accumulator */ + unsigned long hold; /* input bit accumulator */ + unsigned bits; /* number of bits in "in" */ + /* for string and stored block copying */ + unsigned length; /* literal or length of data to copy */ + unsigned offset; /* distance back to copy string from */ + /* for table and code decoding */ + unsigned extra; /* extra bits needed */ + /* fixed and dynamic code tables */ + code const FAR *lencode; /* starting table for length/literal codes */ + code const FAR *distcode; /* starting table for distance codes */ + unsigned lenbits; /* index bits for lencode */ + unsigned distbits; /* index bits for distcode */ + /* dynamic table building */ + unsigned ncode; /* number of code length code lengths */ + unsigned nlen; /* number of length code lengths */ + unsigned ndist; /* number of distance code lengths */ + unsigned have; /* number of code lengths in lens[] */ + code FAR *next; /* next available space in codes[] */ + unsigned short lens[320]; /* temporary storage for code lengths */ + unsigned short work[288]; /* work area for code table building */ + code codes[ENOUGH]; /* space for code tables */ + int sane; /* if false, allow invalid distance too far */ + int back; /* bits back of last unprocessed length/lit */ + unsigned was; /* initial length of match */ +}; ADDED compat/zlib/inftrees.c Index: compat/zlib/inftrees.c ================================================================== --- /dev/null +++ compat/zlib/inftrees.c @@ -0,0 +1,304 @@ +/* inftrees.c -- generate Huffman trees for efficient decoding + * Copyright (C) 1995-2017 Mark Adler + * For conditions of distribution and use, see copyright notice in zlib.h + */ + +#include "zutil.h" +#include "inftrees.h" + +#define MAXBITS 15 + +const char inflate_copyright[] = + " inflate 1.2.11 Copyright 1995-2017 Mark Adler "; +/* + If you use the zlib library in a product, an acknowledgment is welcome + in the documentation of your product. If for some reason you cannot + include such an acknowledgment, I would appreciate that you keep this + copyright string in the executable of your product. + */ + +/* + Build a set of tables to decode the provided canonical Huffman code. + The code lengths are lens[0..codes-1]. The result starts at *table, + whose indices are 0..2^bits-1. work is a writable array of at least + lens shorts, which is used as a work area. type is the type of code + to be generated, CODES, LENS, or DISTS. On return, zero is success, + -1 is an invalid code, and +1 means that ENOUGH isn't enough. table + on return points to the next available entry's address. bits is the + requested root table index bits, and on return it is the actual root + table index bits. It will differ if the request is greater than the + longest code or if it is less than the shortest code. + */ +int ZLIB_INTERNAL inflate_table(type, lens, codes, table, bits, work) +codetype type; +unsigned short FAR *lens; +unsigned codes; +code FAR * FAR *table; +unsigned FAR *bits; +unsigned short FAR *work; +{ + unsigned len; /* a code's length in bits */ + unsigned sym; /* index of code symbols */ + unsigned min, max; /* minimum and maximum code lengths */ + unsigned root; /* number of index bits for root table */ + unsigned curr; /* number of index bits for current table */ + unsigned drop; /* code bits to drop for sub-table */ + int left; /* number of prefix codes available */ + unsigned used; /* code entries in table used */ + unsigned huff; /* Huffman code */ + unsigned incr; /* for incrementing code, index */ + unsigned fill; /* index for replicating entries */ + unsigned low; /* low bits for current root entry */ + unsigned mask; /* mask for low root bits */ + code here; /* table entry for duplication */ + code FAR *next; /* next available space in table */ + const unsigned short FAR *base; /* base value table to use */ + const unsigned short FAR *extra; /* extra bits table to use */ + unsigned match; /* use base and extra for symbol >= match */ + unsigned short count[MAXBITS+1]; /* number of codes of each length */ + unsigned short offs[MAXBITS+1]; /* offsets in table for each length */ + static const unsigned short lbase[31] = { /* Length codes 257..285 base */ + 3, 4, 5, 6, 7, 8, 9, 10, 11, 13, 15, 17, 19, 23, 27, 31, + 35, 43, 51, 59, 67, 83, 99, 115, 131, 163, 195, 227, 258, 0, 0}; + static const unsigned short lext[31] = { /* Length codes 257..285 extra */ + 16, 16, 16, 16, 16, 16, 16, 16, 17, 17, 17, 17, 18, 18, 18, 18, + 19, 19, 19, 19, 20, 20, 20, 20, 21, 21, 21, 21, 16, 77, 202}; + static const unsigned short dbase[32] = { /* Distance codes 0..29 base */ + 1, 2, 3, 4, 5, 7, 9, 13, 17, 25, 33, 49, 65, 97, 129, 193, + 257, 385, 513, 769, 1025, 1537, 2049, 3073, 4097, 6145, + 8193, 12289, 16385, 24577, 0, 0}; + static const unsigned short dext[32] = { /* Distance codes 0..29 extra */ + 16, 16, 16, 16, 17, 17, 18, 18, 19, 19, 20, 20, 21, 21, 22, 22, + 23, 23, 24, 24, 25, 25, 26, 26, 27, 27, + 28, 28, 29, 29, 64, 64}; + + /* + Process a set of code lengths to create a canonical Huffman code. The + code lengths are lens[0..codes-1]. Each length corresponds to the + symbols 0..codes-1. The Huffman code is generated by first sorting the + symbols by length from short to long, and retaining the symbol order + for codes with equal lengths. Then the code starts with all zero bits + for the first code of the shortest length, and the codes are integer + increments for the same length, and zeros are appended as the length + increases. For the deflate format, these bits are stored backwards + from their more natural integer increment ordering, and so when the + decoding tables are built in the large loop below, the integer codes + are incremented backwards. + + This routine assumes, but does not check, that all of the entries in + lens[] are in the range 0..MAXBITS. The caller must assure this. + 1..MAXBITS is interpreted as that code length. zero means that that + symbol does not occur in this code. + + The codes are sorted by computing a count of codes for each length, + creating from that a table of starting indices for each length in the + sorted table, and then entering the symbols in order in the sorted + table. The sorted table is work[], with that space being provided by + the caller. + + The length counts are used for other purposes as well, i.e. finding + the minimum and maximum length codes, determining if there are any + codes at all, checking for a valid set of lengths, and looking ahead + at length counts to determine sub-table sizes when building the + decoding tables. + */ + + /* accumulate lengths for codes (assumes lens[] all in 0..MAXBITS) */ + for (len = 0; len <= MAXBITS; len++) + count[len] = 0; + for (sym = 0; sym < codes; sym++) + count[lens[sym]]++; + + /* bound code lengths, force root to be within code lengths */ + root = *bits; + for (max = MAXBITS; max >= 1; max--) + if (count[max] != 0) break; + if (root > max) root = max; + if (max == 0) { /* no symbols to code at all */ + here.op = (unsigned char)64; /* invalid code marker */ + here.bits = (unsigned char)1; + here.val = (unsigned short)0; + *(*table)++ = here; /* make a table to force an error */ + *(*table)++ = here; + *bits = 1; + return 0; /* no symbols, but wait for decoding to report error */ + } + for (min = 1; min < max; min++) + if (count[min] != 0) break; + if (root < min) root = min; + + /* check for an over-subscribed or incomplete set of lengths */ + left = 1; + for (len = 1; len <= MAXBITS; len++) { + left <<= 1; + left -= count[len]; + if (left < 0) return -1; /* over-subscribed */ + } + if (left > 0 && (type == CODES || max != 1)) + return -1; /* incomplete set */ + + /* generate offsets into symbol table for each length for sorting */ + offs[1] = 0; + for (len = 1; len < MAXBITS; len++) + offs[len + 1] = offs[len] + count[len]; + + /* sort symbols by length, by symbol order within each length */ + for (sym = 0; sym < codes; sym++) + if (lens[sym] != 0) work[offs[lens[sym]]++] = (unsigned short)sym; + + /* + Create and fill in decoding tables. In this loop, the table being + filled is at next and has curr index bits. The code being used is huff + with length len. That code is converted to an index by dropping drop + bits off of the bottom. For codes where len is less than drop + curr, + those top drop + curr - len bits are incremented through all values to + fill the table with replicated entries. + + root is the number of index bits for the root table. When len exceeds + root, sub-tables are created pointed to by the root entry with an index + of the low root bits of huff. This is saved in low to check for when a + new sub-table should be started. drop is zero when the root table is + being filled, and drop is root when sub-tables are being filled. + + When a new sub-table is needed, it is necessary to look ahead in the + code lengths to determine what size sub-table is needed. The length + counts are used for this, and so count[] is decremented as codes are + entered in the tables. + + used keeps track of how many table entries have been allocated from the + provided *table space. It is checked for LENS and DIST tables against + the constants ENOUGH_LENS and ENOUGH_DISTS to guard against changes in + the initial root table size constants. See the comments in inftrees.h + for more information. + + sym increments through all symbols, and the loop terminates when + all codes of length max, i.e. all codes, have been processed. This + routine permits incomplete codes, so another loop after this one fills + in the rest of the decoding tables with invalid code markers. + */ + + /* set up for code type */ + switch (type) { + case CODES: + base = extra = work; /* dummy value--not used */ + match = 20; + break; + case LENS: + base = lbase; + extra = lext; + match = 257; + break; + default: /* DISTS */ + base = dbase; + extra = dext; + match = 0; + } + + /* initialize state for loop */ + huff = 0; /* starting code */ + sym = 0; /* starting code symbol */ + len = min; /* starting code length */ + next = *table; /* current table to fill in */ + curr = root; /* current table index bits */ + drop = 0; /* current bits to drop from code for index */ + low = (unsigned)(-1); /* trigger new sub-table when len > root */ + used = 1U << root; /* use root table entries */ + mask = used - 1; /* mask for comparing low */ + + /* check available table space */ + if ((type == LENS && used > ENOUGH_LENS) || + (type == DISTS && used > ENOUGH_DISTS)) + return 1; + + /* process all codes and make table entries */ + for (;;) { + /* create table entry */ + here.bits = (unsigned char)(len - drop); + if (work[sym] + 1U < match) { + here.op = (unsigned char)0; + here.val = work[sym]; + } + else if (work[sym] >= match) { + here.op = (unsigned char)(extra[work[sym] - match]); + here.val = base[work[sym] - match]; + } + else { + here.op = (unsigned char)(32 + 64); /* end of block */ + here.val = 0; + } + + /* replicate for those indices with low len bits equal to huff */ + incr = 1U << (len - drop); + fill = 1U << curr; + min = fill; /* save offset to next table */ + do { + fill -= incr; + next[(huff >> drop) + fill] = here; + } while (fill != 0); + + /* backwards increment the len-bit code huff */ + incr = 1U << (len - 1); + while (huff & incr) + incr >>= 1; + if (incr != 0) { + huff &= incr - 1; + huff += incr; + } + else + huff = 0; + + /* go to next symbol, update count, len */ + sym++; + if (--(count[len]) == 0) { + if (len == max) break; + len = lens[work[sym]]; + } + + /* create new sub-table if needed */ + if (len > root && (huff & mask) != low) { + /* if first time, transition to sub-tables */ + if (drop == 0) + drop = root; + + /* increment past last table */ + next += min; /* here min is 1 << curr */ + + /* determine length of next table */ + curr = len - drop; + left = (int)(1 << curr); + while (curr + drop < max) { + left -= count[curr + drop]; + if (left <= 0) break; + curr++; + left <<= 1; + } + + /* check for enough space */ + used += 1U << curr; + if ((type == LENS && used > ENOUGH_LENS) || + (type == DISTS && used > ENOUGH_DISTS)) + return 1; + + /* point entry in root table to sub-table */ + low = huff & mask; + (*table)[low].op = (unsigned char)curr; + (*table)[low].bits = (unsigned char)root; + (*table)[low].val = (unsigned short)(next - *table); + } + } + + /* fill in remaining table entry if code is incomplete (guaranteed to have + at most one remaining entry, since if the code is incomplete, the + maximum code length that was allowed to get this far is one bit) */ + if (huff != 0) { + here.op = (unsigned char)64; /* invalid code marker */ + here.bits = (unsigned char)(len - drop); + here.val = (unsigned short)0; + next[huff] = here; + } + + /* set return parameters */ + *table += used; + *bits = root; + return 0; +} ADDED compat/zlib/inftrees.h Index: compat/zlib/inftrees.h ================================================================== --- /dev/null +++ compat/zlib/inftrees.h @@ -0,0 +1,62 @@ +/* inftrees.h -- header to use inftrees.c + * Copyright (C) 1995-2005, 2010 Mark Adler + * For conditions of distribution and use, see copyright notice in zlib.h + */ + +/* WARNING: this file should *not* be used by applications. It is + part of the implementation of the compression library and is + subject to change. Applications should only use zlib.h. + */ + +/* Structure for decoding tables. Each entry provides either the + information needed to do the operation requested by the code that + indexed that table entry, or it provides a pointer to another + table that indexes more bits of the code. op indicates whether + the entry is a pointer to another table, a literal, a length or + distance, an end-of-block, or an invalid code. For a table + pointer, the low four bits of op is the number of index bits of + that table. For a length or distance, the low four bits of op + is the number of extra bits to get after the code. bits is + the number of bits in this code or part of the code to drop off + of the bit buffer. val is the actual byte to output in the case + of a literal, the base length or distance, or the offset from + the current table to the next table. Each entry is four bytes. */ +typedef struct { + unsigned char op; /* operation, extra bits, table bits */ + unsigned char bits; /* bits in this part of the code */ + unsigned short val; /* offset in table or code value */ +} code; + +/* op values as set by inflate_table(): + 00000000 - literal + 0000tttt - table link, tttt != 0 is the number of table index bits + 0001eeee - length or distance, eeee is the number of extra bits + 01100000 - end of block + 01000000 - invalid code + */ + +/* Maximum size of the dynamic table. The maximum number of code structures is + 1444, which is the sum of 852 for literal/length codes and 592 for distance + codes. These values were found by exhaustive searches using the program + examples/enough.c found in the zlib distribtution. The arguments to that + program are the number of symbols, the initial root table size, and the + maximum bit length of a code. "enough 286 9 15" for literal/length codes + returns returns 852, and "enough 30 6 15" for distance codes returns 592. + The initial root table size (9 or 6) is found in the fifth argument of the + inflate_table() calls in inflate.c and infback.c. If the root table size is + changed, then these maximum sizes would be need to be recalculated and + updated. */ +#define ENOUGH_LENS 852 +#define ENOUGH_DISTS 592 +#define ENOUGH (ENOUGH_LENS+ENOUGH_DISTS) + +/* Type of code to build for inflate_table() */ +typedef enum { + CODES, + LENS, + DISTS +} codetype; + +int ZLIB_INTERNAL inflate_table OF((codetype type, unsigned short FAR *lens, + unsigned codes, code FAR * FAR *table, + unsigned FAR *bits, unsigned short FAR *work)); ADDED compat/zlib/make_vms.com Index: compat/zlib/make_vms.com ================================================================== --- /dev/null +++ compat/zlib/make_vms.com @@ -0,0 +1,867 @@ +$! make libz under VMS written by +$! Martin P.J. Zinser +$! +$! In case of problems with the install you might contact me at +$! zinser@zinser.no-ip.info(preferred) or +$! martin.zinser@eurexchange.com (work) +$! +$! Make procedure history for Zlib +$! +$!------------------------------------------------------------------------------ +$! Version history +$! 0.01 20060120 First version to receive a number +$! 0.02 20061008 Adapt to new Makefile.in +$! 0.03 20091224 Add support for large file check +$! 0.04 20100110 Add new gzclose, gzlib, gzread, gzwrite +$! 0.05 20100221 Exchange zlibdefs.h by zconf.h.in +$! 0.06 20120111 Fix missing amiss_err, update zconf_h.in, fix new exmples +$! subdir path, update module search in makefile.in +$! 0.07 20120115 Triggered by work done by Alexey Chupahin completly redesigned +$! shared image creation +$! 0.08 20120219 Make it work on VAX again, pre-load missing symbols to shared +$! image +$! 0.09 20120305 SMS. P1 sets builder ("MMK", "MMS", " " (built-in)). +$! "" -> automatic, preference: MMK, MMS, built-in. +$! +$ on error then goto err_exit +$! +$ true = 1 +$ false = 0 +$ tmpnam = "temp_" + f$getjpi("","pid") +$ tt = tmpnam + ".txt" +$ tc = tmpnam + ".c" +$ th = tmpnam + ".h" +$ define/nolog tconfig 'th' +$ its_decc = false +$ its_vaxc = false +$ its_gnuc = false +$ s_case = False +$! +$! Setup variables holding "config" information +$! +$ Make = "''p1'" +$ name = "Zlib" +$ version = "?.?.?" +$ v_string = "ZLIB_VERSION" +$ v_file = "zlib.h" +$ ccopt = "/include = []" +$ lopts = "" +$ dnsrl = "" +$ aconf_in_file = "zconf.h.in#zconf.h_in#zconf_h.in" +$ conf_check_string = "" +$ linkonly = false +$ optfile = name + ".opt" +$ mapfile = name + ".map" +$ libdefs = "" +$ vax = f$getsyi("HW_MODEL").lt.1024 +$ axp = f$getsyi("HW_MODEL").ge.1024 .and. f$getsyi("HW_MODEL").lt.4096 +$ ia64 = f$getsyi("HW_MODEL").ge.4096 +$! +$! 2012-03-05 SMS. +$! Why is this needed? And if it is needed, why not simply ".not. vax"? +$! +$!!! if axp .or. ia64 then set proc/parse=extended +$! +$ whoami = f$parse(f$environment("Procedure"),,,,"NO_CONCEAL") +$ mydef = F$parse(whoami,,,"DEVICE") +$ mydir = f$parse(whoami,,,"DIRECTORY") - "][" +$ myproc = f$parse(whoami,,,"Name") + f$parse(whoami,,,"type") +$! +$! Check for MMK/MMS +$! +$ if (Make .eqs. "") +$ then +$ If F$Search ("Sys$System:MMS.EXE") .nes. "" Then Make = "MMS" +$ If F$Type (MMK) .eqs. "STRING" Then Make = "MMK" +$ else +$ Make = f$edit( Make, "trim") +$ endif +$! +$ gosub find_version +$! +$ open/write topt tmp.opt +$ open/write optf 'optfile' +$! +$ gosub check_opts +$! +$! Look for the compiler used +$! +$ gosub check_compiler +$ close topt +$ close optf +$! +$ if its_decc +$ then +$ ccopt = "/prefix=all" + ccopt +$ if f$trnlnm("SYS") .eqs. "" +$ then +$ if axp +$ then +$ define sys sys$library: +$ else +$ ccopt = "/decc" + ccopt +$ define sys decc$library_include: +$ endif +$ endif +$! +$! 2012-03-05 SMS. +$! Why /NAMES = AS_IS? Why not simply ".not. vax"? And why not on VAX? +$! +$ if axp .or. ia64 +$ then +$ ccopt = ccopt + "/name=as_is/opt=(inline=speed)" +$ s_case = true +$ endif +$ endif +$ if its_vaxc .or. its_gnuc +$ then +$ if f$trnlnm("SYS").eqs."" then define sys sys$library: +$ endif +$! +$! Build a fake configure input header +$! +$ open/write conf_hin config.hin +$ write conf_hin "#undef _LARGEFILE64_SOURCE" +$ close conf_hin +$! +$! +$ i = 0 +$FIND_ACONF: +$ fname = f$element(i,"#",aconf_in_file) +$ if fname .eqs. "#" then goto AMISS_ERR +$ if f$search(fname) .eqs. "" +$ then +$ i = i + 1 +$ goto find_aconf +$ endif +$ open/read/err=aconf_err aconf_in 'fname' +$ open/write aconf zconf.h +$ACONF_LOOP: +$ read/end_of_file=aconf_exit aconf_in line +$ work = f$edit(line, "compress,trim") +$ if f$extract(0,6,work) .nes. "#undef" +$ then +$ if f$extract(0,12,work) .nes. "#cmakedefine" +$ then +$ write aconf line +$ endif +$ else +$ cdef = f$element(1," ",work) +$ gosub check_config +$ endif +$ goto aconf_loop +$ACONF_EXIT: +$ write aconf "" +$ write aconf "/* VMS specifics added by make_vms.com: */" +$ write aconf "#define VMS 1" +$ write aconf "#include " +$ write aconf "#include " +$ write aconf "#ifdef _LARGEFILE" +$ write aconf "# define off64_t __off64_t" +$ write aconf "# define fopen64 fopen" +$ write aconf "# define fseeko64 fseeko" +$ write aconf "# define lseek64 lseek" +$ write aconf "# define ftello64 ftell" +$ write aconf "#endif" +$ write aconf "#if !defined( __VAX) && (__CRTL_VER >= 70312000)" +$ write aconf "# define HAVE_VSNPRINTF" +$ write aconf "#endif" +$ close aconf_in +$ close aconf +$ if f$search("''th'") .nes. "" then delete 'th';* +$! Build the thing plain or with mms +$! +$ write sys$output "Compiling Zlib sources ..." +$ if make.eqs."" +$ then +$ if (f$search( "example.obj;*") .nes. "") then delete example.obj;* +$ if (f$search( "minigzip.obj;*") .nes. "") then delete minigzip.obj;* +$ CALL MAKE adler32.OBJ "CC ''CCOPT' adler32" - + adler32.c zlib.h zconf.h +$ CALL MAKE compress.OBJ "CC ''CCOPT' compress" - + compress.c zlib.h zconf.h +$ CALL MAKE crc32.OBJ "CC ''CCOPT' crc32" - + crc32.c zlib.h zconf.h +$ CALL MAKE deflate.OBJ "CC ''CCOPT' deflate" - + deflate.c deflate.h zutil.h zlib.h zconf.h +$ CALL MAKE gzclose.OBJ "CC ''CCOPT' gzclose" - + gzclose.c zutil.h zlib.h zconf.h +$ CALL MAKE gzlib.OBJ "CC ''CCOPT' gzlib" - + gzlib.c zutil.h zlib.h zconf.h +$ CALL MAKE gzread.OBJ "CC ''CCOPT' gzread" - + gzread.c zutil.h zlib.h zconf.h +$ CALL MAKE gzwrite.OBJ "CC ''CCOPT' gzwrite" - + gzwrite.c zutil.h zlib.h zconf.h +$ CALL MAKE infback.OBJ "CC ''CCOPT' infback" - + infback.c zutil.h inftrees.h inflate.h inffast.h inffixed.h +$ CALL MAKE inffast.OBJ "CC ''CCOPT' inffast" - + inffast.c zutil.h zlib.h zconf.h inffast.h +$ CALL MAKE inflate.OBJ "CC ''CCOPT' inflate" - + inflate.c zutil.h zlib.h zconf.h infblock.h +$ CALL MAKE inftrees.OBJ "CC ''CCOPT' inftrees" - + inftrees.c zutil.h zlib.h zconf.h inftrees.h +$ CALL MAKE trees.OBJ "CC ''CCOPT' trees" - + trees.c deflate.h zutil.h zlib.h zconf.h +$ CALL MAKE uncompr.OBJ "CC ''CCOPT' uncompr" - + uncompr.c zlib.h zconf.h +$ CALL MAKE zutil.OBJ "CC ''CCOPT' zutil" - + zutil.c zutil.h zlib.h zconf.h +$ write sys$output "Building Zlib ..." +$ CALL MAKE libz.OLB "lib/crea libz.olb *.obj" *.OBJ +$ write sys$output "Building example..." +$ CALL MAKE example.OBJ "CC ''CCOPT' [.test]example" - + [.test]example.c zlib.h zconf.h +$ call make example.exe "LINK example,libz.olb/lib" example.obj libz.olb +$ write sys$output "Building minigzip..." +$ CALL MAKE minigzip.OBJ "CC ''CCOPT' [.test]minigzip" - + [.test]minigzip.c zlib.h zconf.h +$ call make minigzip.exe - + "LINK minigzip,libz.olb/lib" - + minigzip.obj libz.olb +$ else +$ gosub crea_mms +$ write sys$output "Make ''name' ''version' with ''Make' " +$ 'make' +$ endif +$! +$! Create shareable image +$! +$ gosub crea_olist +$ write sys$output "Creating libzshr.exe" +$ call map_2_shopt 'mapfile' 'optfile' +$ LINK_'lopts'/SHARE=libzshr.exe modules.opt/opt,'optfile'/opt +$ write sys$output "Zlib build completed" +$ delete/nolog tmp.opt;* +$ exit +$AMISS_ERR: +$ write sys$output "No source for config.hin found." +$ write sys$output "Tried any of ''aconf_in_file'" +$ goto err_exit +$CC_ERR: +$ write sys$output "C compiler required to build ''name'" +$ goto err_exit +$ERR_EXIT: +$ set message/facil/ident/sever/text +$ close/nolog optf +$ close/nolog topt +$ close/nolog aconf_in +$ close/nolog aconf +$ close/nolog out +$ close/nolog min +$ close/nolog mod +$ close/nolog h_in +$ write sys$output "Exiting..." +$ exit 2 +$! +$! +$MAKE: SUBROUTINE !SUBROUTINE TO CHECK DEPENDENCIES +$ V = 'F$Verify(0) +$! P1 = What we are trying to make +$! P2 = Command to make it +$! P3 - P8 What it depends on +$ +$ If F$Search(P1) .Eqs. "" Then Goto Makeit +$ Time = F$CvTime(F$File(P1,"RDT")) +$arg=3 +$Loop: +$ Argument = P'arg +$ If Argument .Eqs. "" Then Goto Exit +$ El=0 +$Loop2: +$ File = F$Element(El," ",Argument) +$ If File .Eqs. " " Then Goto Endl +$ AFile = "" +$Loop3: +$ OFile = AFile +$ AFile = F$Search(File) +$ If AFile .Eqs. "" .Or. AFile .Eqs. OFile Then Goto NextEl +$ If F$CvTime(F$File(AFile,"RDT")) .Ges. Time Then Goto Makeit +$ Goto Loop3 +$NextEL: +$ El = El + 1 +$ Goto Loop2 +$EndL: +$ arg=arg+1 +$ If arg .Le. 8 Then Goto Loop +$ Goto Exit +$ +$Makeit: +$ VV=F$VERIFY(0) +$ write sys$output P2 +$ 'P2 +$ VV='F$Verify(VV) +$Exit: +$ If V Then Set Verify +$ENDSUBROUTINE +$!------------------------------------------------------------------------------ +$! +$! Check command line options and set symbols accordingly +$! +$!------------------------------------------------------------------------------ +$! Version history +$! 0.01 20041206 First version to receive a number +$! 0.02 20060126 Add new "HELP" target +$ CHECK_OPTS: +$ i = 1 +$ OPT_LOOP: +$ if i .lt. 9 +$ then +$ cparm = f$edit(p'i',"upcase") +$! +$! Check if parameter actually contains something +$! +$ if f$edit(cparm,"trim") .nes. "" +$ then +$ if cparm .eqs. "DEBUG" +$ then +$ ccopt = ccopt + "/noopt/deb" +$ lopts = lopts + "/deb" +$ endif +$ if f$locate("CCOPT=",cparm) .lt. f$length(cparm) +$ then +$ start = f$locate("=",cparm) + 1 +$ len = f$length(cparm) - start +$ ccopt = ccopt + f$extract(start,len,cparm) +$ if f$locate("AS_IS",f$edit(ccopt,"UPCASE")) .lt. f$length(ccopt) - + then s_case = true +$ endif +$ if cparm .eqs. "LINK" then linkonly = true +$ if f$locate("LOPTS=",cparm) .lt. f$length(cparm) +$ then +$ start = f$locate("=",cparm) + 1 +$ len = f$length(cparm) - start +$ lopts = lopts + f$extract(start,len,cparm) +$ endif +$ if f$locate("CC=",cparm) .lt. f$length(cparm) +$ then +$ start = f$locate("=",cparm) + 1 +$ len = f$length(cparm) - start +$ cc_com = f$extract(start,len,cparm) + if (cc_com .nes. "DECC") .and. - + (cc_com .nes. "VAXC") .and. - + (cc_com .nes. "GNUC") +$ then +$ write sys$output "Unsupported compiler choice ''cc_com' ignored" +$ write sys$output "Use DECC, VAXC, or GNUC instead" +$ else +$ if cc_com .eqs. "DECC" then its_decc = true +$ if cc_com .eqs. "VAXC" then its_vaxc = true +$ if cc_com .eqs. "GNUC" then its_gnuc = true +$ endif +$ endif +$ if f$locate("MAKE=",cparm) .lt. f$length(cparm) +$ then +$ start = f$locate("=",cparm) + 1 +$ len = f$length(cparm) - start +$ mmks = f$extract(start,len,cparm) +$ if (mmks .eqs. "MMK") .or. (mmks .eqs. "MMS") +$ then +$ make = mmks +$ else +$ write sys$output "Unsupported make choice ''mmks' ignored" +$ write sys$output "Use MMK or MMS instead" +$ endif +$ endif +$ if cparm .eqs. "HELP" then gosub bhelp +$ endif +$ i = i + 1 +$ goto opt_loop +$ endif +$ return +$!------------------------------------------------------------------------------ +$! +$! Look for the compiler used +$! +$! Version history +$! 0.01 20040223 First version to receive a number +$! 0.02 20040229 Save/set value of decc$no_rooted_search_lists +$! 0.03 20060202 Extend handling of GNU C +$! 0.04 20090402 Compaq -> hp +$CHECK_COMPILER: +$ if (.not. (its_decc .or. its_vaxc .or. its_gnuc)) +$ then +$ its_decc = (f$search("SYS$SYSTEM:DECC$COMPILER.EXE") .nes. "") +$ its_vaxc = .not. its_decc .and. (F$Search("SYS$System:VAXC.Exe") .nes. "") +$ its_gnuc = .not. (its_decc .or. its_vaxc) .and. (f$trnlnm("gnu_cc") .nes. "") +$ endif +$! +$! Exit if no compiler available +$! +$ if (.not. (its_decc .or. its_vaxc .or. its_gnuc)) +$ then goto CC_ERR +$ else +$ if its_decc +$ then +$ write sys$output "CC compiler check ... hp C" +$ if f$trnlnm("decc$no_rooted_search_lists") .nes. "" +$ then +$ dnrsl = f$trnlnm("decc$no_rooted_search_lists") +$ endif +$ define/nolog decc$no_rooted_search_lists 1 +$ else +$ if its_vaxc then write sys$output "CC compiler check ... VAX C" +$ if its_gnuc +$ then +$ write sys$output "CC compiler check ... GNU C" +$ if f$trnlnm(topt) then write topt "gnu_cc:[000000]gcclib.olb/lib" +$ if f$trnlnm(optf) then write optf "gnu_cc:[000000]gcclib.olb/lib" +$ cc = "gcc" +$ endif +$ if f$trnlnm(topt) then write topt "sys$share:vaxcrtl.exe/share" +$ if f$trnlnm(optf) then write optf "sys$share:vaxcrtl.exe/share" +$ endif +$ endif +$ return +$!------------------------------------------------------------------------------ +$! +$! If MMS/MMK are available dump out the descrip.mms if required +$! +$CREA_MMS: +$ write sys$output "Creating descrip.mms..." +$ create descrip.mms +$ open/append out descrip.mms +$ copy sys$input: out +$ deck +# descrip.mms: MMS description file for building zlib on VMS +# written by Martin P.J. Zinser +# + +OBJS = adler32.obj, compress.obj, crc32.obj, gzclose.obj, gzlib.obj\ + gzread.obj, gzwrite.obj, uncompr.obj, infback.obj\ + deflate.obj, trees.obj, zutil.obj, inflate.obj, \ + inftrees.obj, inffast.obj + +$ eod +$ write out "CFLAGS=", ccopt +$ write out "LOPTS=", lopts +$ write out "all : example.exe minigzip.exe libz.olb" +$ copy sys$input: out +$ deck + @ write sys$output " Example applications available" + +libz.olb : libz.olb($(OBJS)) + @ write sys$output " libz available" + +example.exe : example.obj libz.olb + link $(LOPTS) example,libz.olb/lib + +minigzip.exe : minigzip.obj libz.olb + link $(LOPTS) minigzip,libz.olb/lib + +clean : + delete *.obj;*,libz.olb;*,*.opt;*,*.exe;* + + +# Other dependencies. +adler32.obj : adler32.c zutil.h zlib.h zconf.h +compress.obj : compress.c zlib.h zconf.h +crc32.obj : crc32.c zutil.h zlib.h zconf.h +deflate.obj : deflate.c deflate.h zutil.h zlib.h zconf.h +example.obj : [.test]example.c zlib.h zconf.h +gzclose.obj : gzclose.c zutil.h zlib.h zconf.h +gzlib.obj : gzlib.c zutil.h zlib.h zconf.h +gzread.obj : gzread.c zutil.h zlib.h zconf.h +gzwrite.obj : gzwrite.c zutil.h zlib.h zconf.h +inffast.obj : inffast.c zutil.h zlib.h zconf.h inftrees.h inffast.h +inflate.obj : inflate.c zutil.h zlib.h zconf.h +inftrees.obj : inftrees.c zutil.h zlib.h zconf.h inftrees.h +minigzip.obj : [.test]minigzip.c zlib.h zconf.h +trees.obj : trees.c deflate.h zutil.h zlib.h zconf.h +uncompr.obj : uncompr.c zlib.h zconf.h +zutil.obj : zutil.c zutil.h zlib.h zconf.h +infback.obj : infback.c zutil.h inftrees.h inflate.h inffast.h inffixed.h +$ eod +$ close out +$ return +$!------------------------------------------------------------------------------ +$! +$! Read list of core library sources from makefile.in and create options +$! needed to build shareable image +$! +$CREA_OLIST: +$ open/read min makefile.in +$ open/write mod modules.opt +$ src_check_list = "OBJZ =#OBJG =" +$MRLOOP: +$ read/end=mrdone min rec +$ i = 0 +$SRC_CHECK_LOOP: +$ src_check = f$element(i, "#", src_check_list) +$ i = i+1 +$ if src_check .eqs. "#" then goto mrloop +$ if (f$extract(0,6,rec) .nes. src_check) then goto src_check_loop +$ rec = rec - src_check +$ gosub extra_filnam +$ if (f$element(1,"\",rec) .eqs. "\") then goto mrloop +$MRSLOOP: +$ read/end=mrdone min rec +$ gosub extra_filnam +$ if (f$element(1,"\",rec) .nes. "\") then goto mrsloop +$MRDONE: +$ close min +$ close mod +$ return +$!------------------------------------------------------------------------------ +$! +$! Take record extracted in crea_olist and split it into single filenames +$! +$EXTRA_FILNAM: +$ myrec = f$edit(rec - "\", "trim,compress") +$ i = 0 +$FELOOP: +$ srcfil = f$element(i," ", myrec) +$ if (srcfil .nes. " ") +$ then +$ write mod f$parse(srcfil,,,"NAME"), ".obj" +$ i = i + 1 +$ goto feloop +$ endif +$ return +$!------------------------------------------------------------------------------ +$! +$! Find current Zlib version number +$! +$FIND_VERSION: +$ open/read h_in 'v_file' +$hloop: +$ read/end=hdone h_in rec +$ rec = f$edit(rec,"TRIM") +$ if (f$extract(0,1,rec) .nes. "#") then goto hloop +$ rec = f$edit(rec - "#", "TRIM") +$ if f$element(0," ",rec) .nes. "define" then goto hloop +$ if f$element(1," ",rec) .eqs. v_string +$ then +$ version = 'f$element(2," ",rec)' +$ goto hdone +$ endif +$ goto hloop +$hdone: +$ close h_in +$ return +$!------------------------------------------------------------------------------ +$! +$CHECK_CONFIG: +$! +$ in_ldef = f$locate(cdef,libdefs) +$ if (in_ldef .lt. f$length(libdefs)) +$ then +$ write aconf "#define ''cdef' 1" +$ libdefs = f$extract(0,in_ldef,libdefs) + - + f$extract(in_ldef + f$length(cdef) + 1, - + f$length(libdefs) - in_ldef - f$length(cdef) - 1, - + libdefs) +$ else +$ if (f$type('cdef') .eqs. "INTEGER") +$ then +$ write aconf "#define ''cdef' ", 'cdef' +$ else +$ if (f$type('cdef') .eqs. "STRING") +$ then +$ write aconf "#define ''cdef' ", """", '''cdef'', """" +$ else +$ gosub check_cc_def +$ endif +$ endif +$ endif +$ return +$!------------------------------------------------------------------------------ +$! +$! Check if this is a define relating to the properties of the C/C++ +$! compiler +$! +$ CHECK_CC_DEF: +$ if (cdef .eqs. "_LARGEFILE64_SOURCE") +$ then +$ copy sys$input: 'tc' +$ deck +#include "tconfig" +#define _LARGEFILE +#include + +int main(){ +FILE *fp; + fp = fopen("temp.txt","r"); + fseeko(fp,1,SEEK_SET); + fclose(fp); +} + +$ eod +$ test_inv = false +$ comm_h = false +$ gosub cc_prop_check +$ return +$ endif +$ write aconf "/* ", line, " */" +$ return +$!------------------------------------------------------------------------------ +$! +$! Check for properties of C/C++ compiler +$! +$! Version history +$! 0.01 20031020 First version to receive a number +$! 0.02 20031022 Added logic for defines with value +$! 0.03 20040309 Make sure local config file gets not deleted +$! 0.04 20041230 Also write include for configure run +$! 0.05 20050103 Add processing of "comment defines" +$CC_PROP_CHECK: +$ cc_prop = true +$ is_need = false +$ is_need = (f$extract(0,4,cdef) .eqs. "NEED") .or. (test_inv .eq. true) +$ if f$search(th) .eqs. "" then create 'th' +$ set message/nofac/noident/nosever/notext +$ on error then continue +$ cc 'tmpnam' +$ if .not. ($status) then cc_prop = false +$ on error then continue +$! The headers might lie about the capabilities of the RTL +$ link 'tmpnam',tmp.opt/opt +$ if .not. ($status) then cc_prop = false +$ set message/fac/ident/sever/text +$ on error then goto err_exit +$ delete/nolog 'tmpnam'.*;*/exclude='th' +$ if (cc_prop .and. .not. is_need) .or. - + (.not. cc_prop .and. is_need) +$ then +$ write sys$output "Checking for ''cdef'... yes" +$ if f$type('cdef_val'_yes) .nes. "" +$ then +$ if f$type('cdef_val'_yes) .eqs. "INTEGER" - + then call write_config f$fao("#define !AS !UL",cdef,'cdef_val'_yes) +$ if f$type('cdef_val'_yes) .eqs. "STRING" - + then call write_config f$fao("#define !AS !AS",cdef,'cdef_val'_yes) +$ else +$ call write_config f$fao("#define !AS 1",cdef) +$ endif +$ if (cdef .eqs. "HAVE_FSEEKO") .or. (cdef .eqs. "_LARGE_FILES") .or. - + (cdef .eqs. "_LARGEFILE64_SOURCE") then - + call write_config f$string("#define _LARGEFILE 1") +$ else +$ write sys$output "Checking for ''cdef'... no" +$ if (comm_h) +$ then + call write_config f$fao("/* !AS */",line) +$ else +$ if f$type('cdef_val'_no) .nes. "" +$ then +$ if f$type('cdef_val'_no) .eqs. "INTEGER" - + then call write_config f$fao("#define !AS !UL",cdef,'cdef_val'_no) +$ if f$type('cdef_val'_no) .eqs. "STRING" - + then call write_config f$fao("#define !AS !AS",cdef,'cdef_val'_no) +$ else +$ call write_config f$fao("#undef !AS",cdef) +$ endif +$ endif +$ endif +$ return +$!------------------------------------------------------------------------------ +$! +$! Check for properties of C/C++ compiler with multiple result values +$! +$! Version history +$! 0.01 20040127 First version +$! 0.02 20050103 Reconcile changes from cc_prop up to version 0.05 +$CC_MPROP_CHECK: +$ cc_prop = true +$ i = 1 +$ idel = 1 +$ MT_LOOP: +$ if f$type(result_'i') .eqs. "STRING" +$ then +$ set message/nofac/noident/nosever/notext +$ on error then continue +$ cc 'tmpnam'_'i' +$ if .not. ($status) then cc_prop = false +$ on error then continue +$! The headers might lie about the capabilities of the RTL +$ link 'tmpnam'_'i',tmp.opt/opt +$ if .not. ($status) then cc_prop = false +$ set message/fac/ident/sever/text +$ on error then goto err_exit +$ delete/nolog 'tmpnam'_'i'.*;* +$ if (cc_prop) +$ then +$ write sys$output "Checking for ''cdef'... ", mdef_'i' +$ if f$type(mdef_'i') .eqs. "INTEGER" - + then call write_config f$fao("#define !AS !UL",cdef,mdef_'i') +$ if f$type('cdef_val'_yes) .eqs. "STRING" - + then call write_config f$fao("#define !AS !AS",cdef,mdef_'i') +$ goto msym_clean +$ else +$ i = i + 1 +$ goto mt_loop +$ endif +$ endif +$ write sys$output "Checking for ''cdef'... no" +$ call write_config f$fao("#undef !AS",cdef) +$ MSYM_CLEAN: +$ if (idel .le. msym_max) +$ then +$ delete/sym mdef_'idel' +$ idel = idel + 1 +$ goto msym_clean +$ endif +$ return +$!------------------------------------------------------------------------------ +$! +$! Write configuration to both permanent and temporary config file +$! +$! Version history +$! 0.01 20031029 First version to receive a number +$! +$WRITE_CONFIG: SUBROUTINE +$ write aconf 'p1' +$ open/append confh 'th' +$ write confh 'p1' +$ close confh +$ENDSUBROUTINE +$!------------------------------------------------------------------------------ +$! +$! Analyze the project map file and create the symbol vector for a shareable +$! image from it +$! +$! Version history +$! 0.01 20120128 First version +$! 0.02 20120226 Add pre-load logic +$! +$ MAP_2_SHOPT: Subroutine +$! +$ SAY := "WRITE_ SYS$OUTPUT" +$! +$ IF F$SEARCH("''P1'") .EQS. "" +$ THEN +$ SAY "MAP_2_SHOPT-E-NOSUCHFILE: Error, inputfile ''p1' not available" +$ goto exit_m2s +$ ENDIF +$ IF "''P2'" .EQS. "" +$ THEN +$ SAY "MAP_2_SHOPT: Error, no output file provided" +$ goto exit_m2s +$ ENDIF +$! +$ module1 = "deflate#deflateEnd#deflateInit_#deflateParams#deflateSetDictionary" +$ module2 = "gzclose#gzerror#gzgetc#gzgets#gzopen#gzprintf#gzputc#gzputs#gzread" +$ module3 = "gzseek#gztell#inflate#inflateEnd#inflateInit_#inflateSetDictionary" +$ module4 = "inflateSync#uncompress#zlibVersion#compress" +$ open/read map 'p1 +$ if axp .or. ia64 +$ then +$ open/write aopt a.opt +$ open/write bopt b.opt +$ write aopt " CASE_SENSITIVE=YES" +$ write bopt "SYMBOL_VECTOR= (-" +$ mod_sym_num = 1 +$ MOD_SYM_LOOP: +$ if f$type(module'mod_sym_num') .nes. "" +$ then +$ mod_in = 0 +$ MOD_SYM_IN: +$ shared_proc = f$element(mod_in, "#", module'mod_sym_num') +$ if shared_proc .nes. "#" +$ then +$ write aopt f$fao(" symbol_vector=(!AS/!AS=PROCEDURE)",- + f$edit(shared_proc,"upcase"),shared_proc) +$ write bopt f$fao("!AS=PROCEDURE,-",shared_proc) +$ mod_in = mod_in + 1 +$ goto mod_sym_in +$ endif +$ mod_sym_num = mod_sym_num + 1 +$ goto mod_sym_loop +$ endif +$MAP_LOOP: +$ read/end=map_end map line +$ if (f$locate("{",line).lt. f$length(line)) .or. - + (f$locate("global:", line) .lt. f$length(line)) +$ then +$ proc = true +$ goto map_loop +$ endif +$ if f$locate("}",line).lt. f$length(line) then proc = false +$ if f$locate("local:", line) .lt. f$length(line) then proc = false +$ if proc +$ then +$ shared_proc = f$edit(line,"collapse") +$ chop_semi = f$locate(";", shared_proc) +$ if chop_semi .lt. f$length(shared_proc) then - + shared_proc = f$extract(0, chop_semi, shared_proc) +$ write aopt f$fao(" symbol_vector=(!AS/!AS=PROCEDURE)",- + f$edit(shared_proc,"upcase"),shared_proc) +$ write bopt f$fao("!AS=PROCEDURE,-",shared_proc) +$ endif +$ goto map_loop +$MAP_END: +$ close/nolog aopt +$ close/nolog bopt +$ open/append libopt 'p2' +$ open/read aopt a.opt +$ open/read bopt b.opt +$ALOOP: +$ read/end=aloop_end aopt line +$ write libopt line +$ goto aloop +$ALOOP_END: +$ close/nolog aopt +$ sv = "" +$BLOOP: +$ read/end=bloop_end bopt svn +$ if (svn.nes."") +$ then +$ if (sv.nes."") then write libopt sv +$ sv = svn +$ endif +$ goto bloop +$BLOOP_END: +$ write libopt f$extract(0,f$length(sv)-2,sv), "-" +$ write libopt ")" +$ close/nolog bopt +$ delete/nolog/noconf a.opt;*,b.opt;* +$ else +$ if vax +$ then +$ open/append libopt 'p2' +$ mod_sym_num = 1 +$ VMOD_SYM_LOOP: +$ if f$type(module'mod_sym_num') .nes. "" +$ then +$ mod_in = 0 +$ VMOD_SYM_IN: +$ shared_proc = f$element(mod_in, "#", module'mod_sym_num') +$ if shared_proc .nes. "#" +$ then +$ write libopt f$fao("UNIVERSAL=!AS",- + f$edit(shared_proc,"upcase")) +$ mod_in = mod_in + 1 +$ goto vmod_sym_in +$ endif +$ mod_sym_num = mod_sym_num + 1 +$ goto vmod_sym_loop +$ endif +$VMAP_LOOP: +$ read/end=vmap_end map line +$ if (f$locate("{",line).lt. f$length(line)) .or. - + (f$locate("global:", line) .lt. f$length(line)) +$ then +$ proc = true +$ goto vmap_loop +$ endif +$ if f$locate("}",line).lt. f$length(line) then proc = false +$ if f$locate("local:", line) .lt. f$length(line) then proc = false +$ if proc +$ then +$ shared_proc = f$edit(line,"collapse") +$ chop_semi = f$locate(";", shared_proc) +$ if chop_semi .lt. f$length(shared_proc) then - + shared_proc = f$extract(0, chop_semi, shared_proc) +$ write libopt f$fao("UNIVERSAL=!AS",- + f$edit(shared_proc,"upcase")) +$ endif +$ goto vmap_loop +$VMAP_END: +$ else +$ write sys$output "Unknown Architecture (Not VAX, AXP, or IA64)" +$ write sys$output "No options file created" +$ endif +$ endif +$ EXIT_M2S: +$ close/nolog map +$ close/nolog libopt +$ endsubroutine ADDED compat/zlib/msdos/Makefile.bor Index: compat/zlib/msdos/Makefile.bor ================================================================== --- /dev/null +++ compat/zlib/msdos/Makefile.bor @@ -0,0 +1,115 @@ +# Makefile for zlib +# Borland C++ +# Last updated: 15-Mar-2003 + +# To use, do "make -fmakefile.bor" +# To compile in small model, set below: MODEL=s + +# WARNING: the small model is supported but only for small values of +# MAX_WBITS and MAX_MEM_LEVEL. For example: +# -DMAX_WBITS=11 -DDEF_WBITS=11 -DMAX_MEM_LEVEL=3 +# If you wish to reduce the memory requirements (default 256K for big +# objects plus a few K), you can add to the LOC macro below: +# -DMAX_MEM_LEVEL=7 -DMAX_WBITS=14 +# See zconf.h for details about the memory requirements. + +# ------------ Turbo C++, Borland C++ ------------ + +# Optional nonstandard preprocessor flags (e.g. -DMAX_MEM_LEVEL=7) +# should be added to the environment via "set LOCAL_ZLIB=-DFOO" or added +# to the declaration of LOC here: +LOC = $(LOCAL_ZLIB) + +# type for CPU required: 0: 8086, 1: 80186, 2: 80286, 3: 80386, etc. +CPU_TYP = 0 + +# memory model: one of s, m, c, l (small, medium, compact, large) +MODEL=l + +# replace bcc with tcc for Turbo C++ 1.0, with bcc32 for the 32 bit version +CC=bcc +LD=bcc +AR=tlib + +# compiler flags +# replace "-O2" by "-O -G -a -d" for Turbo C++ 1.0 +CFLAGS=-O2 -Z -m$(MODEL) $(LOC) + +LDFLAGS=-m$(MODEL) -f- + + +# variables +ZLIB_LIB = zlib_$(MODEL).lib + +OBJ1 = adler32.obj compress.obj crc32.obj deflate.obj gzclose.obj gzlib.obj gzread.obj +OBJ2 = gzwrite.obj infback.obj inffast.obj inflate.obj inftrees.obj trees.obj uncompr.obj zutil.obj +OBJP1 = +adler32.obj+compress.obj+crc32.obj+deflate.obj+gzclose.obj+gzlib.obj+gzread.obj +OBJP2 = +gzwrite.obj+infback.obj+inffast.obj+inflate.obj+inftrees.obj+trees.obj+uncompr.obj+zutil.obj + + +# targets +all: $(ZLIB_LIB) example.exe minigzip.exe + +.c.obj: + $(CC) -c $(CFLAGS) $*.c + +adler32.obj: adler32.c zlib.h zconf.h + +compress.obj: compress.c zlib.h zconf.h + +crc32.obj: crc32.c zlib.h zconf.h crc32.h + +deflate.obj: deflate.c deflate.h zutil.h zlib.h zconf.h + +gzclose.obj: gzclose.c zlib.h zconf.h gzguts.h + +gzlib.obj: gzlib.c zlib.h zconf.h gzguts.h + +gzread.obj: gzread.c zlib.h zconf.h gzguts.h + +gzwrite.obj: gzwrite.c zlib.h zconf.h gzguts.h + +infback.obj: infback.c zutil.h zlib.h zconf.h inftrees.h inflate.h \ + inffast.h inffixed.h + +inffast.obj: inffast.c zutil.h zlib.h zconf.h inftrees.h inflate.h \ + inffast.h + +inflate.obj: inflate.c zutil.h zlib.h zconf.h inftrees.h inflate.h \ + inffast.h inffixed.h + +inftrees.obj: inftrees.c zutil.h zlib.h zconf.h inftrees.h + +trees.obj: trees.c zutil.h zlib.h zconf.h deflate.h trees.h + +uncompr.obj: uncompr.c zlib.h zconf.h + +zutil.obj: zutil.c zutil.h zlib.h zconf.h + +example.obj: test/example.c zlib.h zconf.h + +minigzip.obj: test/minigzip.c zlib.h zconf.h + + +# the command line is cut to fit in the MS-DOS 128 byte limit: +$(ZLIB_LIB): $(OBJ1) $(OBJ2) + -del $(ZLIB_LIB) + $(AR) $(ZLIB_LIB) $(OBJP1) + $(AR) $(ZLIB_LIB) $(OBJP2) + +example.exe: example.obj $(ZLIB_LIB) + $(LD) $(LDFLAGS) example.obj $(ZLIB_LIB) + +minigzip.exe: minigzip.obj $(ZLIB_LIB) + $(LD) $(LDFLAGS) minigzip.obj $(ZLIB_LIB) + +test: example.exe minigzip.exe + example + echo hello world | minigzip | minigzip -d + +clean: + -del *.obj + -del *.lib + -del *.exe + -del zlib_*.bak + -del foo.gz ADDED compat/zlib/msdos/Makefile.dj2 Index: compat/zlib/msdos/Makefile.dj2 ================================================================== --- /dev/null +++ compat/zlib/msdos/Makefile.dj2 @@ -0,0 +1,104 @@ +# Makefile for zlib. Modified for djgpp v2.0 by F. J. Donahoe, 3/15/96. +# Copyright (C) 1995-1998 Jean-loup Gailly. +# For conditions of distribution and use, see copyright notice in zlib.h + +# To compile, or to compile and test, type: +# +# make -fmakefile.dj2; make test -fmakefile.dj2 +# +# To install libz.a, zconf.h and zlib.h in the djgpp directories, type: +# +# make install -fmakefile.dj2 +# +# after first defining LIBRARY_PATH and INCLUDE_PATH in djgpp.env as +# in the sample below if the pattern of the DJGPP distribution is to +# be followed. Remember that, while 'es around <=> are ignored in +# makefiles, they are *not* in batch files or in djgpp.env. +# - - - - - +# [make] +# INCLUDE_PATH=%\>;INCLUDE_PATH%%\DJDIR%\include +# LIBRARY_PATH=%\>;LIBRARY_PATH%%\DJDIR%\lib +# BUTT=-m486 +# - - - - - +# Alternately, these variables may be defined below, overriding the values +# in djgpp.env, as +# INCLUDE_PATH=c:\usr\include +# LIBRARY_PATH=c:\usr\lib + +CC=gcc + +#CFLAGS=-MMD -O +#CFLAGS=-O -DMAX_WBITS=14 -DMAX_MEM_LEVEL=7 +#CFLAGS=-MMD -g -DZLIB_DEBUG +CFLAGS=-MMD -O3 $(BUTT) -Wall -Wwrite-strings -Wpointer-arith -Wconversion \ + -Wstrict-prototypes -Wmissing-prototypes + +# If cp.exe is available, replace "copy /Y" with "cp -fp" . +CP=copy /Y +# If gnu install.exe is available, replace $(CP) with ginstall. +INSTALL=$(CP) +# The default value of RM is "rm -f." If "rm.exe" is found, comment out: +RM=del +LDLIBS=-L. -lz +LD=$(CC) -s -o +LDSHARED=$(CC) + +INCL=zlib.h zconf.h +LIBS=libz.a + +AR=ar rcs + +prefix=/usr/local +exec_prefix = $(prefix) + +OBJS = adler32.o compress.o crc32.o gzclose.o gzlib.o gzread.o gzwrite.o \ + uncompr.o deflate.o trees.o zutil.o inflate.o infback.o inftrees.o inffast.o + +OBJA = +# to use the asm code: make OBJA=match.o + +TEST_OBJS = example.o minigzip.o + +all: example.exe minigzip.exe + +check: test +test: all + ./example + echo hello world | .\minigzip | .\minigzip -d + +%.o : %.c + $(CC) $(CFLAGS) -c $< -o $@ + +libz.a: $(OBJS) $(OBJA) + $(AR) $@ $(OBJS) $(OBJA) + +%.exe : %.o $(LIBS) + $(LD) $@ $< $(LDLIBS) + +# INCLUDE_PATH and LIBRARY_PATH were set for [make] in djgpp.env . + +.PHONY : uninstall clean + +install: $(INCL) $(LIBS) + -@if not exist $(INCLUDE_PATH)\nul mkdir $(INCLUDE_PATH) + -@if not exist $(LIBRARY_PATH)\nul mkdir $(LIBRARY_PATH) + $(INSTALL) zlib.h $(INCLUDE_PATH) + $(INSTALL) zconf.h $(INCLUDE_PATH) + $(INSTALL) libz.a $(LIBRARY_PATH) + +uninstall: + $(RM) $(INCLUDE_PATH)\zlib.h + $(RM) $(INCLUDE_PATH)\zconf.h + $(RM) $(LIBRARY_PATH)\libz.a + +clean: + $(RM) *.d + $(RM) *.o + $(RM) *.exe + $(RM) libz.a + $(RM) foo.gz + +DEPS := $(wildcard *.d) +ifneq ($(DEPS),) +include $(DEPS) +endif ADDED compat/zlib/msdos/Makefile.emx Index: compat/zlib/msdos/Makefile.emx ================================================================== --- /dev/null +++ compat/zlib/msdos/Makefile.emx @@ -0,0 +1,69 @@ +# Makefile for zlib. Modified for emx 0.9c by Chr. Spieler, 6/17/98. +# Copyright (C) 1995-1998 Jean-loup Gailly. +# For conditions of distribution and use, see copyright notice in zlib.h + +# To compile, or to compile and test, type: +# +# make -fmakefile.emx; make test -fmakefile.emx +# + +CC=gcc + +#CFLAGS=-MMD -O +#CFLAGS=-O -DMAX_WBITS=14 -DMAX_MEM_LEVEL=7 +#CFLAGS=-MMD -g -DZLIB_DEBUG +CFLAGS=-MMD -O3 $(BUTT) -Wall -Wwrite-strings -Wpointer-arith -Wconversion \ + -Wstrict-prototypes -Wmissing-prototypes + +# If cp.exe is available, replace "copy /Y" with "cp -fp" . +CP=copy /Y +# If gnu install.exe is available, replace $(CP) with ginstall. +INSTALL=$(CP) +# The default value of RM is "rm -f." If "rm.exe" is found, comment out: +RM=del +LDLIBS=-L. -lzlib +LD=$(CC) -s -o +LDSHARED=$(CC) + +INCL=zlib.h zconf.h +LIBS=zlib.a + +AR=ar rcs + +prefix=/usr/local +exec_prefix = $(prefix) + +OBJS = adler32.o compress.o crc32.o gzclose.o gzlib.o gzread.o gzwrite.o \ + uncompr.o deflate.o trees.o zutil.o inflate.o infback.o inftrees.o inffast.o + +TEST_OBJS = example.o minigzip.o + +all: example.exe minigzip.exe + +test: all + ./example + echo hello world | .\minigzip | .\minigzip -d + +%.o : %.c + $(CC) $(CFLAGS) -c $< -o $@ + +zlib.a: $(OBJS) + $(AR) $@ $(OBJS) + +%.exe : %.o $(LIBS) + $(LD) $@ $< $(LDLIBS) + + +.PHONY : clean + +clean: + $(RM) *.d + $(RM) *.o + $(RM) *.exe + $(RM) zlib.a + $(RM) foo.gz + +DEPS := $(wildcard *.d) +ifneq ($(DEPS),) +include $(DEPS) +endif ADDED compat/zlib/msdos/Makefile.msc Index: compat/zlib/msdos/Makefile.msc ================================================================== --- /dev/null +++ compat/zlib/msdos/Makefile.msc @@ -0,0 +1,112 @@ +# Makefile for zlib +# Microsoft C 5.1 or later +# Last updated: 19-Mar-2003 + +# To use, do "make makefile.msc" +# To compile in small model, set below: MODEL=S + +# If you wish to reduce the memory requirements (default 256K for big +# objects plus a few K), you can add to the LOC macro below: +# -DMAX_MEM_LEVEL=7 -DMAX_WBITS=14 +# See zconf.h for details about the memory requirements. + +# ------------- Microsoft C 5.1 and later ------------- + +# Optional nonstandard preprocessor flags (e.g. -DMAX_MEM_LEVEL=7) +# should be added to the environment via "set LOCAL_ZLIB=-DFOO" or added +# to the declaration of LOC here: +LOC = $(LOCAL_ZLIB) + +# Type for CPU required: 0: 8086, 1: 80186, 2: 80286, 3: 80386, etc. +CPU_TYP = 0 + +# Memory model: one of S, M, C, L (small, medium, compact, large) +MODEL=L + +CC=cl +CFLAGS=-nologo -A$(MODEL) -G$(CPU_TYP) -W3 -Oait -Gs $(LOC) +#-Ox generates bad code with MSC 5.1 +LIB_CFLAGS=-Zl $(CFLAGS) + +LD=link +LDFLAGS=/noi/e/st:0x1500/noe/farcall/packcode +# "/farcall/packcode" are only useful for `large code' memory models +# but should be a "no-op" for small code models. + + +# variables +ZLIB_LIB = zlib_$(MODEL).lib + +OBJ1 = adler32.obj compress.obj crc32.obj deflate.obj gzclose.obj gzlib.obj gzread.obj +OBJ2 = gzwrite.obj infback.obj inffast.obj inflate.obj inftrees.obj trees.obj uncompr.obj zutil.obj + + +# targets +all: $(ZLIB_LIB) example.exe minigzip.exe + +.c.obj: + $(CC) -c $(LIB_CFLAGS) $*.c + +adler32.obj: adler32.c zlib.h zconf.h + +compress.obj: compress.c zlib.h zconf.h + +crc32.obj: crc32.c zlib.h zconf.h crc32.h + +deflate.obj: deflate.c deflate.h zutil.h zlib.h zconf.h + +gzclose.obj: gzclose.c zlib.h zconf.h gzguts.h + +gzlib.obj: gzlib.c zlib.h zconf.h gzguts.h + +gzread.obj: gzread.c zlib.h zconf.h gzguts.h + +gzwrite.obj: gzwrite.c zlib.h zconf.h gzguts.h + +infback.obj: infback.c zutil.h zlib.h zconf.h inftrees.h inflate.h \ + inffast.h inffixed.h + +inffast.obj: inffast.c zutil.h zlib.h zconf.h inftrees.h inflate.h \ + inffast.h + +inflate.obj: inflate.c zutil.h zlib.h zconf.h inftrees.h inflate.h \ + inffast.h inffixed.h + +inftrees.obj: inftrees.c zutil.h zlib.h zconf.h inftrees.h + +trees.obj: trees.c zutil.h zlib.h zconf.h deflate.h trees.h + +uncompr.obj: uncompr.c zlib.h zconf.h + +zutil.obj: zutil.c zutil.h zlib.h zconf.h + +example.obj: test/example.c zlib.h zconf.h + $(CC) -c $(CFLAGS) $*.c + +minigzip.obj: test/minigzip.c zlib.h zconf.h + $(CC) -c $(CFLAGS) $*.c + + +# the command line is cut to fit in the MS-DOS 128 byte limit: +$(ZLIB_LIB): $(OBJ1) $(OBJ2) + if exist $(ZLIB_LIB) del $(ZLIB_LIB) + lib $(ZLIB_LIB) $(OBJ1); + lib $(ZLIB_LIB) $(OBJ2); + +example.exe: example.obj $(ZLIB_LIB) + $(LD) $(LDFLAGS) example.obj,,,$(ZLIB_LIB); + +minigzip.exe: minigzip.obj $(ZLIB_LIB) + $(LD) $(LDFLAGS) minigzip.obj,,,$(ZLIB_LIB); + +test: example.exe minigzip.exe + example + echo hello world | minigzip | minigzip -d + +clean: + -del *.obj + -del *.lib + -del *.exe + -del *.map + -del zlib_*.bak + -del foo.gz ADDED compat/zlib/msdos/Makefile.tc Index: compat/zlib/msdos/Makefile.tc ================================================================== --- /dev/null +++ compat/zlib/msdos/Makefile.tc @@ -0,0 +1,100 @@ +# Makefile for zlib +# Turbo C 2.01, Turbo C++ 1.01 +# Last updated: 15-Mar-2003 + +# To use, do "make -fmakefile.tc" +# To compile in small model, set below: MODEL=s + +# WARNING: the small model is supported but only for small values of +# MAX_WBITS and MAX_MEM_LEVEL. For example: +# -DMAX_WBITS=11 -DMAX_MEM_LEVEL=3 +# If you wish to reduce the memory requirements (default 256K for big +# objects plus a few K), you can add to CFLAGS below: +# -DMAX_MEM_LEVEL=7 -DMAX_WBITS=14 +# See zconf.h for details about the memory requirements. + +# ------------ Turbo C 2.01, Turbo C++ 1.01 ------------ +MODEL=l +CC=tcc +LD=tcc +AR=tlib +# CFLAGS=-O2 -G -Z -m$(MODEL) -DMAX_WBITS=11 -DMAX_MEM_LEVEL=3 +CFLAGS=-O2 -G -Z -m$(MODEL) +LDFLAGS=-m$(MODEL) -f- + + +# variables +ZLIB_LIB = zlib_$(MODEL).lib + +OBJ1 = adler32.obj compress.obj crc32.obj deflate.obj gzclose.obj gzlib.obj gzread.obj +OBJ2 = gzwrite.obj infback.obj inffast.obj inflate.obj inftrees.obj trees.obj uncompr.obj zutil.obj +OBJP1 = +adler32.obj+compress.obj+crc32.obj+deflate.obj+gzclose.obj+gzlib.obj+gzread.obj +OBJP2 = +gzwrite.obj+infback.obj+inffast.obj+inflate.obj+inftrees.obj+trees.obj+uncompr.obj+zutil.obj + + +# targets +all: $(ZLIB_LIB) example.exe minigzip.exe + +.c.obj: + $(CC) -c $(CFLAGS) $*.c + +adler32.obj: adler32.c zlib.h zconf.h + +compress.obj: compress.c zlib.h zconf.h + +crc32.obj: crc32.c zlib.h zconf.h crc32.h + +deflate.obj: deflate.c deflate.h zutil.h zlib.h zconf.h + +gzclose.obj: gzclose.c zlib.h zconf.h gzguts.h + +gzlib.obj: gzlib.c zlib.h zconf.h gzguts.h + +gzread.obj: gzread.c zlib.h zconf.h gzguts.h + +gzwrite.obj: gzwrite.c zlib.h zconf.h gzguts.h + +infback.obj: infback.c zutil.h zlib.h zconf.h inftrees.h inflate.h \ + inffast.h inffixed.h + +inffast.obj: inffast.c zutil.h zlib.h zconf.h inftrees.h inflate.h \ + inffast.h + +inflate.obj: inflate.c zutil.h zlib.h zconf.h inftrees.h inflate.h \ + inffast.h inffixed.h + +inftrees.obj: inftrees.c zutil.h zlib.h zconf.h inftrees.h + +trees.obj: trees.c zutil.h zlib.h zconf.h deflate.h trees.h + +uncompr.obj: uncompr.c zlib.h zconf.h + +zutil.obj: zutil.c zutil.h zlib.h zconf.h + +example.obj: test/example.c zlib.h zconf.h + +minigzip.obj: test/minigzip.c zlib.h zconf.h + + +# the command line is cut to fit in the MS-DOS 128 byte limit: +$(ZLIB_LIB): $(OBJ1) $(OBJ2) + -del $(ZLIB_LIB) + $(AR) $(ZLIB_LIB) $(OBJP1) + $(AR) $(ZLIB_LIB) $(OBJP2) + +example.exe: example.obj $(ZLIB_LIB) + $(LD) $(LDFLAGS) example.obj $(ZLIB_LIB) + +minigzip.exe: minigzip.obj $(ZLIB_LIB) + $(LD) $(LDFLAGS) minigzip.obj $(ZLIB_LIB) + +test: example.exe minigzip.exe + example + echo hello world | minigzip | minigzip -d + +clean: + -del *.obj + -del *.lib + -del *.exe + -del zlib_*.bak + -del foo.gz ADDED compat/zlib/nintendods/Makefile Index: compat/zlib/nintendods/Makefile ================================================================== --- /dev/null +++ compat/zlib/nintendods/Makefile @@ -0,0 +1,126 @@ +#--------------------------------------------------------------------------------- +.SUFFIXES: +#--------------------------------------------------------------------------------- + +ifeq ($(strip $(DEVKITARM)),) +$(error "Please set DEVKITARM in your environment. export DEVKITARM=devkitARM") +endif + +include $(DEVKITARM)/ds_rules + +#--------------------------------------------------------------------------------- +# TARGET is the name of the output +# BUILD is the directory where object files & intermediate files will be placed +# SOURCES is a list of directories containing source code +# DATA is a list of directories containing data files +# INCLUDES is a list of directories containing header files +#--------------------------------------------------------------------------------- +TARGET := $(shell basename $(CURDIR)) +BUILD := build +SOURCES := ../../ +DATA := data +INCLUDES := include + +#--------------------------------------------------------------------------------- +# options for code generation +#--------------------------------------------------------------------------------- +ARCH := -mthumb -mthumb-interwork + +CFLAGS := -Wall -O2\ + -march=armv5te -mtune=arm946e-s \ + -fomit-frame-pointer -ffast-math \ + $(ARCH) + +CFLAGS += $(INCLUDE) -DARM9 +CXXFLAGS := $(CFLAGS) -fno-rtti -fno-exceptions + +ASFLAGS := $(ARCH) -march=armv5te -mtune=arm946e-s +LDFLAGS = -specs=ds_arm9.specs -g $(ARCH) -Wl,-Map,$(notdir $*.map) + +#--------------------------------------------------------------------------------- +# list of directories containing libraries, this must be the top level containing +# include and lib +#--------------------------------------------------------------------------------- +LIBDIRS := $(LIBNDS) + +#--------------------------------------------------------------------------------- +# no real need to edit anything past this point unless you need to add additional +# rules for different file extensions +#--------------------------------------------------------------------------------- +ifneq ($(BUILD),$(notdir $(CURDIR))) +#--------------------------------------------------------------------------------- + +export OUTPUT := $(CURDIR)/lib/libz.a + +export VPATH := $(foreach dir,$(SOURCES),$(CURDIR)/$(dir)) \ + $(foreach dir,$(DATA),$(CURDIR)/$(dir)) + +export DEPSDIR := $(CURDIR)/$(BUILD) + +CFILES := $(foreach dir,$(SOURCES),$(notdir $(wildcard $(dir)/*.c))) +CPPFILES := $(foreach dir,$(SOURCES),$(notdir $(wildcard $(dir)/*.cpp))) +SFILES := $(foreach dir,$(SOURCES),$(notdir $(wildcard $(dir)/*.s))) +BINFILES := $(foreach dir,$(DATA),$(notdir $(wildcard $(dir)/*.*))) + +#--------------------------------------------------------------------------------- +# use CXX for linking C++ projects, CC for standard C +#--------------------------------------------------------------------------------- +ifeq ($(strip $(CPPFILES)),) +#--------------------------------------------------------------------------------- + export LD := $(CC) +#--------------------------------------------------------------------------------- +else +#--------------------------------------------------------------------------------- + export LD := $(CXX) +#--------------------------------------------------------------------------------- +endif +#--------------------------------------------------------------------------------- + +export OFILES := $(addsuffix .o,$(BINFILES)) \ + $(CPPFILES:.cpp=.o) $(CFILES:.c=.o) $(SFILES:.s=.o) + +export INCLUDE := $(foreach dir,$(INCLUDES),-I$(CURDIR)/$(dir)) \ + $(foreach dir,$(LIBDIRS),-I$(dir)/include) \ + -I$(CURDIR)/$(BUILD) + +.PHONY: $(BUILD) clean all + +#--------------------------------------------------------------------------------- +all: $(BUILD) + @[ -d $@ ] || mkdir -p include + @cp ../../*.h include + +lib: + @[ -d $@ ] || mkdir -p $@ + +$(BUILD): lib + @[ -d $@ ] || mkdir -p $@ + @$(MAKE) --no-print-directory -C $(BUILD) -f $(CURDIR)/Makefile + +#--------------------------------------------------------------------------------- +clean: + @echo clean ... + @rm -fr $(BUILD) lib + +#--------------------------------------------------------------------------------- +else + +DEPENDS := $(OFILES:.o=.d) + +#--------------------------------------------------------------------------------- +# main targets +#--------------------------------------------------------------------------------- +$(OUTPUT) : $(OFILES) + +#--------------------------------------------------------------------------------- +%.bin.o : %.bin +#--------------------------------------------------------------------------------- + @echo $(notdir $<) + @$(bin2o) + + +-include $(DEPENDS) + +#--------------------------------------------------------------------------------------- +endif +#--------------------------------------------------------------------------------------- ADDED compat/zlib/nintendods/README Index: compat/zlib/nintendods/README ================================================================== --- /dev/null +++ compat/zlib/nintendods/README @@ -0,0 +1,5 @@ +This Makefile requires devkitARM (http://www.devkitpro.org/category/devkitarm/) and works inside "contrib/nds". It is based on a devkitARM template. + +Eduardo Costa +January 3, 2009 + ADDED compat/zlib/old/Makefile.emx Index: compat/zlib/old/Makefile.emx ================================================================== --- /dev/null +++ compat/zlib/old/Makefile.emx @@ -0,0 +1,69 @@ +# Makefile for zlib. Modified for emx/rsxnt by Chr. Spieler, 6/16/98. +# Copyright (C) 1995-1998 Jean-loup Gailly. +# For conditions of distribution and use, see copyright notice in zlib.h + +# To compile, or to compile and test, type: +# +# make -fmakefile.emx; make test -fmakefile.emx +# + +CC=gcc -Zwin32 + +#CFLAGS=-MMD -O +#CFLAGS=-O -DMAX_WBITS=14 -DMAX_MEM_LEVEL=7 +#CFLAGS=-MMD -g -DZLIB_DEBUG +CFLAGS=-MMD -O3 $(BUTT) -Wall -Wwrite-strings -Wpointer-arith -Wconversion \ + -Wstrict-prototypes -Wmissing-prototypes + +# If cp.exe is available, replace "copy /Y" with "cp -fp" . +CP=copy /Y +# If gnu install.exe is available, replace $(CP) with ginstall. +INSTALL=$(CP) +# The default value of RM is "rm -f." If "rm.exe" is found, comment out: +RM=del +LDLIBS=-L. -lzlib +LD=$(CC) -s -o +LDSHARED=$(CC) + +INCL=zlib.h zconf.h +LIBS=zlib.a + +AR=ar rcs + +prefix=/usr/local +exec_prefix = $(prefix) + +OBJS = adler32.o compress.o crc32.o deflate.o gzclose.o gzlib.o gzread.o \ + gzwrite.o infback.o inffast.o inflate.o inftrees.o trees.o uncompr.o zutil.o + +TEST_OBJS = example.o minigzip.o + +all: example.exe minigzip.exe + +test: all + ./example + echo hello world | .\minigzip | .\minigzip -d + +%.o : %.c + $(CC) $(CFLAGS) -c $< -o $@ + +zlib.a: $(OBJS) + $(AR) $@ $(OBJS) + +%.exe : %.o $(LIBS) + $(LD) $@ $< $(LDLIBS) + + +.PHONY : clean + +clean: + $(RM) *.d + $(RM) *.o + $(RM) *.exe + $(RM) zlib.a + $(RM) foo.gz + +DEPS := $(wildcard *.d) +ifneq ($(DEPS),) +include $(DEPS) +endif ADDED compat/zlib/old/Makefile.riscos Index: compat/zlib/old/Makefile.riscos ================================================================== --- /dev/null +++ compat/zlib/old/Makefile.riscos @@ -0,0 +1,151 @@ +# Project: zlib_1_03 +# Patched for zlib 1.1.2 rw@shadow.org.uk 19980430 +# test works out-of-the-box, installs `somewhere' on demand + +# Toolflags: +CCflags = -c -depend !Depend -IC: -g -throwback -DRISCOS -fah +C++flags = -c -depend !Depend -IC: -throwback +Linkflags = -aif -c++ -o $@ +ObjAsmflags = -throwback -NoCache -depend !Depend +CMHGflags = +LibFileflags = -c -l -o $@ +Squeezeflags = -o $@ + +# change the line below to where _you_ want the library installed. +libdest = lib:zlib + +# Final targets: +@.lib: @.o.adler32 @.o.compress @.o.crc32 @.o.deflate @.o.gzio \ + @.o.infblock @.o.infcodes @.o.inffast @.o.inflate @.o.inftrees @.o.infutil @.o.trees \ + @.o.uncompr @.o.zutil + LibFile $(LibFileflags) @.o.adler32 @.o.compress @.o.crc32 @.o.deflate \ + @.o.gzio @.o.infblock @.o.infcodes @.o.inffast @.o.inflate @.o.inftrees @.o.infutil \ + @.o.trees @.o.uncompr @.o.zutil +test: @.minigzip @.example @.lib + @copy @.lib @.libc A~C~DF~L~N~P~Q~RS~TV + @echo running tests: hang on. + @/@.minigzip -f -9 libc + @/@.minigzip -d libc-gz + @/@.minigzip -f -1 libc + @/@.minigzip -d libc-gz + @/@.minigzip -h -9 libc + @/@.minigzip -d libc-gz + @/@.minigzip -h -1 libc + @/@.minigzip -d libc-gz + @/@.minigzip -9 libc + @/@.minigzip -d libc-gz + @/@.minigzip -1 libc + @/@.minigzip -d libc-gz + @diff @.lib @.libc + @echo that should have reported '@.lib and @.libc identical' if you have diff. + @/@.example @.fred @.fred + @echo that will have given lots of hello!'s. + +@.minigzip: @.o.minigzip @.lib C:o.Stubs + Link $(Linkflags) @.o.minigzip @.lib C:o.Stubs +@.example: @.o.example @.lib C:o.Stubs + Link $(Linkflags) @.o.example @.lib C:o.Stubs + +install: @.lib + cdir $(libdest) + cdir $(libdest).h + @copy @.h.zlib $(libdest).h.zlib A~C~DF~L~N~P~Q~RS~TV + @copy @.h.zconf $(libdest).h.zconf A~C~DF~L~N~P~Q~RS~TV + @copy @.lib $(libdest).lib A~C~DF~L~N~P~Q~RS~TV + @echo okay, installed zlib in $(libdest) + +clean:; remove @.minigzip + remove @.example + remove @.libc + -wipe @.o.* F~r~cV + remove @.fred + +# User-editable dependencies: +.c.o: + cc $(ccflags) -o $@ $< + +# Static dependencies: + +# Dynamic dependencies: +o.example: c.example +o.example: h.zlib +o.example: h.zconf +o.minigzip: c.minigzip +o.minigzip: h.zlib +o.minigzip: h.zconf +o.adler32: c.adler32 +o.adler32: h.zlib +o.adler32: h.zconf +o.compress: c.compress +o.compress: h.zlib +o.compress: h.zconf +o.crc32: c.crc32 +o.crc32: h.zlib +o.crc32: h.zconf +o.deflate: c.deflate +o.deflate: h.deflate +o.deflate: h.zutil +o.deflate: h.zlib +o.deflate: h.zconf +o.gzio: c.gzio +o.gzio: h.zutil +o.gzio: h.zlib +o.gzio: h.zconf +o.infblock: c.infblock +o.infblock: h.zutil +o.infblock: h.zlib +o.infblock: h.zconf +o.infblock: h.infblock +o.infblock: h.inftrees +o.infblock: h.infcodes +o.infblock: h.infutil +o.infcodes: c.infcodes +o.infcodes: h.zutil +o.infcodes: h.zlib +o.infcodes: h.zconf +o.infcodes: h.inftrees +o.infcodes: h.infblock +o.infcodes: h.infcodes +o.infcodes: h.infutil +o.infcodes: h.inffast +o.inffast: c.inffast +o.inffast: h.zutil +o.inffast: h.zlib +o.inffast: h.zconf +o.inffast: h.inftrees +o.inffast: h.infblock +o.inffast: h.infcodes +o.inffast: h.infutil +o.inffast: h.inffast +o.inflate: c.inflate +o.inflate: h.zutil +o.inflate: h.zlib +o.inflate: h.zconf +o.inflate: h.infblock +o.inftrees: c.inftrees +o.inftrees: h.zutil +o.inftrees: h.zlib +o.inftrees: h.zconf +o.inftrees: h.inftrees +o.inftrees: h.inffixed +o.infutil: c.infutil +o.infutil: h.zutil +o.infutil: h.zlib +o.infutil: h.zconf +o.infutil: h.infblock +o.infutil: h.inftrees +o.infutil: h.infcodes +o.infutil: h.infutil +o.trees: c.trees +o.trees: h.deflate +o.trees: h.zutil +o.trees: h.zlib +o.trees: h.zconf +o.trees: h.trees +o.uncompr: c.uncompr +o.uncompr: h.zlib +o.uncompr: h.zconf +o.zutil: c.zutil +o.zutil: h.zutil +o.zutil: h.zlib +o.zutil: h.zconf ADDED compat/zlib/old/README Index: compat/zlib/old/README ================================================================== --- /dev/null +++ compat/zlib/old/README @@ -0,0 +1,3 @@ +This directory contains files that have not been updated for zlib 1.2.x + +(Volunteers are encouraged to help clean this up. Thanks.) ADDED compat/zlib/old/descrip.mms Index: compat/zlib/old/descrip.mms ================================================================== --- /dev/null +++ compat/zlib/old/descrip.mms @@ -0,0 +1,48 @@ +# descrip.mms: MMS description file for building zlib on VMS +# written by Martin P.J. Zinser + +cc_defs = +c_deb = + +.ifdef __DECC__ +pref = /prefix=all +.endif + +OBJS = adler32.obj, compress.obj, crc32.obj, gzio.obj, uncompr.obj,\ + deflate.obj, trees.obj, zutil.obj, inflate.obj, infblock.obj,\ + inftrees.obj, infcodes.obj, infutil.obj, inffast.obj + +CFLAGS= $(C_DEB) $(CC_DEFS) $(PREF) + +all : example.exe minigzip.exe + @ write sys$output " Example applications available" +libz.olb : libz.olb($(OBJS)) + @ write sys$output " libz available" + +example.exe : example.obj libz.olb + link example,libz.olb/lib + +minigzip.exe : minigzip.obj libz.olb + link minigzip,libz.olb/lib,x11vms:xvmsutils.olb/lib + +clean : + delete *.obj;*,libz.olb;* + + +# Other dependencies. +adler32.obj : zutil.h zlib.h zconf.h +compress.obj : zlib.h zconf.h +crc32.obj : zutil.h zlib.h zconf.h +deflate.obj : deflate.h zutil.h zlib.h zconf.h +example.obj : zlib.h zconf.h +gzio.obj : zutil.h zlib.h zconf.h +infblock.obj : zutil.h zlib.h zconf.h infblock.h inftrees.h infcodes.h infutil.h +infcodes.obj : zutil.h zlib.h zconf.h inftrees.h infutil.h infcodes.h inffast.h +inffast.obj : zutil.h zlib.h zconf.h inftrees.h infutil.h inffast.h +inflate.obj : zutil.h zlib.h zconf.h infblock.h +inftrees.obj : zutil.h zlib.h zconf.h inftrees.h +infutil.obj : zutil.h zlib.h zconf.h inftrees.h infutil.h +minigzip.obj : zlib.h zconf.h +trees.obj : deflate.h zutil.h zlib.h zconf.h +uncompr.obj : zlib.h zconf.h +zutil.obj : zutil.h zlib.h zconf.h ADDED compat/zlib/old/os2/Makefile.os2 Index: compat/zlib/old/os2/Makefile.os2 ================================================================== --- /dev/null +++ compat/zlib/old/os2/Makefile.os2 @@ -0,0 +1,136 @@ +# Makefile for zlib under OS/2 using GCC (PGCC) +# For conditions of distribution and use, see copyright notice in zlib.h + +# To compile and test, type: +# cp Makefile.os2 .. +# cd .. +# make -f Makefile.os2 test + +# This makefile will build a static library z.lib, a shared library +# z.dll and a import library zdll.lib. You can use either z.lib or +# zdll.lib by specifying either -lz or -lzdll on gcc's command line + +CC=gcc -Zomf -s + +CFLAGS=-O6 -Wall +#CFLAGS=-O -DMAX_WBITS=14 -DMAX_MEM_LEVEL=7 +#CFLAGS=-g -DZLIB_DEBUG +#CFLAGS=-O3 -Wall -Wwrite-strings -Wpointer-arith -Wconversion \ +# -Wstrict-prototypes -Wmissing-prototypes + +#################### BUG WARNING: ##################### +## infcodes.c hits a bug in pgcc-1.0, so you have to use either +## -O# where # <= 4 or one of (-fno-ommit-frame-pointer or -fno-force-mem) +## This bug is reportedly fixed in pgcc >1.0, but this was not tested +CFLAGS+=-fno-force-mem + +LDFLAGS=-s -L. -lzdll -Zcrtdll +LDSHARED=$(CC) -s -Zomf -Zdll -Zcrtdll + +VER=1.1.0 +ZLIB=z.lib +SHAREDLIB=z.dll +SHAREDLIBIMP=zdll.lib +LIBS=$(ZLIB) $(SHAREDLIB) $(SHAREDLIBIMP) + +AR=emxomfar cr +IMPLIB=emximp +RANLIB=echo +TAR=tar +SHELL=bash + +prefix=/usr/local +exec_prefix = $(prefix) + +OBJS = adler32.o compress.o crc32.o gzio.o uncompr.o deflate.o trees.o \ + zutil.o inflate.o infblock.o inftrees.o infcodes.o infutil.o inffast.o + +TEST_OBJS = example.o minigzip.o + +DISTFILES = README INDEX ChangeLog configure Make*[a-z0-9] *.[ch] descrip.mms \ + algorithm.txt zlib.3 msdos/Make*[a-z0-9] msdos/zlib.def msdos/zlib.rc \ + nt/Makefile.nt nt/zlib.dnt contrib/README.contrib contrib/*.txt \ + contrib/asm386/*.asm contrib/asm386/*.c \ + contrib/asm386/*.bat contrib/asm386/zlibvc.d?? contrib/iostream/*.cpp \ + contrib/iostream/*.h contrib/iostream2/*.h contrib/iostream2/*.cpp \ + contrib/untgz/Makefile contrib/untgz/*.c contrib/untgz/*.w32 + +all: example.exe minigzip.exe + +test: all + @LD_LIBRARY_PATH=.:$(LD_LIBRARY_PATH) ; export LD_LIBRARY_PATH; \ + echo hello world | ./minigzip | ./minigzip -d || \ + echo ' *** minigzip test FAILED ***' ; \ + if ./example; then \ + echo ' *** zlib test OK ***'; \ + else \ + echo ' *** zlib test FAILED ***'; \ + fi + +$(ZLIB): $(OBJS) + $(AR) $@ $(OBJS) + -@ ($(RANLIB) $@ || true) >/dev/null 2>&1 + +$(SHAREDLIB): $(OBJS) os2/z.def + $(LDSHARED) -o $@ $^ + +$(SHAREDLIBIMP): os2/z.def + $(IMPLIB) -o $@ $^ + +example.exe: example.o $(LIBS) + $(CC) $(CFLAGS) -o $@ example.o $(LDFLAGS) + +minigzip.exe: minigzip.o $(LIBS) + $(CC) $(CFLAGS) -o $@ minigzip.o $(LDFLAGS) + +clean: + rm -f *.o *~ example minigzip libz.a libz.so* foo.gz + +distclean: clean + +zip: + mv Makefile Makefile~; cp -p Makefile.in Makefile + rm -f test.c ztest*.c + v=`sed -n -e 's/\.//g' -e '/VERSION "/s/.*"\(.*\)".*/\1/p' < zlib.h`;\ + zip -ul9 zlib$$v $(DISTFILES) + mv Makefile~ Makefile + +dist: + mv Makefile Makefile~; cp -p Makefile.in Makefile + rm -f test.c ztest*.c + d=zlib-`sed -n '/VERSION "/s/.*"\(.*\)".*/\1/p' < zlib.h`;\ + rm -f $$d.tar.gz; \ + if test ! -d ../$$d; then rm -f ../$$d; ln -s `pwd` ../$$d; fi; \ + files=""; \ + for f in $(DISTFILES); do files="$$files $$d/$$f"; done; \ + cd ..; \ + GZIP=-9 $(TAR) chofz $$d/$$d.tar.gz $$files; \ + if test ! -d $$d; then rm -f $$d; fi + mv Makefile~ Makefile + +tags: + etags *.[ch] + +depend: + makedepend -- $(CFLAGS) -- *.[ch] + +# DO NOT DELETE THIS LINE -- make depend depends on it. + +adler32.o: zlib.h zconf.h +compress.o: zlib.h zconf.h +crc32.o: zlib.h zconf.h +deflate.o: deflate.h zutil.h zlib.h zconf.h +example.o: zlib.h zconf.h +gzio.o: zutil.h zlib.h zconf.h +infblock.o: infblock.h inftrees.h infcodes.h infutil.h zutil.h zlib.h zconf.h +infcodes.o: zutil.h zlib.h zconf.h +infcodes.o: inftrees.h infblock.h infcodes.h infutil.h inffast.h +inffast.o: zutil.h zlib.h zconf.h inftrees.h +inffast.o: infblock.h infcodes.h infutil.h inffast.h +inflate.o: zutil.h zlib.h zconf.h infblock.h +inftrees.o: zutil.h zlib.h zconf.h inftrees.h +infutil.o: zutil.h zlib.h zconf.h infblock.h inftrees.h infcodes.h infutil.h +minigzip.o: zlib.h zconf.h +trees.o: deflate.h zutil.h zlib.h zconf.h trees.h +uncompr.o: zlib.h zconf.h +zutil.o: zutil.h zlib.h zconf.h ADDED compat/zlib/old/os2/zlib.def Index: compat/zlib/old/os2/zlib.def ================================================================== --- /dev/null +++ compat/zlib/old/os2/zlib.def @@ -0,0 +1,51 @@ +; +; Slightly modified version of ../nt/zlib.dnt :-) +; + +LIBRARY Z +DESCRIPTION "Zlib compression library for OS/2" +CODE PRELOAD MOVEABLE DISCARDABLE +DATA PRELOAD MOVEABLE MULTIPLE + +EXPORTS + adler32 + compress + crc32 + deflate + deflateCopy + deflateEnd + deflateInit2_ + deflateInit_ + deflateParams + deflateReset + deflateSetDictionary + gzclose + gzdopen + gzerror + gzflush + gzopen + gzread + gzwrite + inflate + inflateEnd + inflateInit2_ + inflateInit_ + inflateReset + inflateSetDictionary + inflateSync + uncompress + zlibVersion + gzprintf + gzputc + gzgetc + gzseek + gzrewind + gztell + gzeof + gzsetparams + zError + inflateSyncPoint + get_crc_table + compress2 + gzputs + gzgets ADDED compat/zlib/old/visual-basic.txt Index: compat/zlib/old/visual-basic.txt ================================================================== --- /dev/null +++ compat/zlib/old/visual-basic.txt @@ -0,0 +1,160 @@ +See below some functions declarations for Visual Basic. + +Frequently Asked Question: + +Q: Each time I use the compress function I get the -5 error (not enough + room in the output buffer). + +A: Make sure that the length of the compressed buffer is passed by + reference ("as any"), not by value ("as long"). Also check that + before the call of compress this length is equal to the total size of + the compressed buffer and not zero. + + +From: "Jon Caruana" +Subject: Re: How to port zlib declares to vb? +Date: Mon, 28 Oct 1996 18:33:03 -0600 + +Got the answer! (I haven't had time to check this but it's what I got, and +looks correct): + +He has the following routines working: + compress + uncompress + gzopen + gzwrite + gzread + gzclose + +Declares follow: (Quoted from Carlos Rios , in Vb4 form) + +#If Win16 Then 'Use Win16 calls. +Declare Function compress Lib "ZLIB.DLL" (ByVal compr As + String, comprLen As Any, ByVal buf As String, ByVal buflen + As Long) As Integer +Declare Function uncompress Lib "ZLIB.DLL" (ByVal uncompr + As String, uncomprLen As Any, ByVal compr As String, ByVal + lcompr As Long) As Integer +Declare Function gzopen Lib "ZLIB.DLL" (ByVal filePath As + String, ByVal mode As String) As Long +Declare Function gzread Lib "ZLIB.DLL" (ByVal file As + Long, ByVal uncompr As String, ByVal uncomprLen As Integer) + As Integer +Declare Function gzwrite Lib "ZLIB.DLL" (ByVal file As + Long, ByVal uncompr As String, ByVal uncomprLen As Integer) + As Integer +Declare Function gzclose Lib "ZLIB.DLL" (ByVal file As + Long) As Integer +#Else +Declare Function compress Lib "ZLIB32.DLL" + (ByVal compr As String, comprLen As Any, ByVal buf As + String, ByVal buflen As Long) As Integer +Declare Function uncompress Lib "ZLIB32.DLL" + (ByVal uncompr As String, uncomprLen As Any, ByVal compr As + String, ByVal lcompr As Long) As Long +Declare Function gzopen Lib "ZLIB32.DLL" + (ByVal file As String, ByVal mode As String) As Long +Declare Function gzread Lib "ZLIB32.DLL" + (ByVal file As Long, ByVal uncompr As String, ByVal + uncomprLen As Long) As Long +Declare Function gzwrite Lib "ZLIB32.DLL" + (ByVal file As Long, ByVal uncompr As String, ByVal + uncomprLen As Long) As Long +Declare Function gzclose Lib "ZLIB32.DLL" + (ByVal file As Long) As Long +#End If + +-Jon Caruana +jon-net@usa.net +Microsoft Sitebuilder Network Level 1 Member - HTML Writer's Guild Member + + +Here is another example from Michael that he +says conforms to the VB guidelines, and that solves the problem of not +knowing the uncompressed size by storing it at the end of the file: + +'Calling the functions: +'bracket meaning: [optional] {Range of possible values} +'Call subCompressFile( [, , [level of compression {1..9}]]) +'Call subUncompressFile() + +Option Explicit +Private lngpvtPcnSml As Long 'Stores value for 'lngPercentSmaller' +Private Const SUCCESS As Long = 0 +Private Const strFilExt As String = ".cpr" +Private Declare Function lngfncCpr Lib "zlib.dll" Alias "compress2" (ByRef +dest As Any, ByRef destLen As Any, ByRef src As Any, ByVal srcLen As Long, +ByVal level As Integer) As Long +Private Declare Function lngfncUcp Lib "zlib.dll" Alias "uncompress" (ByRef +dest As Any, ByRef destLen As Any, ByRef src As Any, ByVal srcLen As Long) +As Long + +Public Sub subCompressFile(ByVal strargOriFilPth As String, Optional ByVal +strargCprFilPth As String, Optional ByVal intLvl As Integer = 9) + Dim strCprPth As String + Dim lngOriSiz As Long + Dim lngCprSiz As Long + Dim bytaryOri() As Byte + Dim bytaryCpr() As Byte + lngOriSiz = FileLen(strargOriFilPth) + ReDim bytaryOri(lngOriSiz - 1) + Open strargOriFilPth For Binary Access Read As #1 + Get #1, , bytaryOri() + Close #1 + strCprPth = IIf(strargCprFilPth = "", strargOriFilPth, strargCprFilPth) +'Select file path and name + strCprPth = strCprPth & IIf(Right(strCprPth, Len(strFilExt)) = +strFilExt, "", strFilExt) 'Add file extension if not exists + lngCprSiz = (lngOriSiz * 1.01) + 12 'Compression needs temporary a bit +more space then original file size + ReDim bytaryCpr(lngCprSiz - 1) + If lngfncCpr(bytaryCpr(0), lngCprSiz, bytaryOri(0), lngOriSiz, intLvl) = +SUCCESS Then + lngpvtPcnSml = (1# - (lngCprSiz / lngOriSiz)) * 100 + ReDim Preserve bytaryCpr(lngCprSiz - 1) + Open strCprPth For Binary Access Write As #1 + Put #1, , bytaryCpr() + Put #1, , lngOriSiz 'Add the the original size value to the end +(last 4 bytes) + Close #1 + Else + MsgBox "Compression error" + End If + Erase bytaryCpr + Erase bytaryOri +End Sub + +Public Sub subUncompressFile(ByVal strargFilPth As String) + Dim bytaryCpr() As Byte + Dim bytaryOri() As Byte + Dim lngOriSiz As Long + Dim lngCprSiz As Long + Dim strOriPth As String + lngCprSiz = FileLen(strargFilPth) + ReDim bytaryCpr(lngCprSiz - 1) + Open strargFilPth For Binary Access Read As #1 + Get #1, , bytaryCpr() + Close #1 + 'Read the original file size value: + lngOriSiz = bytaryCpr(lngCprSiz - 1) * (2 ^ 24) _ + + bytaryCpr(lngCprSiz - 2) * (2 ^ 16) _ + + bytaryCpr(lngCprSiz - 3) * (2 ^ 8) _ + + bytaryCpr(lngCprSiz - 4) + ReDim Preserve bytaryCpr(lngCprSiz - 5) 'Cut of the original size value + ReDim bytaryOri(lngOriSiz - 1) + If lngfncUcp(bytaryOri(0), lngOriSiz, bytaryCpr(0), lngCprSiz) = SUCCESS +Then + strOriPth = Left(strargFilPth, Len(strargFilPth) - Len(strFilExt)) + Open strOriPth For Binary Access Write As #1 + Put #1, , bytaryOri() + Close #1 + Else + MsgBox "Uncompression error" + End If + Erase bytaryCpr + Erase bytaryOri +End Sub +Public Property Get lngPercentSmaller() As Long + lngPercentSmaller = lngpvtPcnSml +End Property ADDED compat/zlib/os400/README400 Index: compat/zlib/os400/README400 ================================================================== --- /dev/null +++ compat/zlib/os400/README400 @@ -0,0 +1,48 @@ + ZLIB version 1.2.11 for OS/400 installation instructions + +1) Download and unpack the zlib tarball to some IFS directory. + (i.e.: /path/to/the/zlib/ifs/source/directory) + + If the installed IFS command suppors gzip format, this is straightforward, +else you have to unpack first to some directory on a system supporting it, +then move the whole directory to the IFS via the network (via SMB or FTP). + +2) Edit the configuration parameters in the compilation script. + + EDTF STMF('/path/to/the/zlib/ifs/source/directory/os400/make.sh') + +Tune the parameters according to your needs if not matching the defaults. +Save the file and exit after edition. + +3) Enter qshell, then work in the zlib OS/400 specific directory. + + QSH + cd /path/to/the/zlib/ifs/source/directory/os400 + +4) Compile and install + + sh make.sh + +The script will: +- create the libraries, objects and IFS directories for the zlib environment, +- compile all modules, +- create a service program, +- create a static and a dynamic binding directory, +- install header files for C/C++ and for ILE/RPG, both for compilation in + DB2 and IFS environments. + +That's all. + + +Notes: For OS/400 ILE RPG programmers, a /copy member defining the ZLIB + API prototypes for ILE RPG can be found in ZLIB/H(ZLIB.INC). + In the ILE environment, the same definitions are available from + file zlib.inc located in the same IFS include directory as the + C/C++ header files. + Please read comments in this member for more information. + + Remember that most foreign textual data are ASCII coded: this + implementation does not handle conversion from/to ASCII, so + text data code conversions must be done explicitely. + + Mainly for the reason above, always open zipped files in binary mode. ADDED compat/zlib/os400/bndsrc Index: compat/zlib/os400/bndsrc ================================================================== --- /dev/null +++ compat/zlib/os400/bndsrc @@ -0,0 +1,119 @@ +STRPGMEXP PGMLVL(*CURRENT) SIGNATURE('ZLIB') + +/*@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@*/ +/* Version 1.1.3 entry points. */ +/*@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@*/ + + EXPORT SYMBOL("adler32") + EXPORT SYMBOL("compress") + EXPORT SYMBOL("compress2") + EXPORT SYMBOL("crc32") + EXPORT SYMBOL("get_crc_table") + EXPORT SYMBOL("deflate") + EXPORT SYMBOL("deflateEnd") + EXPORT SYMBOL("deflateSetDictionary") + EXPORT SYMBOL("deflateCopy") + EXPORT SYMBOL("deflateReset") + EXPORT SYMBOL("deflateParams") + EXPORT SYMBOL("deflatePrime") + EXPORT SYMBOL("deflateInit_") + EXPORT SYMBOL("deflateInit2_") + EXPORT SYMBOL("gzopen") + EXPORT SYMBOL("gzdopen") + EXPORT SYMBOL("gzsetparams") + EXPORT SYMBOL("gzread") + EXPORT SYMBOL("gzwrite") + EXPORT SYMBOL("gzprintf") + EXPORT SYMBOL("gzputs") + EXPORT SYMBOL("gzgets") + EXPORT SYMBOL("gzputc") + EXPORT SYMBOL("gzgetc") + EXPORT SYMBOL("gzflush") + EXPORT SYMBOL("gzseek") + EXPORT SYMBOL("gzrewind") + EXPORT SYMBOL("gztell") + EXPORT SYMBOL("gzeof") + EXPORT SYMBOL("gzclose") + EXPORT SYMBOL("gzerror") + EXPORT SYMBOL("inflate") + EXPORT SYMBOL("inflateEnd") + EXPORT SYMBOL("inflateSetDictionary") + EXPORT SYMBOL("inflateSync") + EXPORT SYMBOL("inflateReset") + EXPORT SYMBOL("inflateInit_") + EXPORT SYMBOL("inflateInit2_") + EXPORT SYMBOL("inflateSyncPoint") + EXPORT SYMBOL("uncompress") + EXPORT SYMBOL("zlibVersion") + EXPORT SYMBOL("zError") + EXPORT SYMBOL("z_errmsg") + +/*@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@*/ +/* Version 1.2.1 additional entry points. */ +/*@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@*/ + + EXPORT SYMBOL("compressBound") + EXPORT SYMBOL("deflateBound") + EXPORT SYMBOL("deflatePending") + EXPORT SYMBOL("gzungetc") + EXPORT SYMBOL("gzclearerr") + EXPORT SYMBOL("inflateBack") + EXPORT SYMBOL("inflateBackEnd") + EXPORT SYMBOL("inflateBackInit_") + EXPORT SYMBOL("inflateCopy") + EXPORT SYMBOL("zlibCompileFlags") + +/*@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@*/ +/* Version 1.2.4 additional entry points. */ +/*@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@*/ + + EXPORT SYMBOL("adler32_combine") + EXPORT SYMBOL("adler32_combine64") + EXPORT SYMBOL("crc32_combine") + EXPORT SYMBOL("crc32_combine64") + EXPORT SYMBOL("deflateSetHeader") + EXPORT SYMBOL("deflateTune") + EXPORT SYMBOL("gzbuffer") + EXPORT SYMBOL("gzclose_r") + EXPORT SYMBOL("gzclose_w") + EXPORT SYMBOL("gzdirect") + EXPORT SYMBOL("gzoffset") + EXPORT SYMBOL("gzoffset64") + EXPORT SYMBOL("gzopen64") + EXPORT SYMBOL("gzseek64") + EXPORT SYMBOL("gztell64") + EXPORT SYMBOL("inflateGetHeader") + EXPORT SYMBOL("inflateMark") + EXPORT SYMBOL("inflatePrime") + EXPORT SYMBOL("inflateReset2") + EXPORT SYMBOL("inflateUndermine") + +/*@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@*/ +/* Version 1.2.6 additional entry points. */ +/*@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@*/ + + EXPORT SYMBOL("deflateResetKeep") + EXPORT SYMBOL("gzgetc_") + EXPORT SYMBOL("inflateResetKeep") + +/*@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@*/ +/* Version 1.2.8 additional entry points. */ +/*@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@*/ + + EXPORT SYMBOL("gzvprintf") + EXPORT SYMBOL("inflateGetDictionary") + +/*@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@*/ +/* Version 1.2.9 additional entry points. */ +/*@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@*/ + + EXPORT SYMBOL("adler32_z") + EXPORT SYMBOL("crc32_z") + EXPORT SYMBOL("deflateGetDictionary") + EXPORT SYMBOL("gzfread") + EXPORT SYMBOL("gzfwrite") + EXPORT SYMBOL("inflateCodesUsed") + EXPORT SYMBOL("inflateValidate") + EXPORT SYMBOL("uncompress2") + +ENDPGMEXP ADDED compat/zlib/os400/make.sh Index: compat/zlib/os400/make.sh ================================================================== --- /dev/null +++ compat/zlib/os400/make.sh @@ -0,0 +1,366 @@ +#!/bin/sh +# +# ZLIB compilation script for the OS/400. +# +# +# This is a shell script since make is not a standard component of OS/400. + + +################################################################################ +# +# Tunable configuration parameters. +# +################################################################################ + +TARGETLIB='ZLIB' # Target OS/400 program library +STATBNDDIR='ZLIB_A' # Static binding directory. +DYNBNDDIR='ZLIB' # Dynamic binding directory. +SRVPGM="ZLIB" # Service program. +IFSDIR='/zlib' # IFS support base directory. +TGTCCSID='500' # Target CCSID of objects +DEBUG='*NONE' # Debug level +OPTIMIZE='40' # Optimisation level +OUTPUT='*NONE' # Compilation output option. +TGTRLS='V6R1M0' # Target OS release + +export TARGETLIB STATBNDDIR DYNBNDDIR SRVPGM IFSDIR +export TGTCCSID DEBUG OPTIMIZE OUTPUT TGTRLS + + +################################################################################ +# +# OS/400 specific definitions. +# +################################################################################ + +LIBIFSNAME="/QSYS.LIB/${TARGETLIB}.LIB" + + +################################################################################ +# +# Procedures. +# +################################################################################ + +# action_needed dest [src] +# +# dest is an object to build +# if specified, src is an object on which dest depends. +# +# exit 0 (succeeds) if some action has to be taken, else 1. + +action_needed() + +{ + [ ! -e "${1}" ] && return 0 + [ "${2}" ] || return 1 + [ "${1}" -ot "${2}" ] && return 0 + return 1 +} + + +# make_module module_name source_name [additional_definitions] +# +# Compile source name into module if needed. +# As side effect, append the module name to variable MODULES. +# Set LINK to "YES" if the module has been compiled. + +make_module() + +{ + MODULES="${MODULES} ${1}" + MODIFSNAME="${LIBIFSNAME}/${1}.MODULE" + CSRC="`basename \"${2}\"`" + + if action_needed "${MODIFSNAME}" "${2}" + then : + elif [ ! "`sed -e \"//,/<\\\\/source>/!d\" \ + -e '/ tmphdrfile + + # Need to translate to target CCSID. + + CMD="CPY OBJ('`pwd`/tmphdrfile') TOOBJ('${DEST}')" + CMD="${CMD} TOCCSID(${TGTCCSID}) DTAFMT(*TEXT) REPLACE(*YES)" + system "${CMD}" + # touch -r "${HFILE}" "${DEST}" + rm -f tmphdrfile + fi + + IFSFILE="${IFSDIR}/include/`basename \"${HFILE}\"`" + + if action_needed "${IFSFILE}" "${DEST}" + then rm -f "${IFSFILE}" + ln -s "${DEST}" "${IFSFILE}" + fi +done + + +# Install the ILE/RPG header file. + + +HFILE="${SCRIPTDIR}/zlib.inc" +DEST="${SRCPF}/ZLIB.INC.MBR" + +if action_needed "${DEST}" "${HFILE}" +then CMD="CPY OBJ('${HFILE}') TOOBJ('${DEST}')" + CMD="${CMD} TOCCSID(${TGTCCSID}) DTAFMT(*TEXT) REPLACE(*YES)" + system "${CMD}" + # touch -r "${HFILE}" "${DEST}" +fi + +IFSFILE="${IFSDIR}/include/`basename \"${HFILE}\"`" + +if action_needed "${IFSFILE}" "${DEST}" +then rm -f "${IFSFILE}" + ln -s "${DEST}" "${IFSFILE}" +fi + + +# Create and compile the identification source file. + +echo '#pragma comment(user, "ZLIB version '"${VERSION}"'")' > os400.c +echo '#pragma comment(user, __DATE__)' >> os400.c +echo '#pragma comment(user, __TIME__)' >> os400.c +echo '#pragma comment(copyright, "Copyright (C) 1995-2017 Jean-Loup Gailly, Mark Adler. OS/400 version by P. Monnerat.")' >> os400.c +make_module OS400 os400.c +LINK= # No need to rebuild service program yet. +MODULES= + + +# Get source list. + +CSOURCES=`sed -e '/ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + Library + + Medium + + 2.0 + + + + zlib + zlib + alain.bonnefoy@icbt.com + Public + public + www.gzip.org/zlib + + + Jean-Loup Gailly,Mark Adler + www.gzip.org/zlib + + zlib@gzip.org + + + A massively spiffy yet delicately unobtrusive compression library. + zlib is designed to be a free, general-purpose, legally unencumbered, lossless data compression library for use on virtually any computer hardware and operating system. + http://www.gzip.org/zlib + + + + + 1.2.11 + Medium + Stable + + + + + + + No License + + + + Software Development/Libraries and Extensions/C Libraries + zlib,compression + qnx6 + qnx6 + None + Developer + + + + + + + + + + + + + + Install + Post + No + Ignore + + No + Optional + + + + + + + + + + + + + InstallOver + zlib + + + + + + + + + + + + + InstallOver + zlib-dev + + + + + + + + + ADDED compat/zlib/test/example.c Index: compat/zlib/test/example.c ================================================================== --- /dev/null +++ compat/zlib/test/example.c @@ -0,0 +1,602 @@ +/* example.c -- usage example of the zlib compression library + * Copyright (C) 1995-2006, 2011, 2016 Jean-loup Gailly + * For conditions of distribution and use, see copyright notice in zlib.h + */ + +/* @(#) $Id$ */ + +#include "zlib.h" +#include + +#ifdef STDC +# include +# include +#endif + +#if defined(VMS) || defined(RISCOS) +# define TESTFILE "foo-gz" +#else +# define TESTFILE "foo.gz" +#endif + +#define CHECK_ERR(err, msg) { \ + if (err != Z_OK) { \ + fprintf(stderr, "%s error: %d\n", msg, err); \ + exit(1); \ + } \ +} + +static z_const char hello[] = "hello, hello!"; +/* "hello world" would be more standard, but the repeated "hello" + * stresses the compression code better, sorry... + */ + +static const char dictionary[] = "hello"; +static uLong dictId; /* Adler32 value of the dictionary */ + +void test_deflate OF((Byte *compr, uLong comprLen)); +void test_inflate OF((Byte *compr, uLong comprLen, + Byte *uncompr, uLong uncomprLen)); +void test_large_deflate OF((Byte *compr, uLong comprLen, + Byte *uncompr, uLong uncomprLen)); +void test_large_inflate OF((Byte *compr, uLong comprLen, + Byte *uncompr, uLong uncomprLen)); +void test_flush OF((Byte *compr, uLong *comprLen)); +void test_sync OF((Byte *compr, uLong comprLen, + Byte *uncompr, uLong uncomprLen)); +void test_dict_deflate OF((Byte *compr, uLong comprLen)); +void test_dict_inflate OF((Byte *compr, uLong comprLen, + Byte *uncompr, uLong uncomprLen)); +int main OF((int argc, char *argv[])); + + +#ifdef Z_SOLO + +void *myalloc OF((void *, unsigned, unsigned)); +void myfree OF((void *, void *)); + +void *myalloc(q, n, m) + void *q; + unsigned n, m; +{ + (void)q; + return calloc(n, m); +} + +void myfree(void *q, void *p) +{ + (void)q; + free(p); +} + +static alloc_func zalloc = myalloc; +static free_func zfree = myfree; + +#else /* !Z_SOLO */ + +static alloc_func zalloc = (alloc_func)0; +static free_func zfree = (free_func)0; + +void test_compress OF((Byte *compr, uLong comprLen, + Byte *uncompr, uLong uncomprLen)); +void test_gzio OF((const char *fname, + Byte *uncompr, uLong uncomprLen)); + +/* =========================================================================== + * Test compress() and uncompress() + */ +void test_compress(compr, comprLen, uncompr, uncomprLen) + Byte *compr, *uncompr; + uLong comprLen, uncomprLen; +{ + int err; + uLong len = (uLong)strlen(hello)+1; + + err = compress(compr, &comprLen, (const Bytef*)hello, len); + CHECK_ERR(err, "compress"); + + strcpy((char*)uncompr, "garbage"); + + err = uncompress(uncompr, &uncomprLen, compr, comprLen); + CHECK_ERR(err, "uncompress"); + + if (strcmp((char*)uncompr, hello)) { + fprintf(stderr, "bad uncompress\n"); + exit(1); + } else { + printf("uncompress(): %s\n", (char *)uncompr); + } +} + +/* =========================================================================== + * Test read/write of .gz files + */ +void test_gzio(fname, uncompr, uncomprLen) + const char *fname; /* compressed file name */ + Byte *uncompr; + uLong uncomprLen; +{ +#ifdef NO_GZCOMPRESS + fprintf(stderr, "NO_GZCOMPRESS -- gz* functions cannot compress\n"); +#else + int err; + int len = (int)strlen(hello)+1; + gzFile file; + z_off_t pos; + + file = gzopen(fname, "wb"); + if (file == NULL) { + fprintf(stderr, "gzopen error\n"); + exit(1); + } + gzputc(file, 'h'); + if (gzputs(file, "ello") != 4) { + fprintf(stderr, "gzputs err: %s\n", gzerror(file, &err)); + exit(1); + } + if (gzprintf(file, ", %s!", "hello") != 8) { + fprintf(stderr, "gzprintf err: %s\n", gzerror(file, &err)); + exit(1); + } + gzseek(file, 1L, SEEK_CUR); /* add one zero byte */ + gzclose(file); + + file = gzopen(fname, "rb"); + if (file == NULL) { + fprintf(stderr, "gzopen error\n"); + exit(1); + } + strcpy((char*)uncompr, "garbage"); + + if (gzread(file, uncompr, (unsigned)uncomprLen) != len) { + fprintf(stderr, "gzread err: %s\n", gzerror(file, &err)); + exit(1); + } + if (strcmp((char*)uncompr, hello)) { + fprintf(stderr, "bad gzread: %s\n", (char*)uncompr); + exit(1); + } else { + printf("gzread(): %s\n", (char*)uncompr); + } + + pos = gzseek(file, -8L, SEEK_CUR); + if (pos != 6 || gztell(file) != pos) { + fprintf(stderr, "gzseek error, pos=%ld, gztell=%ld\n", + (long)pos, (long)gztell(file)); + exit(1); + } + + if (gzgetc(file) != ' ') { + fprintf(stderr, "gzgetc error\n"); + exit(1); + } + + if (gzungetc(' ', file) != ' ') { + fprintf(stderr, "gzungetc error\n"); + exit(1); + } + + gzgets(file, (char*)uncompr, (int)uncomprLen); + if (strlen((char*)uncompr) != 7) { /* " hello!" */ + fprintf(stderr, "gzgets err after gzseek: %s\n", gzerror(file, &err)); + exit(1); + } + if (strcmp((char*)uncompr, hello + 6)) { + fprintf(stderr, "bad gzgets after gzseek\n"); + exit(1); + } else { + printf("gzgets() after gzseek: %s\n", (char*)uncompr); + } + + gzclose(file); +#endif +} + +#endif /* Z_SOLO */ + +/* =========================================================================== + * Test deflate() with small buffers + */ +void test_deflate(compr, comprLen) + Byte *compr; + uLong comprLen; +{ + z_stream c_stream; /* compression stream */ + int err; + uLong len = (uLong)strlen(hello)+1; + + c_stream.zalloc = zalloc; + c_stream.zfree = zfree; + c_stream.opaque = (voidpf)0; + + err = deflateInit(&c_stream, Z_DEFAULT_COMPRESSION); + CHECK_ERR(err, "deflateInit"); + + c_stream.next_in = (z_const unsigned char *)hello; + c_stream.next_out = compr; + + while (c_stream.total_in != len && c_stream.total_out < comprLen) { + c_stream.avail_in = c_stream.avail_out = 1; /* force small buffers */ + err = deflate(&c_stream, Z_NO_FLUSH); + CHECK_ERR(err, "deflate"); + } + /* Finish the stream, still forcing small buffers: */ + for (;;) { + c_stream.avail_out = 1; + err = deflate(&c_stream, Z_FINISH); + if (err == Z_STREAM_END) break; + CHECK_ERR(err, "deflate"); + } + + err = deflateEnd(&c_stream); + CHECK_ERR(err, "deflateEnd"); +} + +/* =========================================================================== + * Test inflate() with small buffers + */ +void test_inflate(compr, comprLen, uncompr, uncomprLen) + Byte *compr, *uncompr; + uLong comprLen, uncomprLen; +{ + int err; + z_stream d_stream; /* decompression stream */ + + strcpy((char*)uncompr, "garbage"); + + d_stream.zalloc = zalloc; + d_stream.zfree = zfree; + d_stream.opaque = (voidpf)0; + + d_stream.next_in = compr; + d_stream.avail_in = 0; + d_stream.next_out = uncompr; + + err = inflateInit(&d_stream); + CHECK_ERR(err, "inflateInit"); + + while (d_stream.total_out < uncomprLen && d_stream.total_in < comprLen) { + d_stream.avail_in = d_stream.avail_out = 1; /* force small buffers */ + err = inflate(&d_stream, Z_NO_FLUSH); + if (err == Z_STREAM_END) break; + CHECK_ERR(err, "inflate"); + } + + err = inflateEnd(&d_stream); + CHECK_ERR(err, "inflateEnd"); + + if (strcmp((char*)uncompr, hello)) { + fprintf(stderr, "bad inflate\n"); + exit(1); + } else { + printf("inflate(): %s\n", (char *)uncompr); + } +} + +/* =========================================================================== + * Test deflate() with large buffers and dynamic change of compression level + */ +void test_large_deflate(compr, comprLen, uncompr, uncomprLen) + Byte *compr, *uncompr; + uLong comprLen, uncomprLen; +{ + z_stream c_stream; /* compression stream */ + int err; + + c_stream.zalloc = zalloc; + c_stream.zfree = zfree; + c_stream.opaque = (voidpf)0; + + err = deflateInit(&c_stream, Z_BEST_SPEED); + CHECK_ERR(err, "deflateInit"); + + c_stream.next_out = compr; + c_stream.avail_out = (uInt)comprLen; + + /* At this point, uncompr is still mostly zeroes, so it should compress + * very well: + */ + c_stream.next_in = uncompr; + c_stream.avail_in = (uInt)uncomprLen; + err = deflate(&c_stream, Z_NO_FLUSH); + CHECK_ERR(err, "deflate"); + if (c_stream.avail_in != 0) { + fprintf(stderr, "deflate not greedy\n"); + exit(1); + } + + /* Feed in already compressed data and switch to no compression: */ + deflateParams(&c_stream, Z_NO_COMPRESSION, Z_DEFAULT_STRATEGY); + c_stream.next_in = compr; + c_stream.avail_in = (uInt)comprLen/2; + err = deflate(&c_stream, Z_NO_FLUSH); + CHECK_ERR(err, "deflate"); + + /* Switch back to compressing mode: */ + deflateParams(&c_stream, Z_BEST_COMPRESSION, Z_FILTERED); + c_stream.next_in = uncompr; + c_stream.avail_in = (uInt)uncomprLen; + err = deflate(&c_stream, Z_NO_FLUSH); + CHECK_ERR(err, "deflate"); + + err = deflate(&c_stream, Z_FINISH); + if (err != Z_STREAM_END) { + fprintf(stderr, "deflate should report Z_STREAM_END\n"); + exit(1); + } + err = deflateEnd(&c_stream); + CHECK_ERR(err, "deflateEnd"); +} + +/* =========================================================================== + * Test inflate() with large buffers + */ +void test_large_inflate(compr, comprLen, uncompr, uncomprLen) + Byte *compr, *uncompr; + uLong comprLen, uncomprLen; +{ + int err; + z_stream d_stream; /* decompression stream */ + + strcpy((char*)uncompr, "garbage"); + + d_stream.zalloc = zalloc; + d_stream.zfree = zfree; + d_stream.opaque = (voidpf)0; + + d_stream.next_in = compr; + d_stream.avail_in = (uInt)comprLen; + + err = inflateInit(&d_stream); + CHECK_ERR(err, "inflateInit"); + + for (;;) { + d_stream.next_out = uncompr; /* discard the output */ + d_stream.avail_out = (uInt)uncomprLen; + err = inflate(&d_stream, Z_NO_FLUSH); + if (err == Z_STREAM_END) break; + CHECK_ERR(err, "large inflate"); + } + + err = inflateEnd(&d_stream); + CHECK_ERR(err, "inflateEnd"); + + if (d_stream.total_out != 2*uncomprLen + comprLen/2) { + fprintf(stderr, "bad large inflate: %ld\n", d_stream.total_out); + exit(1); + } else { + printf("large_inflate(): OK\n"); + } +} + +/* =========================================================================== + * Test deflate() with full flush + */ +void test_flush(compr, comprLen) + Byte *compr; + uLong *comprLen; +{ + z_stream c_stream; /* compression stream */ + int err; + uInt len = (uInt)strlen(hello)+1; + + c_stream.zalloc = zalloc; + c_stream.zfree = zfree; + c_stream.opaque = (voidpf)0; + + err = deflateInit(&c_stream, Z_DEFAULT_COMPRESSION); + CHECK_ERR(err, "deflateInit"); + + c_stream.next_in = (z_const unsigned char *)hello; + c_stream.next_out = compr; + c_stream.avail_in = 3; + c_stream.avail_out = (uInt)*comprLen; + err = deflate(&c_stream, Z_FULL_FLUSH); + CHECK_ERR(err, "deflate"); + + compr[3]++; /* force an error in first compressed block */ + c_stream.avail_in = len - 3; + + err = deflate(&c_stream, Z_FINISH); + if (err != Z_STREAM_END) { + CHECK_ERR(err, "deflate"); + } + err = deflateEnd(&c_stream); + CHECK_ERR(err, "deflateEnd"); + + *comprLen = c_stream.total_out; +} + +/* =========================================================================== + * Test inflateSync() + */ +void test_sync(compr, comprLen, uncompr, uncomprLen) + Byte *compr, *uncompr; + uLong comprLen, uncomprLen; +{ + int err; + z_stream d_stream; /* decompression stream */ + + strcpy((char*)uncompr, "garbage"); + + d_stream.zalloc = zalloc; + d_stream.zfree = zfree; + d_stream.opaque = (voidpf)0; + + d_stream.next_in = compr; + d_stream.avail_in = 2; /* just read the zlib header */ + + err = inflateInit(&d_stream); + CHECK_ERR(err, "inflateInit"); + + d_stream.next_out = uncompr; + d_stream.avail_out = (uInt)uncomprLen; + + err = inflate(&d_stream, Z_NO_FLUSH); + CHECK_ERR(err, "inflate"); + + d_stream.avail_in = (uInt)comprLen-2; /* read all compressed data */ + err = inflateSync(&d_stream); /* but skip the damaged part */ + CHECK_ERR(err, "inflateSync"); + + err = inflate(&d_stream, Z_FINISH); + if (err != Z_DATA_ERROR) { + fprintf(stderr, "inflate should report DATA_ERROR\n"); + /* Because of incorrect adler32 */ + exit(1); + } + err = inflateEnd(&d_stream); + CHECK_ERR(err, "inflateEnd"); + + printf("after inflateSync(): hel%s\n", (char *)uncompr); +} + +/* =========================================================================== + * Test deflate() with preset dictionary + */ +void test_dict_deflate(compr, comprLen) + Byte *compr; + uLong comprLen; +{ + z_stream c_stream; /* compression stream */ + int err; + + c_stream.zalloc = zalloc; + c_stream.zfree = zfree; + c_stream.opaque = (voidpf)0; + + err = deflateInit(&c_stream, Z_BEST_COMPRESSION); + CHECK_ERR(err, "deflateInit"); + + err = deflateSetDictionary(&c_stream, + (const Bytef*)dictionary, (int)sizeof(dictionary)); + CHECK_ERR(err, "deflateSetDictionary"); + + dictId = c_stream.adler; + c_stream.next_out = compr; + c_stream.avail_out = (uInt)comprLen; + + c_stream.next_in = (z_const unsigned char *)hello; + c_stream.avail_in = (uInt)strlen(hello)+1; + + err = deflate(&c_stream, Z_FINISH); + if (err != Z_STREAM_END) { + fprintf(stderr, "deflate should report Z_STREAM_END\n"); + exit(1); + } + err = deflateEnd(&c_stream); + CHECK_ERR(err, "deflateEnd"); +} + +/* =========================================================================== + * Test inflate() with a preset dictionary + */ +void test_dict_inflate(compr, comprLen, uncompr, uncomprLen) + Byte *compr, *uncompr; + uLong comprLen, uncomprLen; +{ + int err; + z_stream d_stream; /* decompression stream */ + + strcpy((char*)uncompr, "garbage"); + + d_stream.zalloc = zalloc; + d_stream.zfree = zfree; + d_stream.opaque = (voidpf)0; + + d_stream.next_in = compr; + d_stream.avail_in = (uInt)comprLen; + + err = inflateInit(&d_stream); + CHECK_ERR(err, "inflateInit"); + + d_stream.next_out = uncompr; + d_stream.avail_out = (uInt)uncomprLen; + + for (;;) { + err = inflate(&d_stream, Z_NO_FLUSH); + if (err == Z_STREAM_END) break; + if (err == Z_NEED_DICT) { + if (d_stream.adler != dictId) { + fprintf(stderr, "unexpected dictionary"); + exit(1); + } + err = inflateSetDictionary(&d_stream, (const Bytef*)dictionary, + (int)sizeof(dictionary)); + } + CHECK_ERR(err, "inflate with dict"); + } + + err = inflateEnd(&d_stream); + CHECK_ERR(err, "inflateEnd"); + + if (strcmp((char*)uncompr, hello)) { + fprintf(stderr, "bad inflate with dict\n"); + exit(1); + } else { + printf("inflate with dictionary: %s\n", (char *)uncompr); + } +} + +/* =========================================================================== + * Usage: example [output.gz [input.gz]] + */ + +int main(argc, argv) + int argc; + char *argv[]; +{ + Byte *compr, *uncompr; + uLong comprLen = 10000*sizeof(int); /* don't overflow on MSDOS */ + uLong uncomprLen = comprLen; + static const char* myVersion = ZLIB_VERSION; + + if (zlibVersion()[0] != myVersion[0]) { + fprintf(stderr, "incompatible zlib version\n"); + exit(1); + + } else if (strcmp(zlibVersion(), ZLIB_VERSION) != 0) { + fprintf(stderr, "warning: different zlib version\n"); + } + + printf("zlib version %s = 0x%04x, compile flags = 0x%lx\n", + ZLIB_VERSION, ZLIB_VERNUM, zlibCompileFlags()); + + compr = (Byte*)calloc((uInt)comprLen, 1); + uncompr = (Byte*)calloc((uInt)uncomprLen, 1); + /* compr and uncompr are cleared to avoid reading uninitialized + * data and to ensure that uncompr compresses well. + */ + if (compr == Z_NULL || uncompr == Z_NULL) { + printf("out of memory\n"); + exit(1); + } + +#ifdef Z_SOLO + (void)argc; + (void)argv; +#else + test_compress(compr, comprLen, uncompr, uncomprLen); + + test_gzio((argc > 1 ? argv[1] : TESTFILE), + uncompr, uncomprLen); +#endif + + test_deflate(compr, comprLen); + test_inflate(compr, comprLen, uncompr, uncomprLen); + + test_large_deflate(compr, comprLen, uncompr, uncomprLen); + test_large_inflate(compr, comprLen, uncompr, uncomprLen); + + test_flush(compr, &comprLen); + test_sync(compr, comprLen, uncompr, uncomprLen); + comprLen = uncomprLen; + + test_dict_deflate(compr, comprLen); + test_dict_inflate(compr, comprLen, uncompr, uncomprLen); + + free(compr); + free(uncompr); + + return 0; +} ADDED compat/zlib/test/infcover.c Index: compat/zlib/test/infcover.c ================================================================== --- /dev/null +++ compat/zlib/test/infcover.c @@ -0,0 +1,671 @@ +/* infcover.c -- test zlib's inflate routines with full code coverage + * Copyright (C) 2011, 2016 Mark Adler + * For conditions of distribution and use, see copyright notice in zlib.h + */ + +/* to use, do: ./configure --cover && make cover */ + +#include +#include +#include +#include +#include "zlib.h" + +/* get definition of internal structure so we can mess with it (see pull()), + and so we can call inflate_trees() (see cover5()) */ +#define ZLIB_INTERNAL +#include "inftrees.h" +#include "inflate.h" + +#define local static + +/* -- memory tracking routines -- */ + +/* + These memory tracking routines are provided to zlib and track all of zlib's + allocations and deallocations, check for LIFO operations, keep a current + and high water mark of total bytes requested, optionally set a limit on the + total memory that can be allocated, and when done check for memory leaks. + + They are used as follows: + + z_stream strm; + mem_setup(&strm) initializes the memory tracking and sets the + zalloc, zfree, and opaque members of strm to use + memory tracking for all zlib operations on strm + mem_limit(&strm, limit) sets a limit on the total bytes requested -- a + request that exceeds this limit will result in an + allocation failure (returns NULL) -- setting the + limit to zero means no limit, which is the default + after mem_setup() + mem_used(&strm, "msg") prints to stderr "msg" and the total bytes used + mem_high(&strm, "msg") prints to stderr "msg" and the high water mark + mem_done(&strm, "msg") ends memory tracking, releases all allocations + for the tracking as well as leaked zlib blocks, if + any. If there was anything unusual, such as leaked + blocks, non-FIFO frees, or frees of addresses not + allocated, then "msg" and information about the + problem is printed to stderr. If everything is + normal, nothing is printed. mem_done resets the + strm members to Z_NULL to use the default memory + allocation routines on the next zlib initialization + using strm. + */ + +/* these items are strung together in a linked list, one for each allocation */ +struct mem_item { + void *ptr; /* pointer to allocated memory */ + size_t size; /* requested size of allocation */ + struct mem_item *next; /* pointer to next item in list, or NULL */ +}; + +/* this structure is at the root of the linked list, and tracks statistics */ +struct mem_zone { + struct mem_item *first; /* pointer to first item in list, or NULL */ + size_t total, highwater; /* total allocations, and largest total */ + size_t limit; /* memory allocation limit, or 0 if no limit */ + int notlifo, rogue; /* counts of non-LIFO frees and rogue frees */ +}; + +/* memory allocation routine to pass to zlib */ +local void *mem_alloc(void *mem, unsigned count, unsigned size) +{ + void *ptr; + struct mem_item *item; + struct mem_zone *zone = mem; + size_t len = count * (size_t)size; + + /* induced allocation failure */ + if (zone == NULL || (zone->limit && zone->total + len > zone->limit)) + return NULL; + + /* perform allocation using the standard library, fill memory with a + non-zero value to make sure that the code isn't depending on zeros */ + ptr = malloc(len); + if (ptr == NULL) + return NULL; + memset(ptr, 0xa5, len); + + /* create a new item for the list */ + item = malloc(sizeof(struct mem_item)); + if (item == NULL) { + free(ptr); + return NULL; + } + item->ptr = ptr; + item->size = len; + + /* insert item at the beginning of the list */ + item->next = zone->first; + zone->first = item; + + /* update the statistics */ + zone->total += item->size; + if (zone->total > zone->highwater) + zone->highwater = zone->total; + + /* return the allocated memory */ + return ptr; +} + +/* memory free routine to pass to zlib */ +local void mem_free(void *mem, void *ptr) +{ + struct mem_item *item, *next; + struct mem_zone *zone = mem; + + /* if no zone, just do a free */ + if (zone == NULL) { + free(ptr); + return; + } + + /* point next to the item that matches ptr, or NULL if not found -- remove + the item from the linked list if found */ + next = zone->first; + if (next) { + if (next->ptr == ptr) + zone->first = next->next; /* first one is it, remove from list */ + else { + do { /* search the linked list */ + item = next; + next = item->next; + } while (next != NULL && next->ptr != ptr); + if (next) { /* if found, remove from linked list */ + item->next = next->next; + zone->notlifo++; /* not a LIFO free */ + } + + } + } + + /* if found, update the statistics and free the item */ + if (next) { + zone->total -= next->size; + free(next); + } + + /* if not found, update the rogue count */ + else + zone->rogue++; + + /* in any case, do the requested free with the standard library function */ + free(ptr); +} + +/* set up a controlled memory allocation space for monitoring, set the stream + parameters to the controlled routines, with opaque pointing to the space */ +local void mem_setup(z_stream *strm) +{ + struct mem_zone *zone; + + zone = malloc(sizeof(struct mem_zone)); + assert(zone != NULL); + zone->first = NULL; + zone->total = 0; + zone->highwater = 0; + zone->limit = 0; + zone->notlifo = 0; + zone->rogue = 0; + strm->opaque = zone; + strm->zalloc = mem_alloc; + strm->zfree = mem_free; +} + +/* set a limit on the total memory allocation, or 0 to remove the limit */ +local void mem_limit(z_stream *strm, size_t limit) +{ + struct mem_zone *zone = strm->opaque; + + zone->limit = limit; +} + +/* show the current total requested allocations in bytes */ +local void mem_used(z_stream *strm, char *prefix) +{ + struct mem_zone *zone = strm->opaque; + + fprintf(stderr, "%s: %lu allocated\n", prefix, zone->total); +} + +/* show the high water allocation in bytes */ +local void mem_high(z_stream *strm, char *prefix) +{ + struct mem_zone *zone = strm->opaque; + + fprintf(stderr, "%s: %lu high water mark\n", prefix, zone->highwater); +} + +/* release the memory allocation zone -- if there are any surprises, notify */ +local void mem_done(z_stream *strm, char *prefix) +{ + int count = 0; + struct mem_item *item, *next; + struct mem_zone *zone = strm->opaque; + + /* show high water mark */ + mem_high(strm, prefix); + + /* free leftover allocations and item structures, if any */ + item = zone->first; + while (item != NULL) { + free(item->ptr); + next = item->next; + free(item); + item = next; + count++; + } + + /* issue alerts about anything unexpected */ + if (count || zone->total) + fprintf(stderr, "** %s: %lu bytes in %d blocks not freed\n", + prefix, zone->total, count); + if (zone->notlifo) + fprintf(stderr, "** %s: %d frees not LIFO\n", prefix, zone->notlifo); + if (zone->rogue) + fprintf(stderr, "** %s: %d frees not recognized\n", + prefix, zone->rogue); + + /* free the zone and delete from the stream */ + free(zone); + strm->opaque = Z_NULL; + strm->zalloc = Z_NULL; + strm->zfree = Z_NULL; +} + +/* -- inflate test routines -- */ + +/* Decode a hexadecimal string, set *len to length, in[] to the bytes. This + decodes liberally, in that hex digits can be adjacent, in which case two in + a row writes a byte. Or they can be delimited by any non-hex character, + where the delimiters are ignored except when a single hex digit is followed + by a delimiter, where that single digit writes a byte. The returned data is + allocated and must eventually be freed. NULL is returned if out of memory. + If the length is not needed, then len can be NULL. */ +local unsigned char *h2b(const char *hex, unsigned *len) +{ + unsigned char *in, *re; + unsigned next, val; + + in = malloc((strlen(hex) + 1) >> 1); + if (in == NULL) + return NULL; + next = 0; + val = 1; + do { + if (*hex >= '0' && *hex <= '9') + val = (val << 4) + *hex - '0'; + else if (*hex >= 'A' && *hex <= 'F') + val = (val << 4) + *hex - 'A' + 10; + else if (*hex >= 'a' && *hex <= 'f') + val = (val << 4) + *hex - 'a' + 10; + else if (val != 1 && val < 32) /* one digit followed by delimiter */ + val += 240; /* make it look like two digits */ + if (val > 255) { /* have two digits */ + in[next++] = val & 0xff; /* save the decoded byte */ + val = 1; /* start over */ + } + } while (*hex++); /* go through the loop with the terminating null */ + if (len != NULL) + *len = next; + re = realloc(in, next); + return re == NULL ? in : re; +} + +/* generic inflate() run, where hex is the hexadecimal input data, what is the + text to include in an error message, step is how much input data to feed + inflate() on each call, or zero to feed it all, win is the window bits + parameter to inflateInit2(), len is the size of the output buffer, and err + is the error code expected from the first inflate() call (the second + inflate() call is expected to return Z_STREAM_END). If win is 47, then + header information is collected with inflateGetHeader(). If a zlib stream + is looking for a dictionary, then an empty dictionary is provided. + inflate() is run until all of the input data is consumed. */ +local void inf(char *hex, char *what, unsigned step, int win, unsigned len, + int err) +{ + int ret; + unsigned have; + unsigned char *in, *out; + z_stream strm, copy; + gz_header head; + + mem_setup(&strm); + strm.avail_in = 0; + strm.next_in = Z_NULL; + ret = inflateInit2(&strm, win); + if (ret != Z_OK) { + mem_done(&strm, what); + return; + } + out = malloc(len); assert(out != NULL); + if (win == 47) { + head.extra = out; + head.extra_max = len; + head.name = out; + head.name_max = len; + head.comment = out; + head.comm_max = len; + ret = inflateGetHeader(&strm, &head); assert(ret == Z_OK); + } + in = h2b(hex, &have); assert(in != NULL); + if (step == 0 || step > have) + step = have; + strm.avail_in = step; + have -= step; + strm.next_in = in; + do { + strm.avail_out = len; + strm.next_out = out; + ret = inflate(&strm, Z_NO_FLUSH); assert(err == 9 || ret == err); + if (ret != Z_OK && ret != Z_BUF_ERROR && ret != Z_NEED_DICT) + break; + if (ret == Z_NEED_DICT) { + ret = inflateSetDictionary(&strm, in, 1); + assert(ret == Z_DATA_ERROR); + mem_limit(&strm, 1); + ret = inflateSetDictionary(&strm, out, 0); + assert(ret == Z_MEM_ERROR); + mem_limit(&strm, 0); + ((struct inflate_state *)strm.state)->mode = DICT; + ret = inflateSetDictionary(&strm, out, 0); + assert(ret == Z_OK); + ret = inflate(&strm, Z_NO_FLUSH); assert(ret == Z_BUF_ERROR); + } + ret = inflateCopy(©, &strm); assert(ret == Z_OK); + ret = inflateEnd(©); assert(ret == Z_OK); + err = 9; /* don't care next time around */ + have += strm.avail_in; + strm.avail_in = step > have ? have : step; + have -= strm.avail_in; + } while (strm.avail_in); + free(in); + free(out); + ret = inflateReset2(&strm, -8); assert(ret == Z_OK); + ret = inflateEnd(&strm); assert(ret == Z_OK); + mem_done(&strm, what); +} + +/* cover all of the lines in inflate.c up to inflate() */ +local void cover_support(void) +{ + int ret; + z_stream strm; + + mem_setup(&strm); + strm.avail_in = 0; + strm.next_in = Z_NULL; + ret = inflateInit(&strm); assert(ret == Z_OK); + mem_used(&strm, "inflate init"); + ret = inflatePrime(&strm, 5, 31); assert(ret == Z_OK); + ret = inflatePrime(&strm, -1, 0); assert(ret == Z_OK); + ret = inflateSetDictionary(&strm, Z_NULL, 0); + assert(ret == Z_STREAM_ERROR); + ret = inflateEnd(&strm); assert(ret == Z_OK); + mem_done(&strm, "prime"); + + inf("63 0", "force window allocation", 0, -15, 1, Z_OK); + inf("63 18 5", "force window replacement", 0, -8, 259, Z_OK); + inf("63 18 68 30 d0 0 0", "force split window update", 4, -8, 259, Z_OK); + inf("3 0", "use fixed blocks", 0, -15, 1, Z_STREAM_END); + inf("", "bad window size", 0, 1, 0, Z_STREAM_ERROR); + + mem_setup(&strm); + strm.avail_in = 0; + strm.next_in = Z_NULL; + ret = inflateInit_(&strm, ZLIB_VERSION - 1, (int)sizeof(z_stream)); + assert(ret == Z_VERSION_ERROR); + mem_done(&strm, "wrong version"); + + strm.avail_in = 0; + strm.next_in = Z_NULL; + ret = inflateInit(&strm); assert(ret == Z_OK); + ret = inflateEnd(&strm); assert(ret == Z_OK); + fputs("inflate built-in memory routines\n", stderr); +} + +/* cover all inflate() header and trailer cases and code after inflate() */ +local void cover_wrap(void) +{ + int ret; + z_stream strm, copy; + unsigned char dict[257]; + + ret = inflate(Z_NULL, 0); assert(ret == Z_STREAM_ERROR); + ret = inflateEnd(Z_NULL); assert(ret == Z_STREAM_ERROR); + ret = inflateCopy(Z_NULL, Z_NULL); assert(ret == Z_STREAM_ERROR); + fputs("inflate bad parameters\n", stderr); + + inf("1f 8b 0 0", "bad gzip method", 0, 31, 0, Z_DATA_ERROR); + inf("1f 8b 8 80", "bad gzip flags", 0, 31, 0, Z_DATA_ERROR); + inf("77 85", "bad zlib method", 0, 15, 0, Z_DATA_ERROR); + inf("8 99", "set window size from header", 0, 0, 0, Z_OK); + inf("78 9c", "bad zlib window size", 0, 8, 0, Z_DATA_ERROR); + inf("78 9c 63 0 0 0 1 0 1", "check adler32", 0, 15, 1, Z_STREAM_END); + inf("1f 8b 8 1e 0 0 0 0 0 0 1 0 0 0 0 0 0", "bad header crc", 0, 47, 1, + Z_DATA_ERROR); + inf("1f 8b 8 2 0 0 0 0 0 0 1d 26 3 0 0 0 0 0 0 0 0 0", "check gzip length", + 0, 47, 0, Z_STREAM_END); + inf("78 90", "bad zlib header check", 0, 47, 0, Z_DATA_ERROR); + inf("8 b8 0 0 0 1", "need dictionary", 0, 8, 0, Z_NEED_DICT); + inf("78 9c 63 0", "compute adler32", 0, 15, 1, Z_OK); + + mem_setup(&strm); + strm.avail_in = 0; + strm.next_in = Z_NULL; + ret = inflateInit2(&strm, -8); + strm.avail_in = 2; + strm.next_in = (void *)"\x63"; + strm.avail_out = 1; + strm.next_out = (void *)&ret; + mem_limit(&strm, 1); + ret = inflate(&strm, Z_NO_FLUSH); assert(ret == Z_MEM_ERROR); + ret = inflate(&strm, Z_NO_FLUSH); assert(ret == Z_MEM_ERROR); + mem_limit(&strm, 0); + memset(dict, 0, 257); + ret = inflateSetDictionary(&strm, dict, 257); + assert(ret == Z_OK); + mem_limit(&strm, (sizeof(struct inflate_state) << 1) + 256); + ret = inflatePrime(&strm, 16, 0); assert(ret == Z_OK); + strm.avail_in = 2; + strm.next_in = (void *)"\x80"; + ret = inflateSync(&strm); assert(ret == Z_DATA_ERROR); + ret = inflate(&strm, Z_NO_FLUSH); assert(ret == Z_STREAM_ERROR); + strm.avail_in = 4; + strm.next_in = (void *)"\0\0\xff\xff"; + ret = inflateSync(&strm); assert(ret == Z_OK); + (void)inflateSyncPoint(&strm); + ret = inflateCopy(©, &strm); assert(ret == Z_MEM_ERROR); + mem_limit(&strm, 0); + ret = inflateUndermine(&strm, 1); assert(ret == Z_DATA_ERROR); + (void)inflateMark(&strm); + ret = inflateEnd(&strm); assert(ret == Z_OK); + mem_done(&strm, "miscellaneous, force memory errors"); +} + +/* input and output functions for inflateBack() */ +local unsigned pull(void *desc, unsigned char **buf) +{ + static unsigned int next = 0; + static unsigned char dat[] = {0x63, 0, 2, 0}; + struct inflate_state *state; + + if (desc == Z_NULL) { + next = 0; + return 0; /* no input (already provided at next_in) */ + } + state = (void *)((z_stream *)desc)->state; + if (state != Z_NULL) + state->mode = SYNC; /* force an otherwise impossible situation */ + return next < sizeof(dat) ? (*buf = dat + next++, 1) : 0; +} + +local int push(void *desc, unsigned char *buf, unsigned len) +{ + buf += len; + return desc != Z_NULL; /* force error if desc not null */ +} + +/* cover inflateBack() up to common deflate data cases and after those */ +local void cover_back(void) +{ + int ret; + z_stream strm; + unsigned char win[32768]; + + ret = inflateBackInit_(Z_NULL, 0, win, 0, 0); + assert(ret == Z_VERSION_ERROR); + ret = inflateBackInit(Z_NULL, 0, win); assert(ret == Z_STREAM_ERROR); + ret = inflateBack(Z_NULL, Z_NULL, Z_NULL, Z_NULL, Z_NULL); + assert(ret == Z_STREAM_ERROR); + ret = inflateBackEnd(Z_NULL); assert(ret == Z_STREAM_ERROR); + fputs("inflateBack bad parameters\n", stderr); + + mem_setup(&strm); + ret = inflateBackInit(&strm, 15, win); assert(ret == Z_OK); + strm.avail_in = 2; + strm.next_in = (void *)"\x03"; + ret = inflateBack(&strm, pull, Z_NULL, push, Z_NULL); + assert(ret == Z_STREAM_END); + /* force output error */ + strm.avail_in = 3; + strm.next_in = (void *)"\x63\x00"; + ret = inflateBack(&strm, pull, Z_NULL, push, &strm); + assert(ret == Z_BUF_ERROR); + /* force mode error by mucking with state */ + ret = inflateBack(&strm, pull, &strm, push, Z_NULL); + assert(ret == Z_STREAM_ERROR); + ret = inflateBackEnd(&strm); assert(ret == Z_OK); + mem_done(&strm, "inflateBack bad state"); + + ret = inflateBackInit(&strm, 15, win); assert(ret == Z_OK); + ret = inflateBackEnd(&strm); assert(ret == Z_OK); + fputs("inflateBack built-in memory routines\n", stderr); +} + +/* do a raw inflate of data in hexadecimal with both inflate and inflateBack */ +local int try(char *hex, char *id, int err) +{ + int ret; + unsigned len, size; + unsigned char *in, *out, *win; + char *prefix; + z_stream strm; + + /* convert to hex */ + in = h2b(hex, &len); + assert(in != NULL); + + /* allocate work areas */ + size = len << 3; + out = malloc(size); + assert(out != NULL); + win = malloc(32768); + assert(win != NULL); + prefix = malloc(strlen(id) + 6); + assert(prefix != NULL); + + /* first with inflate */ + strcpy(prefix, id); + strcat(prefix, "-late"); + mem_setup(&strm); + strm.avail_in = 0; + strm.next_in = Z_NULL; + ret = inflateInit2(&strm, err < 0 ? 47 : -15); + assert(ret == Z_OK); + strm.avail_in = len; + strm.next_in = in; + do { + strm.avail_out = size; + strm.next_out = out; + ret = inflate(&strm, Z_TREES); + assert(ret != Z_STREAM_ERROR && ret != Z_MEM_ERROR); + if (ret == Z_DATA_ERROR || ret == Z_NEED_DICT) + break; + } while (strm.avail_in || strm.avail_out == 0); + if (err) { + assert(ret == Z_DATA_ERROR); + assert(strcmp(id, strm.msg) == 0); + } + inflateEnd(&strm); + mem_done(&strm, prefix); + + /* then with inflateBack */ + if (err >= 0) { + strcpy(prefix, id); + strcat(prefix, "-back"); + mem_setup(&strm); + ret = inflateBackInit(&strm, 15, win); + assert(ret == Z_OK); + strm.avail_in = len; + strm.next_in = in; + ret = inflateBack(&strm, pull, Z_NULL, push, Z_NULL); + assert(ret != Z_STREAM_ERROR); + if (err) { + assert(ret == Z_DATA_ERROR); + assert(strcmp(id, strm.msg) == 0); + } + inflateBackEnd(&strm); + mem_done(&strm, prefix); + } + + /* clean up */ + free(prefix); + free(win); + free(out); + free(in); + return ret; +} + +/* cover deflate data cases in both inflate() and inflateBack() */ +local void cover_inflate(void) +{ + try("0 0 0 0 0", "invalid stored block lengths", 1); + try("3 0", "fixed", 0); + try("6", "invalid block type", 1); + try("1 1 0 fe ff 0", "stored", 0); + try("fc 0 0", "too many length or distance symbols", 1); + try("4 0 fe ff", "invalid code lengths set", 1); + try("4 0 24 49 0", "invalid bit length repeat", 1); + try("4 0 24 e9 ff ff", "invalid bit length repeat", 1); + try("4 0 24 e9 ff 6d", "invalid code -- missing end-of-block", 1); + try("4 80 49 92 24 49 92 24 71 ff ff 93 11 0", + "invalid literal/lengths set", 1); + try("4 80 49 92 24 49 92 24 f b4 ff ff c3 84", "invalid distances set", 1); + try("4 c0 81 8 0 0 0 0 20 7f eb b 0 0", "invalid literal/length code", 1); + try("2 7e ff ff", "invalid distance code", 1); + try("c c0 81 0 0 0 0 0 90 ff 6b 4 0", "invalid distance too far back", 1); + + /* also trailer mismatch just in inflate() */ + try("1f 8b 8 0 0 0 0 0 0 0 3 0 0 0 0 1", "incorrect data check", -1); + try("1f 8b 8 0 0 0 0 0 0 0 3 0 0 0 0 0 0 0 0 1", + "incorrect length check", -1); + try("5 c0 21 d 0 0 0 80 b0 fe 6d 2f 91 6c", "pull 17", 0); + try("5 e0 81 91 24 cb b2 2c 49 e2 f 2e 8b 9a 47 56 9f fb fe ec d2 ff 1f", + "long code", 0); + try("ed c0 1 1 0 0 0 40 20 ff 57 1b 42 2c 4f", "length extra", 0); + try("ed cf c1 b1 2c 47 10 c4 30 fa 6f 35 1d 1 82 59 3d fb be 2e 2a fc f c", + "long distance and extra", 0); + try("ed c0 81 0 0 0 0 80 a0 fd a9 17 a9 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 " + "0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 6", "window end", 0); + inf("2 8 20 80 0 3 0", "inflate_fast TYPE return", 0, -15, 258, + Z_STREAM_END); + inf("63 18 5 40 c 0", "window wrap", 3, -8, 300, Z_OK); +} + +/* cover remaining lines in inftrees.c */ +local void cover_trees(void) +{ + int ret; + unsigned bits; + unsigned short lens[16], work[16]; + code *next, table[ENOUGH_DISTS]; + + /* we need to call inflate_table() directly in order to manifest not- + enough errors, since zlib insures that enough is always enough */ + for (bits = 0; bits < 15; bits++) + lens[bits] = (unsigned short)(bits + 1); + lens[15] = 15; + next = table; + bits = 15; + ret = inflate_table(DISTS, lens, 16, &next, &bits, work); + assert(ret == 1); + next = table; + bits = 1; + ret = inflate_table(DISTS, lens, 16, &next, &bits, work); + assert(ret == 1); + fputs("inflate_table not enough errors\n", stderr); +} + +/* cover remaining inffast.c decoding and window copying */ +local void cover_fast(void) +{ + inf("e5 e0 81 ad 6d cb b2 2c c9 01 1e 59 63 ae 7d ee fb 4d fd b5 35 41 68" + " ff 7f 0f 0 0 0", "fast length extra bits", 0, -8, 258, Z_DATA_ERROR); + inf("25 fd 81 b5 6d 59 b6 6a 49 ea af 35 6 34 eb 8c b9 f6 b9 1e ef 67 49" + " 50 fe ff ff 3f 0 0", "fast distance extra bits", 0, -8, 258, + Z_DATA_ERROR); + inf("3 7e 0 0 0 0 0", "fast invalid distance code", 0, -8, 258, + Z_DATA_ERROR); + inf("1b 7 0 0 0 0 0", "fast invalid literal/length code", 0, -8, 258, + Z_DATA_ERROR); + inf("d c7 1 ae eb 38 c 4 41 a0 87 72 de df fb 1f b8 36 b1 38 5d ff ff 0", + "fast 2nd level codes and too far back", 0, -8, 258, Z_DATA_ERROR); + inf("63 18 5 8c 10 8 0 0 0 0", "very common case", 0, -8, 259, Z_OK); + inf("63 60 60 18 c9 0 8 18 18 18 26 c0 28 0 29 0 0 0", + "contiguous and wrap around window", 6, -8, 259, Z_OK); + inf("63 0 3 0 0 0 0 0", "copy direct from output", 0, -8, 259, + Z_STREAM_END); +} + +int main(void) +{ + fprintf(stderr, "%s\n", zlibVersion()); + cover_support(); + cover_wrap(); + cover_back(); + cover_inflate(); + cover_trees(); + cover_fast(); + return 0; +} ADDED compat/zlib/test/minigzip.c Index: compat/zlib/test/minigzip.c ================================================================== --- /dev/null +++ compat/zlib/test/minigzip.c @@ -0,0 +1,651 @@ +/* minigzip.c -- simulate gzip using the zlib compression library + * Copyright (C) 1995-2006, 2010, 2011, 2016 Jean-loup Gailly + * For conditions of distribution and use, see copyright notice in zlib.h + */ + +/* + * minigzip is a minimal implementation of the gzip utility. This is + * only an example of using zlib and isn't meant to replace the + * full-featured gzip. No attempt is made to deal with file systems + * limiting names to 14 or 8+3 characters, etc... Error checking is + * very limited. So use minigzip only for testing; use gzip for the + * real thing. On MSDOS, use only on file names without extension + * or in pipe mode. + */ + +/* @(#) $Id$ */ + +#include "zlib.h" +#include + +#ifdef STDC +# include +# include +#endif + +#ifdef USE_MMAP +# include +# include +# include +#endif + +#if defined(MSDOS) || defined(OS2) || defined(WIN32) || defined(__CYGWIN__) +# include +# include +# ifdef UNDER_CE +# include +# endif +# define SET_BINARY_MODE(file) setmode(fileno(file), O_BINARY) +#else +# define SET_BINARY_MODE(file) +#endif + +#if defined(_MSC_VER) && _MSC_VER < 1900 +# define snprintf _snprintf +#endif + +#ifdef VMS +# define unlink delete +# define GZ_SUFFIX "-gz" +#endif +#ifdef RISCOS +# define unlink remove +# define GZ_SUFFIX "-gz" +# define fileno(file) file->__file +#endif +#if defined(__MWERKS__) && __dest_os != __be_os && __dest_os != __win32_os +# include /* for fileno */ +#endif + +#if !defined(Z_HAVE_UNISTD_H) && !defined(_LARGEFILE64_SOURCE) +#ifndef WIN32 /* unlink already in stdio.h for WIN32 */ + extern int unlink OF((const char *)); +#endif +#endif + +#if defined(UNDER_CE) +# include +# define perror(s) pwinerror(s) + +/* Map the Windows error number in ERROR to a locale-dependent error + message string and return a pointer to it. Typically, the values + for ERROR come from GetLastError. + + The string pointed to shall not be modified by the application, + but may be overwritten by a subsequent call to strwinerror + + The strwinerror function does not change the current setting + of GetLastError. */ + +static char *strwinerror (error) + DWORD error; +{ + static char buf[1024]; + + wchar_t *msgbuf; + DWORD lasterr = GetLastError(); + DWORD chars = FormatMessage(FORMAT_MESSAGE_FROM_SYSTEM + | FORMAT_MESSAGE_ALLOCATE_BUFFER, + NULL, + error, + 0, /* Default language */ + (LPVOID)&msgbuf, + 0, + NULL); + if (chars != 0) { + /* If there is an \r\n appended, zap it. */ + if (chars >= 2 + && msgbuf[chars - 2] == '\r' && msgbuf[chars - 1] == '\n') { + chars -= 2; + msgbuf[chars] = 0; + } + + if (chars > sizeof (buf) - 1) { + chars = sizeof (buf) - 1; + msgbuf[chars] = 0; + } + + wcstombs(buf, msgbuf, chars + 1); + LocalFree(msgbuf); + } + else { + sprintf(buf, "unknown win32 error (%ld)", error); + } + + SetLastError(lasterr); + return buf; +} + +static void pwinerror (s) + const char *s; +{ + if (s && *s) + fprintf(stderr, "%s: %s\n", s, strwinerror(GetLastError ())); + else + fprintf(stderr, "%s\n", strwinerror(GetLastError ())); +} + +#endif /* UNDER_CE */ + +#ifndef GZ_SUFFIX +# define GZ_SUFFIX ".gz" +#endif +#define SUFFIX_LEN (sizeof(GZ_SUFFIX)-1) + +#define BUFLEN 16384 +#define MAX_NAME_LEN 1024 + +#ifdef MAXSEG_64K +# define local static + /* Needed for systems with limitation on stack size. */ +#else +# define local +#endif + +#ifdef Z_SOLO +/* for Z_SOLO, create simplified gz* functions using deflate and inflate */ + +#if defined(Z_HAVE_UNISTD_H) || defined(Z_LARGE) +# include /* for unlink() */ +#endif + +void *myalloc OF((void *, unsigned, unsigned)); +void myfree OF((void *, void *)); + +void *myalloc(q, n, m) + void *q; + unsigned n, m; +{ + (void)q; + return calloc(n, m); +} + +void myfree(q, p) + void *q, *p; +{ + (void)q; + free(p); +} + +typedef struct gzFile_s { + FILE *file; + int write; + int err; + char *msg; + z_stream strm; +} *gzFile; + +gzFile gzopen OF((const char *, const char *)); +gzFile gzdopen OF((int, const char *)); +gzFile gz_open OF((const char *, int, const char *)); + +gzFile gzopen(path, mode) +const char *path; +const char *mode; +{ + return gz_open(path, -1, mode); +} + +gzFile gzdopen(fd, mode) +int fd; +const char *mode; +{ + return gz_open(NULL, fd, mode); +} + +gzFile gz_open(path, fd, mode) + const char *path; + int fd; + const char *mode; +{ + gzFile gz; + int ret; + + gz = malloc(sizeof(struct gzFile_s)); + if (gz == NULL) + return NULL; + gz->write = strchr(mode, 'w') != NULL; + gz->strm.zalloc = myalloc; + gz->strm.zfree = myfree; + gz->strm.opaque = Z_NULL; + if (gz->write) + ret = deflateInit2(&(gz->strm), -1, 8, 15 + 16, 8, 0); + else { + gz->strm.next_in = 0; + gz->strm.avail_in = Z_NULL; + ret = inflateInit2(&(gz->strm), 15 + 16); + } + if (ret != Z_OK) { + free(gz); + return NULL; + } + gz->file = path == NULL ? fdopen(fd, gz->write ? "wb" : "rb") : + fopen(path, gz->write ? "wb" : "rb"); + if (gz->file == NULL) { + gz->write ? deflateEnd(&(gz->strm)) : inflateEnd(&(gz->strm)); + free(gz); + return NULL; + } + gz->err = 0; + gz->msg = ""; + return gz; +} + +int gzwrite OF((gzFile, const void *, unsigned)); + +int gzwrite(gz, buf, len) + gzFile gz; + const void *buf; + unsigned len; +{ + z_stream *strm; + unsigned char out[BUFLEN]; + + if (gz == NULL || !gz->write) + return 0; + strm = &(gz->strm); + strm->next_in = (void *)buf; + strm->avail_in = len; + do { + strm->next_out = out; + strm->avail_out = BUFLEN; + (void)deflate(strm, Z_NO_FLUSH); + fwrite(out, 1, BUFLEN - strm->avail_out, gz->file); + } while (strm->avail_out == 0); + return len; +} + +int gzread OF((gzFile, void *, unsigned)); + +int gzread(gz, buf, len) + gzFile gz; + void *buf; + unsigned len; +{ + int ret; + unsigned got; + unsigned char in[1]; + z_stream *strm; + + if (gz == NULL || gz->write) + return 0; + if (gz->err) + return 0; + strm = &(gz->strm); + strm->next_out = (void *)buf; + strm->avail_out = len; + do { + got = fread(in, 1, 1, gz->file); + if (got == 0) + break; + strm->next_in = in; + strm->avail_in = 1; + ret = inflate(strm, Z_NO_FLUSH); + if (ret == Z_DATA_ERROR) { + gz->err = Z_DATA_ERROR; + gz->msg = strm->msg; + return 0; + } + if (ret == Z_STREAM_END) + inflateReset(strm); + } while (strm->avail_out); + return len - strm->avail_out; +} + +int gzclose OF((gzFile)); + +int gzclose(gz) + gzFile gz; +{ + z_stream *strm; + unsigned char out[BUFLEN]; + + if (gz == NULL) + return Z_STREAM_ERROR; + strm = &(gz->strm); + if (gz->write) { + strm->next_in = Z_NULL; + strm->avail_in = 0; + do { + strm->next_out = out; + strm->avail_out = BUFLEN; + (void)deflate(strm, Z_FINISH); + fwrite(out, 1, BUFLEN - strm->avail_out, gz->file); + } while (strm->avail_out == 0); + deflateEnd(strm); + } + else + inflateEnd(strm); + fclose(gz->file); + free(gz); + return Z_OK; +} + +const char *gzerror OF((gzFile, int *)); + +const char *gzerror(gz, err) + gzFile gz; + int *err; +{ + *err = gz->err; + return gz->msg; +} + +#endif + +static char *prog; + +void error OF((const char *msg)); +void gz_compress OF((FILE *in, gzFile out)); +#ifdef USE_MMAP +int gz_compress_mmap OF((FILE *in, gzFile out)); +#endif +void gz_uncompress OF((gzFile in, FILE *out)); +void file_compress OF((char *file, char *mode)); +void file_uncompress OF((char *file)); +int main OF((int argc, char *argv[])); + +/* =========================================================================== + * Display error message and exit + */ +void error(msg) + const char *msg; +{ + fprintf(stderr, "%s: %s\n", prog, msg); + exit(1); +} + +/* =========================================================================== + * Compress input to output then close both files. + */ + +void gz_compress(in, out) + FILE *in; + gzFile out; +{ + local char buf[BUFLEN]; + int len; + int err; + +#ifdef USE_MMAP + /* Try first compressing with mmap. If mmap fails (minigzip used in a + * pipe), use the normal fread loop. + */ + if (gz_compress_mmap(in, out) == Z_OK) return; +#endif + for (;;) { + len = (int)fread(buf, 1, sizeof(buf), in); + if (ferror(in)) { + perror("fread"); + exit(1); + } + if (len == 0) break; + + if (gzwrite(out, buf, (unsigned)len) != len) error(gzerror(out, &err)); + } + fclose(in); + if (gzclose(out) != Z_OK) error("failed gzclose"); +} + +#ifdef USE_MMAP /* MMAP version, Miguel Albrecht */ + +/* Try compressing the input file at once using mmap. Return Z_OK if + * if success, Z_ERRNO otherwise. + */ +int gz_compress_mmap(in, out) + FILE *in; + gzFile out; +{ + int len; + int err; + int ifd = fileno(in); + caddr_t buf; /* mmap'ed buffer for the entire input file */ + off_t buf_len; /* length of the input file */ + struct stat sb; + + /* Determine the size of the file, needed for mmap: */ + if (fstat(ifd, &sb) < 0) return Z_ERRNO; + buf_len = sb.st_size; + if (buf_len <= 0) return Z_ERRNO; + + /* Now do the actual mmap: */ + buf = mmap((caddr_t) 0, buf_len, PROT_READ, MAP_SHARED, ifd, (off_t)0); + if (buf == (caddr_t)(-1)) return Z_ERRNO; + + /* Compress the whole file at once: */ + len = gzwrite(out, (char *)buf, (unsigned)buf_len); + + if (len != (int)buf_len) error(gzerror(out, &err)); + + munmap(buf, buf_len); + fclose(in); + if (gzclose(out) != Z_OK) error("failed gzclose"); + return Z_OK; +} +#endif /* USE_MMAP */ + +/* =========================================================================== + * Uncompress input to output then close both files. + */ +void gz_uncompress(in, out) + gzFile in; + FILE *out; +{ + local char buf[BUFLEN]; + int len; + int err; + + for (;;) { + len = gzread(in, buf, sizeof(buf)); + if (len < 0) error (gzerror(in, &err)); + if (len == 0) break; + + if ((int)fwrite(buf, 1, (unsigned)len, out) != len) { + error("failed fwrite"); + } + } + if (fclose(out)) error("failed fclose"); + + if (gzclose(in) != Z_OK) error("failed gzclose"); +} + + +/* =========================================================================== + * Compress the given file: create a corresponding .gz file and remove the + * original. + */ +void file_compress(file, mode) + char *file; + char *mode; +{ + local char outfile[MAX_NAME_LEN]; + FILE *in; + gzFile out; + + if (strlen(file) + strlen(GZ_SUFFIX) >= sizeof(outfile)) { + fprintf(stderr, "%s: filename too long\n", prog); + exit(1); + } + +#if !defined(NO_snprintf) && !defined(NO_vsnprintf) + snprintf(outfile, sizeof(outfile), "%s%s", file, GZ_SUFFIX); +#else + strcpy(outfile, file); + strcat(outfile, GZ_SUFFIX); +#endif + + in = fopen(file, "rb"); + if (in == NULL) { + perror(file); + exit(1); + } + out = gzopen(outfile, mode); + if (out == NULL) { + fprintf(stderr, "%s: can't gzopen %s\n", prog, outfile); + exit(1); + } + gz_compress(in, out); + + unlink(file); +} + + +/* =========================================================================== + * Uncompress the given file and remove the original. + */ +void file_uncompress(file) + char *file; +{ + local char buf[MAX_NAME_LEN]; + char *infile, *outfile; + FILE *out; + gzFile in; + unsigned len = strlen(file); + + if (len + strlen(GZ_SUFFIX) >= sizeof(buf)) { + fprintf(stderr, "%s: filename too long\n", prog); + exit(1); + } + +#if !defined(NO_snprintf) && !defined(NO_vsnprintf) + snprintf(buf, sizeof(buf), "%s", file); +#else + strcpy(buf, file); +#endif + + if (len > SUFFIX_LEN && strcmp(file+len-SUFFIX_LEN, GZ_SUFFIX) == 0) { + infile = file; + outfile = buf; + outfile[len-3] = '\0'; + } else { + outfile = file; + infile = buf; +#if !defined(NO_snprintf) && !defined(NO_vsnprintf) + snprintf(buf + len, sizeof(buf) - len, "%s", GZ_SUFFIX); +#else + strcat(infile, GZ_SUFFIX); +#endif + } + in = gzopen(infile, "rb"); + if (in == NULL) { + fprintf(stderr, "%s: can't gzopen %s\n", prog, infile); + exit(1); + } + out = fopen(outfile, "wb"); + if (out == NULL) { + perror(file); + exit(1); + } + + gz_uncompress(in, out); + + unlink(infile); +} + + +/* =========================================================================== + * Usage: minigzip [-c] [-d] [-f] [-h] [-r] [-1 to -9] [files...] + * -c : write to standard output + * -d : decompress + * -f : compress with Z_FILTERED + * -h : compress with Z_HUFFMAN_ONLY + * -r : compress with Z_RLE + * -1 to -9 : compression level + */ + +int main(argc, argv) + int argc; + char *argv[]; +{ + int copyout = 0; + int uncompr = 0; + gzFile file; + char *bname, outmode[20]; + +#if !defined(NO_snprintf) && !defined(NO_vsnprintf) + snprintf(outmode, sizeof(outmode), "%s", "wb6 "); +#else + strcpy(outmode, "wb6 "); +#endif + + prog = argv[0]; + bname = strrchr(argv[0], '/'); + if (bname) + bname++; + else + bname = argv[0]; + argc--, argv++; + + if (!strcmp(bname, "gunzip")) + uncompr = 1; + else if (!strcmp(bname, "zcat")) + copyout = uncompr = 1; + + while (argc > 0) { + if (strcmp(*argv, "-c") == 0) + copyout = 1; + else if (strcmp(*argv, "-d") == 0) + uncompr = 1; + else if (strcmp(*argv, "-f") == 0) + outmode[3] = 'f'; + else if (strcmp(*argv, "-h") == 0) + outmode[3] = 'h'; + else if (strcmp(*argv, "-r") == 0) + outmode[3] = 'R'; + else if ((*argv)[0] == '-' && (*argv)[1] >= '1' && (*argv)[1] <= '9' && + (*argv)[2] == 0) + outmode[2] = (*argv)[1]; + else + break; + argc--, argv++; + } + if (outmode[3] == ' ') + outmode[3] = 0; + if (argc == 0) { + SET_BINARY_MODE(stdin); + SET_BINARY_MODE(stdout); + if (uncompr) { + file = gzdopen(fileno(stdin), "rb"); + if (file == NULL) error("can't gzdopen stdin"); + gz_uncompress(file, stdout); + } else { + file = gzdopen(fileno(stdout), outmode); + if (file == NULL) error("can't gzdopen stdout"); + gz_compress(stdin, file); + } + } else { + if (copyout) { + SET_BINARY_MODE(stdout); + } + do { + if (uncompr) { + if (copyout) { + file = gzopen(*argv, "rb"); + if (file == NULL) + fprintf(stderr, "%s: can't gzopen %s\n", prog, *argv); + else + gz_uncompress(file, stdout); + } else { + file_uncompress(*argv); + } + } else { + if (copyout) { + FILE * in = fopen(*argv, "rb"); + + if (in == NULL) { + perror(*argv); + } else { + file = gzdopen(fileno(stdout), outmode); + if (file == NULL) error("can't gzdopen stdout"); + + gz_compress(in, file); + } + + } else { + file_compress(*argv, outmode); + } + } + } while (argv++, --argc); + } + return 0; +} ADDED compat/zlib/treebuild.xml Index: compat/zlib/treebuild.xml ================================================================== --- /dev/null +++ compat/zlib/treebuild.xml @@ -0,0 +1,116 @@ + + + + zip compression library + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + ADDED compat/zlib/trees.c Index: compat/zlib/trees.c ================================================================== --- /dev/null +++ compat/zlib/trees.c @@ -0,0 +1,1203 @@ +/* trees.c -- output deflated data using Huffman coding + * Copyright (C) 1995-2017 Jean-loup Gailly + * detect_data_type() function provided freely by Cosmin Truta, 2006 + * For conditions of distribution and use, see copyright notice in zlib.h + */ + +/* + * ALGORITHM + * + * The "deflation" process uses several Huffman trees. The more + * common source values are represented by shorter bit sequences. + * + * Each code tree is stored in a compressed form which is itself + * a Huffman encoding of the lengths of all the code strings (in + * ascending order by source values). The actual code strings are + * reconstructed from the lengths in the inflate process, as described + * in the deflate specification. + * + * REFERENCES + * + * Deutsch, L.P.,"'Deflate' Compressed Data Format Specification". + * Available in ftp.uu.net:/pub/archiving/zip/doc/deflate-1.1.doc + * + * Storer, James A. + * Data Compression: Methods and Theory, pp. 49-50. + * Computer Science Press, 1988. ISBN 0-7167-8156-5. + * + * Sedgewick, R. + * Algorithms, p290. + * Addison-Wesley, 1983. ISBN 0-201-06672-6. + */ + +/* @(#) $Id$ */ + +/* #define GEN_TREES_H */ + +#include "deflate.h" + +#ifdef ZLIB_DEBUG +# include +#endif + +/* =========================================================================== + * Constants + */ + +#define MAX_BL_BITS 7 +/* Bit length codes must not exceed MAX_BL_BITS bits */ + +#define END_BLOCK 256 +/* end of block literal code */ + +#define REP_3_6 16 +/* repeat previous bit length 3-6 times (2 bits of repeat count) */ + +#define REPZ_3_10 17 +/* repeat a zero length 3-10 times (3 bits of repeat count) */ + +#define REPZ_11_138 18 +/* repeat a zero length 11-138 times (7 bits of repeat count) */ + +local const int extra_lbits[LENGTH_CODES] /* extra bits for each length code */ + = {0,0,0,0,0,0,0,0,1,1,1,1,2,2,2,2,3,3,3,3,4,4,4,4,5,5,5,5,0}; + +local const int extra_dbits[D_CODES] /* extra bits for each distance code */ + = {0,0,0,0,1,1,2,2,3,3,4,4,5,5,6,6,7,7,8,8,9,9,10,10,11,11,12,12,13,13}; + +local const int extra_blbits[BL_CODES]/* extra bits for each bit length code */ + = {0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,2,3,7}; + +local const uch bl_order[BL_CODES] + = {16,17,18,0,8,7,9,6,10,5,11,4,12,3,13,2,14,1,15}; +/* The lengths of the bit length codes are sent in order of decreasing + * probability, to avoid transmitting the lengths for unused bit length codes. + */ + +/* =========================================================================== + * Local data. These are initialized only once. + */ + +#define DIST_CODE_LEN 512 /* see definition of array dist_code below */ + +#if defined(GEN_TREES_H) || !defined(STDC) +/* non ANSI compilers may not accept trees.h */ + +local ct_data static_ltree[L_CODES+2]; +/* The static literal tree. Since the bit lengths are imposed, there is no + * need for the L_CODES extra codes used during heap construction. However + * The codes 286 and 287 are needed to build a canonical tree (see _tr_init + * below). + */ + +local ct_data static_dtree[D_CODES]; +/* The static distance tree. (Actually a trivial tree since all codes use + * 5 bits.) + */ + +uch _dist_code[DIST_CODE_LEN]; +/* Distance codes. The first 256 values correspond to the distances + * 3 .. 258, the last 256 values correspond to the top 8 bits of + * the 15 bit distances. + */ + +uch _length_code[MAX_MATCH-MIN_MATCH+1]; +/* length code for each normalized match length (0 == MIN_MATCH) */ + +local int base_length[LENGTH_CODES]; +/* First normalized length for each code (0 = MIN_MATCH) */ + +local int base_dist[D_CODES]; +/* First normalized distance for each code (0 = distance of 1) */ + +#else +# include "trees.h" +#endif /* GEN_TREES_H */ + +struct static_tree_desc_s { + const ct_data *static_tree; /* static tree or NULL */ + const intf *extra_bits; /* extra bits for each code or NULL */ + int extra_base; /* base index for extra_bits */ + int elems; /* max number of elements in the tree */ + int max_length; /* max bit length for the codes */ +}; + +local const static_tree_desc static_l_desc = +{static_ltree, extra_lbits, LITERALS+1, L_CODES, MAX_BITS}; + +local const static_tree_desc static_d_desc = +{static_dtree, extra_dbits, 0, D_CODES, MAX_BITS}; + +local const static_tree_desc static_bl_desc = +{(const ct_data *)0, extra_blbits, 0, BL_CODES, MAX_BL_BITS}; + +/* =========================================================================== + * Local (static) routines in this file. + */ + +local void tr_static_init OF((void)); +local void init_block OF((deflate_state *s)); +local void pqdownheap OF((deflate_state *s, ct_data *tree, int k)); +local void gen_bitlen OF((deflate_state *s, tree_desc *desc)); +local void gen_codes OF((ct_data *tree, int max_code, ushf *bl_count)); +local void build_tree OF((deflate_state *s, tree_desc *desc)); +local void scan_tree OF((deflate_state *s, ct_data *tree, int max_code)); +local void send_tree OF((deflate_state *s, ct_data *tree, int max_code)); +local int build_bl_tree OF((deflate_state *s)); +local void send_all_trees OF((deflate_state *s, int lcodes, int dcodes, + int blcodes)); +local void compress_block OF((deflate_state *s, const ct_data *ltree, + const ct_data *dtree)); +local int detect_data_type OF((deflate_state *s)); +local unsigned bi_reverse OF((unsigned value, int length)); +local void bi_windup OF((deflate_state *s)); +local void bi_flush OF((deflate_state *s)); + +#ifdef GEN_TREES_H +local void gen_trees_header OF((void)); +#endif + +#ifndef ZLIB_DEBUG +# define send_code(s, c, tree) send_bits(s, tree[c].Code, tree[c].Len) + /* Send a code of the given tree. c and tree must not have side effects */ + +#else /* !ZLIB_DEBUG */ +# define send_code(s, c, tree) \ + { if (z_verbose>2) fprintf(stderr,"\ncd %3d ",(c)); \ + send_bits(s, tree[c].Code, tree[c].Len); } +#endif + +/* =========================================================================== + * Output a short LSB first on the stream. + * IN assertion: there is enough room in pendingBuf. + */ +#define put_short(s, w) { \ + put_byte(s, (uch)((w) & 0xff)); \ + put_byte(s, (uch)((ush)(w) >> 8)); \ +} + +/* =========================================================================== + * Send a value on a given number of bits. + * IN assertion: length <= 16 and value fits in length bits. + */ +#ifdef ZLIB_DEBUG +local void send_bits OF((deflate_state *s, int value, int length)); + +local void send_bits(s, value, length) + deflate_state *s; + int value; /* value to send */ + int length; /* number of bits */ +{ + Tracevv((stderr," l %2d v %4x ", length, value)); + Assert(length > 0 && length <= 15, "invalid length"); + s->bits_sent += (ulg)length; + + /* If not enough room in bi_buf, use (valid) bits from bi_buf and + * (16 - bi_valid) bits from value, leaving (width - (16-bi_valid)) + * unused bits in value. + */ + if (s->bi_valid > (int)Buf_size - length) { + s->bi_buf |= (ush)value << s->bi_valid; + put_short(s, s->bi_buf); + s->bi_buf = (ush)value >> (Buf_size - s->bi_valid); + s->bi_valid += length - Buf_size; + } else { + s->bi_buf |= (ush)value << s->bi_valid; + s->bi_valid += length; + } +} +#else /* !ZLIB_DEBUG */ + +#define send_bits(s, value, length) \ +{ int len = length;\ + if (s->bi_valid > (int)Buf_size - len) {\ + int val = (int)value;\ + s->bi_buf |= (ush)val << s->bi_valid;\ + put_short(s, s->bi_buf);\ + s->bi_buf = (ush)val >> (Buf_size - s->bi_valid);\ + s->bi_valid += len - Buf_size;\ + } else {\ + s->bi_buf |= (ush)(value) << s->bi_valid;\ + s->bi_valid += len;\ + }\ +} +#endif /* ZLIB_DEBUG */ + + +/* the arguments must not have side effects */ + +/* =========================================================================== + * Initialize the various 'constant' tables. + */ +local void tr_static_init() +{ +#if defined(GEN_TREES_H) || !defined(STDC) + static int static_init_done = 0; + int n; /* iterates over tree elements */ + int bits; /* bit counter */ + int length; /* length value */ + int code; /* code value */ + int dist; /* distance index */ + ush bl_count[MAX_BITS+1]; + /* number of codes at each bit length for an optimal tree */ + + if (static_init_done) return; + + /* For some embedded targets, global variables are not initialized: */ +#ifdef NO_INIT_GLOBAL_POINTERS + static_l_desc.static_tree = static_ltree; + static_l_desc.extra_bits = extra_lbits; + static_d_desc.static_tree = static_dtree; + static_d_desc.extra_bits = extra_dbits; + static_bl_desc.extra_bits = extra_blbits; +#endif + + /* Initialize the mapping length (0..255) -> length code (0..28) */ + length = 0; + for (code = 0; code < LENGTH_CODES-1; code++) { + base_length[code] = length; + for (n = 0; n < (1< dist code (0..29) */ + dist = 0; + for (code = 0 ; code < 16; code++) { + base_dist[code] = dist; + for (n = 0; n < (1<>= 7; /* from now on, all distances are divided by 128 */ + for ( ; code < D_CODES; code++) { + base_dist[code] = dist << 7; + for (n = 0; n < (1<<(extra_dbits[code]-7)); n++) { + _dist_code[256 + dist++] = (uch)code; + } + } + Assert (dist == 256, "tr_static_init: 256+dist != 512"); + + /* Construct the codes of the static literal tree */ + for (bits = 0; bits <= MAX_BITS; bits++) bl_count[bits] = 0; + n = 0; + while (n <= 143) static_ltree[n++].Len = 8, bl_count[8]++; + while (n <= 255) static_ltree[n++].Len = 9, bl_count[9]++; + while (n <= 279) static_ltree[n++].Len = 7, bl_count[7]++; + while (n <= 287) static_ltree[n++].Len = 8, bl_count[8]++; + /* Codes 286 and 287 do not exist, but we must include them in the + * tree construction to get a canonical Huffman tree (longest code + * all ones) + */ + gen_codes((ct_data *)static_ltree, L_CODES+1, bl_count); + + /* The static distance tree is trivial: */ + for (n = 0; n < D_CODES; n++) { + static_dtree[n].Len = 5; + static_dtree[n].Code = bi_reverse((unsigned)n, 5); + } + static_init_done = 1; + +# ifdef GEN_TREES_H + gen_trees_header(); +# endif +#endif /* defined(GEN_TREES_H) || !defined(STDC) */ +} + +/* =========================================================================== + * Genererate the file trees.h describing the static trees. + */ +#ifdef GEN_TREES_H +# ifndef ZLIB_DEBUG +# include +# endif + +# define SEPARATOR(i, last, width) \ + ((i) == (last)? "\n};\n\n" : \ + ((i) % (width) == (width)-1 ? ",\n" : ", ")) + +void gen_trees_header() +{ + FILE *header = fopen("trees.h", "w"); + int i; + + Assert (header != NULL, "Can't open trees.h"); + fprintf(header, + "/* header created automatically with -DGEN_TREES_H */\n\n"); + + fprintf(header, "local const ct_data static_ltree[L_CODES+2] = {\n"); + for (i = 0; i < L_CODES+2; i++) { + fprintf(header, "{{%3u},{%3u}}%s", static_ltree[i].Code, + static_ltree[i].Len, SEPARATOR(i, L_CODES+1, 5)); + } + + fprintf(header, "local const ct_data static_dtree[D_CODES] = {\n"); + for (i = 0; i < D_CODES; i++) { + fprintf(header, "{{%2u},{%2u}}%s", static_dtree[i].Code, + static_dtree[i].Len, SEPARATOR(i, D_CODES-1, 5)); + } + + fprintf(header, "const uch ZLIB_INTERNAL _dist_code[DIST_CODE_LEN] = {\n"); + for (i = 0; i < DIST_CODE_LEN; i++) { + fprintf(header, "%2u%s", _dist_code[i], + SEPARATOR(i, DIST_CODE_LEN-1, 20)); + } + + fprintf(header, + "const uch ZLIB_INTERNAL _length_code[MAX_MATCH-MIN_MATCH+1]= {\n"); + for (i = 0; i < MAX_MATCH-MIN_MATCH+1; i++) { + fprintf(header, "%2u%s", _length_code[i], + SEPARATOR(i, MAX_MATCH-MIN_MATCH, 20)); + } + + fprintf(header, "local const int base_length[LENGTH_CODES] = {\n"); + for (i = 0; i < LENGTH_CODES; i++) { + fprintf(header, "%1u%s", base_length[i], + SEPARATOR(i, LENGTH_CODES-1, 20)); + } + + fprintf(header, "local const int base_dist[D_CODES] = {\n"); + for (i = 0; i < D_CODES; i++) { + fprintf(header, "%5u%s", base_dist[i], + SEPARATOR(i, D_CODES-1, 10)); + } + + fclose(header); +} +#endif /* GEN_TREES_H */ + +/* =========================================================================== + * Initialize the tree data structures for a new zlib stream. + */ +void ZLIB_INTERNAL _tr_init(s) + deflate_state *s; +{ + tr_static_init(); + + s->l_desc.dyn_tree = s->dyn_ltree; + s->l_desc.stat_desc = &static_l_desc; + + s->d_desc.dyn_tree = s->dyn_dtree; + s->d_desc.stat_desc = &static_d_desc; + + s->bl_desc.dyn_tree = s->bl_tree; + s->bl_desc.stat_desc = &static_bl_desc; + + s->bi_buf = 0; + s->bi_valid = 0; +#ifdef ZLIB_DEBUG + s->compressed_len = 0L; + s->bits_sent = 0L; +#endif + + /* Initialize the first block of the first file: */ + init_block(s); +} + +/* =========================================================================== + * Initialize a new block. + */ +local void init_block(s) + deflate_state *s; +{ + int n; /* iterates over tree elements */ + + /* Initialize the trees. */ + for (n = 0; n < L_CODES; n++) s->dyn_ltree[n].Freq = 0; + for (n = 0; n < D_CODES; n++) s->dyn_dtree[n].Freq = 0; + for (n = 0; n < BL_CODES; n++) s->bl_tree[n].Freq = 0; + + s->dyn_ltree[END_BLOCK].Freq = 1; + s->opt_len = s->static_len = 0L; + s->last_lit = s->matches = 0; +} + +#define SMALLEST 1 +/* Index within the heap array of least frequent node in the Huffman tree */ + + +/* =========================================================================== + * Remove the smallest element from the heap and recreate the heap with + * one less element. Updates heap and heap_len. + */ +#define pqremove(s, tree, top) \ +{\ + top = s->heap[SMALLEST]; \ + s->heap[SMALLEST] = s->heap[s->heap_len--]; \ + pqdownheap(s, tree, SMALLEST); \ +} + +/* =========================================================================== + * Compares to subtrees, using the tree depth as tie breaker when + * the subtrees have equal frequency. This minimizes the worst case length. + */ +#define smaller(tree, n, m, depth) \ + (tree[n].Freq < tree[m].Freq || \ + (tree[n].Freq == tree[m].Freq && depth[n] <= depth[m])) + +/* =========================================================================== + * Restore the heap property by moving down the tree starting at node k, + * exchanging a node with the smallest of its two sons if necessary, stopping + * when the heap property is re-established (each father smaller than its + * two sons). + */ +local void pqdownheap(s, tree, k) + deflate_state *s; + ct_data *tree; /* the tree to restore */ + int k; /* node to move down */ +{ + int v = s->heap[k]; + int j = k << 1; /* left son of k */ + while (j <= s->heap_len) { + /* Set j to the smallest of the two sons: */ + if (j < s->heap_len && + smaller(tree, s->heap[j+1], s->heap[j], s->depth)) { + j++; + } + /* Exit if v is smaller than both sons */ + if (smaller(tree, v, s->heap[j], s->depth)) break; + + /* Exchange v with the smallest son */ + s->heap[k] = s->heap[j]; k = j; + + /* And continue down the tree, setting j to the left son of k */ + j <<= 1; + } + s->heap[k] = v; +} + +/* =========================================================================== + * Compute the optimal bit lengths for a tree and update the total bit length + * for the current block. + * IN assertion: the fields freq and dad are set, heap[heap_max] and + * above are the tree nodes sorted by increasing frequency. + * OUT assertions: the field len is set to the optimal bit length, the + * array bl_count contains the frequencies for each bit length. + * The length opt_len is updated; static_len is also updated if stree is + * not null. + */ +local void gen_bitlen(s, desc) + deflate_state *s; + tree_desc *desc; /* the tree descriptor */ +{ + ct_data *tree = desc->dyn_tree; + int max_code = desc->max_code; + const ct_data *stree = desc->stat_desc->static_tree; + const intf *extra = desc->stat_desc->extra_bits; + int base = desc->stat_desc->extra_base; + int max_length = desc->stat_desc->max_length; + int h; /* heap index */ + int n, m; /* iterate over the tree elements */ + int bits; /* bit length */ + int xbits; /* extra bits */ + ush f; /* frequency */ + int overflow = 0; /* number of elements with bit length too large */ + + for (bits = 0; bits <= MAX_BITS; bits++) s->bl_count[bits] = 0; + + /* In a first pass, compute the optimal bit lengths (which may + * overflow in the case of the bit length tree). + */ + tree[s->heap[s->heap_max]].Len = 0; /* root of the heap */ + + for (h = s->heap_max+1; h < HEAP_SIZE; h++) { + n = s->heap[h]; + bits = tree[tree[n].Dad].Len + 1; + if (bits > max_length) bits = max_length, overflow++; + tree[n].Len = (ush)bits; + /* We overwrite tree[n].Dad which is no longer needed */ + + if (n > max_code) continue; /* not a leaf node */ + + s->bl_count[bits]++; + xbits = 0; + if (n >= base) xbits = extra[n-base]; + f = tree[n].Freq; + s->opt_len += (ulg)f * (unsigned)(bits + xbits); + if (stree) s->static_len += (ulg)f * (unsigned)(stree[n].Len + xbits); + } + if (overflow == 0) return; + + Tracev((stderr,"\nbit length overflow\n")); + /* This happens for example on obj2 and pic of the Calgary corpus */ + + /* Find the first bit length which could increase: */ + do { + bits = max_length-1; + while (s->bl_count[bits] == 0) bits--; + s->bl_count[bits]--; /* move one leaf down the tree */ + s->bl_count[bits+1] += 2; /* move one overflow item as its brother */ + s->bl_count[max_length]--; + /* The brother of the overflow item also moves one step up, + * but this does not affect bl_count[max_length] + */ + overflow -= 2; + } while (overflow > 0); + + /* Now recompute all bit lengths, scanning in increasing frequency. + * h is still equal to HEAP_SIZE. (It is simpler to reconstruct all + * lengths instead of fixing only the wrong ones. This idea is taken + * from 'ar' written by Haruhiko Okumura.) + */ + for (bits = max_length; bits != 0; bits--) { + n = s->bl_count[bits]; + while (n != 0) { + m = s->heap[--h]; + if (m > max_code) continue; + if ((unsigned) tree[m].Len != (unsigned) bits) { + Tracev((stderr,"code %d bits %d->%d\n", m, tree[m].Len, bits)); + s->opt_len += ((ulg)bits - tree[m].Len) * tree[m].Freq; + tree[m].Len = (ush)bits; + } + n--; + } + } +} + +/* =========================================================================== + * Generate the codes for a given tree and bit counts (which need not be + * optimal). + * IN assertion: the array bl_count contains the bit length statistics for + * the given tree and the field len is set for all tree elements. + * OUT assertion: the field code is set for all tree elements of non + * zero code length. + */ +local void gen_codes (tree, max_code, bl_count) + ct_data *tree; /* the tree to decorate */ + int max_code; /* largest code with non zero frequency */ + ushf *bl_count; /* number of codes at each bit length */ +{ + ush next_code[MAX_BITS+1]; /* next code value for each bit length */ + unsigned code = 0; /* running code value */ + int bits; /* bit index */ + int n; /* code index */ + + /* The distribution counts are first used to generate the code values + * without bit reversal. + */ + for (bits = 1; bits <= MAX_BITS; bits++) { + code = (code + bl_count[bits-1]) << 1; + next_code[bits] = (ush)code; + } + /* Check that the bit counts in bl_count are consistent. The last code + * must be all ones. + */ + Assert (code + bl_count[MAX_BITS]-1 == (1<dyn_tree; + const ct_data *stree = desc->stat_desc->static_tree; + int elems = desc->stat_desc->elems; + int n, m; /* iterate over heap elements */ + int max_code = -1; /* largest code with non zero frequency */ + int node; /* new node being created */ + + /* Construct the initial heap, with least frequent element in + * heap[SMALLEST]. The sons of heap[n] are heap[2*n] and heap[2*n+1]. + * heap[0] is not used. + */ + s->heap_len = 0, s->heap_max = HEAP_SIZE; + + for (n = 0; n < elems; n++) { + if (tree[n].Freq != 0) { + s->heap[++(s->heap_len)] = max_code = n; + s->depth[n] = 0; + } else { + tree[n].Len = 0; + } + } + + /* The pkzip format requires that at least one distance code exists, + * and that at least one bit should be sent even if there is only one + * possible code. So to avoid special checks later on we force at least + * two codes of non zero frequency. + */ + while (s->heap_len < 2) { + node = s->heap[++(s->heap_len)] = (max_code < 2 ? ++max_code : 0); + tree[node].Freq = 1; + s->depth[node] = 0; + s->opt_len--; if (stree) s->static_len -= stree[node].Len; + /* node is 0 or 1 so it does not have extra bits */ + } + desc->max_code = max_code; + + /* The elements heap[heap_len/2+1 .. heap_len] are leaves of the tree, + * establish sub-heaps of increasing lengths: + */ + for (n = s->heap_len/2; n >= 1; n--) pqdownheap(s, tree, n); + + /* Construct the Huffman tree by repeatedly combining the least two + * frequent nodes. + */ + node = elems; /* next internal node of the tree */ + do { + pqremove(s, tree, n); /* n = node of least frequency */ + m = s->heap[SMALLEST]; /* m = node of next least frequency */ + + s->heap[--(s->heap_max)] = n; /* keep the nodes sorted by frequency */ + s->heap[--(s->heap_max)] = m; + + /* Create a new node father of n and m */ + tree[node].Freq = tree[n].Freq + tree[m].Freq; + s->depth[node] = (uch)((s->depth[n] >= s->depth[m] ? + s->depth[n] : s->depth[m]) + 1); + tree[n].Dad = tree[m].Dad = (ush)node; +#ifdef DUMP_BL_TREE + if (tree == s->bl_tree) { + fprintf(stderr,"\nnode %d(%d), sons %d(%d) %d(%d)", + node, tree[node].Freq, n, tree[n].Freq, m, tree[m].Freq); + } +#endif + /* and insert the new node in the heap */ + s->heap[SMALLEST] = node++; + pqdownheap(s, tree, SMALLEST); + + } while (s->heap_len >= 2); + + s->heap[--(s->heap_max)] = s->heap[SMALLEST]; + + /* At this point, the fields freq and dad are set. We can now + * generate the bit lengths. + */ + gen_bitlen(s, (tree_desc *)desc); + + /* The field len is now set, we can generate the bit codes */ + gen_codes ((ct_data *)tree, max_code, s->bl_count); +} + +/* =========================================================================== + * Scan a literal or distance tree to determine the frequencies of the codes + * in the bit length tree. + */ +local void scan_tree (s, tree, max_code) + deflate_state *s; + ct_data *tree; /* the tree to be scanned */ + int max_code; /* and its largest code of non zero frequency */ +{ + int n; /* iterates over all tree elements */ + int prevlen = -1; /* last emitted length */ + int curlen; /* length of current code */ + int nextlen = tree[0].Len; /* length of next code */ + int count = 0; /* repeat count of the current code */ + int max_count = 7; /* max repeat count */ + int min_count = 4; /* min repeat count */ + + if (nextlen == 0) max_count = 138, min_count = 3; + tree[max_code+1].Len = (ush)0xffff; /* guard */ + + for (n = 0; n <= max_code; n++) { + curlen = nextlen; nextlen = tree[n+1].Len; + if (++count < max_count && curlen == nextlen) { + continue; + } else if (count < min_count) { + s->bl_tree[curlen].Freq += count; + } else if (curlen != 0) { + if (curlen != prevlen) s->bl_tree[curlen].Freq++; + s->bl_tree[REP_3_6].Freq++; + } else if (count <= 10) { + s->bl_tree[REPZ_3_10].Freq++; + } else { + s->bl_tree[REPZ_11_138].Freq++; + } + count = 0; prevlen = curlen; + if (nextlen == 0) { + max_count = 138, min_count = 3; + } else if (curlen == nextlen) { + max_count = 6, min_count = 3; + } else { + max_count = 7, min_count = 4; + } + } +} + +/* =========================================================================== + * Send a literal or distance tree in compressed form, using the codes in + * bl_tree. + */ +local void send_tree (s, tree, max_code) + deflate_state *s; + ct_data *tree; /* the tree to be scanned */ + int max_code; /* and its largest code of non zero frequency */ +{ + int n; /* iterates over all tree elements */ + int prevlen = -1; /* last emitted length */ + int curlen; /* length of current code */ + int nextlen = tree[0].Len; /* length of next code */ + int count = 0; /* repeat count of the current code */ + int max_count = 7; /* max repeat count */ + int min_count = 4; /* min repeat count */ + + /* tree[max_code+1].Len = -1; */ /* guard already set */ + if (nextlen == 0) max_count = 138, min_count = 3; + + for (n = 0; n <= max_code; n++) { + curlen = nextlen; nextlen = tree[n+1].Len; + if (++count < max_count && curlen == nextlen) { + continue; + } else if (count < min_count) { + do { send_code(s, curlen, s->bl_tree); } while (--count != 0); + + } else if (curlen != 0) { + if (curlen != prevlen) { + send_code(s, curlen, s->bl_tree); count--; + } + Assert(count >= 3 && count <= 6, " 3_6?"); + send_code(s, REP_3_6, s->bl_tree); send_bits(s, count-3, 2); + + } else if (count <= 10) { + send_code(s, REPZ_3_10, s->bl_tree); send_bits(s, count-3, 3); + + } else { + send_code(s, REPZ_11_138, s->bl_tree); send_bits(s, count-11, 7); + } + count = 0; prevlen = curlen; + if (nextlen == 0) { + max_count = 138, min_count = 3; + } else if (curlen == nextlen) { + max_count = 6, min_count = 3; + } else { + max_count = 7, min_count = 4; + } + } +} + +/* =========================================================================== + * Construct the Huffman tree for the bit lengths and return the index in + * bl_order of the last bit length code to send. + */ +local int build_bl_tree(s) + deflate_state *s; +{ + int max_blindex; /* index of last bit length code of non zero freq */ + + /* Determine the bit length frequencies for literal and distance trees */ + scan_tree(s, (ct_data *)s->dyn_ltree, s->l_desc.max_code); + scan_tree(s, (ct_data *)s->dyn_dtree, s->d_desc.max_code); + + /* Build the bit length tree: */ + build_tree(s, (tree_desc *)(&(s->bl_desc))); + /* opt_len now includes the length of the tree representations, except + * the lengths of the bit lengths codes and the 5+5+4 bits for the counts. + */ + + /* Determine the number of bit length codes to send. The pkzip format + * requires that at least 4 bit length codes be sent. (appnote.txt says + * 3 but the actual value used is 4.) + */ + for (max_blindex = BL_CODES-1; max_blindex >= 3; max_blindex--) { + if (s->bl_tree[bl_order[max_blindex]].Len != 0) break; + } + /* Update opt_len to include the bit length tree and counts */ + s->opt_len += 3*((ulg)max_blindex+1) + 5+5+4; + Tracev((stderr, "\ndyn trees: dyn %ld, stat %ld", + s->opt_len, s->static_len)); + + return max_blindex; +} + +/* =========================================================================== + * Send the header for a block using dynamic Huffman trees: the counts, the + * lengths of the bit length codes, the literal tree and the distance tree. + * IN assertion: lcodes >= 257, dcodes >= 1, blcodes >= 4. + */ +local void send_all_trees(s, lcodes, dcodes, blcodes) + deflate_state *s; + int lcodes, dcodes, blcodes; /* number of codes for each tree */ +{ + int rank; /* index in bl_order */ + + Assert (lcodes >= 257 && dcodes >= 1 && blcodes >= 4, "not enough codes"); + Assert (lcodes <= L_CODES && dcodes <= D_CODES && blcodes <= BL_CODES, + "too many codes"); + Tracev((stderr, "\nbl counts: ")); + send_bits(s, lcodes-257, 5); /* not +255 as stated in appnote.txt */ + send_bits(s, dcodes-1, 5); + send_bits(s, blcodes-4, 4); /* not -3 as stated in appnote.txt */ + for (rank = 0; rank < blcodes; rank++) { + Tracev((stderr, "\nbl code %2d ", bl_order[rank])); + send_bits(s, s->bl_tree[bl_order[rank]].Len, 3); + } + Tracev((stderr, "\nbl tree: sent %ld", s->bits_sent)); + + send_tree(s, (ct_data *)s->dyn_ltree, lcodes-1); /* literal tree */ + Tracev((stderr, "\nlit tree: sent %ld", s->bits_sent)); + + send_tree(s, (ct_data *)s->dyn_dtree, dcodes-1); /* distance tree */ + Tracev((stderr, "\ndist tree: sent %ld", s->bits_sent)); +} + +/* =========================================================================== + * Send a stored block + */ +void ZLIB_INTERNAL _tr_stored_block(s, buf, stored_len, last) + deflate_state *s; + charf *buf; /* input block */ + ulg stored_len; /* length of input block */ + int last; /* one if this is the last block for a file */ +{ + send_bits(s, (STORED_BLOCK<<1)+last, 3); /* send block type */ + bi_windup(s); /* align on byte boundary */ + put_short(s, (ush)stored_len); + put_short(s, (ush)~stored_len); + zmemcpy(s->pending_buf + s->pending, (Bytef *)buf, stored_len); + s->pending += stored_len; +#ifdef ZLIB_DEBUG + s->compressed_len = (s->compressed_len + 3 + 7) & (ulg)~7L; + s->compressed_len += (stored_len + 4) << 3; + s->bits_sent += 2*16; + s->bits_sent += stored_len<<3; +#endif +} + +/* =========================================================================== + * Flush the bits in the bit buffer to pending output (leaves at most 7 bits) + */ +void ZLIB_INTERNAL _tr_flush_bits(s) + deflate_state *s; +{ + bi_flush(s); +} + +/* =========================================================================== + * Send one empty static block to give enough lookahead for inflate. + * This takes 10 bits, of which 7 may remain in the bit buffer. + */ +void ZLIB_INTERNAL _tr_align(s) + deflate_state *s; +{ + send_bits(s, STATIC_TREES<<1, 3); + send_code(s, END_BLOCK, static_ltree); +#ifdef ZLIB_DEBUG + s->compressed_len += 10L; /* 3 for block type, 7 for EOB */ +#endif + bi_flush(s); +} + +/* =========================================================================== + * Determine the best encoding for the current block: dynamic trees, static + * trees or store, and write out the encoded block. + */ +void ZLIB_INTERNAL _tr_flush_block(s, buf, stored_len, last) + deflate_state *s; + charf *buf; /* input block, or NULL if too old */ + ulg stored_len; /* length of input block */ + int last; /* one if this is the last block for a file */ +{ + ulg opt_lenb, static_lenb; /* opt_len and static_len in bytes */ + int max_blindex = 0; /* index of last bit length code of non zero freq */ + + /* Build the Huffman trees unless a stored block is forced */ + if (s->level > 0) { + + /* Check if the file is binary or text */ + if (s->strm->data_type == Z_UNKNOWN) + s->strm->data_type = detect_data_type(s); + + /* Construct the literal and distance trees */ + build_tree(s, (tree_desc *)(&(s->l_desc))); + Tracev((stderr, "\nlit data: dyn %ld, stat %ld", s->opt_len, + s->static_len)); + + build_tree(s, (tree_desc *)(&(s->d_desc))); + Tracev((stderr, "\ndist data: dyn %ld, stat %ld", s->opt_len, + s->static_len)); + /* At this point, opt_len and static_len are the total bit lengths of + * the compressed block data, excluding the tree representations. + */ + + /* Build the bit length tree for the above two trees, and get the index + * in bl_order of the last bit length code to send. + */ + max_blindex = build_bl_tree(s); + + /* Determine the best encoding. Compute the block lengths in bytes. */ + opt_lenb = (s->opt_len+3+7)>>3; + static_lenb = (s->static_len+3+7)>>3; + + Tracev((stderr, "\nopt %lu(%lu) stat %lu(%lu) stored %lu lit %u ", + opt_lenb, s->opt_len, static_lenb, s->static_len, stored_len, + s->last_lit)); + + if (static_lenb <= opt_lenb) opt_lenb = static_lenb; + + } else { + Assert(buf != (char*)0, "lost buf"); + opt_lenb = static_lenb = stored_len + 5; /* force a stored block */ + } + +#ifdef FORCE_STORED + if (buf != (char*)0) { /* force stored block */ +#else + if (stored_len+4 <= opt_lenb && buf != (char*)0) { + /* 4: two words for the lengths */ +#endif + /* The test buf != NULL is only necessary if LIT_BUFSIZE > WSIZE. + * Otherwise we can't have processed more than WSIZE input bytes since + * the last block flush, because compression would have been + * successful. If LIT_BUFSIZE <= WSIZE, it is never too late to + * transform a block into a stored block. + */ + _tr_stored_block(s, buf, stored_len, last); + +#ifdef FORCE_STATIC + } else if (static_lenb >= 0) { /* force static trees */ +#else + } else if (s->strategy == Z_FIXED || static_lenb == opt_lenb) { +#endif + send_bits(s, (STATIC_TREES<<1)+last, 3); + compress_block(s, (const ct_data *)static_ltree, + (const ct_data *)static_dtree); +#ifdef ZLIB_DEBUG + s->compressed_len += 3 + s->static_len; +#endif + } else { + send_bits(s, (DYN_TREES<<1)+last, 3); + send_all_trees(s, s->l_desc.max_code+1, s->d_desc.max_code+1, + max_blindex+1); + compress_block(s, (const ct_data *)s->dyn_ltree, + (const ct_data *)s->dyn_dtree); +#ifdef ZLIB_DEBUG + s->compressed_len += 3 + s->opt_len; +#endif + } + Assert (s->compressed_len == s->bits_sent, "bad compressed size"); + /* The above check is made mod 2^32, for files larger than 512 MB + * and uLong implemented on 32 bits. + */ + init_block(s); + + if (last) { + bi_windup(s); +#ifdef ZLIB_DEBUG + s->compressed_len += 7; /* align on byte boundary */ +#endif + } + Tracev((stderr,"\ncomprlen %lu(%lu) ", s->compressed_len>>3, + s->compressed_len-7*last)); +} + +/* =========================================================================== + * Save the match info and tally the frequency counts. Return true if + * the current block must be flushed. + */ +int ZLIB_INTERNAL _tr_tally (s, dist, lc) + deflate_state *s; + unsigned dist; /* distance of matched string */ + unsigned lc; /* match length-MIN_MATCH or unmatched char (if dist==0) */ +{ + s->d_buf[s->last_lit] = (ush)dist; + s->l_buf[s->last_lit++] = (uch)lc; + if (dist == 0) { + /* lc is the unmatched char */ + s->dyn_ltree[lc].Freq++; + } else { + s->matches++; + /* Here, lc is the match length - MIN_MATCH */ + dist--; /* dist = match distance - 1 */ + Assert((ush)dist < (ush)MAX_DIST(s) && + (ush)lc <= (ush)(MAX_MATCH-MIN_MATCH) && + (ush)d_code(dist) < (ush)D_CODES, "_tr_tally: bad match"); + + s->dyn_ltree[_length_code[lc]+LITERALS+1].Freq++; + s->dyn_dtree[d_code(dist)].Freq++; + } + +#ifdef TRUNCATE_BLOCK + /* Try to guess if it is profitable to stop the current block here */ + if ((s->last_lit & 0x1fff) == 0 && s->level > 2) { + /* Compute an upper bound for the compressed length */ + ulg out_length = (ulg)s->last_lit*8L; + ulg in_length = (ulg)((long)s->strstart - s->block_start); + int dcode; + for (dcode = 0; dcode < D_CODES; dcode++) { + out_length += (ulg)s->dyn_dtree[dcode].Freq * + (5L+extra_dbits[dcode]); + } + out_length >>= 3; + Tracev((stderr,"\nlast_lit %u, in %ld, out ~%ld(%ld%%) ", + s->last_lit, in_length, out_length, + 100L - out_length*100L/in_length)); + if (s->matches < s->last_lit/2 && out_length < in_length/2) return 1; + } +#endif + return (s->last_lit == s->lit_bufsize-1); + /* We avoid equality with lit_bufsize because of wraparound at 64K + * on 16 bit machines and because stored blocks are restricted to + * 64K-1 bytes. + */ +} + +/* =========================================================================== + * Send the block data compressed using the given Huffman trees + */ +local void compress_block(s, ltree, dtree) + deflate_state *s; + const ct_data *ltree; /* literal tree */ + const ct_data *dtree; /* distance tree */ +{ + unsigned dist; /* distance of matched string */ + int lc; /* match length or unmatched char (if dist == 0) */ + unsigned lx = 0; /* running index in l_buf */ + unsigned code; /* the code to send */ + int extra; /* number of extra bits to send */ + + if (s->last_lit != 0) do { + dist = s->d_buf[lx]; + lc = s->l_buf[lx++]; + if (dist == 0) { + send_code(s, lc, ltree); /* send a literal byte */ + Tracecv(isgraph(lc), (stderr," '%c' ", lc)); + } else { + /* Here, lc is the match length - MIN_MATCH */ + code = _length_code[lc]; + send_code(s, code+LITERALS+1, ltree); /* send the length code */ + extra = extra_lbits[code]; + if (extra != 0) { + lc -= base_length[code]; + send_bits(s, lc, extra); /* send the extra length bits */ + } + dist--; /* dist is now the match distance - 1 */ + code = d_code(dist); + Assert (code < D_CODES, "bad d_code"); + + send_code(s, code, dtree); /* send the distance code */ + extra = extra_dbits[code]; + if (extra != 0) { + dist -= (unsigned)base_dist[code]; + send_bits(s, dist, extra); /* send the extra distance bits */ + } + } /* literal or match pair ? */ + + /* Check that the overlay between pending_buf and d_buf+l_buf is ok: */ + Assert((uInt)(s->pending) < s->lit_bufsize + 2*lx, + "pendingBuf overflow"); + + } while (lx < s->last_lit); + + send_code(s, END_BLOCK, ltree); +} + +/* =========================================================================== + * Check if the data type is TEXT or BINARY, using the following algorithm: + * - TEXT if the two conditions below are satisfied: + * a) There are no non-portable control characters belonging to the + * "black list" (0..6, 14..25, 28..31). + * b) There is at least one printable character belonging to the + * "white list" (9 {TAB}, 10 {LF}, 13 {CR}, 32..255). + * - BINARY otherwise. + * - The following partially-portable control characters form a + * "gray list" that is ignored in this detection algorithm: + * (7 {BEL}, 8 {BS}, 11 {VT}, 12 {FF}, 26 {SUB}, 27 {ESC}). + * IN assertion: the fields Freq of dyn_ltree are set. + */ +local int detect_data_type(s) + deflate_state *s; +{ + /* black_mask is the bit mask of black-listed bytes + * set bits 0..6, 14..25, and 28..31 + * 0xf3ffc07f = binary 11110011111111111100000001111111 + */ + unsigned long black_mask = 0xf3ffc07fUL; + int n; + + /* Check for non-textual ("black-listed") bytes. */ + for (n = 0; n <= 31; n++, black_mask >>= 1) + if ((black_mask & 1) && (s->dyn_ltree[n].Freq != 0)) + return Z_BINARY; + + /* Check for textual ("white-listed") bytes. */ + if (s->dyn_ltree[9].Freq != 0 || s->dyn_ltree[10].Freq != 0 + || s->dyn_ltree[13].Freq != 0) + return Z_TEXT; + for (n = 32; n < LITERALS; n++) + if (s->dyn_ltree[n].Freq != 0) + return Z_TEXT; + + /* There are no "black-listed" or "white-listed" bytes: + * this stream either is empty or has tolerated ("gray-listed") bytes only. + */ + return Z_BINARY; +} + +/* =========================================================================== + * Reverse the first len bits of a code, using straightforward code (a faster + * method would use a table) + * IN assertion: 1 <= len <= 15 + */ +local unsigned bi_reverse(code, len) + unsigned code; /* the value to invert */ + int len; /* its bit length */ +{ + register unsigned res = 0; + do { + res |= code & 1; + code >>= 1, res <<= 1; + } while (--len > 0); + return res >> 1; +} + +/* =========================================================================== + * Flush the bit buffer, keeping at most 7 bits in it. + */ +local void bi_flush(s) + deflate_state *s; +{ + if (s->bi_valid == 16) { + put_short(s, s->bi_buf); + s->bi_buf = 0; + s->bi_valid = 0; + } else if (s->bi_valid >= 8) { + put_byte(s, (Byte)s->bi_buf); + s->bi_buf >>= 8; + s->bi_valid -= 8; + } +} + +/* =========================================================================== + * Flush the bit buffer and align the output on a byte boundary + */ +local void bi_windup(s) + deflate_state *s; +{ + if (s->bi_valid > 8) { + put_short(s, s->bi_buf); + } else if (s->bi_valid > 0) { + put_byte(s, (Byte)s->bi_buf); + } + s->bi_buf = 0; + s->bi_valid = 0; +#ifdef ZLIB_DEBUG + s->bits_sent = (s->bits_sent+7) & ~7; +#endif +} ADDED compat/zlib/trees.h Index: compat/zlib/trees.h ================================================================== --- /dev/null +++ compat/zlib/trees.h @@ -0,0 +1,128 @@ +/* header created automatically with -DGEN_TREES_H */ + +local const ct_data static_ltree[L_CODES+2] = { +{{ 12},{ 8}}, {{140},{ 8}}, {{ 76},{ 8}}, {{204},{ 8}}, {{ 44},{ 8}}, +{{172},{ 8}}, {{108},{ 8}}, {{236},{ 8}}, {{ 28},{ 8}}, {{156},{ 8}}, +{{ 92},{ 8}}, {{220},{ 8}}, {{ 60},{ 8}}, {{188},{ 8}}, {{124},{ 8}}, +{{252},{ 8}}, {{ 2},{ 8}}, {{130},{ 8}}, {{ 66},{ 8}}, {{194},{ 8}}, +{{ 34},{ 8}}, {{162},{ 8}}, {{ 98},{ 8}}, {{226},{ 8}}, {{ 18},{ 8}}, +{{146},{ 8}}, {{ 82},{ 8}}, {{210},{ 8}}, {{ 50},{ 8}}, {{178},{ 8}}, +{{114},{ 8}}, {{242},{ 8}}, {{ 10},{ 8}}, {{138},{ 8}}, {{ 74},{ 8}}, +{{202},{ 8}}, {{ 42},{ 8}}, {{170},{ 8}}, {{106},{ 8}}, {{234},{ 8}}, +{{ 26},{ 8}}, {{154},{ 8}}, {{ 90},{ 8}}, {{218},{ 8}}, {{ 58},{ 8}}, +{{186},{ 8}}, {{122},{ 8}}, {{250},{ 8}}, {{ 6},{ 8}}, {{134},{ 8}}, +{{ 70},{ 8}}, {{198},{ 8}}, {{ 38},{ 8}}, {{166},{ 8}}, {{102},{ 8}}, +{{230},{ 8}}, {{ 22},{ 8}}, {{150},{ 8}}, {{ 86},{ 8}}, {{214},{ 8}}, +{{ 54},{ 8}}, {{182},{ 8}}, {{118},{ 8}}, {{246},{ 8}}, {{ 14},{ 8}}, +{{142},{ 8}}, {{ 78},{ 8}}, {{206},{ 8}}, {{ 46},{ 8}}, {{174},{ 8}}, +{{110},{ 8}}, {{238},{ 8}}, {{ 30},{ 8}}, {{158},{ 8}}, {{ 94},{ 8}}, +{{222},{ 8}}, {{ 62},{ 8}}, {{190},{ 8}}, {{126},{ 8}}, {{254},{ 8}}, +{{ 1},{ 8}}, {{129},{ 8}}, {{ 65},{ 8}}, {{193},{ 8}}, {{ 33},{ 8}}, +{{161},{ 8}}, {{ 97},{ 8}}, {{225},{ 8}}, {{ 17},{ 8}}, {{145},{ 8}}, +{{ 81},{ 8}}, {{209},{ 8}}, {{ 49},{ 8}}, {{177},{ 8}}, {{113},{ 8}}, +{{241},{ 8}}, {{ 9},{ 8}}, {{137},{ 8}}, {{ 73},{ 8}}, {{201},{ 8}}, +{{ 41},{ 8}}, {{169},{ 8}}, {{105},{ 8}}, {{233},{ 8}}, {{ 25},{ 8}}, +{{153},{ 8}}, {{ 89},{ 8}}, {{217},{ 8}}, {{ 57},{ 8}}, {{185},{ 8}}, +{{121},{ 8}}, {{249},{ 8}}, {{ 5},{ 8}}, {{133},{ 8}}, {{ 69},{ 8}}, +{{197},{ 8}}, {{ 37},{ 8}}, {{165},{ 8}}, {{101},{ 8}}, {{229},{ 8}}, +{{ 21},{ 8}}, {{149},{ 8}}, {{ 85},{ 8}}, {{213},{ 8}}, {{ 53},{ 8}}, +{{181},{ 8}}, {{117},{ 8}}, {{245},{ 8}}, {{ 13},{ 8}}, {{141},{ 8}}, +{{ 77},{ 8}}, {{205},{ 8}}, {{ 45},{ 8}}, {{173},{ 8}}, {{109},{ 8}}, +{{237},{ 8}}, {{ 29},{ 8}}, {{157},{ 8}}, {{ 93},{ 8}}, {{221},{ 8}}, +{{ 61},{ 8}}, {{189},{ 8}}, {{125},{ 8}}, {{253},{ 8}}, {{ 19},{ 9}}, +{{275},{ 9}}, {{147},{ 9}}, {{403},{ 9}}, {{ 83},{ 9}}, {{339},{ 9}}, +{{211},{ 9}}, {{467},{ 9}}, {{ 51},{ 9}}, {{307},{ 9}}, {{179},{ 9}}, +{{435},{ 9}}, {{115},{ 9}}, {{371},{ 9}}, {{243},{ 9}}, {{499},{ 9}}, +{{ 11},{ 9}}, {{267},{ 9}}, {{139},{ 9}}, {{395},{ 9}}, {{ 75},{ 9}}, +{{331},{ 9}}, {{203},{ 9}}, {{459},{ 9}}, {{ 43},{ 9}}, {{299},{ 9}}, +{{171},{ 9}}, {{427},{ 9}}, {{107},{ 9}}, {{363},{ 9}}, {{235},{ 9}}, +{{491},{ 9}}, {{ 27},{ 9}}, {{283},{ 9}}, {{155},{ 9}}, {{411},{ 9}}, +{{ 91},{ 9}}, {{347},{ 9}}, {{219},{ 9}}, {{475},{ 9}}, {{ 59},{ 9}}, +{{315},{ 9}}, {{187},{ 9}}, {{443},{ 9}}, {{123},{ 9}}, {{379},{ 9}}, +{{251},{ 9}}, {{507},{ 9}}, {{ 7},{ 9}}, {{263},{ 9}}, {{135},{ 9}}, +{{391},{ 9}}, {{ 71},{ 9}}, {{327},{ 9}}, {{199},{ 9}}, {{455},{ 9}}, +{{ 39},{ 9}}, {{295},{ 9}}, {{167},{ 9}}, {{423},{ 9}}, {{103},{ 9}}, +{{359},{ 9}}, {{231},{ 9}}, {{487},{ 9}}, {{ 23},{ 9}}, {{279},{ 9}}, +{{151},{ 9}}, {{407},{ 9}}, {{ 87},{ 9}}, {{343},{ 9}}, {{215},{ 9}}, +{{471},{ 9}}, {{ 55},{ 9}}, {{311},{ 9}}, {{183},{ 9}}, {{439},{ 9}}, +{{119},{ 9}}, {{375},{ 9}}, {{247},{ 9}}, {{503},{ 9}}, {{ 15},{ 9}}, +{{271},{ 9}}, {{143},{ 9}}, {{399},{ 9}}, {{ 79},{ 9}}, {{335},{ 9}}, +{{207},{ 9}}, {{463},{ 9}}, {{ 47},{ 9}}, {{303},{ 9}}, {{175},{ 9}}, +{{431},{ 9}}, {{111},{ 9}}, {{367},{ 9}}, {{239},{ 9}}, {{495},{ 9}}, +{{ 31},{ 9}}, {{287},{ 9}}, {{159},{ 9}}, {{415},{ 9}}, {{ 95},{ 9}}, +{{351},{ 9}}, {{223},{ 9}}, {{479},{ 9}}, {{ 63},{ 9}}, {{319},{ 9}}, +{{191},{ 9}}, {{447},{ 9}}, {{127},{ 9}}, {{383},{ 9}}, {{255},{ 9}}, +{{511},{ 9}}, {{ 0},{ 7}}, {{ 64},{ 7}}, {{ 32},{ 7}}, {{ 96},{ 7}}, +{{ 16},{ 7}}, {{ 80},{ 7}}, {{ 48},{ 7}}, {{112},{ 7}}, {{ 8},{ 7}}, +{{ 72},{ 7}}, {{ 40},{ 7}}, {{104},{ 7}}, {{ 24},{ 7}}, {{ 88},{ 7}}, +{{ 56},{ 7}}, {{120},{ 7}}, {{ 4},{ 7}}, {{ 68},{ 7}}, {{ 36},{ 7}}, +{{100},{ 7}}, {{ 20},{ 7}}, {{ 84},{ 7}}, {{ 52},{ 7}}, {{116},{ 7}}, +{{ 3},{ 8}}, {{131},{ 8}}, {{ 67},{ 8}}, {{195},{ 8}}, {{ 35},{ 8}}, +{{163},{ 8}}, {{ 99},{ 8}}, {{227},{ 8}} +}; + +local const ct_data static_dtree[D_CODES] = { +{{ 0},{ 5}}, {{16},{ 5}}, {{ 8},{ 5}}, {{24},{ 5}}, {{ 4},{ 5}}, +{{20},{ 5}}, {{12},{ 5}}, {{28},{ 5}}, {{ 2},{ 5}}, {{18},{ 5}}, +{{10},{ 5}}, {{26},{ 5}}, {{ 6},{ 5}}, {{22},{ 5}}, {{14},{ 5}}, +{{30},{ 5}}, {{ 1},{ 5}}, {{17},{ 5}}, {{ 9},{ 5}}, {{25},{ 5}}, +{{ 5},{ 5}}, {{21},{ 5}}, {{13},{ 5}}, {{29},{ 5}}, {{ 3},{ 5}}, +{{19},{ 5}}, {{11},{ 5}}, {{27},{ 5}}, {{ 7},{ 5}}, {{23},{ 5}} +}; + +const uch ZLIB_INTERNAL _dist_code[DIST_CODE_LEN] = { + 0, 1, 2, 3, 4, 4, 5, 5, 6, 6, 6, 6, 7, 7, 7, 7, 8, 8, 8, 8, + 8, 8, 8, 8, 9, 9, 9, 9, 9, 9, 9, 9, 10, 10, 10, 10, 10, 10, 10, 10, +10, 10, 10, 10, 10, 10, 10, 10, 11, 11, 11, 11, 11, 11, 11, 11, 11, 11, 11, 11, +11, 11, 11, 11, 12, 12, 12, 12, 12, 12, 12, 12, 12, 12, 12, 12, 12, 12, 12, 12, +12, 12, 12, 12, 12, 12, 12, 12, 12, 12, 12, 12, 12, 12, 12, 12, 13, 13, 13, 13, +13, 13, 13, 13, 13, 13, 13, 13, 13, 13, 13, 13, 13, 13, 13, 13, 13, 13, 13, 13, +13, 13, 13, 13, 13, 13, 13, 13, 14, 14, 14, 14, 14, 14, 14, 14, 14, 14, 14, 14, +14, 14, 14, 14, 14, 14, 14, 14, 14, 14, 14, 14, 14, 14, 14, 14, 14, 14, 14, 14, +14, 14, 14, 14, 14, 14, 14, 14, 14, 14, 14, 14, 14, 14, 14, 14, 14, 14, 14, 14, +14, 14, 14, 14, 14, 14, 14, 14, 14, 14, 14, 14, 15, 15, 15, 15, 15, 15, 15, 15, +15, 15, 15, 15, 15, 15, 15, 15, 15, 15, 15, 15, 15, 15, 15, 15, 15, 15, 15, 15, +15, 15, 15, 15, 15, 15, 15, 15, 15, 15, 15, 15, 15, 15, 15, 15, 15, 15, 15, 15, +15, 15, 15, 15, 15, 15, 15, 15, 15, 15, 15, 15, 15, 15, 15, 15, 0, 0, 16, 17, +18, 18, 19, 19, 20, 20, 20, 20, 21, 21, 21, 21, 22, 22, 22, 22, 22, 22, 22, 22, +23, 23, 23, 23, 23, 23, 23, 23, 24, 24, 24, 24, 24, 24, 24, 24, 24, 24, 24, 24, +24, 24, 24, 24, 25, 25, 25, 25, 25, 25, 25, 25, 25, 25, 25, 25, 25, 25, 25, 25, +26, 26, 26, 26, 26, 26, 26, 26, 26, 26, 26, 26, 26, 26, 26, 26, 26, 26, 26, 26, +26, 26, 26, 26, 26, 26, 26, 26, 26, 26, 26, 26, 27, 27, 27, 27, 27, 27, 27, 27, +27, 27, 27, 27, 27, 27, 27, 27, 27, 27, 27, 27, 27, 27, 27, 27, 27, 27, 27, 27, +27, 27, 27, 27, 28, 28, 28, 28, 28, 28, 28, 28, 28, 28, 28, 28, 28, 28, 28, 28, +28, 28, 28, 28, 28, 28, 28, 28, 28, 28, 28, 28, 28, 28, 28, 28, 28, 28, 28, 28, +28, 28, 28, 28, 28, 28, 28, 28, 28, 28, 28, 28, 28, 28, 28, 28, 28, 28, 28, 28, +28, 28, 28, 28, 28, 28, 28, 28, 29, 29, 29, 29, 29, 29, 29, 29, 29, 29, 29, 29, +29, 29, 29, 29, 29, 29, 29, 29, 29, 29, 29, 29, 29, 29, 29, 29, 29, 29, 29, 29, +29, 29, 29, 29, 29, 29, 29, 29, 29, 29, 29, 29, 29, 29, 29, 29, 29, 29, 29, 29, +29, 29, 29, 29, 29, 29, 29, 29, 29, 29, 29, 29 +}; + +const uch ZLIB_INTERNAL _length_code[MAX_MATCH-MIN_MATCH+1]= { + 0, 1, 2, 3, 4, 5, 6, 7, 8, 8, 9, 9, 10, 10, 11, 11, 12, 12, 12, 12, +13, 13, 13, 13, 14, 14, 14, 14, 15, 15, 15, 15, 16, 16, 16, 16, 16, 16, 16, 16, +17, 17, 17, 17, 17, 17, 17, 17, 18, 18, 18, 18, 18, 18, 18, 18, 19, 19, 19, 19, +19, 19, 19, 19, 20, 20, 20, 20, 20, 20, 20, 20, 20, 20, 20, 20, 20, 20, 20, 20, +21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 22, 22, 22, 22, +22, 22, 22, 22, 22, 22, 22, 22, 22, 22, 22, 22, 23, 23, 23, 23, 23, 23, 23, 23, +23, 23, 23, 23, 23, 23, 23, 23, 24, 24, 24, 24, 24, 24, 24, 24, 24, 24, 24, 24, +24, 24, 24, 24, 24, 24, 24, 24, 24, 24, 24, 24, 24, 24, 24, 24, 24, 24, 24, 24, +25, 25, 25, 25, 25, 25, 25, 25, 25, 25, 25, 25, 25, 25, 25, 25, 25, 25, 25, 25, +25, 25, 25, 25, 25, 25, 25, 25, 25, 25, 25, 25, 26, 26, 26, 26, 26, 26, 26, 26, +26, 26, 26, 26, 26, 26, 26, 26, 26, 26, 26, 26, 26, 26, 26, 26, 26, 26, 26, 26, +26, 26, 26, 26, 27, 27, 27, 27, 27, 27, 27, 27, 27, 27, 27, 27, 27, 27, 27, 27, +27, 27, 27, 27, 27, 27, 27, 27, 27, 27, 27, 27, 27, 27, 27, 28 +}; + +local const int base_length[LENGTH_CODES] = { +0, 1, 2, 3, 4, 5, 6, 7, 8, 10, 12, 14, 16, 20, 24, 28, 32, 40, 48, 56, +64, 80, 96, 112, 128, 160, 192, 224, 0 +}; + +local const int base_dist[D_CODES] = { + 0, 1, 2, 3, 4, 6, 8, 12, 16, 24, + 32, 48, 64, 96, 128, 192, 256, 384, 512, 768, + 1024, 1536, 2048, 3072, 4096, 6144, 8192, 12288, 16384, 24576 +}; + ADDED compat/zlib/uncompr.c Index: compat/zlib/uncompr.c ================================================================== --- /dev/null +++ compat/zlib/uncompr.c @@ -0,0 +1,93 @@ +/* uncompr.c -- decompress a memory buffer + * Copyright (C) 1995-2003, 2010, 2014, 2016 Jean-loup Gailly, Mark Adler + * For conditions of distribution and use, see copyright notice in zlib.h + */ + +/* @(#) $Id$ */ + +#define ZLIB_INTERNAL +#include "zlib.h" + +/* =========================================================================== + Decompresses the source buffer into the destination buffer. *sourceLen is + the byte length of the source buffer. Upon entry, *destLen is the total size + of the destination buffer, which must be large enough to hold the entire + uncompressed data. (The size of the uncompressed data must have been saved + previously by the compressor and transmitted to the decompressor by some + mechanism outside the scope of this compression library.) Upon exit, + *destLen is the size of the decompressed data and *sourceLen is the number + of source bytes consumed. Upon return, source + *sourceLen points to the + first unused input byte. + + uncompress returns Z_OK if success, Z_MEM_ERROR if there was not enough + memory, Z_BUF_ERROR if there was not enough room in the output buffer, or + Z_DATA_ERROR if the input data was corrupted, including if the input data is + an incomplete zlib stream. +*/ +int ZEXPORT uncompress2 (dest, destLen, source, sourceLen) + Bytef *dest; + uLongf *destLen; + const Bytef *source; + uLong *sourceLen; +{ + z_stream stream; + int err; + const uInt max = (uInt)-1; + uLong len, left; + Byte buf[1]; /* for detection of incomplete stream when *destLen == 0 */ + + len = *sourceLen; + if (*destLen) { + left = *destLen; + *destLen = 0; + } + else { + left = 1; + dest = buf; + } + + stream.next_in = (z_const Bytef *)source; + stream.avail_in = 0; + stream.zalloc = (alloc_func)0; + stream.zfree = (free_func)0; + stream.opaque = (voidpf)0; + + err = inflateInit(&stream); + if (err != Z_OK) return err; + + stream.next_out = dest; + stream.avail_out = 0; + + do { + if (stream.avail_out == 0) { + stream.avail_out = left > (uLong)max ? max : (uInt)left; + left -= stream.avail_out; + } + if (stream.avail_in == 0) { + stream.avail_in = len > (uLong)max ? max : (uInt)len; + len -= stream.avail_in; + } + err = inflate(&stream, Z_NO_FLUSH); + } while (err == Z_OK); + + *sourceLen -= len + stream.avail_in; + if (dest != buf) + *destLen = stream.total_out; + else if (stream.total_out && err == Z_BUF_ERROR) + left = 1; + + inflateEnd(&stream); + return err == Z_STREAM_END ? Z_OK : + err == Z_NEED_DICT ? Z_DATA_ERROR : + err == Z_BUF_ERROR && left + stream.avail_out ? Z_DATA_ERROR : + err; +} + +int ZEXPORT uncompress (dest, destLen, source, sourceLen) + Bytef *dest; + uLongf *destLen; + const Bytef *source; + uLong sourceLen; +{ + return uncompress2(dest, destLen, source, &sourceLen); +} ADDED compat/zlib/watcom/watcom_f.mak Index: compat/zlib/watcom/watcom_f.mak ================================================================== --- /dev/null +++ compat/zlib/watcom/watcom_f.mak @@ -0,0 +1,43 @@ +# Makefile for zlib +# OpenWatcom flat model +# Last updated: 28-Dec-2005 + +# To use, do "wmake -f watcom_f.mak" + +C_SOURCE = adler32.c compress.c crc32.c deflate.c & + gzclose.c gzlib.c gzread.c gzwrite.c & + infback.c inffast.c inflate.c inftrees.c & + trees.c uncompr.c zutil.c + +OBJS = adler32.obj compress.obj crc32.obj deflate.obj & + gzclose.obj gzlib.obj gzread.obj gzwrite.obj & + infback.obj inffast.obj inflate.obj inftrees.obj & + trees.obj uncompr.obj zutil.obj + +CC = wcc386 +LINKER = wcl386 +CFLAGS = -zq -mf -3r -fp3 -s -bt=dos -oilrtfm -fr=nul -wx +ZLIB_LIB = zlib_f.lib + +.C.OBJ: + $(CC) $(CFLAGS) $[@ + +all: $(ZLIB_LIB) example.exe minigzip.exe + +$(ZLIB_LIB): $(OBJS) + wlib -b -c $(ZLIB_LIB) -+adler32.obj -+compress.obj -+crc32.obj + wlib -b -c $(ZLIB_LIB) -+gzclose.obj -+gzlib.obj -+gzread.obj -+gzwrite.obj + wlib -b -c $(ZLIB_LIB) -+deflate.obj -+infback.obj + wlib -b -c $(ZLIB_LIB) -+inffast.obj -+inflate.obj -+inftrees.obj + wlib -b -c $(ZLIB_LIB) -+trees.obj -+uncompr.obj -+zutil.obj + +example.exe: $(ZLIB_LIB) example.obj + $(LINKER) -ldos32a -fe=example.exe example.obj $(ZLIB_LIB) + +minigzip.exe: $(ZLIB_LIB) minigzip.obj + $(LINKER) -ldos32a -fe=minigzip.exe minigzip.obj $(ZLIB_LIB) + +clean: .SYMBOLIC + del *.obj + del $(ZLIB_LIB) + @echo Cleaning done ADDED compat/zlib/watcom/watcom_l.mak Index: compat/zlib/watcom/watcom_l.mak ================================================================== --- /dev/null +++ compat/zlib/watcom/watcom_l.mak @@ -0,0 +1,43 @@ +# Makefile for zlib +# OpenWatcom large model +# Last updated: 28-Dec-2005 + +# To use, do "wmake -f watcom_l.mak" + +C_SOURCE = adler32.c compress.c crc32.c deflate.c & + gzclose.c gzlib.c gzread.c gzwrite.c & + infback.c inffast.c inflate.c inftrees.c & + trees.c uncompr.c zutil.c + +OBJS = adler32.obj compress.obj crc32.obj deflate.obj & + gzclose.obj gzlib.obj gzread.obj gzwrite.obj & + infback.obj inffast.obj inflate.obj inftrees.obj & + trees.obj uncompr.obj zutil.obj + +CC = wcc +LINKER = wcl +CFLAGS = -zq -ml -s -bt=dos -oilrtfm -fr=nul -wx +ZLIB_LIB = zlib_l.lib + +.C.OBJ: + $(CC) $(CFLAGS) $[@ + +all: $(ZLIB_LIB) example.exe minigzip.exe + +$(ZLIB_LIB): $(OBJS) + wlib -b -c $(ZLIB_LIB) -+adler32.obj -+compress.obj -+crc32.obj + wlib -b -c $(ZLIB_LIB) -+gzclose.obj -+gzlib.obj -+gzread.obj -+gzwrite.obj + wlib -b -c $(ZLIB_LIB) -+deflate.obj -+infback.obj + wlib -b -c $(ZLIB_LIB) -+inffast.obj -+inflate.obj -+inftrees.obj + wlib -b -c $(ZLIB_LIB) -+trees.obj -+uncompr.obj -+zutil.obj + +example.exe: $(ZLIB_LIB) example.obj + $(LINKER) -fe=example.exe example.obj $(ZLIB_LIB) + +minigzip.exe: $(ZLIB_LIB) minigzip.obj + $(LINKER) -fe=minigzip.exe minigzip.obj $(ZLIB_LIB) + +clean: .SYMBOLIC + del *.obj + del $(ZLIB_LIB) + @echo Cleaning done ADDED compat/zlib/win32/DLL_FAQ.txt Index: compat/zlib/win32/DLL_FAQ.txt ================================================================== --- /dev/null +++ compat/zlib/win32/DLL_FAQ.txt @@ -0,0 +1,397 @@ + + Frequently Asked Questions about ZLIB1.DLL + + +This document describes the design, the rationale, and the usage +of the official DLL build of zlib, named ZLIB1.DLL. If you have +general questions about zlib, you should see the file "FAQ" found +in the zlib distribution, or at the following location: + http://www.gzip.org/zlib/zlib_faq.html + + + 1. What is ZLIB1.DLL, and how can I get it? + + - ZLIB1.DLL is the official build of zlib as a DLL. + (Please remark the character '1' in the name.) + + Pointers to a precompiled ZLIB1.DLL can be found in the zlib + web site at: + http://www.zlib.net/ + + Applications that link to ZLIB1.DLL can rely on the following + specification: + + * The exported symbols are exclusively defined in the source + files "zlib.h" and "zlib.def", found in an official zlib + source distribution. + * The symbols are exported by name, not by ordinal. + * The exported names are undecorated. + * The calling convention of functions is "C" (CDECL). + * The ZLIB1.DLL binary is linked to MSVCRT.DLL. + + The archive in which ZLIB1.DLL is bundled contains compiled + test programs that must run with a valid build of ZLIB1.DLL. + It is recommended to download the prebuilt DLL from the zlib + web site, instead of building it yourself, to avoid potential + incompatibilities that could be introduced by your compiler + and build settings. If you do build the DLL yourself, please + make sure that it complies with all the above requirements, + and it runs with the precompiled test programs, bundled with + the original ZLIB1.DLL distribution. + + If, for any reason, you need to build an incompatible DLL, + please use a different file name. + + + 2. Why did you change the name of the DLL to ZLIB1.DLL? + What happened to the old ZLIB.DLL? + + - The old ZLIB.DLL, built from zlib-1.1.4 or earlier, required + compilation settings that were incompatible to those used by + a static build. The DLL settings were supposed to be enabled + by defining the macro ZLIB_DLL, before including "zlib.h". + Incorrect handling of this macro was silently accepted at + build time, resulting in two major problems: + + * ZLIB_DLL was missing from the old makefile. When building + the DLL, not all people added it to the build options. In + consequence, incompatible incarnations of ZLIB.DLL started + to circulate around the net. + + * When switching from using the static library to using the + DLL, applications had to define the ZLIB_DLL macro and + to recompile all the sources that contained calls to zlib + functions. Failure to do so resulted in creating binaries + that were unable to run with the official ZLIB.DLL build. + + The only possible solution that we could foresee was to make + a binary-incompatible change in the DLL interface, in order to + remove the dependency on the ZLIB_DLL macro, and to release + the new DLL under a different name. + + We chose the name ZLIB1.DLL, where '1' indicates the major + zlib version number. We hope that we will not have to break + the binary compatibility again, at least not as long as the + zlib-1.x series will last. + + There is still a ZLIB_DLL macro, that can trigger a more + efficient build and use of the DLL, but compatibility no + longer dependents on it. + + + 3. Can I build ZLIB.DLL from the new zlib sources, and replace + an old ZLIB.DLL, that was built from zlib-1.1.4 or earlier? + + - In principle, you can do it by assigning calling convention + keywords to the macros ZEXPORT and ZEXPORTVA. In practice, + it depends on what you mean by "an old ZLIB.DLL", because the + old DLL exists in several mutually-incompatible versions. + You have to find out first what kind of calling convention is + being used in your particular ZLIB.DLL build, and to use the + same one in the new build. If you don't know what this is all + about, you might be better off if you would just leave the old + DLL intact. + + + 4. Can I compile my application using the new zlib interface, and + link it to an old ZLIB.DLL, that was built from zlib-1.1.4 or + earlier? + + - The official answer is "no"; the real answer depends again on + what kind of ZLIB.DLL you have. Even if you are lucky, this + course of action is unreliable. + + If you rebuild your application and you intend to use a newer + version of zlib (post- 1.1.4), it is strongly recommended to + link it to the new ZLIB1.DLL. + + + 5. Why are the zlib symbols exported by name, and not by ordinal? + + - Although exporting symbols by ordinal is a little faster, it + is risky. Any single glitch in the maintenance or use of the + DEF file that contains the ordinals can result in incompatible + builds and frustrating crashes. Simply put, the benefits of + exporting symbols by ordinal do not justify the risks. + + Technically, it should be possible to maintain ordinals in + the DEF file, and still export the symbols by name. Ordinals + exist in every DLL, and even if the dynamic linking performed + at the DLL startup is searching for names, ordinals serve as + hints, for a faster name lookup. However, if the DEF file + contains ordinals, the Microsoft linker automatically builds + an implib that will cause the executables linked to it to use + those ordinals, and not the names. It is interesting to + notice that the GNU linker for Win32 does not suffer from this + problem. + + It is possible to avoid the DEF file if the exported symbols + are accompanied by a "__declspec(dllexport)" attribute in the + source files. You can do this in zlib by predefining the + ZLIB_DLL macro. + + + 6. I see that the ZLIB1.DLL functions use the "C" (CDECL) calling + convention. Why not use the STDCALL convention? + STDCALL is the standard convention in Win32, and I need it in + my Visual Basic project! + + (For readability, we use CDECL to refer to the convention + triggered by the "__cdecl" keyword, STDCALL to refer to + the convention triggered by "__stdcall", and FASTCALL to + refer to the convention triggered by "__fastcall".) + + - Most of the native Windows API functions (without varargs) use + indeed the WINAPI convention (which translates to STDCALL in + Win32), but the standard C functions use CDECL. If a user + application is intrinsically tied to the Windows API (e.g. + it calls native Windows API functions such as CreateFile()), + sometimes it makes sense to decorate its own functions with + WINAPI. But if ANSI C or POSIX portability is a goal (e.g. + it calls standard C functions such as fopen()), it is not a + sound decision to request the inclusion of , or to + use non-ANSI constructs, for the sole purpose to make the user + functions STDCALL-able. + + The functionality offered by zlib is not in the category of + "Windows functionality", but is more like "C functionality". + + Technically, STDCALL is not bad; in fact, it is slightly + faster than CDECL, and it works with variable-argument + functions, just like CDECL. It is unfortunate that, in spite + of using STDCALL in the Windows API, it is not the default + convention used by the C compilers that run under Windows. + The roots of the problem reside deep inside the unsafety of + the K&R-style function prototypes, where the argument types + are not specified; but that is another story for another day. + + The remaining fact is that CDECL is the default convention. + Even if an explicit convention is hard-coded into the function + prototypes inside C headers, problems may appear. The + necessity to expose the convention in users' callbacks is one + of these problems. + + The calling convention issues are also important when using + zlib in other programming languages. Some of them, like Ada + (GNAT) and Fortran (GNU G77), have C bindings implemented + initially on Unix, and relying on the C calling convention. + On the other hand, the pre- .NET versions of Microsoft Visual + Basic require STDCALL, while Borland Delphi prefers, although + it does not require, FASTCALL. + + In fairness to all possible uses of zlib outside the C + programming language, we choose the default "C" convention. + Anyone interested in different bindings or conventions is + encouraged to maintain specialized projects. The "contrib/" + directory from the zlib distribution already holds a couple + of foreign bindings, such as Ada, C++, and Delphi. + + + 7. I need a DLL for my Visual Basic project. What can I do? + + - Define the ZLIB_WINAPI macro before including "zlib.h", when + building both the DLL and the user application (except that + you don't need to define anything when using the DLL in Visual + Basic). The ZLIB_WINAPI macro will switch on the WINAPI + (STDCALL) convention. The name of this DLL must be different + than the official ZLIB1.DLL. + + Gilles Vollant has contributed a build named ZLIBWAPI.DLL, + with the ZLIB_WINAPI macro turned on, and with the minizip + functionality built in. For more information, please read + the notes inside "contrib/vstudio/readme.txt", found in the + zlib distribution. + + + 8. I need to use zlib in my Microsoft .NET project. What can I + do? + + - Henrik Ravn has contributed a .NET wrapper around zlib. Look + into contrib/dotzlib/, inside the zlib distribution. + + + 9. If my application uses ZLIB1.DLL, should I link it to + MSVCRT.DLL? Why? + + - It is not required, but it is recommended to link your + application to MSVCRT.DLL, if it uses ZLIB1.DLL. + + The executables (.EXE, .DLL, etc.) that are involved in the + same process and are using the C run-time library (i.e. they + are calling standard C functions), must link to the same + library. There are several libraries in the Win32 system: + CRTDLL.DLL, MSVCRT.DLL, the static C libraries, etc. + Since ZLIB1.DLL is linked to MSVCRT.DLL, the executables that + depend on it should also be linked to MSVCRT.DLL. + + +10. Why are you saying that ZLIB1.DLL and my application should + be linked to the same C run-time (CRT) library? I linked my + application and my DLLs to different C libraries (e.g. my + application to a static library, and my DLLs to MSVCRT.DLL), + and everything works fine. + + - If a user library invokes only pure Win32 API (accessible via + and the related headers), its DLL build will work + in any context. But if this library invokes standard C API, + things get more complicated. + + There is a single Win32 library in a Win32 system. Every + function in this library resides in a single DLL module, that + is safe to call from anywhere. On the other hand, there are + multiple versions of the C library, and each of them has its + own separate internal state. Standalone executables and user + DLLs that call standard C functions must link to a C run-time + (CRT) library, be it static or shared (DLL). Intermixing + occurs when an executable (not necessarily standalone) and a + DLL are linked to different CRTs, and both are running in the + same process. + + Intermixing multiple CRTs is possible, as long as their + internal states are kept intact. The Microsoft Knowledge Base + articles KB94248 "HOWTO: Use the C Run-Time" and KB140584 + "HOWTO: Link with the Correct C Run-Time (CRT) Library" + mention the potential problems raised by intermixing. + + If intermixing works for you, it's because your application + and DLLs are avoiding the corruption of each of the CRTs' + internal states, maybe by careful design, or maybe by fortune. + + Also note that linking ZLIB1.DLL to non-Microsoft CRTs, such + as those provided by Borland, raises similar problems. + + +11. Why are you linking ZLIB1.DLL to MSVCRT.DLL? + + - MSVCRT.DLL exists on every Windows 95 with a new service pack + installed, or with Microsoft Internet Explorer 4 or later, and + on all other Windows 4.x or later (Windows 98, Windows NT 4, + or later). It is freely distributable; if not present in the + system, it can be downloaded from Microsoft or from other + software provider for free. + + The fact that MSVCRT.DLL does not exist on a virgin Windows 95 + is not so problematic. Windows 95 is scarcely found nowadays, + Microsoft ended its support a long time ago, and many recent + applications from various vendors, including Microsoft, do not + even run on it. Furthermore, no serious user should run + Windows 95 without a proper update installed. + + +12. Why are you not linking ZLIB1.DLL to + <> ? + + - We considered and abandoned the following alternatives: + + * Linking ZLIB1.DLL to a static C library (LIBC.LIB, or + LIBCMT.LIB) is not a good option. People are using the DLL + mainly to save disk space. If you are linking your program + to a static C library, you may as well consider linking zlib + in statically, too. + + * Linking ZLIB1.DLL to CRTDLL.DLL looks appealing, because + CRTDLL.DLL is present on every Win32 installation. + Unfortunately, it has a series of problems: it does not + work properly with Microsoft's C++ libraries, it does not + provide support for 64-bit file offsets, (and so on...), + and Microsoft discontinued its support a long time ago. + + * Linking ZLIB1.DLL to MSVCR70.DLL or MSVCR71.DLL, supplied + with the Microsoft .NET platform, and Visual C++ 7.0/7.1, + raises problems related to the status of ZLIB1.DLL as a + system component. According to the Microsoft Knowledge Base + article KB326922 "INFO: Redistribution of the Shared C + Runtime Component in Visual C++ .NET", MSVCR70.DLL and + MSVCR71.DLL are not supposed to function as system DLLs, + because they may clash with MSVCRT.DLL. Instead, the + application's installer is supposed to put these DLLs + (if needed) in the application's private directory. + If ZLIB1.DLL depends on a non-system runtime, it cannot + function as a redistributable system component. + + * Linking ZLIB1.DLL to non-Microsoft runtimes, such as + Borland's, or Cygwin's, raises problems related to the + reliable presence of these runtimes on Win32 systems. + It's easier to let the DLL build of zlib up to the people + who distribute these runtimes, and who may proceed as + explained in the answer to Question 14. + + +13. If ZLIB1.DLL cannot be linked to MSVCR70.DLL or MSVCR71.DLL, + how can I build/use ZLIB1.DLL in Microsoft Visual C++ 7.0 + (Visual Studio .NET) or newer? + + - Due to the problems explained in the Microsoft Knowledge Base + article KB326922 (see the previous answer), the C runtime that + comes with the VC7 environment is no longer considered a + system component. That is, it should not be assumed that this + runtime exists, or may be installed in a system directory. + Since ZLIB1.DLL is supposed to be a system component, it may + not depend on a non-system component. + + In order to link ZLIB1.DLL and your application to MSVCRT.DLL + in VC7, you need the library of Visual C++ 6.0 or older. If + you don't have this library at hand, it's probably best not to + use ZLIB1.DLL. + + We are hoping that, in the future, Microsoft will provide a + way to build applications linked to a proper system runtime, + from the Visual C++ environment. Until then, you have a + couple of alternatives, such as linking zlib in statically. + If your application requires dynamic linking, you may proceed + as explained in the answer to Question 14. + + +14. I need to link my own DLL build to a CRT different than + MSVCRT.DLL. What can I do? + + - Feel free to rebuild the DLL from the zlib sources, and link + it the way you want. You should, however, clearly state that + your build is unofficial. You should give it a different file + name, and/or install it in a private directory that can be + accessed by your application only, and is not visible to the + others (i.e. it's neither in the PATH, nor in the SYSTEM or + SYSTEM32 directories). Otherwise, your build may clash with + applications that link to the official build. + + For example, in Cygwin, zlib is linked to the Cygwin runtime + CYGWIN1.DLL, and it is distributed under the name CYGZ.DLL. + + +15. May I include additional pieces of code that I find useful, + link them in ZLIB1.DLL, and export them? + + - No. A legitimate build of ZLIB1.DLL must not include code + that does not originate from the official zlib source code. + But you can make your own private DLL build, under a different + file name, as suggested in the previous answer. + + For example, zlib is a part of the VCL library, distributed + with Borland Delphi and C++ Builder. The DLL build of VCL + is a redistributable file, named VCLxx.DLL. + + +16. May I remove some functionality out of ZLIB1.DLL, by enabling + macros like NO_GZCOMPRESS or NO_GZIP at compile time? + + - No. A legitimate build of ZLIB1.DLL must provide the complete + zlib functionality, as implemented in the official zlib source + code. But you can make your own private DLL build, under a + different file name, as suggested in the previous answer. + + +17. I made my own ZLIB1.DLL build. Can I test it for compliance? + + - We prefer that you download the official DLL from the zlib + web site. If you need something peculiar from this DLL, you + can send your suggestion to the zlib mailing list. + + However, in case you do rebuild the DLL yourself, you can run + it with the test programs found in the DLL distribution. + Running these test programs is not a guarantee of compliance, + but a failure can imply a detected problem. + +** + +This document is written and maintained by +Cosmin Truta ADDED compat/zlib/win32/Makefile.bor Index: compat/zlib/win32/Makefile.bor ================================================================== --- /dev/null +++ compat/zlib/win32/Makefile.bor @@ -0,0 +1,110 @@ +# Makefile for zlib +# Borland C++ for Win32 +# +# Usage: +# make -f win32/Makefile.bor +# make -f win32/Makefile.bor LOCAL_ZLIB=-DASMV OBJA=match.obj OBJPA=+match.obj + +# ------------ Borland C++ ------------ + +# Optional nonstandard preprocessor flags (e.g. -DMAX_MEM_LEVEL=7) +# should be added to the environment via "set LOCAL_ZLIB=-DFOO" or +# added to the declaration of LOC here: +LOC = $(LOCAL_ZLIB) + +CC = bcc32 +AS = bcc32 +LD = bcc32 +AR = tlib +CFLAGS = -a -d -k- -O2 $(LOC) +ASFLAGS = $(LOC) +LDFLAGS = $(LOC) + + +# variables +ZLIB_LIB = zlib.lib + +OBJ1 = adler32.obj compress.obj crc32.obj deflate.obj gzclose.obj gzlib.obj gzread.obj +OBJ2 = gzwrite.obj infback.obj inffast.obj inflate.obj inftrees.obj trees.obj uncompr.obj zutil.obj +#OBJA = +OBJP1 = +adler32.obj+compress.obj+crc32.obj+deflate.obj+gzclose.obj+gzlib.obj+gzread.obj +OBJP2 = +gzwrite.obj+infback.obj+inffast.obj+inflate.obj+inftrees.obj+trees.obj+uncompr.obj+zutil.obj +#OBJPA= + + +# targets +all: $(ZLIB_LIB) example.exe minigzip.exe + +.c.obj: + $(CC) -c $(CFLAGS) $< + +.asm.obj: + $(AS) -c $(ASFLAGS) $< + +adler32.obj: adler32.c zlib.h zconf.h + +compress.obj: compress.c zlib.h zconf.h + +crc32.obj: crc32.c zlib.h zconf.h crc32.h + +deflate.obj: deflate.c deflate.h zutil.h zlib.h zconf.h + +gzclose.obj: gzclose.c zlib.h zconf.h gzguts.h + +gzlib.obj: gzlib.c zlib.h zconf.h gzguts.h + +gzread.obj: gzread.c zlib.h zconf.h gzguts.h + +gzwrite.obj: gzwrite.c zlib.h zconf.h gzguts.h + +infback.obj: infback.c zutil.h zlib.h zconf.h inftrees.h inflate.h \ + inffast.h inffixed.h + +inffast.obj: inffast.c zutil.h zlib.h zconf.h inftrees.h inflate.h \ + inffast.h + +inflate.obj: inflate.c zutil.h zlib.h zconf.h inftrees.h inflate.h \ + inffast.h inffixed.h + +inftrees.obj: inftrees.c zutil.h zlib.h zconf.h inftrees.h + +trees.obj: trees.c zutil.h zlib.h zconf.h deflate.h trees.h + +uncompr.obj: uncompr.c zlib.h zconf.h + +zutil.obj: zutil.c zutil.h zlib.h zconf.h + +example.obj: test/example.c zlib.h zconf.h + +minigzip.obj: test/minigzip.c zlib.h zconf.h + + +# For the sake of the old Borland make, +# the command line is cut to fit in the MS-DOS 128 byte limit: +$(ZLIB_LIB): $(OBJ1) $(OBJ2) $(OBJA) + -del $(ZLIB_LIB) + $(AR) $(ZLIB_LIB) $(OBJP1) + $(AR) $(ZLIB_LIB) $(OBJP2) + $(AR) $(ZLIB_LIB) $(OBJPA) + + +# testing +test: example.exe minigzip.exe + example + echo hello world | minigzip | minigzip -d + +example.exe: example.obj $(ZLIB_LIB) + $(LD) $(LDFLAGS) example.obj $(ZLIB_LIB) + +minigzip.exe: minigzip.obj $(ZLIB_LIB) + $(LD) $(LDFLAGS) minigzip.obj $(ZLIB_LIB) + + +# cleanup +clean: + -del $(ZLIB_LIB) + -del *.obj + -del *.exe + -del *.tds + -del zlib.bak + -del foo.gz ADDED compat/zlib/win32/Makefile.gcc Index: compat/zlib/win32/Makefile.gcc ================================================================== --- /dev/null +++ compat/zlib/win32/Makefile.gcc @@ -0,0 +1,182 @@ +# Makefile for zlib, derived from Makefile.dj2. +# Modified for mingw32 by C. Spieler, 6/16/98. +# Updated for zlib 1.2.x by Christian Spieler and Cosmin Truta, Mar-2003. +# Last updated: Mar 2012. +# Tested under Cygwin and MinGW. + +# Copyright (C) 1995-2003 Jean-loup Gailly. +# For conditions of distribution and use, see copyright notice in zlib.h + +# To compile, or to compile and test, type from the top level zlib directory: +# +# make -fwin32/Makefile.gcc; make test testdll -fwin32/Makefile.gcc +# +# To use the asm code, type: +# cp contrib/asm?86/match.S ./match.S +# make LOC=-DASMV OBJA=match.o -fwin32/Makefile.gcc +# +# To install libz.a, zconf.h and zlib.h in the system directories, type: +# +# make install -fwin32/Makefile.gcc +# +# BINARY_PATH, INCLUDE_PATH and LIBRARY_PATH must be set. +# +# To install the shared lib, append SHARED_MODE=1 to the make command : +# +# make install -fwin32/Makefile.gcc SHARED_MODE=1 + +# Note: +# If the platform is *not* MinGW (e.g. it is Cygwin or UWIN), +# the DLL name should be changed from "zlib1.dll". + +STATICLIB = libz.a +SHAREDLIB = zlib1.dll +IMPLIB = libz.dll.a + +# +# Set to 1 if shared object needs to be installed +# +SHARED_MODE=0 + +#LOC = -DASMV +#LOC = -DZLIB_DEBUG -g + +PREFIX = +CC = $(PREFIX)gcc +CFLAGS = $(LOC) -O3 -Wall + +AS = $(CC) +ASFLAGS = $(LOC) -Wall + +LD = $(CC) +LDFLAGS = $(LOC) + +AR = $(PREFIX)ar +ARFLAGS = rcs + +RC = $(PREFIX)windres +RCFLAGS = --define GCC_WINDRES + +STRIP = $(PREFIX)strip + +CP = cp -fp +# If GNU install is available, replace $(CP) with install. +INSTALL = $(CP) +RM = rm -f + +prefix ?= /usr/local +exec_prefix = $(prefix) + +OBJS = adler32.o compress.o crc32.o deflate.o gzclose.o gzlib.o gzread.o \ + gzwrite.o infback.o inffast.o inflate.o inftrees.o trees.o uncompr.o zutil.o +OBJA = + +all: $(STATICLIB) $(SHAREDLIB) $(IMPLIB) example.exe minigzip.exe example_d.exe minigzip_d.exe + +test: example.exe minigzip.exe + ./example + echo hello world | ./minigzip | ./minigzip -d + +testdll: example_d.exe minigzip_d.exe + ./example_d + echo hello world | ./minigzip_d | ./minigzip_d -d + +.c.o: + $(CC) $(CFLAGS) -c -o $@ $< + +.S.o: + $(AS) $(ASFLAGS) -c -o $@ $< + +$(STATICLIB): $(OBJS) $(OBJA) + $(AR) $(ARFLAGS) $@ $(OBJS) $(OBJA) + +$(IMPLIB): $(SHAREDLIB) + +$(SHAREDLIB): win32/zlib.def $(OBJS) $(OBJA) zlibrc.o + $(CC) -shared -Wl,--out-implib,$(IMPLIB) $(LDFLAGS) \ + -o $@ win32/zlib.def $(OBJS) $(OBJA) zlibrc.o + $(STRIP) $@ + +example.exe: example.o $(STATICLIB) + $(LD) $(LDFLAGS) -o $@ example.o $(STATICLIB) + $(STRIP) $@ + +minigzip.exe: minigzip.o $(STATICLIB) + $(LD) $(LDFLAGS) -o $@ minigzip.o $(STATICLIB) + $(STRIP) $@ + +example_d.exe: example.o $(IMPLIB) + $(LD) $(LDFLAGS) -o $@ example.o $(IMPLIB) + $(STRIP) $@ + +minigzip_d.exe: minigzip.o $(IMPLIB) + $(LD) $(LDFLAGS) -o $@ minigzip.o $(IMPLIB) + $(STRIP) $@ + +example.o: test/example.c zlib.h zconf.h + $(CC) $(CFLAGS) -I. -c -o $@ test/example.c + +minigzip.o: test/minigzip.c zlib.h zconf.h + $(CC) $(CFLAGS) -I. -c -o $@ test/minigzip.c + +zlibrc.o: win32/zlib1.rc + $(RC) $(RCFLAGS) -o $@ win32/zlib1.rc + +.PHONY: install uninstall clean + +install: zlib.h zconf.h $(STATICLIB) $(IMPLIB) + @if test -z "$(DESTDIR)$(INCLUDE_PATH)" -o -z "$(DESTDIR)$(LIBRARY_PATH)" -o -z "$(DESTDIR)$(BINARY_PATH)"; then \ + echo INCLUDE_PATH, LIBRARY_PATH, and BINARY_PATH must be specified; \ + exit 1; \ + fi + -@mkdir -p '$(DESTDIR)$(INCLUDE_PATH)' + -@mkdir -p '$(DESTDIR)$(LIBRARY_PATH)' '$(DESTDIR)$(LIBRARY_PATH)'/pkgconfig + -if [ "$(SHARED_MODE)" = "1" ]; then \ + mkdir -p '$(DESTDIR)$(BINARY_PATH)'; \ + $(INSTALL) $(SHAREDLIB) '$(DESTDIR)$(BINARY_PATH)'; \ + $(INSTALL) $(IMPLIB) '$(DESTDIR)$(LIBRARY_PATH)'; \ + fi + -$(INSTALL) zlib.h '$(DESTDIR)$(INCLUDE_PATH)' + -$(INSTALL) zconf.h '$(DESTDIR)$(INCLUDE_PATH)' + -$(INSTALL) $(STATICLIB) '$(DESTDIR)$(LIBRARY_PATH)' + sed \ + -e 's|@prefix@|${prefix}|g' \ + -e 's|@exec_prefix@|${exec_prefix}|g' \ + -e 's|@libdir@|$(LIBRARY_PATH)|g' \ + -e 's|@sharedlibdir@|$(LIBRARY_PATH)|g' \ + -e 's|@includedir@|$(INCLUDE_PATH)|g' \ + -e 's|@VERSION@|'`sed -n -e '/VERSION "/s/.*"\(.*\)".*/\1/p' zlib.h`'|g' \ + zlib.pc.in > '$(DESTDIR)$(LIBRARY_PATH)'/pkgconfig/zlib.pc + +uninstall: + -if [ "$(SHARED_MODE)" = "1" ]; then \ + $(RM) '$(DESTDIR)$(BINARY_PATH)'/$(SHAREDLIB); \ + $(RM) '$(DESTDIR)$(LIBRARY_PATH)'/$(IMPLIB); \ + fi + -$(RM) '$(DESTDIR)$(INCLUDE_PATH)'/zlib.h + -$(RM) '$(DESTDIR)$(INCLUDE_PATH)'/zconf.h + -$(RM) '$(DESTDIR)$(LIBRARY_PATH)'/$(STATICLIB) + +clean: + -$(RM) $(STATICLIB) + -$(RM) $(SHAREDLIB) + -$(RM) $(IMPLIB) + -$(RM) *.o + -$(RM) *.exe + -$(RM) foo.gz + +adler32.o: zlib.h zconf.h +compress.o: zlib.h zconf.h +crc32.o: crc32.h zlib.h zconf.h +deflate.o: deflate.h zutil.h zlib.h zconf.h +gzclose.o: zlib.h zconf.h gzguts.h +gzlib.o: zlib.h zconf.h gzguts.h +gzread.o: zlib.h zconf.h gzguts.h +gzwrite.o: zlib.h zconf.h gzguts.h +inffast.o: zutil.h zlib.h zconf.h inftrees.h inflate.h inffast.h +inflate.o: zutil.h zlib.h zconf.h inftrees.h inflate.h inffast.h +infback.o: zutil.h zlib.h zconf.h inftrees.h inflate.h inffast.h +inftrees.o: zutil.h zlib.h zconf.h inftrees.h +trees.o: deflate.h zutil.h zlib.h zconf.h trees.h +uncompr.o: zlib.h zconf.h +zutil.o: zutil.h zlib.h zconf.h ADDED compat/zlib/win32/Makefile.msc Index: compat/zlib/win32/Makefile.msc ================================================================== --- /dev/null +++ compat/zlib/win32/Makefile.msc @@ -0,0 +1,163 @@ +# Makefile for zlib using Microsoft (Visual) C +# zlib is copyright (C) 1995-2017 Jean-loup Gailly and Mark Adler +# +# Usage: +# nmake -f win32/Makefile.msc (standard build) +# nmake -f win32/Makefile.msc LOC=-DFOO (nonstandard build) +# nmake -f win32/Makefile.msc LOC="-DASMV -DASMINF" \ +# OBJA="inffas32.obj match686.obj" (use ASM code, x86) +# nmake -f win32/Makefile.msc AS=ml64 LOC="-DASMV -DASMINF -I." \ +# OBJA="inffasx64.obj gvmat64.obj inffas8664.obj" (use ASM code, x64) + +# The toplevel directory of the source tree. +# +TOP = . + +# optional build flags +LOC = + +# variables +STATICLIB = zlib.lib +SHAREDLIB = zlib1.dll +IMPLIB = zdll.lib + +CC = cl +AS = ml +LD = link +AR = lib +RC = rc +CFLAGS = -nologo -MT -W3 -O2 -Oy- -Zi -Fd"zlib" $(LOC) +WFLAGS = -D_CRT_SECURE_NO_DEPRECATE -D_CRT_NONSTDC_NO_DEPRECATE +ASFLAGS = -coff -Zi $(LOC) +LDFLAGS = -nologo -debug -incremental:no -opt:ref +ARFLAGS = -nologo +RCFLAGS = /dWIN32 /r + +OBJS = adler32.obj compress.obj crc32.obj deflate.obj gzclose.obj gzlib.obj gzread.obj \ + gzwrite.obj infback.obj inflate.obj inftrees.obj inffast.obj trees.obj uncompr.obj zutil.obj +OBJA = + + +# targets +all: $(STATICLIB) $(SHAREDLIB) $(IMPLIB) \ + example.exe minigzip.exe example_d.exe minigzip_d.exe + +$(STATICLIB): $(OBJS) $(OBJA) + $(AR) $(ARFLAGS) -out:$@ $(OBJS) $(OBJA) + +$(IMPLIB): $(SHAREDLIB) + +$(SHAREDLIB): $(TOP)/win32/zlib.def $(OBJS) $(OBJA) zlib1.res + $(LD) $(LDFLAGS) -def:$(TOP)/win32/zlib.def -dll -implib:$(IMPLIB) \ + -out:$@ -base:0x5A4C0000 $(OBJS) $(OBJA) zlib1.res + if exist $@.manifest \ + mt -nologo -manifest $@.manifest -outputresource:$@;2 + +example.exe: example.obj $(STATICLIB) + $(LD) $(LDFLAGS) example.obj $(STATICLIB) + if exist $@.manifest \ + mt -nologo -manifest $@.manifest -outputresource:$@;1 + +minigzip.exe: minigzip.obj $(STATICLIB) + $(LD) $(LDFLAGS) minigzip.obj $(STATICLIB) + if exist $@.manifest \ + mt -nologo -manifest $@.manifest -outputresource:$@;1 + +example_d.exe: example.obj $(IMPLIB) + $(LD) $(LDFLAGS) -out:$@ example.obj $(IMPLIB) + if exist $@.manifest \ + mt -nologo -manifest $@.manifest -outputresource:$@;1 + +minigzip_d.exe: minigzip.obj $(IMPLIB) + $(LD) $(LDFLAGS) -out:$@ minigzip.obj $(IMPLIB) + if exist $@.manifest \ + mt -nologo -manifest $@.manifest -outputresource:$@;1 + +{$(TOP)}.c.obj: + $(CC) -c $(WFLAGS) $(CFLAGS) $< + +{$(TOP)/test}.c.obj: + $(CC) -c -I$(TOP) $(WFLAGS) $(CFLAGS) $< + +{$(TOP)/contrib/masmx64}.c.obj: + $(CC) -c $(WFLAGS) $(CFLAGS) $< + +{$(TOP)/contrib/masmx64}.asm.obj: + $(AS) -c $(ASFLAGS) $< + +{$(TOP)/contrib/masmx86}.asm.obj: + $(AS) -c $(ASFLAGS) $< + +adler32.obj: $(TOP)/adler32.c $(TOP)/zlib.h $(TOP)/zconf.h + +compress.obj: $(TOP)/compress.c $(TOP)/zlib.h $(TOP)/zconf.h + +crc32.obj: $(TOP)/crc32.c $(TOP)/zlib.h $(TOP)/zconf.h $(TOP)/crc32.h + +deflate.obj: $(TOP)/deflate.c $(TOP)/deflate.h $(TOP)/zutil.h $(TOP)/zlib.h $(TOP)/zconf.h + +gzclose.obj: $(TOP)/gzclose.c $(TOP)/zlib.h $(TOP)/zconf.h $(TOP)/gzguts.h + +gzlib.obj: $(TOP)/gzlib.c $(TOP)/zlib.h $(TOP)/zconf.h $(TOP)/gzguts.h + +gzread.obj: $(TOP)/gzread.c $(TOP)/zlib.h $(TOP)/zconf.h $(TOP)/gzguts.h + +gzwrite.obj: $(TOP)/gzwrite.c $(TOP)/zlib.h $(TOP)/zconf.h $(TOP)/gzguts.h + +infback.obj: $(TOP)/infback.c $(TOP)/zutil.h $(TOP)/zlib.h $(TOP)/zconf.h $(TOP)/inftrees.h $(TOP)/inflate.h \ + $(TOP)/inffast.h $(TOP)/inffixed.h + +inffast.obj: $(TOP)/inffast.c $(TOP)/zutil.h $(TOP)/zlib.h $(TOP)/zconf.h $(TOP)/inftrees.h $(TOP)/inflate.h \ + $(TOP)/inffast.h + +inflate.obj: $(TOP)/inflate.c $(TOP)/zutil.h $(TOP)/zlib.h $(TOP)/zconf.h $(TOP)/inftrees.h $(TOP)/inflate.h \ + $(TOP)/inffast.h $(TOP)/inffixed.h + +inftrees.obj: $(TOP)/inftrees.c $(TOP)/zutil.h $(TOP)/zlib.h $(TOP)/zconf.h $(TOP)/inftrees.h + +trees.obj: $(TOP)/trees.c $(TOP)/zutil.h $(TOP)/zlib.h $(TOP)/zconf.h $(TOP)/deflate.h $(TOP)/trees.h + +uncompr.obj: $(TOP)/uncompr.c $(TOP)/zlib.h $(TOP)/zconf.h + +zutil.obj: $(TOP)/zutil.c $(TOP)/zutil.h $(TOP)/zlib.h $(TOP)/zconf.h + +gvmat64.obj: $(TOP)/contrib\masmx64\gvmat64.asm + +inffasx64.obj: $(TOP)/contrib\masmx64\inffasx64.asm + +inffas8664.obj: $(TOP)/contrib\masmx64\inffas8664.c $(TOP)/zutil.h $(TOP)/zlib.h $(TOP)/zconf.h \ + $(TOP)/inftrees.h $(TOP)/inflate.h $(TOP)/inffast.h + +inffas32.obj: $(TOP)/contrib\masmx86\inffas32.asm + +match686.obj: $(TOP)/contrib\masmx86\match686.asm + +example.obj: $(TOP)/test/example.c $(TOP)/zlib.h $(TOP)/zconf.h + +minigzip.obj: $(TOP)/test/minigzip.c $(TOP)/zlib.h $(TOP)/zconf.h + +zlib1.res: $(TOP)/win32/zlib1.rc + $(RC) $(RCFLAGS) /fo$@ $(TOP)/win32/zlib1.rc + +# testing +test: example.exe minigzip.exe + example + echo hello world | minigzip | minigzip -d + +testdll: example_d.exe minigzip_d.exe + example_d + echo hello world | minigzip_d | minigzip_d -d + + +# cleanup +clean: + -del $(STATICLIB) + -del $(SHAREDLIB) + -del $(IMPLIB) + -del *.obj + -del *.res + -del *.exp + -del *.exe + -del *.pdb + -del *.manifest + -del foo.gz ADDED compat/zlib/win32/README-WIN32.txt Index: compat/zlib/win32/README-WIN32.txt ================================================================== --- /dev/null +++ compat/zlib/win32/README-WIN32.txt @@ -0,0 +1,103 @@ +ZLIB DATA COMPRESSION LIBRARY + +zlib 1.2.11 is a general purpose data compression library. All the code is +thread safe. The data format used by the zlib library is described by RFCs +(Request for Comments) 1950 to 1952 in the files +http://www.ietf.org/rfc/rfc1950.txt (zlib format), rfc1951.txt (deflate format) +and rfc1952.txt (gzip format). + +All functions of the compression library are documented in the file zlib.h +(volunteer to write man pages welcome, contact zlib@gzip.org). Two compiled +examples are distributed in this package, example and minigzip. The example_d +and minigzip_d flavors validate that the zlib1.dll file is working correctly. + +Questions about zlib should be sent to . The zlib home page +is http://zlib.net/ . Before reporting a problem, please check this site to +verify that you have the latest version of zlib; otherwise get the latest +version and check whether the problem still exists or not. + +PLEASE read DLL_FAQ.txt, and the the zlib FAQ http://zlib.net/zlib_faq.html +before asking for help. + + +Manifest: + +The package zlib-1.2.11-win32-x86.zip will contain the following files: + + README-WIN32.txt This document + ChangeLog Changes since previous zlib packages + DLL_FAQ.txt Frequently asked questions about zlib1.dll + zlib.3.pdf Documentation of this library in Adobe Acrobat format + + example.exe A statically-bound example (using zlib.lib, not the dll) + example.pdb Symbolic information for debugging example.exe + + example_d.exe A zlib1.dll bound example (using zdll.lib) + example_d.pdb Symbolic information for debugging example_d.exe + + minigzip.exe A statically-bound test program (using zlib.lib, not the dll) + minigzip.pdb Symbolic information for debugging minigzip.exe + + minigzip_d.exe A zlib1.dll bound test program (using zdll.lib) + minigzip_d.pdb Symbolic information for debugging minigzip_d.exe + + zlib.h Install these files into the compilers' INCLUDE path to + zconf.h compile programs which use zlib.lib or zdll.lib + + zdll.lib Install these files into the compilers' LIB path if linking + zdll.exp a compiled program to the zlib1.dll binary + + zlib.lib Install these files into the compilers' LIB path to link zlib + zlib.pdb into compiled programs, without zlib1.dll runtime dependency + (zlib.pdb provides debugging info to the compile time linker) + + zlib1.dll Install this binary shared library into the system PATH, or + the program's runtime directory (where the .exe resides) + zlib1.pdb Install in the same directory as zlib1.dll, in order to debug + an application crash using WinDbg or similar tools. + +All .pdb files above are entirely optional, but are very useful to a developer +attempting to diagnose program misbehavior or a crash. Many additional +important files for developers can be found in the zlib127.zip source package +available from http://zlib.net/ - review that package's README file for details. + + +Acknowledgments: + +The deflate format used by zlib was defined by Phil Katz. The deflate and +zlib specifications were written by L. Peter Deutsch. Thanks to all the +people who reported problems and suggested various improvements in zlib; they +are too numerous to cite here. + + +Copyright notice: + + (C) 1995-2017 Jean-loup Gailly and Mark Adler + + This software is provided 'as-is', without any express or implied + warranty. In no event will the authors be held liable for any damages + arising from the use of this software. + + Permission is granted to anyone to use this software for any purpose, + including commercial applications, and to alter it and redistribute it + freely, subject to the following restrictions: + + 1. The origin of this software must not be misrepresented; you must not + claim that you wrote the original software. If you use this software + in a product, an acknowledgment in the product documentation would be + appreciated but is not required. + 2. Altered source versions must be plainly marked as such, and must not be + misrepresented as being the original software. + 3. This notice may not be removed or altered from any source distribution. + + Jean-loup Gailly Mark Adler + jloup@gzip.org madler@alumni.caltech.edu + +If you use the zlib library in a product, we would appreciate *not* receiving +lengthy legal documents to sign. The sources are provided for free but without +warranty of any kind. The library has been entirely written by Jean-loup +Gailly and Mark Adler; it does not include third-party code. + +If you redistribute modified sources, we would appreciate that you include in +the file ChangeLog history information documenting your changes. Please read +the FAQ for more information on the distribution of modified source versions. ADDED compat/zlib/win32/VisualC.txt Index: compat/zlib/win32/VisualC.txt ================================================================== --- /dev/null +++ compat/zlib/win32/VisualC.txt @@ -0,0 +1,3 @@ + +To build zlib using the Microsoft Visual C++ environment, +use the appropriate project from the contrib/vstudio/ directory. ADDED compat/zlib/win32/zlib.def Index: compat/zlib/win32/zlib.def ================================================================== --- /dev/null +++ compat/zlib/win32/zlib.def @@ -0,0 +1,94 @@ +; zlib data compression library +EXPORTS +; basic functions + zlibVersion + deflate + deflateEnd + inflate + inflateEnd +; advanced functions + deflateSetDictionary + deflateGetDictionary + deflateCopy + deflateReset + deflateParams + deflateTune + deflateBound + deflatePending + deflatePrime + deflateSetHeader + inflateSetDictionary + inflateGetDictionary + inflateSync + inflateCopy + inflateReset + inflateReset2 + inflatePrime + inflateMark + inflateGetHeader + inflateBack + inflateBackEnd + zlibCompileFlags +; utility functions + compress + compress2 + compressBound + uncompress + uncompress2 + gzopen + gzdopen + gzbuffer + gzsetparams + gzread + gzfread + gzwrite + gzfwrite + gzprintf + gzvprintf + gzputs + gzgets + gzputc + gzgetc + gzungetc + gzflush + gzseek + gzrewind + gztell + gzoffset + gzeof + gzdirect + gzclose + gzclose_r + gzclose_w + gzerror + gzclearerr +; large file functions + gzopen64 + gzseek64 + gztell64 + gzoffset64 + adler32_combine64 + crc32_combine64 +; checksum functions + adler32 + adler32_z + crc32 + crc32_z + adler32_combine + crc32_combine +; various hacks, don't look :) + deflateInit_ + deflateInit2_ + inflateInit_ + inflateInit2_ + inflateBackInit_ + gzgetc_ + zError + inflateSyncPoint + get_crc_table + inflateUndermine + inflateValidate + inflateCodesUsed + inflateResetKeep + deflateResetKeep + gzopen_w ADDED compat/zlib/win32/zlib1.rc Index: compat/zlib/win32/zlib1.rc ================================================================== --- /dev/null +++ compat/zlib/win32/zlib1.rc @@ -0,0 +1,40 @@ +#include +#include "../zlib.h" + +#ifdef GCC_WINDRES +VS_VERSION_INFO VERSIONINFO +#else +VS_VERSION_INFO VERSIONINFO MOVEABLE IMPURE LOADONCALL DISCARDABLE +#endif + FILEVERSION ZLIB_VER_MAJOR,ZLIB_VER_MINOR,ZLIB_VER_REVISION,0 + PRODUCTVERSION ZLIB_VER_MAJOR,ZLIB_VER_MINOR,ZLIB_VER_REVISION,0 + FILEFLAGSMASK VS_FFI_FILEFLAGSMASK +#ifdef _DEBUG + FILEFLAGS 1 +#else + FILEFLAGS 0 +#endif + FILEOS VOS__WINDOWS32 + FILETYPE VFT_DLL + FILESUBTYPE 0 // not used +BEGIN + BLOCK "StringFileInfo" + BEGIN + BLOCK "040904E4" + //language ID = U.S. English, char set = Windows, Multilingual + BEGIN + VALUE "FileDescription", "zlib data compression library\0" + VALUE "FileVersion", ZLIB_VERSION "\0" + VALUE "InternalName", "zlib1.dll\0" + VALUE "LegalCopyright", "(C) 1995-2017 Jean-loup Gailly & Mark Adler\0" + VALUE "OriginalFilename", "zlib1.dll\0" + VALUE "ProductName", "zlib\0" + VALUE "ProductVersion", ZLIB_VERSION "\0" + VALUE "Comments", "For more information visit http://www.zlib.net/\0" + END + END + BLOCK "VarFileInfo" + BEGIN + VALUE "Translation", 0x0409, 1252 + END +END ADDED compat/zlib/zconf.h Index: compat/zlib/zconf.h ================================================================== --- /dev/null +++ compat/zlib/zconf.h @@ -0,0 +1,534 @@ +/* zconf.h -- configuration of the zlib compression library + * Copyright (C) 1995-2016 Jean-loup Gailly, Mark Adler + * For conditions of distribution and use, see copyright notice in zlib.h + */ + +/* @(#) $Id$ */ + +#ifndef ZCONF_H +#define ZCONF_H + +/* + * If you *really* need a unique prefix for all types and library functions, + * compile with -DZ_PREFIX. The "standard" zlib should be compiled without it. + * Even better than compiling with -DZ_PREFIX would be to use configure to set + * this permanently in zconf.h using "./configure --zprefix". + */ +#ifdef Z_PREFIX /* may be set to #if 1 by ./configure */ +# define Z_PREFIX_SET + +/* all linked symbols and init macros */ +# define _dist_code z__dist_code +# define _length_code z__length_code +# define _tr_align z__tr_align +# define _tr_flush_bits z__tr_flush_bits +# define _tr_flush_block z__tr_flush_block +# define _tr_init z__tr_init +# define _tr_stored_block z__tr_stored_block +# define _tr_tally z__tr_tally +# define adler32 z_adler32 +# define adler32_combine z_adler32_combine +# define adler32_combine64 z_adler32_combine64 +# define adler32_z z_adler32_z +# ifndef Z_SOLO +# define compress z_compress +# define compress2 z_compress2 +# define compressBound z_compressBound +# endif +# define crc32 z_crc32 +# define crc32_combine z_crc32_combine +# define crc32_combine64 z_crc32_combine64 +# define crc32_z z_crc32_z +# define deflate z_deflate +# define deflateBound z_deflateBound +# define deflateCopy z_deflateCopy +# define deflateEnd z_deflateEnd +# define deflateGetDictionary z_deflateGetDictionary +# define deflateInit z_deflateInit +# define deflateInit2 z_deflateInit2 +# define deflateInit2_ z_deflateInit2_ +# define deflateInit_ z_deflateInit_ +# define deflateParams z_deflateParams +# define deflatePending z_deflatePending +# define deflatePrime z_deflatePrime +# define deflateReset z_deflateReset +# define deflateResetKeep z_deflateResetKeep +# define deflateSetDictionary z_deflateSetDictionary +# define deflateSetHeader z_deflateSetHeader +# define deflateTune z_deflateTune +# define deflate_copyright z_deflate_copyright +# define get_crc_table z_get_crc_table +# ifndef Z_SOLO +# define gz_error z_gz_error +# define gz_intmax z_gz_intmax +# define gz_strwinerror z_gz_strwinerror +# define gzbuffer z_gzbuffer +# define gzclearerr z_gzclearerr +# define gzclose z_gzclose +# define gzclose_r z_gzclose_r +# define gzclose_w z_gzclose_w +# define gzdirect z_gzdirect +# define gzdopen z_gzdopen +# define gzeof z_gzeof +# define gzerror z_gzerror +# define gzflush z_gzflush +# define gzfread z_gzfread +# define gzfwrite z_gzfwrite +# define gzgetc z_gzgetc +# define gzgetc_ z_gzgetc_ +# define gzgets z_gzgets +# define gzoffset z_gzoffset +# define gzoffset64 z_gzoffset64 +# define gzopen z_gzopen +# define gzopen64 z_gzopen64 +# ifdef _WIN32 +# define gzopen_w z_gzopen_w +# endif +# define gzprintf z_gzprintf +# define gzputc z_gzputc +# define gzputs z_gzputs +# define gzread z_gzread +# define gzrewind z_gzrewind +# define gzseek z_gzseek +# define gzseek64 z_gzseek64 +# define gzsetparams z_gzsetparams +# define gztell z_gztell +# define gztell64 z_gztell64 +# define gzungetc z_gzungetc +# define gzvprintf z_gzvprintf +# define gzwrite z_gzwrite +# endif +# define inflate z_inflate +# define inflateBack z_inflateBack +# define inflateBackEnd z_inflateBackEnd +# define inflateBackInit z_inflateBackInit +# define inflateBackInit_ z_inflateBackInit_ +# define inflateCodesUsed z_inflateCodesUsed +# define inflateCopy z_inflateCopy +# define inflateEnd z_inflateEnd +# define inflateGetDictionary z_inflateGetDictionary +# define inflateGetHeader z_inflateGetHeader +# define inflateInit z_inflateInit +# define inflateInit2 z_inflateInit2 +# define inflateInit2_ z_inflateInit2_ +# define inflateInit_ z_inflateInit_ +# define inflateMark z_inflateMark +# define inflatePrime z_inflatePrime +# define inflateReset z_inflateReset +# define inflateReset2 z_inflateReset2 +# define inflateResetKeep z_inflateResetKeep +# define inflateSetDictionary z_inflateSetDictionary +# define inflateSync z_inflateSync +# define inflateSyncPoint z_inflateSyncPoint +# define inflateUndermine z_inflateUndermine +# define inflateValidate z_inflateValidate +# define inflate_copyright z_inflate_copyright +# define inflate_fast z_inflate_fast +# define inflate_table z_inflate_table +# ifndef Z_SOLO +# define uncompress z_uncompress +# define uncompress2 z_uncompress2 +# endif +# define zError z_zError +# ifndef Z_SOLO +# define zcalloc z_zcalloc +# define zcfree z_zcfree +# endif +# define zlibCompileFlags z_zlibCompileFlags +# define zlibVersion z_zlibVersion + +/* all zlib typedefs in zlib.h and zconf.h */ +# define Byte z_Byte +# define Bytef z_Bytef +# define alloc_func z_alloc_func +# define charf z_charf +# define free_func z_free_func +# ifndef Z_SOLO +# define gzFile z_gzFile +# endif +# define gz_header z_gz_header +# define gz_headerp z_gz_headerp +# define in_func z_in_func +# define intf z_intf +# define out_func z_out_func +# define uInt z_uInt +# define uIntf z_uIntf +# define uLong z_uLong +# define uLongf z_uLongf +# define voidp z_voidp +# define voidpc z_voidpc +# define voidpf z_voidpf + +/* all zlib structs in zlib.h and zconf.h */ +# define gz_header_s z_gz_header_s +# define internal_state z_internal_state + +#endif + +#if defined(__MSDOS__) && !defined(MSDOS) +# define MSDOS +#endif +#if (defined(OS_2) || defined(__OS2__)) && !defined(OS2) +# define OS2 +#endif +#if defined(_WINDOWS) && !defined(WINDOWS) +# define WINDOWS +#endif +#if defined(_WIN32) || defined(_WIN32_WCE) || defined(__WIN32__) +# ifndef WIN32 +# define WIN32 +# endif +#endif +#if (defined(MSDOS) || defined(OS2) || defined(WINDOWS)) && !defined(WIN32) +# if !defined(__GNUC__) && !defined(__FLAT__) && !defined(__386__) +# ifndef SYS16BIT +# define SYS16BIT +# endif +# endif +#endif + +/* + * Compile with -DMAXSEG_64K if the alloc function cannot allocate more + * than 64k bytes at a time (needed on systems with 16-bit int). + */ +#ifdef SYS16BIT +# define MAXSEG_64K +#endif +#ifdef MSDOS +# define UNALIGNED_OK +#endif + +#ifdef __STDC_VERSION__ +# ifndef STDC +# define STDC +# endif +# if __STDC_VERSION__ >= 199901L +# ifndef STDC99 +# define STDC99 +# endif +# endif +#endif +#if !defined(STDC) && (defined(__STDC__) || defined(__cplusplus)) +# define STDC +#endif +#if !defined(STDC) && (defined(__GNUC__) || defined(__BORLANDC__)) +# define STDC +#endif +#if !defined(STDC) && (defined(MSDOS) || defined(WINDOWS) || defined(WIN32)) +# define STDC +#endif +#if !defined(STDC) && (defined(OS2) || defined(__HOS_AIX__)) +# define STDC +#endif + +#if defined(__OS400__) && !defined(STDC) /* iSeries (formerly AS/400). */ +# define STDC +#endif + +#ifndef STDC +# ifndef const /* cannot use !defined(STDC) && !defined(const) on Mac */ +# define const /* note: need a more gentle solution here */ +# endif +#endif + +#if defined(ZLIB_CONST) && !defined(z_const) +# define z_const const +#else +# define z_const +#endif + +#ifdef Z_SOLO + typedef unsigned long z_size_t; +#else +# define z_longlong long long +# if defined(NO_SIZE_T) + typedef unsigned NO_SIZE_T z_size_t; +# elif defined(STDC) +# include + typedef size_t z_size_t; +# else + typedef unsigned long z_size_t; +# endif +# undef z_longlong +#endif + +/* Maximum value for memLevel in deflateInit2 */ +#ifndef MAX_MEM_LEVEL +# ifdef MAXSEG_64K +# define MAX_MEM_LEVEL 8 +# else +# define MAX_MEM_LEVEL 9 +# endif +#endif + +/* Maximum value for windowBits in deflateInit2 and inflateInit2. + * WARNING: reducing MAX_WBITS makes minigzip unable to extract .gz files + * created by gzip. (Files created by minigzip can still be extracted by + * gzip.) + */ +#ifndef MAX_WBITS +# define MAX_WBITS 15 /* 32K LZ77 window */ +#endif + +/* The memory requirements for deflate are (in bytes): + (1 << (windowBits+2)) + (1 << (memLevel+9)) + that is: 128K for windowBits=15 + 128K for memLevel = 8 (default values) + plus a few kilobytes for small objects. For example, if you want to reduce + the default memory requirements from 256K to 128K, compile with + make CFLAGS="-O -DMAX_WBITS=14 -DMAX_MEM_LEVEL=7" + Of course this will generally degrade compression (there's no free lunch). + + The memory requirements for inflate are (in bytes) 1 << windowBits + that is, 32K for windowBits=15 (default value) plus about 7 kilobytes + for small objects. +*/ + + /* Type declarations */ + +#ifndef OF /* function prototypes */ +# ifdef STDC +# define OF(args) args +# else +# define OF(args) () +# endif +#endif + +#ifndef Z_ARG /* function prototypes for stdarg */ +# if defined(STDC) || defined(Z_HAVE_STDARG_H) +# define Z_ARG(args) args +# else +# define Z_ARG(args) () +# endif +#endif + +/* The following definitions for FAR are needed only for MSDOS mixed + * model programming (small or medium model with some far allocations). + * This was tested only with MSC; for other MSDOS compilers you may have + * to define NO_MEMCPY in zutil.h. If you don't need the mixed model, + * just define FAR to be empty. + */ +#ifdef SYS16BIT +# if defined(M_I86SM) || defined(M_I86MM) + /* MSC small or medium model */ +# define SMALL_MEDIUM +# ifdef _MSC_VER +# define FAR _far +# else +# define FAR far +# endif +# endif +# if (defined(__SMALL__) || defined(__MEDIUM__)) + /* Turbo C small or medium model */ +# define SMALL_MEDIUM +# ifdef __BORLANDC__ +# define FAR _far +# else +# define FAR far +# endif +# endif +#endif + +#if defined(WINDOWS) || defined(WIN32) + /* If building or using zlib as a DLL, define ZLIB_DLL. + * This is not mandatory, but it offers a little performance increase. + */ +# ifdef ZLIB_DLL +# if defined(WIN32) && (!defined(__BORLANDC__) || (__BORLANDC__ >= 0x500)) +# ifdef ZLIB_INTERNAL +# define ZEXTERN extern __declspec(dllexport) +# else +# define ZEXTERN extern __declspec(dllimport) +# endif +# endif +# endif /* ZLIB_DLL */ + /* If building or using zlib with the WINAPI/WINAPIV calling convention, + * define ZLIB_WINAPI. + * Caution: the standard ZLIB1.DLL is NOT compiled using ZLIB_WINAPI. + */ +# ifdef ZLIB_WINAPI +# ifdef FAR +# undef FAR +# endif +# include + /* No need for _export, use ZLIB.DEF instead. */ + /* For complete Windows compatibility, use WINAPI, not __stdcall. */ +# define ZEXPORT WINAPI +# ifdef WIN32 +# define ZEXPORTVA WINAPIV +# else +# define ZEXPORTVA FAR CDECL +# endif +# endif +#endif + +#if defined (__BEOS__) +# ifdef ZLIB_DLL +# ifdef ZLIB_INTERNAL +# define ZEXPORT __declspec(dllexport) +# define ZEXPORTVA __declspec(dllexport) +# else +# define ZEXPORT __declspec(dllimport) +# define ZEXPORTVA __declspec(dllimport) +# endif +# endif +#endif + +#ifndef ZEXTERN +# define ZEXTERN extern +#endif +#ifndef ZEXPORT +# define ZEXPORT +#endif +#ifndef ZEXPORTVA +# define ZEXPORTVA +#endif + +#ifndef FAR +# define FAR +#endif + +#if !defined(__MACTYPES__) +typedef unsigned char Byte; /* 8 bits */ +#endif +typedef unsigned int uInt; /* 16 bits or more */ +typedef unsigned long uLong; /* 32 bits or more */ + +#ifdef SMALL_MEDIUM + /* Borland C/C++ and some old MSC versions ignore FAR inside typedef */ +# define Bytef Byte FAR +#else + typedef Byte FAR Bytef; +#endif +typedef char FAR charf; +typedef int FAR intf; +typedef uInt FAR uIntf; +typedef uLong FAR uLongf; + +#ifdef STDC + typedef void const *voidpc; + typedef void FAR *voidpf; + typedef void *voidp; +#else + typedef Byte const *voidpc; + typedef Byte FAR *voidpf; + typedef Byte *voidp; +#endif + +#if !defined(Z_U4) && !defined(Z_SOLO) && defined(STDC) +# include +# if (UINT_MAX == 0xffffffffUL) +# define Z_U4 unsigned +# elif (ULONG_MAX == 0xffffffffUL) +# define Z_U4 unsigned long +# elif (USHRT_MAX == 0xffffffffUL) +# define Z_U4 unsigned short +# endif +#endif + +#ifdef Z_U4 + typedef Z_U4 z_crc_t; +#else + typedef unsigned long z_crc_t; +#endif + +#ifdef HAVE_UNISTD_H /* may be set to #if 1 by ./configure */ +# define Z_HAVE_UNISTD_H +#endif + +#ifdef HAVE_STDARG_H /* may be set to #if 1 by ./configure */ +# define Z_HAVE_STDARG_H +#endif + +#ifdef STDC +# ifndef Z_SOLO +# include /* for off_t */ +# endif +#endif + +#if defined(STDC) || defined(Z_HAVE_STDARG_H) +# ifndef Z_SOLO +# include /* for va_list */ +# endif +#endif + +#ifdef _WIN32 +# ifndef Z_SOLO +# include /* for wchar_t */ +# endif +#endif + +/* a little trick to accommodate both "#define _LARGEFILE64_SOURCE" and + * "#define _LARGEFILE64_SOURCE 1" as requesting 64-bit operations, (even + * though the former does not conform to the LFS document), but considering + * both "#undef _LARGEFILE64_SOURCE" and "#define _LARGEFILE64_SOURCE 0" as + * equivalently requesting no 64-bit operations + */ +#if defined(_LARGEFILE64_SOURCE) && -_LARGEFILE64_SOURCE - -1 == 1 +# undef _LARGEFILE64_SOURCE +#endif + +#if defined(__WATCOMC__) && !defined(Z_HAVE_UNISTD_H) +# define Z_HAVE_UNISTD_H +#endif +#ifndef Z_SOLO +# if defined(Z_HAVE_UNISTD_H) || defined(_LARGEFILE64_SOURCE) +# include /* for SEEK_*, off_t, and _LFS64_LARGEFILE */ +# ifdef VMS +# include /* for off_t */ +# endif +# ifndef z_off_t +# define z_off_t off_t +# endif +# endif +#endif + +#if defined(_LFS64_LARGEFILE) && _LFS64_LARGEFILE-0 +# define Z_LFS64 +#endif + +#if defined(_LARGEFILE64_SOURCE) && defined(Z_LFS64) +# define Z_LARGE64 +#endif + +#if defined(_FILE_OFFSET_BITS) && _FILE_OFFSET_BITS-0 == 64 && defined(Z_LFS64) +# define Z_WANT64 +#endif + +#if !defined(SEEK_SET) && !defined(Z_SOLO) +# define SEEK_SET 0 /* Seek from beginning of file. */ +# define SEEK_CUR 1 /* Seek from current position. */ +# define SEEK_END 2 /* Set file pointer to EOF plus "offset" */ +#endif + +#ifndef z_off_t +# define z_off_t long +#endif + +#if !defined(_WIN32) && defined(Z_LARGE64) +# define z_off64_t off64_t +#else +# if defined(_WIN32) && !defined(__GNUC__) && !defined(Z_SOLO) +# define z_off64_t __int64 +# else +# define z_off64_t z_off_t +# endif +#endif + +/* MVS linker does not support external names larger than 8 bytes */ +#if defined(__MVS__) + #pragma map(deflateInit_,"DEIN") + #pragma map(deflateInit2_,"DEIN2") + #pragma map(deflateEnd,"DEEND") + #pragma map(deflateBound,"DEBND") + #pragma map(inflateInit_,"ININ") + #pragma map(inflateInit2_,"ININ2") + #pragma map(inflateEnd,"INEND") + #pragma map(inflateSync,"INSY") + #pragma map(inflateSetDictionary,"INSEDI") + #pragma map(compressBound,"CMBND") + #pragma map(inflate_table,"INTABL") + #pragma map(inflate_fast,"INFA") + #pragma map(inflate_copyright,"INCOPY") +#endif + +#endif /* ZCONF_H */ ADDED compat/zlib/zconf.h.cmakein Index: compat/zlib/zconf.h.cmakein ================================================================== --- /dev/null +++ compat/zlib/zconf.h.cmakein @@ -0,0 +1,536 @@ +/* zconf.h -- configuration of the zlib compression library + * Copyright (C) 1995-2016 Jean-loup Gailly, Mark Adler + * For conditions of distribution and use, see copyright notice in zlib.h + */ + +/* @(#) $Id$ */ + +#ifndef ZCONF_H +#define ZCONF_H +#cmakedefine Z_PREFIX +#cmakedefine Z_HAVE_UNISTD_H + +/* + * If you *really* need a unique prefix for all types and library functions, + * compile with -DZ_PREFIX. The "standard" zlib should be compiled without it. + * Even better than compiling with -DZ_PREFIX would be to use configure to set + * this permanently in zconf.h using "./configure --zprefix". + */ +#ifdef Z_PREFIX /* may be set to #if 1 by ./configure */ +# define Z_PREFIX_SET + +/* all linked symbols and init macros */ +# define _dist_code z__dist_code +# define _length_code z__length_code +# define _tr_align z__tr_align +# define _tr_flush_bits z__tr_flush_bits +# define _tr_flush_block z__tr_flush_block +# define _tr_init z__tr_init +# define _tr_stored_block z__tr_stored_block +# define _tr_tally z__tr_tally +# define adler32 z_adler32 +# define adler32_combine z_adler32_combine +# define adler32_combine64 z_adler32_combine64 +# define adler32_z z_adler32_z +# ifndef Z_SOLO +# define compress z_compress +# define compress2 z_compress2 +# define compressBound z_compressBound +# endif +# define crc32 z_crc32 +# define crc32_combine z_crc32_combine +# define crc32_combine64 z_crc32_combine64 +# define crc32_z z_crc32_z +# define deflate z_deflate +# define deflateBound z_deflateBound +# define deflateCopy z_deflateCopy +# define deflateEnd z_deflateEnd +# define deflateGetDictionary z_deflateGetDictionary +# define deflateInit z_deflateInit +# define deflateInit2 z_deflateInit2 +# define deflateInit2_ z_deflateInit2_ +# define deflateInit_ z_deflateInit_ +# define deflateParams z_deflateParams +# define deflatePending z_deflatePending +# define deflatePrime z_deflatePrime +# define deflateReset z_deflateReset +# define deflateResetKeep z_deflateResetKeep +# define deflateSetDictionary z_deflateSetDictionary +# define deflateSetHeader z_deflateSetHeader +# define deflateTune z_deflateTune +# define deflate_copyright z_deflate_copyright +# define get_crc_table z_get_crc_table +# ifndef Z_SOLO +# define gz_error z_gz_error +# define gz_intmax z_gz_intmax +# define gz_strwinerror z_gz_strwinerror +# define gzbuffer z_gzbuffer +# define gzclearerr z_gzclearerr +# define gzclose z_gzclose +# define gzclose_r z_gzclose_r +# define gzclose_w z_gzclose_w +# define gzdirect z_gzdirect +# define gzdopen z_gzdopen +# define gzeof z_gzeof +# define gzerror z_gzerror +# define gzflush z_gzflush +# define gzfread z_gzfread +# define gzfwrite z_gzfwrite +# define gzgetc z_gzgetc +# define gzgetc_ z_gzgetc_ +# define gzgets z_gzgets +# define gzoffset z_gzoffset +# define gzoffset64 z_gzoffset64 +# define gzopen z_gzopen +# define gzopen64 z_gzopen64 +# ifdef _WIN32 +# define gzopen_w z_gzopen_w +# endif +# define gzprintf z_gzprintf +# define gzputc z_gzputc +# define gzputs z_gzputs +# define gzread z_gzread +# define gzrewind z_gzrewind +# define gzseek z_gzseek +# define gzseek64 z_gzseek64 +# define gzsetparams z_gzsetparams +# define gztell z_gztell +# define gztell64 z_gztell64 +# define gzungetc z_gzungetc +# define gzvprintf z_gzvprintf +# define gzwrite z_gzwrite +# endif +# define inflate z_inflate +# define inflateBack z_inflateBack +# define inflateBackEnd z_inflateBackEnd +# define inflateBackInit z_inflateBackInit +# define inflateBackInit_ z_inflateBackInit_ +# define inflateCodesUsed z_inflateCodesUsed +# define inflateCopy z_inflateCopy +# define inflateEnd z_inflateEnd +# define inflateGetDictionary z_inflateGetDictionary +# define inflateGetHeader z_inflateGetHeader +# define inflateInit z_inflateInit +# define inflateInit2 z_inflateInit2 +# define inflateInit2_ z_inflateInit2_ +# define inflateInit_ z_inflateInit_ +# define inflateMark z_inflateMark +# define inflatePrime z_inflatePrime +# define inflateReset z_inflateReset +# define inflateReset2 z_inflateReset2 +# define inflateResetKeep z_inflateResetKeep +# define inflateSetDictionary z_inflateSetDictionary +# define inflateSync z_inflateSync +# define inflateSyncPoint z_inflateSyncPoint +# define inflateUndermine z_inflateUndermine +# define inflateValidate z_inflateValidate +# define inflate_copyright z_inflate_copyright +# define inflate_fast z_inflate_fast +# define inflate_table z_inflate_table +# ifndef Z_SOLO +# define uncompress z_uncompress +# define uncompress2 z_uncompress2 +# endif +# define zError z_zError +# ifndef Z_SOLO +# define zcalloc z_zcalloc +# define zcfree z_zcfree +# endif +# define zlibCompileFlags z_zlibCompileFlags +# define zlibVersion z_zlibVersion + +/* all zlib typedefs in zlib.h and zconf.h */ +# define Byte z_Byte +# define Bytef z_Bytef +# define alloc_func z_alloc_func +# define charf z_charf +# define free_func z_free_func +# ifndef Z_SOLO +# define gzFile z_gzFile +# endif +# define gz_header z_gz_header +# define gz_headerp z_gz_headerp +# define in_func z_in_func +# define intf z_intf +# define out_func z_out_func +# define uInt z_uInt +# define uIntf z_uIntf +# define uLong z_uLong +# define uLongf z_uLongf +# define voidp z_voidp +# define voidpc z_voidpc +# define voidpf z_voidpf + +/* all zlib structs in zlib.h and zconf.h */ +# define gz_header_s z_gz_header_s +# define internal_state z_internal_state + +#endif + +#if defined(__MSDOS__) && !defined(MSDOS) +# define MSDOS +#endif +#if (defined(OS_2) || defined(__OS2__)) && !defined(OS2) +# define OS2 +#endif +#if defined(_WINDOWS) && !defined(WINDOWS) +# define WINDOWS +#endif +#if defined(_WIN32) || defined(_WIN32_WCE) || defined(__WIN32__) +# ifndef WIN32 +# define WIN32 +# endif +#endif +#if (defined(MSDOS) || defined(OS2) || defined(WINDOWS)) && !defined(WIN32) +# if !defined(__GNUC__) && !defined(__FLAT__) && !defined(__386__) +# ifndef SYS16BIT +# define SYS16BIT +# endif +# endif +#endif + +/* + * Compile with -DMAXSEG_64K if the alloc function cannot allocate more + * than 64k bytes at a time (needed on systems with 16-bit int). + */ +#ifdef SYS16BIT +# define MAXSEG_64K +#endif +#ifdef MSDOS +# define UNALIGNED_OK +#endif + +#ifdef __STDC_VERSION__ +# ifndef STDC +# define STDC +# endif +# if __STDC_VERSION__ >= 199901L +# ifndef STDC99 +# define STDC99 +# endif +# endif +#endif +#if !defined(STDC) && (defined(__STDC__) || defined(__cplusplus)) +# define STDC +#endif +#if !defined(STDC) && (defined(__GNUC__) || defined(__BORLANDC__)) +# define STDC +#endif +#if !defined(STDC) && (defined(MSDOS) || defined(WINDOWS) || defined(WIN32)) +# define STDC +#endif +#if !defined(STDC) && (defined(OS2) || defined(__HOS_AIX__)) +# define STDC +#endif + +#if defined(__OS400__) && !defined(STDC) /* iSeries (formerly AS/400). */ +# define STDC +#endif + +#ifndef STDC +# ifndef const /* cannot use !defined(STDC) && !defined(const) on Mac */ +# define const /* note: need a more gentle solution here */ +# endif +#endif + +#if defined(ZLIB_CONST) && !defined(z_const) +# define z_const const +#else +# define z_const +#endif + +#ifdef Z_SOLO + typedef unsigned long z_size_t; +#else +# define z_longlong long long +# if defined(NO_SIZE_T) + typedef unsigned NO_SIZE_T z_size_t; +# elif defined(STDC) +# include + typedef size_t z_size_t; +# else + typedef unsigned long z_size_t; +# endif +# undef z_longlong +#endif + +/* Maximum value for memLevel in deflateInit2 */ +#ifndef MAX_MEM_LEVEL +# ifdef MAXSEG_64K +# define MAX_MEM_LEVEL 8 +# else +# define MAX_MEM_LEVEL 9 +# endif +#endif + +/* Maximum value for windowBits in deflateInit2 and inflateInit2. + * WARNING: reducing MAX_WBITS makes minigzip unable to extract .gz files + * created by gzip. (Files created by minigzip can still be extracted by + * gzip.) + */ +#ifndef MAX_WBITS +# define MAX_WBITS 15 /* 32K LZ77 window */ +#endif + +/* The memory requirements for deflate are (in bytes): + (1 << (windowBits+2)) + (1 << (memLevel+9)) + that is: 128K for windowBits=15 + 128K for memLevel = 8 (default values) + plus a few kilobytes for small objects. For example, if you want to reduce + the default memory requirements from 256K to 128K, compile with + make CFLAGS="-O -DMAX_WBITS=14 -DMAX_MEM_LEVEL=7" + Of course this will generally degrade compression (there's no free lunch). + + The memory requirements for inflate are (in bytes) 1 << windowBits + that is, 32K for windowBits=15 (default value) plus about 7 kilobytes + for small objects. +*/ + + /* Type declarations */ + +#ifndef OF /* function prototypes */ +# ifdef STDC +# define OF(args) args +# else +# define OF(args) () +# endif +#endif + +#ifndef Z_ARG /* function prototypes for stdarg */ +# if defined(STDC) || defined(Z_HAVE_STDARG_H) +# define Z_ARG(args) args +# else +# define Z_ARG(args) () +# endif +#endif + +/* The following definitions for FAR are needed only for MSDOS mixed + * model programming (small or medium model with some far allocations). + * This was tested only with MSC; for other MSDOS compilers you may have + * to define NO_MEMCPY in zutil.h. If you don't need the mixed model, + * just define FAR to be empty. + */ +#ifdef SYS16BIT +# if defined(M_I86SM) || defined(M_I86MM) + /* MSC small or medium model */ +# define SMALL_MEDIUM +# ifdef _MSC_VER +# define FAR _far +# else +# define FAR far +# endif +# endif +# if (defined(__SMALL__) || defined(__MEDIUM__)) + /* Turbo C small or medium model */ +# define SMALL_MEDIUM +# ifdef __BORLANDC__ +# define FAR _far +# else +# define FAR far +# endif +# endif +#endif + +#if defined(WINDOWS) || defined(WIN32) + /* If building or using zlib as a DLL, define ZLIB_DLL. + * This is not mandatory, but it offers a little performance increase. + */ +# ifdef ZLIB_DLL +# if defined(WIN32) && (!defined(__BORLANDC__) || (__BORLANDC__ >= 0x500)) +# ifdef ZLIB_INTERNAL +# define ZEXTERN extern __declspec(dllexport) +# else +# define ZEXTERN extern __declspec(dllimport) +# endif +# endif +# endif /* ZLIB_DLL */ + /* If building or using zlib with the WINAPI/WINAPIV calling convention, + * define ZLIB_WINAPI. + * Caution: the standard ZLIB1.DLL is NOT compiled using ZLIB_WINAPI. + */ +# ifdef ZLIB_WINAPI +# ifdef FAR +# undef FAR +# endif +# include + /* No need for _export, use ZLIB.DEF instead. */ + /* For complete Windows compatibility, use WINAPI, not __stdcall. */ +# define ZEXPORT WINAPI +# ifdef WIN32 +# define ZEXPORTVA WINAPIV +# else +# define ZEXPORTVA FAR CDECL +# endif +# endif +#endif + +#if defined (__BEOS__) +# ifdef ZLIB_DLL +# ifdef ZLIB_INTERNAL +# define ZEXPORT __declspec(dllexport) +# define ZEXPORTVA __declspec(dllexport) +# else +# define ZEXPORT __declspec(dllimport) +# define ZEXPORTVA __declspec(dllimport) +# endif +# endif +#endif + +#ifndef ZEXTERN +# define ZEXTERN extern +#endif +#ifndef ZEXPORT +# define ZEXPORT +#endif +#ifndef ZEXPORTVA +# define ZEXPORTVA +#endif + +#ifndef FAR +# define FAR +#endif + +#if !defined(__MACTYPES__) +typedef unsigned char Byte; /* 8 bits */ +#endif +typedef unsigned int uInt; /* 16 bits or more */ +typedef unsigned long uLong; /* 32 bits or more */ + +#ifdef SMALL_MEDIUM + /* Borland C/C++ and some old MSC versions ignore FAR inside typedef */ +# define Bytef Byte FAR +#else + typedef Byte FAR Bytef; +#endif +typedef char FAR charf; +typedef int FAR intf; +typedef uInt FAR uIntf; +typedef uLong FAR uLongf; + +#ifdef STDC + typedef void const *voidpc; + typedef void FAR *voidpf; + typedef void *voidp; +#else + typedef Byte const *voidpc; + typedef Byte FAR *voidpf; + typedef Byte *voidp; +#endif + +#if !defined(Z_U4) && !defined(Z_SOLO) && defined(STDC) +# include +# if (UINT_MAX == 0xffffffffUL) +# define Z_U4 unsigned +# elif (ULONG_MAX == 0xffffffffUL) +# define Z_U4 unsigned long +# elif (USHRT_MAX == 0xffffffffUL) +# define Z_U4 unsigned short +# endif +#endif + +#ifdef Z_U4 + typedef Z_U4 z_crc_t; +#else + typedef unsigned long z_crc_t; +#endif + +#ifdef HAVE_UNISTD_H /* may be set to #if 1 by ./configure */ +# define Z_HAVE_UNISTD_H +#endif + +#ifdef HAVE_STDARG_H /* may be set to #if 1 by ./configure */ +# define Z_HAVE_STDARG_H +#endif + +#ifdef STDC +# ifndef Z_SOLO +# include /* for off_t */ +# endif +#endif + +#if defined(STDC) || defined(Z_HAVE_STDARG_H) +# ifndef Z_SOLO +# include /* for va_list */ +# endif +#endif + +#ifdef _WIN32 +# ifndef Z_SOLO +# include /* for wchar_t */ +# endif +#endif + +/* a little trick to accommodate both "#define _LARGEFILE64_SOURCE" and + * "#define _LARGEFILE64_SOURCE 1" as requesting 64-bit operations, (even + * though the former does not conform to the LFS document), but considering + * both "#undef _LARGEFILE64_SOURCE" and "#define _LARGEFILE64_SOURCE 0" as + * equivalently requesting no 64-bit operations + */ +#if defined(_LARGEFILE64_SOURCE) && -_LARGEFILE64_SOURCE - -1 == 1 +# undef _LARGEFILE64_SOURCE +#endif + +#if defined(__WATCOMC__) && !defined(Z_HAVE_UNISTD_H) +# define Z_HAVE_UNISTD_H +#endif +#ifndef Z_SOLO +# if defined(Z_HAVE_UNISTD_H) || defined(_LARGEFILE64_SOURCE) +# include /* for SEEK_*, off_t, and _LFS64_LARGEFILE */ +# ifdef VMS +# include /* for off_t */ +# endif +# ifndef z_off_t +# define z_off_t off_t +# endif +# endif +#endif + +#if defined(_LFS64_LARGEFILE) && _LFS64_LARGEFILE-0 +# define Z_LFS64 +#endif + +#if defined(_LARGEFILE64_SOURCE) && defined(Z_LFS64) +# define Z_LARGE64 +#endif + +#if defined(_FILE_OFFSET_BITS) && _FILE_OFFSET_BITS-0 == 64 && defined(Z_LFS64) +# define Z_WANT64 +#endif + +#if !defined(SEEK_SET) && !defined(Z_SOLO) +# define SEEK_SET 0 /* Seek from beginning of file. */ +# define SEEK_CUR 1 /* Seek from current position. */ +# define SEEK_END 2 /* Set file pointer to EOF plus "offset" */ +#endif + +#ifndef z_off_t +# define z_off_t long +#endif + +#if !defined(_WIN32) && defined(Z_LARGE64) +# define z_off64_t off64_t +#else +# if defined(_WIN32) && !defined(__GNUC__) && !defined(Z_SOLO) +# define z_off64_t __int64 +# else +# define z_off64_t z_off_t +# endif +#endif + +/* MVS linker does not support external names larger than 8 bytes */ +#if defined(__MVS__) + #pragma map(deflateInit_,"DEIN") + #pragma map(deflateInit2_,"DEIN2") + #pragma map(deflateEnd,"DEEND") + #pragma map(deflateBound,"DEBND") + #pragma map(inflateInit_,"ININ") + #pragma map(inflateInit2_,"ININ2") + #pragma map(inflateEnd,"INEND") + #pragma map(inflateSync,"INSY") + #pragma map(inflateSetDictionary,"INSEDI") + #pragma map(compressBound,"CMBND") + #pragma map(inflate_table,"INTABL") + #pragma map(inflate_fast,"INFA") + #pragma map(inflate_copyright,"INCOPY") +#endif + +#endif /* ZCONF_H */ ADDED compat/zlib/zconf.h.in Index: compat/zlib/zconf.h.in ================================================================== --- /dev/null +++ compat/zlib/zconf.h.in @@ -0,0 +1,534 @@ +/* zconf.h -- configuration of the zlib compression library + * Copyright (C) 1995-2016 Jean-loup Gailly, Mark Adler + * For conditions of distribution and use, see copyright notice in zlib.h + */ + +/* @(#) $Id$ */ + +#ifndef ZCONF_H +#define ZCONF_H + +/* + * If you *really* need a unique prefix for all types and library functions, + * compile with -DZ_PREFIX. The "standard" zlib should be compiled without it. + * Even better than compiling with -DZ_PREFIX would be to use configure to set + * this permanently in zconf.h using "./configure --zprefix". + */ +#ifdef Z_PREFIX /* may be set to #if 1 by ./configure */ +# define Z_PREFIX_SET + +/* all linked symbols and init macros */ +# define _dist_code z__dist_code +# define _length_code z__length_code +# define _tr_align z__tr_align +# define _tr_flush_bits z__tr_flush_bits +# define _tr_flush_block z__tr_flush_block +# define _tr_init z__tr_init +# define _tr_stored_block z__tr_stored_block +# define _tr_tally z__tr_tally +# define adler32 z_adler32 +# define adler32_combine z_adler32_combine +# define adler32_combine64 z_adler32_combine64 +# define adler32_z z_adler32_z +# ifndef Z_SOLO +# define compress z_compress +# define compress2 z_compress2 +# define compressBound z_compressBound +# endif +# define crc32 z_crc32 +# define crc32_combine z_crc32_combine +# define crc32_combine64 z_crc32_combine64 +# define crc32_z z_crc32_z +# define deflate z_deflate +# define deflateBound z_deflateBound +# define deflateCopy z_deflateCopy +# define deflateEnd z_deflateEnd +# define deflateGetDictionary z_deflateGetDictionary +# define deflateInit z_deflateInit +# define deflateInit2 z_deflateInit2 +# define deflateInit2_ z_deflateInit2_ +# define deflateInit_ z_deflateInit_ +# define deflateParams z_deflateParams +# define deflatePending z_deflatePending +# define deflatePrime z_deflatePrime +# define deflateReset z_deflateReset +# define deflateResetKeep z_deflateResetKeep +# define deflateSetDictionary z_deflateSetDictionary +# define deflateSetHeader z_deflateSetHeader +# define deflateTune z_deflateTune +# define deflate_copyright z_deflate_copyright +# define get_crc_table z_get_crc_table +# ifndef Z_SOLO +# define gz_error z_gz_error +# define gz_intmax z_gz_intmax +# define gz_strwinerror z_gz_strwinerror +# define gzbuffer z_gzbuffer +# define gzclearerr z_gzclearerr +# define gzclose z_gzclose +# define gzclose_r z_gzclose_r +# define gzclose_w z_gzclose_w +# define gzdirect z_gzdirect +# define gzdopen z_gzdopen +# define gzeof z_gzeof +# define gzerror z_gzerror +# define gzflush z_gzflush +# define gzfread z_gzfread +# define gzfwrite z_gzfwrite +# define gzgetc z_gzgetc +# define gzgetc_ z_gzgetc_ +# define gzgets z_gzgets +# define gzoffset z_gzoffset +# define gzoffset64 z_gzoffset64 +# define gzopen z_gzopen +# define gzopen64 z_gzopen64 +# ifdef _WIN32 +# define gzopen_w z_gzopen_w +# endif +# define gzprintf z_gzprintf +# define gzputc z_gzputc +# define gzputs z_gzputs +# define gzread z_gzread +# define gzrewind z_gzrewind +# define gzseek z_gzseek +# define gzseek64 z_gzseek64 +# define gzsetparams z_gzsetparams +# define gztell z_gztell +# define gztell64 z_gztell64 +# define gzungetc z_gzungetc +# define gzvprintf z_gzvprintf +# define gzwrite z_gzwrite +# endif +# define inflate z_inflate +# define inflateBack z_inflateBack +# define inflateBackEnd z_inflateBackEnd +# define inflateBackInit z_inflateBackInit +# define inflateBackInit_ z_inflateBackInit_ +# define inflateCodesUsed z_inflateCodesUsed +# define inflateCopy z_inflateCopy +# define inflateEnd z_inflateEnd +# define inflateGetDictionary z_inflateGetDictionary +# define inflateGetHeader z_inflateGetHeader +# define inflateInit z_inflateInit +# define inflateInit2 z_inflateInit2 +# define inflateInit2_ z_inflateInit2_ +# define inflateInit_ z_inflateInit_ +# define inflateMark z_inflateMark +# define inflatePrime z_inflatePrime +# define inflateReset z_inflateReset +# define inflateReset2 z_inflateReset2 +# define inflateResetKeep z_inflateResetKeep +# define inflateSetDictionary z_inflateSetDictionary +# define inflateSync z_inflateSync +# define inflateSyncPoint z_inflateSyncPoint +# define inflateUndermine z_inflateUndermine +# define inflateValidate z_inflateValidate +# define inflate_copyright z_inflate_copyright +# define inflate_fast z_inflate_fast +# define inflate_table z_inflate_table +# ifndef Z_SOLO +# define uncompress z_uncompress +# define uncompress2 z_uncompress2 +# endif +# define zError z_zError +# ifndef Z_SOLO +# define zcalloc z_zcalloc +# define zcfree z_zcfree +# endif +# define zlibCompileFlags z_zlibCompileFlags +# define zlibVersion z_zlibVersion + +/* all zlib typedefs in zlib.h and zconf.h */ +# define Byte z_Byte +# define Bytef z_Bytef +# define alloc_func z_alloc_func +# define charf z_charf +# define free_func z_free_func +# ifndef Z_SOLO +# define gzFile z_gzFile +# endif +# define gz_header z_gz_header +# define gz_headerp z_gz_headerp +# define in_func z_in_func +# define intf z_intf +# define out_func z_out_func +# define uInt z_uInt +# define uIntf z_uIntf +# define uLong z_uLong +# define uLongf z_uLongf +# define voidp z_voidp +# define voidpc z_voidpc +# define voidpf z_voidpf + +/* all zlib structs in zlib.h and zconf.h */ +# define gz_header_s z_gz_header_s +# define internal_state z_internal_state + +#endif + +#if defined(__MSDOS__) && !defined(MSDOS) +# define MSDOS +#endif +#if (defined(OS_2) || defined(__OS2__)) && !defined(OS2) +# define OS2 +#endif +#if defined(_WINDOWS) && !defined(WINDOWS) +# define WINDOWS +#endif +#if defined(_WIN32) || defined(_WIN32_WCE) || defined(__WIN32__) +# ifndef WIN32 +# define WIN32 +# endif +#endif +#if (defined(MSDOS) || defined(OS2) || defined(WINDOWS)) && !defined(WIN32) +# if !defined(__GNUC__) && !defined(__FLAT__) && !defined(__386__) +# ifndef SYS16BIT +# define SYS16BIT +# endif +# endif +#endif + +/* + * Compile with -DMAXSEG_64K if the alloc function cannot allocate more + * than 64k bytes at a time (needed on systems with 16-bit int). + */ +#ifdef SYS16BIT +# define MAXSEG_64K +#endif +#ifdef MSDOS +# define UNALIGNED_OK +#endif + +#ifdef __STDC_VERSION__ +# ifndef STDC +# define STDC +# endif +# if __STDC_VERSION__ >= 199901L +# ifndef STDC99 +# define STDC99 +# endif +# endif +#endif +#if !defined(STDC) && (defined(__STDC__) || defined(__cplusplus)) +# define STDC +#endif +#if !defined(STDC) && (defined(__GNUC__) || defined(__BORLANDC__)) +# define STDC +#endif +#if !defined(STDC) && (defined(MSDOS) || defined(WINDOWS) || defined(WIN32)) +# define STDC +#endif +#if !defined(STDC) && (defined(OS2) || defined(__HOS_AIX__)) +# define STDC +#endif + +#if defined(__OS400__) && !defined(STDC) /* iSeries (formerly AS/400). */ +# define STDC +#endif + +#ifndef STDC +# ifndef const /* cannot use !defined(STDC) && !defined(const) on Mac */ +# define const /* note: need a more gentle solution here */ +# endif +#endif + +#if defined(ZLIB_CONST) && !defined(z_const) +# define z_const const +#else +# define z_const +#endif + +#ifdef Z_SOLO + typedef unsigned long z_size_t; +#else +# define z_longlong long long +# if defined(NO_SIZE_T) + typedef unsigned NO_SIZE_T z_size_t; +# elif defined(STDC) +# include + typedef size_t z_size_t; +# else + typedef unsigned long z_size_t; +# endif +# undef z_longlong +#endif + +/* Maximum value for memLevel in deflateInit2 */ +#ifndef MAX_MEM_LEVEL +# ifdef MAXSEG_64K +# define MAX_MEM_LEVEL 8 +# else +# define MAX_MEM_LEVEL 9 +# endif +#endif + +/* Maximum value for windowBits in deflateInit2 and inflateInit2. + * WARNING: reducing MAX_WBITS makes minigzip unable to extract .gz files + * created by gzip. (Files created by minigzip can still be extracted by + * gzip.) + */ +#ifndef MAX_WBITS +# define MAX_WBITS 15 /* 32K LZ77 window */ +#endif + +/* The memory requirements for deflate are (in bytes): + (1 << (windowBits+2)) + (1 << (memLevel+9)) + that is: 128K for windowBits=15 + 128K for memLevel = 8 (default values) + plus a few kilobytes for small objects. For example, if you want to reduce + the default memory requirements from 256K to 128K, compile with + make CFLAGS="-O -DMAX_WBITS=14 -DMAX_MEM_LEVEL=7" + Of course this will generally degrade compression (there's no free lunch). + + The memory requirements for inflate are (in bytes) 1 << windowBits + that is, 32K for windowBits=15 (default value) plus about 7 kilobytes + for small objects. +*/ + + /* Type declarations */ + +#ifndef OF /* function prototypes */ +# ifdef STDC +# define OF(args) args +# else +# define OF(args) () +# endif +#endif + +#ifndef Z_ARG /* function prototypes for stdarg */ +# if defined(STDC) || defined(Z_HAVE_STDARG_H) +# define Z_ARG(args) args +# else +# define Z_ARG(args) () +# endif +#endif + +/* The following definitions for FAR are needed only for MSDOS mixed + * model programming (small or medium model with some far allocations). + * This was tested only with MSC; for other MSDOS compilers you may have + * to define NO_MEMCPY in zutil.h. If you don't need the mixed model, + * just define FAR to be empty. + */ +#ifdef SYS16BIT +# if defined(M_I86SM) || defined(M_I86MM) + /* MSC small or medium model */ +# define SMALL_MEDIUM +# ifdef _MSC_VER +# define FAR _far +# else +# define FAR far +# endif +# endif +# if (defined(__SMALL__) || defined(__MEDIUM__)) + /* Turbo C small or medium model */ +# define SMALL_MEDIUM +# ifdef __BORLANDC__ +# define FAR _far +# else +# define FAR far +# endif +# endif +#endif + +#if defined(WINDOWS) || defined(WIN32) + /* If building or using zlib as a DLL, define ZLIB_DLL. + * This is not mandatory, but it offers a little performance increase. + */ +# ifdef ZLIB_DLL +# if defined(WIN32) && (!defined(__BORLANDC__) || (__BORLANDC__ >= 0x500)) +# ifdef ZLIB_INTERNAL +# define ZEXTERN extern __declspec(dllexport) +# else +# define ZEXTERN extern __declspec(dllimport) +# endif +# endif +# endif /* ZLIB_DLL */ + /* If building or using zlib with the WINAPI/WINAPIV calling convention, + * define ZLIB_WINAPI. + * Caution: the standard ZLIB1.DLL is NOT compiled using ZLIB_WINAPI. + */ +# ifdef ZLIB_WINAPI +# ifdef FAR +# undef FAR +# endif +# include + /* No need for _export, use ZLIB.DEF instead. */ + /* For complete Windows compatibility, use WINAPI, not __stdcall. */ +# define ZEXPORT WINAPI +# ifdef WIN32 +# define ZEXPORTVA WINAPIV +# else +# define ZEXPORTVA FAR CDECL +# endif +# endif +#endif + +#if defined (__BEOS__) +# ifdef ZLIB_DLL +# ifdef ZLIB_INTERNAL +# define ZEXPORT __declspec(dllexport) +# define ZEXPORTVA __declspec(dllexport) +# else +# define ZEXPORT __declspec(dllimport) +# define ZEXPORTVA __declspec(dllimport) +# endif +# endif +#endif + +#ifndef ZEXTERN +# define ZEXTERN extern +#endif +#ifndef ZEXPORT +# define ZEXPORT +#endif +#ifndef ZEXPORTVA +# define ZEXPORTVA +#endif + +#ifndef FAR +# define FAR +#endif + +#if !defined(__MACTYPES__) +typedef unsigned char Byte; /* 8 bits */ +#endif +typedef unsigned int uInt; /* 16 bits or more */ +typedef unsigned long uLong; /* 32 bits or more */ + +#ifdef SMALL_MEDIUM + /* Borland C/C++ and some old MSC versions ignore FAR inside typedef */ +# define Bytef Byte FAR +#else + typedef Byte FAR Bytef; +#endif +typedef char FAR charf; +typedef int FAR intf; +typedef uInt FAR uIntf; +typedef uLong FAR uLongf; + +#ifdef STDC + typedef void const *voidpc; + typedef void FAR *voidpf; + typedef void *voidp; +#else + typedef Byte const *voidpc; + typedef Byte FAR *voidpf; + typedef Byte *voidp; +#endif + +#if !defined(Z_U4) && !defined(Z_SOLO) && defined(STDC) +# include +# if (UINT_MAX == 0xffffffffUL) +# define Z_U4 unsigned +# elif (ULONG_MAX == 0xffffffffUL) +# define Z_U4 unsigned long +# elif (USHRT_MAX == 0xffffffffUL) +# define Z_U4 unsigned short +# endif +#endif + +#ifdef Z_U4 + typedef Z_U4 z_crc_t; +#else + typedef unsigned long z_crc_t; +#endif + +#ifdef HAVE_UNISTD_H /* may be set to #if 1 by ./configure */ +# define Z_HAVE_UNISTD_H +#endif + +#ifdef HAVE_STDARG_H /* may be set to #if 1 by ./configure */ +# define Z_HAVE_STDARG_H +#endif + +#ifdef STDC +# ifndef Z_SOLO +# include /* for off_t */ +# endif +#endif + +#if defined(STDC) || defined(Z_HAVE_STDARG_H) +# ifndef Z_SOLO +# include /* for va_list */ +# endif +#endif + +#ifdef _WIN32 +# ifndef Z_SOLO +# include /* for wchar_t */ +# endif +#endif + +/* a little trick to accommodate both "#define _LARGEFILE64_SOURCE" and + * "#define _LARGEFILE64_SOURCE 1" as requesting 64-bit operations, (even + * though the former does not conform to the LFS document), but considering + * both "#undef _LARGEFILE64_SOURCE" and "#define _LARGEFILE64_SOURCE 0" as + * equivalently requesting no 64-bit operations + */ +#if defined(_LARGEFILE64_SOURCE) && -_LARGEFILE64_SOURCE - -1 == 1 +# undef _LARGEFILE64_SOURCE +#endif + +#if defined(__WATCOMC__) && !defined(Z_HAVE_UNISTD_H) +# define Z_HAVE_UNISTD_H +#endif +#ifndef Z_SOLO +# if defined(Z_HAVE_UNISTD_H) || defined(_LARGEFILE64_SOURCE) +# include /* for SEEK_*, off_t, and _LFS64_LARGEFILE */ +# ifdef VMS +# include /* for off_t */ +# endif +# ifndef z_off_t +# define z_off_t off_t +# endif +# endif +#endif + +#if defined(_LFS64_LARGEFILE) && _LFS64_LARGEFILE-0 +# define Z_LFS64 +#endif + +#if defined(_LARGEFILE64_SOURCE) && defined(Z_LFS64) +# define Z_LARGE64 +#endif + +#if defined(_FILE_OFFSET_BITS) && _FILE_OFFSET_BITS-0 == 64 && defined(Z_LFS64) +# define Z_WANT64 +#endif + +#if !defined(SEEK_SET) && !defined(Z_SOLO) +# define SEEK_SET 0 /* Seek from beginning of file. */ +# define SEEK_CUR 1 /* Seek from current position. */ +# define SEEK_END 2 /* Set file pointer to EOF plus "offset" */ +#endif + +#ifndef z_off_t +# define z_off_t long +#endif + +#if !defined(_WIN32) && defined(Z_LARGE64) +# define z_off64_t off64_t +#else +# if defined(_WIN32) && !defined(__GNUC__) && !defined(Z_SOLO) +# define z_off64_t __int64 +# else +# define z_off64_t z_off_t +# endif +#endif + +/* MVS linker does not support external names larger than 8 bytes */ +#if defined(__MVS__) + #pragma map(deflateInit_,"DEIN") + #pragma map(deflateInit2_,"DEIN2") + #pragma map(deflateEnd,"DEEND") + #pragma map(deflateBound,"DEBND") + #pragma map(inflateInit_,"ININ") + #pragma map(inflateInit2_,"ININ2") + #pragma map(inflateEnd,"INEND") + #pragma map(inflateSync,"INSY") + #pragma map(inflateSetDictionary,"INSEDI") + #pragma map(compressBound,"CMBND") + #pragma map(inflate_table,"INTABL") + #pragma map(inflate_fast,"INFA") + #pragma map(inflate_copyright,"INCOPY") +#endif + +#endif /* ZCONF_H */ ADDED compat/zlib/zlib.3 Index: compat/zlib/zlib.3 ================================================================== --- /dev/null +++ compat/zlib/zlib.3 @@ -0,0 +1,149 @@ +.TH ZLIB 3 "15 Jan 2017" +.SH NAME +zlib \- compression/decompression library +.SH SYNOPSIS +[see +.I zlib.h +for full description] +.SH DESCRIPTION +The +.I zlib +library is a general purpose data compression library. +The code is thread safe, assuming that the standard library functions +used are thread safe, such as memory allocation routines. +It provides in-memory compression and decompression functions, +including integrity checks of the uncompressed data. +This version of the library supports only one compression method (deflation) +but other algorithms may be added later +with the same stream interface. +.LP +Compression can be done in a single step if the buffers are large enough +or can be done by repeated calls of the compression function. +In the latter case, +the application must provide more input and/or consume the output +(providing more output space) before each call. +.LP +The library also supports reading and writing files in +.IR gzip (1) +(.gz) format +with an interface similar to that of stdio. +.LP +The library does not install any signal handler. +The decoder checks the consistency of the compressed data, +so the library should never crash even in the case of corrupted input. +.LP +All functions of the compression library are documented in the file +.IR zlib.h . +The distribution source includes examples of use of the library +in the files +.I test/example.c +and +.IR test/minigzip.c, +as well as other examples in the +.IR examples/ +directory. +.LP +Changes to this version are documented in the file +.I ChangeLog +that accompanies the source. +.LP +.I zlib +is built in to many languages and operating systems, including but not limited to +Java, Python, .NET, PHP, Perl, Ruby, Swift, and Go. +.LP +An experimental package to read and write files in the .zip format, +written on top of +.I zlib +by Gilles Vollant (info@winimage.com), +is available at: +.IP +http://www.winimage.com/zLibDll/minizip.html +and also in the +.I contrib/minizip +directory of the main +.I zlib +source distribution. +.SH "SEE ALSO" +The +.I zlib +web site can be found at: +.IP +http://zlib.net/ +.LP +The data format used by the +.I zlib +library is described by RFC +(Request for Comments) 1950 to 1952 in the files: +.IP +http://tools.ietf.org/html/rfc1950 (for the zlib header and trailer format) +.br +http://tools.ietf.org/html/rfc1951 (for the deflate compressed data format) +.br +http://tools.ietf.org/html/rfc1952 (for the gzip header and trailer format) +.LP +Mark Nelson wrote an article about +.I zlib +for the Jan. 1997 issue of Dr. Dobb's Journal; +a copy of the article is available at: +.IP +http://marknelson.us/1997/01/01/zlib-engine/ +.SH "REPORTING PROBLEMS" +Before reporting a problem, +please check the +.I zlib +web site to verify that you have the latest version of +.IR zlib ; +otherwise, +obtain the latest version and see if the problem still exists. +Please read the +.I zlib +FAQ at: +.IP +http://zlib.net/zlib_faq.html +.LP +before asking for help. +Send questions and/or comments to zlib@gzip.org, +or (for the Windows DLL version) to Gilles Vollant (info@winimage.com). +.SH AUTHORS AND LICENSE +Version 1.2.11 +.LP +Copyright (C) 1995-2017 Jean-loup Gailly and Mark Adler +.LP +This software is provided 'as-is', without any express or implied +warranty. In no event will the authors be held liable for any damages +arising from the use of this software. +.LP +Permission is granted to anyone to use this software for any purpose, +including commercial applications, and to alter it and redistribute it +freely, subject to the following restrictions: +.LP +.nr step 1 1 +.IP \n[step]. 3 +The origin of this software must not be misrepresented; you must not +claim that you wrote the original software. If you use this software +in a product, an acknowledgment in the product documentation would be +appreciated but is not required. +.IP \n+[step]. +Altered source versions must be plainly marked as such, and must not be +misrepresented as being the original software. +.IP \n+[step]. +This notice may not be removed or altered from any source distribution. +.LP +Jean-loup Gailly Mark Adler +.br +jloup@gzip.org madler@alumni.caltech.edu +.LP +The deflate format used by +.I zlib +was defined by Phil Katz. +The deflate and +.I zlib +specifications were written by L. Peter Deutsch. +Thanks to all the people who reported problems and suggested various +improvements in +.IR zlib ; +who are too numerous to cite here. +.LP +UNIX manual page by R. P. C. Rodgers, +U.S. National Library of Medicine (rodgers@nlm.nih.gov). +.\" end of man page ADDED compat/zlib/zlib.3.pdf Index: compat/zlib/zlib.3.pdf ================================================================== --- /dev/null +++ compat/zlib/zlib.3.pdf cannot compute difference between binary files ADDED compat/zlib/zlib.h Index: compat/zlib/zlib.h ================================================================== --- /dev/null +++ compat/zlib/zlib.h @@ -0,0 +1,1912 @@ +/* zlib.h -- interface of the 'zlib' general purpose compression library + version 1.2.11, January 15th, 2017 + + Copyright (C) 1995-2017 Jean-loup Gailly and Mark Adler + + This software is provided 'as-is', without any express or implied + warranty. In no event will the authors be held liable for any damages + arising from the use of this software. + + Permission is granted to anyone to use this software for any purpose, + including commercial applications, and to alter it and redistribute it + freely, subject to the following restrictions: + + 1. The origin of this software must not be misrepresented; you must not + claim that you wrote the original software. If you use this software + in a product, an acknowledgment in the product documentation would be + appreciated but is not required. + 2. Altered source versions must be plainly marked as such, and must not be + misrepresented as being the original software. + 3. This notice may not be removed or altered from any source distribution. + + Jean-loup Gailly Mark Adler + jloup@gzip.org madler@alumni.caltech.edu + + + The data format used by the zlib library is described by RFCs (Request for + Comments) 1950 to 1952 in the files http://tools.ietf.org/html/rfc1950 + (zlib format), rfc1951 (deflate format) and rfc1952 (gzip format). +*/ + +#ifndef ZLIB_H +#define ZLIB_H + +#include "zconf.h" + +#ifdef __cplusplus +extern "C" { +#endif + +#define ZLIB_VERSION "1.2.11" +#define ZLIB_VERNUM 0x12b0 +#define ZLIB_VER_MAJOR 1 +#define ZLIB_VER_MINOR 2 +#define ZLIB_VER_REVISION 11 +#define ZLIB_VER_SUBREVISION 0 + +/* + The 'zlib' compression library provides in-memory compression and + decompression functions, including integrity checks of the uncompressed data. + This version of the library supports only one compression method (deflation) + but other algorithms will be added later and will have the same stream + interface. + + Compression can be done in a single step if the buffers are large enough, + or can be done by repeated calls of the compression function. In the latter + case, the application must provide more input and/or consume the output + (providing more output space) before each call. + + The compressed data format used by default by the in-memory functions is + the zlib format, which is a zlib wrapper documented in RFC 1950, wrapped + around a deflate stream, which is itself documented in RFC 1951. + + The library also supports reading and writing files in gzip (.gz) format + with an interface similar to that of stdio using the functions that start + with "gz". The gzip format is different from the zlib format. gzip is a + gzip wrapper, documented in RFC 1952, wrapped around a deflate stream. + + This library can optionally read and write gzip and raw deflate streams in + memory as well. + + The zlib format was designed to be compact and fast for use in memory + and on communications channels. The gzip format was designed for single- + file compression on file systems, has a larger header than zlib to maintain + directory information, and uses a different, slower check method than zlib. + + The library does not install any signal handler. The decoder checks + the consistency of the compressed data, so the library should never crash + even in the case of corrupted input. +*/ + +typedef voidpf (*alloc_func) OF((voidpf opaque, uInt items, uInt size)); +typedef void (*free_func) OF((voidpf opaque, voidpf address)); + +struct internal_state; + +typedef struct z_stream_s { + z_const Bytef *next_in; /* next input byte */ + uInt avail_in; /* number of bytes available at next_in */ + uLong total_in; /* total number of input bytes read so far */ + + Bytef *next_out; /* next output byte will go here */ + uInt avail_out; /* remaining free space at next_out */ + uLong total_out; /* total number of bytes output so far */ + + z_const char *msg; /* last error message, NULL if no error */ + struct internal_state FAR *state; /* not visible by applications */ + + alloc_func zalloc; /* used to allocate the internal state */ + free_func zfree; /* used to free the internal state */ + voidpf opaque; /* private data object passed to zalloc and zfree */ + + int data_type; /* best guess about the data type: binary or text + for deflate, or the decoding state for inflate */ + uLong adler; /* Adler-32 or CRC-32 value of the uncompressed data */ + uLong reserved; /* reserved for future use */ +} z_stream; + +typedef z_stream FAR *z_streamp; + +/* + gzip header information passed to and from zlib routines. See RFC 1952 + for more details on the meanings of these fields. +*/ +typedef struct gz_header_s { + int text; /* true if compressed data believed to be text */ + uLong time; /* modification time */ + int xflags; /* extra flags (not used when writing a gzip file) */ + int os; /* operating system */ + Bytef *extra; /* pointer to extra field or Z_NULL if none */ + uInt extra_len; /* extra field length (valid if extra != Z_NULL) */ + uInt extra_max; /* space at extra (only when reading header) */ + Bytef *name; /* pointer to zero-terminated file name or Z_NULL */ + uInt name_max; /* space at name (only when reading header) */ + Bytef *comment; /* pointer to zero-terminated comment or Z_NULL */ + uInt comm_max; /* space at comment (only when reading header) */ + int hcrc; /* true if there was or will be a header crc */ + int done; /* true when done reading gzip header (not used + when writing a gzip file) */ +} gz_header; + +typedef gz_header FAR *gz_headerp; + +/* + The application must update next_in and avail_in when avail_in has dropped + to zero. It must update next_out and avail_out when avail_out has dropped + to zero. The application must initialize zalloc, zfree and opaque before + calling the init function. All other fields are set by the compression + library and must not be updated by the application. + + The opaque value provided by the application will be passed as the first + parameter for calls of zalloc and zfree. This can be useful for custom + memory management. The compression library attaches no meaning to the + opaque value. + + zalloc must return Z_NULL if there is not enough memory for the object. + If zlib is used in a multi-threaded application, zalloc and zfree must be + thread safe. In that case, zlib is thread-safe. When zalloc and zfree are + Z_NULL on entry to the initialization function, they are set to internal + routines that use the standard library functions malloc() and free(). + + On 16-bit systems, the functions zalloc and zfree must be able to allocate + exactly 65536 bytes, but will not be required to allocate more than this if + the symbol MAXSEG_64K is defined (see zconf.h). WARNING: On MSDOS, pointers + returned by zalloc for objects of exactly 65536 bytes *must* have their + offset normalized to zero. The default allocation function provided by this + library ensures this (see zutil.c). To reduce memory requirements and avoid + any allocation of 64K objects, at the expense of compression ratio, compile + the library with -DMAX_WBITS=14 (see zconf.h). + + The fields total_in and total_out can be used for statistics or progress + reports. After compression, total_in holds the total size of the + uncompressed data and may be saved for use by the decompressor (particularly + if the decompressor wants to decompress everything in a single step). +*/ + + /* constants */ + +#define Z_NO_FLUSH 0 +#define Z_PARTIAL_FLUSH 1 +#define Z_SYNC_FLUSH 2 +#define Z_FULL_FLUSH 3 +#define Z_FINISH 4 +#define Z_BLOCK 5 +#define Z_TREES 6 +/* Allowed flush values; see deflate() and inflate() below for details */ + +#define Z_OK 0 +#define Z_STREAM_END 1 +#define Z_NEED_DICT 2 +#define Z_ERRNO (-1) +#define Z_STREAM_ERROR (-2) +#define Z_DATA_ERROR (-3) +#define Z_MEM_ERROR (-4) +#define Z_BUF_ERROR (-5) +#define Z_VERSION_ERROR (-6) +/* Return codes for the compression/decompression functions. Negative values + * are errors, positive values are used for special but normal events. + */ + +#define Z_NO_COMPRESSION 0 +#define Z_BEST_SPEED 1 +#define Z_BEST_COMPRESSION 9 +#define Z_DEFAULT_COMPRESSION (-1) +/* compression levels */ + +#define Z_FILTERED 1 +#define Z_HUFFMAN_ONLY 2 +#define Z_RLE 3 +#define Z_FIXED 4 +#define Z_DEFAULT_STRATEGY 0 +/* compression strategy; see deflateInit2() below for details */ + +#define Z_BINARY 0 +#define Z_TEXT 1 +#define Z_ASCII Z_TEXT /* for compatibility with 1.2.2 and earlier */ +#define Z_UNKNOWN 2 +/* Possible values of the data_type field for deflate() */ + +#define Z_DEFLATED 8 +/* The deflate compression method (the only one supported in this version) */ + +#define Z_NULL 0 /* for initializing zalloc, zfree, opaque */ + +#define zlib_version zlibVersion() +/* for compatibility with versions < 1.0.2 */ + + + /* basic functions */ + +ZEXTERN const char * ZEXPORT zlibVersion OF((void)); +/* The application can compare zlibVersion and ZLIB_VERSION for consistency. + If the first character differs, the library code actually used is not + compatible with the zlib.h header file used by the application. This check + is automatically made by deflateInit and inflateInit. + */ + +/* +ZEXTERN int ZEXPORT deflateInit OF((z_streamp strm, int level)); + + Initializes the internal stream state for compression. The fields + zalloc, zfree and opaque must be initialized before by the caller. If + zalloc and zfree are set to Z_NULL, deflateInit updates them to use default + allocation functions. + + The compression level must be Z_DEFAULT_COMPRESSION, or between 0 and 9: + 1 gives best speed, 9 gives best compression, 0 gives no compression at all + (the input data is simply copied a block at a time). Z_DEFAULT_COMPRESSION + requests a default compromise between speed and compression (currently + equivalent to level 6). + + deflateInit returns Z_OK if success, Z_MEM_ERROR if there was not enough + memory, Z_STREAM_ERROR if level is not a valid compression level, or + Z_VERSION_ERROR if the zlib library version (zlib_version) is incompatible + with the version assumed by the caller (ZLIB_VERSION). msg is set to null + if there is no error message. deflateInit does not perform any compression: + this will be done by deflate(). +*/ + + +ZEXTERN int ZEXPORT deflate OF((z_streamp strm, int flush)); +/* + deflate compresses as much data as possible, and stops when the input + buffer becomes empty or the output buffer becomes full. It may introduce + some output latency (reading input without producing any output) except when + forced to flush. + + The detailed semantics are as follows. deflate performs one or both of the + following actions: + + - Compress more input starting at next_in and update next_in and avail_in + accordingly. If not all input can be processed (because there is not + enough room in the output buffer), next_in and avail_in are updated and + processing will resume at this point for the next call of deflate(). + + - Generate more output starting at next_out and update next_out and avail_out + accordingly. This action is forced if the parameter flush is non zero. + Forcing flush frequently degrades the compression ratio, so this parameter + should be set only when necessary. Some output may be provided even if + flush is zero. + + Before the call of deflate(), the application should ensure that at least + one of the actions is possible, by providing more input and/or consuming more + output, and updating avail_in or avail_out accordingly; avail_out should + never be zero before the call. The application can consume the compressed + output when it wants, for example when the output buffer is full (avail_out + == 0), or after each call of deflate(). If deflate returns Z_OK and with + zero avail_out, it must be called again after making room in the output + buffer because there might be more output pending. See deflatePending(), + which can be used if desired to determine whether or not there is more ouput + in that case. + + Normally the parameter flush is set to Z_NO_FLUSH, which allows deflate to + decide how much data to accumulate before producing output, in order to + maximize compression. + + If the parameter flush is set to Z_SYNC_FLUSH, all pending output is + flushed to the output buffer and the output is aligned on a byte boundary, so + that the decompressor can get all input data available so far. (In + particular avail_in is zero after the call if enough output space has been + provided before the call.) Flushing may degrade compression for some + compression algorithms and so it should be used only when necessary. This + completes the current deflate block and follows it with an empty stored block + that is three bits plus filler bits to the next byte, followed by four bytes + (00 00 ff ff). + + If flush is set to Z_PARTIAL_FLUSH, all pending output is flushed to the + output buffer, but the output is not aligned to a byte boundary. All of the + input data so far will be available to the decompressor, as for Z_SYNC_FLUSH. + This completes the current deflate block and follows it with an empty fixed + codes block that is 10 bits long. This assures that enough bytes are output + in order for the decompressor to finish the block before the empty fixed + codes block. + + If flush is set to Z_BLOCK, a deflate block is completed and emitted, as + for Z_SYNC_FLUSH, but the output is not aligned on a byte boundary, and up to + seven bits of the current block are held to be written as the next byte after + the next deflate block is completed. In this case, the decompressor may not + be provided enough bits at this point in order to complete decompression of + the data provided so far to the compressor. It may need to wait for the next + block to be emitted. This is for advanced applications that need to control + the emission of deflate blocks. + + If flush is set to Z_FULL_FLUSH, all output is flushed as with + Z_SYNC_FLUSH, and the compression state is reset so that decompression can + restart from this point if previous compressed data has been damaged or if + random access is desired. Using Z_FULL_FLUSH too often can seriously degrade + compression. + + If deflate returns with avail_out == 0, this function must be called again + with the same value of the flush parameter and more output space (updated + avail_out), until the flush is complete (deflate returns with non-zero + avail_out). In the case of a Z_FULL_FLUSH or Z_SYNC_FLUSH, make sure that + avail_out is greater than six to avoid repeated flush markers due to + avail_out == 0 on return. + + If the parameter flush is set to Z_FINISH, pending input is processed, + pending output is flushed and deflate returns with Z_STREAM_END if there was + enough output space. If deflate returns with Z_OK or Z_BUF_ERROR, this + function must be called again with Z_FINISH and more output space (updated + avail_out) but no more input data, until it returns with Z_STREAM_END or an + error. After deflate has returned Z_STREAM_END, the only possible operations + on the stream are deflateReset or deflateEnd. + + Z_FINISH can be used in the first deflate call after deflateInit if all the + compression is to be done in a single step. In order to complete in one + call, avail_out must be at least the value returned by deflateBound (see + below). Then deflate is guaranteed to return Z_STREAM_END. If not enough + output space is provided, deflate will not return Z_STREAM_END, and it must + be called again as described above. + + deflate() sets strm->adler to the Adler-32 checksum of all input read + so far (that is, total_in bytes). If a gzip stream is being generated, then + strm->adler will be the CRC-32 checksum of the input read so far. (See + deflateInit2 below.) + + deflate() may update strm->data_type if it can make a good guess about + the input data type (Z_BINARY or Z_TEXT). If in doubt, the data is + considered binary. This field is only for information purposes and does not + affect the compression algorithm in any manner. + + deflate() returns Z_OK if some progress has been made (more input + processed or more output produced), Z_STREAM_END if all input has been + consumed and all output has been produced (only when flush is set to + Z_FINISH), Z_STREAM_ERROR if the stream state was inconsistent (for example + if next_in or next_out was Z_NULL or the state was inadvertently written over + by the application), or Z_BUF_ERROR if no progress is possible (for example + avail_in or avail_out was zero). Note that Z_BUF_ERROR is not fatal, and + deflate() can be called again with more input and more output space to + continue compressing. +*/ + + +ZEXTERN int ZEXPORT deflateEnd OF((z_streamp strm)); +/* + All dynamically allocated data structures for this stream are freed. + This function discards any unprocessed input and does not flush any pending + output. + + deflateEnd returns Z_OK if success, Z_STREAM_ERROR if the + stream state was inconsistent, Z_DATA_ERROR if the stream was freed + prematurely (some input or output was discarded). In the error case, msg + may be set but then points to a static string (which must not be + deallocated). +*/ + + +/* +ZEXTERN int ZEXPORT inflateInit OF((z_streamp strm)); + + Initializes the internal stream state for decompression. The fields + next_in, avail_in, zalloc, zfree and opaque must be initialized before by + the caller. In the current version of inflate, the provided input is not + read or consumed. The allocation of a sliding window will be deferred to + the first call of inflate (if the decompression does not complete on the + first call). If zalloc and zfree are set to Z_NULL, inflateInit updates + them to use default allocation functions. + + inflateInit returns Z_OK if success, Z_MEM_ERROR if there was not enough + memory, Z_VERSION_ERROR if the zlib library version is incompatible with the + version assumed by the caller, or Z_STREAM_ERROR if the parameters are + invalid, such as a null pointer to the structure. msg is set to null if + there is no error message. inflateInit does not perform any decompression. + Actual decompression will be done by inflate(). So next_in, and avail_in, + next_out, and avail_out are unused and unchanged. The current + implementation of inflateInit() does not process any header information -- + that is deferred until inflate() is called. +*/ + + +ZEXTERN int ZEXPORT inflate OF((z_streamp strm, int flush)); +/* + inflate decompresses as much data as possible, and stops when the input + buffer becomes empty or the output buffer becomes full. It may introduce + some output latency (reading input without producing any output) except when + forced to flush. + + The detailed semantics are as follows. inflate performs one or both of the + following actions: + + - Decompress more input starting at next_in and update next_in and avail_in + accordingly. If not all input can be processed (because there is not + enough room in the output buffer), then next_in and avail_in are updated + accordingly, and processing will resume at this point for the next call of + inflate(). + + - Generate more output starting at next_out and update next_out and avail_out + accordingly. inflate() provides as much output as possible, until there is + no more input data or no more space in the output buffer (see below about + the flush parameter). + + Before the call of inflate(), the application should ensure that at least + one of the actions is possible, by providing more input and/or consuming more + output, and updating the next_* and avail_* values accordingly. If the + caller of inflate() does not provide both available input and available + output space, it is possible that there will be no progress made. The + application can consume the uncompressed output when it wants, for example + when the output buffer is full (avail_out == 0), or after each call of + inflate(). If inflate returns Z_OK and with zero avail_out, it must be + called again after making room in the output buffer because there might be + more output pending. + + The flush parameter of inflate() can be Z_NO_FLUSH, Z_SYNC_FLUSH, Z_FINISH, + Z_BLOCK, or Z_TREES. Z_SYNC_FLUSH requests that inflate() flush as much + output as possible to the output buffer. Z_BLOCK requests that inflate() + stop if and when it gets to the next deflate block boundary. When decoding + the zlib or gzip format, this will cause inflate() to return immediately + after the header and before the first block. When doing a raw inflate, + inflate() will go ahead and process the first block, and will return when it + gets to the end of that block, or when it runs out of data. + + The Z_BLOCK option assists in appending to or combining deflate streams. + To assist in this, on return inflate() always sets strm->data_type to the + number of unused bits in the last byte taken from strm->next_in, plus 64 if + inflate() is currently decoding the last block in the deflate stream, plus + 128 if inflate() returned immediately after decoding an end-of-block code or + decoding the complete header up to just before the first byte of the deflate + stream. The end-of-block will not be indicated until all of the uncompressed + data from that block has been written to strm->next_out. The number of + unused bits may in general be greater than seven, except when bit 7 of + data_type is set, in which case the number of unused bits will be less than + eight. data_type is set as noted here every time inflate() returns for all + flush options, and so can be used to determine the amount of currently + consumed input in bits. + + The Z_TREES option behaves as Z_BLOCK does, but it also returns when the + end of each deflate block header is reached, before any actual data in that + block is decoded. This allows the caller to determine the length of the + deflate block header for later use in random access within a deflate block. + 256 is added to the value of strm->data_type when inflate() returns + immediately after reaching the end of the deflate block header. + + inflate() should normally be called until it returns Z_STREAM_END or an + error. However if all decompression is to be performed in a single step (a + single call of inflate), the parameter flush should be set to Z_FINISH. In + this case all pending input is processed and all pending output is flushed; + avail_out must be large enough to hold all of the uncompressed data for the + operation to complete. (The size of the uncompressed data may have been + saved by the compressor for this purpose.) The use of Z_FINISH is not + required to perform an inflation in one step. However it may be used to + inform inflate that a faster approach can be used for the single inflate() + call. Z_FINISH also informs inflate to not maintain a sliding window if the + stream completes, which reduces inflate's memory footprint. If the stream + does not complete, either because not all of the stream is provided or not + enough output space is provided, then a sliding window will be allocated and + inflate() can be called again to continue the operation as if Z_NO_FLUSH had + been used. + + In this implementation, inflate() always flushes as much output as + possible to the output buffer, and always uses the faster approach on the + first call. So the effects of the flush parameter in this implementation are + on the return value of inflate() as noted below, when inflate() returns early + when Z_BLOCK or Z_TREES is used, and when inflate() avoids the allocation of + memory for a sliding window when Z_FINISH is used. + + If a preset dictionary is needed after this call (see inflateSetDictionary + below), inflate sets strm->adler to the Adler-32 checksum of the dictionary + chosen by the compressor and returns Z_NEED_DICT; otherwise it sets + strm->adler to the Adler-32 checksum of all output produced so far (that is, + total_out bytes) and returns Z_OK, Z_STREAM_END or an error code as described + below. At the end of the stream, inflate() checks that its computed Adler-32 + checksum is equal to that saved by the compressor and returns Z_STREAM_END + only if the checksum is correct. + + inflate() can decompress and check either zlib-wrapped or gzip-wrapped + deflate data. The header type is detected automatically, if requested when + initializing with inflateInit2(). Any information contained in the gzip + header is not retained unless inflateGetHeader() is used. When processing + gzip-wrapped deflate data, strm->adler32 is set to the CRC-32 of the output + produced so far. The CRC-32 is checked against the gzip trailer, as is the + uncompressed length, modulo 2^32. + + inflate() returns Z_OK if some progress has been made (more input processed + or more output produced), Z_STREAM_END if the end of the compressed data has + been reached and all uncompressed output has been produced, Z_NEED_DICT if a + preset dictionary is needed at this point, Z_DATA_ERROR if the input data was + corrupted (input stream not conforming to the zlib format or incorrect check + value, in which case strm->msg points to a string with a more specific + error), Z_STREAM_ERROR if the stream structure was inconsistent (for example + next_in or next_out was Z_NULL, or the state was inadvertently written over + by the application), Z_MEM_ERROR if there was not enough memory, Z_BUF_ERROR + if no progress was possible or if there was not enough room in the output + buffer when Z_FINISH is used. Note that Z_BUF_ERROR is not fatal, and + inflate() can be called again with more input and more output space to + continue decompressing. If Z_DATA_ERROR is returned, the application may + then call inflateSync() to look for a good compression block if a partial + recovery of the data is to be attempted. +*/ + + +ZEXTERN int ZEXPORT inflateEnd OF((z_streamp strm)); +/* + All dynamically allocated data structures for this stream are freed. + This function discards any unprocessed input and does not flush any pending + output. + + inflateEnd returns Z_OK if success, or Z_STREAM_ERROR if the stream state + was inconsistent. +*/ + + + /* Advanced functions */ + +/* + The following functions are needed only in some special applications. +*/ + +/* +ZEXTERN int ZEXPORT deflateInit2 OF((z_streamp strm, + int level, + int method, + int windowBits, + int memLevel, + int strategy)); + + This is another version of deflateInit with more compression options. The + fields next_in, zalloc, zfree and opaque must be initialized before by the + caller. + + The method parameter is the compression method. It must be Z_DEFLATED in + this version of the library. + + The windowBits parameter is the base two logarithm of the window size + (the size of the history buffer). It should be in the range 8..15 for this + version of the library. Larger values of this parameter result in better + compression at the expense of memory usage. The default value is 15 if + deflateInit is used instead. + + For the current implementation of deflate(), a windowBits value of 8 (a + window size of 256 bytes) is not supported. As a result, a request for 8 + will result in 9 (a 512-byte window). In that case, providing 8 to + inflateInit2() will result in an error when the zlib header with 9 is + checked against the initialization of inflate(). The remedy is to not use 8 + with deflateInit2() with this initialization, or at least in that case use 9 + with inflateInit2(). + + windowBits can also be -8..-15 for raw deflate. In this case, -windowBits + determines the window size. deflate() will then generate raw deflate data + with no zlib header or trailer, and will not compute a check value. + + windowBits can also be greater than 15 for optional gzip encoding. Add + 16 to windowBits to write a simple gzip header and trailer around the + compressed data instead of a zlib wrapper. The gzip header will have no + file name, no extra data, no comment, no modification time (set to zero), no + header crc, and the operating system will be set to the appropriate value, + if the operating system was determined at compile time. If a gzip stream is + being written, strm->adler is a CRC-32 instead of an Adler-32. + + For raw deflate or gzip encoding, a request for a 256-byte window is + rejected as invalid, since only the zlib header provides a means of + transmitting the window size to the decompressor. + + The memLevel parameter specifies how much memory should be allocated + for the internal compression state. memLevel=1 uses minimum memory but is + slow and reduces compression ratio; memLevel=9 uses maximum memory for + optimal speed. The default value is 8. See zconf.h for total memory usage + as a function of windowBits and memLevel. + + The strategy parameter is used to tune the compression algorithm. Use the + value Z_DEFAULT_STRATEGY for normal data, Z_FILTERED for data produced by a + filter (or predictor), Z_HUFFMAN_ONLY to force Huffman encoding only (no + string match), or Z_RLE to limit match distances to one (run-length + encoding). Filtered data consists mostly of small values with a somewhat + random distribution. In this case, the compression algorithm is tuned to + compress them better. The effect of Z_FILTERED is to force more Huffman + coding and less string matching; it is somewhat intermediate between + Z_DEFAULT_STRATEGY and Z_HUFFMAN_ONLY. Z_RLE is designed to be almost as + fast as Z_HUFFMAN_ONLY, but give better compression for PNG image data. The + strategy parameter only affects the compression ratio but not the + correctness of the compressed output even if it is not set appropriately. + Z_FIXED prevents the use of dynamic Huffman codes, allowing for a simpler + decoder for special applications. + + deflateInit2 returns Z_OK if success, Z_MEM_ERROR if there was not enough + memory, Z_STREAM_ERROR if any parameter is invalid (such as an invalid + method), or Z_VERSION_ERROR if the zlib library version (zlib_version) is + incompatible with the version assumed by the caller (ZLIB_VERSION). msg is + set to null if there is no error message. deflateInit2 does not perform any + compression: this will be done by deflate(). +*/ + +ZEXTERN int ZEXPORT deflateSetDictionary OF((z_streamp strm, + const Bytef *dictionary, + uInt dictLength)); +/* + Initializes the compression dictionary from the given byte sequence + without producing any compressed output. When using the zlib format, this + function must be called immediately after deflateInit, deflateInit2 or + deflateReset, and before any call of deflate. When doing raw deflate, this + function must be called either before any call of deflate, or immediately + after the completion of a deflate block, i.e. after all input has been + consumed and all output has been delivered when using any of the flush + options Z_BLOCK, Z_PARTIAL_FLUSH, Z_SYNC_FLUSH, or Z_FULL_FLUSH. The + compressor and decompressor must use exactly the same dictionary (see + inflateSetDictionary). + + The dictionary should consist of strings (byte sequences) that are likely + to be encountered later in the data to be compressed, with the most commonly + used strings preferably put towards the end of the dictionary. Using a + dictionary is most useful when the data to be compressed is short and can be + predicted with good accuracy; the data can then be compressed better than + with the default empty dictionary. + + Depending on the size of the compression data structures selected by + deflateInit or deflateInit2, a part of the dictionary may in effect be + discarded, for example if the dictionary is larger than the window size + provided in deflateInit or deflateInit2. Thus the strings most likely to be + useful should be put at the end of the dictionary, not at the front. In + addition, the current implementation of deflate will use at most the window + size minus 262 bytes of the provided dictionary. + + Upon return of this function, strm->adler is set to the Adler-32 value + of the dictionary; the decompressor may later use this value to determine + which dictionary has been used by the compressor. (The Adler-32 value + applies to the whole dictionary even if only a subset of the dictionary is + actually used by the compressor.) If a raw deflate was requested, then the + Adler-32 value is not computed and strm->adler is not set. + + deflateSetDictionary returns Z_OK if success, or Z_STREAM_ERROR if a + parameter is invalid (e.g. dictionary being Z_NULL) or the stream state is + inconsistent (for example if deflate has already been called for this stream + or if not at a block boundary for raw deflate). deflateSetDictionary does + not perform any compression: this will be done by deflate(). +*/ + +ZEXTERN int ZEXPORT deflateGetDictionary OF((z_streamp strm, + Bytef *dictionary, + uInt *dictLength)); +/* + Returns the sliding dictionary being maintained by deflate. dictLength is + set to the number of bytes in the dictionary, and that many bytes are copied + to dictionary. dictionary must have enough space, where 32768 bytes is + always enough. If deflateGetDictionary() is called with dictionary equal to + Z_NULL, then only the dictionary length is returned, and nothing is copied. + Similary, if dictLength is Z_NULL, then it is not set. + + deflateGetDictionary() may return a length less than the window size, even + when more than the window size in input has been provided. It may return up + to 258 bytes less in that case, due to how zlib's implementation of deflate + manages the sliding window and lookahead for matches, where matches can be + up to 258 bytes long. If the application needs the last window-size bytes of + input, then that would need to be saved by the application outside of zlib. + + deflateGetDictionary returns Z_OK on success, or Z_STREAM_ERROR if the + stream state is inconsistent. +*/ + +ZEXTERN int ZEXPORT deflateCopy OF((z_streamp dest, + z_streamp source)); +/* + Sets the destination stream as a complete copy of the source stream. + + This function can be useful when several compression strategies will be + tried, for example when there are several ways of pre-processing the input + data with a filter. The streams that will be discarded should then be freed + by calling deflateEnd. Note that deflateCopy duplicates the internal + compression state which can be quite large, so this strategy is slow and can + consume lots of memory. + + deflateCopy returns Z_OK if success, Z_MEM_ERROR if there was not + enough memory, Z_STREAM_ERROR if the source stream state was inconsistent + (such as zalloc being Z_NULL). msg is left unchanged in both source and + destination. +*/ + +ZEXTERN int ZEXPORT deflateReset OF((z_streamp strm)); +/* + This function is equivalent to deflateEnd followed by deflateInit, but + does not free and reallocate the internal compression state. The stream + will leave the compression level and any other attributes that may have been + set unchanged. + + deflateReset returns Z_OK if success, or Z_STREAM_ERROR if the source + stream state was inconsistent (such as zalloc or state being Z_NULL). +*/ + +ZEXTERN int ZEXPORT deflateParams OF((z_streamp strm, + int level, + int strategy)); +/* + Dynamically update the compression level and compression strategy. The + interpretation of level and strategy is as in deflateInit2(). This can be + used to switch between compression and straight copy of the input data, or + to switch to a different kind of input data requiring a different strategy. + If the compression approach (which is a function of the level) or the + strategy is changed, and if any input has been consumed in a previous + deflate() call, then the input available so far is compressed with the old + level and strategy using deflate(strm, Z_BLOCK). There are three approaches + for the compression levels 0, 1..3, and 4..9 respectively. The new level + and strategy will take effect at the next call of deflate(). + + If a deflate(strm, Z_BLOCK) is performed by deflateParams(), and it does + not have enough output space to complete, then the parameter change will not + take effect. In this case, deflateParams() can be called again with the + same parameters and more output space to try again. + + In order to assure a change in the parameters on the first try, the + deflate stream should be flushed using deflate() with Z_BLOCK or other flush + request until strm.avail_out is not zero, before calling deflateParams(). + Then no more input data should be provided before the deflateParams() call. + If this is done, the old level and strategy will be applied to the data + compressed before deflateParams(), and the new level and strategy will be + applied to the the data compressed after deflateParams(). + + deflateParams returns Z_OK on success, Z_STREAM_ERROR if the source stream + state was inconsistent or if a parameter was invalid, or Z_BUF_ERROR if + there was not enough output space to complete the compression of the + available input data before a change in the strategy or approach. Note that + in the case of a Z_BUF_ERROR, the parameters are not changed. A return + value of Z_BUF_ERROR is not fatal, in which case deflateParams() can be + retried with more output space. +*/ + +ZEXTERN int ZEXPORT deflateTune OF((z_streamp strm, + int good_length, + int max_lazy, + int nice_length, + int max_chain)); +/* + Fine tune deflate's internal compression parameters. This should only be + used by someone who understands the algorithm used by zlib's deflate for + searching for the best matching string, and even then only by the most + fanatic optimizer trying to squeeze out the last compressed bit for their + specific input data. Read the deflate.c source code for the meaning of the + max_lazy, good_length, nice_length, and max_chain parameters. + + deflateTune() can be called after deflateInit() or deflateInit2(), and + returns Z_OK on success, or Z_STREAM_ERROR for an invalid deflate stream. + */ + +ZEXTERN uLong ZEXPORT deflateBound OF((z_streamp strm, + uLong sourceLen)); +/* + deflateBound() returns an upper bound on the compressed size after + deflation of sourceLen bytes. It must be called after deflateInit() or + deflateInit2(), and after deflateSetHeader(), if used. This would be used + to allocate an output buffer for deflation in a single pass, and so would be + called before deflate(). If that first deflate() call is provided the + sourceLen input bytes, an output buffer allocated to the size returned by + deflateBound(), and the flush value Z_FINISH, then deflate() is guaranteed + to return Z_STREAM_END. Note that it is possible for the compressed size to + be larger than the value returned by deflateBound() if flush options other + than Z_FINISH or Z_NO_FLUSH are used. +*/ + +ZEXTERN int ZEXPORT deflatePending OF((z_streamp strm, + unsigned *pending, + int *bits)); +/* + deflatePending() returns the number of bytes and bits of output that have + been generated, but not yet provided in the available output. The bytes not + provided would be due to the available output space having being consumed. + The number of bits of output not provided are between 0 and 7, where they + await more bits to join them in order to fill out a full byte. If pending + or bits are Z_NULL, then those values are not set. + + deflatePending returns Z_OK if success, or Z_STREAM_ERROR if the source + stream state was inconsistent. + */ + +ZEXTERN int ZEXPORT deflatePrime OF((z_streamp strm, + int bits, + int value)); +/* + deflatePrime() inserts bits in the deflate output stream. The intent + is that this function is used to start off the deflate output with the bits + leftover from a previous deflate stream when appending to it. As such, this + function can only be used for raw deflate, and must be used before the first + deflate() call after a deflateInit2() or deflateReset(). bits must be less + than or equal to 16, and that many of the least significant bits of value + will be inserted in the output. + + deflatePrime returns Z_OK if success, Z_BUF_ERROR if there was not enough + room in the internal buffer to insert the bits, or Z_STREAM_ERROR if the + source stream state was inconsistent. +*/ + +ZEXTERN int ZEXPORT deflateSetHeader OF((z_streamp strm, + gz_headerp head)); +/* + deflateSetHeader() provides gzip header information for when a gzip + stream is requested by deflateInit2(). deflateSetHeader() may be called + after deflateInit2() or deflateReset() and before the first call of + deflate(). The text, time, os, extra field, name, and comment information + in the provided gz_header structure are written to the gzip header (xflag is + ignored -- the extra flags are set according to the compression level). The + caller must assure that, if not Z_NULL, name and comment are terminated with + a zero byte, and that if extra is not Z_NULL, that extra_len bytes are + available there. If hcrc is true, a gzip header crc is included. Note that + the current versions of the command-line version of gzip (up through version + 1.3.x) do not support header crc's, and will report that it is a "multi-part + gzip file" and give up. + + If deflateSetHeader is not used, the default gzip header has text false, + the time set to zero, and os set to 255, with no extra, name, or comment + fields. The gzip header is returned to the default state by deflateReset(). + + deflateSetHeader returns Z_OK if success, or Z_STREAM_ERROR if the source + stream state was inconsistent. +*/ + +/* +ZEXTERN int ZEXPORT inflateInit2 OF((z_streamp strm, + int windowBits)); + + This is another version of inflateInit with an extra parameter. The + fields next_in, avail_in, zalloc, zfree and opaque must be initialized + before by the caller. + + The windowBits parameter is the base two logarithm of the maximum window + size (the size of the history buffer). It should be in the range 8..15 for + this version of the library. The default value is 15 if inflateInit is used + instead. windowBits must be greater than or equal to the windowBits value + provided to deflateInit2() while compressing, or it must be equal to 15 if + deflateInit2() was not used. If a compressed stream with a larger window + size is given as input, inflate() will return with the error code + Z_DATA_ERROR instead of trying to allocate a larger window. + + windowBits can also be zero to request that inflate use the window size in + the zlib header of the compressed stream. + + windowBits can also be -8..-15 for raw inflate. In this case, -windowBits + determines the window size. inflate() will then process raw deflate data, + not looking for a zlib or gzip header, not generating a check value, and not + looking for any check values for comparison at the end of the stream. This + is for use with other formats that use the deflate compressed data format + such as zip. Those formats provide their own check values. If a custom + format is developed using the raw deflate format for compressed data, it is + recommended that a check value such as an Adler-32 or a CRC-32 be applied to + the uncompressed data as is done in the zlib, gzip, and zip formats. For + most applications, the zlib format should be used as is. Note that comments + above on the use in deflateInit2() applies to the magnitude of windowBits. + + windowBits can also be greater than 15 for optional gzip decoding. Add + 32 to windowBits to enable zlib and gzip decoding with automatic header + detection, or add 16 to decode only the gzip format (the zlib format will + return a Z_DATA_ERROR). If a gzip stream is being decoded, strm->adler is a + CRC-32 instead of an Adler-32. Unlike the gunzip utility and gzread() (see + below), inflate() will not automatically decode concatenated gzip streams. + inflate() will return Z_STREAM_END at the end of the gzip stream. The state + would need to be reset to continue decoding a subsequent gzip stream. + + inflateInit2 returns Z_OK if success, Z_MEM_ERROR if there was not enough + memory, Z_VERSION_ERROR if the zlib library version is incompatible with the + version assumed by the caller, or Z_STREAM_ERROR if the parameters are + invalid, such as a null pointer to the structure. msg is set to null if + there is no error message. inflateInit2 does not perform any decompression + apart from possibly reading the zlib header if present: actual decompression + will be done by inflate(). (So next_in and avail_in may be modified, but + next_out and avail_out are unused and unchanged.) The current implementation + of inflateInit2() does not process any header information -- that is + deferred until inflate() is called. +*/ + +ZEXTERN int ZEXPORT inflateSetDictionary OF((z_streamp strm, + const Bytef *dictionary, + uInt dictLength)); +/* + Initializes the decompression dictionary from the given uncompressed byte + sequence. This function must be called immediately after a call of inflate, + if that call returned Z_NEED_DICT. The dictionary chosen by the compressor + can be determined from the Adler-32 value returned by that call of inflate. + The compressor and decompressor must use exactly the same dictionary (see + deflateSetDictionary). For raw inflate, this function can be called at any + time to set the dictionary. If the provided dictionary is smaller than the + window and there is already data in the window, then the provided dictionary + will amend what's there. The application must insure that the dictionary + that was used for compression is provided. + + inflateSetDictionary returns Z_OK if success, Z_STREAM_ERROR if a + parameter is invalid (e.g. dictionary being Z_NULL) or the stream state is + inconsistent, Z_DATA_ERROR if the given dictionary doesn't match the + expected one (incorrect Adler-32 value). inflateSetDictionary does not + perform any decompression: this will be done by subsequent calls of + inflate(). +*/ + +ZEXTERN int ZEXPORT inflateGetDictionary OF((z_streamp strm, + Bytef *dictionary, + uInt *dictLength)); +/* + Returns the sliding dictionary being maintained by inflate. dictLength is + set to the number of bytes in the dictionary, and that many bytes are copied + to dictionary. dictionary must have enough space, where 32768 bytes is + always enough. If inflateGetDictionary() is called with dictionary equal to + Z_NULL, then only the dictionary length is returned, and nothing is copied. + Similary, if dictLength is Z_NULL, then it is not set. + + inflateGetDictionary returns Z_OK on success, or Z_STREAM_ERROR if the + stream state is inconsistent. +*/ + +ZEXTERN int ZEXPORT inflateSync OF((z_streamp strm)); +/* + Skips invalid compressed data until a possible full flush point (see above + for the description of deflate with Z_FULL_FLUSH) can be found, or until all + available input is skipped. No output is provided. + + inflateSync searches for a 00 00 FF FF pattern in the compressed data. + All full flush points have this pattern, but not all occurrences of this + pattern are full flush points. + + inflateSync returns Z_OK if a possible full flush point has been found, + Z_BUF_ERROR if no more input was provided, Z_DATA_ERROR if no flush point + has been found, or Z_STREAM_ERROR if the stream structure was inconsistent. + In the success case, the application may save the current current value of + total_in which indicates where valid compressed data was found. In the + error case, the application may repeatedly call inflateSync, providing more + input each time, until success or end of the input data. +*/ + +ZEXTERN int ZEXPORT inflateCopy OF((z_streamp dest, + z_streamp source)); +/* + Sets the destination stream as a complete copy of the source stream. + + This function can be useful when randomly accessing a large stream. The + first pass through the stream can periodically record the inflate state, + allowing restarting inflate at those points when randomly accessing the + stream. + + inflateCopy returns Z_OK if success, Z_MEM_ERROR if there was not + enough memory, Z_STREAM_ERROR if the source stream state was inconsistent + (such as zalloc being Z_NULL). msg is left unchanged in both source and + destination. +*/ + +ZEXTERN int ZEXPORT inflateReset OF((z_streamp strm)); +/* + This function is equivalent to inflateEnd followed by inflateInit, + but does not free and reallocate the internal decompression state. The + stream will keep attributes that may have been set by inflateInit2. + + inflateReset returns Z_OK if success, or Z_STREAM_ERROR if the source + stream state was inconsistent (such as zalloc or state being Z_NULL). +*/ + +ZEXTERN int ZEXPORT inflateReset2 OF((z_streamp strm, + int windowBits)); +/* + This function is the same as inflateReset, but it also permits changing + the wrap and window size requests. The windowBits parameter is interpreted + the same as it is for inflateInit2. If the window size is changed, then the + memory allocated for the window is freed, and the window will be reallocated + by inflate() if needed. + + inflateReset2 returns Z_OK if success, or Z_STREAM_ERROR if the source + stream state was inconsistent (such as zalloc or state being Z_NULL), or if + the windowBits parameter is invalid. +*/ + +ZEXTERN int ZEXPORT inflatePrime OF((z_streamp strm, + int bits, + int value)); +/* + This function inserts bits in the inflate input stream. The intent is + that this function is used to start inflating at a bit position in the + middle of a byte. The provided bits will be used before any bytes are used + from next_in. This function should only be used with raw inflate, and + should be used before the first inflate() call after inflateInit2() or + inflateReset(). bits must be less than or equal to 16, and that many of the + least significant bits of value will be inserted in the input. + + If bits is negative, then the input stream bit buffer is emptied. Then + inflatePrime() can be called again to put bits in the buffer. This is used + to clear out bits leftover after feeding inflate a block description prior + to feeding inflate codes. + + inflatePrime returns Z_OK if success, or Z_STREAM_ERROR if the source + stream state was inconsistent. +*/ + +ZEXTERN long ZEXPORT inflateMark OF((z_streamp strm)); +/* + This function returns two values, one in the lower 16 bits of the return + value, and the other in the remaining upper bits, obtained by shifting the + return value down 16 bits. If the upper value is -1 and the lower value is + zero, then inflate() is currently decoding information outside of a block. + If the upper value is -1 and the lower value is non-zero, then inflate is in + the middle of a stored block, with the lower value equaling the number of + bytes from the input remaining to copy. If the upper value is not -1, then + it is the number of bits back from the current bit position in the input of + the code (literal or length/distance pair) currently being processed. In + that case the lower value is the number of bytes already emitted for that + code. + + A code is being processed if inflate is waiting for more input to complete + decoding of the code, or if it has completed decoding but is waiting for + more output space to write the literal or match data. + + inflateMark() is used to mark locations in the input data for random + access, which may be at bit positions, and to note those cases where the + output of a code may span boundaries of random access blocks. The current + location in the input stream can be determined from avail_in and data_type + as noted in the description for the Z_BLOCK flush parameter for inflate. + + inflateMark returns the value noted above, or -65536 if the provided + source stream state was inconsistent. +*/ + +ZEXTERN int ZEXPORT inflateGetHeader OF((z_streamp strm, + gz_headerp head)); +/* + inflateGetHeader() requests that gzip header information be stored in the + provided gz_header structure. inflateGetHeader() may be called after + inflateInit2() or inflateReset(), and before the first call of inflate(). + As inflate() processes the gzip stream, head->done is zero until the header + is completed, at which time head->done is set to one. If a zlib stream is + being decoded, then head->done is set to -1 to indicate that there will be + no gzip header information forthcoming. Note that Z_BLOCK or Z_TREES can be + used to force inflate() to return immediately after header processing is + complete and before any actual data is decompressed. + + The text, time, xflags, and os fields are filled in with the gzip header + contents. hcrc is set to true if there is a header CRC. (The header CRC + was valid if done is set to one.) If extra is not Z_NULL, then extra_max + contains the maximum number of bytes to write to extra. Once done is true, + extra_len contains the actual extra field length, and extra contains the + extra field, or that field truncated if extra_max is less than extra_len. + If name is not Z_NULL, then up to name_max characters are written there, + terminated with a zero unless the length is greater than name_max. If + comment is not Z_NULL, then up to comm_max characters are written there, + terminated with a zero unless the length is greater than comm_max. When any + of extra, name, or comment are not Z_NULL and the respective field is not + present in the header, then that field is set to Z_NULL to signal its + absence. This allows the use of deflateSetHeader() with the returned + structure to duplicate the header. However if those fields are set to + allocated memory, then the application will need to save those pointers + elsewhere so that they can be eventually freed. + + If inflateGetHeader is not used, then the header information is simply + discarded. The header is always checked for validity, including the header + CRC if present. inflateReset() will reset the process to discard the header + information. The application would need to call inflateGetHeader() again to + retrieve the header from the next gzip stream. + + inflateGetHeader returns Z_OK if success, or Z_STREAM_ERROR if the source + stream state was inconsistent. +*/ + +/* +ZEXTERN int ZEXPORT inflateBackInit OF((z_streamp strm, int windowBits, + unsigned char FAR *window)); + + Initialize the internal stream state for decompression using inflateBack() + calls. The fields zalloc, zfree and opaque in strm must be initialized + before the call. If zalloc and zfree are Z_NULL, then the default library- + derived memory allocation routines are used. windowBits is the base two + logarithm of the window size, in the range 8..15. window is a caller + supplied buffer of that size. Except for special applications where it is + assured that deflate was used with small window sizes, windowBits must be 15 + and a 32K byte window must be supplied to be able to decompress general + deflate streams. + + See inflateBack() for the usage of these routines. + + inflateBackInit will return Z_OK on success, Z_STREAM_ERROR if any of + the parameters are invalid, Z_MEM_ERROR if the internal state could not be + allocated, or Z_VERSION_ERROR if the version of the library does not match + the version of the header file. +*/ + +typedef unsigned (*in_func) OF((void FAR *, + z_const unsigned char FAR * FAR *)); +typedef int (*out_func) OF((void FAR *, unsigned char FAR *, unsigned)); + +ZEXTERN int ZEXPORT inflateBack OF((z_streamp strm, + in_func in, void FAR *in_desc, + out_func out, void FAR *out_desc)); +/* + inflateBack() does a raw inflate with a single call using a call-back + interface for input and output. This is potentially more efficient than + inflate() for file i/o applications, in that it avoids copying between the + output and the sliding window by simply making the window itself the output + buffer. inflate() can be faster on modern CPUs when used with large + buffers. inflateBack() trusts the application to not change the output + buffer passed by the output function, at least until inflateBack() returns. + + inflateBackInit() must be called first to allocate the internal state + and to initialize the state with the user-provided window buffer. + inflateBack() may then be used multiple times to inflate a complete, raw + deflate stream with each call. inflateBackEnd() is then called to free the + allocated state. + + A raw deflate stream is one with no zlib or gzip header or trailer. + This routine would normally be used in a utility that reads zip or gzip + files and writes out uncompressed files. The utility would decode the + header and process the trailer on its own, hence this routine expects only + the raw deflate stream to decompress. This is different from the default + behavior of inflate(), which expects a zlib header and trailer around the + deflate stream. + + inflateBack() uses two subroutines supplied by the caller that are then + called by inflateBack() for input and output. inflateBack() calls those + routines until it reads a complete deflate stream and writes out all of the + uncompressed data, or until it encounters an error. The function's + parameters and return types are defined above in the in_func and out_func + typedefs. inflateBack() will call in(in_desc, &buf) which should return the + number of bytes of provided input, and a pointer to that input in buf. If + there is no input available, in() must return zero -- buf is ignored in that + case -- and inflateBack() will return a buffer error. inflateBack() will + call out(out_desc, buf, len) to write the uncompressed data buf[0..len-1]. + out() should return zero on success, or non-zero on failure. If out() + returns non-zero, inflateBack() will return with an error. Neither in() nor + out() are permitted to change the contents of the window provided to + inflateBackInit(), which is also the buffer that out() uses to write from. + The length written by out() will be at most the window size. Any non-zero + amount of input may be provided by in(). + + For convenience, inflateBack() can be provided input on the first call by + setting strm->next_in and strm->avail_in. If that input is exhausted, then + in() will be called. Therefore strm->next_in must be initialized before + calling inflateBack(). If strm->next_in is Z_NULL, then in() will be called + immediately for input. If strm->next_in is not Z_NULL, then strm->avail_in + must also be initialized, and then if strm->avail_in is not zero, input will + initially be taken from strm->next_in[0 .. strm->avail_in - 1]. + + The in_desc and out_desc parameters of inflateBack() is passed as the + first parameter of in() and out() respectively when they are called. These + descriptors can be optionally used to pass any information that the caller- + supplied in() and out() functions need to do their job. + + On return, inflateBack() will set strm->next_in and strm->avail_in to + pass back any unused input that was provided by the last in() call. The + return values of inflateBack() can be Z_STREAM_END on success, Z_BUF_ERROR + if in() or out() returned an error, Z_DATA_ERROR if there was a format error + in the deflate stream (in which case strm->msg is set to indicate the nature + of the error), or Z_STREAM_ERROR if the stream was not properly initialized. + In the case of Z_BUF_ERROR, an input or output error can be distinguished + using strm->next_in which will be Z_NULL only if in() returned an error. If + strm->next_in is not Z_NULL, then the Z_BUF_ERROR was due to out() returning + non-zero. (in() will always be called before out(), so strm->next_in is + assured to be defined if out() returns non-zero.) Note that inflateBack() + cannot return Z_OK. +*/ + +ZEXTERN int ZEXPORT inflateBackEnd OF((z_streamp strm)); +/* + All memory allocated by inflateBackInit() is freed. + + inflateBackEnd() returns Z_OK on success, or Z_STREAM_ERROR if the stream + state was inconsistent. +*/ + +ZEXTERN uLong ZEXPORT zlibCompileFlags OF((void)); +/* Return flags indicating compile-time options. + + Type sizes, two bits each, 00 = 16 bits, 01 = 32, 10 = 64, 11 = other: + 1.0: size of uInt + 3.2: size of uLong + 5.4: size of voidpf (pointer) + 7.6: size of z_off_t + + Compiler, assembler, and debug options: + 8: ZLIB_DEBUG + 9: ASMV or ASMINF -- use ASM code + 10: ZLIB_WINAPI -- exported functions use the WINAPI calling convention + 11: 0 (reserved) + + One-time table building (smaller code, but not thread-safe if true): + 12: BUILDFIXED -- build static block decoding tables when needed + 13: DYNAMIC_CRC_TABLE -- build CRC calculation tables when needed + 14,15: 0 (reserved) + + Library content (indicates missing functionality): + 16: NO_GZCOMPRESS -- gz* functions cannot compress (to avoid linking + deflate code when not needed) + 17: NO_GZIP -- deflate can't write gzip streams, and inflate can't detect + and decode gzip streams (to avoid linking crc code) + 18-19: 0 (reserved) + + Operation variations (changes in library functionality): + 20: PKZIP_BUG_WORKAROUND -- slightly more permissive inflate + 21: FASTEST -- deflate algorithm with only one, lowest compression level + 22,23: 0 (reserved) + + The sprintf variant used by gzprintf (zero is best): + 24: 0 = vs*, 1 = s* -- 1 means limited to 20 arguments after the format + 25: 0 = *nprintf, 1 = *printf -- 1 means gzprintf() not secure! + 26: 0 = returns value, 1 = void -- 1 means inferred string length returned + + Remainder: + 27-31: 0 (reserved) + */ + +#ifndef Z_SOLO + + /* utility functions */ + +/* + The following utility functions are implemented on top of the basic + stream-oriented functions. To simplify the interface, some default options + are assumed (compression level and memory usage, standard memory allocation + functions). The source code of these utility functions can be modified if + you need special options. +*/ + +ZEXTERN int ZEXPORT compress OF((Bytef *dest, uLongf *destLen, + const Bytef *source, uLong sourceLen)); +/* + Compresses the source buffer into the destination buffer. sourceLen is + the byte length of the source buffer. Upon entry, destLen is the total size + of the destination buffer, which must be at least the value returned by + compressBound(sourceLen). Upon exit, destLen is the actual size of the + compressed data. compress() is equivalent to compress2() with a level + parameter of Z_DEFAULT_COMPRESSION. + + compress returns Z_OK if success, Z_MEM_ERROR if there was not + enough memory, Z_BUF_ERROR if there was not enough room in the output + buffer. +*/ + +ZEXTERN int ZEXPORT compress2 OF((Bytef *dest, uLongf *destLen, + const Bytef *source, uLong sourceLen, + int level)); +/* + Compresses the source buffer into the destination buffer. The level + parameter has the same meaning as in deflateInit. sourceLen is the byte + length of the source buffer. Upon entry, destLen is the total size of the + destination buffer, which must be at least the value returned by + compressBound(sourceLen). Upon exit, destLen is the actual size of the + compressed data. + + compress2 returns Z_OK if success, Z_MEM_ERROR if there was not enough + memory, Z_BUF_ERROR if there was not enough room in the output buffer, + Z_STREAM_ERROR if the level parameter is invalid. +*/ + +ZEXTERN uLong ZEXPORT compressBound OF((uLong sourceLen)); +/* + compressBound() returns an upper bound on the compressed size after + compress() or compress2() on sourceLen bytes. It would be used before a + compress() or compress2() call to allocate the destination buffer. +*/ + +ZEXTERN int ZEXPORT uncompress OF((Bytef *dest, uLongf *destLen, + const Bytef *source, uLong sourceLen)); +/* + Decompresses the source buffer into the destination buffer. sourceLen is + the byte length of the source buffer. Upon entry, destLen is the total size + of the destination buffer, which must be large enough to hold the entire + uncompressed data. (The size of the uncompressed data must have been saved + previously by the compressor and transmitted to the decompressor by some + mechanism outside the scope of this compression library.) Upon exit, destLen + is the actual size of the uncompressed data. + + uncompress returns Z_OK if success, Z_MEM_ERROR if there was not + enough memory, Z_BUF_ERROR if there was not enough room in the output + buffer, or Z_DATA_ERROR if the input data was corrupted or incomplete. In + the case where there is not enough room, uncompress() will fill the output + buffer with the uncompressed data up to that point. +*/ + +ZEXTERN int ZEXPORT uncompress2 OF((Bytef *dest, uLongf *destLen, + const Bytef *source, uLong *sourceLen)); +/* + Same as uncompress, except that sourceLen is a pointer, where the + length of the source is *sourceLen. On return, *sourceLen is the number of + source bytes consumed. +*/ + + /* gzip file access functions */ + +/* + This library supports reading and writing files in gzip (.gz) format with + an interface similar to that of stdio, using the functions that start with + "gz". The gzip format is different from the zlib format. gzip is a gzip + wrapper, documented in RFC 1952, wrapped around a deflate stream. +*/ + +typedef struct gzFile_s *gzFile; /* semi-opaque gzip file descriptor */ + +/* +ZEXTERN gzFile ZEXPORT gzopen OF((const char *path, const char *mode)); + + Opens a gzip (.gz) file for reading or writing. The mode parameter is as + in fopen ("rb" or "wb") but can also include a compression level ("wb9") or + a strategy: 'f' for filtered data as in "wb6f", 'h' for Huffman-only + compression as in "wb1h", 'R' for run-length encoding as in "wb1R", or 'F' + for fixed code compression as in "wb9F". (See the description of + deflateInit2 for more information about the strategy parameter.) 'T' will + request transparent writing or appending with no compression and not using + the gzip format. + + "a" can be used instead of "w" to request that the gzip stream that will + be written be appended to the file. "+" will result in an error, since + reading and writing to the same gzip file is not supported. The addition of + "x" when writing will create the file exclusively, which fails if the file + already exists. On systems that support it, the addition of "e" when + reading or writing will set the flag to close the file on an execve() call. + + These functions, as well as gzip, will read and decode a sequence of gzip + streams in a file. The append function of gzopen() can be used to create + such a file. (Also see gzflush() for another way to do this.) When + appending, gzopen does not test whether the file begins with a gzip stream, + nor does it look for the end of the gzip streams to begin appending. gzopen + will simply append a gzip stream to the existing file. + + gzopen can be used to read a file which is not in gzip format; in this + case gzread will directly read from the file without decompression. When + reading, this will be detected automatically by looking for the magic two- + byte gzip header. + + gzopen returns NULL if the file could not be opened, if there was + insufficient memory to allocate the gzFile state, or if an invalid mode was + specified (an 'r', 'w', or 'a' was not provided, or '+' was provided). + errno can be checked to determine if the reason gzopen failed was that the + file could not be opened. +*/ + +ZEXTERN gzFile ZEXPORT gzdopen OF((int fd, const char *mode)); +/* + gzdopen associates a gzFile with the file descriptor fd. File descriptors + are obtained from calls like open, dup, creat, pipe or fileno (if the file + has been previously opened with fopen). The mode parameter is as in gzopen. + + The next call of gzclose on the returned gzFile will also close the file + descriptor fd, just like fclose(fdopen(fd, mode)) closes the file descriptor + fd. If you want to keep fd open, use fd = dup(fd_keep); gz = gzdopen(fd, + mode);. The duplicated descriptor should be saved to avoid a leak, since + gzdopen does not close fd if it fails. If you are using fileno() to get the + file descriptor from a FILE *, then you will have to use dup() to avoid + double-close()ing the file descriptor. Both gzclose() and fclose() will + close the associated file descriptor, so they need to have different file + descriptors. + + gzdopen returns NULL if there was insufficient memory to allocate the + gzFile state, if an invalid mode was specified (an 'r', 'w', or 'a' was not + provided, or '+' was provided), or if fd is -1. The file descriptor is not + used until the next gz* read, write, seek, or close operation, so gzdopen + will not detect if fd is invalid (unless fd is -1). +*/ + +ZEXTERN int ZEXPORT gzbuffer OF((gzFile file, unsigned size)); +/* + Set the internal buffer size used by this library's functions. The + default buffer size is 8192 bytes. This function must be called after + gzopen() or gzdopen(), and before any other calls that read or write the + file. The buffer memory allocation is always deferred to the first read or + write. Three times that size in buffer space is allocated. A larger buffer + size of, for example, 64K or 128K bytes will noticeably increase the speed + of decompression (reading). + + The new buffer size also affects the maximum length for gzprintf(). + + gzbuffer() returns 0 on success, or -1 on failure, such as being called + too late. +*/ + +ZEXTERN int ZEXPORT gzsetparams OF((gzFile file, int level, int strategy)); +/* + Dynamically update the compression level or strategy. See the description + of deflateInit2 for the meaning of these parameters. Previously provided + data is flushed before the parameter change. + + gzsetparams returns Z_OK if success, Z_STREAM_ERROR if the file was not + opened for writing, Z_ERRNO if there is an error writing the flushed data, + or Z_MEM_ERROR if there is a memory allocation error. +*/ + +ZEXTERN int ZEXPORT gzread OF((gzFile file, voidp buf, unsigned len)); +/* + Reads the given number of uncompressed bytes from the compressed file. If + the input file is not in gzip format, gzread copies the given number of + bytes into the buffer directly from the file. + + After reaching the end of a gzip stream in the input, gzread will continue + to read, looking for another gzip stream. Any number of gzip streams may be + concatenated in the input file, and will all be decompressed by gzread(). + If something other than a gzip stream is encountered after a gzip stream, + that remaining trailing garbage is ignored (and no error is returned). + + gzread can be used to read a gzip file that is being concurrently written. + Upon reaching the end of the input, gzread will return with the available + data. If the error code returned by gzerror is Z_OK or Z_BUF_ERROR, then + gzclearerr can be used to clear the end of file indicator in order to permit + gzread to be tried again. Z_OK indicates that a gzip stream was completed + on the last gzread. Z_BUF_ERROR indicates that the input file ended in the + middle of a gzip stream. Note that gzread does not return -1 in the event + of an incomplete gzip stream. This error is deferred until gzclose(), which + will return Z_BUF_ERROR if the last gzread ended in the middle of a gzip + stream. Alternatively, gzerror can be used before gzclose to detect this + case. + + gzread returns the number of uncompressed bytes actually read, less than + len for end of file, or -1 for error. If len is too large to fit in an int, + then nothing is read, -1 is returned, and the error state is set to + Z_STREAM_ERROR. +*/ + +ZEXTERN z_size_t ZEXPORT gzfread OF((voidp buf, z_size_t size, z_size_t nitems, + gzFile file)); +/* + Read up to nitems items of size size from file to buf, otherwise operating + as gzread() does. This duplicates the interface of stdio's fread(), with + size_t request and return types. If the library defines size_t, then + z_size_t is identical to size_t. If not, then z_size_t is an unsigned + integer type that can contain a pointer. + + gzfread() returns the number of full items read of size size, or zero if + the end of the file was reached and a full item could not be read, or if + there was an error. gzerror() must be consulted if zero is returned in + order to determine if there was an error. If the multiplication of size and + nitems overflows, i.e. the product does not fit in a z_size_t, then nothing + is read, zero is returned, and the error state is set to Z_STREAM_ERROR. + + In the event that the end of file is reached and only a partial item is + available at the end, i.e. the remaining uncompressed data length is not a + multiple of size, then the final partial item is nevetheless read into buf + and the end-of-file flag is set. The length of the partial item read is not + provided, but could be inferred from the result of gztell(). This behavior + is the same as the behavior of fread() implementations in common libraries, + but it prevents the direct use of gzfread() to read a concurrently written + file, reseting and retrying on end-of-file, when size is not 1. +*/ + +ZEXTERN int ZEXPORT gzwrite OF((gzFile file, + voidpc buf, unsigned len)); +/* + Writes the given number of uncompressed bytes into the compressed file. + gzwrite returns the number of uncompressed bytes written or 0 in case of + error. +*/ + +ZEXTERN z_size_t ZEXPORT gzfwrite OF((voidpc buf, z_size_t size, + z_size_t nitems, gzFile file)); +/* + gzfwrite() writes nitems items of size size from buf to file, duplicating + the interface of stdio's fwrite(), with size_t request and return types. If + the library defines size_t, then z_size_t is identical to size_t. If not, + then z_size_t is an unsigned integer type that can contain a pointer. + + gzfwrite() returns the number of full items written of size size, or zero + if there was an error. If the multiplication of size and nitems overflows, + i.e. the product does not fit in a z_size_t, then nothing is written, zero + is returned, and the error state is set to Z_STREAM_ERROR. +*/ + +ZEXTERN int ZEXPORTVA gzprintf Z_ARG((gzFile file, const char *format, ...)); +/* + Converts, formats, and writes the arguments to the compressed file under + control of the format string, as in fprintf. gzprintf returns the number of + uncompressed bytes actually written, or a negative zlib error code in case + of error. The number of uncompressed bytes written is limited to 8191, or + one less than the buffer size given to gzbuffer(). The caller should assure + that this limit is not exceeded. If it is exceeded, then gzprintf() will + return an error (0) with nothing written. In this case, there may also be a + buffer overflow with unpredictable consequences, which is possible only if + zlib was compiled with the insecure functions sprintf() or vsprintf() + because the secure snprintf() or vsnprintf() functions were not available. + This can be determined using zlibCompileFlags(). +*/ + +ZEXTERN int ZEXPORT gzputs OF((gzFile file, const char *s)); +/* + Writes the given null-terminated string to the compressed file, excluding + the terminating null character. + + gzputs returns the number of characters written, or -1 in case of error. +*/ + +ZEXTERN char * ZEXPORT gzgets OF((gzFile file, char *buf, int len)); +/* + Reads bytes from the compressed file until len-1 characters are read, or a + newline character is read and transferred to buf, or an end-of-file + condition is encountered. If any characters are read or if len == 1, the + string is terminated with a null character. If no characters are read due + to an end-of-file or len < 1, then the buffer is left untouched. + + gzgets returns buf which is a null-terminated string, or it returns NULL + for end-of-file or in case of error. If there was an error, the contents at + buf are indeterminate. +*/ + +ZEXTERN int ZEXPORT gzputc OF((gzFile file, int c)); +/* + Writes c, converted to an unsigned char, into the compressed file. gzputc + returns the value that was written, or -1 in case of error. +*/ + +ZEXTERN int ZEXPORT gzgetc OF((gzFile file)); +/* + Reads one byte from the compressed file. gzgetc returns this byte or -1 + in case of end of file or error. This is implemented as a macro for speed. + As such, it does not do all of the checking the other functions do. I.e. + it does not check to see if file is NULL, nor whether the structure file + points to has been clobbered or not. +*/ + +ZEXTERN int ZEXPORT gzungetc OF((int c, gzFile file)); +/* + Push one character back onto the stream to be read as the first character + on the next read. At least one character of push-back is allowed. + gzungetc() returns the character pushed, or -1 on failure. gzungetc() will + fail if c is -1, and may fail if a character has been pushed but not read + yet. If gzungetc is used immediately after gzopen or gzdopen, at least the + output buffer size of pushed characters is allowed. (See gzbuffer above.) + The pushed character will be discarded if the stream is repositioned with + gzseek() or gzrewind(). +*/ + +ZEXTERN int ZEXPORT gzflush OF((gzFile file, int flush)); +/* + Flushes all pending output into the compressed file. The parameter flush + is as in the deflate() function. The return value is the zlib error number + (see function gzerror below). gzflush is only permitted when writing. + + If the flush parameter is Z_FINISH, the remaining data is written and the + gzip stream is completed in the output. If gzwrite() is called again, a new + gzip stream will be started in the output. gzread() is able to read such + concatenated gzip streams. + + gzflush should be called only when strictly necessary because it will + degrade compression if called too often. +*/ + +/* +ZEXTERN z_off_t ZEXPORT gzseek OF((gzFile file, + z_off_t offset, int whence)); + + Sets the starting position for the next gzread or gzwrite on the given + compressed file. The offset represents a number of bytes in the + uncompressed data stream. The whence parameter is defined as in lseek(2); + the value SEEK_END is not supported. + + If the file is opened for reading, this function is emulated but can be + extremely slow. If the file is opened for writing, only forward seeks are + supported; gzseek then compresses a sequence of zeroes up to the new + starting position. + + gzseek returns the resulting offset location as measured in bytes from + the beginning of the uncompressed stream, or -1 in case of error, in + particular if the file is opened for writing and the new starting position + would be before the current position. +*/ + +ZEXTERN int ZEXPORT gzrewind OF((gzFile file)); +/* + Rewinds the given file. This function is supported only for reading. + + gzrewind(file) is equivalent to (int)gzseek(file, 0L, SEEK_SET) +*/ + +/* +ZEXTERN z_off_t ZEXPORT gztell OF((gzFile file)); + + Returns the starting position for the next gzread or gzwrite on the given + compressed file. This position represents a number of bytes in the + uncompressed data stream, and is zero when starting, even if appending or + reading a gzip stream from the middle of a file using gzdopen(). + + gztell(file) is equivalent to gzseek(file, 0L, SEEK_CUR) +*/ + +/* +ZEXTERN z_off_t ZEXPORT gzoffset OF((gzFile file)); + + Returns the current offset in the file being read or written. This offset + includes the count of bytes that precede the gzip stream, for example when + appending or when using gzdopen() for reading. When reading, the offset + does not include as yet unused buffered input. This information can be used + for a progress indicator. On error, gzoffset() returns -1. +*/ + +ZEXTERN int ZEXPORT gzeof OF((gzFile file)); +/* + Returns true (1) if the end-of-file indicator has been set while reading, + false (0) otherwise. Note that the end-of-file indicator is set only if the + read tried to go past the end of the input, but came up short. Therefore, + just like feof(), gzeof() may return false even if there is no more data to + read, in the event that the last read request was for the exact number of + bytes remaining in the input file. This will happen if the input file size + is an exact multiple of the buffer size. + + If gzeof() returns true, then the read functions will return no more data, + unless the end-of-file indicator is reset by gzclearerr() and the input file + has grown since the previous end of file was detected. +*/ + +ZEXTERN int ZEXPORT gzdirect OF((gzFile file)); +/* + Returns true (1) if file is being copied directly while reading, or false + (0) if file is a gzip stream being decompressed. + + If the input file is empty, gzdirect() will return true, since the input + does not contain a gzip stream. + + If gzdirect() is used immediately after gzopen() or gzdopen() it will + cause buffers to be allocated to allow reading the file to determine if it + is a gzip file. Therefore if gzbuffer() is used, it should be called before + gzdirect(). + + When writing, gzdirect() returns true (1) if transparent writing was + requested ("wT" for the gzopen() mode), or false (0) otherwise. (Note: + gzdirect() is not needed when writing. Transparent writing must be + explicitly requested, so the application already knows the answer. When + linking statically, using gzdirect() will include all of the zlib code for + gzip file reading and decompression, which may not be desired.) +*/ + +ZEXTERN int ZEXPORT gzclose OF((gzFile file)); +/* + Flushes all pending output if necessary, closes the compressed file and + deallocates the (de)compression state. Note that once file is closed, you + cannot call gzerror with file, since its structures have been deallocated. + gzclose must not be called more than once on the same file, just as free + must not be called more than once on the same allocation. + + gzclose will return Z_STREAM_ERROR if file is not valid, Z_ERRNO on a + file operation error, Z_MEM_ERROR if out of memory, Z_BUF_ERROR if the + last read ended in the middle of a gzip stream, or Z_OK on success. +*/ + +ZEXTERN int ZEXPORT gzclose_r OF((gzFile file)); +ZEXTERN int ZEXPORT gzclose_w OF((gzFile file)); +/* + Same as gzclose(), but gzclose_r() is only for use when reading, and + gzclose_w() is only for use when writing or appending. The advantage to + using these instead of gzclose() is that they avoid linking in zlib + compression or decompression code that is not used when only reading or only + writing respectively. If gzclose() is used, then both compression and + decompression code will be included the application when linking to a static + zlib library. +*/ + +ZEXTERN const char * ZEXPORT gzerror OF((gzFile file, int *errnum)); +/* + Returns the error message for the last error which occurred on the given + compressed file. errnum is set to zlib error number. If an error occurred + in the file system and not in the compression library, errnum is set to + Z_ERRNO and the application may consult errno to get the exact error code. + + The application must not modify the returned string. Future calls to + this function may invalidate the previously returned string. If file is + closed, then the string previously returned by gzerror will no longer be + available. + + gzerror() should be used to distinguish errors from end-of-file for those + functions above that do not distinguish those cases in their return values. +*/ + +ZEXTERN void ZEXPORT gzclearerr OF((gzFile file)); +/* + Clears the error and end-of-file flags for file. This is analogous to the + clearerr() function in stdio. This is useful for continuing to read a gzip + file that is being written concurrently. +*/ + +#endif /* !Z_SOLO */ + + /* checksum functions */ + +/* + These functions are not related to compression but are exported + anyway because they might be useful in applications using the compression + library. +*/ + +ZEXTERN uLong ZEXPORT adler32 OF((uLong adler, const Bytef *buf, uInt len)); +/* + Update a running Adler-32 checksum with the bytes buf[0..len-1] and + return the updated checksum. If buf is Z_NULL, this function returns the + required initial value for the checksum. + + An Adler-32 checksum is almost as reliable as a CRC-32 but can be computed + much faster. + + Usage example: + + uLong adler = adler32(0L, Z_NULL, 0); + + while (read_buffer(buffer, length) != EOF) { + adler = adler32(adler, buffer, length); + } + if (adler != original_adler) error(); +*/ + +ZEXTERN uLong ZEXPORT adler32_z OF((uLong adler, const Bytef *buf, + z_size_t len)); +/* + Same as adler32(), but with a size_t length. +*/ + +/* +ZEXTERN uLong ZEXPORT adler32_combine OF((uLong adler1, uLong adler2, + z_off_t len2)); + + Combine two Adler-32 checksums into one. For two sequences of bytes, seq1 + and seq2 with lengths len1 and len2, Adler-32 checksums were calculated for + each, adler1 and adler2. adler32_combine() returns the Adler-32 checksum of + seq1 and seq2 concatenated, requiring only adler1, adler2, and len2. Note + that the z_off_t type (like off_t) is a signed integer. If len2 is + negative, the result has no meaning or utility. +*/ + +ZEXTERN uLong ZEXPORT crc32 OF((uLong crc, const Bytef *buf, uInt len)); +/* + Update a running CRC-32 with the bytes buf[0..len-1] and return the + updated CRC-32. If buf is Z_NULL, this function returns the required + initial value for the crc. Pre- and post-conditioning (one's complement) is + performed within this function so it shouldn't be done by the application. + + Usage example: + + uLong crc = crc32(0L, Z_NULL, 0); + + while (read_buffer(buffer, length) != EOF) { + crc = crc32(crc, buffer, length); + } + if (crc != original_crc) error(); +*/ + +ZEXTERN uLong ZEXPORT crc32_z OF((uLong adler, const Bytef *buf, + z_size_t len)); +/* + Same as crc32(), but with a size_t length. +*/ + +/* +ZEXTERN uLong ZEXPORT crc32_combine OF((uLong crc1, uLong crc2, z_off_t len2)); + + Combine two CRC-32 check values into one. For two sequences of bytes, + seq1 and seq2 with lengths len1 and len2, CRC-32 check values were + calculated for each, crc1 and crc2. crc32_combine() returns the CRC-32 + check value of seq1 and seq2 concatenated, requiring only crc1, crc2, and + len2. +*/ + + + /* various hacks, don't look :) */ + +/* deflateInit and inflateInit are macros to allow checking the zlib version + * and the compiler's view of z_stream: + */ +ZEXTERN int ZEXPORT deflateInit_ OF((z_streamp strm, int level, + const char *version, int stream_size)); +ZEXTERN int ZEXPORT inflateInit_ OF((z_streamp strm, + const char *version, int stream_size)); +ZEXTERN int ZEXPORT deflateInit2_ OF((z_streamp strm, int level, int method, + int windowBits, int memLevel, + int strategy, const char *version, + int stream_size)); +ZEXTERN int ZEXPORT inflateInit2_ OF((z_streamp strm, int windowBits, + const char *version, int stream_size)); +ZEXTERN int ZEXPORT inflateBackInit_ OF((z_streamp strm, int windowBits, + unsigned char FAR *window, + const char *version, + int stream_size)); +#ifdef Z_PREFIX_SET +# define z_deflateInit(strm, level) \ + deflateInit_((strm), (level), ZLIB_VERSION, (int)sizeof(z_stream)) +# define z_inflateInit(strm) \ + inflateInit_((strm), ZLIB_VERSION, (int)sizeof(z_stream)) +# define z_deflateInit2(strm, level, method, windowBits, memLevel, strategy) \ + deflateInit2_((strm),(level),(method),(windowBits),(memLevel),\ + (strategy), ZLIB_VERSION, (int)sizeof(z_stream)) +# define z_inflateInit2(strm, windowBits) \ + inflateInit2_((strm), (windowBits), ZLIB_VERSION, \ + (int)sizeof(z_stream)) +# define z_inflateBackInit(strm, windowBits, window) \ + inflateBackInit_((strm), (windowBits), (window), \ + ZLIB_VERSION, (int)sizeof(z_stream)) +#else +# define deflateInit(strm, level) \ + deflateInit_((strm), (level), ZLIB_VERSION, (int)sizeof(z_stream)) +# define inflateInit(strm) \ + inflateInit_((strm), ZLIB_VERSION, (int)sizeof(z_stream)) +# define deflateInit2(strm, level, method, windowBits, memLevel, strategy) \ + deflateInit2_((strm),(level),(method),(windowBits),(memLevel),\ + (strategy), ZLIB_VERSION, (int)sizeof(z_stream)) +# define inflateInit2(strm, windowBits) \ + inflateInit2_((strm), (windowBits), ZLIB_VERSION, \ + (int)sizeof(z_stream)) +# define inflateBackInit(strm, windowBits, window) \ + inflateBackInit_((strm), (windowBits), (window), \ + ZLIB_VERSION, (int)sizeof(z_stream)) +#endif + +#ifndef Z_SOLO + +/* gzgetc() macro and its supporting function and exposed data structure. Note + * that the real internal state is much larger than the exposed structure. + * This abbreviated structure exposes just enough for the gzgetc() macro. The + * user should not mess with these exposed elements, since their names or + * behavior could change in the future, perhaps even capriciously. They can + * only be used by the gzgetc() macro. You have been warned. + */ +struct gzFile_s { + unsigned have; + unsigned char *next; + z_off64_t pos; +}; +ZEXTERN int ZEXPORT gzgetc_ OF((gzFile file)); /* backward compatibility */ +#ifdef Z_PREFIX_SET +# undef z_gzgetc +# define z_gzgetc(g) \ + ((g)->have ? ((g)->have--, (g)->pos++, *((g)->next)++) : (gzgetc)(g)) +#else +# define gzgetc(g) \ + ((g)->have ? ((g)->have--, (g)->pos++, *((g)->next)++) : (gzgetc)(g)) +#endif + +/* provide 64-bit offset functions if _LARGEFILE64_SOURCE defined, and/or + * change the regular functions to 64 bits if _FILE_OFFSET_BITS is 64 (if + * both are true, the application gets the *64 functions, and the regular + * functions are changed to 64 bits) -- in case these are set on systems + * without large file support, _LFS64_LARGEFILE must also be true + */ +#ifdef Z_LARGE64 + ZEXTERN gzFile ZEXPORT gzopen64 OF((const char *, const char *)); + ZEXTERN z_off64_t ZEXPORT gzseek64 OF((gzFile, z_off64_t, int)); + ZEXTERN z_off64_t ZEXPORT gztell64 OF((gzFile)); + ZEXTERN z_off64_t ZEXPORT gzoffset64 OF((gzFile)); + ZEXTERN uLong ZEXPORT adler32_combine64 OF((uLong, uLong, z_off64_t)); + ZEXTERN uLong ZEXPORT crc32_combine64 OF((uLong, uLong, z_off64_t)); +#endif + +#if !defined(ZLIB_INTERNAL) && defined(Z_WANT64) +# ifdef Z_PREFIX_SET +# define z_gzopen z_gzopen64 +# define z_gzseek z_gzseek64 +# define z_gztell z_gztell64 +# define z_gzoffset z_gzoffset64 +# define z_adler32_combine z_adler32_combine64 +# define z_crc32_combine z_crc32_combine64 +# else +# define gzopen gzopen64 +# define gzseek gzseek64 +# define gztell gztell64 +# define gzoffset gzoffset64 +# define adler32_combine adler32_combine64 +# define crc32_combine crc32_combine64 +# endif +# ifndef Z_LARGE64 + ZEXTERN gzFile ZEXPORT gzopen64 OF((const char *, const char *)); + ZEXTERN z_off_t ZEXPORT gzseek64 OF((gzFile, z_off_t, int)); + ZEXTERN z_off_t ZEXPORT gztell64 OF((gzFile)); + ZEXTERN z_off_t ZEXPORT gzoffset64 OF((gzFile)); + ZEXTERN uLong ZEXPORT adler32_combine64 OF((uLong, uLong, z_off_t)); + ZEXTERN uLong ZEXPORT crc32_combine64 OF((uLong, uLong, z_off_t)); +# endif +#else + ZEXTERN gzFile ZEXPORT gzopen OF((const char *, const char *)); + ZEXTERN z_off_t ZEXPORT gzseek OF((gzFile, z_off_t, int)); + ZEXTERN z_off_t ZEXPORT gztell OF((gzFile)); + ZEXTERN z_off_t ZEXPORT gzoffset OF((gzFile)); + ZEXTERN uLong ZEXPORT adler32_combine OF((uLong, uLong, z_off_t)); + ZEXTERN uLong ZEXPORT crc32_combine OF((uLong, uLong, z_off_t)); +#endif + +#else /* Z_SOLO */ + + ZEXTERN uLong ZEXPORT adler32_combine OF((uLong, uLong, z_off_t)); + ZEXTERN uLong ZEXPORT crc32_combine OF((uLong, uLong, z_off_t)); + +#endif /* !Z_SOLO */ + +/* undocumented functions */ +ZEXTERN const char * ZEXPORT zError OF((int)); +ZEXTERN int ZEXPORT inflateSyncPoint OF((z_streamp)); +ZEXTERN const z_crc_t FAR * ZEXPORT get_crc_table OF((void)); +ZEXTERN int ZEXPORT inflateUndermine OF((z_streamp, int)); +ZEXTERN int ZEXPORT inflateValidate OF((z_streamp, int)); +ZEXTERN unsigned long ZEXPORT inflateCodesUsed OF ((z_streamp)); +ZEXTERN int ZEXPORT inflateResetKeep OF((z_streamp)); +ZEXTERN int ZEXPORT deflateResetKeep OF((z_streamp)); +#if (defined(_WIN32) || defined(__CYGWIN__)) && !defined(Z_SOLO) +ZEXTERN gzFile ZEXPORT gzopen_w OF((const wchar_t *path, + const char *mode)); +#endif +#if defined(STDC) || defined(Z_HAVE_STDARG_H) +# ifndef Z_SOLO +ZEXTERN int ZEXPORTVA gzvprintf Z_ARG((gzFile file, + const char *format, + va_list va)); +# endif +#endif + +#ifdef __cplusplus +} +#endif + +#endif /* ZLIB_H */ ADDED compat/zlib/zlib.map Index: compat/zlib/zlib.map ================================================================== --- /dev/null +++ compat/zlib/zlib.map @@ -0,0 +1,94 @@ +ZLIB_1.2.0 { + global: + compressBound; + deflateBound; + inflateBack; + inflateBackEnd; + inflateBackInit_; + inflateCopy; + local: + deflate_copyright; + inflate_copyright; + inflate_fast; + inflate_table; + zcalloc; + zcfree; + z_errmsg; + gz_error; + gz_intmax; + _*; +}; + +ZLIB_1.2.0.2 { + gzclearerr; + gzungetc; + zlibCompileFlags; +} ZLIB_1.2.0; + +ZLIB_1.2.0.8 { + deflatePrime; +} ZLIB_1.2.0.2; + +ZLIB_1.2.2 { + adler32_combine; + crc32_combine; + deflateSetHeader; + inflateGetHeader; +} ZLIB_1.2.0.8; + +ZLIB_1.2.2.3 { + deflateTune; + gzdirect; +} ZLIB_1.2.2; + +ZLIB_1.2.2.4 { + inflatePrime; +} ZLIB_1.2.2.3; + +ZLIB_1.2.3.3 { + adler32_combine64; + crc32_combine64; + gzopen64; + gzseek64; + gztell64; + inflateUndermine; +} ZLIB_1.2.2.4; + +ZLIB_1.2.3.4 { + inflateReset2; + inflateMark; +} ZLIB_1.2.3.3; + +ZLIB_1.2.3.5 { + gzbuffer; + gzoffset; + gzoffset64; + gzclose_r; + gzclose_w; +} ZLIB_1.2.3.4; + +ZLIB_1.2.5.1 { + deflatePending; +} ZLIB_1.2.3.5; + +ZLIB_1.2.5.2 { + deflateResetKeep; + gzgetc_; + inflateResetKeep; +} ZLIB_1.2.5.1; + +ZLIB_1.2.7.1 { + inflateGetDictionary; + gzvprintf; +} ZLIB_1.2.5.2; + +ZLIB_1.2.9 { + inflateCodesUsed; + inflateValidate; + uncompress2; + gzfread; + gzfwrite; + deflateGetDictionary; + adler32_z; + crc32_z; +} ZLIB_1.2.7.1; ADDED compat/zlib/zlib.pc.cmakein Index: compat/zlib/zlib.pc.cmakein ================================================================== --- /dev/null +++ compat/zlib/zlib.pc.cmakein @@ -0,0 +1,13 @@ +prefix=@CMAKE_INSTALL_PREFIX@ +exec_prefix=@CMAKE_INSTALL_PREFIX@ +libdir=@INSTALL_LIB_DIR@ +sharedlibdir=@INSTALL_LIB_DIR@ +includedir=@INSTALL_INC_DIR@ + +Name: zlib +Description: zlib compression library +Version: @VERSION@ + +Requires: +Libs: -L${libdir} -L${sharedlibdir} -lz +Cflags: -I${includedir} ADDED compat/zlib/zlib.pc.in Index: compat/zlib/zlib.pc.in ================================================================== --- /dev/null +++ compat/zlib/zlib.pc.in @@ -0,0 +1,13 @@ +prefix=@prefix@ +exec_prefix=@exec_prefix@ +libdir=@libdir@ +sharedlibdir=@sharedlibdir@ +includedir=@includedir@ + +Name: zlib +Description: zlib compression library +Version: @VERSION@ + +Requires: +Libs: -L${libdir} -L${sharedlibdir} -lz +Cflags: -I${includedir} ADDED compat/zlib/zlib2ansi Index: compat/zlib/zlib2ansi ================================================================== --- /dev/null +++ compat/zlib/zlib2ansi @@ -0,0 +1,152 @@ +#!/usr/bin/perl + +# Transform K&R C function definitions into ANSI equivalent. +# +# Author: Paul Marquess +# Version: 1.0 +# Date: 3 October 2006 + +# TODO +# +# Asumes no function pointer parameters. unless they are typedefed. +# Assumes no literal strings that look like function definitions +# Assumes functions start at the beginning of a line + +use strict; +use warnings; + +local $/; +$_ = <>; + +my $sp = qr{ \s* (?: /\* .*? \*/ )? \s* }x; # assume no nested comments + +my $d1 = qr{ $sp (?: [\w\*\s]+ $sp)* $sp \w+ $sp [\[\]\s]* $sp }x ; +my $decl = qr{ $sp (?: \w+ $sp )+ $d1 }xo ; +my $dList = qr{ $sp $decl (?: $sp , $d1 )* $sp ; $sp }xo ; + + +while (s/^ + ( # Start $1 + ( # Start $2 + .*? # Minimal eat content + ( ^ \w [\w\s\*]+ ) # $3 -- function name + \s* # optional whitespace + ) # $2 - Matched up to before parameter list + + \( \s* # Literal "(" + optional whitespace + ( [^\)]+ ) # $4 - one or more anythings except ")" + \s* \) # optional whitespace surrounding a Literal ")" + + ( (?: $dList )+ ) # $5 + + $sp ^ { # literal "{" at start of line + ) # Remember to $1 + //xsom + ) +{ + my $all = $1 ; + my $prefix = $2; + my $param_list = $4 ; + my $params = $5; + + StripComments($params); + StripComments($param_list); + $param_list =~ s/^\s+//; + $param_list =~ s/\s+$//; + + my $i = 0 ; + my %pList = map { $_ => $i++ } + split /\s*,\s*/, $param_list; + my $pMatch = '(\b' . join('|', keys %pList) . '\b)\W*$' ; + + my @params = split /\s*;\s*/, $params; + my @outParams = (); + foreach my $p (@params) + { + if ($p =~ /,/) + { + my @bits = split /\s*,\s*/, $p; + my $first = shift @bits; + $first =~ s/^\s*//; + push @outParams, $first; + $first =~ /^(\w+\s*)/; + my $type = $1 ; + push @outParams, map { $type . $_ } @bits; + } + else + { + $p =~ s/^\s+//; + push @outParams, $p; + } + } + + + my %tmp = map { /$pMatch/; $_ => $pList{$1} } + @outParams ; + + @outParams = map { " $_" } + sort { $tmp{$a} <=> $tmp{$b} } + @outParams ; + + print $prefix ; + print "(\n" . join(",\n", @outParams) . ")\n"; + print "{" ; + +} + +# Output any trailing code. +print ; +exit 0; + + +sub StripComments +{ + + no warnings; + + # Strip C & C++ coments + # From the perlfaq + $_[0] =~ + + s{ + /\* ## Start of /* ... */ comment + [^*]*\*+ ## Non-* followed by 1-or-more *'s + ( + [^/*][^*]*\*+ + )* ## 0-or-more things which don't start with / + ## but do end with '*' + / ## End of /* ... */ comment + + | ## OR C++ Comment + // ## Start of C++ comment // + [^\n]* ## followed by 0-or-more non end of line characters + + | ## OR various things which aren't comments: + + ( + " ## Start of " ... " string + ( + \\. ## Escaped char + | ## OR + [^"\\] ## Non "\ + )* + " ## End of " ... " string + + | ## OR + + ' ## Start of ' ... ' string + ( + \\. ## Escaped char + | ## OR + [^'\\] ## Non '\ + )* + ' ## End of ' ... ' string + + | ## OR + + . ## Anything other char + [^/"'\\]* ## Chars which doesn't start a comment, string or escape + ) + }{$2}gxs; + +} ADDED compat/zlib/zutil.c Index: compat/zlib/zutil.c ================================================================== --- /dev/null +++ compat/zlib/zutil.c @@ -0,0 +1,325 @@ +/* zutil.c -- target dependent utility functions for the compression library + * Copyright (C) 1995-2017 Jean-loup Gailly + * For conditions of distribution and use, see copyright notice in zlib.h + */ + +/* @(#) $Id$ */ + +#include "zutil.h" +#ifndef Z_SOLO +# include "gzguts.h" +#endif + +z_const char * const z_errmsg[10] = { + (z_const char *)"need dictionary", /* Z_NEED_DICT 2 */ + (z_const char *)"stream end", /* Z_STREAM_END 1 */ + (z_const char *)"", /* Z_OK 0 */ + (z_const char *)"file error", /* Z_ERRNO (-1) */ + (z_const char *)"stream error", /* Z_STREAM_ERROR (-2) */ + (z_const char *)"data error", /* Z_DATA_ERROR (-3) */ + (z_const char *)"insufficient memory", /* Z_MEM_ERROR (-4) */ + (z_const char *)"buffer error", /* Z_BUF_ERROR (-5) */ + (z_const char *)"incompatible version",/* Z_VERSION_ERROR (-6) */ + (z_const char *)"" +}; + + +const char * ZEXPORT zlibVersion() +{ + return ZLIB_VERSION; +} + +uLong ZEXPORT zlibCompileFlags() +{ + uLong flags; + + flags = 0; + switch ((int)(sizeof(uInt))) { + case 2: break; + case 4: flags += 1; break; + case 8: flags += 2; break; + default: flags += 3; + } + switch ((int)(sizeof(uLong))) { + case 2: break; + case 4: flags += 1 << 2; break; + case 8: flags += 2 << 2; break; + default: flags += 3 << 2; + } + switch ((int)(sizeof(voidpf))) { + case 2: break; + case 4: flags += 1 << 4; break; + case 8: flags += 2 << 4; break; + default: flags += 3 << 4; + } + switch ((int)(sizeof(z_off_t))) { + case 2: break; + case 4: flags += 1 << 6; break; + case 8: flags += 2 << 6; break; + default: flags += 3 << 6; + } +#ifdef ZLIB_DEBUG + flags += 1 << 8; +#endif +#if defined(ASMV) || defined(ASMINF) + flags += 1 << 9; +#endif +#ifdef ZLIB_WINAPI + flags += 1 << 10; +#endif +#ifdef BUILDFIXED + flags += 1 << 12; +#endif +#ifdef DYNAMIC_CRC_TABLE + flags += 1 << 13; +#endif +#ifdef NO_GZCOMPRESS + flags += 1L << 16; +#endif +#ifdef NO_GZIP + flags += 1L << 17; +#endif +#ifdef PKZIP_BUG_WORKAROUND + flags += 1L << 20; +#endif +#ifdef FASTEST + flags += 1L << 21; +#endif +#if defined(STDC) || defined(Z_HAVE_STDARG_H) +# ifdef NO_vsnprintf + flags += 1L << 25; +# ifdef HAS_vsprintf_void + flags += 1L << 26; +# endif +# else +# ifdef HAS_vsnprintf_void + flags += 1L << 26; +# endif +# endif +#else + flags += 1L << 24; +# ifdef NO_snprintf + flags += 1L << 25; +# ifdef HAS_sprintf_void + flags += 1L << 26; +# endif +# else +# ifdef HAS_snprintf_void + flags += 1L << 26; +# endif +# endif +#endif + return flags; +} + +#ifdef ZLIB_DEBUG +#include +# ifndef verbose +# define verbose 0 +# endif +int ZLIB_INTERNAL z_verbose = verbose; + +void ZLIB_INTERNAL z_error (m) + char *m; +{ + fprintf(stderr, "%s\n", m); + exit(1); +} +#endif + +/* exported to allow conversion of error code to string for compress() and + * uncompress() + */ +const char * ZEXPORT zError(err) + int err; +{ + return ERR_MSG(err); +} + +#if defined(_WIN32_WCE) + /* The Microsoft C Run-Time Library for Windows CE doesn't have + * errno. We define it as a global variable to simplify porting. + * Its value is always 0 and should not be used. + */ + int errno = 0; +#endif + +#ifndef HAVE_MEMCPY + +void ZLIB_INTERNAL zmemcpy(dest, source, len) + Bytef* dest; + const Bytef* source; + uInt len; +{ + if (len == 0) return; + do { + *dest++ = *source++; /* ??? to be unrolled */ + } while (--len != 0); +} + +int ZLIB_INTERNAL zmemcmp(s1, s2, len) + const Bytef* s1; + const Bytef* s2; + uInt len; +{ + uInt j; + + for (j = 0; j < len; j++) { + if (s1[j] != s2[j]) return 2*(s1[j] > s2[j])-1; + } + return 0; +} + +void ZLIB_INTERNAL zmemzero(dest, len) + Bytef* dest; + uInt len; +{ + if (len == 0) return; + do { + *dest++ = 0; /* ??? to be unrolled */ + } while (--len != 0); +} +#endif + +#ifndef Z_SOLO + +#ifdef SYS16BIT + +#ifdef __TURBOC__ +/* Turbo C in 16-bit mode */ + +# define MY_ZCALLOC + +/* Turbo C malloc() does not allow dynamic allocation of 64K bytes + * and farmalloc(64K) returns a pointer with an offset of 8, so we + * must fix the pointer. Warning: the pointer must be put back to its + * original form in order to free it, use zcfree(). + */ + +#define MAX_PTR 10 +/* 10*64K = 640K */ + +local int next_ptr = 0; + +typedef struct ptr_table_s { + voidpf org_ptr; + voidpf new_ptr; +} ptr_table; + +local ptr_table table[MAX_PTR]; +/* This table is used to remember the original form of pointers + * to large buffers (64K). Such pointers are normalized with a zero offset. + * Since MSDOS is not a preemptive multitasking OS, this table is not + * protected from concurrent access. This hack doesn't work anyway on + * a protected system like OS/2. Use Microsoft C instead. + */ + +voidpf ZLIB_INTERNAL zcalloc (voidpf opaque, unsigned items, unsigned size) +{ + voidpf buf; + ulg bsize = (ulg)items*size; + + (void)opaque; + + /* If we allocate less than 65520 bytes, we assume that farmalloc + * will return a usable pointer which doesn't have to be normalized. + */ + if (bsize < 65520L) { + buf = farmalloc(bsize); + if (*(ush*)&buf != 0) return buf; + } else { + buf = farmalloc(bsize + 16L); + } + if (buf == NULL || next_ptr >= MAX_PTR) return NULL; + table[next_ptr].org_ptr = buf; + + /* Normalize the pointer to seg:0 */ + *((ush*)&buf+1) += ((ush)((uch*)buf-0) + 15) >> 4; + *(ush*)&buf = 0; + table[next_ptr++].new_ptr = buf; + return buf; +} + +void ZLIB_INTERNAL zcfree (voidpf opaque, voidpf ptr) +{ + int n; + + (void)opaque; + + if (*(ush*)&ptr != 0) { /* object < 64K */ + farfree(ptr); + return; + } + /* Find the original pointer */ + for (n = 0; n < next_ptr; n++) { + if (ptr != table[n].new_ptr) continue; + + farfree(table[n].org_ptr); + while (++n < next_ptr) { + table[n-1] = table[n]; + } + next_ptr--; + return; + } + Assert(0, "zcfree: ptr not found"); +} + +#endif /* __TURBOC__ */ + + +#ifdef M_I86 +/* Microsoft C in 16-bit mode */ + +# define MY_ZCALLOC + +#if (!defined(_MSC_VER) || (_MSC_VER <= 600)) +# define _halloc halloc +# define _hfree hfree +#endif + +voidpf ZLIB_INTERNAL zcalloc (voidpf opaque, uInt items, uInt size) +{ + (void)opaque; + return _halloc((long)items, size); +} + +void ZLIB_INTERNAL zcfree (voidpf opaque, voidpf ptr) +{ + (void)opaque; + _hfree(ptr); +} + +#endif /* M_I86 */ + +#endif /* SYS16BIT */ + + +#ifndef MY_ZCALLOC /* Any system without a special alloc function */ + +#ifndef STDC +extern voidp malloc OF((uInt size)); +extern voidp calloc OF((uInt items, uInt size)); +extern void free OF((voidpf ptr)); +#endif + +voidpf ZLIB_INTERNAL zcalloc (opaque, items, size) + voidpf opaque; + unsigned items; + unsigned size; +{ + (void)opaque; + return sizeof(uInt) > 2 ? (voidpf)malloc(items * size) : + (voidpf)calloc(items, size); +} + +void ZLIB_INTERNAL zcfree (opaque, ptr) + voidpf opaque; + voidpf ptr; +{ + (void)opaque; + free(ptr); +} + +#endif /* MY_ZCALLOC */ + +#endif /* !Z_SOLO */ ADDED compat/zlib/zutil.h Index: compat/zlib/zutil.h ================================================================== --- /dev/null +++ compat/zlib/zutil.h @@ -0,0 +1,271 @@ +/* zutil.h -- internal interface and configuration of the compression library + * Copyright (C) 1995-2016 Jean-loup Gailly, Mark Adler + * For conditions of distribution and use, see copyright notice in zlib.h + */ + +/* WARNING: this file should *not* be used by applications. It is + part of the implementation of the compression library and is + subject to change. Applications should only use zlib.h. + */ + +/* @(#) $Id$ */ + +#ifndef ZUTIL_H +#define ZUTIL_H + +#ifdef HAVE_HIDDEN +# define ZLIB_INTERNAL __attribute__((visibility ("hidden"))) +#else +# define ZLIB_INTERNAL +#endif + +#include "zlib.h" + +#if defined(STDC) && !defined(Z_SOLO) +# if !(defined(_WIN32_WCE) && defined(_MSC_VER)) +# include +# endif +# include +# include +#endif + +#ifdef Z_SOLO + typedef long ptrdiff_t; /* guess -- will be caught if guess is wrong */ +#endif + +#ifndef local +# define local static +#endif +/* since "static" is used to mean two completely different things in C, we + define "local" for the non-static meaning of "static", for readability + (compile with -Dlocal if your debugger can't find static symbols) */ + +typedef unsigned char uch; +typedef uch FAR uchf; +typedef unsigned short ush; +typedef ush FAR ushf; +typedef unsigned long ulg; + +extern z_const char * const z_errmsg[10]; /* indexed by 2-zlib_error */ +/* (size given to avoid silly warnings with Visual C++) */ + +#define ERR_MSG(err) z_errmsg[Z_NEED_DICT-(err)] + +#define ERR_RETURN(strm,err) \ + return (strm->msg = ERR_MSG(err), (err)) +/* To be used only when the state is known to be valid */ + + /* common constants */ + +#ifndef DEF_WBITS +# define DEF_WBITS MAX_WBITS +#endif +/* default windowBits for decompression. MAX_WBITS is for compression only */ + +#if MAX_MEM_LEVEL >= 8 +# define DEF_MEM_LEVEL 8 +#else +# define DEF_MEM_LEVEL MAX_MEM_LEVEL +#endif +/* default memLevel */ + +#define STORED_BLOCK 0 +#define STATIC_TREES 1 +#define DYN_TREES 2 +/* The three kinds of block type */ + +#define MIN_MATCH 3 +#define MAX_MATCH 258 +/* The minimum and maximum match lengths */ + +#define PRESET_DICT 0x20 /* preset dictionary flag in zlib header */ + + /* target dependencies */ + +#if defined(MSDOS) || (defined(WINDOWS) && !defined(WIN32)) +# define OS_CODE 0x00 +# ifndef Z_SOLO +# if defined(__TURBOC__) || defined(__BORLANDC__) +# if (__STDC__ == 1) && (defined(__LARGE__) || defined(__COMPACT__)) + /* Allow compilation with ANSI keywords only enabled */ + void _Cdecl farfree( void *block ); + void *_Cdecl farmalloc( unsigned long nbytes ); +# else +# include +# endif +# else /* MSC or DJGPP */ +# include +# endif +# endif +#endif + +#ifdef AMIGA +# define OS_CODE 1 +#endif + +#if defined(VAXC) || defined(VMS) +# define OS_CODE 2 +# define F_OPEN(name, mode) \ + fopen((name), (mode), "mbc=60", "ctx=stm", "rfm=fix", "mrs=512") +#endif + +#ifdef __370__ +# if __TARGET_LIB__ < 0x20000000 +# define OS_CODE 4 +# elif __TARGET_LIB__ < 0x40000000 +# define OS_CODE 11 +# else +# define OS_CODE 8 +# endif +#endif + +#if defined(ATARI) || defined(atarist) +# define OS_CODE 5 +#endif + +#ifdef OS2 +# define OS_CODE 6 +# if defined(M_I86) && !defined(Z_SOLO) +# include +# endif +#endif + +#if defined(MACOS) || defined(TARGET_OS_MAC) +# define OS_CODE 7 +# ifndef Z_SOLO +# if defined(__MWERKS__) && __dest_os != __be_os && __dest_os != __win32_os +# include /* for fdopen */ +# else +# ifndef fdopen +# define fdopen(fd,mode) NULL /* No fdopen() */ +# endif +# endif +# endif +#endif + +#ifdef __acorn +# define OS_CODE 13 +#endif + +#if defined(WIN32) && !defined(__CYGWIN__) +# define OS_CODE 10 +#endif + +#ifdef _BEOS_ +# define OS_CODE 16 +#endif + +#ifdef __TOS_OS400__ +# define OS_CODE 18 +#endif + +#ifdef __APPLE__ +# define OS_CODE 19 +#endif + +#if defined(_BEOS_) || defined(RISCOS) +# define fdopen(fd,mode) NULL /* No fdopen() */ +#endif + +#if (defined(_MSC_VER) && (_MSC_VER > 600)) && !defined __INTERIX +# if defined(_WIN32_WCE) +# define fdopen(fd,mode) NULL /* No fdopen() */ +# ifndef _PTRDIFF_T_DEFINED + typedef int ptrdiff_t; +# define _PTRDIFF_T_DEFINED +# endif +# else +# define fdopen(fd,type) _fdopen(fd,type) +# endif +#endif + +#if defined(__BORLANDC__) && !defined(MSDOS) + #pragma warn -8004 + #pragma warn -8008 + #pragma warn -8066 +#endif + +/* provide prototypes for these when building zlib without LFS */ +#if !defined(_WIN32) && \ + (!defined(_LARGEFILE64_SOURCE) || _LFS64_LARGEFILE-0 == 0) + ZEXTERN uLong ZEXPORT adler32_combine64 OF((uLong, uLong, z_off_t)); + ZEXTERN uLong ZEXPORT crc32_combine64 OF((uLong, uLong, z_off_t)); +#endif + + /* common defaults */ + +#ifndef OS_CODE +# define OS_CODE 3 /* assume Unix */ +#endif + +#ifndef F_OPEN +# define F_OPEN(name, mode) fopen((name), (mode)) +#endif + + /* functions */ + +#if defined(pyr) || defined(Z_SOLO) +# define NO_MEMCPY +#endif +#if defined(SMALL_MEDIUM) && !defined(_MSC_VER) && !defined(__SC__) + /* Use our own functions for small and medium model with MSC <= 5.0. + * You may have to use the same strategy for Borland C (untested). + * The __SC__ check is for Symantec. + */ +# define NO_MEMCPY +#endif +#if defined(STDC) && !defined(HAVE_MEMCPY) && !defined(NO_MEMCPY) +# define HAVE_MEMCPY +#endif +#ifdef HAVE_MEMCPY +# ifdef SMALL_MEDIUM /* MSDOS small or medium model */ +# define zmemcpy _fmemcpy +# define zmemcmp _fmemcmp +# define zmemzero(dest, len) _fmemset(dest, 0, len) +# else +# define zmemcpy memcpy +# define zmemcmp memcmp +# define zmemzero(dest, len) memset(dest, 0, len) +# endif +#else + void ZLIB_INTERNAL zmemcpy OF((Bytef* dest, const Bytef* source, uInt len)); + int ZLIB_INTERNAL zmemcmp OF((const Bytef* s1, const Bytef* s2, uInt len)); + void ZLIB_INTERNAL zmemzero OF((Bytef* dest, uInt len)); +#endif + +/* Diagnostic functions */ +#ifdef ZLIB_DEBUG +# include + extern int ZLIB_INTERNAL z_verbose; + extern void ZLIB_INTERNAL z_error OF((char *m)); +# define Assert(cond,msg) {if(!(cond)) z_error(msg);} +# define Trace(x) {if (z_verbose>=0) fprintf x ;} +# define Tracev(x) {if (z_verbose>0) fprintf x ;} +# define Tracevv(x) {if (z_verbose>1) fprintf x ;} +# define Tracec(c,x) {if (z_verbose>0 && (c)) fprintf x ;} +# define Tracecv(c,x) {if (z_verbose>1 && (c)) fprintf x ;} +#else +# define Assert(cond,msg) +# define Trace(x) +# define Tracev(x) +# define Tracevv(x) +# define Tracec(c,x) +# define Tracecv(c,x) +#endif + +#ifndef Z_SOLO + voidpf ZLIB_INTERNAL zcalloc OF((voidpf opaque, unsigned items, + unsigned size)); + void ZLIB_INTERNAL zcfree OF((voidpf opaque, voidpf ptr)); +#endif + +#define ZALLOC(strm, items, size) \ + (*((strm)->zalloc))((strm)->opaque, (items), (size)) +#define ZFREE(strm, addr) (*((strm)->zfree))((strm)->opaque, (voidpf)(addr)) +#define TRY_FREE(s, p) {if (p) ZFREE(s, p);} + +/* Reverse the bytes in a 32-bit value */ +#define ZSWAP32(q) ((((q) >> 24) & 0xff) + (((q) >> 8) & 0xff00) + \ + (((q) & 0xff00) << 8) + (((q) & 0xff) << 24)) + +#endif /* ZUTIL_H */ ADDED configure Index: configure ================================================================== --- /dev/null +++ configure @@ -0,0 +1,3 @@ +#!/bin/sh +dir="`dirname "$0"`/autosetup" +WRAPPER="$0"; export WRAPPER; exec "`"$dir/autosetup-find-tclsh"`" "$dir/autosetup" "$@" DELETED cvs2fossil.txt Index: cvs2fossil.txt ================================================================== --- cvs2fossil.txt +++ /dev/null @@ -1,66 +0,0 @@ - -Known problems and areas to work on -=================================== - -* Not yet able to handle the specification of multiple projects - for one CVS repository. I.e. I can, for example, import all of - tcllib, or a single subproject of tcllib, like tklib, but not - multiple sub-projects in one go. - -* Consider to rework the breaker- and sort-passes so that they - do not need all changesets as objects in memory. - - Current memory consumption after all changesets are loaded: - - bwidget 6971627 6.6 - cvs-memchan 4634049 4.4 - cvs-sqlite 45674501 43.6 - cvs-trf 8781289 8.4 - faqs 2835116 2.7 - libtommath 4405066 4.2 - mclistbox 3350190 3.2 - newclock 5020460 4.8 - oocore 4064574 3.9 - sampleextension 4729932 4.5 - tclapps 8482135 8.1 - tclbench 4116887 3.9 - tcl_bignum 2545192 2.4 - tclconfig 4105042 3.9 - tcllib 31707688 30.2 - tcltutorial 3512048 3.3 - tcl 109926382 104.8 - thread 8953139 8.5 - tklib 13935220 13.3 - tk 66149870 63.1 - widget 2625609 2.5 - -* Look at the dependencies on external packages and consider - which of them can be moved into the importer, either as a - simple utility command, or wholesale. - - struct::list - assign, map, reverse, filter - - Very few and self-contained commands. - - struct::set - size, empty, contains, add, include, exclude, - intersect, subsetof - - Most of the core commands. - - fileutil - cat, appendToFile, writeFile, - tempfile, stripPath, test - - fileutil::traverse - In toto - - struct::graph - In toto - - snit - In toto - - sqlite3 - In toto Index: debian/makedeb.sh ================================================================== --- debian/makedeb.sh +++ debian/makedeb.sh @@ -1,21 +1,19 @@ #!/bin/bash # A quick hack to generate a Debian package of fossil. i took most of this # from Martin Krafft's "The Debian System" book. DEB_REV=${1-1} # .deb package build/revision number. -PACKAGE_DEBNAME=fossil-scm +PACKAGE_DEBNAME=fossil THISDIR=${PWD} if uname -a | grep -i nexenta &>/dev/null; then # Assume NexentaOS/GnuSolaris: - DEB_PLATFORM=nexenta DEB_ARCH_NAME=solaris-i386 DEB_ARCH_PKGDEPENDS="sunwcsl" # for -lsocket else - DEB_PLATFORM=${DEB_PLATFORM-ubuntu-gutsy} - DEB_ARCH_NAME=i386 + DEB_ARCH_NAME=$(dpkg --print-architecture) fi SRCDIR=$(cd ..; pwd) test -e ${SRCDIR}/fossil || { echo "This script must be run from a BUILT copy of the source tree." @@ -41,11 +39,11 @@ rm -fr DEBIAN mkdir DEBIAN PACKAGE_VERSION=$(date +%Y.%m.%d) PACKAGE_DEB_VERSION=${PACKAGE_VERSION}-${DEB_REV} -DEBFILE=${THISDIR}/${PACKAGE_DEBNAME}-${PACKAGE_DEB_VERSION}-dev-${DEB_ARCH_NAME}-${DEB_PLATFORM}.deb +DEBFILE=${THISDIR}/${PACKAGE_DEBNAME}-${PACKAGE_DEB_VERSION}-dev-${DEB_ARCH_NAME}.deb PACKAGE_TIME=$(/bin/date) rm -f ${DEBFILE} echo "Creating .deb package [${DEBFILE}]..." @@ -54,18 +52,18 @@ true && { echo "Generating Debian-specific files..." COPYRIGHT=${DEBLOCALPREFIX}/share/doc/${PACKAGE_DEBNAME}/copyright cat < ${COPYRIGHT} -This package was created by stephan beal +This package was created by fossil-scm on ${PACKAGE_TIME}. The original sources for fossil can be downloaded for free from: -http://www.fossil-scm.org/ +http://fossil-scm.org/ -fossil is released under the terms of the GNU General Public License. +fossil is released under the terms of the 2-clause BSD License. EOF } true && { @@ -74,11 +72,11 @@ ${PACKAGE_DEBNAME} ${PACKAGE_DEB_VERSION}; urgency=low This release has no changes over the core source distribution. It has simply been Debianized. -Packaged by stephan beal on +Packaged by fossil-dev on ${PACKAGE_TIME}. EOF } @@ -87,24 +85,24 @@ true && { CONTROL=DEBIAN/control echo "Generating ${CONTROL}..." cat < ${CONTROL} Package: ${PACKAGE_DEBNAME} -Section: devel +Section: vcs Priority: optional -Maintainer: stephan beal +Maintainer: fossil-dev Architecture: ${DEB_ARCH_NAME} -Depends: libc6-dev ${DEB_ARCH_PKGDEPENDS+, }${DEB_ARCH_PKGDEPENDS} +Depends: libc6 ${DEB_ARCH_PKGDEPENDS+, }${DEB_ARCH_PKGDEPENDS} Version: ${PACKAGE_DEB_VERSION} Description: Fossil is a unique SCM (Software Configuration Management) system. This package contains the Fossil binary for *buntu/Debian systems. Fossil is a unique SCM program which supports distributed source control management using local repositories, access over HTTP CGI, or using the built-in HTTP server. It has a built-in wiki, file browsing, etc. Fossil home page: http://fossil-scm.org Fossil author: D. Richard Hipp - License: GNU GPLv2 + License: 2-clause BSD EOF } ADDED fossil.1 Index: fossil.1 ================================================================== --- /dev/null +++ fossil.1 @@ -0,0 +1,102 @@ +.TH FOSSIL "1" "September 2018" "http://fossil-scm.org" "User Commands" +.SH NAME +fossil \- Distributed Version Control System +.SH SYNOPSIS +.B fossil +\fIhelp\fR +.br +.B fossil +\fIhelp COMMAND\fR +.br +.B fossil +\fICOMMAND [OPTIONS]\fR +.SH DESCRIPTION +Fossil is a distributed version control system (DVCS) with built-in +forum, wiki, ticket tracker, CGI/HTTP interface, and HTTP server. + +.SH Common COMMANDs: + +add cat diff ls revert timeline +.br +addremove changes extras merge rm ui +.br +all chat finfo mv settings undo +.br +amend clean gdiff open sql unversioned +.br +annotate clone grep pull stash update +.br +bisect commit help push status version +.br +blame dbstat info rebuild sync +.br +branch delete init remote tag +.br + +.SH FEATURES + +Features as described on the fossil home page. + +.HP +1. +.B Integrated Bug Tracking, Wiki, Forum, and Technotes +- In addition to doing distributed version control like Git and +Mercurial, Fossil also supports bug tracking, wiki, forum, and +technotes. + +.HP +2. +.B Built-in Web Interface +- Fossil has a built-in and intuitive web interface that promotes +project situational awareness. Type "fossil ui" and Fossil automatically +opens a web browser to a page that shows detailed graphical history and +status information on that project. + +.HP +3. +.B Self-Contained +- Fossil is a single self-contained stand-alone executable. To install, +simply download a precompiled binary for Linux, Mac, OpenBSD, or Windows +and put it on your $PATH. Easy-to-compile source code is available for +users on other platforms. + +.HP +4. +.B Simple Networking +- No custom protocols or TCP ports. Fossil uses plain old HTTP (or HTTPS +or SSH) for all network communications, so it works fine from behind +restrictive firewalls, including proxies. The protocol is bandwidth +efficient to the point that Fossil can be used comfortably over dial-up +or over the exceedingly slow Wifi on airliners. + +.HP +5. +.B CGI/SCGI Enabled +- No server is required, but if you want to set one up, Fossil supports +four easy server configurations. + +.HP +6. +.B Autosync +- Fossil supports "autosync" mode which helps to keep projects moving +forward by reducing the amount of needless forking and merging often +associated with distributed projects. + +.HP +7. +.B Robust & Reliable +- Fossil stores content using an enduring file format in an SQLite +database so that transactions are atomic even if interrupted by a +power loss or system crash. Automatic self-checks verify that all +aspects of the repository are consistent prior to each commit. + +.HP +8. +.B Free and Open-Source +- Uses the 2-clause BSD license. + +.SH DOCUMENTATION +http://fossil-scm.org/ +.br +.B fossil +\fIui\fR DELETED fossil.nsi Index: fossil.nsi ================================================================== --- fossil.nsi +++ /dev/null @@ -1,59 +0,0 @@ -; example2.nsi -; -; This script is based on example1.nsi, but adds uninstall support -; and (optionally) start menu shortcuts. -; -; It will install notepad.exe into a directory that the user selects, -; - -; The name of the installer -Name "Fossil" - -; The file to write -OutFile "fossil-setup-7c0bd3ee08.exe" - -; The default installation directory -InstallDir $PROGRAMFILES\Fossil -; Registry key to check for directory (so if you install again, it will -; overwrite the old one automatically) -InstallDirRegKey HKLM SOFTWARE\Fossil "Install_Dir" - -; The text to prompt the user to enter a directory -ComponentText "This will install fossil on your computer." -; The text to prompt the user to enter a directory -DirText "Choose a directory to install in to:" - -; The stuff to install -Section "Fossil (required)" - ; Set output path to the installation directory. - SetOutPath $INSTDIR - ; Put file there - File ".\build\fossil.exe" - ; Write the installation path into the registry - WriteRegStr HKLM SOFTWARE\Fossil "Install_Dir" "$INSTDIR" - ; Write the uninstall keys for Windows - WriteRegStr HKLM "Software\Microsoft\Windows\CurrentVersion\Uninstall\Fossil" "DisplayName" "Fossil (remove only)" - WriteRegStr HKLM "Software\Microsoft\Windows\CurrentVersion\Uninstall\Fossil" "UninstallString" '"$INSTDIR\uninstall.exe"' - WriteUninstaller "uninstall.exe" -SectionEnd - - -; uninstall stuff - -UninstallText "This will uninstall fossil. Hit next to continue." - -; special uninstall section. -Section "Uninstall" - ; remove registry keys - DeleteRegKey HKLM "Software\Microsoft\Windows\CurrentVersion\Uninstall\Fossil" - DeleteRegKey HKLM SOFTWARE\Fossil - ; remove files - Delete $INSTDIR\fossil.exe - ; MUST REMOVE UNINSTALLER, too - Delete $INSTDIR\uninstall.exe - ; remove shortcuts, if any. - RMDir "$SMPROGRAMS\Fossil" - RMDir "$INSTDIR" -SectionEnd - -; eof DELETED kktodo.wiki Index: kktodo.wiki ================================================================== --- kktodo.wiki +++ /dev/null @@ -1,69 +0,0 @@ -

kkinnell

- -.plan -- Fossil, the DG Bwahahahaha! The cover art could be an homo erectus skull lying on some COBOL code... - - 1. Command line interface reference docs -
    -
  • Finish initial pages.
  • -
  • Start on tech-spec (serious, not "chatty") reference pages.
  • -
  • Edit, edit, edit.
  • -
- - 2. Support docs -
    -
  • Basic explanation of Distributed SCM. -
  • Tutorial -
      -
    • Silly source. Start with existing dir struct.
    • -
    • Repository. Creatiing and populating.
    • -
    • Where? Local, Intranet, Internet.
    • -
    • Who? Project size defined by size of code base versus - number of developers. Size matters.
    • -
    • How? -
        -
      • Open, close, commit, checkout, update, merge.
      • -
      -
    • -
    • Hmmm. Experimenting.
    • -
        -
      • The road less travelled, or where'd that - fork come from?
      • -
      -
    • Oops! Going back in time.
    • -
        -
      • Versions
      • -
          -
        • What is a version?
        • -
        • Is it a "version" or a "tag"?
        • -
        • DSCM redux: Revisionist versioning.
        • -
        -
      -
    -
  • -
  • Basic explanation of merge. -
      -
    1. Leaves, branches and baselines: We want a shrubbery!
    2. -
    3. update merges vs. merge merges. - All merges are equal, but some are more equal than others.
    4. -
    -
  • -
- - 3. Configuration - - 42. General -
    -
  • Co-ordinate style and tone with drh, other devs. (documentation - standard? yuck.)
  • -
- - * Tips & tricks. - - * Fossil and Sqlite -
    -
  • Get a word in for Mrinal Kant's - SQLite Manager - XUL app. Great tool.
  • -
- - * Th (code groveling [and code groveling {and code groveling ... } ... ] ... ) DELETED rse-notes.txt Index: rse-notes.txt ================================================================== --- rse-notes.txt +++ /dev/null @@ -1,198 +0,0 @@ -From: "Ralf S. Engelschall" -Date: October 18, 2008 1:40:53 PM EDT -To: drh@hwaci.com -Subject: Some Fossil Feedback - -I've today looked at your Fossil project (latest version from your -repository). That's a _really_ interesting and very promising DVCS. -While I looked around and tested it, I stumbled over some issues I want -to share with you: - -o No tarball - - You currently still do not provide any tarballs of the Fossil sources. - Sure, you are still hacking wild on the code and one can on-the-fly - fetch a ZIP archive, but that's a manual process. For packagers (like - me in the OpenPKG project) this is very nasty and just means that - Fossil will usually still not be packaged. As a result it will be not - spreaded as much as possible and this way you still do not get as much - as possible feedback. Hence, I recommend that you let daily snapshot - tarballs (or ZIP files) be rolled. This would be great. - -o UUID - - Under http://www.fossil-scm.org/index.html/doc/tip/www/concepts.wiki - you describe the concepts and you clearly name the artifact ID a - "UUID" and even say that this is a "Universally Unique Identifier". - Unfortunately, a UUID is a 128-bit entity standardized by the ISO - (under ISO/IEC 11578:1996) and IETF (under RFC-4122) and hence it is - *VERY MUCH* confusing and unclean that your 160-bit SHA1-based ID is - called "UUID" in Fossil. - - I *STRONGLY* recommend you to either use real UUIDs (128-bit - entities, where a UUID of version 5 even is SHA1-based!) or you name - your 160-bit SHA1 entities differently (perhaps AID for "Artifact - Identifier"?). - -o "fossil cgi ADDED skins/bootstrap/header.txt Index: skins/bootstrap/header.txt ================================================================== --- /dev/null +++ skins/bootstrap/header.txt @@ -0,0 +1,73 @@ + + + + + $<project_name>: $<title> + + + + + + + +
+ +
+ + html "
" + html "" + ADDED skins/darkmode/css.txt Index: skins/darkmode/css.txt ================================================================== --- /dev/null +++ skins/darkmode/css.txt @@ -0,0 +1,564 @@ +/* General settings for the entire page */ +body { + margin: 0ex 1ex; + padding: 0; + background-color: #1f1f1f; + color: #ffffffe0; + font-family: sans-serif; +} + +/* The page title centered at the top of each page */ +div.title { + display: table-cell; + font-size: 2em; + font-weight: bold; + text-align: center; + vertical-align: bottom; + width: 100%; +} + +/* The login status message in the top right-hand corner */ +div.status { + display: table-cell; + text-align: right; + vertical-align: bottom; + color: #ddddddc9; + font-size: 0.8em; + font-weight: bold; + white-space: nowrap; +} +/* The leftoftitle is a
to the left of the title
+** that contains the same text as the status div. But we want +** the area to show as blank. The purpose is to cause the +** title to be exactly centered. */ +div.leftoftitle { + visibility: hidden; +} + +/* The header across the top of the page */ +div.header { + display: table; + width: 100%; +} + +/* The main menu bar that appears at the top of the page beneath +** the header */ +div.mainmenu { + padding: 0.25em 0.5em; + font-size: 0.9em; + font-weight: bold; + text-align: center; + border-top-left-radius: 0.5em; + border-top-right-radius: 0.5em; + border-bottom: 1px dotted rgba(200,200,200,0.3); + z-index: 21; /* just above hbdrop */ +} +div#hbdrop { + background-color: #1f1f1f; + border: 2px solid #303536; + border-radius: 0 0 0.5em 0.5em; + display: none; + left: 2em; + width: calc(100% - 4em); + position: absolute; + z-index: 20; /* just below mainmenu, but above timeline bubbles */ +} + +div.mainmenu, div.submenu, div.sectionmenu { + color: #ffffffcc; + background-color: #303536/*#0000ff60*/; +} +/* The submenu bar that *sometimes* appears below the main menu */ +div.submenu, div.sectionmenu { + padding: 0.15em 0.5em 0.15em 0; + font-size: 0.9em; + text-align: center; + border-bottom-left-radius: 0.5em; + border-bottom-right-radius: 0.5em; +} +a, a:visited { + color: rgba(127, 201, 255, 0.9); + display: inline; + text-decoration: none; +} +a:visited {opacity: 0.8} +div.mainmenu a, div.submenu a, +div.sectionmenu>a.button, div.submenu label, +div.footer a { + padding: 0.15em 0.5em; +} +div.mainmenu a.active { + border-bottom: 1px solid #FF4500f0; +} +a:hover, +a:visited:hover { + background-color: #FF4500f0; + color: rgba(24,24,24,0.8); + border-radius: 0.1em; +} +.fileage tr:hover, +div.filetreeline:hover { + background-color: #333; +} +.button, +button { + color: #aaa; + background-color: #444; + border-radius: 5px; + border: 0 +} +.button:hover, +button:hover { + background-color: #FF4500f0; + color: rgba(24,24,24,0.8); + outline: 0 +} +input[type=button], +input[type=reset], +input[type=submit] { + color: #ddd; + background-color: #446979; + border: 0; + border-radius: 5px +} +input[type=button]:hover, +input[type=reset]:hover, +input[type=submit]:hover { + background-color: #FF4500f0; + color: rgba(24,24,24,0.8); + outline: 0 +} +.button:focus, +button:focus, +input[type=button]:focus, +input[type=reset]:focus, +input[type=submit]:focus { + color: #333; + border-color: #888; + outline: 0 +} + +/* All page content from the bottom of the menu or submenu down to +** the footer */ +div.content { + padding: 0ex 1ex 1ex 1ex; +} + +/* Some pages have section dividers */ +div.section { + margin-bottom: 0; + margin-top: 1em; + padding: 0.1em; + font-size: 1.2em; + font-weight: bold; + background-color: #303536/*#0000ff60*/; + white-space: nowrap; + border-top-left-radius: 0.5em; + border-top-right-radius: 0.5em; + border-bottom: 1px dotted rgba(200,200,200,0.3); +} + +/* The "Date" that occurs on the left hand side of timelines */ +div.divider { + background: #303536; + border: 1px #558195 solid; + font-size: 1em; font-weight: normal; + padding: .25em; + margin: .2em 0 .2em 0; + float: left; + clear: left; + white-space: nowrap; +} + +/* The footer at the very bottom of the page */ +div.footer { + clear: both; + font-size: 0.8em; + padding: 0.15em 0.5em; + text-align: right; + background-color: #303536/*#0000ff60*/; + border-top: 1px dotted rgba(200,200,200,0.3); + border-bottom-left-radius: 0.5em; + border-bottom-right-radius: 0.5em; +} + +/* Hyperlink colors in the footer */ + +pre { + border-radius: 0.25em; +} +pre > code { + display: block; +} +/* verbatim blocks */ +pre.verbatim { + padding: 0.12em; + white-space: pre-wrap; +} +pre:not(.verbatim) { + margin-left: 1rem; + margin-right: 1rem; + background-color: rgba(200,200,200, 0.1); + padding: 0.5em 1em; +} + +/* The label/value pairs on (for example) the ci page */ +table.label-value th { + vertical-align: top; + text-align: right; + padding: 0.2ex 2ex; +} + +h1 {margin: 0.6em 0} +h2 {margin: 0.5em 0} +h3 {margin: 0.5em 0} +h4 {margin: 0.5em 0} +h5 {margin: 0.5em 0} + + +/********** +td.timelineTime, +tr.timelineBottom td { + border-bottom: 0 +} +table.timelineTable { + border-spacing: 0.3em 0.3em; +} +table.timelineTable tr td { + padding: 0.5em 1em; +} +.timelineModernCell[id], +.timelineColumnarCell[id], +.timelineDetailCell[id] { + background-color: #ffffff40; +} +table.timelineTable tr td:nth-of-type(2) { + background-color: #ffffffc0; +} +div.tl-canvas { +} +*/ + +.fossil-tooltip, +.fossil-toast-message { + background-color: rgba(251, 106, 0, 1); + border-color: rgba(127, 201, 255, 0.9); + color: black; +} + +/************************************************************************ +timeline... +************************************************************************/ +table.timelineTable tr:not(.timelineDateRow){ + background-color: #ffffff17; +} +table.timelineTable tr:not(.timelineDateRow):hover{ + background-color: #FF450080; +} +table.timelineTable tr td:first-of-type { + vertical-align: middle; + padding: 0.2em 0.5em; +} +div.timelineDate { + font-weight: 700; + white-space: nowrap; + border-radius: 0.2em; +} +td.timelineTime { + text-align: right; + white-space: nowrap; +} +td.timelineGraph { + width: 20px; + text-align: left; + border-bottom: 0 +} +a.timelineHistLink { + /*text-transform: lowercase*/ +} +span.timelineComment { + padding: 0 5px +} +.report th, +span.timelineEllipsis { + cursor: pointer +} +table.timelineTable { + border-spacing: 0 0.2em; +} +.timelineModernCell, .timelineColumnarCell, +.timelineDetailCell, .timelineCompactCell, +.timelineVerboseCell { + vertical-align: top; + text-align: left; + padding: .75em; + border-radius: 0.25em; + background: inherit /*#000*/; +} +.timelineSelected > .timelineColumnarCell, +.timelineSelected > .timelineCompactCell, +.timelineSelected > .timelineDetailCell, +.timelineSelected > .timelineModernCell, +.timelineSelected > .timelineVerboseCell { + padding: .75em; + border-radius: 0.2em; + border: 1px solid #ff8000; + vertical-align: top; + text-align: left; + background: #442800 +} + +/* Timeline has a blank line at the bottom. Apparently it's to provide the + graph with a good starting place. Hiding it causes a slight graph + unsightliness, but we can change its bg color. */ +table.timelineTable tr.timelineBottom, +table.timelineTable tr.timelineBottom:hover { + background: inherit; +} +span.timelineSelected { + border-radius: 0.2em; + border: 1px solid #ff8000; + /*vertical-align: top; + text-align: left;*/ + background: #442800 +} +.timelineSelected { + background-color: #ffffff40; +} +.timelineSecondary {} +.timelineSecondary > .timelineColumnarCell, +.timelineSecondary > .timelineCompactCell, +.timelineSecondary > .timelineDetailCell, +.timelineSecondary > .timelineModernCell, +.timelineSecondary > .timelineVerboseCell { + padding: .75em; + border-radius: 5px; + border: solid #0080ff; + /*vertical-align: top; + text-align: left;*/ + background: #002844 +} +span.timelineSecondary { + border-radius: 5px; + border: solid #0080ff; + /*vertical-align: top; + text-align: left;*/ + background: #002844 +} +.timelineCurrent > .timelineColumnarCell, +.timelineCurrent > .timelineCompactCell, +.timelineCurrent > .timelineDetailCell, +.timelineCurrent > .timelineModernCell, +.timelineCurrent > .timelineVerboseCell { + /*vertical-align: top; + text-align: left;*/ + padding: .75em; + border-radius: 5px; + border: dashed #ff8000 +} +.timelineModernCell[id], .timelineColumnarCell[id], .timelineDetailCell[id] { + background-color: inherit;/*#000*/ +} +.tl-canvas { + margin: 0 6px 0 10px +} +.tl-rail { + width: 18px +} +.tl-mergeoffset { + width: 2px +} +.tl-nodemark { + margin-top: .8em +} +.tl-node { + width: 10px; + height: 10px; + border: 2px solid #bbb; + background: #111; + cursor: pointer +} +.tl-node.leaf:after { + content: ''; + position: absolute; + top: 3px; + left: 3px; + width: 4px; + height: 4px; + background: #bbb +} +.tl-node.sel:after { + content: ''; + position: absolute; + top: 1px; + left: 1px; + width: 8px; + height: 8px; + background: #ff8000 +} +.tl-arrow { + width: 0; + height: 0; + transform: scale(.999); + border: 0 solid transparent +} +.tl-arrow.u { + margin-top: -1px; + border-width: 0 3px; + border-bottom: 7px solid +} +.tl-arrow.u.sm { + border-bottom: 5px solid #bbb +} +.tl-line { + background: #bbb; + width: 2px +} +.tl-arrow.merge { + height: 1px; + border-width: 2px 0 +} +.tl-arrow.merge.l { + border-right: 3px solid #bbb +} +.tl-arrow.merge.r { + border-left: 3px solid #bbb +} +.tl-line.merge { + width: 1px +} +.tl-arrow.cherrypick { + height: 1px; + border-width: 2px 0; +} +.tl-arrow.cherrypick.l { + border-right: 3px solid #bbb; +} +.tl-arrow.cherrypick.r { + border-left: 3px solid #bbb; +} +.tl-line.cherrypick.h { + width: 0px; + border-top: 1px dashed #bbb; + border-left: 0px dashed #bbb; + background: rgba(255,255,255,0); +} +.tl-line.cherrypick.v { + width: 0px; + border-top: 0px dashed #bbb; + border-left: 1px dashed #bbb; + background: rgba(255,255,255,0); +} + +/************************************************************************ +diffs... +************************************************************************/ +span.diffchng { + background-color: #8080e8; + color: #000 +} +span.diffadd { + background-color: #559855; + color: #000 +} +span.diffrm { + background-color: #c55; + color: #000 +} +div.diffmkrcol { + background: #111 +} +span.diffhr { + color: #555 +} +span.diffln { + color: #666 +} + +/************************************************************************ +************************************************************************/ +body.wikiedit #fossil-status-bar, +body.fileedit #fossil-status-bar{ + border-radius: 0.25em 0.25em 0 0; +} +.tab-container > .tabs { + border-radius: 0.25em; +} + +blockquote.file-content { + margin: 0; +} +blockquote.file-content > pre { + padding: 0; +} +blockquote.file-content > pre > code { + padding: 0 0.5em; +} +svg.pikchr { + /* swap the pikchr svg colors around so they're readable in + this dark theme. 2020-02: changes in fossil have made this + obsolete. */ + /*filter: invert(1) hue-rotate(180deg);*/ +} +span.snippet>mark { + color: white; + font-weight: bold; +} +button, +input, +optgroup, +select, +textarea { + background-color: inherit; + color: inherit; + font: inherit; + margin: 0 +} +input, textarea, select { + border: 1px solid rgba(127, 201, 255, 0.9); + padding: 1px; +} +.capsumOff { + background-color: #222; +} +.capsumRead { + background-color: #262; +} +.capsumWrite { + background-color: #662; +} + +body.forum div.forumSel { + background: inherit; + border-left-width: 0.5em; + border-left-style: double; +} + +body.forum .debug { + background-color: #FF4500f0; + color: rgba(24,24,24,0.8); +} + +body.forum .fileage tr:hover { + background-color: #333; + color: rgba(24,24,24,0.8); +} + +body.forum .forumPostBody > div blockquote { + border: 1px inset; + padding: 0 0.5em; +} + +pre.udiff { + overflow-x: auto; +} + +body.report table.report tr td { color: black } +body.report table.report a { color: blue } +body.tkt td.tktDspValue { color: black } +body.tkt td.tktDspValue a { color: blue } + +body.branch .brlist > table > tbody > tr:hover:not(.selected), +body.branch .brlist > table > tbody > tr.selected { + background-color: #442800; +} ADDED skins/darkmode/details.txt Index: skins/darkmode/details.txt ================================================================== --- /dev/null +++ skins/darkmode/details.txt @@ -0,0 +1,4 @@ +timeline-arrowheads: 0 +timeline-circle-nodes: 1 +timeline-color-graph-lines: 1 +white-foreground: 1 ADDED skins/darkmode/footer.txt Index: skins/darkmode/footer.txt ================================================================== --- /dev/null +++ skins/darkmode/footer.txt @@ -0,0 +1,8 @@ + ADDED skins/darkmode/header.txt Index: skins/darkmode/header.txt ================================================================== --- /dev/null +++ skins/darkmode/header.txt @@ -0,0 +1,30 @@ +
+
+ if {[info exists login]} { + set logintext "$login\n" + } else { + set logintext "Login\n" + } + html $logintext +
+
$</div> + <div class="status"><nobr><th1> + html $logintext + </th1></nobr></div> +</div> +<div class="mainmenu"> +<th1> +html "<a id='hbbtn' href='$home/sitemap' aria-label='Site Map'>☰</a>" +builtin_request_js hbmenu.js +foreach {name url expr class} $mainmenu { + if {![capexpr $expr]} continue + if {[string match /* $url]} { + if {[string match $url\[/?#\]* /$current_page/]} { + set class "active $class" + } + set url $home$url + } + html "<a href='$url' class='$class'>$name</a>\n" +} +</th1></div> +<div id='hbdrop'></div> ADDED skins/default/README.md Index: skins/default/README.md ================================================================== --- /dev/null +++ skins/default/README.md @@ -0,0 +1,5 @@ +This skin was contributed by Étienne Deparis. + +On 2015-03-14 this skin was promoted from an option to the default, which +involved moving it from its original home in the skins/etienne1 directory +into skins/default. ADDED skins/default/css.txt Index: skins/default/css.txt ================================================================== --- /dev/null +++ skins/default/css.txt @@ -0,0 +1,295 @@ +/* Overall page style */ + +body { + margin: 0 auto; + background-color: white; + font-family: sans-serif; + font-size: 14pt; + -moz-text-size-adjust: none; + -mx-text-size-adjust: none; + -webkit-text-size-adjust: none; +} + +a { + color: #4183C4; + text-decoration: none; +} +a:hover { + color: #4183C4; + text-decoration: underline; +} + + +/* Page title, above menu bars */ + +.title { + color: #4183C4; + float: left; +} +.title h1 { + display: inline; +} +.title h1:after { + content: " / "; + color: #777; + font-weight: normal; +} +.status { + float: right; + font-size: 0.7em; +} + + +/* Main menu and optional sub-menu */ + +.mainmenu { + font-size: 0.8em; + clear: both; + background: #eaeaea linear-gradient(#fafafa, #eaeaea) repeat-x; + border: 1px solid #eaeaea; + border-radius: 5px; + overflow-x: auto; + overflow-y: hidden; + white-space: nowrap; + z-index: 21; /* just above hbdrop */ +} +.mainmenu a { + text-decoration: none; + color: #777; + border-right: 1px solid #eaeaea; +} +.mainmenu a.active, +.mainmenu a:hover { + color: #000; + border-bottom: 2px solid #D26911; +} +div#hbdrop { + background-color: white; + border: 1px solid black; + border-top: white; + border-radius: 0 0 0.5em 0.5em; + display: none; + font-size: 80%; + left: 2em; + width: 90%; + padding-right: 1em; + position: absolute; + z-index: 20; /* just below mainmenu, but above timeline bubbles */ +} + +.submenu { + font-size: .7em; + padding: 10px; + border-bottom: 1px solid #ccc; +} +.submenu a, .submenu label { + padding: 10px 11px; + text-decoration: none; + color: #777; +} +.submenu label { + white-space: nowrap; +} +.submenu a:hover, .submenu label:hover { + padding: 6px 10px; + border: 1px solid #ccc; + border-radius: 5px; + color: #000; +} +span.submenuctrl, span.submenuctrl input, select.submenuctrl { + color: #777; +} +span.submenuctrl { + white-space: nowrap; +} + + +/* Main document area; elements common to most pages. */ + +.content { + padding-top: 10px; + font-size: 0.8em; + color: #444; +} +.content blockquote { + padding: 0 15px; +} +.content h1 { + font-size: 1.25em; +} +.content h2 { + font-size: 1.15em; +} +.content h3 { + font-size: 1.05em; +} + +.section { + font-size: 1em; + font-weight: bold; + background-color: #f5f5f5; + border: 1px solid #d8d8d8; + border-radius: 3px 3px 0 0; + padding: 9px 10px 10px; + margin: 10px 0; +} +.sectionmenu { + border: 1px solid #d8d8d8; + border-radius: 0 0 3px 3px; + border-top: 0; + margin-top: -10px; + margin-bottom: 10px; + padding: 10px; +} +.sectionmenu a { + display: inline-block; + margin-right: 1em; +} + +hr { + color: #eee; +} + + +/* Page footer */ + +.footer { + border-top: 1px solid #ccc; + padding: 10px; + font-size: 0.7em; + margin-top: 10px; + color: #ccc; +} + + +/* Exceptions for /info diff views */ + +.udiff, .sbsdiff { + font-size: .85em !important; + overflow: auto; + border: 1px solid #ccc; + border-radius: 5px; +} + + +/* Forum */ + +.forum a:visited { + color: #6A7F94; +} + +.forum blockquote { + background-color: rgba(65, 131, 196, 0.1); + border-left: 3px solid #254769; + padding: .1em 1em; +} + + +/* Tickets */ + +table.report { + cursor: auto; + border-radius: 5px; + border: 1px solid #ccc; + margin: 1em 0; +} +.report td, .report th { + border: 0; + font-size: .8em; + padding: 10px; +} +.report td:first-child { + border-top-left-radius: 5px; +} +.report tbody tr:last-child td:first-child { + border-bottom-left-radius: 5px; +} +.report td:last-child { + border-top-right-radius: 5px; +} +.report tbody tr:last-child { + border-bottom-left-radius: 5px; + border-bottom-right-radius: 5px; +} +.report tbody tr:last-child td:last-child { + border-bottom-right-radius: 5px; +} +.report th { + cursor: pointer; +} +.report thead+tbody tr:hover { + background-color: #f5f9fc !important; +} + +td.tktDspLabel { + width: 70px; + text-align: right; + overflow: hidden; +} +td.tktDspValue { + text-align: left; + vertical-align: top; + background-color: #f8f8f8; + border: 1px solid #ccc; +} +td.tktDspValue pre { + white-space: pre-wrap; +} + + +/* Timeline */ + +span.timelineDetail { + font-size: 90%; +} +div.timelineDate { + font-weight: bold; + white-space: nowrap; +} + + +/* Miscellaneous UI elements */ + +.fossil-tooltip.help-buttonlet-content { + background-color: lightyellow; +} + + +/* Exceptions for specific screen sizes */ + +@media screen and (max-width: 600px) { + /* Spacing for mobile */ + body { + padding-left: 4px; + padding-right: 4px; + } + .title { + padding-top: 0px; + padding-bottom: 0px; + } + .status {padding-top: 0px;} + .mainmenu a { + padding: 8px 10px; + } + .mainmenu { + padding: 10px; + } +} +@media screen and (min-width: 600px) { + /* Spacing for desktop */ + body { + padding-left: 20px; + padding-right: 20px; + } + .title { + padding-top: 10px; + padding-bottom: 10px; + } + .status {padding-top: 30px;} + .mainmenu a { + padding: 8px 20px; + } + .mainmenu { + padding: 10px; + } +} ADDED skins/default/details.txt Index: skins/default/details.txt ================================================================== --- /dev/null +++ skins/default/details.txt @@ -0,0 +1,4 @@ +timeline-arrowheads: 1 +timeline-circle-nodes: 1 +timeline-color-graph-lines: 1 +white-foreground: 0 ADDED skins/default/footer.txt Index: skins/default/footer.txt ================================================================== --- /dev/null +++ skins/default/footer.txt @@ -0,0 +1,5 @@ +<div class="footer"> +This page was generated in about +<th1>puts [expr {([utime]+[stime]+1000)/1000*0.001}]</th1>s by +Fossil $release_version $manifest_version $manifest_date +</div> ADDED skins/default/header.txt Index: skins/default/header.txt ================================================================== --- /dev/null +++ skins/default/header.txt @@ -0,0 +1,26 @@ +<div class="header"> + <div class="title"><h1>$<project_name></h1>$<title></div> + <div class="status"><th1> + if {[info exists login]} { + html "<a href='$home/login'>$login</a>\n" + } else { + html "<a href='$home/login'>Login</a>\n" + } + </th1></div> +</div> +<div class="mainmenu"> +<th1> +html "<a id='hbbtn' href='$home/sitemap' aria-label='Site Map'>☰</a>" +builtin_request_js hbmenu.js +foreach {name url expr class} $mainmenu { + if {![capexpr $expr]} continue + if {[string match /* $url]} { + if {[string match $url\[/?#\]* /$current_page/]} { + set class "active $class" + } + set url $home$url + } + html "<a href='$url' class='$class'>$name</a>\n" +} +</th1></div> +<div id='hbdrop'></div> ADDED skins/eagle/README.md Index: skins/eagle/README.md ================================================================== --- /dev/null +++ skins/eagle/README.md @@ -0,0 +1,4 @@ +For this skin to look exactly as it was intended to, the **white-foreground** +setting should be enabled. + +This skin was contributed by Joe Mistachkin. ADDED skins/eagle/css.txt Index: skins/eagle/css.txt ================================================================== --- /dev/null +++ skins/eagle/css.txt @@ -0,0 +1,442 @@ +/* General settings for the entire page */ +body { + margin: 0ex 1ex; + padding: 0px; + background-color: #485D7B; + font-family: sans-serif; + color: white; +} + +/* The project logo in the upper left-hand corner of each page */ +div.logo { + display: table-cell; + text-align: center; + vertical-align: bottom; + font-weight: bold; + color: white; + min-width: 50px; + padding: 5 0 5 0em; + white-space: nowrap; +} + +/* The page title centered at the top of each page */ +div.title { + display: table-cell; + font-size: 2em; + font-weight: bold; + text-align: left; + padding: 0 0 0 1em; + color: white; + vertical-align: bottom; + width: 100%; +} + +/* The login status message in the top right-hand corner */ +div.status { + display: table-cell; + text-align: right; + vertical-align: bottom; + color: white; + font-size: 0.8em; + font-weight: bold; + white-space: nowrap; +} + +/* The header across the top of the page */ +div.header { + display: table; + width: 100%; +} + +/* The main menu bar that appears at the top of the page beneath +** the header */ +div.mainmenu { + padding: 5px 10px 5px 10px; + font-size: 0.9em; + font-weight: bold; + text-align: center; + letter-spacing: 1px; + background-color: #76869D; + border-top-left-radius: 8px; + border-top-right-radius: 8px; + color: white; +} + +div#hbdrop { + background-color: #485D7B; + border-radius: 0 0 15px 15px; + border-left: 0.5em solid #76869d; + border-bottom: 1.2em solid #76869d; + display: none; + width: 98%; + position: absolute; + z-index: 20; +} + + +/* The submenu bar that *sometimes* appears below the main menu */ +div.submenu, div.sectionmenu { + padding: 3px 10px 3px 0px; + font-size: 0.9em; + font-weight: bold; + text-align: center; + background-color: #485D7B; + color: white; +} +div.mainmenu a, div.mainmenu a:visited, div.submenu a, div.submenu a:visited, +div.sectionmenu>a.button:link, div.sectionmenu>a.button:visited, +div.submenu label { + padding: 3px 10px 3px 10px; + color: white; + text-decoration: none; +} +div.mainmenu a:hover, div.submenu a:hover, div.sectionmenu>a.button:hover, +div.submenu label:hover { + text-decoration: underline; +} + +/* All page content from the bottom of the menu or submenu down to +** the footer */ +div.content { + padding: 0ex 1ex 0ex 2ex; +} + +/* Some pages have section dividers */ +div.section { + margin-bottom: 0px; + margin-top: 1em; + padding: 1px 1px 1px 1px; + font-size: 1.2em; + font-weight: bold; + background-color: #485D7B; + color: white; + white-space: nowrap; +} + +/* The "Date" that occurs on the left hand side of timelines */ +div.divider { + background: #9DB0CC; + color: white; + border: 2px white solid; + font-size: 1em; font-weight: normal; + padding: .25em; + margin: .2em 0 .2em 0; + float: left; + clear: left; + white-space: nowrap; +} + +/* The footer at the very bottom of the page */ +div.footer { + clear: both; + font-size: 0.8em; + margin-top: 12px; + padding: 5px 10px 5px 10px; + text-align: right; + background-color: #485D7B; + border-bottom-left-radius: 8px; + border-bottom-right-radius: 8px; + color: white; +} + +/* Hyperlink colors in the footer */ +a { color: white; } +a:link { color: white; } +a:visited { color: white; } +a:hover { color: #9DB0CC; } + +/* verbatim blocks */ +pre.verbatim { + background-color: #485D7B; + color: white; + padding: 0.5em; + white-space: pre-wrap; +} + +/* The label/value pairs on (for example) the ci page */ +table.label-value th { + vertical-align: top; + text-align: right; + padding: 0.2ex 2ex; +} + +/* The nomenclature sidebox for branches,.. */ +div.sidebox { + float: right; + background-color: #485D7B; + border-width: medium; + border-style: double; + margin: 10px; +} + +/* the format for the timeline data table */ +table.timelineTable { + cellspacing: 0; + border: 0; + cellpadding: 0; + font-family: "courier new"; + border-spacing: 0px 2px; + // border-collapse: collapse; +} + +.timelineSelected { + background-color: #7EA2D9; +} +.timelineSecondary { + background-color: #7EA27E; +} + +/* commit node */ +.tl-node { + width: 10px; + height: 10px; + border: 1px solid #fff; + background: #485D7B; + cursor: pointer; +} + +/* leaf commit marker */ +.tl-node.leaf:after { + content: ''; + position: absolute; + top: 3px; + left: 3px; + width: 4px; + height: 4px; + background: #fff; +} + +/* up arrow */ +.tl-arrow.u { + margin-top: -1px; + border-width: 0 3px; + border-bottom: 7px solid #fff; +} + +/* small up arrow */ +.tl-arrow.u.sm { + border-bottom: 5px solid #fff; +} + +/* line */ +.tl-line { + background: #fff; + width: 2px; +} + +/* left merge arrow */ +.tl-arrow.merge.l { + border-right: 3px solid #fff; +} + +/* right merge arrow */ +.tl-arrow.merge.r { + border-left: 3px solid #fff; +} + +.tl-arrow.cherrypick { + height: 1px; + border-width: 2px 0; +} +.tl-arrow.cherrypick.l { + border-right: 3px solid #fff; +} +.tl-arrow.cherrypick.r { + border-left: 3px solid #fff; +} +.tl-line.cherrypick.h { + width: 0px; + border-top: 1px dashed #fff; + border-left: 0px dashed #fff; + background: rgba(255,255,255,0); +} +.tl-line.cherrypick.v { + width: 0px; + border-top: 0px dashed #fff; + border-left: 1px dashed #fff; + background: rgba(255,255,255,0); +} + + +/* Side-by-side diff */ +table.sbsdiff { + background-color: #485D7B; + font-family: fixed, Dejavu Sans Mono, Monaco, Lucida Console, monospace; + font-size: 8pt; + border-collapse:collapse; + white-space: pre; + width: 98%; + border: 1px #000 dashed; + margin-left: auto; + margin-right: auto; +} + +/* format for the layout table, used for the captcha display */ +table.captcha { + margin: auto; + padding: 10px; + border-width: 4px; + border-style: double; + border-color: white; +} + +/* format for the user list table on the user setup page */ +table.usetupUserList { + outline-style: double; + outline-width: 1px; + outline-color: white; + padding: 10px; +} + +/* color for capabilities, inherited by reader */ +span.ueditInheritReader { + color: white; +} + +/* format for values on ticket display page */ +td.tktDspValue { + text-align: left; + vertical-align: top; + background-color: #485D7B; +} + +/* Ticket display on timelines */ +td.tktTlOpen { + color: #ffc0c0; +} +td.tktTlClose { + color: #c0c0c0; +} + +/* format for example table cells on the report edit page */ +td.rpteditex { + border-width: thin; + border-color: white; + border-style: solid; +} + +/* List of files in a timeline */ +ul.filelist { + margin-top: 3px; + line-height: 100%; +} + +/* side-by-side diff display */ +div.sbsdiff { + font-family: monospace; + font-size: smaller; + white-space: pre; +} + +/* context diff display */ +div.udiff { + font-family: monospace; + white-space: pre; +} + +/* changes in a diff */ +span.diffchng { + background-color: rgb(170, 170, 140); +} + +/* added code in a diff */ +span.diffadd { + background-color: rgb(100, 200, 100); +} + +/* deleted in a diff */ +span.diffrm { + background-color: rgb(230, 110, 110); +} + +/* suppressed lines in a diff */ +span.diffhr { + display: inline-block; + margin: .5em 0 1em; + color: rgb(150, 150, 140); +} + +/* line numbers in a diff */ +span.diffln { + color: white; +} + +.fileage tr:hover { + background-color: #7EA2D9; +} + +span.modpending { + color: #c0c0c0; + font-style: italic; +} +span.forum_author { + color: white; + font-size: 75%; +} +span.forum_age { + color: white; + font-size: 85%; +} +span.forum_npost { + color: white; + font-size: 75%; +} +.debug { + background-color: #808080; + border: 2px solid white; +} +div.forumEdit { + border: 1px solid white; +} +div.forumTimeline { + border: 1px solid white; +} +div.forumTime { + border: 1px solid white; +} +div.forumSel { + background-color: #808080; +} +div.forumObs { + color: white; +} + +.fileage td { + font-family: "courier new"; +} + +div.filetreeline:hover { + background-color: #7EA2D9; +} + +table.numbered-lines td.line-numbers span.selected-line { + background-color: #7EA2D9; +} + +.statistics-report-graph-line { + background-color: #7EA2D9; +} + +.timelineModernCell[id], .timelineColumnarCell[id], .timelineDetailCell[id] { + background-color: #455978; +} + +div.difflncol { + padding-right: 1em; + text-align: right; + color: white; +} +.capsumOff { + background-color: #bbbbbb; +} +.capsumRead { + background-color: #006d00; +} +.capsumWrite { + background-color: #e5e500; +} + +body.branch .brlist > table > tbody > tr:hover:not(.selected), +body.branch .brlist > table > tbody > tr.selected { + background-color: #7EA2D9; +} ADDED skins/eagle/details.txt Index: skins/eagle/details.txt ================================================================== --- /dev/null +++ skins/eagle/details.txt @@ -0,0 +1,5 @@ +timeline-arrowheads: 1 +timeline-circle-nodes: 0 +timeline-color-graph-lines: 0 +white-foreground: 1 +pikchr-background: 0x485d7b ADDED skins/eagle/footer.txt Index: skins/eagle/footer.txt ================================================================== --- /dev/null +++ skins/eagle/footer.txt @@ -0,0 +1,24 @@ +<div class="footer"> + <th1> + proc getTclVersion {} { + if {[catch {tclEval info patchlevel} tclVersion] == 0} { + return "<a href=\"https://www.tcl.tk/\">Tcl</a> version $tclVersion" + } + return "" + } + proc getVersion { version } { + set length [string length $version] + return [string range $version 1 [expr {$length - 2}]] + } + set version [getVersion $manifest_version] + set tclVersion [getTclVersion] + set fossilUrl https://www.fossil-scm.org + set fossilDate [string range $manifest_date 0 9]T[string range $manifest_date 11 end] + </th1> + This page was generated in about + <th1>puts [expr {([utime]+[stime]+1000)/1000*0.001}]</th1>s by + <a href="$fossilUrl/">Fossil</a> + version $release_version $tclVersion + <a href="$fossilUrl/index.html/info/$version">$manifest_version</a> + <a href="$fossilUrl/index.html/timeline?c=$fossilDate&y=ci">$manifest_date</a> +</div> ADDED skins/eagle/header.txt Index: skins/eagle/header.txt ================================================================== --- /dev/null +++ skins/eagle/header.txt @@ -0,0 +1,108 @@ +<div class="header"> + <div class="logo"> + <th1> + ## + ## NOTE: The purpose of this procedure is to take the base URL of the + ## Fossil project and return the root of the entire web site using + ## the same URI scheme as the base URL (e.g. http or https). + ## + proc getLogoUrl { baseurl } { + set idx(first) [string first // $baseurl] + if {$idx(first) != -1} { + ## + ## NOTE: Skip second slash. + ## + set idx(first+1) [expr {$idx(first) + 2}] + ## + ## NOTE: (part 1) The [string first] command does NOT actually + ## support the optional startIndex argument as specified + ## in the TH1 support manual; therefore, we fake it by + ## using the [string range] command and then adding the + ## necessary offset to the resulting index manually + ## (below). In Tcl, we could use the following instead: + ## + ## set idx(next) [string first / $baseurl $idx(first+1)] + ## + set idx(nextRange) [string range $baseurl $idx(first+1) end] + set idx(next) [string first / $idx(nextRange)] + if {$idx(next) != -1} { + ## + ## NOTE: (part 2) Add the necessary offset to the result of + ## the search for the next slash (i.e. the one after + ## the initial search for the two slashes). + ## + set idx(next) [expr {$idx(next) + $idx(first+1)}] + ## + ## NOTE: Back up one character from the next slash. + ## + set idx(next-1) [expr {$idx(next) - 1}] + ## + ## NOTE: Extract the URI scheme and host from the base URL. + ## + set scheme [string range $baseurl 0 $idx(first)] + set host [string range $baseurl $idx(first+1) $idx(next-1)] + ## + ## NOTE: Try to stay in SSL mode if we are there now. + ## + if {[string compare $scheme http:/] == 0} { + set scheme http:// + } else { + set scheme https:// + } + set logourl $scheme$host/ + } else { + set logourl $baseurl + } + } else { + set logourl $baseurl + } + return $logourl + } + set logourl [getLogoUrl $baseurl] + </th1> + <a href="$logourl"> + <img src="$logo_image_url" border="0" alt="$project_name"> + </a> + </div> + <div class="title">$<title></div> + <div class="status"><nobr><th1> + if {[info exists login]} { + puts "Logged in as $login" + } else { + puts "Not logged in" + } + </th1></nobr><small><div id="clock"></div></small></div> +</div> +<th1>html "<script nonce='$nonce'>"</th1> +function updateClock(){ + var e = document.getElementById("clock"); + if(e){ + var d = new Date(); + function f(n) { + return n < 10 ? '0' + n : n; + } + e.innerHTML = d.getUTCFullYear()+ '-' + + f(d.getUTCMonth() + 1) + '-' + + f(d.getUTCDate()) + ' ' + + f(d.getUTCHours()) + ':' + + f(d.getUTCMinutes()); + setTimeout(updateClock,(60-d.getUTCSeconds())*1000); + } +} +updateClock(); +</script> +<div class="mainmenu"><th1> +html "<a id='hbbtn' href='$home/sitemap' aria-label='Site Map'>☰</a>\n" +builtin_request_js hbmenu.js +foreach {name url expr class} $mainmenu { + if {![capexpr $expr]} continue + if {[string match /* $url]} {set url $home$url} + html "<a href='$url' class='$class'>$name</a>\n" +} +if {[info exists login]} { + html "<a href='$home/logout' class='desktoponly'>Logout</a>\n" +} else { + html "<a href='$home/login' class='desktoponly'>Login</a>\n" +} +</th1></div> +<div id="hbdrop"></div> ADDED skins/khaki/css.txt Index: skins/khaki/css.txt ================================================================== --- /dev/null +++ skins/khaki/css.txt @@ -0,0 +1,174 @@ +/* General settings for the entire page */ +body { + margin: 0ex 0ex; + padding: 0px; + background-color: #fef3bc; + font-family: sans-serif; + -moz-text-size-adjust: none; + -webkit-text-size-adjust: none; + -mx-text-size-adjust: none; +} + +/* The project logo in the upper left-hand corner of each page */ +div.logo { + display: inline; + text-align: center; + vertical-align: bottom; + font-weight: bold; + font-size: 2.5em; + color: #a09048; + white-space: nowrap; +} + +/* The page title centered at the top of each page */ +div.title { + display: table-cell; + font-size: 2em; + font-weight: bold; + text-align: left; + padding: 0 0 0 5px; + color: #a09048; + vertical-align: bottom; + width: 100%; +} + +/* The login status message in the top right-hand corner */ +div.status { + display: table-cell; + text-align: right; + vertical-align: bottom; + color: #a09048; + padding: 5px 5px 0 0; + font-size: 0.8em; + font-weight: bold; + white-space: nowrap; +} + +/* The header across the top of the page */ +div.header { + display: table; + width: 100%; +} + +/* The main menu bar that appears at the top of the page beneath +** the header */ +div.mainmenu { + padding: 5px 10px 5px 10px; + font-size: 0.9em; + font-weight: bold; + text-align: center; + letter-spacing: 1px; + background-color: #a09048; + color: black; + z-index: 21; /* just above hbdrop */ +} +div#hbdrop { + background-color: #fef3bc; + border: 2px solid #a09048; + border-radius: 0 0 0.5em 0.5em; + display: none; + left: 2em; + width: 90%; + padding-right: 1em; + position: absolute; + z-index: 20; /* just below mainmenu, but above timeline bubbles */ +} + + +/* The submenu bar that *sometimes* appears below the main menu */ +div.submenu, div.sectionmenu { + padding: 3px 10px 3px 0px; + font-size: 0.9em; + text-align: center; + background-color: #c0af58; + color: white; +} +div.mainmenu a, div.mainmenu a:visited, div.submenu a, div.submenu a:visited, +div.sectionmenu>a.button:link, div.sectionmenu>a.button:visited, +div.submenu label { + padding: 3px 10px 3px 10px; + color: white; + text-decoration: none; +} +div.mainmenu a:hover, div.submenu a:hover, div.sectionmenu>a.button:hover, +div.submenu label:hover, div#hbdrop a:hover { + color: #a09048; + background-color: white; +} + +/* All page content from the bottom of the menu or submenu down to +** the footer */ +div.content { + padding: 1ex 5px; +} +div.content a, div#hbdrop a { color: #706532; } +div.content a:link, div#hbdrop a:link { color: #706532; } +div.content a:visited, div#hbdrop a:visited { color: #704032; } +div.content a:hover, div#hbdrop a:hover { + background-color: white; color: #706532; +} +a, a:visited { + text-decoration: none; +} + + +/* Some pages have section dividers */ +div.section { + margin-bottom: 0px; + margin-top: 1em; + padding: 3px 3px 0 3px; + font-size: 1.2em; + font-weight: bold; + background-color: #a09048; + color: white; + white-space: nowrap; +} + +/* The "Date" that occurs on the left hand side of timelines */ +div.divider { + background: #e1d498; + border: 2px #a09048 solid; + font-size: 1em; font-weight: normal; + padding: .25em; + margin: .2em 0 .2em 0; + float: left; + clear: left; + white-space: nowrap; +} + +/* The footer at the very bottom of the page */ +div.footer { + font-size: 0.8em; + margin-top: 12px; + padding: 5px 10px 5px 10px; + text-align: right; + background-color: #a09048; + color: white; +} + +/* Hyperlink colors */ +div.footer a { color: white; } +div.footer a:link { color: white; } +div.footer a:visited { color: white; } +div.footer a:hover { background-color: white; color: #558195; } + +/* <verbatim> blocks */ +pre.verbatim { + background-color: #f5f5f5; + padding: 0.5em; + white-space: pre-wrap; +} + +/* The label/value pairs on (for example) the ci page */ +table.label-value th { + vertical-align: top; + text-align: right; + padding: 0.2ex 2ex; +} + +div.forumPostBody blockquote { + border-width: 1pt; + border-radius: 0.25em; + border-style: solid; + padding: 0 0.5em; +} ADDED skins/khaki/details.txt Index: skins/khaki/details.txt ================================================================== --- /dev/null +++ skins/khaki/details.txt @@ -0,0 +1,4 @@ +timeline-arrowheads: 1 +timeline-circle-nodes: 1 +timeline-color-graph-lines: 1 +white-foreground: 0 ADDED skins/khaki/footer.txt Index: skins/khaki/footer.txt ================================================================== --- /dev/null +++ skins/khaki/footer.txt @@ -0,0 +1,3 @@ +<div class="footer"> +Fossil $release_version $manifest_version $manifest_date +</div> ADDED skins/khaki/header.txt Index: skins/khaki/header.txt ================================================================== --- /dev/null +++ skins/khaki/header.txt @@ -0,0 +1,22 @@ +<div class="header"> + <div class="title">$<title></div> + <div class="status"> + <div class="logo">$<project_name></div><br/> + <th1> + if {[info exists login]} { + puts "Logged in as $login" + } else { + puts "Not logged in" + } + </th1></div> +</div> +<div class="mainmenu"><th1> +html "<a id='hbbtn' href='$home/sitemap' aria-label='Site Map'>☰</a>" +builtin_request_js hbmenu.js +foreach {name url expr class} $mainmenu { + if {![capexpr $expr]} continue + if {[string match /* $url]} {set url $home$url} + html "<a href='$url' class='$class'>$name</a>\n" +} +</th1></div> +<div id='hbdrop'></div> ADDED skins/original/css.txt Index: skins/original/css.txt ================================================================== --- /dev/null +++ skins/original/css.txt @@ -0,0 +1,149 @@ +/* General settings for the entire page */ +body { + margin: 0ex 1ex; + padding: 0px; + background-color: white; + font-family: sans-serif; + -moz-text-size-adjust: none; + -webkit-text-size-adjust: none; + -mx-text-size-adjust: none; +} + +/* The project logo in the upper left-hand corner of each page */ +div.logo { + display: table-cell; + text-align: center; + vertical-align: bottom; + font-weight: bold; + color: #558195; + min-width: 50px; + white-space: nowrap; +} + +/* The page title centered at the top of each page */ +div.title { + display: table-cell; + font-size: 2em; + font-weight: bold; + text-align: center; + padding: 0 0 0 1em; + color: #558195; + vertical-align: bottom; + width: 100%; +} + +/* The login status message in the top right-hand corner */ +div.status { + display: table-cell; + text-align: right; + vertical-align: bottom; + color: #558195; + font-size: 0.8em; + font-weight: bold; + white-space: nowrap; +} + +/* The header across the top of the page */ +div.header { + display: table; + width: 100%; +} + +/* The main menu bar that appears at the top of the page beneath +** the header */ +div.mainmenu { + padding: 5px; + font-size: 0.9em; + font-weight: bold; + text-align: center; + letter-spacing: 1px; + background-color: #558195; + border-top-left-radius: 8px; + border-top-right-radius: 8px; + color: white; +} + +/* The submenu bar that *sometimes* appears below the main menu */ +div.submenu, div.sectionmenu { + padding: 3px 10px 3px 0px; + font-size: 0.9em; + text-align: center; + background-color: #456878; + color: white; +} +div.mainmenu a, div.mainmenu a:visited, div.submenu a, div.submenu a:visited, +div.sectionmenu>a.button:link, div.sectionmenu>a.button:visited, +div.submenu label { + padding: 3px 10px 3px 10px; + color: white; + text-decoration: none; +} +div.mainmenu a:hover, div.submenu a:hover, div.sectionmenu>a.button:hover, +div.submenu label:hover { + color: #558195; + background-color: white; +} + +/* All page content from the bottom of the menu or submenu down to +** the footer */ +div.content { + padding: 0ex 1ex 1ex 1ex; + border: solid #aaa; + border-width: 1px; +} + +/* Some pages have section dividers */ +div.section { + margin-bottom: 0px; + margin-top: 1em; + padding: 1px 1px 1px 1px; + font-size: 1.2em; + font-weight: bold; + background-color: #558195; + color: white; + white-space: nowrap; +} + +/* The "Date" that occurs on the left hand side of timelines */ +div.divider { + background: #a1c4d4; + border: 2px #558195 solid; + font-size: 1em; font-weight: normal; + padding: .25em; + margin: .2em 0 .2em 0; + float: left; + clear: left; + white-space: nowrap; +} + +/* The footer at the very bottom of the page */ +div.footer { + clear: both; + font-size: 0.8em; + padding: 5px 10px 5px 10px; + text-align: right; + background-color: #558195; + border-bottom-left-radius: 8px; + border-bottom-right-radius: 8px; + color: white; +} + +/* Hyperlink colors in the footer */ +div.footer a { color: white; } +div.footer a:link { color: white; } +div.footer a:visited { color: white; } +div.footer a:hover { background-color: white; color: #558195; } + +/* verbatim blocks */ +pre.verbatim { + background-color: #f5f5f5; + padding: 0.5em; + white-space: pre-wrap; +} + +/* The label/value pairs on (for example) the ci page */ +table.label-value th { + vertical-align: top; + text-align: right; + padding: 0.2ex 2ex; +} ADDED skins/original/details.txt Index: skins/original/details.txt ================================================================== --- /dev/null +++ skins/original/details.txt @@ -0,0 +1,4 @@ +timeline-arrowheads: 1 +timeline-circle-nodes: 0 +timeline-color-graph-lines: 0 +white-foreground: 0 ADDED skins/original/footer.txt Index: skins/original/footer.txt ================================================================== --- /dev/null +++ skins/original/footer.txt @@ -0,0 +1,24 @@ +<div class="footer"> + <th1> + proc getTclVersion {} { + if {[catch {tclEval info patchlevel} tclVersion] == 0} { + return "<a href=\"https://www.tcl.tk/\">Tcl</a> version $tclVersion" + } + return "" + } + proc getVersion { version } { + set length [string length $version] + return [string range $version 1 [expr {$length - 2}]] + } + set version [getVersion $manifest_version] + set tclVersion [getTclVersion] + set fossilUrl https://www.fossil-scm.org + set fossilDate [string range $manifest_date 0 9]T[string range $manifest_date 11 end] + </th1> + This page was generated in about + <th1>puts [expr {([utime]+[stime]+1000)/1000*0.001}]</th1>s by + <a href="$fossilUrl/">Fossil</a> + version $release_version $tclVersion + <a href="$fossilUrl/index.html/info/$version">$manifest_version</a> + <a href="$fossilUrl/index.html/timeline?c=$fossilDate&y=ci">$manifest_date</a> +</div> ADDED skins/original/header.txt Index: skins/original/header.txt ================================================================== --- /dev/null +++ skins/original/header.txt @@ -0,0 +1,105 @@ +<div class="header"> + <div class="logo"> + <th1> + ## + ## NOTE: The purpose of this procedure is to take the base URL of the + ## Fossil project and return the root of the entire web site using + ## the same URI scheme as the base URL (e.g. http or https). + ## + proc getLogoUrl { baseurl } { + set idx(first) [string first // $baseurl] + if {$idx(first) != -1} { + ## + ## NOTE: Skip second slash. + ## + set idx(first+1) [expr {$idx(first) + 2}] + ## + ## NOTE: (part 1) The [string first] command does NOT actually + ## support the optional startIndex argument as specified + ## in the TH1 support manual; therefore, we fake it by + ## using the [string range] command and then adding the + ## necessary offset to the resulting index manually + ## (below). In Tcl, we could use the following instead: + ## + ## set idx(next) [string first / $baseurl $idx(first+1)] + ## + set idx(nextRange) [string range $baseurl $idx(first+1) end] + set idx(next) [string first / $idx(nextRange)] + if {$idx(next) != -1} { + ## + ## NOTE: (part 2) Add the necessary offset to the result of + ## the search for the next slash (i.e. the one after + ## the initial search for the two slashes). + ## + set idx(next) [expr {$idx(next) + $idx(first+1)}] + ## + ## NOTE: Back up one character from the next slash. + ## + set idx(next-1) [expr {$idx(next) - 1}] + ## + ## NOTE: Extract the URI scheme and host from the base URL. + ## + set scheme [string range $baseurl 0 $idx(first)] + set host [string range $baseurl $idx(first+1) $idx(next-1)] + ## + ## NOTE: Try to stay in SSL mode if we are there now. + ## + if {[string compare $scheme http:/] == 0} { + set scheme http:// + } else { + set scheme https:// + } + set logourl $scheme$host/ + } else { + set logourl $baseurl + } + } else { + set logourl $baseurl + } + return $logourl + } + set logourl [getLogoUrl $baseurl] + </th1> + <a href="$logourl"> + <img src="$logo_image_url" border="0" alt="$project_name"> + </a> + </div> + <div class="title">$<title></div> + <div class="status"><nobr><th1> + if {[info exists login]} { + puts "Logged in as $login" + } else { + puts "Not logged in" + } + </th1></nobr><small><div id="clock"></div></small></div> +</div> +<th1>html "<script nonce='$nonce'>"</th1> +function updateClock(){ + var e = document.getElementById("clock"); + if(e){ + var d = new Date(); + function f(n) { + return n < 10 ? '0' + n : n; + } + e.innerHTML = d.getUTCFullYear()+ '-' + + f(d.getUTCMonth() + 1) + '-' + + f(d.getUTCDate()) + ' ' + + f(d.getUTCHours()) + ':' + + f(d.getUTCMinutes()); + setTimeout(updateClock,(60-d.getUTCSeconds())*1000); + } +} +updateClock(); +</script> +<div class="mainmenu"><th1> +set sitemap 0 +foreach {name url expr class} $mainmenu { + if {![capexpr $expr]} continue + if {[string match /* $url]} {set url $home$url} + html "<a href='$url' class='$class'>$name</a>\n" + if {[string match */sitemap $url]} {set sitemap 1} +} +if {!$sitemap} { + html "<a href='$home/sitemap'>...</a>" +} +</th1></div> ADDED skins/plain_gray/css.txt Index: skins/plain_gray/css.txt ================================================================== --- /dev/null +++ skins/plain_gray/css.txt @@ -0,0 +1,152 @@ +/* General settings for the entire page */ +body { + margin: 0ex 1ex; + padding: 0px; + background-color: white; + font-family: sans-serif; + -moz-text-size-adjust: none; + -webkit-text-size-adjust: none; + -mx-text-size-adjust: none; +} + +/* The page title centered at the top of each page */ +div.title { + display: table-cell; + font-size: 1.5em; + font-weight: bold; + text-align: center; + padding: 0 0 0 10px; + color: #404040; + vertical-align: bottom; + width: 100%; +} + +/* The login status message in the top right-hand corner */ +div.status { + display: table-cell; + text-align: right; + vertical-align: bottom; + color: #404040; + font-weight: bold; + white-space: nowrap; +} + +/* The header across the top of the page */ +div.header { + display: table; + width: 100%; +} + +/* The main menu bar that appears at the top of the page beneath +** the header */ +div.mainmenu { + padding: 5px 10px 5px 10px; + font-size: 0.9em; + font-weight: bold; + text-align: center; + letter-spacing: 1px; + background-color: #404040; + color: white; + z-index: 21; /* just above hbdrop */ +} +.hbdrop { + background-color: white; + border: 1px solid black; + border-radius: 0.5em; + display: none; + width: 95%; + position: absolute; + z-index: 20; /* just below mainmenu, but above timeline bubbles */ +} +div.hbdrop a { color: #604000; } +div.hbdrop a:link { color: #604000;} +div.hbdrop a:visited { color: #600000; } + + +/* The submenu bar that *sometimes* appears below the main menu */ +div.submenu, div.sectionmenu { + padding: 3px 10px 3px 0px; + font-size: 0.9em; + text-align: center; + background-color: #606060; + color: white; +} +div.mainmenu a, +div.mainmenu a:visited, +div.submenu a, +div.submenu a:visited, +div.sectionmenu>a.button:link, +div.sectionmenu>a.button:visited, +div.submenu label { + padding: 3px 10px 3px 10px; + color: white; + text-decoration: none; +} +div.mainmenu a:hover, +div.submenu a:hover, +div.sectionmenu>a.button:hover, +div.submenu label:hover { + color: #404040; + background-color: white; +} +a, a:visited { + text-decoration: none; +} + +/* All page content from the bottom of the menu or submenu down to +** the footer */ +div.content { + padding: 0ex 0ex 0ex 0ex; +} +/* Hyperlink colors */ +div.content a { color: #604000; } +div.content a:link { color: #604000;} +div.content a:visited { color: #600000; } + +/* <verbatim> blocks */ +pre.verbatim { + background-color: #ffffff; + padding: 0.5em; + white-space: pre-wrap; +} + +/* Some pages have section dividers */ +div.section { + margin-bottom: 0px; + margin-top: 1em; + padding: 1px 1px 1px 1px; + font-size: 1.2em; + font-weight: bold; + background-color: #404040; + color: white; + white-space: nowrap; +} + +/* The "Date" that occurs on the left hand side of timelines */ +div.divider { + background: #a0a0a0; + border: 2px #505050 solid; + font-size: 1em; font-weight: normal; + padding: .25em; + margin: .2em 0 .2em 0; + float: left; + clear: left; + white-space: nowrap; +} + +/* The footer at the very bottom of the page */ +div.footer { + font-size: 0.8em; + margin-top: 12px; + padding: 5px 10px 5px 10px; + text-align: right; + background-color: #404040; + color: white; +} + +/* The label/value pairs on (for example) the vinfo page */ +table.label-value th { + vertical-align: top; + text-align: right; + padding: 0.2ex 2ex; +} ADDED skins/plain_gray/details.txt Index: skins/plain_gray/details.txt ================================================================== --- /dev/null +++ skins/plain_gray/details.txt @@ -0,0 +1,4 @@ +timeline-arrowheads: 1 +timeline-circle-nodes: 1 +timeline-color-graph-lines: 0 +white-foreground: 0 ADDED skins/plain_gray/footer.txt Index: skins/plain_gray/footer.txt ================================================================== --- /dev/null +++ skins/plain_gray/footer.txt @@ -0,0 +1,3 @@ +<div class="footer"> +Fossil $release_version $manifest_version $manifest_date +</div> ADDED skins/plain_gray/header.txt Index: skins/plain_gray/header.txt ================================================================== --- /dev/null +++ skins/plain_gray/header.txt @@ -0,0 +1,14 @@ +<div class="header"> + <div class="title">$<project_name>: $<title></div> +</div> +<div class="mainmenu"> +<th1> +html "<a id='hbbtn' href='$home/sitemap' aria-label='Site Map'>☰</a>" +builtin_request_js hbmenu.js +foreach {name url expr class} $mainmenu { + if {![capexpr $expr]} continue + if {[string match /* $url]} {set url $home$url} + html "<a href='$url' class='$class'>$name</a>\n" +} +</th1></div> +<div id='hbdrop' class='hbdrop'></div> ADDED skins/xekri/README.md Index: skins/xekri/README.md ================================================================== --- /dev/null +++ skins/xekri/README.md @@ -0,0 +1,2 @@ +"xekri" is a Lojban word that means "extermely dark-colored". +This skin was contributed by Andrew Moore. ADDED skins/xekri/css.txt Index: skins/xekri/css.txt ================================================================== --- /dev/null +++ skins/xekri/css.txt @@ -0,0 +1,1150 @@ +/****************************************************************************** + * Xekri + * + * To adjust the width of the contents for this skin, look for the "max-width" + * property and change its value. (It's in the "Main Area" section) The value + * determines how much of the browser window to use. Some like 100%, so that + * the entire window is used. Others prefer 80%, which makes the contents + * easier to read for them. + */ + + +/************************************** + * General HTML + */ + +html { + background-color: #333; + color: #eee; + font-family: Monospace; + font-size: 1em; + min-height: 100%; +} + +body { + margin: 0; + padding: 0; + -moz-text-size-adjust: none; + -ms-text-size-adjust: none; + -webkit-text-size-adjust: none; +} + +a { + color: #40a0ff; +} + +a:hover { + font-weight: bold; +} + +blockquote pre { + border: 1px dashed #ee0; +} + +blockquote pre, pre.verbatim { + background-color: #000; + border-radius: 0.75rem; + padding: 0.5rem; + white-space: pre-wrap; +} + +input[type="password"], input[type="text"], textarea { + background-color: #111; + color: #fff; + font-size: 1rem; +} + +h1 { + font-size: 2rem; +} + +h2 { + font-size: 1.5rem; +} + +h3 { + font-size: 1.25rem; +} + +span[style^=background-color] { + color: #000; +} + +td[style^=background-color] { + color: #000; +} + + +/************************************** + * Main Area + */ + +div.header, div.mainmenu, div.submenu, div.content, div.footer { + clear: both; + margin: 0 auto; + max-width: 90%; + padding: 0.25rem 1rem; +} + + +/************************************** + * Main Area: Header + */ + +div.header { + margin: 0.5rem auto 0 auto; +} + +div.logo img { + float: left; + padding: 0; + box-shadow: 3px 3px 1px #000; + margin: 0 6px 6px 0; +} + +div.logo br { + display: none; +} + +div.logo nobr { + color: #eee; + font-size: 1.2rem; + font-weight: bold; + padding: 0; + text-shadow: 3px 3px 1px #000; + vertical-align: top; + white-space: nowrap; +} + +div.title { + color: #3297f9; + font-family: Verdana, sans-serif; + font-weight: bold; + font-size: 2.5rem; + padding: 0.5rem; + text-align: center; + text-shadow: 3px 3px 1px #000; +} + +div.status { + color: #ee0; + font-size: 1rem; + padding: 0.25rem; + text-align: right; + text-shadow: 2px 2px 1px #000; +} + + +/************************************** + * Main Area: Global Menu + */ + +div.mainmenu, div.submenu { + background-color: #080; + border-radius: 1rem 1rem 0 0; + box-shadow: 3px 4px 1px #000; + color: #000; + font-weight: bold; + font-size: 1.1rem; + text-align: center; +} + +div.mainmenu { + padding-top: 0.33rem; + padding-bottom: 0.25rem; +} + +div.submenu { + border-top: 1px solid #0a0; + border-radius: 0; + display: block; +} + +div.mainmenu a, div.submenu a, div.submenu label { + color: #000; + padding: 0 0.75rem; + text-decoration: none; +} + +div.mainmenu a:hover, div.submenu a:hover, div.submenu label:hover { + color: #fff; + text-shadow: 0px 0px 6px #0f0; +} + +div.submenu * { + margin: 0 0.5rem; + vertical-align: middle; +} + +div.submenu select, div.submenu input { + background-color: #222; + border: 1px inset #080; + color: #eee; + cursor: pointer; + font-size: 0.9rem; +} + +div.submenu select { + height: 1.75rem; +} + +/************************************** + * Main Area: Content + */ + +div.content { + background-color: #222; + border-radius: 0 0 1rem 1rem; + box-shadow: 3px 3px 1px #000; + min-height:40%; + padding-bottom: 1rem; + padding-top: 0.5rem; +} + +div.content table[bgcolor="white"] { + color: #000; +} + +.piechartLabel { + fill: white; +} +.piechartLine { + stroke: white; +} + +/************************************** + * Main Area: Footer + */ + +div.footer { + color: #ee0; + font-size: 0.75rem; + padding: 0; + text-align: right; + width: 75%; +} + + +div.footer div { + background-color: #222; + box-shadow: 3px 3px 1px #000; + border-radius: 0 0 1rem 1rem; + margin: 0 0 10px 0; + padding: 0.5rem 0.75rem; +} + +div.footer div.page-time { + float: left; +} + +div.footer div.fossil-info { + float: right; +} + +div.footer a, div.footer a:link, div.footer a:visited { + color: #ee0; +} + +div.footer a:hover { + color: #fff; + text-shadow: 0px 0px 6px #ee0; +} + + +/************************************** + * Check-in + */ + +table.label-value th { + vertical-align: top; + text-align: right; + padding: 0.1rem 1rem; +} + + +/************************************** + * Diffs + */ + +/* Code Added */ +span.diffadd { + background-color: #7f7; + color: #000; +} + +/* Code Changed */ +span.diffchng { + background-color: #77f; + color: #000; +} + +/* Code Deleted */ +span.diffrm { + background-color: #f77; + color: #000; +} + + +/************************************** + * Diffs : Side-By-Side + */ + +/* display (column-based) */ +table.sbsdiffcols { + border-spacing: 0; + font-size: 0.85rem; + width: 90%; +} + +table.sbsdiffcols pre { + border: 0; + margin: 0; + padding: 0; +} + +table.sbsdiffcols td { + padding: 0; + vertical-align: top; +} + +/* line number column */ +div.difflncol { + color: #ee0; + padding-right: 0.75em; + text-align: right; +} + +/* diff text column */ +div.difftxtcol { + background-color: #111; + overflow-x: auto; + width: 45em; +} + +/* suppressed lines */ +span.diffhr { + display: inline-block; + margin-bottom: 0.75em; + color: #ff0; +} + +/* diff marker column */ +div.diffmkrcol { + padding: 0 0.5em; +} + + +/************************************** + * Diffs : Unified + */ + +pre.udiff { + background-color: #111; +} + +/* line numbers */ +span.diffln { + background-color: #222; + color: #ee0; +} + + +/************************************** + * File List : Flat + */ + +table.browser { + width: 100%; + border: 0; +} + +td.browser { + width: 24%; + vertical-align: top; +} + +ul.browser { + margin: 0.5rem; + padding: 0.5rem; + white-space: nowrap; +} + +ul.browser li.dir { + font-style: italic +} + + +/************************************** + * File List : Age + */ + +.fileage tr:hover { + background-color: #225; +} + + +/************************************** + * File List : Tree + */ + +.filetree { + line-height: 1.5; + margin: 1rem 0; +} + +/* list */ +.filetree ul { + list-style: none; + margin: 0; + padding: 0; +} + +/* collapsed list */ +.filetree ul.collapsed { + display: none; +} + +/* lists below the root */ +.filetree ul ul { + margin: 0 0 0 21px; + position: relative; +} + +/* lists items */ +.filetree li { + margin: 0; + padding: 0; + position: relative; +} + +/* node lines */ +.filetree li li:before { + border-bottom: 2px solid #000; + border-left: 2px solid #000; + content: ''; + height: 1.5rem; + left: -14px; + position: absolute; + top: -0.8rem; + width: 14px; +} + +/* directory lines */ +.filetree li > ul:before { + border-left: 2px solid #000; + bottom: 0; + content: ''; + left: -35px; + position: absolute; + top: -1.5rem; +} + +/* hide lines for last-child directories */ +.filetree li.last > ul:before { + display: none; +} + +.filetree a { + background-image: url(data:image/gif;base64,R0lGODlhEAAQAJEAAP\/\/\/yEhIf\/\/\/wAAACH5BAEHAAIALAAAAAAQABAAAAIvlIKpxqcfmgOUvoaqDSCxrEEfF14GqFXImJZsu73wepJzVMNxrtNTj3NATMKhpwAAOw==); + background-position: center left; + background-repeat: no-repeat; + display: inline-block; + min-height: 16px; + padding-left: 21px; + position: relative; + z-index: 1; +} + +.filetree .dir > a { + background-image: url(data:image/gif;base64,R0lGODlhEAAQAJEAAP/WVCIiIv\/\/\/wAAACH5BAEHAAIALAAAAAAQABAAAAInlI9pwa3XYniCgQtkrAFfLXkiFo1jaXpo+jUs6b5Z/K4siDu5RPUFADs=); + font-style: italic +} + +.filetreeline:hover { + color: #000; + font-weight: bold; +} + +.filetreeline .filetreeage { + padding-right: 0.5rem; +} + +/************************************** + * Logout + */ + +span.loginError { + color: #f00; +} + +table.login_out { + margin: 10px; + text-align: left; +} + +td.login_out_label { + text-align: center; +} + +div.captcha { + padding: 1rem; + text-align: center; +} + +table.captcha { + background-color: #111; + border-color: #111; + border-style: inset; + border-width: 2px; + margin: auto; + padding: 0.5rem; +} + +table.captcha pre { + color: #ee0; +} + + +/************************************** + * Statistics Reports + */ + +.statistics-report-graph-line { + background-color: #22e; +} + +.statistics-report-table-events th { + padding: 0 1rem; +} + +.statistics-report-table-events td { + padding: 0.1rem 1rem; +} + +.statistics-report-row-year { + color: #ee0; + text-align: left; +} + +.statistics-report-week-number-label { + font-size: 0.8rem; + text-align: right; +} + +.statistics-report-week-of-year-list { + font-size: 0.8rem; +} + + +/************************************** + * Search + */ + +.searchResult .snippet mark { + color: #ee0; +} + + +/************************************** + * Sections + */ + +div.section, div.sectionmenu { + color: #2ee; + background-color: #22c; + border-radius: 0 3rem; + box-shadow: 2px 2px #000; + display: flex; + font-size: 1.1rem; + font-weight: bold; + justify-content: space-around; + margin: 1.2rem auto 0.75rem auto; + padding: 0.2rem; + text-align: center; +} + +div.sectionmenu { + border-radius: 0 0 3rem 3rem; + margin-top: -0.75rem; + width: 75%; +} + +div.sectionmenu > a:link, div.sectionmenu > a:visited { + color: #000; + text-decoration: none; +} + +div.sectionmenu > a:hover { + color: #eee; + text-shadow: 0px 0px 6px #eee; +} + + +/************************************** + * Sidebox + */ + +div.sidebox { + background-color: #333; + border-radius: 0.5rem; + box-shadow: 3px 3px 1px #000; + float: right; + margin: 1rem 0.5rem; + padding: 0.5rem; +} + +div.sidebox ol { + margin: 0 0 0.5rem 2.5rem; + padding: 0 0; +} + +div.sidebox ol li { + margin-top: 0.75rem; +} + +div.sideboxTitle { + background-color: #ee0; + border-radius: 0.5rem 0.5rem 0 0; + color: #000; + font-weight: bold; + margin: -0.5rem -0.5rem 0 -0.5rem; + padding: 0.25rem; + text-align: center; +} + +div.sideboxDescribed { + display: inline; +} + +/* --- Untested : Begin --- */ +/* The defined element in sideboxes for branches,.. */ +span.disabled { + color: #f00; +} +/* --- Untested : End --- */ + + +/************************************** + * Tag + */ + +/* --- Untested : Begin --- */ +/* the format for the tag links */ +a.tagLink { +} +/* the format for the tag display(no history permission!) */ +span.tagDsp { + font-weight: bold; +} +/* the format for fixed/canceled tags,.. */ +span.infoTagCancelled { + font-weight: bold; + text-decoration: line-through; +} +/* --- Untested : End --- */ + + +/************************************** + * Ticket + */ + +table.report { + color: #000; + border: 1px solid #999; + border-collapse: collapse; + margin: 1rem 0; +} + +table.report tr th { + color: #eee; + padding: 3px 5px; + text-transform : capitalize; +} + +table.report tr td { + padding: 3px 5px; +} + +/* example ticket colors */ +table.rpteditex { + border-collapse: collapse; + border-spacing: 0; + color: #000; + float: right; + margin: 0; + padding: 0; + text-align: center; + width: 125px; +} + +td.rpteditex { + border-color: #000; + border-style: solid; + border-width: thin; +} + +#reportTable { +} + +/* format for labels on ticket display page */ +td.tktDspLabel { + text-align: right; +} + +/* format for values on ticket display page */ +td.tktDspValue { + background-color: #111; + text-align: left; + vertical-align: top; +} + +/* Tickets on timelines */ +td.tktTlOpen { + color: #ffa0a0; +} + +/* format for ticket error messages */ +span.tktError { + color: #f00; + font-weight: bold; +} + + +/************************************** + * Timeline + */ + +/* The suppressed duplicates lines in timeline, .. */ +.timelineDisabled { + font-size: 0.5rem; + font-style: italic; +} + +/* the format for the timeline version display(no history permission!) */ +.timelineHistDsp { + font-weight: bold; +} + +.content .timelineTable { + border: 0; + border-spacing: 0 0.5rem; +} + +.content .timelineTable tr { + background: #222; + border: 0; + padding: 0; + box-shadow: none; +} + +.timelineTable .timelineDate { + color: #ee0; + font-size: 1.2rem; + font-weight: bold; + margin-top: 1rem; + white-space: nowrap; +} + +.timelineTable .timelineTime { + border-radius: 0; + border-width: 0; + padding: 0.25rem 0.5rem 0.5rem 0.5rem; + white-space: nowrap; +} + +.timelineGraph { + text-align: left; + vertical-align: top; + width: 20px; +} + +.timelineTable .timelineModernCell , +.timelineTable .timelineCompactCell , +.timelineTable .timelineVerboseCell , +.timelineTable .timelineDetailCell { +/* + background: linear-gradient(to bottom, #222 0%, #333 16%, #222 100%); +*/ + border-radius: 0; + border-width: 0; + padding: 0.25rem 0.5rem 0.5rem 0.5rem; +} + +.timelineTable .timelineColumnarCell { +/* + background: linear-gradient(to bottom, #222 0%, #333 16%, #222 100%); +*/ + border-radius: 0; + border-width: 0; + padding: 0.25rem 0.5rem 0.5rem 0.5rem; +} + +.timelineTable .timelineModernCell[id] , +.timelineTable .timelineCompactCell[id] , +.timelineTable .timelineVerboseCell[id] , +.timelineTable .timelineColumnarCell[id] , +.timelineTable .timelineDetailCell[id] { + background: #272727; +} + +.timelineTable .timelineCurrent .timelineTime { + background: #333; + border-radius: 1rem 0 0 1rem; + border-width: 0; +} + +.timelineTable .timelineCurrent .timelineColumnarCell { + background: #333; +} + +.timelineTable .timelineCurrent .timelineModernCell , +.timelineTable .timelineCurrent .timelineCompactCell , +.timelineTable .timelineCurrent .timelineVerboseCell , +.timelineTable .timelineCurrent .timelineDetailCell { + background: #333; + border-radius: 0 1rem 1rem 0; +} + +.timelineTable .timelineSelected { + background: #222; + border: 0; + box-shadow: none; +} +.timelineSelected {} +.timelineSecondary {} + +.timelineTable .timelineSelected .timelineTime { + background: #333; + border-radius: 1rem 0 0 1rem; + box-shadow: 2px 2px 1px #000; +} + +.timelineTable .timelineSelected .timelineColumnarCell { + background: #333; + box-shadow: 2px 2px 1px #000; +} + +.timelineTable .timelineSelected .timelineModernCell , +.timelineTable .timelineSelected .timelineCompactCell , +.timelineTable .timelineSelected .timelineVerboseCell , +.timelineTable .timelineSelected .timelineDetailCell { + background: #333; + border-radius: 0 1rem 1rem 0; + box-shadow: 2px 2px 1px #000; +} + +span.timelineSelected { + padding: 0 1em 0 1em; + border-radius: 1rem; + background: #333; + box-shadow: 2px 2px 1px #000; +} + +.timelineTable .timelineModernCell .timelineModernComment , +.timelineTable .timelineModernCell .timelineModernDetail , +.timelineTable .timelineCompactCell .timelineCompactComment , +.timelineTable .timelineCompactCell .timelineCompactDetail , +.timelineTable .timelineVerboseCell .timelineVerboseComment , +.timelineTable .timelineVerboseCell .timelineVerboseDetail { +} + +.timelineTable .timelineModernCell .timelineLeaf , +.timelineTable .timelineCompactCell .timelineLeaf , +.timelineTable .timelineVerboseCell .timelineLeaf , +.timelineTable .timelineVerboseComment .timelineLeaf { + font-weight: bold; +} + +.timelineTable .timelineModernCell .timelineModernDetail , +.timelineTable .timelineDetailCell { + font-size: 85%; +} + +.timelineTable .timelineDetailCell .timelineColumnarDetail { + white-space: pre-line; +} + +.timelineTable .timelineDetailCell ul.filelist::before { + content: "files:"; +} + +.timelineTable .timelineDetailCell ul.filelist { + margin-left: 0; + padding-left: 0; +} + +.timelineTable .timelineDetailCell ul.filelist li { + margin-left: 1.5rem; + padding-left: 0; + white-space: nowrap; +} + +/* the format for the timeline version links */ +a.timelineHistLink { +} + + +/************************************** + * User Edit + */ + +/* layout definition for the capabilities box on the user edit detail page */ +div.ueditCapBox { + float: left; + margin: 0 20px 20px 0; +} + +/* format of the label cells in the detailed user edit page */ +td.usetupEditLabel { + text-align: right; + vertical-align: top; + white-space: nowrap; +} + +/* color for capabilities, inherited by nobody */ +span.ueditInheritNobody { + color: #0f0; +} + +/* color for capabilities, inherited by developer */ +span.ueditInheritDeveloper { + color: #f00; +} + +/* color for capabilities, inherited by reader */ +span.ueditInheritReader { + color: #ee0; +} + +/* color for capabilities, inherited by anonymous */ +span.ueditInheritAnonymous { + color: #00f; +} + +/* format for capabilities */ +span.capability { + font-weight: bold; +} + +/* format for different user types */ +span.usertype { + font-weight: bold; +} + +span.usertype:before { + content:"'"; +} + +span.usertype:after { + content:"'"; +} + + +/************************************** + * User List + */ + +table.usetupLayoutTable { + margin: 0.5rem; + outline-style: none; + padding: 0; +} + +td.usetupColumnLayout { + vertical-align: top +} + +td.usetupColumnLayout ol th { + padding: 0 0.75rem 0.5rem 0; +} + +span.note { + color: #ee0; + font-weight: bold; +} + +table.usetupUserList { + margin: 0.5rem; +} + +.usetupListUser { + padding-right: 20px; + text-align: right; +} + +.usetupListCap { + padding-right: 15px; + text-align: center; +} + +.usetupListCon { + text-align: left; +} + + +/************************************** + * Wiki + */ + +span.wikiError { + font-weight: bold; + color: #f00; +} + +/* the format for fixed/cancelled tags */ +span.wikiTagCancelled { + text-decoration: line-through; +} + + +/************************************** + * Did not encounter these + */ + +/* selected lines of text within a linenumbered artifact display */ +table.numbered-lines td.line-numbers span.selected-line { + font-weight: bold; + color: #00f; + background-color: #d5d5ff; + border-color: #00f; +} + +/* format for missing privileges note on user setup page */ +p.missingPriv { + color: #00f; +} + +/* format for leading text in wikirules definitions */ +span.wikiruleHead { + font-weight: bold; +} + + +/* format for user color input on checkin edit page */ +input.checkinUserColor { + /* no special definitions, class defined, to enable color pickers, + * f.e.: + * ** add the color picker found at http:jscolor.com as java script + * include + * ** to the header and configure the java script file with + * ** 1. use as bindClass :checkinUserColor + * ** 2. change the default hash adding behaviour to ON + * ** or change the class definition of element identified by + * id="clrcust" + * ** to a standard jscolor definition with java script in the footer. + * */ +} + +/* format for end of content area, to be used to clear page flow. */ +div.endContent { + clear: both; +} + +/* format for general errors */ +p.generalError { + color: #f00; +} + +/* format for tktsetup errors */ +p.tktsetupError { + color: #f00; + font-weight: bold; +} +/* format for xfersetup errors */ +p.xfersetupError { + color: #f00; + font-weight: bold; +} +/* format for th script errors */ +p.thmainError { + color: #f00; + font-weight: bold; +} +/* format for th script trace messages */ +span.thTrace { + color: #f00; +} +/* format for report configuration errors */ +p.reportError { + color: #f00; + font-weight: bold; +} +/* format for report configuration errors */ +blockquote.reportError { + color: #f00; + font-weight: bold; +} +/* format for artifact lines, no longer shunned */ +p.noMoreShun { + color: #00f; +} +/* format for artifact lines being shunned */ +p.shunned { + color: #00f; +} +/* a broken hyperlink */ +span.brokenlink { + color: #f00; +} +/* List of files in a timeline */ +ul.filelist { + margin-top: 3px; + line-height: 100%; +} +/* Moderation Pending message on timeline */ +span.modpending { + color: #b30; + font-style: italic; +} +/* format for textarea labels */ +span.textareaLabel { + font-weight: bold; +} +/* format for th1 script results */ +pre.th1result { + white-space: pre-wrap; + word-wrap: break-word; +} +/* format for th1 script errors */ +pre.th1error { + white-space: pre-wrap; + word-wrap: break-word; + color: #f00; +} + +/* even table row color */ +tr.row0 { + /* use default */ +} +/* odd table row color */ +tr.row1 { + /* Use default */ +} + +.fossil-PopupWidget, +.fossil-tooltip.help-buttonlet-content { + background-color: #111; + border: 1px solid rgba(255,255,255,0.5); +} +.fossil-PopupWidget a, +.fossil-PopupWidget a:visited { + color: white; +} +div.forumSel { + background-color: #663399; +} +div.forumPostBody blockquote { + border-width: 1pt; + border-style: solid; + padding: 0 0.5em; + border-radius: 0.25em; +} + +.debug { + color: black; +} + +body.branch .brlist > table > tbody > tr:hover:not(.selected), +body.branch .brlist > table > tbody > tr.selected { + background-color: #444; +} ADDED skins/xekri/details.txt Index: skins/xekri/details.txt ================================================================== --- /dev/null +++ skins/xekri/details.txt @@ -0,0 +1,4 @@ +timeline-arrowheads: 1 +timeline-circle-nodes: 0 +timeline-color-graph-lines: 1 +white-foreground: 1 ADDED skins/xekri/footer.txt Index: skins/xekri/footer.txt ================================================================== --- /dev/null +++ skins/xekri/footer.txt @@ -0,0 +1,9 @@ +</div> +<div class="footer"> +<div class="page-time"> +Generated in <th1>puts [expr {([utime]+[stime]+1000)/1000*0.001}]</th1>s +</div> +<div class="fossil-info"> +Fossil v$release_version $manifest_version +</div> +</div> ADDED skins/xekri/header.txt Index: skins/xekri/header.txt ================================================================== --- /dev/null +++ skins/xekri/header.txt @@ -0,0 +1,110 @@ +<div class="header"> + <div class="logo"> + <th1> + ## + ## NOTE: The purpose of this procedure is to take the base URL of the + ## Fossil project and return the root of the entire web site using + ## the same URI scheme as the base URL (e.g. http or https). + ## + proc getLogoUrl { baseurl } { + set idx(first) [string first // $baseurl] + if {$idx(first) != -1} { + ## + ## NOTE: Skip second slash. + ## + set idx(first+1) [expr {$idx(first) + 2}] + ## + ## NOTE: (part 1) The [string first] command does NOT actually + ## support the optional startIndex argument as specified + ## in the TH1 support manual; therefore, we fake it by + ## using the [string range] command and then adding the + ## necessary offset to the resulting index manually + ## (below). In Tcl, we could use the following instead: + ## + ## set idx(next) [string first / $baseurl $idx(first+1)] + ## + set idx(nextRange) [string range $baseurl $idx(first+1) end] + set idx(next) [string first / $idx(nextRange)] + if {$idx(next) != -1} { + ## + ## NOTE: (part 2) Add the necessary offset to the result of + ## the search for the next slash (i.e. the one after + ## the initial search for the two slashes). + ## + set idx(next) [expr {$idx(next) + $idx(first+1)}] + ## + ## NOTE: Back up one character from the next slash. + ## + set idx(next-1) [expr {$idx(next) - 1}] + ## + ## NOTE: Extract the URI scheme and host from the base URL. + ## + set scheme [string range $baseurl 0 $idx(first)] + set host [string range $baseurl $idx(first+1) $idx(next-1)] + ## + ## NOTE: Try to stay in SSL mode if we are there now. + ## + if {[string compare $scheme http:/] == 0} { + set scheme http:// + } else { + set scheme https:// + } + set logourl $scheme$host/ + } else { + set logourl $baseurl + } + } else { + set logourl $baseurl + } + return $logourl + } + set logourl [getLogoUrl $baseurl] + </th1> + <a href="$logourl"> + <img src="$logo_image_url" border="0" alt="$project_name"> + </a> + </div> + <div class="title">$<title></div> + <div class="status"><nobr><th1> + if {[info exists login]} { + puts "Logged in as $login" + } else { + puts "Not logged in" + } + </th1></nobr><small><div id="clock"></div></small></div> +</div> +<th1>html "<script nonce='$nonce'>"</th1> +function updateClock(){ + var e = document.getElementById("clock"); + if(e){ + var d = new Date(); + function f(n) { + return n < 10 ? '0' + n : n; + } + e.innerHTML = d.getUTCFullYear()+ '-' + + f(d.getUTCMonth() + 1) + '-' + + f(d.getUTCDate()) + ' ' + + f(d.getUTCHours()) + ':' + + f(d.getUTCMinutes()); + setTimeout(updateClock,(60-d.getUTCSeconds())*1000); + } +} +updateClock(); +</script> +<div class="mainmenu"><th1> +set sitemap 0 +foreach {name url expr class} $mainmenu { + if {![capexpr $expr]} continue + if {[string match /* $url]} { + if {[string match $url\[/?#\]* /$current_page/]} { + set class "active $class" + } + set url $home$url + } + html "<a href='$url' class='$class'>$name</a>\n" + if {[string match */sitemap $url]} {set sitemap 1} +} +if {!$sitemap} { + html "<a href='$home/sitemap'>...</a>\n" +} +</th1></div> ADDED src/Makefile Index: src/Makefile ================================================================== --- /dev/null +++ src/Makefile @@ -0,0 +1,3 @@ +all clean: + $(MAKE) -C .. $(MAKECMDGOALS) + ADDED src/accordion.js Index: src/accordion.js ================================================================== --- /dev/null +++ src/accordion.js @@ -0,0 +1,35 @@ +/* Attach appropriate javascript to each ".accordion" button so that +** it expands and contracts when clicked. +** The uncompressed source code for the SVG icons can be found on the +** wiki page "branch/accordion-experiments" in the Fossil repository. +*/ +var acc_svgdata = ["data:image/svg+xml,"+ + "%3Csvg xmlns='http:"+"/"+"/www.w3.org/2000/svg' viewBox='0 0 16 16'%3E"+ + "%3Cpath style='fill:black;opacity:0' d='M16,16H0V0h16v16z'/%3E"+ + "%3Cpath style='fill:rgb(240,240,240)' d='M14,14H2V2h12v12z'/%3E"+ + "%3Cpath style='fill:rgb(64,64,64)' d='M13,13H3V3h10v10z'/%3E"+ + "%3Cpath style='fill:rgb(248,248,248)' d='M12,12H4V4h8v8z'/%3E"+ + "%3Cpath style='fill:rgb(80,128,208)' d='", "'/%3E%3C/svg%3E", + "M5,7h2v-2h2v2h2v2h-2v2h-2v-2h-2z", "M11,9H5V7h6v6z"]; +var a = document.getElementsByClassName("accordion"); +for(var i=0; i<a.length; i++){ + var img = document.createElement("img"); + img.src = acc_svgdata[0]+acc_svgdata[2]+acc_svgdata[1]; + img.className = "accordion_btn accordion_btn_plus"; + a[i].insertBefore(img,a[i].firstChild); + img = document.createElement("img"); + img.src = acc_svgdata[0]+acc_svgdata[3]+acc_svgdata[1]; + img.className = "accordion_btn accordion_btn_minus"; + a[i].insertBefore(img,a[i].firstChild); + var p = a[i].nextElementSibling; + p.style.maxHeight = p.scrollHeight + "px"; + a[i].addEventListener("click",function(){ + var x = this.nextElementSibling; + if( this.classList.contains("accordion_closed") ){ + x.style.maxHeight = x.scrollHeight + "px"; + }else{ + x.style.maxHeight = "0"; + } + this.classList.toggle("accordion_closed"); + }); +} Index: src/add.c ================================================================== --- src/add.c +++ src/add.c @@ -20,316 +20,1077 @@ */ #include "config.h" #include "add.h" #include <assert.h> #include <dirent.h> - -/* -** Set to true if files whose names begin with "." should be -** included when processing a recursive "add" command. -*/ -static int includeDotFiles = 0; - -/* -** Add a single file -*/ -static void add_one_file(const char *zName, int vid, Blob *pOmit){ - Blob pathname; - const char *zPath; - - file_tree_name(zName, &pathname, 1); - zPath = blob_str(&pathname); - if( strcmp(zPath, "manifest")==0 - || strcmp(zPath, "_FOSSIL_")==0 - || strcmp(zPath, "_FOSSIL_-journal")==0 - || strcmp(zPath, ".fos")==0 - || strcmp(zPath, ".fos-journal")==0 - || strcmp(zPath, "manifest.uuid")==0 - || blob_compare(&pathname, pOmit)==0 - ){ - fossil_warning("cannot add %s", zPath); - }else{ - if( !file_is_simple_pathname(zPath) ){ - fossil_fatal("filename contains illegal characters: %s", zPath); - } -#ifdef __MINGW32__ - if( db_exists("SELECT 1 FROM vfile" - " WHERE pathname=%Q COLLATE nocase", zPath) ){ - db_multi_exec("UPDATE vfile SET deleted=0" - " WHERE pathname=%Q COLLATE nocase", zPath); - } -#else - if( db_exists("SELECT 1 FROM vfile WHERE pathname=%Q", zPath) ){ - db_multi_exec("UPDATE vfile SET deleted=0 WHERE pathname=%Q", zPath); - } -#endif - else{ +#include "cygsup.h" + +/* +** This routine returns the names of files in a working checkout that +** are created by Fossil itself, and hence should not be added, deleted, +** or merge, and should be omitted from "clean" and "extras" lists. +** +** Return the N-th name. The first name has N==0. When all names have +** been used, return 0. +*/ +const char *fossil_reserved_name(int N, int omitRepo){ + /* Possible names of the local per-checkout database file and + ** its associated journals + */ + static const char *const azName[] = { + "_FOSSIL_", + "_FOSSIL_-journal", + "_FOSSIL_-wal", + "_FOSSIL_-shm", + ".fslckout", + ".fslckout-journal", + ".fslckout-wal", + ".fslckout-shm", + + /* The use of ".fos" as the name of the checkout database is + ** deprecated. Use ".fslckout" instead. At some point, the following + ** entries should be removed. 2012-02-04 */ + ".fos", + ".fos-journal", + ".fos-wal", + ".fos-shm", + }; + + /* Possible names of auxiliary files generated when the "manifest" property + ** is used + */ + static const struct { + const char *fname; + int flg; + }aManifestflags[] = { + { "manifest", MFESTFLG_RAW }, + { "manifest.uuid", MFESTFLG_UUID }, + { "manifest.tags", MFESTFLG_TAGS } + }; + static const char *azManifests[3]; + + /* + ** Names of repository files, if they exist in the checkout. + */ + static const char *azRepo[4] = { 0, 0, 0, 0 }; + + /* Cached setting "manifest" */ + static int cachedManifest = -1; + static int numManifests; + + if( cachedManifest == -1 ){ + int i; + Blob repo; + cachedManifest = db_get_manifest_setting(); + numManifests = 0; + for(i=0; i<count(aManifestflags); i++){ + if( cachedManifest&aManifestflags[i].flg ) { + azManifests[numManifests++] = aManifestflags[i].fname; + } + } + blob_zero(&repo); + if( file_tree_name(g.zRepositoryName, &repo, 0, 0) ){ + const char *zRepo = blob_str(&repo); + azRepo[0] = zRepo; + azRepo[1] = mprintf("%s-journal", zRepo); + azRepo[2] = mprintf("%s-wal", zRepo); + azRepo[3] = mprintf("%s-shm", zRepo); + } + } + + if( N<0 ) return 0; + if( N<count(azName) ) return azName[N]; + N -= count(azName); + if( cachedManifest ){ + if( N<numManifests ) return azManifests[N]; + N -= numManifests; + } + if( !omitRepo && N<count(azRepo) ) return azRepo[N]; + return 0; +} + +/* +** Return a list of all reserved filenames as an SQL list. +*/ +const char *fossil_all_reserved_names(int omitRepo){ + static char *zAll = 0; + if( zAll==0 ){ + Blob x; + int i; + const char *z; + blob_zero(&x); + for(i=0; (z = fossil_reserved_name(i, omitRepo))!=0; i++){ + if( i>0 ) blob_append(&x, ",", 1); + blob_appendf(&x, "'%q'", z); + } + zAll = blob_str(&x); + } + return zAll; +} + +/* +** COMMAND: test-reserved-names +** +** Usage: %fossil test-reserved-names [-omitrepo] +** +** Show all reserved filenames for the current check-out. +*/ +void test_reserved_names(void){ + int i; + const char *z; + int omitRepo = find_option("omitrepo",0,0)!=0; + + /* We should be done with options.. */ + verify_all_options(); + + db_must_be_within_tree(); + for(i=0; (z = fossil_reserved_name(i, omitRepo))!=0; i++){ + fossil_print("%3d: %s\n", i, z); + } + fossil_print("ALL: (%s)\n", fossil_all_reserved_names(omitRepo)); +} + +/* +** Add a single file named zName to the VFILE table with vid. +** +** Omit any file whose name is pOmit. +*/ +static int add_one_file( + const char *zPath, /* Tree-name of file to add. */ + int vid /* Add to this VFILE */ +){ + int doSkip = 0; + if( !file_is_simple_pathname(zPath, 1) ){ + fossil_warning("filename contains illegal characters: %s", zPath); + return 0; + } + if( db_exists("SELECT 1 FROM vfile" + " WHERE pathname=%Q %s", zPath, filename_collation()) ){ + db_multi_exec("UPDATE vfile SET deleted=0" + " WHERE pathname=%Q %s AND deleted", + zPath, filename_collation()); + }else{ + char *zFullname = mprintf("%s%s", g.zLocalRoot, zPath); + int isExe = file_isexe(zFullname, RepoFILE); + int isLink = file_islink(0); + if( file_nondir_objects_on_path(g.zLocalRoot, zFullname) ){ + /* Do not add unsafe files to the vfile */ + doSkip = 1; + }else{ db_multi_exec( - "INSERT INTO vfile(vid,deleted,rid,mrid,pathname)" - "VALUES(%d,0,0,0,%Q)", vid, zPath); - } - printf("ADDED %s\n", zPath); - } - blob_reset(&pathname); -} - -/* -** All content of the zDir directory to the SFILE table. -*/ -void add_directory_content(const char *zDir){ - DIR *d; - int origSize; - struct dirent *pEntry; - Blob path; - - blob_zero(&path); - blob_append(&path, zDir, -1); - origSize = blob_size(&path); - d = opendir(zDir); - if( d ){ - while( (pEntry=readdir(d))!=0 ){ - char *zPath; - if( pEntry->d_name[0]=='.' ){ - if( !includeDotFiles ) continue; - if( pEntry->d_name[1]==0 ) continue; - if( pEntry->d_name[1]=='.' && pEntry->d_name[2]==0 ) continue; - } - blob_appendf(&path, "/%s", pEntry->d_name); - zPath = blob_str(&path); - if( file_isdir(zPath)==1 ){ - add_directory_content(zPath); - }else if( file_isfile(zPath) ){ - db_multi_exec("INSERT INTO sfile VALUES(%Q)", zPath); - } - blob_resize(&path, origSize); - } - } - closedir(d); - blob_reset(&path); -} - -/* -** Add all content of a directory. -*/ -void add_directory(const char *zDir, int vid, Blob *pOmit){ - Stmt q; - add_directory_content(zDir); - db_prepare(&q, "SELECT x FROM sfile ORDER BY x"); - while( db_step(&q)==SQLITE_ROW ){ - const char *zName = db_column_text(&q, 0); - add_one_file(zName, vid, pOmit); - } - db_finalize(&q); - db_multi_exec("DELETE FROM sfile"); -} + "INSERT INTO vfile(vid,deleted,rid,mrid,pathname,isexe,islink,mhash)" + "VALUES(%d,0,0,0,%Q,%d,%d,NULL)", + vid, zPath, isExe, isLink); + } + fossil_free(zFullname); + } + if( db_changes() && !doSkip ){ + fossil_print("ADDED %s\n", zPath); + return 1; + }else{ + fossil_print("SKIP %s\n", zPath); + return 0; + } +} + +/* +** Add all files in the sfile temp table. +** +** Automatically exclude the repository file and any other files +** with reserved names. Also exclude files that are beneath an +** existing symlink. +*/ +static int add_files_in_sfile(int vid){ + const char *zRepo; /* Name of the repository database file */ + int nAdd = 0; /* Number of files added */ + int i; /* Loop counter */ + const char *zReserved; /* Name of a reserved file */ + Blob repoName; /* Treename of the repository */ + Stmt loop; /* SQL to loop over all files to add */ + int (*xCmp)(const char*,const char*); + + if( !file_tree_name(g.zRepositoryName, &repoName, 0, 0) ){ + blob_zero(&repoName); + zRepo = ""; + }else{ + zRepo = blob_str(&repoName); + } + if( filenames_are_case_sensitive() ){ + xCmp = fossil_strcmp; + }else{ + xCmp = fossil_stricmp; + } + db_prepare(&loop, + "SELECT pathname FROM sfile" + " WHERE pathname NOT IN (" + "SELECT sfile.pathname FROM vfile, sfile" + " WHERE vfile.islink" + " AND NOT vfile.deleted" + " AND sfile.pathname>(vfile.pathname||'/')" + " AND sfile.pathname<(vfile.pathname||'0'))" + " ORDER BY pathname"); + while( db_step(&loop)==SQLITE_ROW ){ + const char *zToAdd = db_column_text(&loop, 0); + if( fossil_strcmp(zToAdd, zRepo)==0 ) continue; + if( strchr(zToAdd,'/') ){ + if( file_is_reserved_name(zToAdd, -1) ) continue; + }else{ + for(i=0; (zReserved = fossil_reserved_name(i, 0))!=0; i++){ + if( xCmp(zToAdd, zReserved)==0 ) break; + } + if( zReserved ) continue; + } + nAdd += add_one_file(zToAdd, vid); + } + db_finalize(&loop); + blob_reset(&repoName); + return nAdd; +} + +/* +** Resets the ADDED/DELETED state of a checkout, such that all +** newly-added (but not yet committed) files are no longer added and +** newly-removed (but not yet committed) files are no longer +** removed. If bIsAdd is true, it operates on the "add" state, else it +** operates on the "rm" state. +** +** If bDryRun is true it outputs what it would have done, but does not +** actually do it. In this case it rolls back the transaction it +** starts (so don't start a transaction before calling this). +** +** If bVerbose is true it outputs the name of each reset entry. +** +** This is intended to be called only in the context of the +** add/rm/addremove commands, after a call to verify_all_options(). +** +** Un-added files are not modified but any un-rm'd files which are +** missing from the checkout are restored from the repo. un-rm'd files +** which exist in the checkout are left as-is, rather than restoring +** them using vfile_to_disk(), to avoid overwriting any local changes +** made to those files. +*/ +static void addremove_reset(int bIsAdd, int bDryRun, int bVerbose){ + int nReset = 0; /* # of entries which get reset */ + Stmt stmt; /* vfile loop query */ + + db_begin_transaction(); + db_prepare(&stmt, "SELECT id, pathname FROM vfile " + "WHERE %s ORDER BY pathname", + bIsAdd==0 ? "deleted<>0" : "rid=0"/*safe-for-%s*/); + while( db_step(&stmt)==SQLITE_ROW ){ + /* This loop exists only so we can restore the contents of un-rm'd + ** files and support verbose mode. All manipulation of vfile's + ** contents happens after the loop. For the ADD case in non-verbose + ** mode we "could" skip this loop entirely. + */ + int const id = db_column_int(&stmt, 0); + char const * zPathname = db_column_text(&stmt, 1); + Blob relName = empty_blob; + if(bIsAdd==0 || bVerbose!=0){ + /* Make filename relative... */ + char *zFullName = mprintf("%s%s", g.zLocalRoot, zPathname); + file_relative_name(zFullName, &relName, 0); + fossil_free(zFullName); + } + if(bIsAdd==0){ + /* Restore contents of missing un-rm'd files. We don't do this + ** unconditionally because we might cause data loss if a file + ** is modified, rm'd, then un-rm'd. + */ + ++nReset; + if(!file_isfile_or_link(blob_str(&relName))){ + if(bDryRun==0){ + vfile_to_disk(0, id, 0, 0); + if(bVerbose){ + fossil_print("Restored missing file: %b\n", &relName); + } + }else{ + fossil_print("Dry-run: not restoring missing file: %b\n", &relName); + } + } + if(bVerbose){ + fossil_print("Un-removed: %b\n", &relName); + } + }else{ + /* un-add... */ + ++nReset; + if(bVerbose){ + fossil_print("Un-added: %b\n", &relName); + } + } + blob_reset(&relName); + } + db_finalize(&stmt); + if(nReset>0){ + if(bIsAdd==0){ + if(bDryRun==0){ + db_exec_sql("UPDATE vfile SET deleted=0 WHERE deleted<>0"); + } + fossil_print("Un-removed %d file(s).\n", nReset); + }else{ + if(bDryRun==0){ + db_exec_sql("DELETE FROM vfile WHERE rid=0"); + } + fossil_print("Un-added %d file(s).\n", nReset); + } + } + db_end_transaction(bDryRun ? 1 : 0); +} + /* ** COMMAND: add ** -** Usage: %fossil add FILE... -** -** Make arrangements to add one or more files to the current checkout -** at the next commit. -** -** When adding files recursively, filenames that begin with "." are -** excluded by default. To include such files, add the "--dotfiles" -** option to the command-line. +** Usage: %fossil add ?OPTIONS? FILE1 ?FILE2 ...? +** +** Make arrangements to add one or more files or directories to the +** current checkout at the next [[commit]]. +** +** When adding files or directories recursively, filenames that begin +** with "." are excluded by default. To include such files, add +** the "--dotfiles" option to the command-line. +** +** The --ignore and --clean options are comma-separated lists of glob patterns +** for files to be excluded. Example: '*.o,*.obj,*.exe' If the --ignore +** option does not appear on the command line then the "ignore-glob" setting +** is used. If the --clean option does not appear on the command line then +** the "clean-glob" setting is used. +** +** If files are attempted to be added explicitly on the command line which +** match "ignore-glob", a confirmation is asked first. This can be prevented +** using the -f|--force option. +** +** The --case-sensitive option determines whether or not filenames should +** be treated case sensitive or not. If the option is not given, the default +** depends on the global setting, or the operating system default, if not set. +** +** Options: +** +** --case-sensitive BOOL Override the case-sensitive setting +** --dotfiles Include files beginning with a dot (".") +** -f|--force Add files without prompting +** --ignore CSG Ignore unmanaged files matching patterns from +** the Comma Separated Glob (CSG) pattern list +** --clean CSG Also ignore files matching patterns from +** the Comma Separated Glob (CSG) list +** --reset Reset the ADDED state of a checkout, such +** that all newly-added (but not yet committed) +** files are no longer added. No flags other +** than --verbose and --dry-run may be used +** with --reset. +** --allow-reserved Permit filenames which are reserved on +** Windows platforms. Such files cannot be +** checked out on Windows, so use with care. +** +** The following options are only valid with --reset: +** -v|--verbose Output information about each --reset file +** -n|--dry-run Display instead of run actions +** +** See also: [[addremove]], [[rm]] */ void add_cmd(void){ - int i; - int vid; - Blob repo; + int i; /* Loop counter */ + int vid; /* Currently checked out version */ + int nRoot; /* Full path characters in g.zLocalRoot */ + const char *zCleanFlag; /* The --clean option or clean-glob setting */ + const char *zIgnoreFlag; /* The --ignore option or ignore-glob setting */ + Glob *pIgnore, *pClean; /* Ignore everything matching the glob patterns */ + unsigned scanFlags = 0; /* Flags passed to vfile_scan() */ + int forceFlag; + int allowReservedFlag = 0; /* --allow-reserved flag */ + + if(0!=find_option("reset",0,0)){ + int const verboseFlag = find_option("verbose","v",0)!=0; + int const dryRunFlag = find_option("dry-run","n",0)!=0; + db_must_be_within_tree(); + verify_all_options(); + addremove_reset(1, dryRunFlag, verboseFlag); + return; + } + + zCleanFlag = find_option("clean",0,1); + zIgnoreFlag = find_option("ignore",0,1); + forceFlag = find_option("force","f",0)!=0; + if( find_option("dotfiles",0,0)!=0 ) scanFlags |= SCAN_ALL; + allowReservedFlag = find_option("allow-reserved",0,0)!=0; - includeDotFiles = find_option("dotfiles",0,0)!=0; + /* We should be done with options.. */ + verify_all_options(); + db_must_be_within_tree(); - vid = db_lget_int("checkout",0); - if( vid==0 ){ - fossil_panic("no checkout to add to"); + if( zCleanFlag==0 ){ + zCleanFlag = db_get("clean-glob", 0); + } + if( zIgnoreFlag==0 ){ + zIgnoreFlag = db_get("ignore-glob", 0); } + if( db_get_boolean("dotfiles", 0) ) scanFlags |= SCAN_ALL; + vid = db_lget_int("checkout",0); db_begin_transaction(); - if( !file_tree_name(g.zRepositoryName, &repo, 0) ){ - blob_zero(&repo); - } - db_multi_exec("CREATE TEMP TABLE sfile(x TEXT PRIMARY KEY)"); -#ifdef __MINGW32__ - db_multi_exec( - "CREATE INDEX IF NOT EXISTS vfile_pathname " - " ON vfile(pathname COLLATE nocase)" - ); -#endif + db_multi_exec("CREATE TEMP TABLE sfile(pathname TEXT PRIMARY KEY %s)", + filename_collation()); + pClean = glob_create(zCleanFlag); + pIgnore = glob_create(zIgnoreFlag); + nRoot = strlen(g.zLocalRoot); + + /* Load the names of all files that are to be added into sfile temp table */ for(i=2; i<g.argc; i++){ char *zName; int isDir; + Blob fullName = empty_blob; - zName = mprintf("%/", g.argv[i]); - isDir = file_isdir(zName); + /* file_tree_name() throws a fatal error if g.argv[i] is outside of the + ** checkout. */ + file_tree_name(g.argv[i], &fullName, 0, 1); + blob_reset(&fullName); + file_canonical_name(g.argv[i], &fullName, 0); + zName = blob_str(&fullName); + isDir = file_isdir(zName, RepoFILE); if( isDir==1 ){ - add_directory(zName, vid, &repo); + vfile_scan(&fullName, nRoot-1, scanFlags, pClean, pIgnore, RepoFILE); }else if( isDir==0 ){ - fossil_fatal("not found: %s", zName); - }else if( access(zName, R_OK) ){ - fossil_fatal("cannot open %s", zName); + fossil_warning("not found: %s", zName); }else{ - add_one_file(zName, vid, &repo); + char *zTreeName = &zName[nRoot]; + if( !forceFlag && glob_match(pIgnore, zTreeName) ){ + Blob ans; + char cReply; + char *prompt = mprintf("file \"%s\" matches \"ignore-glob\". " + "Add it (a=all/y/N)? ", zTreeName); + prompt_user(prompt, &ans); + fossil_free(prompt); + cReply = blob_str(&ans)[0]; + blob_reset(&ans); + if( cReply=='a' || cReply=='A' ){ + forceFlag = 1; + }else if( cReply!='y' && cReply!='Y' ){ + blob_reset(&fullName); + continue; + } + } + db_multi_exec( + "INSERT OR IGNORE INTO sfile(pathname) VALUES(%Q)", + zTreeName + ); + } + blob_reset(&fullName); + } + glob_free(pIgnore); + glob_free(pClean); + + /** Check for Windows-reserved names and warn or exit, as + ** appopriate. Note that the 'add' internal machinery already + ** _silently_ skips over any names for which + ** file_is_reserved_name() returns true or which is in the + ** fossil_reserved_name() list. We do not need to warn for those, + ** as they're outright verboten. */ + if(db_exists("SELECT 1 FROM sfile WHERE win_reserved(pathname)")){ + int reservedCount = 0; + Stmt q = empty_Stmt; + db_prepare(&q,"SELECT pathname FROM sfile " + "WHERE win_reserved(pathname)"); + while( db_step(&q)==SQLITE_ROW ){ + const char * zName = db_column_text(&q, 0); + ++reservedCount; + if(allowReservedFlag){ + fossil_warning("WARNING: Windows-reserved " + "filename: %s", zName); + }else{ + fossil_warning("ERROR: Windows-reserved filename: %s", zName); + } + } + db_finalize(&q); + if(allowReservedFlag==0){ + fossil_fatal("ERROR: %d Windows-reserved filename(s) added. " + "Use --allow-reserved to permit such names.", + reservedCount); } - free(zName); } + add_files_in_sfile(vid); db_end_transaction(0); } /* -** Remove all contents of zDir -*/ -void del_directory_content(const char *zDir){ - DIR *d; - int origSize; - struct dirent *pEntry; - Blob path; - - blob_zero(&path); - blob_append(&path, zDir, -1); - origSize = blob_size(&path); - d = opendir(zDir); - if( d ){ - while( (pEntry=readdir(d))!=0 ){ - char *zPath; - if( pEntry->d_name[0]=='.'){ - if( !includeDotFiles ) continue; - if( pEntry->d_name[1]==0 ) continue; - if( pEntry->d_name[1]=='.' && pEntry->d_name[2]==0 ) continue; - } - blob_appendf(&path, "/%s", pEntry->d_name); - zPath = blob_str(&path); - if( file_isdir(zPath)==1 ){ - del_directory_content(zPath); - }else if( file_isfile(zPath) ){ - char *zFilePath; - Blob pathname; - file_tree_name(zPath, &pathname, 1); - zFilePath = blob_str(&pathname); - if( !db_exists( - "SELECT 1 FROM vfile WHERE pathname=%Q AND NOT deleted", zFilePath) - ){ - printf("SKIPPED %s\n", zPath); - }else{ - db_multi_exec("UPDATE vfile SET deleted=1 WHERE pathname=%Q", zPath); - printf("DELETED %s\n", zPath); - } - blob_reset(&pathname); - } - blob_resize(&path, origSize); - } - } - closedir(d); - blob_reset(&path); +** This function adds a file to list of files to delete from disk after +** the other actions required for the parent operation have completed +** successfully. The first time it is called for the current process, +** it creates a temporary table named "fremove", to keep track of these +** files. +*/ +static void add_file_to_remove( + const char *zOldName /* The old name of the file on disk. */ +){ + static int tableCreated = 0; + Blob fullOldName; + if( !tableCreated ){ + db_multi_exec("CREATE TEMP TABLE fremove(x TEXT PRIMARY KEY %s)", + filename_collation()); + tableCreated = 1; + } + file_tree_name(zOldName, &fullOldName, 1, 1); + db_multi_exec("INSERT INTO fremove VALUES('%q');", blob_str(&fullOldName)); + blob_reset(&fullOldName); +} + +/* +** This function deletes files from the checkout, using the file names +** contained in the temporary table "fremove". The temporary table is +** created on demand by the add_file_to_remove() function. +** +** If dryRunFlag is non-zero, no files will be removed; however, their +** names will still be output. +** +** The temporary table "fremove" is dropped after being processed. +*/ +static void process_files_to_remove( + int dryRunFlag /* Zero to actually operate on the file-system. */ +){ + Stmt remove; + if( db_table_exists("temp", "fremove") ){ + db_prepare(&remove, "SELECT x FROM fremove ORDER BY x;"); + while( db_step(&remove)==SQLITE_ROW ){ + const char *zOldName = db_column_text(&remove, 0); + if( !dryRunFlag ){ + file_delete(zOldName); + } + fossil_print("DELETED_FILE %s\n", zOldName); + } + db_finalize(&remove); + db_multi_exec("DROP TABLE fremove;"); + } } /* ** COMMAND: rm -** COMMAND: del -** -** Usage: %fossil rm FILE... -** or: %fossil del FILE... -** -** Remove one or more files from the tree. -** -** This command does not remove the files from disk. It just marks the -** files as no longer being part of the project. In other words, future -** changes to the named files will not be versioned. +** COMMAND: delete +** COMMAND: forget* +** +** Usage: %fossil rm|delete|forget FILE1 ?FILE2 ...? +** +** Remove one or more files or directories from the repository. +** +** The 'rm' and 'delete' commands do NOT normally remove the files from +** disk. They just mark the files as no longer being part of the project. +** In other words, future changes to the named files will not be versioned. +** However, the default behavior of this command may be overridden via the +** command line options listed below and/or the 'mv-rm-files' setting. +** +** The 'forget' command never removes files from disk, even when the command +** line options and/or the 'mv-rm-files' setting would otherwise require it +** to do so. +** +** WARNING: If the "--hard" option is specified -OR- the "mv-rm-files" +** setting is non-zero, files WILL BE removed from disk as well. +** This does NOT apply to the 'forget' command. +** +** Options: +** --soft Skip removing files from the checkout. +** This supersedes the --hard option. +** --hard Remove files from the checkout. +** --case-sensitive BOOL Override the case-sensitive setting. +** -n|--dry-run If given, display instead of run actions. +** --reset Reset the DELETED state of a checkout, such +** that all newly-rm'd (but not yet committed) +** files are no longer removed. No flags other +** than --verbose or --dry-run may be used with +** --reset. +** -v|--verbose Outputs information about each --reset file. +** Only usable with --reset. +** +** See also: [[addremove]], [[add]] */ -void del_cmd(void){ +void delete_cmd(void){ int i; - int vid; + int removeFiles; + int dryRunFlag = find_option("dry-run","n",0)!=0; + int softFlag; + int hardFlag; + Stmt loop; + + if(0!=find_option("reset",0,0)){ + int const verboseFlag = find_option("verbose","v",0)!=0; + db_must_be_within_tree(); + verify_all_options(); + addremove_reset(0, dryRunFlag, verboseFlag); + return; + } + + softFlag = find_option("soft",0,0)!=0; + hardFlag = find_option("hard",0,0)!=0; + + /* We should be done with options.. */ + verify_all_options(); db_must_be_within_tree(); - vid = db_lget_int("checkout", 0); - if( vid==0 ){ - fossil_panic("no checkout to remove from"); - } db_begin_transaction(); + if( g.argv[1][0]=='f' ){ /* i.e. "forget" */ + removeFiles = 0; + }else if( softFlag ){ + removeFiles = 0; + }else if( hardFlag ){ + removeFiles = 1; + }else{ + removeFiles = db_get_boolean("mv-rm-files",0); + } + db_multi_exec("CREATE TEMP TABLE sfile(pathname TEXT PRIMARY KEY %s)", + filename_collation()); for(i=2; i<g.argc; i++){ - char *zName; - - zName = mprintf("%/", g.argv[i]); - if( file_isdir(zName) == 1 ){ - del_directory_content(zName); - } else { - char *zPath; - Blob pathname; - file_tree_name(zName, &pathname, 1); - zPath = blob_str(&pathname); - if( !db_exists( - "SELECT 1 FROM vfile WHERE pathname=%Q AND NOT deleted", zPath) ){ - fossil_fatal("not in the repository: %s", zName); - }else{ - db_multi_exec("UPDATE vfile SET deleted=1 WHERE pathname=%Q", zPath); - printf("DELETED %s\n", zPath); - } - blob_reset(&pathname); - } - free(zName); - } - db_multi_exec("DELETE FROM vfile WHERE deleted AND rid=0"); + Blob treeName; + char *zTreeName; + + file_tree_name(g.argv[i], &treeName, 0, 1); + zTreeName = blob_str(&treeName); + db_multi_exec( + "INSERT OR IGNORE INTO sfile" + " SELECT pathname FROM vfile" + " WHERE (pathname=%Q %s" + " OR (pathname>'%q/' %s AND pathname<'%q0' %s))" + " AND NOT deleted", + zTreeName, filename_collation(), zTreeName, + filename_collation(), zTreeName, filename_collation() + ); + blob_reset(&treeName); + } + + db_prepare(&loop, "SELECT pathname FROM sfile"); + while( db_step(&loop)==SQLITE_ROW ){ + fossil_print("DELETED %s\n", db_column_text(&loop, 0)); + if( removeFiles ) add_file_to_remove(db_column_text(&loop, 0)); + } + db_finalize(&loop); + if( !dryRunFlag ){ + db_multi_exec( + "UPDATE vfile SET deleted=1 WHERE pathname IN sfile;" + "DELETE FROM vfile WHERE rid=0 AND deleted;" + ); + } db_end_transaction(0); + if( removeFiles ) process_files_to_remove(dryRunFlag); +} + +/* +** Capture the command-line --case-sensitive option. +*/ +static const char *zCaseSensitive = 0; +void capture_case_sensitive_option(void){ + if( zCaseSensitive==0 ){ + zCaseSensitive = find_option("case-sensitive",0,1); + } +} + +/* +** This routine determines if files should be case-sensitive or not. +** In other words, this routine determines if two filenames that +** differ only in case should be considered the same name or not. +** +** The case-sensitive setting determines the default value. If +** the case-sensitive setting is undefined, then case sensitivity +** defaults off for Cygwin, Mac and Windows and on for all other unix. +** If case-sensitivity is enabled in the windows kernel, the Cygwin port +** of fossil.exe can detect that, and modifies the default to 'on'. +** +** The "--case-sensitive BOOL" command-line option overrides any +** setting. +*/ +int filenames_are_case_sensitive(void){ + static int caseSensitive; + static int once = 1; + + if( once ){ + once = 0; + if( zCaseSensitive ){ + caseSensitive = is_truth(zCaseSensitive); + }else{ +#if defined(_WIN32) || defined(__DARWIN__) || defined(__APPLE__) + caseSensitive = 0; /* Mac and Windows */ +#elif defined(__CYGWIN__) + /* Cygwin can be configured to be case-sensitive, check this. */ + void *hKey; + int value = 1, length = sizeof(int); + caseSensitive = 0; /* Cygwin default */ + if( (RegOpenKeyExW((void *)0x80000002, L"SYSTEM\\CurrentControlSet\\" + "Control\\Session Manager\\kernel", 0, 1, (void *)&hKey) + == 0) && (RegQueryValueExW(hKey, L"obcaseinsensitive", + 0, NULL, (void *)&value, (void *)&length) == 0) && !value ){ + caseSensitive = 1; + } +#else + caseSensitive = 1; /* Unix */ +#endif + caseSensitive = db_get_boolean("case-sensitive",caseSensitive); + } + if( !caseSensitive && g.localOpen ){ + db_multi_exec( + "CREATE INDEX IF NOT EXISTS localdb.vfile_nocase" + " ON vfile(pathname COLLATE nocase)" + ); + } + } + return caseSensitive; +} + +/* +** Return one of two things: +** +** "" (empty string) if filenames are case sensitive +** +** "COLLATE nocase" if filenames are not case sensitive. +*/ +const char *filename_collation(void){ + return filenames_are_case_sensitive() ? "" : "COLLATE nocase"; +} + +/* +** COMMAND: addremove +** +** Usage: %fossil addremove ?OPTIONS? +** +** Do all necessary "[[add]]" and "[[rm]]" commands to synchronize the +** repository with the content of the working checkout: +** +** * All files in the checkout but not in the repository (that is, +** all files displayed using the "extras" command) are added as +** if by the "[[add]]" command. +** +** * All files in the repository but missing from the checkout (that is, +** all files that show as MISSING with the "status" command) are +** removed as if by the "[[rm]]" command. +** +** The command does not "[[commit]]". You must run the "[[commit]]" separately +** as a separate step. +** +** Files and directories whose names begin with "." are ignored unless +** the --dotfiles option is used. +** +** The --ignore option overrides the "ignore-glob" setting, as do the +** --case-sensitive option with the "case-sensitive" setting and the +** --clean option with the "clean-glob" setting. See the documentation +** on the "settings" command for further information. +** +** The -n|--dry-run option shows what would happen without actually doing +** anything. +** +** This command can be used to track third party software. +** +** Options: +** --case-sensitive BOOL Override the case-sensitive setting. +** --dotfiles Include files beginning with a dot (".") +** --ignore CSG Ignore unmanaged files matching patterns from +** the Comma Separated Glob (CSG) list +** --clean CSG Also ignore files matching patterns from +** the Comma Separated Glob (CSG) list +** -n|--dry-run If given, display instead of run actions. +** --reset Reset the ADDED/DELETED state of a checkout, +** such that all newly-added (but not yet committed) +** files are no longer added and all newly-removed +** (but not yet committed) files are no longer +** removed. No flags other than --verbose and +** --dry-run may be used with --reset. +** -v|--verbose Outputs information about each --reset file. +** Only usable with --reset. +** +** See also: [[add]], [[rm]] +*/ +void addremove_cmd(void){ + Blob path; + const char *zCleanFlag; + const char *zIgnoreFlag; + unsigned scanFlags; + int dryRunFlag = find_option("dry-run","n",0)!=0; + int n; + Stmt q; + int vid; + int nAdd = 0; + int nDelete = 0; + Glob *pIgnore, *pClean; + + if( !dryRunFlag ){ + dryRunFlag = find_option("test",0,0)!=0; /* deprecated */ + } + + if(0!=find_option("reset",0,0)){ + int const verboseFlag = find_option("verbose","v",0)!=0; + db_must_be_within_tree(); + verify_all_options(); + addremove_reset(0, dryRunFlag, verboseFlag); + addremove_reset(1, dryRunFlag, verboseFlag); + return; + } + + zCleanFlag = find_option("clean",0,1); + zIgnoreFlag = find_option("ignore",0,1); + scanFlags = find_option("dotfiles",0,0)!=0 ? SCAN_ALL : 0; + + /* We should be done with options.. */ + verify_all_options(); + + /* Fail if unprocessed arguments are present, in case user expect the + ** addremove command to accept a list of file or directory. + */ + if( g.argc>2 ){ + fossil_fatal( + "%s: Can only work on the entire checkout, no arguments supported.", + g.argv[1]); + } + db_must_be_within_tree(); + if( zCleanFlag==0 ){ + zCleanFlag = db_get("clean-glob", 0); + } + if( zIgnoreFlag==0 ){ + zIgnoreFlag = db_get("ignore-glob", 0); + } + if( db_get_boolean("dotfiles", 0) ) scanFlags |= SCAN_ALL; + vid = db_lget_int("checkout",0); + db_begin_transaction(); + + /* step 1: + ** Populate the temp table "sfile" with the names of all unmanaged + ** files currently in the check-out, except for files that match the + ** --ignore or ignore-glob patterns and dot-files. Then add all of + ** the files in the sfile temp table to the set of managed files. + */ + db_multi_exec("CREATE TEMP TABLE sfile(pathname TEXT PRIMARY KEY %s)", + filename_collation()); + n = strlen(g.zLocalRoot); + blob_init(&path, g.zLocalRoot, n-1); + /* now we read the complete file structure into a temp table */ + pClean = glob_create(zCleanFlag); + pIgnore = glob_create(zIgnoreFlag); + vfile_scan(&path, blob_size(&path), scanFlags, pClean, pIgnore, RepoFILE); + glob_free(pIgnore); + glob_free(pClean); + nAdd = add_files_in_sfile(vid); + + /* step 2: search for missing files */ + db_prepare(&q, + "SELECT pathname, %Q || pathname, deleted FROM vfile" + " WHERE NOT deleted" + " ORDER BY 1", + g.zLocalRoot + ); + while( db_step(&q)==SQLITE_ROW ){ + const char *zFile; + const char *zPath; + + zFile = db_column_text(&q, 0); + zPath = db_column_text(&q, 1); + if( !file_isfile_or_link(zPath) ){ + if( !dryRunFlag ){ + db_multi_exec("UPDATE vfile SET deleted=1 WHERE pathname=%Q", zFile); + } + fossil_print("DELETED %s\n", zFile); + nDelete++; + } + } + db_finalize(&q); + /* show command summary */ + fossil_print("added %d files, deleted %d files\n", nAdd, nDelete); + + db_end_transaction(dryRunFlag); } /* ** Rename a single file. ** ** The original name of the file is zOrig. The new filename is zNew. */ -static void mv_one_file(int vid, const char *zOrig, const char *zNew){ - printf("RENAME %s %s\n", zOrig, zNew); - db_multi_exec( - "UPDATE vfile SET pathname='%s' WHERE pathname='%s' AND vid=%d", - zNew, zOrig, vid - ); +static void mv_one_file( + int vid, + const char *zOrig, + const char *zNew, + int dryRunFlag +){ + int x = db_int(-1, "SELECT deleted FROM vfile WHERE pathname=%Q %s", + zNew, filename_collation()); + if( x>=0 ){ + if( x==0 ){ + if( !filenames_are_case_sensitive() && fossil_stricmp(zOrig,zNew)==0 ){ + /* Case change only */ + }else{ + fossil_fatal("cannot rename '%s' to '%s' since another file named '%s'" + " is currently under management", zOrig, zNew, zNew); + } + }else{ + fossil_fatal("cannot rename '%s' to '%s' since the delete of '%s' has " + "not yet been committed", zOrig, zNew, zNew); + } + } + fossil_print("RENAME %s %s\n", zOrig, zNew); + if( !dryRunFlag ){ + db_multi_exec( + "UPDATE vfile SET pathname='%q' WHERE pathname='%q' %s AND vid=%d", + zNew, zOrig, filename_collation(), vid + ); + } +} + +/* +** This function adds a file to list of files to move on disk after the +** other actions required for the parent operation have completed +** successfully. The first time it is called for the current process, +** it creates a temporary table named "fmove", to keep track of these +** files. +*/ +static void add_file_to_move( + const char *zOldName, /* The old name of the file on disk. */ + const char *zNewName /* The new name of the file on disk. */ +){ + static int tableCreated = 0; + Blob fullOldName; + Blob fullNewName; + char *zOld, *zNew; + if( !tableCreated ){ + db_multi_exec("CREATE TEMP TABLE fmove(x TEXT PRIMARY KEY %s, y TEXT %s)", + filename_collation(), filename_collation()); + tableCreated = 1; + } + file_tree_name(zOldName, &fullOldName, 1, 1); + zOld = blob_str(&fullOldName); + file_tree_name(zNewName, &fullNewName, 1, 1); + zNew = blob_str(&fullNewName); + if( filenames_are_case_sensitive() || fossil_stricmp(zOld,zNew)!=0 ){ + db_multi_exec("INSERT INTO fmove VALUES('%q','%q');", zOld, zNew); + } + blob_reset(&fullNewName); + blob_reset(&fullOldName); +} + +/* +** This function moves files within the checkout, using the file names +** contained in the temporary table "fmove". The temporary table is +** created on demand by the add_file_to_move() function. +** +** If dryRunFlag is non-zero, no files will be moved; however, their +** names will still be output. +** +** The temporary table "fmove" is dropped after being processed. +*/ +static void process_files_to_move( + int dryRunFlag /* Zero to actually operate on the file-system. */ +){ + Stmt move; + if( db_table_exists("temp", "fmove") ){ + db_prepare(&move, "SELECT x, y FROM fmove ORDER BY x;"); + while( db_step(&move)==SQLITE_ROW ){ + const char *zOldName = db_column_text(&move, 0); + const char *zNewName = db_column_text(&move, 1); + if( !dryRunFlag ){ + int isOldDir = file_isdir(zOldName, RepoFILE); + if( isOldDir==1 ){ + int isNewDir = file_isdir(zNewName, RepoFILE); + if( isNewDir==0 ){ + file_rename(zOldName, zNewName, isOldDir, isNewDir); + } + }else{ + if( file_islink(zOldName) ){ + symlink_copy(zOldName, zNewName); + }else{ + file_copy(zOldName, zNewName); + } + file_delete(zOldName); + } + } + fossil_print("MOVED_FILE %s\n", zOldName); + } + db_finalize(&move); + db_multi_exec("DROP TABLE fmove;"); + } } /* ** COMMAND: mv -** COMMAND: rename +** COMMAND: rename* ** ** Usage: %fossil mv|rename OLDNAME NEWNAME ** or: %fossil mv|rename OLDNAME... DIR ** -** Move or rename one or more files within the tree +** Move or rename one or more files or directories within the repository tree. +** You can either rename a file or directory or move it to another subdirectory. +** +** The 'mv' command does NOT normally rename or move the files on disk. +** This command merely records the fact that file names have changed so +** that appropriate notations can be made at the next [[commit]]. +** However, the default behavior of this command may be overridden via +** command line options listed below and/or the 'mv-rm-files' setting. +** +** The 'rename' command never renames or moves files on disk, even when the +** command line options and/or the 'mv-rm-files' setting would otherwise +** require it to do so. +** +** WARNING: If the "--hard" option is specified -OR- the "mv-rm-files" +** setting is non-zero, files WILL BE renamed or moved on disk +** as well. This does NOT apply to the 'rename' command. +** +** Options: +** --soft Skip moving files within the checkout. +** This supersedes the --hard option. +** --hard Move files within the checkout +** --case-sensitive BOOL Override the case-sensitive setting +** -n|--dry-run If given, display instead of run actions ** -** This command does not rename the files on disk. This command merely -** records the fact that filenames have changed so that appropriate notations -** can be made at the next commit/checkin. +** See also: [[changes]], [[status]] */ void mv_cmd(void){ int i; int vid; + int moveFiles; + int dryRunFlag; + int softFlag; + int hardFlag; + int origType; + int destType; char *zDest; Blob dest; Stmt q; db_must_be_within_tree(); + dryRunFlag = find_option("dry-run","n",0)!=0; + softFlag = find_option("soft",0,0)!=0; + hardFlag = find_option("hard",0,0)!=0; + + /* We should be done with options.. */ + verify_all_options(); + vid = db_lget_int("checkout", 0); if( vid==0 ){ - fossil_panic("no checkout rename files in"); + fossil_fatal("no checkout in which to rename files"); } if( g.argc<4 ){ usage("OLDNAME NEWNAME"); } zDest = g.argv[g.argc-1]; db_begin_transaction(); - file_tree_name(zDest, &dest, 1); + if( g.argv[1][0]=='r' ){ /* i.e. "rename" */ + moveFiles = 0; + }else if( softFlag ){ + moveFiles = 0; + }else if( hardFlag ){ + moveFiles = 1; + }else{ + moveFiles = db_get_boolean("mv-rm-files",0); + } + file_tree_name(zDest, &dest, 0, 1); db_multi_exec( "UPDATE vfile SET origname=pathname WHERE origname IS NULL;" ); db_multi_exec( "CREATE TEMP TABLE mv(f TEXT UNIQUE ON CONFLICT IGNORE, t TEXT);" ); - if( file_isdir(zDest)!=1 ){ + if( g.argc!=4 ){ + origType = -1; + }else{ + origType = (file_isdir(g.argv[2], RepoFILE) == 1); + } + destType = file_isdir(zDest, RepoFILE); + if( origType==-1 && destType!=1 ){ + usage("OLDNAME NEWNAME"); + }else if( origType==1 && destType==2 ){ + fossil_fatal("cannot rename '%s' to '%s' since another file named" + " '%s' exists", g.argv[2], zDest, zDest); + }else if( origType==0 && destType!=1 ){ Blob orig; - if( g.argc!=4 ){ - usage("OLDNAME NEWNAME"); - } - file_tree_name(g.argv[2], &orig, 1); + file_tree_name(g.argv[2], &orig, 0, 1); db_multi_exec( "INSERT INTO mv VALUES(%B,%B)", &orig, &dest ); }else{ if( blob_eq(&dest, ".") ){ @@ -339,31 +1100,34 @@ } for(i=2; i<g.argc-1; i++){ Blob orig; char *zOrig; int nOrig; - file_tree_name(g.argv[i], &orig, 1); + file_tree_name(g.argv[i], &orig, 0, 1); zOrig = blob_str(&orig); nOrig = blob_size(&orig); db_prepare(&q, "SELECT pathname FROM vfile" " WHERE vid=%d" - " AND (pathname='%s' OR pathname GLOB '%s/*')" + " AND (pathname='%q' %s OR (pathname>'%q/' %s AND pathname<'%q0' %s))" " ORDER BY 1", - vid, zOrig, zOrig + vid, zOrig, filename_collation(), zOrig, filename_collation(), + zOrig, filename_collation() ); while( db_step(&q)==SQLITE_ROW ){ const char *zPath = db_column_text(&q, 0); int nPath = db_column_bytes(&q, 0); const char *zTail; if( nPath==nOrig ){ zTail = file_tail(zPath); + }else if( origType!=0 && destType==1 ){ + zTail = &zPath[nOrig-strlen(file_tail(zOrig))]; }else{ zTail = &zPath[nOrig+1]; } db_multi_exec( - "INSERT INTO mv VALUES('%s','%s%s')", + "INSERT INTO mv VALUES('%q','%q%q')", zPath, blob_str(&dest), zTail ); } db_finalize(&q); } @@ -370,10 +1134,21 @@ } db_prepare(&q, "SELECT f, t FROM mv ORDER BY f"); while( db_step(&q)==SQLITE_ROW ){ const char *zFrom = db_column_text(&q, 0); const char *zTo = db_column_text(&q, 1); - mv_one_file(vid, zFrom, zTo); + mv_one_file(vid, zFrom, zTo, dryRunFlag); + if( moveFiles ) add_file_to_move(zFrom, zTo); } db_finalize(&q); + undo_reset(); db_end_transaction(0); + if( moveFiles ) process_files_to_move(dryRunFlag); +} + +/* +** Function for stash_apply to be able to restore a file and indicate +** newly ADDED state. +*/ +int stash_add_files_in_sfile(int vid){ + return add_files_in_sfile(vid); } ADDED src/ajax.c Index: src/ajax.c ================================================================== --- /dev/null +++ src/ajax.c @@ -0,0 +1,406 @@ +/* +** Copyright (c) 2020 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 shared Ajax-related code for /fileedit, the wiki/forum +** editors, and friends. +*/ +#include "config.h" +#include "ajax.h" +#include <assert.h> +#include <stdarg.h> + +#if INTERFACE +/* enum ajax_render_preview_flags: */ +#define AJAX_PREVIEW_LINE_NUMBERS 1 +/* enum ajax_render_modes: */ +#define AJAX_RENDER_GUESS 0 /* Guess rendering mode based on mimetype. */ +/* GUESS must be 0. All others have unspecified values. */ +#define AJAX_RENDER_PLAIN_TEXT 1 /* Render as plain text. */ +#define AJAX_RENDER_HTML_IFRAME 2 /* Render as HTML inside an IFRAME. */ +#define AJAX_RENDER_HTML_INLINE 3 /* Render as HTML without an IFRAME. */ +#define AJAX_RENDER_WIKI 4 /* Render as wiki/markdown. */ +#endif + +/* +** Emits JS code which initializes the +** fossil.page.previewModes object to a map of AJAX_RENDER_xxx values +** and symbolic names for use by client-side scripts. +** +** If addScriptTag is true then the output is wrapped in a SCRIPT tag +** with the current nonce, else no SCRIPT tag is emitted. +** +** Requires that builtin_emit_script_fossil_bootstrap() has already been +** called in order to initialize the window.fossil.page object. +*/ +void ajax_emit_js_preview_modes(int addScriptTag){ + if(addScriptTag){ + style_script_begin(__FILE__,__LINE__); + } + CX("fossil.page.previewModes={" + "guess: %d, %d: 'guess', wiki: %d, %d: 'wiki'," + "htmlIframe: %d, %d: 'htmlIframe', " + "htmlInline: %d, %d: 'htmlInline', " + "text: %d, %d: 'text'" + "};\n", + AJAX_RENDER_GUESS, AJAX_RENDER_GUESS, + AJAX_RENDER_WIKI, AJAX_RENDER_WIKI, + AJAX_RENDER_HTML_IFRAME, AJAX_RENDER_HTML_IFRAME, + AJAX_RENDER_HTML_INLINE, AJAX_RENDER_HTML_INLINE, + AJAX_RENDER_PLAIN_TEXT, AJAX_RENDER_PLAIN_TEXT); + if(addScriptTag){ + style_script_end(); + } +} + +/* +** Returns a value from the ajax_render_modes enum, based on the +** given mimetype string (which may be NULL), defaulting to +** AJAX_RENDER_PLAIN_TEXT. + */ +int ajax_render_mode_for_mimetype(const char * zMimetype){ + int rc = AJAX_RENDER_PLAIN_TEXT; + if( zMimetype ){ + if( fossil_strcmp(zMimetype, "text/html")==0 ){ + rc = AJAX_RENDER_HTML_IFRAME; + }else if( fossil_strcmp(zMimetype, "text/x-fossil-wiki")==0 + || fossil_strcmp(zMimetype, "text/x-markdown")==0 ){ + rc = AJAX_RENDER_WIKI; + } + } + return rc; +} + +/* +** Renders text/wiki content preview for various /ajax routes. +** +** pContent is text/wiki content to preview. zName is the name of the +** content, for purposes of determining the mimetype based on the +** extension (if NULL, mimetype text/plain is assumed). flags may be a +** bitmask of values from the ajax_render_preview_flags +** enum. *renderMode must specify the render mode to use. If +** *renderMode==AJAX_RENDER_GUESS then *renderMode gets set to the +** mode which is guessed at for the rendering (based on the mimetype). +** +** nIframeHeightEm is only used for the AJAX_RENDER_HTML_IFRAME +** renderMode, and specifies the height, in EM's, of the resulting +** iframe. If passed 0, it defaults to "some sane value." +*/ +void ajax_render_preview(Blob * pContent, const char *zName, + int flags, int * renderMode, + int nIframeHeightEm){ + const char * zMime; + + zMime = zName ? mimetype_from_name(zName) : "text/plain"; + if(AJAX_RENDER_GUESS==*renderMode){ + *renderMode = ajax_render_mode_for_mimetype(zMime); + } + switch(*renderMode){ + case AJAX_RENDER_HTML_IFRAME:{ + char * z64 = encode64(blob_str(pContent), blob_size(pContent)); + CX("<iframe width='100%%' frameborder='0' " + "marginwidth='0' style='height:%dem' " + "marginheight='0' sandbox='allow-same-origin' " + "src='data:text/html;base64,%z'" + "></iframe>", + nIframeHeightEm ? nIframeHeightEm : 40, + z64); + break; + } + case AJAX_RENDER_HTML_INLINE:{ + CX("%b",pContent); + break; + } + case AJAX_RENDER_WIKI: + safe_html_context(DOCSRC_FILE); + wiki_render_by_mimetype(pContent, zMime); + break; + default:{ + const char *zContent = blob_str(pContent); + if(AJAX_PREVIEW_LINE_NUMBERS & flags){ + output_text_with_line_numbers(zContent, blob_size(pContent), + zName, "on", 0); + }else{ + const char *zExt = strrchr(zName,'.'); + if(zExt && zExt[1]){ + CX("<pre><code class='language-%s'>%h</code></pre>", + zExt+1, zContent); + }else{ + CX("<pre>%h</pre>", zContent); + } + } + break; + } + } +} + +/* +** Renders diffs for ajax routes. pOrig is the "original" (v1) content +** and pContent is the locally-edited (v2) content. diffFlags is any +** set of flags suitable for passing to text_diff(). +*/ +void ajax_render_diff(Blob * pOrig, Blob *pContent, u64 diffFlags){ + Blob out = empty_blob; + + text_diff(pOrig, pContent, &out, 0, diffFlags); + if(blob_size(&out)==0){ + /* nothing to do */ + }else if(DIFF_SIDEBYSIDE & diffFlags){ + CX("%b",&out); + }else{ + CX("<pre class='udiff'>%b</pre>",&out); + } + blob_reset(&out); +} + +/* +** Uses P(zKey) to fetch a CGI environment variable. If that var is +** NULL or starts with '0' or 'f' then this function returns false, +** else it returns true. +*/ +int ajax_p_bool(char const *zKey){ + const char * zVal = P(zKey); + return (!zVal || '0'==*zVal || 'f'==*zVal) ? 0 : 1; +} + +/* +** Helper for /ajax routes. Clears the CGI content buffer, sets an +** HTTP error status code, and queues up a JSON response in the form +** of an object: +** +** {error: formatted message} +** +** If httpCode<=0 then it defaults to 500. +** +** After calling this, the caller should immediately return. +*/ +void ajax_route_error(int httpCode, const char * zFmt, ...){ + Blob msg = empty_blob; + Blob content = empty_blob; + va_list vargs; + va_start(vargs,zFmt); + blob_vappendf(&msg, zFmt, vargs); + va_end(vargs); + blob_appendf(&content,"{\"error\":%!j}", blob_str(&msg)); + blob_reset(&msg); + cgi_set_content(&content); + cgi_set_status(httpCode>0 ? httpCode : 500, "Error"); + cgi_set_content_type("application/json"); +} + +/* +** Performs bootstrapping common to the /ajax/xyz AJAX routes, such as +** logging in the user. +** +** Returns false (0) if bootstrapping fails, in which case it has +** reported the error and the route should immediately return. Returns +** true on success. +** +** If requireWrite is true then write permissions are required. +** If requirePost is true then the request is assumed to be using +** POST'ed data and CSRF validation is performed. +** +*/ +int ajax_route_bootstrap(int requireWrite, int requirePost){ + login_check_credentials(); + if( requireWrite!=0 && g.perm.Write==0 ){ + ajax_route_error(403,"Write permissions required."); + return 0; + }else if(0==cgi_csrf_safe(requirePost)){ + ajax_route_error(403, + "CSRF violation (make sure sending of HTTP " + "Referer headers is enabled for XHR " + "connections)."); + return 0; + } + return 1; +} + +/* +** Helper for collecting filename/checkin request parameters. +** +** If zFn is not NULL, it is assigned the value of the first one of +** the "filename" or "fn" CGI parameters which is set. +** +** If zCi is not NULL, it is assigned the value of the first one of +** the "checkin" or "ci" CGI parameters which is set. +** +** If a parameter is not NULL, it will be assigned NULL if the +** corresponding parameter is not set. +** +** Returns the number of non-NULL values it assigns to arguments. Thus +** if passed (&x, NULL), it returns 1 if it assigns non-NULL to *x and +** 0 if it assigns NULL to *x. +*/ +int ajax_get_fnci_args( const char **zFn, const char **zCi ){ + int rc = 0; + if(zCi!=0){ + *zCi = PD("checkin",P("ci")); + if( *zCi ) ++rc; + } + if(zFn!=0){ + *zFn = PD("filename",P("fn")); + if (*zFn) ++rc; + } + return rc; +} + +/* +** AJAX route /ajax/preview-wiki +** +** Required query parameters: +** +** filename=name of content, for use in determining the +** mimetype/render mode. content=text +** +** Optional query parameters: +** +** render_mode=integer (AJAX_RENDER_xxx) (default=AJAX_RENDER_GUESS) +** +** ln=0 or 1 to disable/enable line number mode in +** AJAX_RENDER_PLAIN_TEXT mode. +** +** iframe_height=integer (default=40) Height, in EMs of HTML preview +** iframe. +** +** User must have Write access to use this page. +** +** Responds with the HTML content of the preview. On error it produces +** a JSON response as documented for ajax_route_error(). +** +** Extra response headers: +** +** x-ajax-render-mode: string representing the rendering mode +** which was really used (which will differ from the requested mode +** only if mode 0 (guess) was requested). The names are documented +** below in code and match those in the emitted JS object +** fossil.page.previewModes. +*/ +void ajax_route_preview_text(void){ + const char * zFilename = 0; + const char * zContent = P("content"); + int renderMode = atoi(PD("render_mode","0")); + int ln = atoi(PD("ln","0")); + int iframeHeight = atoi(PD("iframe_height","40")); + Blob content = empty_blob; + const char * zRenderMode = 0; + + ajax_get_fnci_args( &zFilename, 0 ); + + if(!ajax_route_bootstrap(1,1)){ + return; + } + if(zFilename==0){ + /* The filename is only used for mimetype determination, + ** so we can default it... */ + zFilename = "foo.txt"; + } + cgi_set_content_type("text/html"); + blob_init(&content, zContent, -1); + ajax_render_preview(&content, zFilename, + ln ? AJAX_PREVIEW_LINE_NUMBERS : 0, + &renderMode, iframeHeight); + /* + ** Now tell the caller if we did indeed use AJAX_RENDER_WIKI, so that + ** they can re-set the <base href> to an appropriate value (which + ** requires knowing the content's current checkin version, which we + ** don't have here). + */ + switch(renderMode){ + /* The strings used here MUST correspond to those used in the JS-side + ** fossil.page.previewModes map. + */ + case AJAX_RENDER_WIKI: zRenderMode = "wiki"; break; + case AJAX_RENDER_HTML_INLINE: zRenderMode = "htmlInline"; break; + case AJAX_RENDER_HTML_IFRAME: zRenderMode = "htmlIframe"; break; + case AJAX_RENDER_PLAIN_TEXT: zRenderMode = "text"; break; + case AJAX_RENDER_GUESS: + assert(!"cannot happen"); + } + if(zRenderMode!=0){ + cgi_printf_header("x-ajax-render-mode: %s\r\n", zRenderMode); + } +} + +#if INTERFACE +/* +** Internal mapping of ajax sub-route names to various metadata. +*/ +struct AjaxRoute { + const char *zName; /* Name part of the route after "ajax/" */ + void (*xCallback)(); /* Impl function for the route. */ + int bWriteMode; /* True if requires write mode */ + int bPost; /* True if requires POST (i.e. CSRF + ** verification) */ +}; +typedef struct AjaxRoute AjaxRoute; +#endif /*INTERFACE*/ + +/* +** Comparison function for bsearch() for searching an AjaxRoute +** list for a matching name. +*/ +int cmp_ajax_route_name(const void *a, const void *b){ + const AjaxRoute * rA = (const AjaxRoute*)a; + const AjaxRoute * rB = (const AjaxRoute*)b; + return fossil_strcmp(rA->zName, rB->zName); +} + +/* +** WEBPAGE: ajax +** +** The main dispatcher for shared ajax-served routes. Requires the +** 'name' parameter be the main route's name (as defined in a list in +** this function), noting that fossil automatically assigns all path +** parts after "ajax" to "name", e.g. /ajax/foo/bar assigns +** name=foo/bar. +** +** This "page" is only intended to be used by higher-level pages which +** have certain Ajax-driven features in common. It is not intended to +** be used by clients and NONE of its HTTP interfaces are considered +** documented/stable/supported - they may change on any given build of +** fossil. +** +** The exact response type depends on the route which gets called. In +** the case of an initialization error it emits a JSON-format response +** as documented for ajax_route_error(). Individual routes may emit +** errors in different formats, e.g. HTML. +*/ +void ajax_route_dispatcher(void){ + const char * zName = P("name"); + AjaxRoute routeName = {0,0,0,0}; + const AjaxRoute * pRoute = 0; + const AjaxRoute routes[] = { + /* Keep these sorted by zName (for bsearch()) */ + {"preview-text", ajax_route_preview_text, 1, 1} + }; + + if(zName==0 || zName[0]==0){ + ajax_route_error(400,"Missing required [route] 'name' parameter."); + return; + } + routeName.zName = zName; + pRoute = (const AjaxRoute *)bsearch(&routeName, routes, + count(routes), sizeof routes[0], + cmp_ajax_route_name); + if(pRoute==0){ + ajax_route_error(404,"Ajax route not found."); + return; + }else if(0==ajax_route_bootstrap(pRoute->bWriteMode, pRoute->bPost)){ + return; + } + pRoute->xCallback(); +} ADDED src/alerts.c Index: src/alerts.c ================================================================== --- /dev/null +++ src/alerts.c @@ -0,0 +1,3340 @@ +/* +** Copyright (c) 2018 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/ +** +******************************************************************************* +** +** Logic for email notification, also known as "alerts" or "subscriptions". +** +** Are you looking for the code that reads and writes the internet +** email protocol? That is not here. See the "smtp.c" file instead. +** Yes, the choice of source code filenames is not the greatest, but +** it is not so bad that changing them seems justified. +*/ +#include "config.h" +#include "alerts.h" +#include <assert.h> +#include <time.h> + +/* +** Maximum size of the subscriberCode blob, in bytes +*/ +#define SUBSCRIBER_CODE_SZ 32 + +/* +** SQL code to implement the tables needed by the email notification +** system. +*/ +static const char zAlertInit[] = +@ DROP TABLE IF EXISTS repository.subscriber; +@ -- Subscribers are distinct from users. A person can have a log-in in +@ -- the USER table without being a subscriber. Or a person can be a +@ -- subscriber without having a USER table entry. Or they can have both. +@ -- In the last case the suname column points from the subscriber entry +@ -- to the USER entry. +@ -- +@ -- The ssub field is a string where each character indicates a particular +@ -- type of event to subscribe to. Choices: +@ -- a - Announcements +@ -- c - Check-ins +@ -- f - Forum posts +@ -- t - Ticket changes +@ -- w - Wiki changes +@ -- Probably different codes will be added in the future. In the future +@ -- we might also add a separate table that allows subscribing to email +@ -- notifications for specific branches or tags or tickets. +@ -- +@ CREATE TABLE repository.subscriber( +@ subscriberId INTEGER PRIMARY KEY, -- numeric subscriber ID. Internal use +@ subscriberCode BLOB DEFAULT (randomblob(32)) UNIQUE, -- UUID for subscriber +@ semail TEXT UNIQUE COLLATE nocase,-- email address +@ suname TEXT, -- corresponding USER entry +@ sverified BOOLEAN DEFAULT true, -- email address verified +@ sdonotcall BOOLEAN, -- true for Do Not Call +@ sdigest BOOLEAN, -- true for daily digests only +@ ssub TEXT, -- baseline subscriptions +@ sctime INTDATE, -- When this entry was created. unixtime +@ mtime INTDATE, -- Last change. unixtime +@ smip TEXT, -- IP address of last change +@ lastContact INT -- Last contact. days since 1970 +@ ); +@ CREATE INDEX repository.subscriberUname +@ ON subscriber(suname) WHERE suname IS NOT NULL; +@ +@ DROP TABLE IF EXISTS repository.pending_alert; +@ -- Email notifications that need to be sent. +@ -- +@ -- The first character of the eventid determines the event type. +@ -- Remaining characters determine the specific event. For example, +@ -- 'c4413' means check-in with rid=4413. +@ -- +@ CREATE TABLE repository.pending_alert( +@ eventid TEXT PRIMARY KEY, -- Object that changed +@ sentSep BOOLEAN DEFAULT false, -- individual alert sent +@ sentDigest BOOLEAN DEFAULT false, -- digest alert sent +@ sentMod BOOLEAN DEFAULT false -- pending moderation alert sent +@ ) WITHOUT ROWID; +@ +@ -- Obsolete table. No longer used. +@ DROP TABLE IF EXISTS repository.alert_bounce; +; + +/* +** Return true if the email notification tables exist. +*/ +int alert_tables_exist(void){ + return db_table_exists("repository", "subscriber"); +} + +/* +** Record the fact that user zUser has made contact with the repository. +** This resets the subscription timeout on that user. +*/ +void alert_user_contact(const char *zUser){ + if( db_table_has_column("repository","subscriber","lastContact") ){ + db_multi_exec( + "UPDATE subscriber SET lastContact=now()/86400 WHERE suname=%Q", + zUser + ); + } +} + +/* +** Make sure the table needed for email notification exist in the repository. +** +** If the bOnlyIfEnabled option is true, then tables are only created +** if the email-send-method is something other than "off". +*/ +void alert_schema(int bOnlyIfEnabled){ + if( !alert_tables_exist() ){ + if( bOnlyIfEnabled + && fossil_strcmp(db_get("email-send-method",0),"off")==0 + ){ + return; /* Don't create table for disabled email */ + } + db_exec_sql(zAlertInit); + return; + } + if( db_table_has_column("repository","subscriber","lastContact") ){ + return; + } + db_multi_exec( + "DROP TABLE IF EXISTS repository.alert_bounde;\n" + "ALTER TABLE repository.subscriber ADD COLUMN lastContact INT;\n" + "UPDATE subscriber SET lastContact=mtime/86400;" + ); + if( db_table_has_column("repository","pending_alert","sentMod") ){ + return; + } + db_multi_exec( + "ALTER TABLE repository.pending_alert" + " ADD COLUMN sentMod BOOLEAN DEFAULT false;" + ); +} + +/* +** Enable triggers that automatically populate the pending_alert +** table. +*/ +void alert_create_trigger(void){ + if( !db_table_exists("repository","pending_alert") ) return; + db_multi_exec( + "DROP TRIGGER IF EXISTS repository.alert_trigger1;\n" /* Purge legacy */ + /* "DROP TRIGGER IF EXISTS repository.email_trigger1;\n" Very old legacy */ + "CREATE TRIGGER temp.alert_trigger1\n" + "AFTER INSERT ON repository.event BEGIN\n" + " INSERT INTO pending_alert(eventid)\n" + " SELECT printf('%%.1c%%d',new.type,new.objid) WHERE true\n" + " ON CONFLICT(eventId) DO NOTHING;\n" + "END;" + ); +} + +/* +** Disable triggers the event_pending triggers. +** +** This must be called before rebuilding the EVENT table, for example +** via the "fossil rebuild" command. +*/ +void alert_drop_trigger(void){ + db_multi_exec( + "DROP TRIGGER IF EXISTS temp.alert_trigger1;\n" + "DROP TRIGGER IF EXISTS repository.alert_trigger1;\n" /* Purge legacy */ + ); +} + +/* +** Return true if email alerts are active. +*/ +int alert_enabled(void){ + if( !alert_tables_exist() ) return 0; + if( fossil_strcmp(db_get("email-send-method",0),"off")==0 ) return 0; + return 1; +} + +/* +** If the subscriber table does not exist, then paint an error message +** web page and return true. +** +** If the subscriber table does exist, return 0 without doing anything. +*/ +static int alert_webpages_disabled(void){ + if( alert_tables_exist() ) return 0; + style_set_current_feature("alerts"); + style_header("Email Alerts Are Disabled"); + @ <p>Email alerts are disabled on this server</p> + style_finish_page(); + return 1; +} + +/* +** Insert a "Subscriber List" submenu link if the current user +** is an administrator. +*/ +void alert_submenu_common(void){ + if( g.perm.Admin ){ + if( fossil_strcmp(g.zPath,"subscribers") ){ + style_submenu_element("Subscribers","%R/subscribers"); + } + if( fossil_strcmp(g.zPath,"subscribe") ){ + style_submenu_element("Add New Subscriber","%R/subscribe"); + } + } +} + + +/* +** WEBPAGE: setup_notification +** +** Administrative page for configuring and controlling email notification. +** Normally accessible via the /Admin/Notification menu. +*/ +void setup_notification(void){ + static const char *const azSendMethods[] = { + "off", "Disabled", + "pipe", "Pipe to a command", + "db", "Store in a database", + "dir", "Store in a directory", + "relay", "SMTP relay" + }; + login_check_credentials(); + if( !g.perm.Setup ){ + login_needed(0); + return; + } + db_begin_transaction(); + + alert_submenu_common(); + style_submenu_element("Send Announcement","%R/announce"); + style_set_current_feature("alerts"); + style_header("Email Notification Setup"); + @ <h1>Status</h1> + @ <table class="label-value"> + if( alert_enabled() ){ + stats_for_email(); + }else{ + @ <th>Disabled</th> + } + @ </table> + @ <hr> + @ <h1> Configuration </h1> + @ <form action="%R/setup_notification" method="post"><div> + @ <input type="submit" name="submit" value="Apply Changes" /><hr> + login_insert_csrf_secret(); + + entry_attribute("Canonical Server URL", 40, "email-url", + "eurl", "", 0); + @ <p><b>Required.</b> + @ This is URL used as the basename for hyperlinks included in + @ email alert text. Omit the trailing "/". + @ Suggested value: "%h(g.zBaseURL)" + @ (Property: "email-url")</p> + @ <hr> + + entry_attribute("Administrator email address", 40, "email-admin", + "eadmin", "", 0); + @ <p>This is the email for the human administrator for the system. + @ Abuse and trouble reports and password reset requests are send here. + @ (Property: "email-admin")</p> + @ <hr> + + entry_attribute("\"Return-Path\" email address", 20, "email-self", + "eself", "", 0); + @ <p><b>Required.</b> + @ This is the email to which email notification bounces should be sent. + @ In cases where the email notification does not align with a specific + @ Fossil login account (for example, digest messages), this is also + @ the "From:" address of the email notification. + @ The system administrator should arrange for emails sent to this address + @ to be handed off to the "fossil email incoming" command so that Fossil + @ can handle bounces. (Property: "email-self")</p> + @ <hr> + + entry_attribute("Repository Nickname", 16, "email-subname", + "enn", "", 0); + @ <p><b>Required.</b> + @ This is short name used to identifies the repository in the + @ Subject: line of email alerts. Traditionally this name is + @ included in square brackets. Examples: "[fossil-src]", "[sqlite-src]". + @ (Property: "email-subname")</p> + @ <hr> + + entry_attribute("Subscription Renewal Interval In Days", 8, + "email-renew-interval", "eri", "", 0); + @ <p> + @ If this value is a integer N greater than or equal to 14, then email + @ notification subscriptions will be suspended N days after the last known + @ interaction with the user. This prevents sending notifications + @ to abandoned accounts. If a subscription comes within 7 days of expiring, + @ a separate email goes out with the daily digest that prompts the + @ subscriber to click on a link to the "/renew" webpage in order to + @ extend their subscription. Subscriptions never expire if this setting + @ is less than 14 or is an empty string. + @ (Property: "email-renew-interval")</p> + @ <hr> + + multiple_choice_attribute("Email Send Method", "email-send-method", "esm", + "off", count(azSendMethods)/2, azSendMethods); + @ <p>How to send email. Requires auxiliary information from the fields + @ that follow. Hint: Use the <a href="%R/announce">/announce</a> page + @ to send test message to debug this setting. + @ (Property: "email-send-method")</p> + alert_schema(1); + entry_attribute("Pipe Email Text Into This Command", 60, "email-send-command", + "ecmd", "sendmail -ti", 0); + @ <p>When the send method is "pipe to a command", this is the command + @ that is run. Email messages are piped into the standard input of this + @ command. The command is expected to extract the sender address, + @ recepient addresses, and subject from the header of the piped email + @ text. (Property: "email-send-command")</p> + + entry_attribute("Store Emails In This Database", 60, "email-send-db", + "esdb", "", 0); + @ <p>When the send method is "store in a database", each email message is + @ stored in an SQLite database file with the name given here. + @ (Property: "email-send-db")</p> + + entry_attribute("Store Emails In This Directory", 60, "email-send-dir", + "esdir", "", 0); + @ <p>When the send method is "store in a directory", each email message is + @ stored as a separate file in the directory shown here. + @ (Property: "email-send-dir")</p> + + entry_attribute("SMTP Relay Host", 60, "email-send-relayhost", + "esrh", "", 0); + @ <p>When the send method is "SMTP relay", each email message is + @ transmitted via the SMTP protocol (rfc5321) to a "Mail Submission + @ Agent" or "MSA" (rfc4409) at the hostname shown here. Optionally + @ append a colon and TCP port number (ex: smtp.example.com:587). + @ The default TCP port number is 25. + @ (Property: "email-send-relayhost")</p> + @ <hr> + + @ <p><input type="submit" name="submit" value="Apply Changes" /></p> + @ </div></form> + db_end_transaction(0); + style_finish_page(); +} + +#if 0 +/* +** Encode pMsg as MIME base64 and append it to pOut +*/ +static void append_base64(Blob *pOut, Blob *pMsg){ + int n, i, k; + char zBuf[100]; + n = blob_size(pMsg); + for(i=0; i<n; i+=54){ + k = translateBase64(blob_buffer(pMsg)+i, i+54<n ? 54 : n-i, zBuf); + blob_append(pOut, zBuf, k); + blob_append(pOut, "\r\n", 2); + } +} +#endif + +/* +** Encode pMsg using the quoted-printable email encoding and +** append it onto pOut +*/ +static void append_quoted(Blob *pOut, Blob *pMsg){ + char *zIn = blob_str(pMsg); + char c; + int iCol = 0; + while( (c = *(zIn++))!=0 ){ + if( (c>='!' && c<='~' && c!='=' && c!=':') + || (c==' ' && zIn[0]!='\r' && zIn[0]!='\n') + ){ + blob_append_char(pOut, c); + iCol++; + if( iCol>=70 ){ + blob_append(pOut, "=\r\n", 3); + iCol = 0; + } + }else if( c=='\r' && zIn[0]=='\n' ){ + zIn++; + blob_append(pOut, "\r\n", 2); + iCol = 0; + }else if( c=='\n' ){ + blob_append(pOut, "\r\n", 2); + iCol = 0; + }else{ + char x[3]; + x[0] = '='; + x[1] = "0123456789ABCDEF"[(c>>4)&0xf]; + x[2] = "0123456789ABCDEF"[c&0xf]; + blob_append(pOut, x, 3); + iCol += 3; + } + } +} + +#if INTERFACE +/* +** An instance of the following object is used to send emails. +*/ +struct AlertSender { + sqlite3 *db; /* Database emails are sent to */ + sqlite3_stmt *pStmt; /* Stmt to insert into the database */ + const char *zDest; /* How to send email. */ + const char *zDb; /* Name of database file */ + const char *zDir; /* Directory in which to store as email files */ + const char *zCmd; /* Command to run for each email */ + const char *zFrom; /* Emails come from here */ + SmtpSession *pSmtp; /* SMTP relay connection */ + Blob out; /* For zDest=="blob" */ + char *zErr; /* Error message */ + u32 mFlags; /* Flags */ + int bImmediateFail; /* On any error, call fossil_fatal() */ +}; + +/* Allowed values for mFlags to alert_sender_new(). +*/ +#define ALERT_IMMEDIATE_FAIL 0x0001 /* Call fossil_fatal() on any error */ +#define ALERT_TRACE 0x0002 /* Log sending process on console */ + +#endif /* INTERFACE */ + +/* +** Shutdown an emailer. Clear all information other than the error message. +*/ +static void emailerShutdown(AlertSender *p){ + sqlite3_finalize(p->pStmt); + p->pStmt = 0; + sqlite3_close(p->db); + p->db = 0; + p->zDb = 0; + p->zDir = 0; + p->zCmd = 0; + if( p->pSmtp ){ + smtp_client_quit(p->pSmtp); + smtp_session_free(p->pSmtp); + p->pSmtp = 0; + } + blob_reset(&p->out); +} + +/* +** Put the AlertSender into an error state. +*/ +static void emailerError(AlertSender *p, const char *zFormat, ...){ + va_list ap; + fossil_free(p->zErr); + va_start(ap, zFormat); + p->zErr = vmprintf(zFormat, ap); + va_end(ap); + emailerShutdown(p); + if( p->mFlags & ALERT_IMMEDIATE_FAIL ){ + fossil_fatal("%s", p->zErr); + } +} + +/* +** Free an email sender object +*/ +void alert_sender_free(AlertSender *p){ + if( p ){ + emailerShutdown(p); + fossil_free(p->zErr); + fossil_free(p); + } +} + +/* +** Get an email setting value. Report an error if not configured. +** Return 0 on success and one if there is an error. +*/ +static int emailerGetSetting( + AlertSender *p, /* Where to report the error */ + const char **pzVal, /* Write the setting value here */ + const char *zName /* Name of the setting */ +){ + const char *z = db_get(zName, 0); + int rc = 0; + if( z==0 || z[0]==0 ){ + emailerError(p, "missing \"%s\" setting", zName); + rc = 1; + }else{ + *pzVal = z; + } + return rc; +} + +/* +** Create a new AlertSender object. +** +** The method used for sending email is determined by various email-* +** settings, and especially email-send-method. The repository +** email-send-method can be overridden by the zAltDest argument to +** cause a different sending mechanism to be used. Pass "stdout" to +** zAltDest to cause all emails to be printed to the console for +** debugging purposes. +** +** The AlertSender object returned must be freed using alert_sender_free(). +*/ +AlertSender *alert_sender_new(const char *zAltDest, u32 mFlags){ + AlertSender *p; + + p = fossil_malloc(sizeof(*p)); + memset(p, 0, sizeof(*p)); + blob_init(&p->out, 0, 0); + p->mFlags = mFlags; + if( zAltDest ){ + p->zDest = zAltDest; + }else{ + p->zDest = db_get("email-send-method",0); + } + if( fossil_strcmp(p->zDest,"off")==0 ) return p; + if( emailerGetSetting(p, &p->zFrom, "email-self") ) return p; + if( fossil_strcmp(p->zDest,"db")==0 ){ + char *zErr; + int rc; + if( emailerGetSetting(p, &p->zDb, "email-send-db") ) return p; + rc = sqlite3_open(p->zDb, &p->db); + if( rc ){ + emailerError(p, "unable to open output database file \"%s\": %s", + p->zDb, sqlite3_errmsg(p->db)); + return p; + } + rc = sqlite3_exec(p->db, "CREATE TABLE IF NOT EXISTS email(\n" + " emailid INTEGER PRIMARY KEY,\n" + " msg TEXT\n);", 0, 0, &zErr); + if( zErr ){ + emailerError(p, "CREATE TABLE failed with \"%s\"", zErr); + sqlite3_free(zErr); + return p; + } + rc = sqlite3_prepare_v2(p->db, "INSERT INTO email(msg) VALUES(?1)", -1, + &p->pStmt, 0); + if( rc ){ + emailerError(p, "cannot prepare INSERT statement: %s", + sqlite3_errmsg(p->db)); + return p; + } + }else if( fossil_strcmp(p->zDest, "pipe")==0 ){ + emailerGetSetting(p, &p->zCmd, "email-send-command"); + }else if( fossil_strcmp(p->zDest, "dir")==0 ){ + emailerGetSetting(p, &p->zDir, "email-send-dir"); + }else if( fossil_strcmp(p->zDest, "blob")==0 ){ + blob_init(&p->out, 0, 0); + }else if( fossil_strcmp(p->zDest, "relay")==0 ){ + const char *zRelay = 0; + emailerGetSetting(p, &zRelay, "email-send-relayhost"); + if( zRelay ){ + u32 smtpFlags = SMTP_DIRECT; + if( mFlags & ALERT_TRACE ) smtpFlags |= SMTP_TRACE_STDOUT; + p->pSmtp = smtp_session_new(p->zFrom, zRelay, smtpFlags); + smtp_client_startup(p->pSmtp); + } + } + return p; +} + +/* +** Scan the header of the email message in pMsg looking for the +** (first) occurrance of zField. Fill pValue with the content of +** that field. +** +** This routine initializes pValue. Any prior content of pValue is +** discarded (leaked). +** +** Return non-zero on success. Return 0 if no instance of the header +** is found. +*/ +int email_header_value(Blob *pMsg, const char *zField, Blob *pValue){ + int nField = (int)strlen(zField); + Blob line; + blob_rewind(pMsg); + blob_init(pValue,0,0); + while( blob_line(pMsg, &line) ){ + int n, i; + char *z; + blob_trim(&line); + n = blob_size(&line); + if( n==0 ) return 0; + if( n<nField+1 ) continue; + z = blob_buffer(&line); + if( sqlite3_strnicmp(z, zField, nField)==0 && z[nField]==':' ){ + for(i=nField+1; i<n && fossil_isspace(z[i]); i++){} + blob_init(pValue, z+i, n-i); + while( blob_line(pMsg, &line) ){ + blob_trim(&line); + n = blob_size(&line); + if( n==0 ) break; + z = blob_buffer(&line); + if( !fossil_isspace(z[0]) ) break; + for(i=1; i<n && fossil_isspace(z[i]); i++){} + blob_append(pValue, " ", 1); + blob_append(pValue, z+i, n-i); + } + return 1; + } + } + return 0; +} + +/* +** Determine whether or not the input string is a valid email address. +** Only look at character up to but not including the first \000 or +** the first cTerm character, whichever comes first. +** +** Return the length of the email addresss string in bytes if the email +** address is valid. If the email address is misformed, return 0. +*/ +int email_address_is_valid(const char *z, char cTerm){ + int i; + int nAt = 0; + int nDot = 0; + char c; + if( z[0]=='.' ) return 0; /* Local part cannot begin with "." */ + for(i=0; (c = z[i])!=0 && c!=cTerm; i++){ + if( fossil_isalnum(c) ){ + /* Alphanumerics are always ok */ + }else if( c=='@' ){ + if( nAt ) return 0; /* Only a single "@" allowed */ + if( i>64 ) return 0; /* Local part too big */ + nAt = 1; + nDot = 0; + if( i==0 ) return 0; /* Disallow empty local part */ + if( z[i-1]=='.' ) return 0; /* Last char of local cannot be "." */ + if( z[i+1]=='.' || z[i+1]=='-' ){ + return 0; /* Domain cannot begin with "." or "-" */ + } + }else if( c=='-' ){ + if( z[i+1]==cTerm ) return 0; /* Last character cannot be "-" */ + }else if( c=='.' ){ + if( z[i+1]=='.' ) return 0; /* Do not allow ".." */ + if( z[i+1]==cTerm ) return 0; /* Domain may not end with . */ + nDot++; + }else if( (c=='_' || c=='+') && nAt==0 ){ + /* _ and + are ok in the local part */ + }else{ + return 0; /* Anything else is an error */ + } + } + if( c!=cTerm ) return 0; /* Missing terminator */ + if( nAt==0 ) return 0; /* No "@" found anywhere */ + if( nDot==0 ) return 0; /* No "." in the domain */ + return i; +} + +/* +** Make a copy of the input string up to but not including the +** first cTerm character. +** +** Verify that the string really that is to be copied really is a +** valid email address. If it is not, then return NULL. +** +** This routine is more restrictive than necessary. It does not +** allow comments, IP address, quoted strings, or certain uncommon +** characters. The only non-alphanumerics allowed in the local +** part are "_", "+", "-" and "+". +*/ +char *email_copy_addr(const char *z, char cTerm ){ + int i = email_address_is_valid(z, cTerm); + return i==0 ? 0 : mprintf("%.*s", i, z); +} + +/* +** Scan the input string for a valid email address enclosed in <...> +** If the string contains one or more email addresses, extract the first +** one into memory obtained from mprintf() and return a pointer to it. +** If no valid email address can be found, return NULL. +*/ +char *alert_find_emailaddr(const char *zIn){ + char *zOut = 0; + while( zIn!=0 ){ + zIn = (const char*)strchr(zIn, '<'); + if( zIn==0 ) break; + zIn++; + zOut = email_copy_addr(zIn, '>'); + if( zOut!=0 ) break; + } + return zOut; +} + +/* +** SQL function: find_emailaddr(X) +** +** Return the first valid email address of the form <...> in input string +** X. Or return NULL if not found. +*/ +void alert_find_emailaddr_func( + sqlite3_context *context, + int argc, + sqlite3_value **argv +){ + const char *zIn = (const char*)sqlite3_value_text(argv[0]); + char *zOut = alert_find_emailaddr(zIn); + if( zOut ){ + sqlite3_result_text(context, zOut, -1, fossil_free); + } +} + +/* +** SQL function: display_name(X) +** +** If X is a string, search for a user name at the beginning of that +** string. The user name must be followed by an email address. If +** found, return the user name. If not found, return NULL. +** +** This routine is used to extract the display name from the USER.INFO +** field. +*/ +void alert_display_name_func( + sqlite3_context *context, + int argc, + sqlite3_value **argv +){ + const char *zIn = (const char*)sqlite3_value_text(argv[0]); + int i; + if( zIn==0 ) return; + while( fossil_isspace(zIn[0]) ) zIn++; + for(i=0; zIn[i] && zIn[i]!='<' && zIn[i]!='\n'; i++){} + if( zIn[i]=='<' ){ + while( i>0 && fossil_isspace(zIn[i-1]) ){ i--; } + if( i>0 ){ + sqlite3_result_text(context, zIn, i, SQLITE_TRANSIENT); + } + } +} + +/* +** Return the hostname portion of an email address - the part following +** the @ +*/ +char *alert_hostname(const char *zAddr){ + char *z = strchr(zAddr, '@'); + if( z ){ + z++; + }else{ + z = (char*)zAddr; + } + return z; +} + +/* +** Return a pointer to a fake email mailbox name that corresponds +** to human-readable name zFromName. The fake mailbox name is based +** on a hash. No huge problems arise if there is a hash collisions, +** but it is still better if collisions can be avoided. +** +** The returned string is held in a static buffer and is overwritten +** by each subsequent call to this routine. +*/ +static char *alert_mailbox_name(const char *zFromName){ + static char zHash[20]; + unsigned int x = 0; + int n = 0; + while( zFromName[0] ){ + n++; + x = x*1103515245 + 12345 + ((unsigned char*)zFromName)[0]; + zFromName++; + } + sqlite3_snprintf(sizeof(zHash), zHash, + "noreply%x%08x", n, x); + return zHash; +} + +/* +** COMMAND: test-mailbox-hashname +** +** Usage: %fossil test-mailbox-hashname HUMAN-NAME ... +** +** Return the mailbox hash name corresponding to each human-readable +** name on the command line. This is a test interface for the +** alert_mailbox_name() function. +*/ +void alert_test_mailbox_hashname(void){ + int i; + for(i=2; i<g.argc; i++){ + fossil_print("%30s: %s\n", g.argv[i], alert_mailbox_name(g.argv[i])); + } +} + +/* +** Extract all To: header values from the email header supplied. +** Store them in the array list. +*/ +void email_header_to(Blob *pMsg, int *pnTo, char ***pazTo){ + int nTo = 0; + char **azTo = 0; + Blob v; + char *z, *zAddr; + int i; + + email_header_value(pMsg, "to", &v); + z = blob_str(&v); + for(i=0; z[i]; i++){ + if( z[i]=='<' && (zAddr = email_copy_addr(&z[i+1],'>'))!=0 ){ + azTo = fossil_realloc(azTo, sizeof(azTo[0])*(nTo+1) ); + azTo[nTo++] = zAddr; + } + } + *pnTo = nTo; + *pazTo = azTo; +} + +/* +** Free a list of To addresses obtained from a prior call to +** email_header_to() +*/ +void email_header_to_free(int nTo, char **azTo){ + int i; + for(i=0; i<nTo; i++) fossil_free(azTo[i]); + fossil_free(azTo); +} + +/* +** Send a single email message. +** +** The recepient(s) must be specified using "To:" or "Cc:" or "Bcc:" fields +** in the header. Likewise, the header must contains a "Subject:" line. +** The header might also include fields like "Message-Id:" or +** "In-Reply-To:". +** +** This routine will add fields to the header as follows: +** +** From: +** Date: +** Message-Id: +** Content-Type: +** Content-Transfer-Encoding: +** MIME-Version: +** Sender: +** +** The caller maintains ownership of the input Blobs. This routine will +** read the Blobs and send them onward to the email system, but it will +** not free them. +** +** The Message-Id: field is added if there is not already a Message-Id +** in the pHdr parameter. +** +** If the zFromName argument is not NULL, then it should be a human-readable +** name or handle for the sender. In that case, "From:" becomes a made-up +** email address based on a hash of zFromName and the domain of email-self, +** and an additional "Sender:" field is inserted with the email-self +** address. Downstream software might use the Sender header to set +** the envelope-from address of the email. If zFromName is a NULL pointer, +** then the "From:" is set to the email-self value and Sender is +** omitted. +*/ +void alert_send( + AlertSender *p, /* Emailer context */ + Blob *pHdr, /* Email header (incomplete) */ + Blob *pBody, /* Email body */ + const char *zFromName /* Optional human-readable name of sender */ +){ + Blob all, *pOut; + u64 r1, r2; + if( p->mFlags & ALERT_TRACE ){ + fossil_print("Sending email\n"); + } + if( fossil_strcmp(p->zDest, "off")==0 ){ + return; + } + blob_init(&all, 0, 0); + if( fossil_strcmp(p->zDest, "blob")==0 ){ + pOut = &p->out; + if( blob_size(pOut) ){ + blob_appendf(pOut, "%.72c\n", '='); + } + }else{ + pOut = &all; + } + blob_append(pOut, blob_buffer(pHdr), blob_size(pHdr)); + if( p->zFrom==0 || p->zFrom[0]==0 ){ + return; /* email-self is not set. Error will be reported separately */ + }else if( zFromName ){ + blob_appendf(pOut, "From: %s <%s@%s>\r\n", + zFromName, alert_mailbox_name(zFromName), alert_hostname(p->zFrom)); + blob_appendf(pOut, "Sender: <%s>\r\n", p->zFrom); + }else{ + blob_appendf(pOut, "From: <%s>\r\n", p->zFrom); + } + blob_appendf(pOut, "Date: %z\r\n", cgi_rfc822_datestamp(time(0))); + if( strstr(blob_str(pHdr), "\r\nMessage-Id:")==0 ){ + /* Message-id format: "<$(date)x$(random)@$(from-host)>" where $(date) is + ** the current unix-time in hex, $(random) is a 64-bit random number, + ** and $(from) is the domain part of the email-self setting. */ + sqlite3_randomness(sizeof(r1), &r1); + r2 = time(0); + blob_appendf(pOut, "Message-Id: <%llxx%016llx@%s>\r\n", + r2, r1, alert_hostname(p->zFrom)); + } + blob_add_final_newline(pBody); + blob_appendf(pOut, "MIME-Version: 1.0\r\n"); + blob_appendf(pOut, "Content-Type: text/plain; charset=\"UTF-8\"\r\n"); +#if 0 + blob_appendf(pOut, "Content-Transfer-Encoding: base64\r\n\r\n"); + append_base64(pOut, pBody); +#else + blob_appendf(pOut, "Content-Transfer-Encoding: quoted-printable\r\n\r\n"); + append_quoted(pOut, pBody); +#endif + if( p->pStmt ){ + int i, rc; + sqlite3_bind_text(p->pStmt, 1, blob_str(&all), -1, SQLITE_TRANSIENT); + for(i=0; i<100 && sqlite3_step(p->pStmt)==SQLITE_BUSY; i++){ + sqlite3_sleep(10); + } + rc = sqlite3_reset(p->pStmt); + if( rc!=SQLITE_OK ){ + emailerError(p, "Failed to insert email message into output queue.\n" + "%s", sqlite3_errmsg(p->db)); + } + }else if( p->zCmd ){ + FILE *out = popen(p->zCmd, "w"); + if( out ){ + fwrite(blob_buffer(&all), 1, blob_size(&all), out); + pclose(out); + }else{ + emailerError(p, "Could not open output pipe \"%s\"", p->zCmd); + } + }else if( p->zDir ){ + char *zFile = file_time_tempname(p->zDir, ".email"); + blob_write_to_file(&all, zFile); + fossil_free(zFile); + }else if( p->pSmtp ){ + char **azTo = 0; + int nTo = 0; + email_header_to(pHdr, &nTo, &azTo); + if( nTo>0 ){ + smtp_send_msg(p->pSmtp, p->zFrom, nTo, (const char**)azTo,blob_str(&all)); + email_header_to_free(nTo, azTo); + } + }else if( strcmp(p->zDest, "stdout")==0 ){ + char **azTo = 0; + int nTo = 0; + int i; + email_header_to(pHdr, &nTo, &azTo); + for(i=0; i<nTo; i++){ + fossil_print("X-To-Test-%d: [%s]\r\n", i, azTo[i]); + } + email_header_to_free(nTo, azTo); + blob_add_final_newline(&all); + fossil_print("%s", blob_str(&all)); + } + blob_reset(&all); +} + +/* +** SETTING: email-url width=40 +** This URL is used as the basename for hyperlinks included in email alert +** text. Omit the trailing "/". +*/ +/* +** SETTING: email-admin width=40 +** This is the email address for the human administrator for the system. +** Abuse and trouble reports and password reset requests are send here. +*/ +/* +** SETTING: email-subname width=16 +** This is a short name used to identifies the repository in the Subject: +** line of email alerts. Traditionally this name is included in square +** brackets. Examples: "[fossil-src]", "[sqlite-src]". +*/ +/* +** SETTING: email-renew-interval width=16 +** If this setting as an integer N that is 14 or greater then email +** notification is suspected for subscriptions that have a "last contact +** time" of more than N days ago. The "last contact time" is recorded +** in the SUBSCRIBER.LASTCONTACT entry of the database. Logging in, +** sending a forum post, editing a wiki page, changing subscription settings +** at /alerts, or visiting /renew all update the last contact time. +** If this setting is not an integer value or is less than 14 or undefined, +** then subscriptions never expire. +*/ +/* X-VARIABLE: email-renew-warning +** X-VARIABLE: email-renew-cutoff +** +** These CONFIG table entries are not considered "settings" since their +** values are computed and updated automatically. +** +** email-renew-cutoff is the lastContact cutoff for subscription. It +** is measured in days since 1970-01-01. If The lastContact time for +** a subscription is less than email-renew-cutoff, then now new emails +** are sent to the subscriber. +** +** email-renew-warning is the time (in days since 1970-01-01) when the +** last batch of "your subscription is about to expire" emails were +** sent out. +** +** email-renew-cutoff is normally 7 days behind email-renew-warning. +*/ +/* +** SETTING: email-send-method width=5 default=off sensitive +** Determine the method used to send email. Allowed values are +** "off", "relay", "pipe", "dir", "db", and "stdout". The "off" value +** means no email is ever sent. The "relay" value means emails are sent +** to an Mail Sending Agent using SMTP located at email-send-relayhost. +** The "pipe" value means email messages are piped into a command +** determined by the email-send-command setting. The "dir" value means +** emails are written to individual files in a directory determined +** by the email-send-dir setting. The "db" value means that emails +** are added to an SQLite database named by the* email-send-db setting. +** The "stdout" value writes email text to standard output, for debugging. +*/ +/* +** SETTING: email-send-command width=40 sensitive +** This is a command to which outbound email content is piped when the +** email-send-method is set to "pipe". The command must extract +** recipient, sender, subject, and all other relevant information +** from the email header. +*/ +/* +** SETTING: email-send-dir width=40 sensitive +** This is a directory into which outbound emails are written as individual +** files if the email-send-method is set to "dir". +*/ +/* +** SETTING: email-send-db width=40 sensitive +** This is an SQLite database file into which outbound emails are written +** if the email-send-method is set to "db". +*/ +/* +** SETTING: email-self width=40 +** This is the email address for the repository. Outbound emails add +** this email address as the "From:" field. +*/ +/* +** SETTING: email-send-relayhost width=40 sensitive +** This is the hostname and TCP port to which output email messages +** are sent when email-send-method is "relay". There should be an +** SMTP server configured as a Mail Submission Agent listening on the +** designated host and port and all times. +*/ + + +/* +** COMMAND: alerts* +** +** Usage: %fossil alerts SUBCOMMAND ARGS... +** +** Subcommands: +** +** pending Show all pending alerts. Useful for debugging. +** +** reset Hard reset of all email notification tables +** in the repository. This erases all subscription +** information. ** Use with extreme care ** +** +** send Compose and send pending email alerts. +** Some installations may want to do this via +** a cron-job to make sure alerts are sent +** in a timely manner. +** Options: +** +** --digest Send digests +** --renewal Send subscription renewal +** notices +** --test Write to standard output +** +** settings [NAME VALUE] With no arguments, list all email settings. +** Or change the value of a single email setting. +** +** status Report on the status of the email alert +** subsystem +** +** subscribers [PATTERN] List all subscribers matching PATTERN. Either +** LIKE or GLOB wildcards can be used in PATTERN. +** +** test-message TO [OPTS] Send a single email message using whatever +** email sending mechanism is currently configured. +** Use this for testing the email notification +** configuration. Options: +** +** --body FILENAME Content from FILENAME +** --smtp-trace Trace SMTP processing +** --stdout Send msg to stdout +** -S|--subject SUBJECT Message "subject:" +** +** unsubscribe EMAIL Remove a single subscriber with the given EMAIL. +*/ +void alert_cmd(void){ + const char *zCmd; + int nCmd; + db_find_and_open_repository(0, 0); + alert_schema(0); + zCmd = g.argc>=3 ? g.argv[2] : "x"; + nCmd = (int)strlen(zCmd); + if( strncmp(zCmd, "pending", nCmd)==0 ){ + Stmt q; + verify_all_options(); + if( g.argc!=3 ) usage("pending"); + db_prepare(&q,"SELECT eventid, sentSep, sentDigest, sentMod" + " FROM pending_alert"); + while( db_step(&q)==SQLITE_ROW ){ + fossil_print("%10s %7s %10s %7s\n", + db_column_text(&q,0), + db_column_int(&q,1) ? "sentSep" : "", + db_column_int(&q,2) ? "sentDigest" : "", + db_column_int(&q,3) ? "sentMod" : ""); + } + db_finalize(&q); + }else + if( strncmp(zCmd, "reset", nCmd)==0 ){ + int c; + int bForce = find_option("force","f",0)!=0; + verify_all_options(); + if( bForce ){ + c = 'y'; + }else{ + Blob yn; + fossil_print( + "This will erase all content in the repository tables, thus\n" + "deleting all subscriber information. The information will be\n" + "unrecoverable.\n"); + prompt_user("Continue? (y/N) ", &yn); + c = blob_str(&yn)[0]; + blob_reset(&yn); + } + if( c=='y' ){ + alert_drop_trigger(); + db_multi_exec( + "DROP TABLE IF EXISTS subscriber;\n" + "DROP TABLE IF EXISTS pending_alert;\n" + "DROP TABLE IF EXISTS alert_bounce;\n" + /* Legacy */ + "DROP TABLE IF EXISTS alert_pending;\n" + "DROP TABLE IF EXISTS subscription;\n" + ); + alert_schema(0); + } + }else + if( strncmp(zCmd, "send", nCmd)==0 ){ + u32 eFlags = 0; + if( find_option("digest",0,0)!=0 ) eFlags |= SENDALERT_DIGEST; + if( find_option("renewal",0,0)!=0 ) eFlags |= SENDALERT_RENEWAL; + if( find_option("test",0,0)!=0 ){ + eFlags |= SENDALERT_PRESERVE|SENDALERT_STDOUT; + } + verify_all_options(); + alert_send_alerts(eFlags); + }else + if( strncmp(zCmd, "settings", nCmd)==0 ){ + int isGlobal = find_option("global",0,0)!=0; + int nSetting; + const Setting *pSetting = setting_info(&nSetting); + db_open_config(1, 0); + verify_all_options(); + if( g.argc!=3 && g.argc!=5 ) usage("setting [NAME VALUE]"); + if( g.argc==5 ){ + const char *zLabel = g.argv[3]; + if( strncmp(zLabel, "email-", 6)!=0 + || (pSetting = db_find_setting(zLabel, 1))==0 ){ + fossil_fatal("not a valid email setting: \"%s\"", zLabel); + } + db_set(pSetting->name, g.argv[4], isGlobal); + g.argc = 3; + } + pSetting = setting_info(&nSetting); + for(; nSetting>0; nSetting--, pSetting++ ){ + if( strncmp(pSetting->name,"email-",6)!=0 ) continue; + print_setting(pSetting); + } + }else + if( strncmp(zCmd, "status", nCmd)==0 ){ + Stmt q; + int iCutoff; + int nSetting, n; + static const char *zFmt = "%-29s %d\n"; + const Setting *pSetting = setting_info(&nSetting); + db_open_config(1, 0); + verify_all_options(); + if( g.argc!=3 ) usage("status"); + pSetting = setting_info(&nSetting); + for(; nSetting>0; nSetting--, pSetting++ ){ + if( strncmp(pSetting->name,"email-",6)!=0 ) continue; + print_setting(pSetting); + } + n = db_int(0,"SELECT count(*) FROM pending_alert WHERE NOT sentSep"); + fossil_print(zFmt/*works-like:"%s%d"*/, "pending-alerts", n); + n = db_int(0,"SELECT count(*) FROM pending_alert WHERE NOT sentDigest"); + fossil_print(zFmt/*works-like:"%s%d"*/, "pending-digest-alerts", n); + db_prepare(&q, + "SELECT" + " name," + " value," + " now()/86400-value," + " date(value*86400,'unixepoch')" + " FROM repository.config" + " WHERE name in ('email-renew-warning','email-renew-cutoff');"); + while( db_step(&q)==SQLITE_ROW ){ + fossil_print("%-29s %-6d (%d days ago on %s)\n", + db_column_text(&q, 0), + db_column_int(&q, 1), + db_column_int(&q, 2), + db_column_text(&q, 3)); + } + db_finalize(&q); + n = db_int(0,"SELECT count(*) FROM subscriber"); + fossil_print(zFmt/*works-like:"%s%d"*/, "total-subscribers", n); + iCutoff = db_get_int("email-renew-cutoff", 0); + n = db_int(0, "SELECT count(*) FROM subscriber WHERE sverified" + " AND NOT sdonotcall AND length(ssub)>1" + " AND lastContact>=%d", iCutoff); + fossil_print(zFmt/*works-like:"%s%d"*/, "active-subscribers", n); + }else + if( strncmp(zCmd, "subscribers", nCmd)==0 ){ + Stmt q; + verify_all_options(); + if( g.argc!=3 && g.argc!=4 ) usage("subscribers [PATTERN]"); + if( g.argc==4 ){ + char *zPattern = g.argv[3]; + db_prepare(&q, + "SELECT semail FROM subscriber" + " WHERE semail LIKE '%%%q%%' OR suname LIKE '%%%q%%'" + " OR semail GLOB '*%q*' or suname GLOB '*%q*'" + " ORDER BY semail", + zPattern, zPattern, zPattern, zPattern); + }else{ + db_prepare(&q, + "SELECT semail FROM subscriber" + " ORDER BY semail"); + } + while( db_step(&q)==SQLITE_ROW ){ + fossil_print("%s\n", db_column_text(&q, 0)); + } + db_finalize(&q); + }else + if( strncmp(zCmd, "test-message", nCmd)==0 ){ + Blob prompt, body, hdr; + const char *zDest = find_option("stdout",0,0)!=0 ? "stdout" : 0; + int i; + u32 mFlags = ALERT_IMMEDIATE_FAIL; + const char *zSubject = find_option("subject", "S", 1); + const char *zSource = find_option("body", 0, 1); + AlertSender *pSender; + if( find_option("smtp-trace",0,0)!=0 ) mFlags |= ALERT_TRACE; + verify_all_options(); + blob_init(&prompt, 0, 0); + blob_init(&body, 0, 0); + blob_init(&hdr, 0, 0); + blob_appendf(&hdr,"To: "); + for(i=3; i<g.argc; i++){ + if( i>3 ) blob_append(&hdr, ", ", 2); + blob_appendf(&hdr, "<%s>", g.argv[i]); + } + blob_append(&hdr,"\r\n",2); + if( zSubject==0 ) zSubject = "fossil alerts test-message"; + blob_appendf(&hdr, "Subject: %s\r\n", zSubject); + if( zSource ){ + blob_read_from_file(&body, zSource, ExtFILE); + }else{ + prompt_for_user_comment(&body, &prompt); + } + blob_add_final_newline(&body); + pSender = alert_sender_new(zDest, mFlags); + alert_send(pSender, &hdr, &body, 0); + alert_sender_free(pSender); + blob_reset(&hdr); + blob_reset(&body); + blob_reset(&prompt); + }else + if( strncmp(zCmd, "unsubscribe", nCmd)==0 ){ + verify_all_options(); + if( g.argc!=4 ) usage("unsubscribe EMAIL"); + db_multi_exec( + "DELETE FROM subscriber WHERE semail=%Q", g.argv[3]); + }else + { + usage("pending|reset|send|setting|status|" + "subscribers|test-message|unsubscribe"); + } +} + +/* +** Do error checking on a submitted subscription form. Return TRUE +** if the submission is valid. Return false if any problems are seen. +*/ +static int subscribe_error_check( + int *peErr, /* Type of error */ + char **pzErr, /* Error message text */ + int needCaptcha /* True if captcha check needed */ +){ + const char *zEAddr; + int i, j, n; + char c; + + *peErr = 0; + *pzErr = 0; + + /* Verify the captcha first */ + if( needCaptcha ){ + if( !captcha_is_correct(1) ){ + *peErr = 2; + *pzErr = mprintf("incorrect security code"); + return 0; + } + } + + /* Check the validity of the email address. + ** + ** (1) Exactly one '@' character. + ** (2) No other characters besides [a-zA-Z0-9._+-] + ** + ** The local part is currently more restrictive than RFC 5322 allows: + ** https://stackoverflow.com/a/2049510/142454 We will expand this as + ** necessary. + */ + zEAddr = P("e"); + if( zEAddr==0 ){ + *peErr = 1; + *pzErr = mprintf("required"); + return 0; + } + for(i=j=n=0; (c = zEAddr[i])!=0; i++){ + if( c=='@' ){ + n = i; + j++; + continue; + } + if( !fossil_isalnum(c) && c!='.' && c!='_' && c!='-' && c!='+' ){ + *peErr = 1; + *pzErr = mprintf("illegal character in email address: 0x%x '%c'", + c, c); + return 0; + } + } + if( j!=1 ){ + *peErr = 1; + *pzErr = mprintf("email address should contain exactly one '@'"); + return 0; + } + if( n<1 ){ + *peErr = 1; + *pzErr = mprintf("name missing before '@' in email address"); + return 0; + } + if( n>i-5 ){ + *peErr = 1; + *pzErr = mprintf("email domain too short"); + return 0; + } + + if( authorized_subscription_email(zEAddr)==0 ){ + *peErr = 1; + *pzErr = mprintf("not an authorized email address"); + return 0; + } + + /* Check to make sure the email address is available for reuse */ + if( db_exists("SELECT 1 FROM subscriber WHERE semail=%Q", zEAddr) ){ + *peErr = 1; + *pzErr = mprintf("this email address is used by someone else"); + return 0; + } + + /* If we reach this point, all is well */ + return 1; +} + +/* +** Text of email message sent in order to confirm a subscription. +*/ +static const char zConfirmMsg[] = +@ Someone has signed you up for email alerts on the Fossil repository +@ at %s. +@ +@ To confirm your subscription and begin receiving alerts, click on +@ the following hyperlink: +@ +@ %s/alerts/%s +@ +@ Save the hyperlink above! You can reuse this same hyperlink to +@ unsubscribe or to change the kinds of alerts you receive. +@ +@ If you do not want to subscribe, you can simply ignore this message. +@ You will not be contacted again. +@ +; + +/* +** Append the text of an email confirmation message to the given +** Blob. The security code is in zCode. +*/ +void alert_append_confirmation_message(Blob *pMsg, const char *zCode){ + blob_appendf(pMsg, zConfirmMsg/*works-like:"%s%s%s"*/, + g.zBaseURL, g.zBaseURL, zCode); +} + +/* +** WEBPAGE: subscribe +** +** Allow users to subscribe to email notifications. +** +** This page is usually run by users who are not logged in. +** A logged-in user can add email notifications on the /alerts page. +** Access to this page by a logged in user (other than an +** administrator) results in a redirect to the /alerts page. +** +** Administrators can visit this page in order to sign up other +** users. +** +** The Alerts permission ("7") is required to access this +** page. To allow anonymous passers-by to sign up for email +** notification, set Email-Alerts on user "nobody" or "anonymous". +*/ +void subscribe_page(void){ + int needCaptcha; + unsigned int uSeed = 0; + const char *zDecoded; + char *zCaptcha = 0; + char *zErr = 0; + int eErr = 0; + int di; + + if( alert_webpages_disabled() ) return; + login_check_credentials(); + if( !g.perm.EmailAlert ){ + login_needed(g.anon.EmailAlert); + return; + } + if( login_is_individual() + && db_exists("SELECT 1 FROM subscriber WHERE suname=%Q",g.zLogin) + ){ + /* This person is already signed up for email alerts. Jump + ** to the screen that lets them edit their alert preferences. + ** Except, administrators can create subscriptions for others so + ** do not jump for them. + */ + if( g.perm.Admin ){ + /* Admins get a link to admin their own account, but they + ** stay on this page so that they can create subscriptions + ** for other people. */ + style_submenu_element("My Subscription","%R/alerts"); + }else{ + /* Everybody else jumps to the page to administer their own + ** account only. */ + cgi_redirectf("%R/alerts"); + return; + } + } + if( !g.perm.Admin && !db_get_boolean("anon-subscribe",1) ){ + register_page(); + return; + } + style_set_current_feature("alerts"); + alert_submenu_common(); + needCaptcha = !login_is_individual(); + if( P("submit") + && cgi_csrf_safe(1) + && subscribe_error_check(&eErr,&zErr,needCaptcha) + ){ + /* A validated request for a new subscription has been received. */ + char ssub[20]; + const char *zEAddr = P("e"); + const char *zCode; /* New subscriber code (in hex) */ + int nsub = 0; + const char *suname = PT("suname"); + if( suname==0 && needCaptcha==0 && !g.perm.Admin ) suname = g.zLogin; + if( suname && suname[0]==0 ) suname = 0; + if( PB("sa") ) ssub[nsub++] = 'a'; + if( g.perm.Read && PB("sc") ) ssub[nsub++] = 'c'; + if( g.perm.RdForum && PB("sf") ) ssub[nsub++] = 'f'; + if( g.perm.RdTkt && PB("st") ) ssub[nsub++] = 't'; + if( g.perm.RdWiki && PB("sw") ) ssub[nsub++] = 'w'; + if( g.perm.RdForum && PB("sx") ) ssub[nsub++] = 'x'; + ssub[nsub] = 0; + zCode = db_text(0, + "INSERT INTO subscriber(semail,suname," + " sverified,sdonotcall,sdigest,ssub,sctime,mtime,smip,lastContact)" + "VALUES(%Q,%Q,%d,0,%d,%Q,now(),now(),%Q,now()/86400)" + "RETURNING hex(subscriberCode);", + /* semail */ zEAddr, + /* suname */ suname, + /* sverified */ needCaptcha==0, + /* sdigest */ PB("di"), + /* ssub */ ssub, + /* smip */ g.zIpAddr + ); + if( !needCaptcha ){ + /* The new subscription has been added on behalf of a logged-in user. + ** No verification is required. Jump immediately to /alerts page. + */ + if( g.perm.Admin ){ + cgi_redirectf("%R/alerts/%.32s", zCode); + }else{ + cgi_redirectf("%R/alerts"); + } + return; + }else{ + /* We need to send a verification email */ + Blob hdr, body; + AlertSender *pSender = alert_sender_new(0,0); + blob_init(&hdr,0,0); + blob_init(&body,0,0); + blob_appendf(&hdr, "To: <%s>\n", zEAddr); + blob_appendf(&hdr, "Subject: Subscription verification\n"); + alert_append_confirmation_message(&body, zCode); + alert_send(pSender, &hdr, &body, 0); + style_header("Email Alert Verification"); + if( pSender->zErr ){ + @ <h1>Internal Error</h1> + @ <p>The following internal error was encountered while trying + @ to send the confirmation email: + @ <blockquote><pre> + @ %h(pSender->zErr) + @ </pre></blockquote> + }else{ + @ <p>An email has been sent to "%h(zEAddr)". That email contains a + @ hyperlink that you must click to activate your + @ subscription.</p> + } + alert_sender_free(pSender); + style_finish_page(); + } + return; + } + style_header("Signup For Email Alerts"); + if( P("submit")==0 ){ + /* If this is the first visit to this page (if this HTTP request did not + ** come from a prior Submit of the form) then default all of the + ** subscription options to "on" */ + cgi_set_parameter_nocopy("sa","1",1); + if( g.perm.Read ) cgi_set_parameter_nocopy("sc","1",1); + if( g.perm.RdForum ) cgi_set_parameter_nocopy("sf","1",1); + if( g.perm.RdTkt ) cgi_set_parameter_nocopy("st","1",1); + if( g.perm.RdWiki ) cgi_set_parameter_nocopy("sw","1",1); + } + @ <p>To receive email notifications for changes to this + @ repository, fill out the form below and press "Submit" button.</p> + form_begin(0, "%R/subscribe"); + @ <table class="subscribe"> + @ <tr> + @ <td class="form_label">Email Address:</td> + @ <td><input type="text" name="e" value="%h(PD("e",""))" size="30"></td> + @ <tr> + if( eErr==1 ){ + @ <tr><td><td><span class='loginError'>↑ %h(zErr)</span></td></tr> + } + @ </tr> + if( needCaptcha ){ + const char *zInit = ""; + if( P("captchaseed")!=0 && eErr!=2 ){ + uSeed = strtoul(P("captchaseed"),0,10); + zInit = P("captcha"); + }else{ + uSeed = captcha_seed(); + } + zDecoded = captcha_decode(uSeed); + zCaptcha = captcha_render(zDecoded); + @ <tr> + @ <td class="form_label">Security Code:</td> + @ <td><input type="text" name="captcha" value="%h(zInit)" size="30"> + captcha_speakit_button(uSeed, "Speak the code"); + @ <input type="hidden" name="captchaseed" value="%u(uSeed)"></td> + @ </tr> + if( eErr==2 ){ + @ <tr><td><td><span class='loginError'>↑ %h(zErr)</span></td></tr> + } + @ </tr> + } + if( g.perm.Admin ){ + @ <tr> + @ <td class="form_label">User:</td> + @ <td><input type="text" name="suname" value="%h(PD("suname",g.zLogin))" \ + @ size="30"></td> + @ </tr> + if( eErr==3 ){ + @ <tr><td><td><span class='loginError'>↑ %h(zErr)</span></td></tr> + } + @ </tr> + } + @ <tr> + @ <td class="form_label">Topics:</td> + @ <td><label><input type="checkbox" name="sa" %s(PCK("sa"))> \ + @ Announcements</label><br> + if( g.perm.Read ){ + @ <label><input type="checkbox" name="sc" %s(PCK("sc"))> \ + @ Check-ins</label><br> + } + if( g.perm.RdForum ){ + @ <label><input type="checkbox" name="sf" %s(PCK("sf"))> \ + @ Forum Posts</label><br> + @ <label><input type="checkbox" name="sx" %s(PCK("sx"))> \ + @ Forum Edits</label><br> + } + if( g.perm.RdTkt ){ + @ <label><input type="checkbox" name="st" %s(PCK("st"))> \ + @ Ticket changes</label><br> + } + if( g.perm.RdWiki ){ + @ <label><input type="checkbox" name="sw" %s(PCK("sw"))> \ + @ Wiki</label><br> + } + di = PB("di"); + @ </td></tr> + @ <tr> + @ <td class="form_label">Delivery:</td> + @ <td><select size="1" name="di"> + @ <option value="0" %s(di?"":"selected")>Individual Emails</option> + @ <option value="1" %s(di?"selected":"")>Daily Digest</option> + @ </select></td> + @ </tr> + if( g.perm.Admin ){ + @ <tr> + @ <td class="form_label">Admin Options:</td><td> + @ <label><input type="checkbox" name="vi" %s(PCK("vi"))> \ + @ Verified</label><br> + @ <label><input type="checkbox" name="dnc" %s(PCK("dnc"))> \ + @ Do not call</label></td></tr> + } + @ <tr> + @ <td></td> + if( needCaptcha && !alert_enabled() ){ + @ <td><input type="submit" name="submit" value="Submit" disabled> + @ (Email current disabled)</td> + }else{ + @ <td><input type="submit" name="submit" value="Submit"></td> + } + @ </tr> + @ </table> + if( needCaptcha ){ + @ <div class="captcha"><table class="captcha"><tr><td><pre class="captcha"> + @ %h(zCaptcha) + @ </pre> + @ Enter the 8 characters above in the "Security Code" box<br/> + @ </td></tr></table></div> + } + @ </form> + fossil_free(zErr); + style_finish_page(); +} + +/* +** Either shutdown or completely delete a subscription entry given +** by the hex value zName. Then paint a webpage that explains that +** the entry has been removed. +*/ +static void alert_unsubscribe(int sid){ + const char *zEmail = 0; + const char *zLogin = 0; + int uid = 0; + Stmt q; + db_prepare(&q, "SELECT semail, suname FROM subscriber" + " WHERE subscriberId=%d", sid); + if( db_step(&q)==SQLITE_ROW ){ + zEmail = db_column_text(&q, 0); + zLogin = db_column_text(&q, 1); + uid = db_int(0, "SELECT uid FROM user WHERE login=%Q", zLogin); + } + style_set_current_feature("alerts"); + if( zEmail==0 ){ + style_header("Unsubscribe Fail"); + @ <p>Unable to locate a subscriber with the requested key</p> + }else{ + + db_multi_exec( + "DELETE FROM subscriber WHERE subscriberId=%d", sid + ); + style_header("Unsubscribed"); + @ <p>The "%h(zEmail)" email address has been unsubscribed from all + @ notifications. All subscription records for "%h(zEmail)" have + @ been purged. No further emails will be sent to "%h(zEmail)".</p> + if( uid && g.perm.Admin ){ + @ <p>You may also want to + @ <a href="%R/setup_uedit?id=%d(uid)">edit or delete + @ the corresponding user "%h(zLogin)"</a></p> + } + } + db_finalize(&q); + style_finish_page(); + return; +} + +/* +** WEBPAGE: alerts +** +** Edit email alert and notification settings. +** +** The subscriber is identified in several ways: +** +** * The name= query parameter contains the complete subscriberCode. +** This only happens when the user receives a verification +** email and clicks on the link in the email. When a +** compilete subscriberCode is seen on the name= query parameter, +** that constitutes verification of the email address. +** +** * The sid= query parameter contains an integer subscriberId. +** This only works for the administrator. It allows the +** administrator to edit any subscription. +** +** * The user is logged into an account other than "nobody" or +** "anonymous". In that case the notification settings +** associated with that account can be edited without needing +** to know the subscriber code. +** +** * The name= query parameter contains a 32-digit prefix of +** subscriber code. (Subscriber codes are normally 64 hex digits +** in length.) This uniquely identifies the subscriber without +** revealing the complete subscriber code, and hence without +** verifying the email address. +*/ +void alert_page(void){ + const char *zName = 0; /* Value of the name= query parameter */ + Stmt q; /* For querying the database */ + int sa, sc, sf, st, sw, sx; /* Types of notifications requested */ + int sdigest = 0, sdonotcall = 0, sverified = 0; /* Other fields */ + int isLogin; /* True if logged in as an individual */ + const char *ssub = 0; /* Subscription flags */ + const char *semail = 0; /* Email address */ + const char *smip; /* */ + const char *suname = 0; /* Corresponding user.login value */ + const char *mtime; /* */ + const char *sctime; /* Time subscription created */ + int eErr = 0; /* Type of error */ + char *zErr = 0; /* Error message text */ + int sid = 0; /* Subscriber ID */ + int nName; /* Length of zName in bytes */ + char *zHalfCode; /* prefix of subscriberCode */ + int keepAlive = 0; /* True to update the last contact time */ + + db_begin_transaction(); + if( alert_webpages_disabled() ){ + db_commit_transaction(); + return; + } + login_check_credentials(); + if( !g.perm.EmailAlert ){ + db_commit_transaction(); + login_needed(g.anon.EmailAlert); + /*NOTREACHED*/ + } + isLogin = login_is_individual(); + zName = P("name"); + nName = zName ? (int)strlen(zName) : 0; + if( g.perm.Admin && P("sid")!=0 ){ + sid = atoi(P("sid")); + } + if( sid==0 && nName>=32 ){ + sid = db_int(0, + "SELECT CASE WHEN hex(subscriberCode) LIKE (%Q||'%%')" + " THEN subscriberId ELSE 0 END" + " FROM subscriber WHERE subscriberCode>=hextoblob(%Q)" + " LIMIT 1", zName, zName); + if( sid ) keepAlive = 1; + } + if( sid==0 && isLogin ){ + sid = db_int(0, "SELECT subscriberId FROM subscriber" + " WHERE suname=%Q", g.zLogin); + } + if( sid==0 ){ + db_commit_transaction(); + cgi_redirect("subscribe"); + /*NOTREACHED*/ + } + alert_submenu_common(); + if( P("submit")!=0 && cgi_csrf_safe(1) ){ + char newSsub[10]; + int nsub = 0; + Blob update; + + sdonotcall = PB("sdonotcall"); + sdigest = PB("sdigest"); + semail = P("semail"); + if( PB("sa") ) newSsub[nsub++] = 'a'; + if( g.perm.Read && PB("sc") ) newSsub[nsub++] = 'c'; + if( g.perm.RdForum && PB("sf") ) newSsub[nsub++] = 'f'; + if( g.perm.RdTkt && PB("st") ) newSsub[nsub++] = 't'; + if( g.perm.RdWiki && PB("sw") ) newSsub[nsub++] = 'w'; + if( g.perm.RdForum && PB("sx") ) newSsub[nsub++] = 'x'; + newSsub[nsub] = 0; + ssub = newSsub; + blob_init(&update, "UPDATE subscriber SET", -1); + blob_append_sql(&update, + " sdonotcall=%d," + " sdigest=%d," + " ssub=%Q," + " mtime=now()," + " lastContact=now()/86400," + " smip=%Q", + sdonotcall, + sdigest, + ssub, + g.zIpAddr + ); + if( g.perm.Admin ){ + suname = PT("suname"); + sverified = PB("sverified"); + if( suname && suname[0]==0 ) suname = 0; + blob_append_sql(&update, + ", suname=%Q," + " sverified=%d", + suname, + sverified + ); + } + if( isLogin ){ + if( semail==0 || email_address_is_valid(semail,0)==0 ){ + eErr = 8; + } + blob_append_sql(&update, ", semail=%Q", semail); + } + blob_append_sql(&update," WHERE subscriberId=%d", sid); + if( eErr==0 ){ + db_exec_sql(blob_str(&update)); + ssub = 0; + } + blob_reset(&update); + }else if( keepAlive ){ + db_multi_exec( + "UPDATE subscriber SET lastContact=now()/86400" + " WHERE subscriberId=%d", sid + ); + } + if( P("delete")!=0 && cgi_csrf_safe(1) ){ + if( !PB("dodelete") ){ + eErr = 9; + zErr = mprintf("Select this checkbox and press \"Unsubscribe\" again to" + " unsubscribe"); + }else{ + alert_unsubscribe(sid); + db_commit_transaction(); + return; + } + } + style_set_current_feature("alerts"); + style_header("Update Subscription"); + db_prepare(&q, + "SELECT" + " semail," /* 0 */ + " sverified," /* 1 */ + " sdonotcall," /* 2 */ + " sdigest," /* 3 */ + " ssub," /* 4 */ + " smip," /* 5 */ + " suname," /* 6 */ + " datetime(mtime,'unixepoch')," /* 7 */ + " datetime(sctime,'unixepoch')," /* 8 */ + " hex(subscriberCode)," /* 9 */ + " date(coalesce(lastContact*86400,mtime),'unixepoch')," /* 10 */ + " now()/86400 - coalesce(lastContact,mtime/86400)" /* 11 */ + " FROM subscriber WHERE subscriberId=%d", sid); + if( db_step(&q)!=SQLITE_ROW ){ + db_finalize(&q); + db_commit_transaction(); + cgi_redirect("subscribe"); + /*NOTREACHED*/ + } + if( ssub==0 ){ + semail = db_column_text(&q, 0); + sdonotcall = db_column_int(&q, 2); + sdigest = db_column_int(&q, 3); + ssub = db_column_text(&q, 4); + } + if( suname==0 ){ + suname = db_column_text(&q, 6); + sverified = db_column_int(&q, 1); + } + sa = strchr(ssub,'a')!=0; + sc = strchr(ssub,'c')!=0; + sf = strchr(ssub,'f')!=0; + st = strchr(ssub,'t')!=0; + sw = strchr(ssub,'w')!=0; + sx = strchr(ssub,'x')!=0; + smip = db_column_text(&q, 5); + mtime = db_column_text(&q, 7); + sctime = db_column_text(&q, 8); + if( !g.perm.Admin && !sverified ){ + if( nName==64 ){ + db_multi_exec( + "UPDATE subscriber SET sverified=1" + " WHERE subscriberCode=hextoblob(%Q)", + zName); + if( db_get_boolean("selfreg-verify",0) ){ + char *zNewCap = db_get("default-perms","u"); + db_unprotect(PROTECT_USER); + db_multi_exec( + "UPDATE user" + " SET cap=%Q" + " WHERE cap='7' AND login=(" + " SELECT suname FROM subscriber" + " WHERE subscriberCode=hextoblob(%Q))", + zNewCap, zName + ); + db_protect_pop(); + login_set_capabilities(zNewCap, 0); + } + @ <h1>Your email alert subscription has been verified!</h1> + @ <p>Use the form below to update your subscription information.</p> + @ <p>Hint: Bookmark this page so that you can more easily update + @ your subscription information in the future</p> + }else{ + @ <h2>Your email address is unverified</h2> + @ <p>You should have received an email message containing a link + @ that you must visit to verify your account. No email notifications + @ will be sent until your email address has been verified.</p> + } + }else{ + @ <p>Make changes to the email subscription shown below and + @ press "Submit".</p> + } + form_begin(0, "%R/alerts"); + zHalfCode = db_text("x","SELECT hex(substr(subscriberCode,1,16))" + " FROM subscriber WHERE subscriberId=%d", sid); + @ <input type="hidden" name="name" value="%h(zHalfCode)"> + @ <table class="subscribe"> + @ <tr> + @ <td class="form_label">Email Address:</td> + if( isLogin ){ + @ <td><input type="text" name="semail" value="%h(semail)" size="30">\ + if( eErr==8 ){ + @ <span class='loginError'>← not a valid email address!</span> + }else if( g.perm.Admin ){ + @   <a href="%R/announce?to=%t(semail)">\ + @ (Send a message to %h(semail))</a>\ + } + @ </td> + }else{ + @ <td>%h(semail)</td> + } + @ </tr> + if( g.perm.Admin ){ + int uid; + @ <tr> + @ <td class='form_label'>Created:</td> + @ <td>%h(sctime)</td> + @ </tr> + @ <tr> + @ <td class='form_label'>Last Modified:</td> + @ <td>%h(mtime)</td> + @ </tr> + @ <tr> + @ <td class='form_label'>IP Address:</td> + @ <td>%h(smip)</td> + @ </tr> + @ <tr> + @ <td class='form_label'>Subscriber Code:</td> + @ <td>%h(db_column_text(&q,9))</td> + @ <tr> + @ <tr> + @ <td class='form_label'>Last Contact:</td> + @ <td>%h(db_column_text(&q,10)) ← \ + @ %,d(db_column_int(&q,11)) days ago</td> + @ </tr> + @ <td class="form_label">User:</td> + @ <td><input type="text" name="suname" value="%h(suname?suname:"")" \ + @ size="30">\ + uid = db_int(0, "SELECT uid FROM user WHERE login=%Q", suname); + if( uid ){ + @   <a href='%R/setup_uedit?id=%d(uid)'>\ + @ (login info for %h(suname))</a>\ + } + @ </tr> + } + @ <tr> + @ <td class="form_label">Topics:</td> + @ <td><label><input type="checkbox" name="sa" %s(sa?"checked":"")>\ + @ Announcements</label><br> + if( g.perm.Read ){ + @ <label><input type="checkbox" name="sc" %s(sc?"checked":"")>\ + @ Check-ins</label><br> + } + if( g.perm.RdForum ){ + @ <label><input type="checkbox" name="sf" %s(sf?"checked":"")>\ + @ Forum Posts</label><br> + @ <label><input type="checkbox" name="sx" %s(sx?"checked":"")>\ + @ Forum Edits</label><br> + } + if( g.perm.RdTkt ){ + @ <label><input type="checkbox" name="st" %s(st?"checked":"")>\ + @ Ticket changes</label><br> + } + if( g.perm.RdWiki ){ + @ <label><input type="checkbox" name="sw" %s(sw?"checked":"")>\ + @ Wiki</label> + } + @ </td></tr> + @ <tr> + @ <td class="form_label">Delivery:</td> + @ <td><select size="1" name="sdigest"> + @ <option value="0" %s(sdigest?"":"selected")>Individual Emails</option> + @ <option value="1" %s(sdigest?"selected":"")>Daily Digest</option> + @ </select></td> + @ </tr> + if( g.perm.Admin ){ + @ <tr> + @ <td class="form_label">Admin Options:</td><td> + @ <label><input type="checkbox" name="sdonotcall" \ + @ %s(sdonotcall?"checked":"")> Do not disturb</label><br> + @ <label><input type="checkbox" name="sverified" \ + @ %s(sverified?"checked":"")>\ + @ Verified</label></td></tr> + } + if( eErr==9 ){ + @ <tr> + @ <td class="form_label">Verify:</td><td> + @ <label><input type="checkbox" name="dodelete"> + @ Unsubscribe</label> + @ <span class="loginError">← %h(zErr)</span> + @ </td></tr> + } + @ <tr> + @ <td></td> + @ <td><input type="submit" name="submit" value="Submit"> + @ <input type="submit" name="delete" value="Unsubscribe"> + @ </tr> + @ </table> + @ </form> + fossil_free(zErr); + db_finalize(&q); + style_finish_page(); + db_commit_transaction(); + return; +} + +/* +** WEBPAGE: renew +** +** Users visit this page to update the last-contact date on their +** subscription. The last-contact date is the day that the subscriber +** last interacted with the repository. If the name= query parameter +** (or POST parameter) contains a valid subscriber code, then the last-contact +** subscription associated with that subscriber code is updated to be the +** current date. +*/ +void renewal_page(void){ + const char *zName = P("name"); + int iInterval = db_get_int("email-renew-interval", 0); + Stmt s; + int rc; + + style_header("Subscription Renewal"); + if( zName==0 || strlen(zName)<4 ){ + @ <p>No subscription specified</p> + style_finish_page(); + return; + } + + if( !db_table_has_column("repository","subscriber","lastContact") + || iInterval<1 + ){ + @ <p>This repository does not expire email notification subscriptions. + @ No renewals are necessary.</p> + style_finish_page(); + return; + } + + db_prepare(&s, + "UPDATE subscriber" + " SET lastContact=now()/86400" + " WHERE subscriberCode=hextoblob(%Q)" + " RETURNING semail, date('now','+%d days');", + zName, iInterval+1 + ); + rc = db_step(&s); + if( rc==SQLITE_ROW ){ + @ <p>The email notification subscription for %h(db_column_text(&s,0)) + @ has been extended until %h(db_column_text(&s,1)) UTC. + }else{ + @ <p>No such subscriber-id: %h(zName)</p> + } + db_finalize(&s); + style_finish_page(); +} + + +/* This is the message that gets sent to describe how to change +** or modify a subscription +*/ +static const char zUnsubMsg[] = +@ To changes your subscription settings at %s visit this link: +@ +@ %s/alerts/%s +@ +@ To completely unsubscribe from %s, visit the following link: +@ +@ %s/unsubscribe/%s +; + +/* +** WEBPAGE: unsubscribe +** +** Users visit this page to be delisted from email alerts. +** +** If a valid subscriber code is supplied in the name= query parameter, +** then that subscriber is delisted. +** +** Otherwise, If the users is logged in, then they are redirected +** to the /alerts page where they have an unsubscribe button. +** +** Non-logged-in users with no name= query parameter are invited to enter +** an email address to which will be sent the unsubscribe link that +** contains the correct subscriber code. +*/ +void unsubscribe_page(void){ + const char *zName = P("name"); + char *zErr = 0; + int eErr = 0; + unsigned int uSeed = 0; + const char *zDecoded; + char *zCaptcha = 0; + int dx; + int bSubmit; + const char *zEAddr; + char *zCode = 0; + int sid = 0; + + /* If a valid subscriber code is supplied, then unsubscribe immediately. + */ + if( zName + && (sid = db_int(0, "SELECT subscriberId FROM subscriber" + " WHERE subscriberCode=hextoblob(%Q)", zName))!=0 + ){ + alert_unsubscribe(sid); + return; + } + + /* Logged in users are redirected to the /alerts page */ + login_check_credentials(); + if( login_is_individual() ){ + cgi_redirectf("%R/alerts"); + return; + } + + style_set_current_feature("alerts"); + + zEAddr = PD("e",""); + dx = atoi(PD("dx","0")); + bSubmit = P("submit")!=0 && P("e")!=0 && cgi_csrf_safe(1); + if( bSubmit ){ + if( !captcha_is_correct(1) ){ + eErr = 2; + zErr = mprintf("enter the security code shown below"); + bSubmit = 0; + } + } + if( bSubmit ){ + zCode = db_text(0,"SELECT hex(subscriberCode) FROM subscriber" + " WHERE semail=%Q", zEAddr); + if( zCode==0 ){ + eErr = 1; + zErr = mprintf("not a valid email address"); + bSubmit = 0; + } + } + if( bSubmit ){ + /* If we get this far, it means that a valid unsubscribe request has + ** been submitted. Send the appropriate email. */ + Blob hdr, body; + AlertSender *pSender = alert_sender_new(0,0); + blob_init(&hdr,0,0); + blob_init(&body,0,0); + blob_appendf(&hdr, "To: <%s>\r\n", zEAddr); + blob_appendf(&hdr, "Subject: Unsubscribe Instructions\r\n"); + blob_appendf(&body, zUnsubMsg/*works-like:"%s%s%s%s%s%s"*/, + g.zBaseURL, g.zBaseURL, zCode, g.zBaseURL, g.zBaseURL, zCode); + alert_send(pSender, &hdr, &body, 0); + style_header("Unsubscribe Instructions Sent"); + if( pSender->zErr ){ + @ <h1>Internal Error</h1> + @ <p>The following error was encountered while trying to send an + @ email to %h(zEAddr): + @ <blockquote><pre> + @ %h(pSender->zErr) + @ </pre></blockquote> + }else{ + @ <p>An email has been sent to "%h(zEAddr)" that explains how to + @ unsubscribe and/or modify your subscription settings</p> + } + alert_sender_free(pSender); + style_finish_page(); + return; + } + + /* Non-logged-in users have to enter an email address to which is + ** sent a message containing the unsubscribe link. + */ + style_header("Unsubscribe Request"); + @ <p>Fill out the form below to request an email message that will + @ explain how to unsubscribe and/or change your subscription settings.</p> + @ + form_begin(0, "%R/unsubscribe"); + @ <table class="subscribe"> + @ <tr> + @ <td class="form_label">Email Address:</td> + @ <td><input type="text" name="e" value="%h(zEAddr)" size="30"></td> + if( eErr==1 ){ + @ <td><span class="loginError">← %h(zErr)</span></td> + } + @ </tr> + uSeed = captcha_seed(); + zDecoded = captcha_decode(uSeed); + zCaptcha = captcha_render(zDecoded); + @ <tr> + @ <td class="form_label">Security Code:</td> + @ <td><input type="text" name="captcha" value="" size="30"> + captcha_speakit_button(uSeed, "Speak the code"); + @ <input type="hidden" name="captchaseed" value="%u(uSeed)"></td> + if( eErr==2 ){ + @ <td><span class="loginError">← %h(zErr)</span></td> + } + @ </tr> + @ <tr> + @ <td class="form_label">Options:</td> + @ <td><label><input type="radio" name="dx" value="0" %s(dx?"":"checked")>\ + @ Modify subscription</label><br> + @ <label><input type="radio" name="dx" value="1" %s(dx?"checked":"")>\ + @ Completely unsubscribe</label><br> + @ <tr> + @ <td></td> + @ <td><input type="submit" name="submit" value="Submit"></td> + @ </tr> + @ </table> + @ <div class="captcha"><table class="captcha"><tr><td><pre class="captcha"> + @ %h(zCaptcha) + @ </pre> + @ Enter the 8 characters above in the "Security Code" box<br/> + @ </td></tr></table></div> + @ </form> + fossil_free(zErr); + style_finish_page(); +} + +/* +** WEBPAGE: subscribers +** +** This page, accessible to administrators only, +** shows a list of subscriber email addresses. +** Clicking on an email takes one to the /alerts page +** for that email where the delivery settings can be +** modified. +*/ +void subscriber_list_page(void){ + Blob sql; + Stmt q; + sqlite3_int64 iNow; + int nTotal; + int nPending; + int nDel = 0; + int iCutoff = db_get_int("email-renew-cutoff",0); + int iWarning = db_get_int("email-renew-warning",0); + char zCutoffClr[8]; + char zWarnClr[8]; + if( alert_webpages_disabled() ) return; + login_check_credentials(); + if( !g.perm.Admin ){ + login_needed(0); + return; + } + alert_submenu_common(); + style_submenu_element("Users","setup_ulist"); + style_set_current_feature("alerts"); + style_header("Subscriber List"); + nTotal = db_int(0, "SELECT count(*) FROM subscriber"); + nPending = db_int(0, "SELECT count(*) FROM subscriber WHERE NOT sverified"); + if( nPending>0 && P("purge") && cgi_csrf_safe(0) ){ + int nNewPending; + db_multi_exec( + "DELETE FROM subscriber" + " WHERE NOT sverified AND mtime<now()-86400" + ); + nNewPending = db_int(0, "SELECT count(*) FROM subscriber" + " WHERE NOT sverified"); + nDel = nPending - nNewPending; + nPending = nNewPending; + nTotal -= nDel; + } + if( nPending>0 ){ + @ <h1>%,d(nTotal) Subscribers, %,d(nPending) Pending</h1> + if( nDel==0 && 0<db_int(0,"SELECT count(*) FROM subscriber" + " WHERE NOT sverified AND mtime<now()-86400") + ){ + style_submenu_element("Purge Pending","subscribers?purge"); + } + }else{ + @ <h1>%,d(nTotal) Subscribers</h1> + } + if( nDel>0 ){ + @ <p>*** %d(nDel) pending subscriptions deleted ***</p> + } + blob_init(&sql, 0, 0); + blob_append_sql(&sql, + "SELECT subscriberId," /* 0 */ + " semail," /* 1 */ + " ssub," /* 2 */ + " suname," /* 3 */ + " sverified," /* 4 */ + " sdigest," /* 5 */ + " mtime," /* 6 */ + " date(sctime,'unixepoch')," /* 7 */ + " (SELECT uid FROM user WHERE login=subscriber.suname)," /* 8 */ + " coalesce(lastContact,mtime/86400)" /* 9 */ + " FROM subscriber" + ); + if( P("only")!=0 ){ + blob_append_sql(&sql, " WHERE ssub LIKE '%%%q%%'", P("only")); + style_submenu_element("Show All","%R/subscribers"); + } + blob_append_sql(&sql," ORDER BY mtime DESC"); + db_prepare_blob(&q, &sql); + iNow = time(0); + memcpy(zCutoffClr, hash_color("A"), sizeof(zCutoffClr)); + memcpy(zWarnClr, hash_color("HIJ"), sizeof(zWarnClr)); + @ <table border='1' class='sortable' \ + @ data-init-sort='6' data-column-types='tttttKKt'> + @ <thead> + @ <tr> + @ <th>Email + @ <th>Events + @ <th>Digest-Only? + @ <th>User + @ <th>Verified? + @ <th>Last change + @ <th>Last contact + @ <th>Created + @ </tr> + @ </thead><tbody> + while( db_step(&q)==SQLITE_ROW ){ + sqlite3_int64 iMtime = db_column_int64(&q, 6); + double rAge = (iNow - iMtime)/86400.0; + int uid = db_column_int(&q, 8); + const char *zUname = db_column_text(&q, 3); + sqlite3_int64 iContact = db_column_int64(&q, 9); + double rContact = (iNow/86400) - iContact; + @ <tr> + @ <td><a href='%R/alerts?sid=%d(db_column_int(&q,0))'>\ + @ %h(db_column_text(&q,1))</a></td> + @ <td>%h(db_column_text(&q,2))</td> + @ <td>%s(db_column_int(&q,5)?"digest":"")</td> + if( uid ){ + @ <td><a href='%R/setup_uedit?id=%d(uid)'>%h(zUname)</a> + }else{ + @ <td>%h(zUname)</td> + } + @ <td>%s(db_column_int(&q,4)?"yes":"pending")</td> + @ <td data-sortkey='%010llx(iMtime)'>%z(human_readable_age(rAge))</td> + @ <td data-sortkey='%010llx(iContact)'>\ + if( iContact>iWarning ){ + @ <span>\ + }else if( iContact>iCutoff ){ + @ <span style='background-color:%s(zWarnClr);'>\ + }else{ + @ <span style='background-color:%s(zCutoffClr);'>\ + } + @ %z(human_readable_age(rContact))</td> + @ <td>%h(db_column_text(&q,7))</td> + @ </tr> + } + @ </tbody></table> + db_finalize(&q); + style_table_sorter(); + style_finish_page(); +} + +#if LOCAL_INTERFACE +/* +** A single event that might appear in an alert is recorded as an +** instance of the following object. +** +** type values: +** +** c A new check-in +** f An original forum post +** x An edit to a prior forum post +** t A new ticket or a change to an existing ticket +** w A change to a wiki page +*/ +struct EmailEvent { + int type; /* 'c', 'f', 't', 'w', 'x' */ + int needMod; /* Pending moderator approval */ + Blob hdr; /* Header content, for forum entries */ + Blob txt; /* Text description to appear in an alert */ + char *zFromName; /* Human name of the sender */ + EmailEvent *pNext; /* Next in chronological order */ +}; +#endif + +/* +** Free a linked list of EmailEvent objects +*/ +void alert_free_eventlist(EmailEvent *p){ + while( p ){ + EmailEvent *pNext = p->pNext; + blob_reset(&p->txt); + blob_reset(&p->hdr); + fossil_free(p->zFromName); + fossil_free(p); + p = pNext; + } +} + +/* +** Compute and return a linked list of EmailEvent objects +** corresponding to the current content of the temp.wantalert +** table which should be defined as follows: +** +** CREATE TEMP TABLE wantalert(eventId TEXT, needMod BOOLEAN); +*/ +EmailEvent *alert_compute_event_text(int *pnEvent, int doDigest){ + Stmt q; + EmailEvent *p; + EmailEvent anchor; + EmailEvent *pLast; + const char *zUrl = db_get("email-url","http://localhost:8080"); + const char *zFrom; + const char *zSub; + + + /* First do non-forum post events */ + db_prepare(&q, + "SELECT" + " CASE WHEN event.type='t'" + " THEN (SELECT substr(tagname,5) FROM tag" + " WHERE tagid=event.tagid AND tagname LIKE 'tkt-%%')" + " ELSE blob.uuid END," /* 0 */ + " datetime(event.mtime)," /* 1 */ + " coalesce(ecomment,comment)" + " || ' (user: ' || coalesce(euser,user,'?')" + " || (SELECT case when length(x)>0 then ' tags: ' || x else '' end" + " FROM (SELECT group_concat(substr(tagname,5), ', ') AS x" + " FROM tag, tagxref" + " WHERE tagname GLOB 'sym-*' AND tag.tagid=tagxref.tagid" + " AND tagxref.rid=blob.rid AND tagxref.tagtype>0))" + " || ')' as comment," /* 2 */ + " wantalert.eventId," /* 3 */ + " wantalert.needMod" /* 4 */ + " FROM temp.wantalert, event, blob" + " WHERE blob.rid=event.objid" + " AND event.objid=substr(wantalert.eventId,2)+0" + " AND (%d OR eventId NOT GLOB 'f*')" + " ORDER BY event.mtime", + doDigest + ); + memset(&anchor, 0, sizeof(anchor)); + pLast = &anchor; + *pnEvent = 0; + while( db_step(&q)==SQLITE_ROW ){ + const char *zType = ""; + const char *zComment = db_column_text(&q, 2); + p = fossil_malloc( sizeof(EmailEvent) ); + pLast->pNext = p; + pLast = p; + p->type = db_column_text(&q, 3)[0]; + p->needMod = db_column_int(&q, 4); + p->zFromName = 0; + p->pNext = 0; + switch( p->type ){ + case 'c': zType = "Check-In"; break; + /* case 'f': -- forum posts omitted from this loop. See below */ + case 't': zType = "Ticket Change"; break; + case 'w': { + zType = "Wiki Edit"; + switch( zComment ? *zComment : 0 ){ + case ':': ++zComment; break; + case '+': zType = "Wiki Added"; ++zComment; break; + case '-': zType = "Wiki Removed"; ++zComment; break; + } + break; + } + } + blob_init(&p->hdr, 0, 0); + blob_init(&p->txt, 0, 0); + blob_appendf(&p->txt,"== %s %s ==\n%s\n%s/info/%.20s\n", + db_column_text(&q,1), + zType, + zComment, + zUrl, + db_column_text(&q,0) + ); + if( p->needMod ){ + blob_appendf(&p->txt, + "** Pending moderator approval (%s/modreq) **\n", + zUrl + ); + } + (*pnEvent)++; + } + db_finalize(&q); + + /* Early-out if forumpost is not a table in this repository */ + if( !db_table_exists("repository","forumpost") ){ + return anchor.pNext; + } + + /* For digests, the previous loop also handled forumposts already */ + if( doDigest ){ + return anchor.pNext; + } + + /* If we reach this point, it means that forumposts exist and this + ** is a normal email alert. Construct full-text forum post alerts + ** using a format that enables them to be sent as separate emails. + */ + db_prepare(&q, + "SELECT" + " forumpost.fpid," /* 0: fpid */ + " (SELECT uuid FROM blob WHERE rid=forumpost.fpid)," /* 1: hash */ + " datetime(event.mtime)," /* 2: date/time */ + " substr(comment,instr(comment,':')+2)," /* 3: comment */ + " (WITH thread(fpid,fprev) AS (" + " SELECT fpid,fprev FROM forumpost AS tx" + " WHERE tx.froot=forumpost.froot)," + " basepid(fpid,bpid) AS (" + " SELECT fpid, fpid FROM thread WHERE fprev IS NULL" + " UNION ALL" + " SELECT thread.fpid, basepid.bpid FROM basepid, thread" + " WHERE basepid.fpid=thread.fprev)" + " SELECT uuid FROM blob, basepid" + " WHERE basepid.fpid=forumpost.firt" + " AND blob.rid=basepid.bpid)," /* 4: in-reply-to */ + " wantalert.needMod," /* 5: moderated */ + " coalesce(display_name(info),euser,user)," /* 6: user */ + " forumpost.fprev IS NULL" /* 7: is an edit */ + " FROM temp.wantalert, event, forumpost" + " LEFT JOIN user ON (login=coalesce(euser,user))" + " WHERE event.objid=substr(wantalert.eventId,2)+0" + " AND eventId GLOB 'f*'" + " AND forumpost.fpid=event.objid" + " ORDER BY event.mtime" + ); + zFrom = db_get("email-self",0); + zSub = db_get("email-subname",""); + while( db_step(&q)==SQLITE_ROW ){ + Manifest *pPost = manifest_get(db_column_int(&q,0), CFTYPE_FORUM, 0); + const char *zIrt; + const char *zUuid; + const char *zTitle; + const char *z; + if( pPost==0 ) continue; + p = fossil_malloc( sizeof(EmailEvent) ); + pLast->pNext = p; + pLast = p; + p->type = db_column_int(&q,7) ? 'f' : 'x'; + p->needMod = db_column_int(&q, 5); + z = db_column_text(&q,6); + p->zFromName = z && z[0] ? fossil_strdup(z) : 0; + p->pNext = 0; + blob_init(&p->hdr, 0, 0); + zUuid = db_column_text(&q, 1); + zTitle = db_column_text(&q, 3); + if( p->needMod ){ + blob_appendf(&p->hdr, "Subject: %s Pending Moderation: %s\r\n", + zSub, zTitle); + }else{ + blob_appendf(&p->hdr, "Subject: %s %s\r\n", zSub, zTitle); + blob_appendf(&p->hdr, "Message-Id: <%.32s@%s>\r\n", + zUuid, alert_hostname(zFrom)); + zIrt = db_column_text(&q, 4); + if( zIrt && zIrt[0] ){ + blob_appendf(&p->hdr, "In-Reply-To: <%.32s@%s>\r\n", + zIrt, alert_hostname(zFrom)); + } + } + blob_init(&p->txt, 0, 0); + if( p->needMod ){ + blob_appendf(&p->txt, + "** Pending moderator approval (%s/modreq) **\n", + zUrl + ); + } + blob_appendf(&p->txt, + "Forum post by %s on %s\n", + pPost->zUser, db_column_text(&q, 2)); + blob_appendf(&p->txt, "%s/forumpost/%S\n\n", zUrl, zUuid); + blob_append(&p->txt, pPost->zWiki, -1); + manifest_destroy(pPost); + (*pnEvent)++; + } + db_finalize(&q); + + return anchor.pNext; +} + +/* +** Put a header on an alert email +*/ +void email_header(Blob *pOut){ + blob_appendf(pOut, + "This is an automated email reporting changes " + "on Fossil repository %s (%s/timeline)\n", + db_get("email-subname","(unknown)"), + db_get("email-url","http://localhost:8080")); +} + +/* +** COMMAND: test-alert +** +** Usage: %fossil test-alert EVENTID ... +** +** Generate the text of an email alert for all of the EVENTIDs +** listed on the command-line. Or if no events are listed on the +** command line, generate text for all events named in the +** pending_alert table. The text of the email alerts appears on +** standard output. +** +** This command is intended for testing and debugging Fossil itself, +** for example when enhancing the email alert system or fixing bugs +** in the email alert system. If you are not making changes to the +** Fossil source code, this command is probably not useful to you. +** +** EVENTIDs are text. The first character is 'c', 'f', 't', or 'w' +** for check-in, forum, ticket, or wiki. The remaining text is a +** integer that references the EVENT.OBJID value for the event. +** Run /timeline?showid to see these OBJID values. +** +** Options: +** +** --digest Generate digest alert text +** --needmod Assume all events are pending moderator approval +*/ +void test_alert_cmd(void){ + Blob out; + int nEvent; + int needMod; + int doDigest; + EmailEvent *pEvent, *p; + + doDigest = find_option("digest",0,0)!=0; + needMod = find_option("needmod",0,0)!=0; + db_find_and_open_repository(0, 0); + verify_all_options(); + db_begin_transaction(); + alert_schema(0); + db_multi_exec("CREATE TEMP TABLE wantalert(eventid TEXT, needMod BOOLEAN)"); + if( g.argc==2 ){ + db_multi_exec( + "INSERT INTO wantalert(eventId,needMod)" + " SELECT eventid, %d FROM pending_alert", needMod); + }else{ + int i; + for(i=2; i<g.argc; i++){ + db_multi_exec("INSERT INTO wantalert(eventId,needMod) VALUES(%Q,%d)", + g.argv[i], needMod); + } + } + blob_init(&out, 0, 0); + email_header(&out); + pEvent = alert_compute_event_text(&nEvent, doDigest); + for(p=pEvent; p; p=p->pNext){ + blob_append(&out, "\n", 1); + if( blob_size(&p->hdr) ){ + blob_append(&out, blob_buffer(&p->hdr), blob_size(&p->hdr)); + blob_append(&out, "\n", 1); + } + blob_append(&out, blob_buffer(&p->txt), blob_size(&p->txt)); + } + alert_free_eventlist(pEvent); + fossil_print("%s", blob_str(&out)); + blob_reset(&out); + db_end_transaction(0); +} + +/* +** COMMAND: test-add-alerts +** +** Usage: %fossil test-add-alerts [OPTIONS] EVENTID ... +** +** Add one or more events to the pending_alert queue. Use this +** command during testing to force email notifications for specific +** events. +** +** EVENTIDs are text. The first character is 'c', 'f', 't', or 'w' +** for check-in, forum, ticket, or wiki. The remaining text is a +** integer that references the EVENT.OBJID value for the event. +** Run /timeline?showid to see these OBJID values. +** +** Options: +** +** --backoffice Run alert_backoffice() after all alerts have +** been added. This will cause the alerts to be +** sent out with the SENDALERT_TRACE option. +** +** --debug Like --backoffice, but add the SENDALERT_STDOUT +** so that emails are printed to standard output +** rather than being sent. +** +** --digest Process emails using SENDALERT_DIGEST +*/ +void test_add_alert_cmd(void){ + int i; + int doAuto = find_option("backoffice",0,0)!=0; + unsigned mFlags = 0; + if( find_option("debug",0,0)!=0 ){ + doAuto = 1; + mFlags = SENDALERT_STDOUT; + } + if( find_option("digest",0,0)!=0 ){ + mFlags |= SENDALERT_DIGEST; + } + db_find_and_open_repository(0, 0); + verify_all_options(); + db_begin_write(); + alert_schema(0); + for(i=2; i<g.argc; i++){ + db_multi_exec("REPLACE INTO pending_alert(eventId) VALUES(%Q)", g.argv[i]); + } + db_end_transaction(0); + if( doAuto ){ + alert_backoffice(SENDALERT_TRACE|mFlags); + } +} + +/* +** Minimum number of days between renewal messages +*/ +#define ALERT_RENEWAL_MSG_FREQUENCY 7 /* Do renewals at most once/week */ + +/* +** Construct the header and body for an email message that will alert +** a subscriber that their subscriptions are about to expire. +*/ +static void alert_renewal_msg( + Blob *pHdr, /* Write email header here */ + Blob *pBody, /* Write email body here */ + const char *zCode, /* The subscriber code */ + int lastContact, /* Last contact (days since 1970) */ + const char *zEAddr, /* Subscriber email address. Send to this. */ + const char *zSub, /* Subscription codes */ + const char *zRepoName, /* Name of the sending Fossil repostory */ + const char *zUrl /* URL for the sending Fossil repostory */ +){ + blob_appendf(pHdr,"To: <%s>\r\n", zEAddr); + blob_appendf(pHdr,"Subject: %s Subscription to %s expires soon\r\n", + zRepoName, zUrl); + blob_appendf(pBody, + "\nTo renew your subscription, click the following link:\n" + "\n %s/renew/%s\n\n", + zUrl, zCode + ); + blob_appendf(pBody, + "You are currently receiving email notification for the following events\n" + "on the %s Fossil repository at %s:\n\n", + zRepoName, zUrl + ); + if( strchr(zSub, 'a') ) blob_appendf(pBody, " * Announcements\n"); + if( strchr(zSub, 'c') ) blob_appendf(pBody, " * Check-ins\n"); + if( strchr(zSub, 'f') ) blob_appendf(pBody, " * Forum posts\n"); + if( strchr(zSub, 't') ) blob_appendf(pBody, " * Ticket changes\n"); + if( strchr(zSub, 'w') ) blob_appendf(pBody, " * Wiki changes\n"); + blob_appendf(pBody, "\n" + "If you take no action, your subscription will expire and you will be\n" + "unsubscribed in about %d days. To make other changes or to unsubscribe\n" + "immediately, visit the following webpage:\n\n" + " %s/alerts/%s\n\n", + ALERT_RENEWAL_MSG_FREQUENCY, zUrl, zCode + ); +} + +#if INTERFACE +/* +** Flags for alert_send_alerts() +*/ +#define SENDALERT_DIGEST 0x0001 /* Send a digest */ +#define SENDALERT_PRESERVE 0x0002 /* Do not mark the task as done */ +#define SENDALERT_STDOUT 0x0004 /* Print emails instead of sending */ +#define SENDALERT_TRACE 0x0008 /* Trace operation for debugging */ +#define SENDALERT_RENEWAL 0x0010 /* Send renewal notices */ + +#endif /* INTERFACE */ + +/* +** Send alert emails to subscribers. +** +** This procedure is run by either the backoffice, or in response to the +** "fossil alerts send" command. Details of operation are controlled by +** the flags parameter. +** +** Here is a summary of what happens: +** +** (1) Create a TEMP table wantalert(eventId,needMod) and fill it with +** all the events that we want to send alerts about. The needMod +** flags is set if and only if the event is still awaiting +** moderator approval. Events with the needMod flag are only +** shown to users that have moderator privileges. +** +** (2) Call alert_compute_event_text() to compute a list of EmailEvent +** objects that describe all events about which we want to send +** alerts. +** +** (3) Loop over all subscribers. Compose and send one or more email +** messages to each subscriber that describe the events for +** which the subscriber has expressed interest and has +** appropriate privileges. +** +** (4) Update the pending_alerts table to indicate that alerts have been +** sent. +** +** Update 2018-08-09: Do step (3) before step (4). Update the +** pending_alerts table *before* the emails are sent. That way, if +** the process malfunctions or crashes, some notifications may never +** be sent. But that is better than some recurring bug causing +** subscribers to be flooded with repeated notifications every 60 +** seconds! +*/ +int alert_send_alerts(u32 flags){ + EmailEvent *pEvents, *p; + int nEvent = 0; + int nSent = 0; + Stmt q; + const char *zDigest = "false"; + Blob hdr, body; + const char *zUrl; + const char *zRepoName; + const char *zFrom; + const char *zDest = (flags & SENDALERT_STDOUT) ? "stdout" : 0; + AlertSender *pSender = 0; + u32 senderFlags = 0; + int iInterval = 0; /* Subscription renewal interval */ + + if( g.fSqlTrace ) fossil_trace("-- BEGIN alert_send_alerts(%u)\n", flags); + alert_schema(0); + if( !alert_enabled() && (flags & SENDALERT_STDOUT)==0 ) goto send_alert_done; + zUrl = db_get("email-url",0); + if( zUrl==0 ) goto send_alert_done; + zRepoName = db_get("email-subname",0); + if( zRepoName==0 ) goto send_alert_done; + zFrom = db_get("email-self",0); + if( zFrom==0 ) goto send_alert_done; + if( flags & SENDALERT_TRACE ){ + senderFlags |= ALERT_TRACE; + } + pSender = alert_sender_new(zDest, senderFlags); + + /* Step (1): Compute the alerts that need sending + */ + db_multi_exec( + "DROP TABLE IF EXISTS temp.wantalert;" + "CREATE TEMP TABLE wantalert(eventId TEXT, needMod BOOLEAN, sentMod);" + ); + if( flags & SENDALERT_DIGEST ){ + /* Unmoderated changes are never sent as part of a digest */ + db_multi_exec( + "INSERT INTO wantalert(eventId,needMod)" + " SELECT eventid, 0" + " FROM pending_alert" + " WHERE sentDigest IS FALSE" + " AND NOT EXISTS(SELECT 1 FROM private WHERE rid=substr(eventid,2));" + ); + zDigest = "true"; + }else{ + /* Immediate alerts might include events that are subject to + ** moderator approval */ + db_multi_exec( + "INSERT INTO wantalert(eventId,needMod,sentMod)" + " SELECT eventid," + " EXISTS(SELECT 1 FROM private WHERE rid=substr(eventid,2))," + " sentMod" + " FROM pending_alert" + " WHERE sentSep IS FALSE;" + "DELETE FROM wantalert WHERE needMod AND sentMod;" + ); + } + + /* Step 2: compute EmailEvent objects for every notification that + ** needs sending. + */ + pEvents = alert_compute_event_text(&nEvent, (flags & SENDALERT_DIGEST)!=0); + if( nEvent==0 ) goto send_alert_expiration_warnings; + + /* Step 4a: Update the pending_alerts table to designate the + ** alerts as having all been sent. This is done *before* step (3) + ** so that a crash will not cause alerts to be sent multiple times. + ** Better a missed alert than being spammed with hundreds of alerts + ** due to a bug. + */ + if( (flags & SENDALERT_PRESERVE)==0 ){ + if( flags & SENDALERT_DIGEST ){ + db_multi_exec( + "UPDATE pending_alert SET sentDigest=true" + " WHERE eventid IN (SELECT eventid FROM wantalert);" + ); + }else{ + db_multi_exec( + "UPDATE pending_alert SET sentSep=true" + " WHERE eventid IN (SELECT eventid FROM wantalert WHERE NOT needMod);" + "UPDATE pending_alert SET sentMod=true" + " WHERE eventid IN (SELECT eventid FROM wantalert WHERE needMod);" + ); + } + } + + /* Step 3: Loop over subscribers. Send alerts + */ + blob_init(&hdr, 0, 0); + blob_init(&body, 0, 0); + db_prepare(&q, + "SELECT" + " hex(subscriberCode)," /* 0 */ + " semail," /* 1 */ + " ssub," /* 2 */ + " fullcap(user.cap)" /* 3 */ + " FROM subscriber LEFT JOIN user ON (login=suname)" + " WHERE sverified" + " AND NOT sdonotcall" + " AND sdigest IS %s" + " AND coalesce(subscriber.lastContact,subscriber.mtime)>=%d", + zDigest/*safe-for-%s*/, + db_get_int("email-renew-cutoff",0) + ); + while( db_step(&q)==SQLITE_ROW ){ + const char *zCode = db_column_text(&q, 0); + const char *zSub = db_column_text(&q, 2); + const char *zEmail = db_column_text(&q, 1); + const char *zCap = db_column_text(&q, 3); + int nHit = 0; + for(p=pEvents; p; p=p->pNext){ + if( strchr(zSub,p->type)==0 ) continue; + if( p->needMod ){ + /* For events that require moderator approval, only send an alert + ** if the recipient is a moderator for that type of event. Setup + ** and Admin users always get notified. */ + char xType = '*'; + if( strpbrk(zCap,"as")==0 ){ + switch( p->type ){ + case 'x': case 'f': xType = '5'; break; + case 't': xType = 'q'; break; + case 'w': xType = 'l'; break; + } + if( strchr(zCap,xType)==0 ) continue; + } + }else if( strchr(zCap,'s')!=0 || strchr(zCap,'a')!=0 ){ + /* Setup and admin users can get any notification that does not + ** require moderation */ + }else{ + /* Other users only see the alert if they have sufficient + ** privilege to view the event itself */ + char xType = '*'; + switch( p->type ){ + case 'c': xType = 'o'; break; + case 'x': case 'f': xType = '2'; break; + case 't': xType = 'r'; break; + case 'w': xType = 'j'; break; + } + if( strchr(zCap,xType)==0 ) continue; + } + if( blob_size(&p->hdr)>0 ){ + /* This alert should be sent as a separate email */ + Blob fhdr, fbody; + blob_init(&fhdr, 0, 0); + blob_appendf(&fhdr, "To: <%s>\r\n", zEmail); + blob_append(&fhdr, blob_buffer(&p->hdr), blob_size(&p->hdr)); + blob_init(&fbody, blob_buffer(&p->txt), blob_size(&p->txt)); + blob_appendf(&fbody, "\n-- \nSubscription info: %s/alerts/%s\n", + zUrl, zCode); + alert_send(pSender,&fhdr,&fbody,p->zFromName); + nSent++; + blob_reset(&fhdr); + blob_reset(&fbody); + }else{ + /* Events other than forum posts are gathered together into + ** a single email message */ + if( nHit==0 ){ + blob_appendf(&hdr,"To: <%s>\r\n", zEmail); + blob_appendf(&hdr,"Subject: %s activity alert\r\n", zRepoName); + blob_appendf(&body, + "This is an automated email sent by the Fossil repository " + "at %s to report changes.\n", + zUrl + ); + } + nHit++; + blob_append(&body, "\n", 1); + blob_append(&body, blob_buffer(&p->txt), blob_size(&p->txt)); + } + } + if( nHit==0 ) continue; + blob_appendf(&hdr, "List-Unsubscribe: <%s/unsubscribe/%s>\r\n", + zUrl, zCode); + blob_appendf(&hdr, "List-Unsubscribe-Post: List-Unsubscribe=One-Click\r\n"); + blob_appendf(&body,"\n-- \nSubscription info: %s/alerts/%s\n", + zUrl, zCode); + alert_send(pSender,&hdr,&body,0); + nSent++; + blob_truncate(&hdr, 0); + blob_truncate(&body, 0); + } + blob_reset(&hdr); + blob_reset(&body); + db_finalize(&q); + alert_free_eventlist(pEvents); + + /* Step 4b: Update the pending_alerts table to remove all of the + ** alerts that have been completely sent. + */ + db_multi_exec("DELETE FROM pending_alert WHERE sentDigest AND sentSep;"); + + /* Send renewal messages to subscribers whose subscriptions are about + ** to expire. Only do this if: + ** + ** (1) email-renew-interval is 14 or greater (or in other words if + ** subscription expiration is enabled). + ** + ** (2) The SENDALERT_RENEWAL flag is set + */ +send_alert_expiration_warnings: + if( (flags & SENDALERT_RENEWAL)!=0 + && (iInterval = db_get_int("email-renew-interval",0))>=14 + ){ + int iNow = (int)(time(0)/86400); + int iOldWarn = db_get_int("email-renew-warning",0); + int iNewWarn = iNow - iInterval + ALERT_RENEWAL_MSG_FREQUENCY; + if( iNewWarn >= iOldWarn + ALERT_RENEWAL_MSG_FREQUENCY ){ + db_prepare(&q, + "SELECT" + " hex(subscriberCode)," /* 0 */ + " lastContact," /* 1 */ + " semail," /* 2 */ + " ssub" /* 3 */ + " FROM subscriber" + " WHERE lastContact<=%d AND lastContact>%d" + " AND NOT sdonotcall" + " AND length(sdigest)>0", + iNewWarn, iOldWarn + ); + while( db_step(&q)==SQLITE_ROW ){ + Blob hdr, body; + blob_init(&hdr, 0, 0); + blob_init(&body, 0, 0); + alert_renewal_msg(&hdr, &body, + db_column_text(&q,0), + db_column_int(&q,1), + db_column_text(&q,2), + db_column_text(&q,3), + zRepoName, zUrl); + alert_send(pSender,&hdr,&body,0); + blob_reset(&hdr); + blob_reset(&body); + } + db_finalize(&q); + if( (flags & SENDALERT_PRESERVE)==0 ){ + if( iOldWarn>0 ){ + db_set_int("email-renew-cutoff", iOldWarn, 0); + } + db_set_int("email-renew-warning", iNewWarn, 0); + } + } + } + +send_alert_done: + alert_sender_free(pSender); + if( g.fSqlTrace ) fossil_trace("-- END alert_send_alerts(%u)\n", flags); + return nSent; +} + +/* +** Do backoffice processing for email notifications. In other words, +** check to see if any email notifications need to occur, and then +** do them. +** +** This routine is intended to run in the background, after webpages. +** +** The mFlags option is zero or more of the SENDALERT_* flags. Normally +** this flag is zero, but the test-set-alert command sets it to +** SENDALERT_TRACE. +*/ +int alert_backoffice(u32 mFlags){ + int iJulianDay; + int nSent = 0; + if( !alert_tables_exist() ) return 0; + nSent = alert_send_alerts(mFlags); + iJulianDay = db_int(0, "SELECT julianday('now')"); + if( iJulianDay>db_get_int("email-last-digest",0) ){ + db_set_int("email-last-digest",iJulianDay,0); + nSent += alert_send_alerts(SENDALERT_DIGEST|SENDALERT_RENEWAL|mFlags); + } + return nSent; +} + +/* +** WEBPAGE: contact_admin +** +** A web-form to send an email message to the repository administrator, +** or (with appropriate permissions) to anybody. +*/ +void contact_admin_page(void){ + const char *zAdminEmail = db_get("email-admin",0); + unsigned int uSeed = 0; + const char *zDecoded; + char *zCaptcha = 0; + + login_check_credentials(); + style_set_current_feature("alerts"); + if( zAdminEmail==0 || zAdminEmail[0]==0 ){ + style_header("Outbound Email Disabled"); + @ <p>Outbound email is disabled on this repository + style_finish_page(); + return; + } + if( P("submit")!=0 + && P("subject")!=0 + && P("msg")!=0 + && P("from")!=0 + && cgi_csrf_safe(1) + && captcha_is_correct(0) + ){ + Blob hdr, body; + AlertSender *pSender = alert_sender_new(0,0); + blob_init(&hdr, 0, 0); + blob_appendf(&hdr, "To: <%s>\r\nSubject: %s administrator message\r\n", + zAdminEmail, db_get("email-subname","Fossil Repo")); + blob_init(&body, 0, 0); + blob_appendf(&body, "Message from [%s]\n", PT("from")/*safe-for-%s*/); + blob_appendf(&body, "Subject: [%s]\n\n", PT("subject")/*safe-for-%s*/); + blob_appendf(&body, "%s", PT("msg")/*safe-for-%s*/); + alert_send(pSender, &hdr, &body, 0); + style_header("Message Sent"); + if( pSender->zErr ){ + @ <h1>Internal Error</h1> + @ <p>The following error was reported by the system: + @ <blockquote><pre> + @ %h(pSender->zErr) + @ </pre></blockquote> + }else{ + @ <p>Your message has been sent to the repository administrator. + @ Thank you for your input.</p> + } + alert_sender_free(pSender); + style_finish_page(); + return; + } + if( captcha_needed() ){ + uSeed = captcha_seed(); + zDecoded = captcha_decode(uSeed); + zCaptcha = captcha_render(zDecoded); + } + style_set_current_feature("alerts"); + style_header("Message To Administrator"); + form_begin(0, "%R/contact_admin"); + @ <p>Enter a message to the repository administrator below:</p> + @ <table class="subscribe"> + if( zCaptcha ){ + @ <tr> + @ <td class="form_label">Security Code:</td> + @ <td><input type="text" name="captcha" value="" size="10"> + captcha_speakit_button(uSeed, "Speak the code"); + @ <input type="hidden" name="captchaseed" value="%u(uSeed)"></td> + @ </tr> + } + @ <tr> + @ <td class="form_label">Your Email Address:</td> + @ <td><input type="text" name="from" value="%h(PT("from"))" size="30"></td> + @ </tr> + @ <tr> + @ <td class="form_label">Subject:</td> + @ <td><input type="text" name="subject" value="%h(PT("subject"))"\ + @ size="80"></td> + @ </tr> + @ <tr> + @ <td class="form_label">Message:</td> + @ <td><textarea name="msg" cols="80" rows="10" wrap="virtual">\ + @ %h(PT("msg"))</textarea> + @ </tr> + @ <tr> + @ <td></td> + @ <td><input type="submit" name="submit" value="Send Message"> + @ </tr> + @ </table> + if( zCaptcha ){ + @ <div class="captcha"><table class="captcha"><tr><td><pre class="captcha"> + @ %h(zCaptcha) + @ </pre> + @ Enter the 8 characters above in the "Security Code" box<br/> + @ </td></tr></table></div> + } + @ </form> + style_finish_page(); +} + +/* +** Send an annoucement message described by query parameter. +** Permission to do this has already been verified. +*/ +static char *alert_send_announcement(void){ + AlertSender *pSender; + char *zErr; + const char *zTo = PT("to"); + char *zSubject = PT("subject"); + int bAll = PB("all"); + int bAA = PB("aa"); + int bMods = PB("mods"); + const char *zSub = db_get("email-subname", "[Fossil Repo]"); + int bTest2 = fossil_strcmp(P("name"),"test2")==0; + Blob hdr, body; + blob_init(&body, 0, 0); + blob_init(&hdr, 0, 0); + blob_appendf(&body, "%s", PT("msg")/*safe-for-%s*/); + pSender = alert_sender_new(bTest2 ? "blob" : 0, 0); + if( zTo[0] ){ + blob_appendf(&hdr, "To: <%s>\r\nSubject: %s %s\r\n", zTo, zSub, zSubject); + alert_send(pSender, &hdr, &body, 0); + } + if( bAll || bAA || bMods ){ + Stmt q; + int nUsed = blob_size(&body); + const char *zURL = db_get("email-url",0); + if( bAll ){ + db_prepare(&q, "SELECT semail, hex(subscriberCode) FROM subscriber " + " WHERE sverified AND NOT sdonotcall"); + }else if( bAA ){ + db_prepare(&q, "SELECT semail, hex(subscriberCode) FROM subscriber " + " WHERE sverified AND NOT sdonotcall" + " AND ssub LIKE '%%a%%'"); + }else if( bMods ){ + db_prepare(&q, + "SELECT semail, hex(subscriberCode)" + " FROM subscriber, user " + " WHERE sverified AND NOT sdonotcall" + " AND suname=login" + " AND fullcap(cap) GLOB '*5*'"); + } + while( db_step(&q)==SQLITE_ROW ){ + const char *zCode = db_column_text(&q, 1); + zTo = db_column_text(&q, 0); + blob_truncate(&hdr, 0); + blob_appendf(&hdr, "To: <%s>\r\nSubject: %s %s\r\n", zTo, zSub, zSubject); + if( zURL ){ + blob_truncate(&body, nUsed); + blob_appendf(&body,"\n-- \nSubscription info: %s/alerts/%s\n", + zURL, zCode); + } + alert_send(pSender, &hdr, &body, 0); + } + db_finalize(&q); + } + if( bTest2 ){ + /* If the URL is /announce/test2 instead of just /announce, then no + ** email is actually sent. Instead, the text of the email that would + ** have been sent is displayed in the result window. */ + @ <pre style='border: 2px solid blue; padding: 1ex'> + @ %h(blob_str(&pSender->out)) + @ </pre> + } + zErr = pSender->zErr; + pSender->zErr = 0; + alert_sender_free(pSender); + return zErr; +} + + +/* +** WEBPAGE: announce +** +** A web-form, available to users with the "Send-Announcement" or "A" +** capability, that allows one to send announcements to whomever +** has subscribed to receive announcements. The administrator can +** also send a message to an arbitrary email address and/or to all +** subscribers regardless of whether or not they have elected to +** receive announcements. +*/ +void announce_page(void){ + const char *zAction = "announce" + /* Maintenance reminder: we need an explicit action=THIS_PAGE on the + ** form element to avoid that a URL arg of to=... passed to this + ** page ends up overwriting the form-posted "to" value. This + ** action value differs for the test1 request path. + */; + + login_check_credentials(); + if( !g.perm.Announce ){ + login_needed(0); + return; + } + style_set_current_feature("alerts"); + if( fossil_strcmp(P("name"),"test1")==0 ){ + /* Visit the /announce/test1 page to see the CGI variables */ + zAction = "announce/test1"; + @ <p style='border: 1px solid black; padding: 1ex;'> + cgi_print_all(0, 0); + @ </p> + }else if( P("submit")!=0 && cgi_csrf_safe(1) ){ + char *zErr = alert_send_announcement(); + style_header("Announcement Sent"); + if( zErr ){ + @ <h1>Internal Error</h1> + @ <p>The following error was reported by the system: + @ <blockquote><pre> + @ %h(zErr) + @ </pre></blockquote> + }else{ + @ <p>The announcement has been sent. + @ <a href="%h(PD("REQUEST_URI","/"))">Send another</a></p> + } + style_finish_page(); + return; + } else if( !alert_enabled() ){ + style_header("Cannot Send Announcement"); + @ <p>Either you have no subscribers yet, or email alerts are not yet + @ <a href="https://fossil-scm.org/fossil/doc/trunk/www/alerts.md">set up</a> + @ for this repository.</p> + return; + } + + style_header("Send Announcement"); + @ <form method="POST" action="%R/%s(zAction)"> + @ <table class="subscribe"> + if( g.perm.Admin ){ + int aa = PB("aa"); + int all = PB("all"); + int aMod = PB("mods"); + const char *aack = aa ? "checked" : ""; + const char *allck = all ? "checked" : ""; + const char *modck = aMod ? "checked" : ""; + @ <tr> + @ <td class="form_label">To:</td> + @ <td><input type="text" name="to" value="%h(PT("to"))" size="30"><br> + @ <label><input type="checkbox" name="aa" %s(aack)> \ + @ All "announcement" subscribers</label> \ + @ <a href="%R/subscribers?only=a" target="_blank">(list)</a><br> + @ <label><input type="checkbox" name="all" %s(allck)> \ + @ All subscribers</label> \ + @ <a href="%R/subscribers" target="_blank">(list)</a><br> + @ <label><input type="checkbox" name="mods" %s(modck)> \ + @ All moderators</label> \ + @ <a href="%R/setup_ulist?with=5" target="_blank">(list)</a><br></td> + @ </tr> + } + @ <tr> + @ <td class="form_label">Subject:</td> + @ <td><input type="text" name="subject" value="%h(PT("subject"))"\ + @ size="80"></td> + @ </tr> + @ <tr> + @ <td class="form_label">Message:</td> + @ <td><textarea name="msg" cols="80" rows="10" wrap="virtual">\ + @ %h(PT("msg"))</textarea> + @ </tr> + @ <tr> + @ <td></td> + if( fossil_strcmp(P("name"),"test2")==0 ){ + @ <td><input type="submit" name="submit" value="Dry Run"> + }else{ + @ <td><input type="submit" name="submit" value="Send Message"> + } + @ </tr> + @ </table> + @ </form> + style_finish_page(); +} ADDED src/alerts/bflat2.wav Index: src/alerts/bflat2.wav ================================================================== --- /dev/null +++ src/alerts/bflat2.wav cannot compute difference between binary files ADDED src/alerts/bflat3.wav Index: src/alerts/bflat3.wav ================================================================== --- /dev/null +++ src/alerts/bflat3.wav cannot compute difference between binary files ADDED src/alerts/bloop.wav Index: src/alerts/bloop.wav ================================================================== --- /dev/null +++ src/alerts/bloop.wav cannot compute difference between binary files ADDED src/alerts/mkwav.c Index: src/alerts/mkwav.c ================================================================== --- /dev/null +++ src/alerts/mkwav.c @@ -0,0 +1,88 @@ +/* +** This C program was used to generate the "g-minor-triad.wav" file. +** A small modification generated the "b-flat.wav" file. +** +** This code is saved as an historical reference. It is not part +** of Fossil. +*/ +#include <stdio.h> +#include <math.h> +#include <stdlib.h> + +/* +** Write a four-byte little-endian integer value to out. +*/ +void write_int4(FILE *out, unsigned int i){ + unsigned char z[4]; + z[0] = i&0xff; + z[1] = (i>>8)&0xff; + z[2] = (i>>16)&0xff; + z[3] = (i>>24)&0xff; + fwrite(z, 4, 1, out); +} + +/* +** Write out the WAV file +*/ +void write_wave( + const char *zFilename, /* The file to write */ + unsigned int nData, /* Bytes of data */ + unsigned char *aData /* 8000 samples/sec, 8 bit samples */ +){ + const unsigned char aWavFmt[] = { + 0x57, 0x41, 0x56, 0x45, /* "WAVE" */ + 0x66, 0x6d, 0x74, 0x20, /* "fmt " */ + 0x10, 0x00, 0x00, 0x00, /* 16 bytes in the "fmt " section */ + 0x01, 0x00, /* FormatTag: WAVE_FORMAT_PCM */ + 0x01, 0x00, /* 1 channel */ + 0x40, 0x1f, 0x00, 0x00, /* 8000 samples/second */ + 0x40, 0x1f, 0x00, 0x00, /* 8000 bytes/second */ + 0x01, 0x00, /* Block alignment */ + 0x08, 0x00, /* bits/sample */ + 0x64, 0x61, 0x74, 0x61, /* "data" */ + }; + FILE *out = fopen(zFilename,"wb"); + if( out==0 ){ + fprintf(stderr, "cannot open \"%s\" for writing\n", zFilename); + exit(1); + } + fwrite("RIFF", 4, 1, out); + write_int4(out, nData+4+20+8); + fwrite(aWavFmt, sizeof(aWavFmt), 1, out); + write_int4(out, nData); + fwrite(aData, nData, 1, out); + fclose(out); +} + +int main(int argc, char **argv){ + int i = 0; + unsigned char aBuf[800]; +# define N sizeof(aBuf) +# define pitch1 195.9977*2 /* G */ +# define pitch2 233.0819*2 /* B-flat */ +# define pitch3 293.6648*2 /* D */ + while( i<N/2 ){ + double v; + v = 99.0*sin((2*M_PI*pitch3*i)/8000); + if( i<200 ){ + v = v*i/200.0; + }else if( i>N-200 ){ + v = v*(N-i)/200.0; + } + aBuf[i] = (char)(v+99.0); + i++; + } + while( i<N ){ + double v; + v = 99.0*sin((2*M_PI*pitch1*i)/8000); + if( i<200 ){ + v = v*i/200.0; + }else if( i>N-200 ){ + v = v*(N-i)/200.0; + } + aBuf[i] = (char)(v+99.0); + i++; + } + write_wave("out.wav", N, aBuf); + return 0; +} ADDED src/alerts/plunk.wav Index: src/alerts/plunk.wav ================================================================== --- /dev/null +++ src/alerts/plunk.wav cannot compute difference between binary files Index: src/allrepo.c ================================================================== --- src/allrepo.c +++ src/allrepo.c @@ -20,139 +20,452 @@ #include "config.h" #include "allrepo.h" #include <assert.h> /* -** The input string is a filename. Return a new copy of this -** filename if the filename requires quoting due to special characters -** such as spaces in the name. -** -** If the filename cannot be safely quoted, return a NULL pointer. -** -** Space to hold the returned string is obtained from malloc. A new -** string is returned even if no quoting is needed. +** Build a string that contains all of the command-line options +** specified as arguments. If the option name begins with "+" then +** it takes an argument. Without the "+" it does not. */ -static char *quoteFilename(const char *zFilename){ - int i, c; - int needQuote = 0; - for(i=0; (c = zFilename[i])!=0; i++){ - if( c=='"' ) return 0; - if( isspace(c) ) needQuote = 1; - if( c=='\\' && zFilename[i+1]==0 ) return 0; - if( c=='$' ) return 0; - } - if( needQuote ){ - return mprintf("\"%s\"", zFilename); - }else{ - return mprintf("%s", zFilename); +static void collect_argument(Blob *pExtra,const char *zArg,const char *zShort){ + const char *z = find_option(zArg, zShort, 0); + if( z!=0 ){ + blob_appendf(pExtra, " %s", z); + } +} +static void collect_argument_value(Blob *pExtra, const char *zArg){ + const char *zValue = find_option(zArg, 0, 1); + if( zValue ){ + if( zValue[0] ){ + blob_appendf(pExtra, " --%s %$", zArg, zValue); + }else{ + blob_appendf(pExtra, " --%s \"\"", zArg); + } + } +} +static void collect_argv(Blob *pExtra, int iStart){ + int i; + for(i=iStart; i<g.argc; i++){ + blob_appendf(pExtra, " %s", g.argv[i]); } } /* ** COMMAND: all ** -** Usage: %fossil all (list|ls|pull|push|rebuild|sync) +** Usage: %fossil all SUBCOMMAND ... ** ** The ~/.fossil file records the location of all repositories for a ** user. This command performs certain operations on all repositories -** that can be useful before or after a period of disconnection operation. +** that can be useful before or after a period of disconnected operation. +** +** On Win32 systems, the file is named "_fossil" and is located in +** %LOCALAPPDATA%, %APPDATA% or %HOMEPATH%. +** ** Available operations are: ** -** list | ls Display the location of all repositories -** -** pull Run a "pull" operation on all repositories -** -** push Run a "push" on all repositories -** -** rebuild Rebuild on all repositories -** -** sync Run a "sync" on all repositories -** -** Respositories are automatically added to the set of known repositories -** when one of the following commands against the repository: clone, info, -** pull, push, or sync +** backup Backup all repositories. The argument must be the name of +** a directory into which all backup repositories are written. +** +** cache Manages the cache used for potentially expensive web +** pages. Any additional arguments are passed on verbatim +** to the cache command. +** +** changes Shows all local checkouts that have uncommitted changes. +** This operation has no additional options. +** +** clean Delete all "extra" files in all local checkouts. Extreme +** caution should be exercised with this command because its +** effects cannot be undone. Use of the --dry-run option to +** carefully review the local checkouts to be operated upon +** and the --whatif option to carefully review the files to +** be deleted beforehand is highly recommended. The command +** line options supported by the clean command itself, if any +** are present, are passed along verbatim. +** +** config Only the "config pull AREA" command works. +** +** dbstat Run the "dbstat" command on all repositories. +** +** extras Shows "extra" files from all local checkouts. The command +** line options supported by the extra command itself, if any +** are present, are passed along verbatim. +** +** fts-config Run the "fts-config" command on all repositories. +** +** git export Do the "git export" command on all repositories for which +** a Git mirror has been previously established. +** +** info Run the "info" command on all repositories. +** +** pull Run a "pull" operation on all repositories. Only the +** --verbose option is supported. +** +** push Run a "push" on all repositories. Only the --verbose +** option is supported. +** +** rebuild Rebuild on all repositories. The command line options +** supported by the rebuild command itself, if any are +** present, are passed along verbatim. The --force and +** --randomize options are not supported. +** +** sync Run a "sync" on all repositories. Only the --verbose +** and --unversioned options are supported. +** +** set Run the "setting" or "set" commands on all +** repositories. These command are particularly useful in +** conjunction with the "max-loadavg" setting which cannot +** otherwise be set globally. +** +** unset Run the "unset" command on all repositories +** +** server Run the "server" commands on all repositories. +** The root URI gives a listing of all repos. +** +** ui Run the "ui" command on all repositories. Like "server" +** but bind to the loopback TCP address only, enable +** the --localauth option and automatically launch a +** web-browser +** +** +** In addition, the following maintenance operations are supported: +** +** add Add all the repositories named to the set of repositories +** tracked by Fossil. Normally Fossil is able to keep up with +** this list by itself, but sometimes it can benefit from this +** hint if you rename repositories. +** +** ignore Arguments are repositories that should be ignored by +** subsequent clean, extras, list, pull, push, rebuild, and +** sync operations. The -c|--ckout option causes the listed +** local checkouts to be ignored instead. +** +** list | ls Display the location of all repositories. The -c|--ckout +** option causes all local checkouts to be listed instead. +** +** Repositories are automatically added to the set of known repositories +** when one of the following commands are run against the repository: +** clone, info, pull, push, or sync. Even previously ignored repositories +** are added back to the list of repositories by these commands. +** +** Options: +** --dry-run If given, display instead of run actions. +** --showfile Show the repository or checkout being operated upon. +** --stop-on-error Halt immediately if any subprocess fails. */ void all_cmd(void){ int n; Stmt q; const char *zCmd; char *zSyscmd; - char *zFossil; - char *zQFilename; - int nMissing; - + Blob extra; + int useCheckouts = 0; + int quiet = 0; + int dryRunFlag = 0; + int showFile = find_option("showfile",0,0)!=0; + int stopOnError; + int nToDel = 0; + int showLabel = 0; + + (void)find_option("dontstop",0,0); /* Legacy. Now the default */ + stopOnError = find_option("stop-on-error",0,0)!=0; + dryRunFlag = find_option("dry-run","n",0)!=0; + if( !dryRunFlag ){ + dryRunFlag = find_option("test",0,0)!=0; /* deprecated */ + } + if( g.argc<3 ){ - usage("list|ls|pull|push|rebuild|sync"); + usage("SUBCOMMAND ..."); } n = strlen(g.argv[2]); - db_open_config(1); + db_open_config(1, 0); + blob_zero(&extra); zCmd = g.argv[2]; + if( !login_is_nobody() ) blob_appendf(&extra, " -U %s", g.zLogin); + if( strncmp(zCmd, "ui", n)==0 || strncmp(zCmd, "server", n)==0 ){ + g.argv[1] = g.argv[2]; + g.argv[2] = "/"; + cmd_webserver(); + return; + } if( strncmp(zCmd, "list", n)==0 || strncmp(zCmd,"ls",n)==0 ){ zCmd = "list"; + useCheckouts = find_option("ckout","c",0)!=0; + }else if( strncmp(zCmd, "backup", n)==0 ){ + char *zDest; + zCmd = "backup -R"; + collect_argument(&extra, "overwrite",0); + if( g.argc!=4 ) usage("backup DIRECTORY"); + zDest = g.argv[3]; + if( file_isdir(zDest, ExtFILE)!=1 ){ + fossil_fatal("argument to \"fossil all backup\" must be a directory"); + } + blob_appendf(&extra, " %$", zDest); + }else if( strncmp(zCmd, "clean", n)==0 ){ + zCmd = "clean --chdir"; + collect_argument(&extra, "allckouts",0); + collect_argument_value(&extra, "case-sensitive"); + collect_argument_value(&extra, "clean"); + collect_argument(&extra, "dirsonly",0); + collect_argument(&extra, "disable-undo",0); + collect_argument(&extra, "dotfiles",0); + collect_argument(&extra, "emptydirs",0); + collect_argument(&extra, "force","f"); + collect_argument_value(&extra, "ignore"); + collect_argument_value(&extra, "keep"); + collect_argument(&extra, "no-prompt",0); + collect_argument(&extra, "temp",0); + collect_argument(&extra, "verbose","v"); + collect_argument(&extra, "whatif",0); + useCheckouts = 1; + }else if( strncmp(zCmd, "config", n)==0 ){ + zCmd = "config -R"; + collect_argv(&extra, 3); + (void)find_option("legacy",0,0); + (void)find_option("overwrite",0,0); + verify_all_options(); + if( g.argc!=5 || fossil_strcmp(g.argv[3],"pull")!=0 ){ + usage("configure pull AREA ?OPTIONS?"); + } + }else if( strncmp(zCmd, "dbstat", n)==0 ){ + zCmd = "dbstat --omit-version-info -R"; + showLabel = 1; + quiet = 1; + collect_argument(&extra, "brief", "b"); + collect_argument(&extra, "db-check", 0); + collect_argument(&extra, "db-verify", 0); + }else if( strncmp(zCmd, "extras", n)==0 ){ + if( showFile ){ + zCmd = "extras --chdir"; + }else{ + zCmd = "extras --header --chdir"; + } + collect_argument(&extra, "abs-paths",0); + collect_argument_value(&extra, "case-sensitive"); + collect_argument(&extra, "dotfiles",0); + collect_argument_value(&extra, "ignore"); + collect_argument(&extra, "rel-paths",0); + useCheckouts = 1; + stopOnError = 0; + quiet = 1; + }else if( strncmp(zCmd, "git", n)==0 ){ + if( g.argc<4 ){ + usage("git (export|status)"); + }else{ + int n3 = (int)strlen(g.argv[3]); + if( strncmp(g.argv[3], "export", n3)==0 ){ + zCmd = "git export --if-mirrored -R"; + }else if( strncmp(g.argv[3], "status", n3)==0 ){ + zCmd = "git status -R"; + }else{ + usage("git (export|status)"); + } + } }else if( strncmp(zCmd, "push", n)==0 ){ zCmd = "push -autourl -R"; + collect_argument(&extra, "verbose","v"); }else if( strncmp(zCmd, "pull", n)==0 ){ zCmd = "pull -autourl -R"; + collect_argument(&extra, "verbose","v"); }else if( strncmp(zCmd, "rebuild", n)==0 ){ zCmd = "rebuild"; + collect_argument(&extra, "cluster",0); + collect_argument(&extra, "compress",0); + collect_argument(&extra, "compress-only",0); + collect_argument(&extra, "noverify",0); + collect_argument_value(&extra, "pagesize"); + collect_argument(&extra, "vacuum",0); + collect_argument(&extra, "deanalyze",0); + collect_argument(&extra, "analyze",0); + collect_argument(&extra, "wal",0); + collect_argument(&extra, "stats",0); + collect_argument(&extra, "index",0); + collect_argument(&extra, "noindex",0); + collect_argument(&extra, "ifneeded", 0); + }else if( strncmp(zCmd, "setting", n)==0 ){ + zCmd = "setting -R"; + collect_argv(&extra, 3); + }else if( strncmp(zCmd, "unset", n)==0 ){ + zCmd = "unset -R"; + collect_argv(&extra, 3); + }else if( strncmp(zCmd, "fts-config", n)==0 ){ + zCmd = "fts-config -R"; + collect_argv(&extra, 3); }else if( strncmp(zCmd, "sync", n)==0 ){ zCmd = "sync -autourl -R"; + collect_argument(&extra, "verbose","v"); + collect_argument(&extra, "unversioned","u"); + }else if( strncmp(zCmd, "test-integrity", n)==0 ){ + collect_argument(&extra, "db-only", "d"); + collect_argument(&extra, "parse", 0); + collect_argument(&extra, "quick", "q"); + zCmd = "test-integrity"; + }else if( strncmp(zCmd, "test-orphans", n)==0 ){ + zCmd = "test-orphans -R"; + }else if( strncmp(zCmd, "test-missing", n)==0 ){ + zCmd = "test-missing -q -R"; + collect_argument(&extra, "notshunned",0); + }else if( strncmp(zCmd, "changes", n)==0 ){ + zCmd = "changes --quiet --header --chdir"; + useCheckouts = 1; + stopOnError = 0; + quiet = 1; + }else if( strncmp(zCmd, "ignore", n)==0 ){ + int j; + Blob fn = BLOB_INITIALIZER; + Blob sql = BLOB_INITIALIZER; + useCheckouts = find_option("ckout","c",0)!=0; + verify_all_options(); + db_begin_transaction(); + for(j=3; j<g.argc; j++, blob_reset(&sql), blob_reset(&fn)){ + file_canonical_name(g.argv[j], &fn, useCheckouts?1:0); + blob_append_sql(&sql, + "DELETE FROM global_config WHERE name GLOB '%s:%q'", + useCheckouts?"ckout":"repo", blob_str(&fn) + ); + if( dryRunFlag ){ + fossil_print("%s\n", blob_sql_text(&sql)); + }else{ + db_unprotect(PROTECT_CONFIG); + db_multi_exec("%s", blob_sql_text(&sql)); + db_protect_pop(); + } + } + db_end_transaction(0); + blob_reset(&sql); + blob_reset(&fn); + blob_reset(&extra); + return; + }else if( strncmp(zCmd, "add", n)==0 ){ + int j; + Blob fn = BLOB_INITIALIZER; + Blob sql = BLOB_INITIALIZER; + verify_all_options(); + db_begin_transaction(); + for(j=3; j<g.argc; j++, blob_reset(&fn), blob_reset(&sql)){ + sqlite3 *db; + int rc; + const char *z; + file_canonical_name(g.argv[j], &fn, 0); + z = blob_str(&fn); + if( !file_isfile(z, ExtFILE) ) continue; + g.dbIgnoreErrors++; + rc = sqlite3_open(z, &db); + if( rc!=SQLITE_OK ){ sqlite3_close(db); g.dbIgnoreErrors--; continue; } + rc = sqlite3_exec(db, "SELECT rcvid FROM blob, delta LIMIT 1", 0, 0, 0); + sqlite3_close(db); + g.dbIgnoreErrors--; + if( rc!=SQLITE_OK ) continue; + blob_append_sql(&sql, + "INSERT OR IGNORE INTO global_config(name,value)" + "VALUES('repo:%q',1)", z + ); + if( dryRunFlag ){ + fossil_print("%s\n", blob_sql_text(&sql)); + }else{ + db_unprotect(PROTECT_CONFIG); + db_multi_exec("%s", blob_sql_text(&sql)); + db_protect_pop(); + } + } + db_end_transaction(0); + blob_reset(&sql); + blob_reset(&fn); + blob_reset(&extra); + return; + }else if( strncmp(zCmd, "info", n)==0 ){ + zCmd = "info"; + showLabel = 1; + quiet = 1; + }else if( strncmp(zCmd, "cache", n)==0 ){ + zCmd = "cache -R"; + showLabel = 1; + collect_argv(&extra, 3); }else{ fossil_fatal("\"all\" subcommand should be one of: " - "list ls push pull rebuild sync"); - } - zFossil = quoteFilename(g.argv[0]); - nMissing = 0; - db_prepare(&q, - "SELECT DISTINCT substr(name, 6) COLLATE nocase" - " FROM global_config" - " WHERE substr(name, 1, 5)=='repo:' ORDER BY 1" - ); + "add cache changes clean dbstat extras fts-config git ignore " + "info list ls pull push rebuild server setting sync ui unset"); + } + verify_all_options(); + db_multi_exec("CREATE TEMP TABLE repolist(name,tag);"); + if( useCheckouts ){ + db_multi_exec( + "INSERT INTO repolist " + "SELECT DISTINCT substr(name, 7), name COLLATE nocase" + " FROM global_config" + " WHERE substr(name, 1, 6)=='ckout:'" + " ORDER BY 1" + ); + }else{ + db_multi_exec( + "INSERT INTO repolist " + "SELECT DISTINCT substr(name, 6), name COLLATE nocase" + " FROM global_config" + " WHERE substr(name, 1, 5)=='repo:'" + " ORDER BY 1" + ); + } + db_multi_exec("CREATE TEMP TABLE toDel(x TEXT)"); + db_prepare(&q, "SELECT name, tag FROM repolist ORDER BY 1"); while( db_step(&q)==SQLITE_ROW ){ - const char *zFilename = db_column_text(&q, 0); - if( access(zFilename, 0) ){ - nMissing++; - continue; - } - if( !file_is_canonical(zFilename) ) nMissing++; - if( zCmd[0]=='l' ){ - printf("%s\n", zFilename); - continue; - } - zQFilename = quoteFilename(zFilename); - zSyscmd = mprintf("%s %s %s", zFossil, zCmd, zQFilename); - printf("%s\n", zSyscmd); - fflush(stdout); - portable_system(zSyscmd); - free(zSyscmd); - free(zQFilename); - } - + int rc; + const char *zFilename = db_column_text(&q, 0); +#if !USE_SEE + if( sqlite3_strglob("*.efossil", zFilename)==0 ) continue; +#endif + if( file_access(zFilename, F_OK) + || !file_is_canonical(zFilename) + || (useCheckouts && file_isdir(zFilename, ExtFILE)!=1) + ){ + db_multi_exec("INSERT INTO toDel VALUES(%Q)", db_column_text(&q, 1)); + nToDel++; + continue; + } + if( zCmd[0]=='l' ){ + fossil_print("%s\n", zFilename); + continue; + }else if( showFile ){ + fossil_print("%s: %s\n", useCheckouts ? "checkout" : "repository", + zFilename); + } + zSyscmd = mprintf("%$ %s %$%s", + g.nameOfExe, zCmd, zFilename, blob_str(&extra)); + if( showLabel ){ + int len = (int)strlen(zFilename); + int nStar = 80 - (len + 15); + if( nStar<2 ) nStar = 1; + fossil_print("%.13c %s %.*c\n", '*', zFilename, nStar, '*'); + fflush(stdout); + } + if( !quiet || dryRunFlag ){ + fossil_print("%s\n", zSyscmd); + fflush(stdout); + } + rc = dryRunFlag ? 0 : fossil_system(zSyscmd); + free(zSyscmd); + if( rc ){ + if( stopOnError ) break; + /* If there is an error, pause briefly, but do not stop. The brief + ** pause is so that if the prior command failed with Ctrl-C then there + ** will be time to stop the whole thing with a second Ctrl-C. */ + sqlite3_sleep(330); + } + } + db_finalize(&q); + + blob_reset(&extra); + /* If any repositories whose names appear in the ~/.fossil file could not ** be found, remove those names from the ~/.fossil file. */ - if( nMissing ){ - db_begin_transaction(); - db_reset(&q); - while( db_step(&q)==SQLITE_ROW ){ - const char *zFilename = db_column_text(&q, 0); - if( access(zFilename, 0) ){ - char *zRepo = mprintf("repo:%s", zFilename); - db_unset(zRepo, 1); - free(zRepo); - }else if( !file_is_canonical(zFilename) ){ - Blob cname; - char *zRepo = mprintf("repo:%s", zFilename); - db_unset(zRepo, 1); - free(zRepo); - file_canonical_name(zFilename, &cname); - zRepo = mprintf("repo:%s", blob_str(&cname)); - db_set(zRepo, "1", 1); - free(zRepo); - } - } - db_reset(&q); - db_end_transaction(0); - } - db_finalize(&q); + if( nToDel>0 ){ + const char *zSql = "DELETE FROM global_config WHERE name IN toDel"; + if( dryRunFlag ){ + fossil_print("%s\n", zSql); + }else{ + db_unprotect(PROTECT_CONFIG); + db_multi_exec("%s", zSql /*safe-for-%s*/ ); + db_protect_pop(); + } + } } Index: src/attach.c ================================================================== --- src/attach.c +++ src/attach.c @@ -2,11 +2,11 @@ ** 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: @@ -21,136 +21,176 @@ #include "attach.h" #include <assert.h> /* ** WEBPAGE: attachlist +** List attachments. ** -** tkt=TICKETUUID +** tkt=HASH ** page=WIKIPAGE +** technote=HASH +** +** At most one of technote=, tkt= or page= may be supplied. +** +** If none are given, all attachments are listed. If one is given, only +** attachments for the designated technote, ticket or wiki page are shown. ** -** List attachments. -** Either one of tkt= or page= are supplied or neither. If neither -** are given, all attachments are listed. If one is given, only -** attachments for the designated ticket or wiki page are shown. -** TICKETUUID must be complete +** HASH may be just a prefix of the relevant technical note or ticket +** artifact hash, in which case all attachments of all technical notes or +** tickets with the prefix will be listed. */ void attachlist_page(void){ const char *zPage = P("page"); const char *zTkt = P("tkt"); + const char *zTechNote = P("technote"); Blob sql; Stmt q; if( zPage && zTkt ) zTkt = 0; login_check_credentials(); + style_set_current_feature("attach"); blob_zero(&sql); - blob_append(&sql, - "SELECT datetime(mtime,'localtime'), src, target, filename, comment, user" - " FROM attachment", - -1 + blob_append_sql(&sql, + "SELECT datetime(mtime,toLocal()), src, target, filename," + " comment, user," + " (SELECT uuid FROM blob WHERE rid=attachid), attachid," + " (CASE WHEN 'tkt-'||target IN (SELECT tagname FROM tag)" + " THEN 1" + " WHEN 'event-'||target IN (SELECT tagname FROM tag)" + " THEN 2" + " ELSE 0 END)" + " FROM attachment" ); if( zPage ){ - if( g.okRdWiki==0 ) login_needed(); + if( g.perm.RdWiki==0 ){ login_needed(g.anon.RdWiki); return; } style_header("Attachments To %h", zPage); - blob_appendf(&sql, " WHERE target=%Q", zPage); + blob_append_sql(&sql, " WHERE target=%Q", zPage); }else if( zTkt ){ - if( g.okRdTkt==0 ) login_needed(); - style_header("Attachments To Ticket %.10s", zTkt); - blob_appendf(&sql, " WHERE target GLOB '%q*'", zTkt); + if( g.perm.RdTkt==0 ){ login_needed(g.anon.RdTkt); return; } + style_header("Attachments To Ticket %S", zTkt); + blob_append_sql(&sql, " WHERE target GLOB '%q*'", zTkt); + }else if( zTechNote ){ + if( g.perm.RdWiki==0 ){ login_needed(g.anon.RdWiki); return; } + style_header("Attachments to Tech Note %S", zTechNote); + blob_append_sql(&sql, " WHERE target GLOB '%q*'", + zTechNote); }else{ - if( g.okRdTkt==0 && g.okRdWiki==0 ) login_needed(); + if( g.perm.RdTkt==0 && g.perm.RdWiki==0 ){ + login_needed(g.anon.RdTkt || g.anon.RdWiki); + return; + } style_header("All Attachments"); } - blob_appendf(&sql, " ORDER BY mtime DESC"); - db_prepare(&q, "%s", blob_str(&sql)); + blob_append_sql(&sql, " ORDER BY mtime DESC"); + db_prepare(&q, "%s", blob_sql_text(&sql)); + @ <ol> while( db_step(&q)==SQLITE_ROW ){ const char *zDate = db_column_text(&q, 0); const char *zSrc = db_column_text(&q, 1); const char *zTarget = db_column_text(&q, 2); const char *zFilename = db_column_text(&q, 3); const char *zComment = db_column_text(&q, 4); const char *zUser = db_column_text(&q, 5); + const char *zUuid = db_column_text(&q, 6); + int attachid = db_column_int(&q, 7); + /* type 0 is a wiki page, 1 is a ticket, 2 is a tech note */ + int type = db_column_int(&q, 8); + const char *zDispUser = zUser && zUser[0] ? zUser : "anonymous"; int i; char *zUrlTail; for(i=0; zFilename[i]; i++){ - if( zFilename[i]=='/' && zFilename[i+1]!=0 ){ + if( zFilename[i]=='/' && zFilename[i+1]!=0 ){ zFilename = &zFilename[i+1]; i = -1; } } - if( strlen(zTarget)==UUID_SIZE && validate16(zTarget,UUID_SIZE) ){ + if( type==1 ){ zUrlTail = mprintf("tkt=%s&file=%t", zTarget, zFilename); + }else if( type==2 ){ + zUrlTail = mprintf("technote=%s&file=%t", zTarget, zFilename); }else{ zUrlTail = mprintf("page=%t&file=%t", zTarget, zFilename); } - @ - @ <p><a href="/attachview?%s(zUrlTail)">%h(zFilename)</a> - @ [<a href="/attachdownload/%t(zFilename)?%s(zUrlTail)">download</a>]<br> - if( zComment ) while( isspace(zComment[0]) ) zComment++; + @ <li><p> + @ Attachment %z(href("%R/ainfo/%!S",zUuid))%S(zUuid)</a> + moderation_pending_www(attachid); + @ <br /><a href="%R/attachview?%s(zUrlTail)">%h(zFilename)</a> + @ [<a href="%R/attachdownload/%t(zFilename)?%s(zUrlTail)">download</a>]<br> + if( zComment ) while( fossil_isspace(zComment[0]) ) zComment++; if( zComment && zComment[0] ){ - @ %w(zComment)<br> + @ %!W(zComment)<br /> } - if( zPage==0 && zTkt==0 ){ + if( zPage==0 && zTkt==0 && zTechNote==0 ){ if( zSrc==0 || zSrc[0]==0 ){ zSrc = "Deleted from"; }else { zSrc = "Added to"; } - if( strlen(zTarget)==UUID_SIZE && validate16(zTarget, UUID_SIZE) ){ - char zShort[20]; - memcpy(zShort, zTarget, 10); - zShort[10] = 0; - @ %s(zSrc) ticket <a href="%s(g.zTop)/tktview?name=%s(zTarget)"> - @ %s(zShort)</a> + if( type==1 ){ + @ %s(zSrc) ticket <a href="%R/tktview?name=%s(zTarget)"> + @ %S(zTarget)</a> + }else if( type==2 ){ + @ %s(zSrc) tech note <a href="%R/technote/%s(zTarget)"> + @ %S(zTarget)</a> }else{ - @ %s(zSrc) wiki page <a href="%s(g.zTop)/wiki?name=%t(zTarget)"> + @ %s(zSrc) wiki page <a href="%R/wiki?name=%t(zTarget)"> @ %h(zTarget)</a> } }else{ if( zSrc==0 || zSrc[0]==0 ){ @ Deleted }else { @ Added } } - @ by %h(zUser) on + @ by %h(zDispUser) on hyperlink_to_date(zDate, "."); free(zUrlTail); } db_finalize(&q); - style_footer(); + @ </ol> + style_finish_page(); return; } /* ** WEBPAGE: attachdownload ** WEBPAGE: attachimage ** WEBPAGE: attachview ** -** tkt=TICKETUUID +** Download or display an attachment. +** +** Query parameters: +** +** tkt=HASH ** page=WIKIPAGE +** technote=HASH ** file=FILENAME ** attachid=ID ** -** List attachments. */ void attachview_page(void){ const char *zPage = P("page"); const char *zTkt = P("tkt"); + const char *zTechNote = P("technote"); const char *zFile = P("file"); const char *zTarget = 0; int attachid = atoi(PD("attachid","0")); char *zUUID; - if( zPage && zTkt ) zTkt = 0; if( zFile==0 ) fossil_redirect_home(); login_check_credentials(); + style_set_current_feature("attach"); if( zPage ){ - if( g.okRdWiki==0 ) login_needed(); + if( g.perm.RdWiki==0 ){ login_needed(g.anon.RdWiki); return; } zTarget = zPage; }else if( zTkt ){ - if( g.okRdTkt==0 ) login_needed(); + if( g.perm.RdTkt==0 ){ login_needed(g.anon.RdTkt); return; } zTarget = zTkt; + }else if( zTechNote ){ + if( g.perm.RdWiki==0 ){ login_needed(g.anon.RdWiki); return; } + zTarget = zTechNote; }else{ fossil_redirect_home(); } if( attachid>0 ){ zUUID = db_text(0, @@ -167,204 +207,576 @@ ); } if( zUUID==0 || zUUID[0]==0 ){ style_header("No Such Attachment"); @ No such attachment.... - style_footer(); + style_finish_page(); return; }else if( zUUID[0]=='x' ){ style_header("Missing"); @ Attachment has been deleted - style_footer(); + style_finish_page(); return; + }else{ + g.perm.Read = 1; + cgi_replace_parameter("name",zUUID); + if( fossil_strcmp(g.zPath,"attachview")==0 ){ + artifact_page(); + }else{ + cgi_replace_parameter("m", mimetype_from_name(zFile)); + rawartifact_page(); + } } - g.okRead = 1; - cgi_replace_parameter("name",zUUID); - if( strcmp(g.zPath,"attachview")==0 ){ - artifact_page(); +} + +/* +** Save an attachment control artifact into the repository +*/ +static void attach_put( + Blob *pAttach, /* Text of the Attachment record */ + int attachRid, /* RID for the file that is being attached */ + int needMod /* True if the attachment is subject to moderation */ +){ + int rid; + if( needMod ){ + rid = content_put_ex(pAttach, 0, 0, 0, 1); + moderation_table_create(); + db_multi_exec( + "INSERT INTO modreq(objid,attachRid) VALUES(%d,%d);", + rid, attachRid + ); }else{ - cgi_replace_parameter("m", mimetype_from_name(zFile)); - rawartifact_page(); + rid = content_put(pAttach); + db_multi_exec("INSERT OR IGNORE INTO unsent VALUES(%d);", rid); + db_multi_exec("INSERT OR IGNORE INTO unclustered VALUES(%d);", rid); } + manifest_crosslink(rid, pAttach, MC_NONE); } /* -** WEBPAGE: attachadd -** -** tkt=TICKETUUID -** page=WIKIPAGE -** from=URL -** -** Add a new attachment. +** Commit a new attachment into the repository */ -void attachadd_page(void){ - const char *zPage = P("page"); - const char *zTkt = P("tkt"); - const char *zFrom = P("from"); - const char *aContent = P("f"); - const char *zName = PD("f:filename","unknown"); - const char *zTarget; - const char *zTargetType; - int szContent = atoi(PD("f:bytes","0")); - - if( P("cancel") ) cgi_redirect(zFrom); - if( zPage && zTkt ) fossil_redirect_home(); - if( zPage==0 && zTkt==0 ) fossil_redirect_home(); - login_check_credentials(); - if( zPage ){ - if( g.okApndWiki==0 || g.okAttach==0 ) login_needed(); - if( !db_exists("SELECT 1 FROM tag WHERE tagname='wiki-%q'", zPage) ){ - fossil_redirect_home(); - } - zTarget = zPage; - zTargetType = mprintf("Wiki Page <a href=\"%s/wiki?name=%h\">%h</a>", - g.zTop, zPage, zPage); - }else{ - if( g.okApndTkt==0 || g.okAttach==0 ) login_needed(); - if( !db_exists("SELECT 1 FROM tag WHERE tagname='tkt-%q'", zTkt) ){ - fossil_redirect_home(); - } - zTarget = zTkt; - zTargetType = mprintf("Ticket <a href=\"%s/tktview?name=%.10s\">%.10s</a>", - g.zTop, zTkt, zTkt); - } - if( zFrom==0 ) zFrom = mprintf("%s/home", g.zTop); - if( P("cancel") ){ - cgi_redirect(zFrom); - } - if( P("ok") && szContent>0 ){ +void attach_commit( + const char *zName, /* The filename of the attachment */ + const char *zTarget, /* The artifact hash to attach to */ + const char *aContent, /* The content of the attachment */ + int szContent, /* The length of the attachment */ + int needModerator, /* Moderate the attachment? */ + const char *zComment /* The comment for the attachment */ +){ Blob content; Blob manifest; Blob cksum; char *zUUID; - const char *zComment; char *zDate; int rid; int i, n; + int addCompress = 0; + Manifest *pManifest; db_begin_transaction(); blob_init(&content, aContent, szContent); - rid = content_put(&content, 0, 0); + pManifest = manifest_parse(&content, 0, 0); + manifest_destroy(pManifest); + blob_init(&content, aContent, szContent); + if( pManifest ){ + blob_compress(&content, &content); + addCompress = 1; + } + rid = content_put_ex(&content, 0, 0, 0, needModerator); zUUID = db_text(0, "SELECT uuid FROM blob WHERE rid=%d", rid); blob_zero(&manifest); for(i=n=0; zName[i]; i++){ - if( zName[i]=='/' || zName[i]=='\\' ) n = i; + if( zName[i]=='/' || zName[i]=='\\' ) n = i+1; } zName += n; if( zName[0]==0 ) zName = "unknown"; - blob_appendf(&manifest, "A %F %F %s\n", zName, zTarget, zUUID); - zComment = PD("comment", ""); - while( isspace(zComment[0]) ) zComment++; + blob_appendf(&manifest, "A %F%s %F %s\n", + zName, addCompress ? ".gz" : "", zTarget, zUUID); + while( fossil_isspace(zComment[0]) ) zComment++; n = strlen(zComment); - while( n>0 && isspace(zComment[n-1]) ){ n--; } + while( n>0 && fossil_isspace(zComment[n-1]) ){ n--; } if( n>0 ){ - blob_appendf(&manifest, "C %F\n", zComment); + blob_appendf(&manifest, "C %#F\n", n, zComment); } - zDate = db_text(0, "SELECT datetime('now')"); - zDate[10] = 'T'; + zDate = date_in_standard_format("now"); blob_appendf(&manifest, "D %s\n", zDate); - blob_appendf(&manifest, "U %F\n", g.zLogin ? g.zLogin : "nobody"); + blob_appendf(&manifest, "U %F\n", login_name()); md5sum_blob(&manifest, &cksum); blob_appendf(&manifest, "Z %b\n", &cksum); - rid = content_put(&manifest, 0, 0); - manifest_crosslink(rid, &manifest); + attach_put(&manifest, rid, needModerator); + assert( blob_is_reset(&manifest) ); db_end_transaction(0); - cgi_redirect(zFrom); - } - style_header("Add Attachment"); - @ <h2>Add Attachment To %s(zTargetType)</h2> - @ <form action="%s(g.zBaseURL)/attachadd" method="POST" - @ enctype="multipart/form-data"> - @ File to Attach: - @ <input type="file" name="f" size="60"><br> - @ Description:<br> - @ <textarea name="comment" cols=80 rows=5 wrap="virtual"></textarea><br> - if( zTkt ){ - @ <input type="hidden" name="tkt" value="%h(zTkt)"> - }else{ - @ <input type="hidden" name="page" value="%h(zPage)"> - } - @ <input type="hidden" name="from" value="%h(zFrom)"> - @ <input type="submit" name="ok" value="Add Attachment"> - @ <input type="submit" name="cancel" value="Cancel"> - @ </form> - style_footer(); -} - +} /* -** WEBPAGE: attachdelete +** WEBPAGE: attachadd +** Add a new attachment. ** -** tkt=TICKETUUID +** tkt=HASH ** page=WIKIPAGE -** file=FILENAME +** technote=HASH +** from=URL ** -** "Delete" an attachment. Because objects in Fossil are immutable -** the attachment isn't really deleted. Instead, we change the content -** of the attachment to NULL, which the system understands as being -** deleted. Historical values of the attachment are preserved. */ -void attachdel_page(void){ +void attachadd_page(void){ const char *zPage = P("page"); const char *zTkt = P("tkt"); - const char *zFile = P("file"); + const char *zTechNote = P("technote"); const char *zFrom = P("from"); + const char *aContent = P("f"); + const char *zName = PD("f:filename","unknown"); const char *zTarget; + char *zTargetType; + int szContent = atoi(PD("f:bytes","0")); + int goodCaptcha = 1; - if( zPage && zTkt ) fossil_redirect_home(); - if( zPage==0 && zTkt==0 ) fossil_redirect_home(); - if( zFile==0 ) fossil_redirect_home(); + if( P("cancel") ) cgi_redirect(zFrom); + if( (zPage && zTkt) + || (zPage && zTechNote) + || (zTkt && zTechNote) + ){ + fossil_redirect_home(); + } + if( zPage==0 && zTkt==0 && zTechNote==0) fossil_redirect_home(); login_check_credentials(); if( zPage ){ - if( g.okWrWiki==0 || g.okAttach==0 ) login_needed(); + if( g.perm.ApndWiki==0 || g.perm.Attach==0 ){ + login_needed(g.anon.ApndWiki && g.anon.Attach); + return; + } + if( !db_exists("SELECT 1 FROM tag WHERE tagname='wiki-%q'", zPage) ){ + fossil_redirect_home(); + } zTarget = zPage; + zTargetType = mprintf("Wiki Page <a href=\"%R/wiki?name=%h\">%h</a>", + zPage, zPage); + }else if ( zTechNote ){ + if( g.perm.Write==0 || g.perm.ApndWiki==0 || g.perm.Attach==0 ){ + login_needed(g.anon.Write && g.anon.ApndWiki && g.anon.Attach); + return; + } + if( !db_exists("SELECT 1 FROM tag WHERE tagname='event-%q'", zTechNote) ){ + zTechNote = db_text(0, "SELECT substr(tagname,7) FROM tag" + " WHERE tagname GLOB 'event-%q*'", zTechNote); + if( zTechNote==0) fossil_redirect_home(); + } + zTarget = zTechNote; + zTargetType = mprintf("Tech Note <a href=\"%R/technote/%s\">%S</a>", + zTechNote, zTechNote); + }else{ - if( g.okWrTkt==0 || g.okAttach==0 ) login_needed(); + if( g.perm.ApndTkt==0 || g.perm.Attach==0 ){ + login_needed(g.anon.ApndTkt && g.anon.Attach); + return; + } + if( !db_exists("SELECT 1 FROM tag WHERE tagname='tkt-%q'", zTkt) ){ + zTkt = db_text(0, "SELECT substr(tagname,5) FROM tag" + " WHERE tagname GLOB 'tkt-%q*'", zTkt); + if( zTkt==0 ) fossil_redirect_home(); + } zTarget = zTkt; + zTargetType = mprintf("Ticket <a href=\"%R/tktview/%s\">%S</a>", + zTkt, zTkt); } - if( zFrom==0 ) zFrom = mprintf("%s/home", g.zTop); + if( zFrom==0 ) zFrom = mprintf("%R/home"); if( P("cancel") ){ cgi_redirect(zFrom); } - if( P("confirm") ){ + if( P("ok") && szContent>0 && (goodCaptcha = captcha_is_correct(0)) ){ + int needModerator = (zTkt!=0 && ticket_need_moderation(0)) || + (zPage!=0 && wiki_need_moderation(0)); + const char *zComment = PD("comment", ""); + attach_commit(zName, zTarget, aContent, szContent, needModerator, zComment); + cgi_redirect(zFrom); + } + style_set_current_feature("attach"); + style_header("Add Attachment"); + if( !goodCaptcha ){ + @ <p class="generalError">Error: Incorrect security code.</p> + } + @ <h2>Add Attachment To %s(zTargetType)</h2> + form_begin("enctype='multipart/form-data'", "%R/attachadd"); + @ <div> + @ File to Attach: + @ <input type="file" name="f" size="60" /><br /> + @ Description:<br /> + @ <textarea name="comment" cols="80" rows="5" wrap="virtual"></textarea><br /> + if( zTkt ){ + @ <input type="hidden" name="tkt" value="%h(zTkt)" /> + }else if( zTechNote ){ + @ <input type="hidden" name="technote" value="%h(zTechNote)" /> + }else{ + @ <input type="hidden" name="page" value="%h(zPage)" /> + } + @ <input type="hidden" name="from" value="%h(zFrom)" /> + @ <input type="submit" name="ok" value="Add Attachment" /> + @ <input type="submit" name="cancel" value="Cancel" /> + @ </div> + captcha_generate(0); + @ </form> + style_finish_page(); + fossil_free(zTargetType); +} + +/* +** WEBPAGE: ainfo +** URL: /ainfo?name=ARTIFACTID +** +** Show the details of an attachment artifact. +*/ +void ainfo_page(void){ + int rid; /* RID for the control artifact */ + int ridSrc; /* RID for the attached file */ + char *zDate; /* Date attached */ + const char *zUuid; /* Hash of the control artifact */ + Manifest *pAttach; /* Parse of the control artifact */ + const char *zTarget; /* Wiki, ticket or tech note attached to */ + const char *zSrc; /* Hash of the attached file */ + const char *zName; /* Name of the attached file */ + const char *zDesc; /* Description of the attached file */ + const char *zWikiName = 0; /* Wiki page name when attached to Wiki */ + const char *zTNUuid = 0; /* Tech Note ID when attached to tech note */ + const char *zTktUuid = 0; /* Ticket ID when attached to a ticket */ + int modPending; /* True if awaiting moderation */ + const char *zModAction; /* Moderation action or NULL */ + int isModerator; /* TRUE if user is the moderator */ + const char *zMime; /* MIME Type */ + Blob attach; /* Content of the attachment */ + int fShowContent = 0; + const char *zLn = P("ln"); + + login_check_credentials(); + if( !g.perm.RdTkt && !g.perm.RdWiki ){ + login_needed(g.anon.RdTkt || g.anon.RdWiki); + return; + } + rid = name_to_rid_www("name"); + if( rid==0 ){ fossil_redirect_home(); } + zUuid = db_text("", "SELECT uuid FROM blob WHERE rid=%d", rid); + pAttach = manifest_get(rid, CFTYPE_ATTACHMENT, 0); + if( pAttach==0 ) fossil_redirect_home(); + zTarget = pAttach->zAttachTarget; + zSrc = pAttach->zAttachSrc; + ridSrc = db_int(0,"SELECT rid FROM blob WHERE uuid='%q'", zSrc); + zName = pAttach->zAttachName; + zDesc = pAttach->zComment; + zMime = mimetype_from_name(zName); + fShowContent = zMime ? strncmp(zMime,"text/", 5)==0 : 0; + if( validate16(zTarget, strlen(zTarget)) + && db_exists("SELECT 1 FROM ticket WHERE tkt_uuid='%q'", zTarget) + ){ + zTktUuid = zTarget; + if( !g.perm.RdTkt ){ login_needed(g.anon.RdTkt); return; } + if( g.perm.WrTkt ){ + style_submenu_element("Delete", "%R/ainfo/%s?del", zUuid); + } + }else if( db_exists("SELECT 1 FROM tag WHERE tagname='wiki-%q'",zTarget) ){ + zWikiName = zTarget; + if( !g.perm.RdWiki ){ login_needed(g.anon.RdWiki); return; } + if( g.perm.WrWiki ){ + style_submenu_element("Delete", "%R/ainfo/%s?del", zUuid); + } + }else if( db_exists("SELECT 1 FROM tag WHERE tagname='event-%q'",zTarget) ){ + zTNUuid = zTarget; + if( !g.perm.RdWiki ){ login_needed(g.anon.RdWiki); return; } + if( g.perm.Write && g.perm.WrWiki ){ + style_submenu_element("Delete", "%R/ainfo/%s?del", zUuid); + } + } + zDate = db_text(0, "SELECT datetime(%.12f)", pAttach->rDate); + + if( P("confirm") + && ((zTktUuid && g.perm.WrTkt) || + (zWikiName && g.perm.WrWiki) || + (zTNUuid && g.perm.Write && g.perm.WrWiki)) + ){ int i, n, rid; char *zDate; Blob manifest; Blob cksum; + const char *zFile = zName; db_begin_transaction(); blob_zero(&manifest); for(i=n=0; zFile[i]; i++){ if( zFile[i]=='/' || zFile[i]=='\\' ) n = i; } zFile += n; if( zFile[0]==0 ) zFile = "unknown"; blob_appendf(&manifest, "A %F %F\n", zFile, zTarget); - zDate = db_text(0, "SELECT datetime('now')"); - zDate[10] = 'T'; + zDate = date_in_standard_format("now"); blob_appendf(&manifest, "D %s\n", zDate); - blob_appendf(&manifest, "U %F\n", g.zLogin ? g.zLogin : "nobody"); + blob_appendf(&manifest, "U %F\n", login_name()); md5sum_blob(&manifest, &cksum); blob_appendf(&manifest, "Z %b\n", &cksum); - rid = content_put(&manifest, 0, 0); - manifest_crosslink(rid, &manifest); + rid = content_put(&manifest); + manifest_crosslink(rid, &manifest, MC_NONE); db_end_transaction(0); - cgi_redirect(zFrom); - } - style_header("Delete Attachment"); - @ <form action="%s(g.zBaseURL)/attachdelete" method="POST"> - @ <p>Confirm that you want to delete the attachment named - @ "%h(zFile)" on %s(zTkt?"ticket":"wiki page") %h(zTarget):<br> - if( zTkt ){ - @ <input type="hidden" name="tkt" value="%h(zTkt)"> + @ <p>The attachment below has been deleted.</p> + } + + if( P("del") + && ((zTktUuid && g.perm.WrTkt) || + (zWikiName && g.perm.WrWiki) || + (zTNUuid && g.perm.Write && g.perm.WrWiki)) + ){ + form_begin(0, "%R/ainfo/%!S", zUuid); + @ <p>Confirm you want to delete the attachment shown below. + @ <input type="submit" name="confirm" value="Confirm"> + @ </form> + } + + isModerator = g.perm.Admin || + (zTktUuid && g.perm.ModTkt) || + (zWikiName && g.perm.ModWiki); + if( isModerator && (zModAction = P("modaction"))!=0 ){ + if( strcmp(zModAction,"delete")==0 ){ + moderation_disapprove(rid); + if( zTktUuid ){ + cgi_redirectf("%R/tktview/%!S", zTktUuid); + }else{ + cgi_redirectf("%R/wiki?name=%t", zWikiName); + } + return; + } + if( strcmp(zModAction,"approve")==0 ){ + moderation_approve('a', rid); + } + } + style_set_current_feature("attach"); + style_header("Attachment Details"); + style_submenu_element("Raw", "%R/artifact/%s", zUuid); + if(fShowContent){ + style_submenu_element("Line Numbers", "%R/ainfo/%s%s", zUuid, + ((zLn&&*zLn) ? "" : "?ln=0")); + } + + @ <div class="section">Overview</div> + @ <p><table class="label-value"> + @ <tr><th>Artifact ID:</th> + @ <td>%z(href("%R/artifact/%!S",zUuid))%s(zUuid)</a> + if( g.perm.Setup ){ + @ (%d(rid)) + } + modPending = moderation_pending_www(rid); + if( zTktUuid ){ + @ <tr><th>Ticket:</th> + @ <td>%z(href("%R/tktview/%s",zTktUuid))%s(zTktUuid)</a></td></tr> + } + if( zTNUuid ){ + @ <tr><th>Tech Note:</th> + @ <td>%z(href("%R/technote/%s",zTNUuid))%s(zTNUuid)</a></td></tr> + } + if( zWikiName ){ + @ <tr><th>Wiki Page:</th> + @ <td>%z(href("%R/wiki?name=%t",zWikiName))%h(zWikiName)</a></td></tr> + } + @ <tr><th>Date:</th><td> + hyperlink_to_date(zDate, "</td></tr>"); + @ <tr><th>User:</th><td> + hyperlink_to_user(pAttach->zUser, zDate, "</td></tr>"); + @ <tr><th>Artifact Attached:</th> + @ <td>%z(href("%R/artifact/%s",zSrc))%s(zSrc)</a> + if( g.perm.Setup ){ + @ (%d(ridSrc)) + } + @ <tr><th>Filename:</th><td>%h(zName)</td></tr> + if( g.perm.Setup ){ + @ <tr><th>MIME-Type:</th><td>%h(zMime)</td></tr> + } + @ <tr><th valign="top">Description:</th><td valign="top">%h(zDesc)</td></tr> + @ </table> + + if( isModerator && modPending ){ + @ <div class="section">Moderation</div> + @ <blockquote> + form_begin(0, "%R/ainfo/%s", zUuid); + @ <label><input type="radio" name="modaction" value="delete"> + @ Delete this change</label><br /> + @ <label><input type="radio" name="modaction" value="approve"> + @ Approve this change</label><br /> + @ <input type="submit" value="Submit"> + @ </form> + @ </blockquote> + } + + @ <div class="section">Content Appended</div> + @ <blockquote> + blob_zero(&attach); + if( fShowContent ){ + const char *z; + content_get(ridSrc, &attach); + blob_to_utf8_no_bom(&attach, 0); + z = blob_str(&attach); + if( zLn ){ + output_text_with_line_numbers(z, blob_size(&attach), zName, zLn, 1); + }else{ + @ <pre> + @ %h(z) + @ </pre> + } + }else if( strncmp(zMime, "image/", 6)==0 ){ + int sz = db_int(0, "SELECT size FROM blob WHERE rid=%d", ridSrc); + @ <i>(file is %d(sz) bytes of image data)</i><br /> + @ <img src="%R/raw/%s(zSrc)?m=%s(zMime)"></img> + style_submenu_element("Image", "%R/raw/%s?m=%s", zSrc, zMime); + }else{ + int sz = db_int(0, "SELECT size FROM blob WHERE rid=%d", ridSrc); + @ <i>(file is %d(sz) bytes of binary data)</i> + } + @ </blockquote> + manifest_destroy(pAttach); + blob_reset(&attach); + style_finish_page(); +} + +/* +** Output HTML to show a list of attachments. +*/ +void attachment_list( + const char *zTarget, /* Object that things are attached to */ + const char *zHeader /* Header to display with attachments */ +){ + int cnt = 0; + Stmt q; + db_prepare(&q, + "SELECT datetime(mtime,toLocal()), filename, user," + " (SELECT uuid FROM blob WHERE rid=attachid), src" + " FROM attachment" + " WHERE isLatest AND src!='' AND target=%Q" + " ORDER BY mtime DESC", + zTarget + ); + while( db_step(&q)==SQLITE_ROW ){ + const char *zDate = db_column_text(&q, 0); + const char *zFile = db_column_text(&q, 1); + const char *zUser = db_column_text(&q, 2); + const char *zUuid = db_column_text(&q, 3); + const char *zSrc = db_column_text(&q, 4); + const char *zDispUser = zUser && zUser[0] ? zUser : "anonymous"; + if( cnt==0 ){ + @ %s(zHeader) + } + cnt++; + @ <li> + @ %z(href("%R/artifact/%!S",zSrc))%h(zFile)</a> + @ added by %h(zDispUser) on + hyperlink_to_date(zDate, "."); + @ [%z(href("%R/ainfo/%!S",zUuid))details</a>] + @ </li> + } + if( cnt ){ + @ </ul> + } + db_finalize(&q); + +} + +/* +** COMMAND: attachment* +** +** Usage: %fossil attachment add ?PAGENAME? FILENAME ?OPTIONS? +** +** Add an attachment to an existing wiki page or tech note. +** Options: +** +** -t|--technote DATETIME Specifies the timestamp of +** the technote to which the attachment +** is to be made. The attachment will be +** to the most recently modified tech note +** with the specified timestamp. +** +** -t|--technote TECHNOTE-ID Specifies the technote to be +** updated by its technote id. +** +** One of PAGENAME, DATETIME or TECHNOTE-ID must be specified. +** +** DATETIME may be "now" or "YYYY-MM-DDTHH:MM:SS.SSS". If in +** year-month-day form, it may be truncated, the "T" may be replaced by +** a space, and it may also name a timezone offset from UTC as "-HH:MM" +** (westward) or "+HH:MM" (eastward). Either no timezone suffix or "Z" +** means UTC. +*/ +void attachment_cmd(void){ + int n; + db_find_and_open_repository(0, 0); + if( g.argc<3 ){ + goto attachment_cmd_usage; + } + n = strlen(g.argv[2]); + if( n==0 ){ + goto attachment_cmd_usage; + } + + if( strncmp(g.argv[2],"add",n)==0 ){ + const char *zPageName = 0; /* Name of the wiki page to attach to */ + const char *zFile; /* Name of the file to be attached */ + const char *zETime; /* The name of the technote to attach to */ + Manifest *pWiki = 0; /* Parsed wiki page content */ + char *zBody = 0; /* Wiki page content */ + int rid; + const char *zTarget; /* Target of the attachment */ + Blob content; /* The content of the attachment */ + zETime = find_option("technote","t",1); + if( !zETime ){ + if( g.argc!=5 ){ + usage("add PAGENAME FILENAME"); + } + zPageName = g.argv[3]; + rid = db_int(0, "SELECT x.rid FROM tag t, tagxref x" + " WHERE x.tagid=t.tagid AND t.tagname='wiki-%q'" + " ORDER BY x.mtime DESC LIMIT 1", + zPageName + ); + if( (pWiki = manifest_get(rid, CFTYPE_WIKI, 0))!=0 ){ + zBody = pWiki->zWiki; + } + if( zBody==0 ){ + fossil_fatal("wiki page [%s] not found",zPageName); + } + zTarget = zPageName; + zFile = g.argv[4]; + }else{ + if( g.argc!=4 ){ + usage("add FILENAME --technote DATETIME|TECHNOTE-ID"); + } + rid = wiki_technote_to_rid(zETime); + if( rid<0 ){ + fossil_fatal("ambiguous tech note id: %s", zETime); + } + if( (pWiki = manifest_get(rid, CFTYPE_EVENT, 0))!=0 ){ + zBody = pWiki->zWiki; + } + if( zBody==0 ){ + fossil_fatal("technote [%s] not found",zETime); + } + zTarget = db_text(0, + "SELECT substr(tagname,7) FROM tag WHERE tagid=(SELECT tagid FROM event WHERE objid='%d')", + rid + ); + zFile = g.argv[3]; + } + blob_read_from_file(&content, zFile, ExtFILE); + user_select(); + attach_commit( + zFile, /* The filename of the attachment */ + zTarget, /* The artifact hash to attach to */ + blob_buffer(&content), /* The content of the attachment */ + blob_size(&content), /* The length of the attachment */ + 0, /* No need to moderate the attachment */ + "" /* Empty attachment comment */ + ); + if( !zETime ){ + fossil_print("Attached %s to wiki page %s.\n", zFile, zPageName); + }else{ + fossil_print("Attached %s to tech note %s.\n", zFile, zETime); + } }else{ - @ <input type="hidden" name="page" value="%h(zPage)"> - } - @ <input type="hidden" name="file" value="%h(zFile)"> - @ <input type="hidden" name="from" value="%h(zFrom)"> - @ <input type="submit" name="confirm" value="Delete"> - @ <input type="submit" name="cancel" value="Cancel"> - @ </form> - style_footer(); - + goto attachment_cmd_usage; + } + return; + +attachment_cmd_usage: + usage("add ?PAGENAME? FILENAME [-t|--technote DATETIME ]"); } ADDED src/backlink.c Index: src/backlink.c ================================================================== --- /dev/null +++ src/backlink.c @@ -0,0 +1,398 @@ +/* +** Copyright (c) 2020 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@sqlite.org +** http://www.hwaci.com/drh/ +** +******************************************************************************* +** +** This file contains code to implement for managing backlinks and +** the "backlink" table of the repository database. +** +** A backlink is a reference in Fossil-Wiki or Markdown to some other +** object in the repository. +*/ +#include "config.h" +#include "backlink.h" +#include <assert.h> + + +/* +** Show a graph all wiki, tickets, and check-ins that refer to object zUuid. +** +** If zLabel is not NULL and the graph is not empty, then output zLabel as +** a prefix to the graph. +*/ +void render_backlink_graph(const char *zUuid, const char *zLabel){ + Blob sql; + Stmt q; + char *zGlob; + zGlob = mprintf("%.5s*", zUuid); + db_multi_exec( + "CREATE TEMP TABLE IF NOT EXISTS ok(rid INTEGER PRIMARY KEY);\n" + "DELETE FROM ok;\n" + "INSERT OR IGNORE INTO ok(rid)\n" + " SELECT CASE srctype\n" + " WHEN 2 THEN (SELECT rid FROM tagxref WHERE tagid=backlink.srcid\n" + " ORDER BY mtime DESC LIMIT 1)\n" + " ELSE srcid END\n" + " FROM backlink\n" + " WHERE target GLOB %Q" + " AND %Q GLOB (target || '*');", + zGlob, zUuid + ); + if( !db_exists("SELECT 1 FROM ok") ) return; + if( zLabel ) cgi_printf("%s", zLabel); + blob_zero(&sql); + blob_append(&sql, timeline_query_for_www(), -1); + blob_append_sql(&sql, " AND event.objid IN ok ORDER BY mtime DESC"); + db_prepare(&q, "%s", blob_sql_text(&sql)); + www_print_timeline(&q, + TIMELINE_DISJOINT|TIMELINE_GRAPH|TIMELINE_NOSCROLL|TIMELINE_REFS, + 0, 0, 0, 0, 0, 0); + db_finalize(&q); +} + +/* +** WEBPAGE: test-backlink-timeline +** +** Show a timeline of all check-ins and other events that have entries +** in the backlink table. This is used for testing the rendering +** of the "References" section of the /info page. +*/ +void backlink_timeline_page(void){ + Blob sql; + Stmt q; + + login_check_credentials(); + if( !g.perm.Read || !g.perm.RdTkt || !g.perm.RdWiki ){ + login_needed(g.anon.Read && g.anon.RdTkt && g.anon.RdWiki); + return; + } + style_set_current_feature("test"); + style_header("Backlink Timeline (Internal Testing Use)"); + db_multi_exec( + "CREATE TEMP TABLE IF NOT EXISTS ok(rid INTEGER PRIMARY KEY);" + "DELETE FROM ok;" + "INSERT OR IGNORE INTO ok" + " SELECT blob.rid FROM backlink, blob" + " WHERE blob.uuid BETWEEN backlink.target AND (backlink.target||'x')" + ); + blob_zero(&sql); + blob_append(&sql, timeline_query_for_www(), -1); + blob_append_sql(&sql, " AND event.objid IN ok ORDER BY mtime DESC"); + db_prepare(&q, "%s", blob_sql_text(&sql)); + www_print_timeline(&q, TIMELINE_DISJOINT|TIMELINE_GRAPH|TIMELINE_NOSCROLL, + 0, 0, 0, 0, 0, 0); + db_finalize(&q); + style_finish_page(); +} + +/* +** WEBPAGE: test-backlinks +** +** Show a table of all backlinks. Admin access only. +*/ +void backlink_table_page(void){ + Stmt q; + int n; + login_check_credentials(); + if( !g.perm.Admin ){ + login_needed(g.anon.Admin); + return; + } + style_set_current_feature("test"); + style_header("Backlink Table (Internal Testing Use)"); + n = db_int(0, "SELECT count(*) FROM backlink"); + @ <p>%d(n) backlink table entries:</p> + db_prepare(&q, + "SELECT target, srctype, srcid, datetime(mtime)," + " CASE srctype" + " WHEN 2 THEN (SELECT substr(tagname,6) FROM tag" + " WHERE tagid=srcid AND tagname GLOB 'wiki-*')" + " ELSE null END FROM backlink" + ); + style_table_sorter(); + @ <table border="1" cellpadding="2" cellspacing="0" \ + @ class='sortable' data-column-types='ttt' data-init-sort='0'> + @ <thead><tr><th> Source <th> Target <th> mtime </tr></thead> + @ <tbody> + while( db_step(&q)==SQLITE_ROW ){ + const char *zTarget = db_column_text(&q, 0); + int srctype = db_column_int(&q, 1); + int srcid = db_column_int(&q, 2); + const char *zMtime = db_column_text(&q, 3); + @ <tr><td><a href="%R/info/%h(zTarget)">%h(zTarget)</a> + switch( srctype ){ + case BKLNK_COMMENT: { + @ <td><a href="%R/info?name=rid:%d(srcid)">checkin-%d(srcid)</a> + break; + } + case BKLNK_TICKET: { + @ <td><a href="%R/info?name=rid:%d(srcid)">ticket-%d(srcid)</a> + break; + } + case BKLNK_WIKI: { + const char *zName = db_column_text(&q, 4); + @ <td><a href="%R/wiki?name=%h(zName)&p">wiki-%d(srcid)</a> + break; + } + case BKLNK_EVENT: { + @ <td><a href="%R/info?name=rid:%d(srcid)">tecknote-%d(srcid)</a> + break; + } + case BKLNK_FORUM: { + @ <td><a href="%R/info?name=rid:%d(srcid)">forum-%d(srcid)</a> + break; + } + default: { + @ <td>unknown(%d(srctype)) - %d(srcid) + break; + } + } + @ <td>%h(zMtime)</tr> + } + @ </tbody> + @ </table> + db_finalize(&q); + style_finish_page(); +} + +/* +** Remove all prior backlinks for the wiki page given. Then +** add new backlinks for the latest version of the wiki page. +*/ +void backlink_wiki_refresh(const char *zWikiTitle){ + int tagid = wiki_tagid(zWikiTitle); + int rid; + Manifest *pWiki; + if( tagid==0 ) return; + rid = db_int(0, "SELECT rid FROM tagxref WHERE tagid=%d" + " ORDER BY mtime DESC LIMIT 1", tagid); + if( rid==0 ) return; + pWiki = manifest_get(rid, CFTYPE_WIKI, 0); + if( pWiki ){ + backlink_extract(pWiki->zWiki, pWiki->zMimetype, tagid, BKLNK_WIKI, + pWiki->rDate, 1); + manifest_destroy(pWiki); + } +} + +/* +** Structure used to pass down state information through the +** markup formatters into the BACKLINK generator. +*/ +#if INTERFACE +struct Backlink { + int srcid; /* srcid for the source document */ + int srctype; /* One of BKLNK_*. 0=comment 1=ticket 2=wiki */ + double mtime; /* mtime field for new BACKLINK table entries */ +}; +#endif + + +/* +** zTarget is a hyperlink target in some markup format. If this +** target is a self-reference to some other object in the repository, +** then create an appropriate backlink. +*/ +void backlink_create(Backlink *p, const char *zTarget, int nTarget){ + char zLink[HNAME_MAX+4]; + if( zTarget==0 ) return; + if( nTarget<4 ) return; + if( nTarget>=10 && strncmp(zTarget,"/info/",6)==0 ){ + zTarget += 6; + nTarget -= 6; + } + if( nTarget>HNAME_MAX ) return; + if( !validate16(zTarget, nTarget) ) return; + memcpy(zLink, zTarget, nTarget); + zLink[nTarget] = 0; + canonical16(zLink, nTarget); + db_multi_exec( + "REPLACE INTO backlink(target,srctype,srcid,mtime)" + "VALUES(%Q,%d,%d,%.17g)", zLink, p->srctype, p->srcid, p->mtime + ); +} + +/* +** This routine is called by the markdown formatter for each hyperlink. +** If the hyperlink is a backlink, add it to the BACKLINK table. +*/ +static int backlink_md_link( + Blob *ob, /* Write output text here (not used in this case) */ + Blob *target, /* The hyperlink target */ + Blob *title, /* Hyperlink title */ + Blob *content, /* Content of the link */ + void *opaque +){ + Backlink *p = (Backlink*)opaque; + char *zTarget = blob_buffer(target); + int nTarget = blob_size(target); + + backlink_create(p, zTarget, nTarget); + return 1; +} + +/* No-op routine for the rendering callbacks that we do not need */ +static void mkdn_noop0(Blob *x){ return; } +static int mkdn_noop1(Blob *x){ return 1; } + +/* +** Scan markdown text and add self-hyperlinks to the BACKLINK table. +*/ +void markdown_extract_links( + char *zInputText, + Backlink *p +){ + struct mkd_renderer html_renderer = { + /* prolog */ (void(*)(Blob*,void*))mkdn_noop0, + /* epilog */ (void(*)(Blob*,void*))mkdn_noop0, + /* blockcode */ (void(*)(Blob*,Blob*,void*))mkdn_noop0, + /* blockquote */ (void(*)(Blob*,Blob*,void*))mkdn_noop0, + /* blockhtml */ (void(*)(Blob*,Blob*,void*))mkdn_noop0, + /* header */ (void(*)(Blob*,Blob*,int,void*))mkdn_noop0, + /* hrule */ (void(*)(Blob*,void*))mkdn_noop0, + /* list */ (void(*)(Blob*,Blob*,int,void*))mkdn_noop0, + /* listitem */ (void(*)(Blob*,Blob*,int,void*))mkdn_noop0, + /* paragraph */ (void(*)(Blob*,Blob*,void*))mkdn_noop0, + /* table */ (void(*)(Blob*,Blob*,Blob*,void*))mkdn_noop0, + /* table_cell */ (void(*)(Blob*,Blob*,int,void*))mkdn_noop0, + /* table_row */ (void(*)(Blob*,Blob*,int,void*))mkdn_noop0, + /* autolink */ (int(*)(Blob*,Blob*,enum mkd_autolink,void*))mkdn_noop1, + /* codespan */ (int(*)(Blob*,Blob*,int,void*))mkdn_noop1, + /* dbl_emphas */ (int(*)(Blob*,Blob*,char,void*))mkdn_noop1, + /* emphasis */ (int(*)(Blob*,Blob*,char,void*))mkdn_noop1, + /* image */ (int(*)(Blob*,Blob*,Blob*,Blob*,void*))mkdn_noop1, + /* linebreak */ (int(*)(Blob*,void*))mkdn_noop1, + /* link */ backlink_md_link, + /* r_html_tag */ (int(*)(Blob*,Blob*,void*))mkdn_noop1, + /* tri_emphas */ (int(*)(Blob*,Blob*,char,void*))mkdn_noop1, + 0, /* entity */ + 0, /* normal_text */ + "*_", /* emphasis characters */ + 0 /* client data */ + }; + Blob out, in; + html_renderer.opaque = (void*)p; + blob_init(&out, 0, 0); + blob_init(&in, zInputText, -1); + markdown(&out, &in, &html_renderer); + blob_reset(&out); + blob_reset(&in); +} + +/* +** Parse text looking for hyperlinks. Insert references into the +** BACKLINK table. +*/ +void backlink_extract( + char *zSrc, /* Input text from which links are extracted */ + const char *zMimetype, /* Mimetype of input. NULL means fossil-wiki */ + int srcid, /* srcid for the source document */ + int srctype, /* One of BKLNK_*. 0=comment 1=ticket 2=wiki */ + double mtime, /* mtime field for new BACKLINK table entries */ + int replaceFlag /* True to overwrite prior BACKLINK entries */ +){ + Backlink bklnk; + if( replaceFlag ){ + db_multi_exec("DELETE FROM backlink WHERE srctype=%d AND srcid=%d", + srctype, srcid); + } + bklnk.srcid = srcid; + assert( ValidBklnk(srctype) ); + bklnk.srctype = srctype; + bklnk.mtime = mtime; + if( zMimetype==0 || strstr(zMimetype,"wiki")!=0 ){ + wiki_extract_links(zSrc, &bklnk, srctype==BKLNK_COMMENT ? WIKI_INLINE : 0); + }else if( strstr(zMimetype,"markdown")!=0 ){ + markdown_extract_links(zSrc, &bklnk); + } +} + +/* +** COMMAND: test-backlinks +** +** Usage: %fossil test-backlinks SRCTYPE SRCID ?OPTIONS? INPUT-FILE +** +** Read the content of INPUT-FILE and pass it into the backlink_extract() +** routine. But instead of adding backlinks to the backlink table, +** just print them on stdout. SRCID and SRCTYPE are integers. +** +** Options: +** --mtime DATETIME Use an alternative date/time. Defaults to the +** current date/time. +** --mimetype TYPE Use an alternative mimetype. +*/ +void test_backlinks_cmd(void){ + const char *zMTime = find_option("mtime",0,1); + const char *zMimetype = find_option("mimetype",0,1); + Blob in; + int srcid; + int srctype; + double mtime; + + verify_all_options(); + if( g.argc!=5 ){ + usage("SRCTYPE SRCID INPUTFILE"); + } + srctype = atoi(g.argv[2]); + if( srctype<0 || srctype>2 ){ + fossil_fatal("SRCTYPE should be a integer 0, 1, or 2"); + } + srcid = atoi(g.argv[3]); + blob_read_from_file(&in, g.argv[4], ExtFILE); + sqlite3_open(":memory:",&g.db); + if( zMTime==0 ) zMTime = "now"; + mtime = db_double(1721059.5,"SELECT julianday(%Q)",zMTime); + g.fSqlPrint = 1; + sqlite3_create_function(g.db, "print", -1, SQLITE_UTF8, 0,db_sql_print,0,0); + db_multi_exec( + "CREATE TEMP TABLE backlink(target,srctype,srcid,mtime);\n" + "CREATE TRIGGER backlink_insert BEFORE INSERT ON backlink BEGIN\n" + " SELECT print(" + " 'target='||quote(new.target)||" + " ' srctype='||quote(new.srctype)||" + " ' srcid='||quote(new.srcid)||" + " ' mtime='||datetime(new.mtime));\n" + " SELECT raise(ignore);\n" + "END;" + ); + backlink_extract(blob_str(&in),zMimetype,srcid,srctype,mtime,0); + blob_reset(&in); +} + + +/* +** COMMAND: test-wiki-relink +** +** Usage: %fossil test-wiki-relink WIKI-PAGE-NAME +** +** Run the backlink_wiki_refresh() procedure on the wiki page +** named. WIKI-PAGE-NAME can be a glob pattern or a prefix +** of the wiki page. +*/ +void test_wiki_relink_cmd(void){ + Stmt q; + db_find_and_open_repository(0, 0); + if( g.argc!=3 ) usage("WIKI-PAGE-NAME"); + db_prepare(&q, + "SELECT substr(tagname,6) FROM tag WHERE tagname GLOB 'wiki-%q*'", + g.argv[2] + ); + while( db_step(&q)==SQLITE_ROW ){ + const char *zPage = db_column_text(&q,0); + fossil_print("Relinking page: %s\n", zPage); + backlink_wiki_refresh(zPage); + } + db_finalize(&q); +} ADDED src/backoffice.c Index: src/backoffice.c ================================================================== --- /dev/null +++ src/backoffice.c @@ -0,0 +1,881 @@ +/* +** Copyright (c) 2018 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 code used to manage a background processes that +** occur after user interaction with the repository. Examples of +** backoffice processing includes: +** +** * Sending alerts and notifications +** * Processing the email queue +** * Handling post-receive hooks +** * Automatically syncing to peer repositories +** +** Backoffice processing is automatically started whenever there are +** changes to the repository. The backoffice process dies off after +** a period of inactivity. +** +** Steps are taken to ensure that only a single backoffice process is +** running at a time. Otherwise, there could be race conditions that +** cause adverse effects such as multiple alerts for the same changes. +** +** At the same time, we do not want a backoffice process to run forever. +** Backoffice processes should die off after doing whatever work they need +** to do. In this way, we avoid having lots of idle processes in the +** process table, doing nothing on rarely accessed repositories, and +** if the Fossil binary is updated on a system, the backoffice processes +** will restart using the new binary automatically. +** +** At any point in time there should be at most two backoffice processes. +** There is a main process that is doing the actually work, and there is +** a second stand-by process that is waiting for the main process to finish +** and that will become the main process after a delay. +** +** After any successful web page reply, the backoffice_check_if_needed() +** routine is called. That routine checks to see if both one or both of +** the backoffice processes are already running. That routine remembers the +** status in a global variable. +** +** Later, after the repository database is closed, the +** backoffice_run_if_needed() routine is called. If the prior call +** to backoffice_check_if_needed() indicated that backoffice processing +** might be required, the run_if_needed() attempts to kick off a backoffice +** process. +** +** All work performance by the backoffice is in the backoffice_work() +** routine. +*/ +#if defined(_WIN32) +# if defined(_WIN32_WINNT) +# undef _WIN32_WINNT +# endif +# define _WIN32_WINNT 0x501 +#endif +#include "config.h" +#include "backoffice.h" +#include <time.h> +#if defined(_WIN32) +# include <windows.h> +# include <stdio.h> +# include <process.h> +# if defined(__MINGW32__) +# include <wchar.h> +# endif +# define GETPID (int)GetCurrentProcessId +#else +# include <unistd.h> +# include <sys/types.h> +# include <signal.h> +# include <errno.h> +# include <sys/time.h> +# include <sys/resource.h> +# include <fcntl.h> +# define GETPID getpid +#endif +#include <time.h> + +/* +** The BKOFCE_LEASE_TIME is the amount of time for which a single backoffice +** processing run is valid. Each backoffice run monopolizes the lease for +** at least this amount of time. Hopefully all backoffice processing is +** finished much faster than this - usually in less than a second. But +** regardless of how long each invocation lasts, successive backoffice runs +** must be spaced out by at least this much time. +*/ +#define BKOFCE_LEASE_TIME 60 /* Length of lease validity in seconds */ + +#if LOCAL_INTERFACE +/* +** An instance of the following object describes a lease on the backoffice +** processing timeslot. This lease is used to help ensure that no more than +** one process is running backoffice at a time. +*/ +struct Lease { + sqlite3_uint64 idCurrent; /* process ID for the current lease holder */ + sqlite3_uint64 tmCurrent; /* Expiration of the current lease */ + sqlite3_uint64 idNext; /* process ID for the next lease holder on queue */ + sqlite3_uint64 tmNext; /* Expiration of the next lease */ +}; +#endif + +/*************************************************************************** +** Local state variables +** +** Set to prevent backoffice processing from ever entering sleep or +** otherwise taking a long time to complete. Set this when a user-visible +** process might need to wait for backoffice to complete. +*/ +static int backofficeNoDelay = 0; + +/* This variable is set to the name of a database on which backoffice +** should run if backoffice process is needed. It is set by the +** backoffice_check_if_needed() routine which must be run while the database +** file is open. Later, after the database is closed, the +** backoffice_run_if_needed() will consult this variable to see if it +** should be a no-op. +** +** The magic string "x" in this variable means "do not run the backoffice". +*/ +static char *backofficeDb = 0; + +/* +** Log backoffice activity to a file named here. If not NULL, this +** overrides the "backoffice-logfile" setting of the database. If NULL, +** the "backoffice-logfile" setting is used instead. +*/ +static const char *backofficeLogfile = 0; + +/* +** Write the log message into this open file. +*/ +static FILE *backofficeFILE = 0; + +/* +** Write backoffice log messages on this BLOB. to this connection: +*/ +static Blob *backofficeBlob = 0; + +/* +** Non-zero for extra logging detail. +*/ +static int backofficeLogDetail = 0; + +/* End of state variables +****************************************************************************/ + +/* +** This function emits a diagnostic message related to the processing in +** this module. +*/ +#if defined(_WIN32) +# define BKOFCE_ALWAYS_TRACE (1) +extern void sqlite3_win32_write_debug(const char *, int); +#else +# define BKOFCE_ALWAYS_TRACE (0) +#endif +static void backofficeTrace(const char *zFormat, ...){ + char *zMsg = 0; + if( BKOFCE_ALWAYS_TRACE || g.fAnyTrace ){ + va_list ap; + va_start(ap, zFormat); + zMsg = sqlite3_vmprintf(zFormat, ap); + va_end(ap); +#if defined(_WIN32) + sqlite3_win32_write_debug(zMsg, -1); +#endif + } + if( g.fAnyTrace ) fprintf(stderr, "%s", zMsg); + if( zMsg ) sqlite3_free(zMsg); +} + +/* +** Do not allow backoffice processes to sleep waiting on a timeslot. +** They must either do their work immediately or exit. +** +** In a perfect world, this interface would not exist, as there would +** never be a problem with waiting backoffice threads. But in some cases +** a backoffice will delay a UI thread, so we don't want them to run for +** longer than needed. +*/ +void backoffice_no_delay(void){ + backofficeNoDelay = 1; +} + +/* +** Sleeps for the specified number of milliseconds -OR- until interrupted +** by another thread (if supported by the underlying platform). Non-zero +** will be returned if the sleep was interrupted. +*/ +static int backofficeSleep(int milliseconds){ +#if defined(_WIN32) + assert( milliseconds>=0 ); + if( SleepEx((DWORD)milliseconds, TRUE)==WAIT_IO_COMPLETION ){ + return 1; + } +#else + sqlite3_sleep(milliseconds); +#endif + return 0; +} + +/* +** Parse a unsigned 64-bit integer from a string. Return a pointer +** to the character of z[] that occurs after the integer. +*/ +static const char *backofficeParseInt(const char *z, sqlite3_uint64 *pVal){ + *pVal = 0; + if( z==0 ) return 0; + while( fossil_isspace(z[0]) ){ z++; } + while( fossil_isdigit(z[0]) ){ + *pVal = (*pVal)*10 + z[0] - '0'; + z++; + } + return z; +} + +/* +** Read the "backoffice" property and parse it into a Lease object. +** +** The backoffice property should consist of four integers: +** +** (1) Process ID for the active backoffice process. +** (2) Time (seconds since 1970) for when the active backoffice +** lease expires. +** (3) Process ID for the on-deck backoffice process. +** (4) Time when the on-deck process should expire. +** +** No other process should start active backoffice processing until +** process (1) no longer exists and the current time exceeds (2). +*/ +static void backofficeReadLease(Lease *pLease){ + Stmt q; + memset(pLease, 0, sizeof(*pLease)); + db_unprotect(PROTECT_CONFIG); + db_prepare(&q, "SELECT value FROM repository.config" + " WHERE name='backoffice'"); + if( db_step(&q)==SQLITE_ROW ){ + const char *z = db_column_text(&q,0); + z = backofficeParseInt(z, &pLease->idCurrent); + z = backofficeParseInt(z, &pLease->tmCurrent); + z = backofficeParseInt(z, &pLease->idNext); + backofficeParseInt(z, &pLease->tmNext); + } + db_finalize(&q); + db_protect_pop(); +} + +/* +** Return a string that describes how long it has been since the +** last backoffice run. The string is obtained from fossil_malloc(). +*/ +char *backoffice_last_run(void){ + Lease x; + sqlite3_uint64 tmNow; + double rAge; + backofficeReadLease(&x); + tmNow = time(0); + if( x.tmCurrent==0 ){ + return fossil_strdup("never"); + } + if( tmNow<=(x.tmCurrent-BKOFCE_LEASE_TIME) ){ + return fossil_strdup("moments ago"); + } + rAge = (tmNow - (x.tmCurrent-BKOFCE_LEASE_TIME))/86400.0; + return mprintf("%z ago", human_readable_age(rAge)); +} + +/* +** Write a lease to the backoffice property +*/ +static void backofficeWriteLease(Lease *pLease){ + db_unprotect(PROTECT_CONFIG); + db_multi_exec( + "REPLACE INTO repository.config(name,value,mtime)" + " VALUES('backoffice','%lld %lld %lld %lld',now())", + pLease->idCurrent, pLease->tmCurrent, + pLease->idNext, pLease->tmNext); + db_protect_pop(); +} + +/* +** Check to see if the specified Win32 process is still alive. It +** should be noted that even if this function returns non-zero, the +** process may die before another operation on it can be completed. +*/ +#if defined(_WIN32) +#ifndef PROCESS_QUERY_LIMITED_INFORMATION +# define PROCESS_QUERY_LIMITED_INFORMATION (0x1000) +#endif +static int backofficeWin32ProcessExists(DWORD dwProcessId){ + HANDLE hProcess; + hProcess = OpenProcess(PROCESS_QUERY_LIMITED_INFORMATION,FALSE,dwProcessId); + if( hProcess==NULL ) return 0; + CloseHandle(hProcess); + return 1; +} +#endif + +/* +** Check to see if the process identified by pid is alive. If +** we cannot prove that the process is dead, return true. +*/ +static int backofficeProcessExists(sqlite3_uint64 pid){ +#if defined(_WIN32) + return pid>0 && backofficeWin32ProcessExists((DWORD)pid)!=0; +#else + return pid>0 && kill((pid_t)pid, 0)==0; +#endif +} + +/* +** Check to see if the process identified by pid has finished. If +** we cannot prove that the process is still running, return true. +*/ +static int backofficeProcessDone(sqlite3_uint64 pid){ +#if defined(_WIN32) + return pid<=0 || backofficeWin32ProcessExists((DWORD)pid)==0; +#else + return pid<=0 || kill((pid_t)pid, 0)!=0; +#endif +} + +/* +** Return a process id number for the current process +*/ +static sqlite3_uint64 backofficeProcessId(void){ + return (sqlite3_uint64)GETPID(); +} + + +/* +** COMMAND: test-process-id +** +** Usage: %fossil [--sleep N] PROCESS-ID ... +** +** Show the current process id, and also tell whether or not all other +** processes IDs on the command line are running or not. If the --sleep N +** option is provide, then sleep for N seconds before exiting. +*/ +void test_process_id_command(void){ + const char *zSleep = find_option("sleep",0,1); + int i; + verify_all_options(); + fossil_print("ProcessID for this process: %lld\n", backofficeProcessId()); + if( zSleep ) sqlite3_sleep(1000*atoi(zSleep)); + for(i=2; i<g.argc; i++){ + sqlite3_uint64 x = (sqlite3_uint64)atoi(g.argv[i]); + fossil_print("ProcessId %lld: exists %d done %d\n", + x, backofficeProcessExists(x), + backofficeProcessDone(x)); + } +} + +/* +** COMMAND: test-backoffice-lease +** +** Usage: %fossil test-backoffice-lease ?--reset? +** +** Print out information about the backoffice "lease" entry in the +** config table that controls whether or not backoffice should run. +** +** If the --reset option is given, the backoffice lease is reset. +** The use of the --reset option can be disruptive. It can cause two +** or more backoffice processes to be run simultaneously. Use it with +** caution. +*/ +void test_backoffice_lease(void){ + sqlite3_int64 tmNow = time(0); + Lease x; + const char *zLease; + db_find_and_open_repository(0,0); + if( find_option("reset",0,0)!=0 ){ + db_unprotect(PROTECT_CONFIG); + db_multi_exec( + "DELETE FROM repository.config WHERE name='backoffice'" + ); + db_protect_pop(); + } + verify_all_options(); + zLease = db_get("backoffice",""); + fossil_print("now: %lld\n", tmNow); + fossil_print("lease: \"%s\"\n", zLease); + backofficeReadLease(&x); + fossil_print("idCurrent: %-20lld", x.idCurrent); + if( backofficeProcessExists(x.idCurrent) ) fossil_print(" (exists)"); + if( backofficeProcessDone(x.idCurrent) ) fossil_print(" (done)"); + fossil_print("\n"); + fossil_print("tmCurrent: %-20lld", x.tmCurrent); + if( x.tmCurrent>0 ){ + fossil_print(" (now%+d)\n",x.tmCurrent-tmNow); + }else{ + fossil_print("\n"); + } + fossil_print("idNext: %-20lld", x.idNext); + if( backofficeProcessExists(x.idNext) ) fossil_print(" (exists)"); + if( backofficeProcessDone(x.idNext) ) fossil_print(" (done)"); + fossil_print("\n"); + fossil_print("tmNext: %-20lld", x.tmNext); + if( x.tmNext>0 ){ + fossil_print(" (now%+d)\n",x.tmNext-tmNow); + }else{ + fossil_print("\n"); + } +} + +/* +** If backoffice processing is needed set the backofficeDb variable to the +** name of the database file. If no backoffice processing is needed, +** this routine makes no changes to state. +*/ +void backoffice_check_if_needed(void){ + Lease x; + sqlite3_uint64 tmNow; + + if( backofficeDb ) return; + if( g.zRepositoryName==0 ) return; + if( g.db==0 ) return; + if( !db_table_exists("repository","config") ) return; + if( db_get_boolean("backoffice-disable",0) ) return; + tmNow = time(0); + backofficeReadLease(&x); + if( x.tmNext>=tmNow && backofficeProcessExists(x.idNext) ){ + /* Another backoffice process is already queued up to run. This + ** process does not need to do any backoffice work. */ + return; + }else{ + /* We need to run backup to be (at a minimum) on-deck */ + backofficeDb = fossil_strdup(g.zRepositoryName); + } +} + +/* +** Call this routine to disable backoffice +*/ +void backoffice_disable(void){ + backofficeDb = "x"; +} + +/* +** Check for errors prior to running backoffice_thread() or backoffice_run(). +*/ +static void backoffice_error_check_one(int *pOnce){ + if( *pOnce ){ + fossil_panic("multiple calls to backoffice()"); + } + *pOnce = 1; + if( g.db==0 ){ + fossil_panic("database not open for backoffice processing"); + } + if( db_transaction_nesting_depth()!=0 ){ + fossil_panic("transaction %s not closed prior to backoffice processing", + db_transaction_start_point()); + } +} + +/* This is the main loop for backoffice processing. +** +** If another process is already working as the current backoffice and +** the on-deck backoffice, then this routine returns very quickly +** without doing any work. +** +** If no backoffice processes are running at all, this routine becomes +** the main backoffice. +** +** If a primary backoffice is running, but a on-deck backoffice is +** needed, this routine becomes that on-deck backoffice. +*/ +static void backoffice_thread(void){ + Lease x; + sqlite3_uint64 tmNow; + sqlite3_uint64 idSelf; + int lastWarning = 0; + int warningDelay = 30; + static int once = 0; + + if( sqlite3_db_readonly(g.db, 0) ) return; + backoffice_error_check_one(&once); + idSelf = backofficeProcessId(); + while(1){ + tmNow = time(0); + db_begin_write(); + backofficeReadLease(&x); + if( x.tmNext>=tmNow + && x.idNext!=idSelf + && backofficeProcessExists(x.idNext) + ){ + /* Another backoffice process is already queued up to run. This + ** process does not need to do any backoffice work and can stop + ** immediately. */ + db_end_transaction(0); + backofficeTrace("/***** Backoffice Processing Not Needed In %d *****/\n", + GETPID()); + break; + } + if( x.tmCurrent<tmNow && backofficeProcessDone(x.idCurrent) ){ + /* This process can start doing backoffice work immediately */ + x.idCurrent = idSelf; + x.tmCurrent = tmNow + BKOFCE_LEASE_TIME; + x.idNext = 0; + x.tmNext = 0; + backofficeWriteLease(&x); + db_end_transaction(0); + backofficeTrace("/***** Begin Backoffice Processing %d *****/\n", + GETPID()); + backoffice_work(); + break; + } + if( backofficeNoDelay || db_get_boolean("backoffice-nodelay",0) ){ + /* If the no-delay flag is set, exit immediately rather than queuing + ** up. Assume that some future request will come along and handle any + ** necessary backoffice work. */ + db_end_transaction(0); + backofficeTrace( + "/***** Backoffice No-Delay Exit For %d *****/\n", + GETPID()); + break; + } + /* This process needs to queue up and wait for the current lease + ** to expire before continuing. */ + x.idNext = idSelf; + x.tmNext = (tmNow>x.tmCurrent ? tmNow : x.tmCurrent) + BKOFCE_LEASE_TIME; + backofficeWriteLease(&x); + db_end_transaction(0); + backofficeTrace("/***** Backoffice On-deck %d *****/\n", GETPID()); + if( x.tmCurrent >= tmNow ){ + if( backofficeSleep(1000*(x.tmCurrent - tmNow + 1)) ){ + /* The sleep was interrupted by a signal from another thread. */ + backofficeTrace("/***** Backoffice Interrupt %d *****/\n", GETPID()); + db_end_transaction(0); + break; + } + }else{ + if( lastWarning+warningDelay < tmNow ){ + fossil_warning( + "backoffice process %lld still running after %d seconds", + x.idCurrent, (int)(BKOFCE_LEASE_TIME + tmNow - x.tmCurrent)); + lastWarning = tmNow; + warningDelay *= 2; + } + if( backofficeSleep(1000) ){ + /* The sleep was interrupted by a signal from another thread. */ + backofficeTrace("/***** Backoffice Interrupt %d *****/\n", GETPID()); + db_end_transaction(0); + break; + } + } + } + return; +} + +/* +** Append to a message to the backoffice log, if the log is open. +*/ +void backoffice_log(const char *zFormat, ...){ + va_list ap; + if( backofficeBlob==0 ) return; + blob_append_char(backofficeBlob, ' '); + va_start(ap, zFormat); + blob_vappendf(backofficeBlob, zFormat, ap); + va_end(ap); +} + +#if !defined(_WIN32) +/* +** Capture routine for signals while running backoffice. +*/ +static void backoffice_signal_handler(int sig){ + const char *zSig = 0; + if( sig==SIGSEGV ) zSig = "SIGSEGV"; + if( sig==SIGFPE ) zSig = "SIGFPE"; + if( sig==SIGABRT ) zSig = "SIGABRT"; + if( sig==SIGILL ) zSig = "SIGILL"; + if( zSig==0 ){ + backoffice_log("signal-%d", sig); + }else{ + backoffice_log("%s", zSig); + } + fprintf(backofficeFILE, "%s\n", blob_str(backofficeBlob)); + fflush(backofficeFILE); + exit(1); +} +#endif + +#if !defined(_WIN32) +/* +** Convert a struct timeval into an integer number of microseconds +*/ +static long long int tvms(struct timeval *p){ + return ((long long int)p->tv_sec)*1000000 + (long long int)p->tv_usec; +} +#endif + + +/* +** This routine runs to do the backoffice processing. When adding new +** backoffice processing tasks, add them here. +*/ +void backoffice_work(void){ + /* Log the backoffice run for testing purposes. For production deployments + ** the "backoffice-logfile" property should be unset and the following code + ** should be a no-op. */ + const char *zLog = backofficeLogfile; + Blob log; + int nThis; + int nTotal = 0; +#if !defined(_WIN32) + struct timeval sStart, sEnd; +#endif + if( zLog==0 ) zLog = db_get("backoffice-logfile",0); + if( zLog && zLog[0] && (backofficeFILE = fossil_fopen(zLog,"a"))!=0 ){ + int i; + char *zName = db_get("project-name",0); +#if !defined(_WIN32) + gettimeofday(&sStart, 0); + signal(SIGSEGV, backoffice_signal_handler); + signal(SIGABRT, backoffice_signal_handler); + signal(SIGFPE, backoffice_signal_handler); + signal(SIGILL, backoffice_signal_handler); +#endif + if( zName==0 ){ + zName = (char*)file_tail(g.zRepositoryName); + if( zName==0 ) zName = "(unnamed)"; + }else{ + /* Convert all spaces in the "project-name" into dashes */ + for(i=0; zName[i]; i++){ if( zName[i]==' ' ) zName[i] = '-'; } + } + blob_init(&log, 0, 0); + backofficeBlob = &log; + blob_appendf(&log, "%s %s", db_text(0, "SELECT datetime('now')"), zName); + } + + /* Here is where the actual work of the backoffice happens */ + nThis = alert_backoffice(0); + if( nThis ){ backoffice_log("%d alerts", nThis); nTotal += nThis; } + nThis = hook_backoffice(); + if( nThis ){ backoffice_log("%d hooks", nThis); nTotal += nThis; } + + /* Close the log */ + if( backofficeFILE ){ + if( nTotal || backofficeLogDetail ){ + if( nTotal==0 ) backoffice_log("no-op"); +#if !defined(_WIN32) + gettimeofday(&sEnd,0); + backoffice_log("elapse-time %d us", tvms(&sEnd) - tvms(&sStart)); +#endif + fprintf(backofficeFILE, "%s\n", blob_str(backofficeBlob)); + } + fclose(backofficeFILE); + } +} + +/* +** COMMAND: backoffice* +** +** Usage: %fossil backoffice [OPTIONS...] [REPOSITORIES...] +** +** Run backoffice processing on the repositories listed. If no +** repository is specified, run it on the repository of the local checkout. +** +** This might be done by a cron job or similar to make sure backoffice +** processing happens periodically. Or, the --poll option can be used +** to run this command as a daemon that will periodically invoke backoffice +** on a collection of repositories. +** +** If only a single repository is named and --poll is omitted, then the +** backoffice work is done in-process. But if there are multiple repositories +** or if --poll is used, a separate sub-process is started for each poll of +** each repository. +** +** Standard options: +** +** --debug Show what this command is doing. +** +** --logfile FILE Append a log of backoffice actions onto FILE. +** +** --min N When polling, invoke backoffice at least +** once every N seconds even if the repository +** never changes. 0 or negative means disable +** this feature. Default: 3600 (once per hour). +** +** --poll N Repeat backoffice calls for repositories that +** change in appoximately N-second intervals. +** N less than 1 turns polling off (the default). +** Recommended polling interval: 60 seconds. +** +** --trace Enable debugging output on stderr +** +** Options intended for internal use only which may change or be +** discontinued in future releases: +** +** --nodelay Do not queue up or wait for a backoffice job +** to complete. If no work is available or if +** backoffice has run recently, return immediately. +** +** --nolease Always run backoffice, even if there is a lease +** conflict. This option implies --nodelay. This +** option is added to secondary backoffice commands +** that are invoked by the --poll option. +*/ +void backoffice_command(void){ + int nPoll; + int nMin; + const char *zPoll; + int bDebug = 0; + int bNoLease = 0; + unsigned int nCmd = 0; + if( find_option("trace",0,0)!=0 ) g.fAnyTrace = 1; + if( find_option("nodelay",0,0)!=0 ) backofficeNoDelay = 1; + backofficeLogfile = find_option("logfile",0,1); + zPoll = find_option("poll",0,1); + nPoll = zPoll ? atoi(zPoll) : 0; + zPoll = find_option("min",0,1); + nMin = zPoll ? atoi(zPoll) : 3600; + bDebug = find_option("debug",0,0)!=0; + bNoLease = find_option("nolease",0,0)!=0; + + /* Silently consume the -R or --repository flag, leaving behind its + ** argument. This is for legacy compatibility. Older versions of the + ** backoffice command only ran on a single repository that was specified + ** using the -R option. */ + (void)find_option("repository","R",0); + + verify_all_options(); + if( g.argc>3 || nPoll>0 ){ + /* Either there are multiple repositories named on the command-line + ** or we are polling. In either case, each backoffice should be run + ** using a separate sub-process */ + int i; + time_t iNow = 0; + time_t ix; + i64 *aLastRun = fossil_malloc( sizeof(i64)*g.argc ); + memset(aLastRun, 0, sizeof(i64)*g.argc ); + while( 1 /* exit via "break;" */){ + time_t iNext = time(0); + for(i=2; i<g.argc; i++){ + Blob cmd; + if( !file_isfile(g.argv[i], ExtFILE) ){ + continue; /* Repo no longer exists. Ignore it. */ + } + if( iNow + && iNow>file_mtime(g.argv[i], ExtFILE) + && (nMin<=0 || aLastRun[i]+nMin>iNow) + ){ + continue; /* Not yet time to run this one */ + } + blob_init(&cmd, 0, 0); + blob_append_escaped_arg(&cmd, g.nameOfExe, 1); + blob_append(&cmd, " backoffice --nodelay", -1); + if( g.fAnyTrace ){ + blob_append(&cmd, " --trace", -1); + } + if( bDebug ){ + blob_append(&cmd, " --debug", -1); + } + if( nPoll>0 ){ + blob_append(&cmd, " --nolease", -1); + } + if( backofficeLogfile ){ + blob_append(&cmd, " --logfile", -1); + blob_append_escaped_arg(&cmd, backofficeLogfile, 1); + } + blob_append_escaped_arg(&cmd, g.argv[i], 1); + nCmd++; + if( bDebug ){ + fossil_print("COMMAND[%u]: %s\n", nCmd, blob_str(&cmd)); + } + fossil_system(blob_str(&cmd)); + aLastRun[i] = iNext; + blob_reset(&cmd); + } + if( nPoll<1 ) break; + iNow = iNext; + ix = time(0); + if( ix < iNow+nPoll ){ + sqlite3_int64 nMS = (iNow + nPoll - ix)*1000; + if( bDebug )fossil_print("SLEEP: %lld\n", nMS); + sqlite3_sleep((int)nMS); + } + } + }else{ + /* Not polling and only one repository named. Backoffice is run + ** once by this process, which then exits */ + if( g.argc==3 ){ + g.zRepositoryOption = g.argv[2]; + g.argc--; + } + db_find_and_open_repository(0,0); + if( bDebug ){ + backofficeLogDetail = 1; + } + if( bNoLease ){ + backoffice_work(); + }else{ + backoffice_thread(); + } + } +} + +/* +** This is the main interface to backoffice from the rest of the system. +** This routine launches either backoffice_thread() directly or as a +** subprocess. +*/ +void backoffice_run_if_needed(void){ + if( backofficeDb==0 ) return; + if( strcmp(backofficeDb,"x")==0 ) return; + if( g.db ) return; + if( g.repositoryOpen ) return; +#if defined(_WIN32) + { + int i; + intptr_t x; + char *argv[4]; + wchar_t *ax[5]; + argv[0] = g.nameOfExe; + argv[1] = "backoffice"; + argv[2] = "-R"; + argv[3] = backofficeDb; + ax[4] = 0; + for(i=0; i<=3; i++) ax[i] = fossil_utf8_to_unicode(argv[i]); + x = _wspawnv(_P_NOWAIT, ax[0], (const wchar_t * const *)ax); + for(i=0; i<=3; i++) fossil_unicode_free(ax[i]); + backofficeTrace( + "/***** Subprocess %d creates backoffice child %lu *****/\n", + GETPID(), GetProcessId((HANDLE)x)); + if( x>=0 ) return; + } +#else /* unix */ + { + pid_t pid = fork(); + if( pid>0 ){ + /* This is the parent in a successful fork(). Return immediately. */ + backofficeTrace( + "/***** Subprocess %d creates backoffice child %d *****/\n", + GETPID(), (int)pid); + return; + } + if( pid==0 ){ + /* This is the child of a successful fork(). Run backoffice. */ + int i; + setsid(); + for(i=0; i<=2; i++){ + close(i); + open("/dev/null", O_RDWR); + } + for(i=3; i<100; i++){ close(i); } + g.fDebug = 0; + g.httpIn = 0; + g.httpOut = 0; + db_open_repository(backofficeDb); + backofficeDb = "x"; + backoffice_thread(); + db_close(1); + backofficeTrace("/***** Backoffice Child %d exits *****/\n", GETPID()); + exit(0); + } + fossil_warning("backoffice process %d fork failed, errno %d", GETPID(), + errno); + } +#endif + /* Fork() failed or is unavailable. Run backoffice in this process, but + ** do so with the no-delay setting. + */ + backofficeNoDelay = 1; + db_open_repository(backofficeDb); + backofficeDb = "x"; + backoffice_thread(); + db_close(1); +} Index: src/bag.c ================================================================== --- src/bag.c +++ src/bag.c @@ -48,10 +48,16 @@ int cnt; /* Number of integers in the bag */ int sz; /* Number of slots in a[] */ int used; /* Number of used slots in a[] */ int *a; /* Hash table of integers that are in the bag */ }; +/* +** An expression for statically initializing a Bag instance, to be +** assigned to Bag instances at their declaration point. It has +** the same effect as passing the Bag to bag_init(). +*/ +#define Bag_INIT {0,0,0,0} #endif /* ** Initialize a Bag structure */ @@ -75,11 +81,11 @@ /* ** 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, it +** entries (indicated by a -1) are removed. When finished, it ** should be the case that p->cnt==p->used. */ static void bag_resize(Bag *p, int newSize){ int i; Bag old; @@ -86,11 +92,11 @@ int nDel = 0; /* Number of deleted entries */ int nLive = 0; /* Number of live entries */ old = *p; assert( newSize>old.cnt ); - p->a = malloc( sizeof(p->a[0])*newSize ); + p->a = fossil_malloc( sizeof(p->a[0])*newSize ); p->sz = newSize; memset(p->a, 0, sizeof(p->a[0])*newSize ); for(i=0; i<old.sz; i++){ int e = old.a[i]; if( e>0 ){ ADDED src/bisect.c Index: src/bisect.c ================================================================== --- /dev/null +++ src/bisect.c @@ -0,0 +1,713 @@ +/* +** 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@sqlite.org +** +******************************************************************************* +** +** This file contains code used to implement the "bisect" command. +** +** This file also contains logic used to compute the closure of filename +** changes that have occurred across multiple check-ins. +*/ +#include "config.h" +#include "bisect.h" +#include <assert.h> + +/* +** Local variables for this module +*/ +static struct { + int bad; /* The bad version */ + int good; /* The good version */ +} bisect; + +/* +** Find the shortest path between bad and good. +*/ +void bisect_path(void){ + PathNode *p; + bisect.bad = db_lget_int("bisect-bad", 0); + bisect.good = db_lget_int("bisect-good", 0); + if( bisect.good>0 && bisect.bad==0 ){ + path_shortest(bisect.good, bisect.good, 0, 0, 0); + }else if( bisect.bad>0 && bisect.good==0 ){ + path_shortest(bisect.bad, bisect.bad, 0, 0, 0); + }else if( bisect.bad==0 && bisect.good==0 ){ + fossil_fatal("neither \"good\" nor \"bad\" versions have been identified"); + }else{ + Bag skip; + int bDirect = bisect_option("direct-only"); + char *zLog = db_lget("bisect-log",""); + Blob log, id; + bag_init(&skip); + blob_init(&log, zLog, -1); + while( blob_token(&log, &id) ){ + if( blob_str(&id)[0]=='s' ){ + bag_insert(&skip, atoi(blob_str(&id)+1)); + } + } + blob_reset(&log); + p = path_shortest(bisect.good, bisect.bad, bDirect, 0, &skip); + bag_clear(&skip); + if( p==0 ){ + char *zBad = db_text(0,"SELECT uuid FROM blob WHERE rid=%d",bisect.bad); + char *zGood = db_text(0,"SELECT uuid FROM blob WHERE rid=%d",bisect.good); + fossil_fatal("no path from good ([%S]) to bad ([%S]) or back", + zGood, zBad); + } + } +} + +/* +** The set of all bisect options. +*/ +static const struct { + const char *zName; + const char *zDefault; + const char *zDesc; +} aBisectOption[] = { + { "auto-next", "on", "Automatically run \"bisect next\" after each " + "\"bisect good\", \"bisect bad\", or \"bisect " + "skip\"" }, + { "direct-only", "on", "Follow only primary parent-child links, not " + "merges\n" }, + { "display", "chart", "Command to run after \"next\". \"chart\", " + "\"log\", \"status\", or \"none\"" }, +}; + +/* +** Return the value of a boolean bisect option. +*/ +int bisect_option(const char *zName){ + unsigned int i; + int r = -1; + for(i=0; i<count(aBisectOption); i++){ + if( fossil_strcmp(zName, aBisectOption[i].zName)==0 ){ + char *zLabel = mprintf("bisect-%s", zName); + char *z; + if( g.localOpen ){ + z = db_lget(zLabel, (char*)aBisectOption[i].zDefault); + }else{ + z = (char*)aBisectOption[i].zDefault; + } + if( is_truth(z) ) r = 1; + if( is_false(z) ) r = 0; + if( r<0 ) r = is_truth(aBisectOption[i].zDefault); + free(zLabel); + break; + } + } + assert( r>=0 ); + return r; +} + +/* +** List a bisect path. +*/ +static void bisect_list(int abbreviated){ + PathNode *p; + int vid = db_lget_int("checkout", 0); + int n; + Stmt s; + int nStep; + int nHidden = 0; + bisect_path(); + db_prepare(&s, "SELECT blob.uuid, datetime(event.mtime) " + " FROM blob, event" + " WHERE blob.rid=:rid AND event.objid=:rid" + " AND event.type='ci'"); + nStep = path_length(); + if( abbreviated ){ + for(p=path_last(); p; p=p->pFrom) p->isHidden = 1; + for(p=path_last(), n=0; p; p=p->pFrom, n++){ + if( p->rid==bisect.good + || p->rid==bisect.bad + || p->rid==vid + || (nStep>1 && n==nStep/2) + ){ + p->isHidden = 0; + if( p->pFrom ) p->pFrom->isHidden = 0; + } + } + for(p=path_last(); p; p=p->pFrom){ + if( p->pFrom && p->pFrom->isHidden==0 ) p->isHidden = 0; + } + } + for(p=path_last(), n=0; p; p=p->pFrom, n++){ + if( p->isHidden && (nHidden || (p->pFrom && p->pFrom->isHidden)) ){ + nHidden++; + continue; + }else if( nHidden ){ + fossil_print(" ... %d other check-ins omitted\n", nHidden); + nHidden = 0; + } + db_bind_int(&s, ":rid", p->rid); + if( db_step(&s)==SQLITE_ROW ){ + const char *zUuid = db_column_text(&s, 0); + const char *zDate = db_column_text(&s, 1); + fossil_print("%s %S", zDate, zUuid); + if( p->rid==bisect.good ) fossil_print(" GOOD"); + if( p->rid==bisect.bad ) fossil_print(" BAD"); + if( p->rid==vid ) fossil_print(" CURRENT"); + if( nStep>1 && n==nStep/2 ) fossil_print(" NEXT"); + fossil_print("\n"); + } + db_reset(&s); + } + db_finalize(&s); +} + +/* +** Append a new entry to the bisect log. Update the bisect-good or +** bisect-bad values as appropriate. +** +** The bisect-log consists of a list of token. Each token is an +** integer RID of a check-in. The RID is negative for "bad" check-ins +** and positive for "good" check-ins. +*/ +static void bisect_append_log(int rid){ + if( rid<0 ){ + if( db_lget_int("bisect-bad",0)==(-rid) ) return; + db_lset_int("bisect-bad", -rid); + }else{ + if( db_lget_int("bisect-good",0)==rid ) return; + db_lset_int("bisect-good", rid); + } + db_multi_exec( + "REPLACE INTO vvar(name,value) VALUES('bisect-log'," + "COALESCE((SELECT value||' ' FROM vvar WHERE name='bisect-log'),'')" + " || '%d')", rid); +} + +/* +** Append a new skip entry to the bisect log. +*/ +static void bisect_append_skip(int rid){ + db_multi_exec( + "UPDATE vvar SET value=value||' s%d' WHERE name='bisect-log'", rid + ); +} + +/* +** Create a TEMP table named "bilog" that contains the complete history +** of the current bisect. +** +** If iCurrent>0 then it is the RID of the current checkout and is included +** in the history table. +** +** If zDesc is not NULL, then it is the bid= query parameter to /timeline +** that describes a bisect. Use the information in zDesc rather than in +** the bisect-log variable. +** +** If bDetail is true, then also include information about every node +** in between the inner-most GOOD and BAD nodes. +*/ +int bisect_create_bilog_table(int iCurrent, const char *zDesc, int bDetail){ + char *zLog; + Blob log, id; + Stmt q; + int cnt = 0; + int lastGood = -1; + int lastBad = -1; + + if( zDesc!=0 ){ + blob_init(&log, 0, 0); + while( zDesc[0]=='y' || zDesc[0]=='n' || zDesc[0]=='s' ){ + int i; + char c; + int rid; + if( blob_size(&log) ) blob_append(&log, " ", 1); + if( zDesc[0]=='n' ) blob_append(&log, "-", 1); + if( zDesc[0]=='s' ) blob_append(&log, "s", 1); + for(i=1; ((c = zDesc[i])>='0' && c<='9') || (c>='a' && c<='f'); i++){} + if( i==1 ) break; + rid = db_int(0, + "SELECT rid FROM blob" + " WHERE uuid LIKE '%.*q%%'" + " AND EXISTS(SELECT 1 FROM plink WHERE cid=rid)", + i-1, zDesc+1 + ); + if( rid==0 ) break; + blob_appendf(&log, "%d", rid); + zDesc += i; + } + }else{ + zLog = db_lget("bisect-log",""); + blob_init(&log, zLog, -1); + } + db_multi_exec( + "CREATE TEMP TABLE bilog(" + " rid INTEGER PRIMARY KEY," /* Sequence of events */ + " stat TEXT," /* Type of occurrence */ + " seq INTEGER UNIQUE" /* Check-in number */ + ");" + ); + db_prepare(&q, "INSERT OR IGNORE INTO bilog(seq,stat,rid)" + " VALUES(:seq,:stat,:rid)"); + while( blob_token(&log, &id) ){ + int rid; + db_bind_int(&q, ":seq", ++cnt); + if( blob_str(&id)[0]=='s' ){ + rid = atoi(blob_str(&id)+1); + db_bind_text(&q, ":stat", "SKIP"); + db_bind_int(&q, ":rid", rid); + }else{ + rid = atoi(blob_str(&id)); + if( rid>0 ){ + db_bind_text(&q, ":stat","GOOD"); + db_bind_int(&q, ":rid", rid); + lastGood = rid; + }else{ + db_bind_text(&q, ":stat", "BAD"); + db_bind_int(&q, ":rid", -rid); + lastBad = -rid; + } + } + db_step(&q); + db_reset(&q); + } + if( iCurrent>0 ){ + db_bind_int(&q, ":seq", ++cnt); + db_bind_text(&q, ":stat", "CURRENT"); + db_bind_int(&q, ":rid", iCurrent); + db_step(&q); + db_reset(&q); + } + if( bDetail && lastGood>0 && lastBad>0 ){ + PathNode *p; + p = path_shortest(lastGood, lastBad, bisect_option("direct-only"),0, 0); + while( p ){ + db_bind_null(&q, ":seq"); + db_bind_null(&q, ":stat"); + db_bind_int(&q, ":rid", p->rid); + db_step(&q); + db_reset(&q); + p = p->u.pTo; + } + path_reset(); + } + db_finalize(&q); + return 1; +} + +/* Return a permalink description of a bisect. Space is obtained from +** fossil_malloc() and should be freed by the caller. +** +** A bisect description consists of characters 'y' and 'n' and lowercase +** hex digits. Each term begins with 'y' or 'n' (success or failure) and +** is followed by a hash prefix in lowercase hex. +*/ +char *bisect_permalink(void){ + char *zLog = db_lget("bisect-log",""); + char *zResult; + Blob log; + Blob link = BLOB_INITIALIZER; + Blob id; + blob_init(&log, zLog, -1); + while( blob_token(&log, &id) ){ + const char *zUuid; + int rid; + char cPrefix = 'y'; + if( blob_str(&id)[0]=='s' ){ + rid = atoi(blob_str(&id)+1); + cPrefix = 's'; + }else{ + rid = atoi(blob_str(&id)); + if( rid<0 ){ + cPrefix = 'n'; + rid = -rid; + } + } + zUuid = db_text(0,"SELECT lower(uuid) FROM blob WHERE rid=%d", rid); + blob_appendf(&link, "%c%.10s", cPrefix, zUuid); + } + zResult = mprintf("%s", blob_str(&link)); + blob_reset(&link); + blob_reset(&log); + blob_reset(&id); + return zResult; +} + +/* +** Show a chart of bisect "good" and "bad" versions. The chart can be +** sorted either chronologically by bisect time, or by check-in time. +*/ +static void bisect_chart(int sortByCkinTime){ + Stmt q; + int iCurrent = db_lget_int("checkout",0); + bisect_create_bilog_table(iCurrent, 0, 0); + db_prepare(&q, + "SELECT bilog.seq, bilog.stat," + " substr(blob.uuid,1,16), datetime(event.mtime)," + " blob.rid==%d" + " FROM bilog, blob, event" + " WHERE blob.rid=bilog.rid AND event.objid=bilog.rid" + " AND event.type='ci'" + " ORDER BY %s bilog.rowid ASC", + iCurrent, (sortByCkinTime ? "event.mtime DESC, " : "") + ); + while( db_step(&q)==SQLITE_ROW ){ + const char *zGoodBad = db_column_text(&q, 1); + fossil_print("%3d %-7s %s %s%s\n", + db_column_int(&q, 0), + zGoodBad, + db_column_text(&q, 3), + db_column_text(&q, 2), + (db_column_int(&q, 4) && zGoodBad[0]!='C') ? " CURRENT" : ""); + } + db_finalize(&q); +} + + +/* +** Reset the bisect subsystem. +*/ +void bisect_reset(void){ + db_multi_exec( + "DELETE FROM vvar WHERE name IN " + " ('bisect-good', 'bisect-bad', 'bisect-log', 'bisect-complete')" + ); +} + +/* +** fossil bisect run [OPTIONS] COMMAND +** +** Invoke COMMAND (with arguments) repeatedly to perform the bisect. +** Options: +** +** -i|--interactive Prompt user for decisions rather than +** using the return code from COMMAND +*/ +static void bisect_run(void){ + const char *zCmd; + int isInteractive = 0; + int i; + if( g.argc<4 ){ + fossil_fatal("Usage: fossil bisect run [OPTIONS] COMMAND\n"); + } + for(i=3; i<g.argc-1; i++){ + const char *zArg = g.argv[i]; + if( zArg[0]=='-' && zArg[1]=='-' && zArg[2]!=0 ) zArg++; + if( strcmp(zArg, "-i")==0 || strcmp(zArg, "-interactive")==0 ){ + isInteractive = 1; + continue; + } + fossil_fatal("unknown command-line option: \"%s\"\n", g.argv[i]); + } + zCmd = g.argv[i]; + if( db_int(0, "SELECT count(*) FROM vvar" + " WHERE name IN ('bisect-good','bisect-bad')")!=2 ){ + fossil_fatal("need good/bad boundaries to use \"fossil bisect run\""); + } + while( db_lget_int("bisect-complete",0)==0 ){ + int rc; + Blob cmd; + blob_init(&cmd, 0, 0); + blob_append_escaped_arg(&cmd, g.nameOfExe, 1); + rc = fossil_unsafe_system(zCmd); + if( isInteractive ){ + Blob in; + fossil_print("test-command result: %d\n", rc); + while(1){ + int n; + char *z; + prompt_user("Enter (g)ood, (b)ad, (s)kip, (a)uto, (h)alt: ", &in); + n = blob_size(&in); + z = blob_str(&in); + if( n<1 ) continue; + if( sqlite3_strnicmp("good", z, n)==0 ){ + rc = 0; + break; + } + if( sqlite3_strnicmp("bad", z, n)==0 ){ + rc = 1; + break; + } + if( sqlite3_strnicmp("skip", z, n)==0 ){ + rc = 125; + break; + } + if( sqlite3_strnicmp("auto", z, n)==0 ){ + isInteractive = 0; + break; + } + if( sqlite3_strnicmp("halt", z, n)==0 ){ + return; + } + blob_reset(&in); + } + } + if( rc==0 ){ + blob_append(&cmd, " bisect good", -1); + }else if( rc==125 ){ + blob_append(&cmd, " bisect skip", -1); + }else{ + blob_append(&cmd, " bisect bad", -1); + } + fossil_print("%s\n", blob_str(&cmd)); + fossil_system(blob_str(&cmd)); + blob_reset(&cmd); + } +} + +/* +** COMMAND: bisect +** +** Usage: %fossil bisect SUBCOMMAND ... +** +** Run various subcommands useful for searching back through the change +** history for a particular checkin that causes or fixes a problem. +** +** > fossil bisect bad ?VERSION? +** +** Identify version VERSION as non-working. If VERSION is omitted, +** the current checkout is marked as non-working. +** +** > fossil bisect good ?VERSION? +** +** Identify version VERSION as working. If VERSION is omitted, +** the current checkout is marked as working. +** +** > fossil bisect log +** > fossil bisect chart +** +** Show a log of "good", "bad", and "skip" versions. "bisect log" +** shows the events in the order that they were tested. +** "bisect chart" shows them in order of check-in. +** +** > fossil bisect next +** +** Update to the next version that is halfway between the working and +** non-working versions. +** +** > fossil bisect options ?NAME? ?VALUE? +** +** List all bisect options, or the value of a single option, or set the +** value of a bisect option. +** +** > fossil bisect reset +** +** Reinitialize a bisect session. This cancels prior bisect history +** and allows a bisect session to start over from the beginning. +** +** > fossil bisect run [OPTIONS] COMMAND +** +** Invoke COMMAND repeatedly to run the bisect. The exit code for +** COMMAND should be 0 for "good", 125 for "skip", and any other value +** for "bad". Options: +** +** -i|--interactive Prompt the user for the good/bad/skip decision +** after each step, rather than using the exit +** code from COMMAND +** +** > fossil bisect skip ?VERSION? +** +** Cause VERSION (or the current checkout if VERSION is omitted) to +** be ignored for the purpose of the current bisect. This might +** be done, for example, because VERSION does not compile correctly +** or is otherwise unsuitable to participate in this bisect. +** +** > fossil bisect vlist|ls|status ?-a|--all? +** +** List the versions in between the inner-most "bad" and "good". +** +** > fossil bisect ui +** +** Like "fossil ui" except start on a timeline that shows only the +** check-ins that are part of the current bisect. +** +** > fossil bisect undo +** +** Undo the most recent "good", "bad", or "skip" command. +*/ +void bisect_cmd(void){ + int n; + const char *zCmd; + int foundCmd = 0; + db_must_be_within_tree(); + if( g.argc<3 ){ + goto usage; + } + zCmd = g.argv[2]; + n = strlen(zCmd); + if( n==0 ) zCmd = "-"; + if( strncmp(zCmd, "bad", n)==0 ){ + int ridBad; + foundCmd = 1; + if( g.argc==3 ){ + ridBad = db_lget_int("checkout",0); + }else{ + ridBad = name_to_typed_rid(g.argv[3], "ci"); + } + if( ridBad>0 ){ + bisect_append_log(-ridBad); + if( bisect_option("auto-next") && db_lget_int("bisect-good",0)>0 ){ + zCmd = "next"; + n = 4; + } + } + }else if( strncmp(zCmd, "good", n)==0 ){ + int ridGood; + foundCmd = 1; + if( g.argc==3 ){ + ridGood = db_lget_int("checkout",0); + }else{ + ridGood = name_to_typed_rid(g.argv[3], "ci"); + } + if( ridGood>0 ){ + bisect_append_log(ridGood); + if( bisect_option("auto-next") && db_lget_int("bisect-bad",0)>0 ){ + zCmd = "next"; + n = 4; + } + } + }else if( strncmp(zCmd, "skip", n)==0 ){ + int ridSkip; + foundCmd = 1; + if( g.argc==3 ){ + ridSkip = db_lget_int("checkout",0); + }else{ + ridSkip = name_to_typed_rid(g.argv[3], "ci"); + } + if( ridSkip>0 ){ + bisect_append_skip(ridSkip); + if( bisect_option("auto-next") + && db_lget_int("bisect-bad",0)>0 + && db_lget_int("bisect-good",0)>0 + ){ + zCmd = "next"; + n = 4; + } + } + }else if( strncmp(zCmd, "undo", n)==0 ){ + char *zLog; + Blob log, id; + int ridBad = 0; + int ridGood = 0; + int cnt = 0, i; + foundCmd = 1; + db_begin_transaction(); + zLog = db_lget("bisect-log",""); + blob_init(&log, zLog, -1); + while( blob_token(&log, &id) ){ cnt++; } + if( cnt==0 ){ + fossil_fatal("no previous bisect steps to undo"); + } + blob_rewind(&log); + for(i=0; i<cnt-1; i++){ + int rid; + blob_token(&log, &id); + rid = atoi(blob_str(&id)); + if( rid<0 ) ridBad = -rid; + else ridGood = rid; + } + db_multi_exec( + "UPDATE vvar SET value=substr(value,1,%d) WHERE name='bisect-log'", + log.iCursor-1 + ); + db_lset_int("bisect-bad", ridBad); + db_lset_int("bisect-good", ridGood); + db_end_transaction(0); + if( ridBad && ridGood ){ + zCmd = "next"; + n = 4; + } + } + /* No else here so that the above commands can morph themselves into + ** a "next" command */ + if( strncmp(zCmd, "next", n)==0 ){ + PathNode *pMid; + char *zDisplay = db_lget("bisect-display","chart"); + int m = (int)strlen(zDisplay); + bisect_path(); + pMid = path_midpoint(); + if( pMid==0 ){ + fossil_print("bisect complete\n"); + db_lset_int("bisect-complete",1); + }else{ + int nSpan = path_length_not_hidden(); + int nStep = path_search_depth(); + g.argv[1] = "update"; + g.argv[2] = db_text(0, "SELECT uuid FROM blob WHERE rid=%d", pMid->rid); + g.argc = 3; + g.fNoSync = 1; + update_cmd(); + fossil_print("span: %d steps-remaining: %d\n", nSpan, nStep); + } + + if( strncmp(zDisplay,"chart",m)==0 ){ + bisect_chart(1); + }else if( strncmp(zDisplay, "log", m)==0 ){ + bisect_chart(0); + }else if( strncmp(zDisplay, "status", m)==0 ){ + bisect_list(1); + } + }else if( strncmp(zCmd, "log", n)==0 ){ + bisect_chart(0); + }else if( strncmp(zCmd, "chart", n)==0 ){ + bisect_chart(1); + }else if( strncmp(zCmd, "run", n)==0 ){ + bisect_run(); + }else if( strncmp(zCmd, "options", n)==0 ){ + if( g.argc==3 ){ + unsigned int i; + for(i=0; i<count(aBisectOption); i++){ + char *z = mprintf("bisect-%s", aBisectOption[i].zName); + fossil_print(" %-15s %-6s ", aBisectOption[i].zName, + db_lget(z, (char*)aBisectOption[i].zDefault)); + fossil_free(z); + comment_print(aBisectOption[i].zDesc, 0, 27, -1, get_comment_format()); + } + }else if( g.argc==4 || g.argc==5 ){ + unsigned int i; + n = strlen(g.argv[3]); + for(i=0; i<count(aBisectOption); i++){ + if( strncmp(g.argv[3], aBisectOption[i].zName, n)==0 ){ + char *z = mprintf("bisect-%s", aBisectOption[i].zName); + if( g.argc==5 ){ + db_lset(z, g.argv[4]); + } + fossil_print("%s\n", db_lget(z, (char*)aBisectOption[i].zDefault)); + fossil_free(z); + break; + } + } + if( i>=count(aBisectOption) ){ + fossil_fatal("no such bisect option: %s", g.argv[3]); + } + }else{ + usage("options ?NAME? ?VALUE?"); + } + }else if( strncmp(zCmd, "reset", n)==0 ){ + bisect_reset(); + }else if( strcmp(zCmd, "ui")==0 ){ + char *newArgv[8]; + newArgv[0] = g.argv[0]; + newArgv[1] = "ui"; + newArgv[2] = "--page"; + newArgv[3] = "timeline?bisect"; + newArgv[4] = 0; + g.argv = newArgv; + g.argc = 4; + cmd_webserver(); + }else if( strncmp(zCmd, "vlist", n)==0 + || strncmp(zCmd, "ls", n)==0 + || strncmp(zCmd, "status", n)==0 + ){ + int fAll = find_option("all", "a", 0)!=0; + bisect_list(!fAll); + }else if( !foundCmd ){ +usage: + usage("bad|good|log|chart|next|options|reset|run|skip|status|ui|undo"); + } +} Index: src/blob.c ================================================================== --- src/blob.c +++ src/blob.c @@ -2,11 +2,11 @@ ** Copyright (c) 2006 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: @@ -17,12 +17,21 @@ ** ** A Blob is a variable-length containers for arbitrary string ** or binary data. */ #include "config.h" -#include <zlib.h> +#if defined(FOSSIL_ENABLE_MINIZ) +# define MINIZ_HEADER_FILE_ONLY +# include "miniz.c" +#else +# include <zlib.h> +#endif #include "blob.h" +#if defined(_WIN32) +#include <fcntl.h> +#include <io.h> +#endif #if INTERFACE /* ** A Blob can hold a string or a binary object of arbitrary size. The ** size changes as necessary. @@ -29,14 +38,20 @@ */ struct Blob { unsigned int nUsed; /* Number of bytes used in aData[] */ unsigned int nAlloc; /* Number of bytes allocated for aData[] */ unsigned int iCursor; /* Next character of input to parse */ + unsigned int blobFlags; /* One or more BLOBFLAG_* bits */ char *aData; /* Where the information is stored */ void (*xRealloc)(Blob*, unsigned int); /* Function to reallocate the buffer */ }; +/* +** Allowed values for Blob.blobFlags +*/ +#define BLOBFLAG_NotSQL 0x0001 /* Non-SQL text */ + /* ** The current size of a Blob */ #define blob_size(X) ((X)->nUsed) @@ -48,11 +63,10 @@ /* ** Seek whence parameter values */ #define BLOB_SEEK_SET 1 #define BLOB_SEEK_CUR 2 -#define BLOB_SEEK_END 3 #endif /* INTERFACE */ /* ** Make sure a blob is initialized @@ -60,56 +74,92 @@ #define blob_is_init(x) \ assert((x)->xRealloc==blobReallocMalloc || (x)->xRealloc==blobReallocStatic) /* ** Make sure a blob does not contain malloced memory. +** +** This might fail if we are unlucky and x is uninitialized. For that +** reason it should only be used locally for debugging. Leave it turned +** off for production. */ #if 0 /* Enable for debugging only */ -#define blob_is_reset(x) \ - assert((x)->xRealloc!=blobReallocMalloc || (x)->nAlloc==0) +#define assert_blob_is_reset(x) assert(blob_is_reset(x)) #else -#define blob_is_reset(x) +#define assert_blob_is_reset(x) #endif + + /* ** We find that the built-in isspace() function does not work for ** some international character sets. So here is a substitute. */ -static int blob_isspace(char c){ +int fossil_isspace(char c){ return c==' ' || (c<='\r' && c>='\t'); } + +/* +** Other replacements for ctype.h functions. +*/ +int fossil_islower(char c){ return c>='a' && c<='z'; } +int fossil_isupper(char c){ return c>='A' && c<='Z'; } +int fossil_isdigit(char c){ return c>='0' && c<='9'; } +int fossil_tolower(char c){ + return fossil_isupper(c) ? c - 'A' + 'a' : c; +} +int fossil_toupper(char c){ + return fossil_islower(c) ? c - 'a' + 'A' : c; +} +int fossil_isalpha(char c){ + return (c>='a' && c<='z') || (c>='A' && c<='Z'); +} +int fossil_isalnum(char c){ + return (c>='a' && c<='z') || (c>='A' && c<='Z') || (c>='0' && c<='9'); +} + +/* Return true if and only if the entire string consists of only +** alphanumeric characters. +*/ +int fossil_no_strange_characters(const char *z){ + while( z && (fossil_isalnum(z[0]) || z[0]=='_' || z[0]=='-') ) z++; + return z[0]==0; +} + /* ** COMMAND: test-isspace +** +** Verify that the fossil_isspace() routine is working correctly by +** testing it on all possible inputs. */ void isspace_cmd(void){ int i; for(i=0; i<=255; i++){ if( i==' ' || i=='\n' || i=='\t' || i=='\v' || i=='\f' || i=='\r' ){ - assert( blob_isspace((char)i) ); + assert( fossil_isspace((char)i) ); }else{ - assert( !blob_isspace((char)i) ); + assert( !fossil_isspace((char)i) ); } } - printf("All 256 characters OK\n"); + fossil_print("All 256 characters OK\n"); } /* ** This routine is called if a blob operation fails because we ** have run out of memory. */ static void blob_panic(void){ static const char zErrMsg[] = "out of memory\n"; - write(2, zErrMsg, sizeof(zErrMsg)-1); - exit(1); + fputs(zErrMsg, stderr); + fossil_exit(1); } /* ** A reallocation function that assumes that aData came from malloc(). ** This function attempts to resize the buffer of the blob to hold -** newSize bytes. +** newSize bytes. ** ** No attempt is made to recover from an out-of-memory error. ** If an OOM error occurs, an error message is printed on stderr ** and the program exits. */ @@ -118,13 +168,13 @@ free(pBlob->aData); pBlob->aData = 0; pBlob->nAlloc = 0; pBlob->nUsed = 0; pBlob->iCursor = 0; + pBlob->blobFlags = 0; }else if( newSize>pBlob->nAlloc || newSize<pBlob->nAlloc-4000 ){ - char *pNew = realloc(pBlob->aData, newSize); - if( pNew==0 ) blob_panic(); + char *pNew = fossil_realloc(pBlob->aData, newSize); pBlob->aData = pNew; pBlob->nAlloc = newSize; if( pBlob->nUsed>pBlob->nAlloc ){ pBlob->nUsed = pBlob->nAlloc; } @@ -133,11 +183,11 @@ /* ** An initializer for Blobs */ #if INTERFACE -#define BLOB_INITIALIZER {0,0,0,0,blobReallocMalloc} +#define BLOB_INITIALIZER {0,0,0,0,0,blobReallocMalloc} #endif const Blob empty_blob = BLOB_INITIALIZER; /* ** A reallocation function for when the initial string is in unmanaged @@ -145,12 +195,11 @@ */ static void blobReallocStatic(Blob *pBlob, unsigned int newSize){ if( newSize==0 ){ *pBlob = empty_blob; }else{ - char *pNew = malloc( newSize ); - if( pNew==0 ) blob_panic(); + char *pNew = fossil_malloc( newSize ); if( pBlob->nUsed>newSize ) pBlob->nUsed = newSize; memcpy(pNew, pBlob->aData, pBlob->nUsed); pBlob->aData = pNew; pBlob->xRealloc = blobReallocMalloc; pBlob->nAlloc = newSize; @@ -163,23 +212,37 @@ void blob_reset(Blob *pBlob){ blob_is_init(pBlob); pBlob->xRealloc(pBlob, 0); } + +/* +** Return true if the blob has been zeroed - in other words if it contains +** no malloced memory. This only works reliably if the blob has been +** initialized - it can return a false negative on an uninitialized blob. +*/ +int blob_is_reset(Blob *pBlob){ + if( pBlob==0 ) return 1; + if( pBlob->nUsed ) return 0; + if( pBlob->xRealloc==blobReallocMalloc && pBlob->nAlloc ) return 0; + return 1; +} + /* ** Initialize a blob to a string or byte-array constant of a specified length. ** Any prior data in the blob is discarded. */ void blob_init(Blob *pBlob, const char *zData, int size){ - blob_is_reset(pBlob); + assert_blob_is_reset(pBlob); if( zData==0 ){ *pBlob = empty_blob; }else{ if( size<=0 ) size = strlen(zData); pBlob->nUsed = pBlob->nAlloc = size; pBlob->aData = (char*)zData; pBlob->iCursor = 0; + pBlob->blobFlags = 0; pBlob->xRealloc = blobReallocStatic; } } /* @@ -187,40 +250,97 @@ ** Any prior data in the blob is discarded. */ void blob_set(Blob *pBlob, const char *zStr){ blob_init(pBlob, zStr, -1); } + +/* +** Initialize a blob to a nul-terminated string obtained from fossil_malloc(). +** The blob will take responsibility for freeing the string. +*/ +void blob_set_dynamic(Blob *pBlob, char *zStr){ + blob_init(pBlob, zStr, -1); + pBlob->xRealloc = blobReallocMalloc; +} /* ** Initialize a blob to an empty string. */ void blob_zero(Blob *pBlob){ static const char zEmpty[] = ""; - blob_is_reset(pBlob); + assert_blob_is_reset(pBlob); pBlob->nUsed = 0; pBlob->nAlloc = 1; pBlob->aData = (char*)zEmpty; pBlob->iCursor = 0; + pBlob->blobFlags = 0; pBlob->xRealloc = blobReallocStatic; } /* ** Append text or data to the end of a blob. +** +** The blob_append_full() routine is a complete implementation. +** The blob_append() routine only works for cases where nData>0 and +** no resizing is required, and falls back to blob_append_full() if +** either condition is not met, but runs faster in the common case +** where all conditions are met. The use of blob_append() is +** recommended, unless it is known in advance that nData<0. */ -void blob_append(Blob *pBlob, const char *aData, int nData){ +void blob_append_full(Blob *pBlob, const char *aData, int nData){ + sqlite3_int64 nNew; + assert( aData!=0 || nData==0 ); blob_is_init(pBlob); if( nData<0 ) nData = strlen(aData); if( nData==0 ) return; - if( pBlob->nUsed + nData >= pBlob->nAlloc ){ - pBlob->xRealloc(pBlob, pBlob->nUsed + nData + pBlob->nAlloc + 100); + nNew = pBlob->nUsed; + nNew += nData; + if( nNew >= pBlob->nAlloc ){ + nNew += pBlob->nAlloc; + nNew += 100; + if( nNew>=0x7fff0000 ){ + blob_panic(); + } + pBlob->xRealloc(pBlob, (int)nNew); if( pBlob->nUsed + nData >= pBlob->nAlloc ){ blob_panic(); } } memcpy(&pBlob->aData[pBlob->nUsed], aData, nData); pBlob->nUsed += nData; pBlob->aData[pBlob->nUsed] = 0; /* Blobs are always nul-terminated */ +} +void blob_append(Blob *pBlob, const char *aData, int nData){ + sqlite3_int64 nUsed; + assert( aData!=0 || nData==0 ); + /* blob_is_init(pBlob); // omitted for speed */ + if( nData<=0 || pBlob->nUsed + nData >= pBlob->nAlloc ){ + blob_append_full(pBlob, aData, nData); + return; + } + nUsed = pBlob->nUsed; + pBlob->nUsed += nData; + pBlob->aData[pBlob->nUsed] = 0; + memcpy(&pBlob->aData[nUsed], aData, nData); +} + +/* +** Append a string literal to a blob. +*/ +#if INTERFACE +#define blob_append_string(BLOB,STR) blob_append(BLOB,STR,sizeof(STR)-1) +#endif + +/* +** Append a single character to the blob +*/ +void blob_append_char(Blob *pBlob, char c){ + if( pBlob->nUsed+1 >= pBlob->nAlloc ){ + blob_append_full(pBlob, &c, 1); + }else{ + pBlob->aData[pBlob->nUsed++] = c; + } } /* ** Copy a blob */ @@ -234,18 +354,45 @@ ** Return a pointer to a null-terminated string for a blob. */ char *blob_str(Blob *p){ blob_is_init(p); if( p->nUsed==0 ){ - blob_append(p, "", 1); + blob_append_char(p, 0); /* NOTE: Changes nUsed. */ p->nUsed = 0; } - if( p->aData[p->nUsed]!=0 ){ + if( p->nUsed<p->nAlloc ){ + p->aData[p->nUsed] = 0; + }else{ blob_materialize(p); } return p->aData; } + +/* +** Compute the string length of a Blob. If there are embedded +** nul characters, truncate the to blob at the first nul. +*/ +int blob_strlen(Blob *p){ + char *z = blob_str(p); + if( z==0 ) return 0; + p->nUsed = (int)strlen(p->aData); + return p->nUsed; +} + +/* +** Return a pointer to a null-terminated string for a blob that has +** been created using blob_append_sql() and not blob_appendf(). If +** text was ever added using blob_appendf() then throw an error. +*/ +char *blob_sql_text(Blob *p){ + blob_is_init(p); + if( (p->blobFlags & BLOBFLAG_NotSQL) ){ + fossil_panic("use of blob_appendf() to construct SQL text"); + } + return blob_str(p); +} + /* ** Return a pointer to a null-terminated string for a blob. ** ** WARNING: If the blob is ephemeral, it might cause a '\000' @@ -264,11 +411,12 @@ p->aData[p->nUsed] = 0; return p->aData; } /* -** Compare two blobs. +** Compare two blobs. Return negative, zero, or positive if the first +** blob is less then, equal to, or greater than the second. */ int blob_compare(Blob *pA, Blob *pB){ int szA, szB, sz, rc; blob_is_init(pA); blob_is_init(pB); @@ -277,10 +425,36 @@ sz = szA<szB ? szA : szB; rc = memcmp(blob_buffer(pA), blob_buffer(pB), sz); if( rc==0 ){ rc = szA - szB; } + 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 blob_constant_time_cmp(Blob *pA, Blob *pB){ + int szA, szB, i; + unsigned char *buf1, *buf2; + unsigned char rc = 0; + + blob_is_init(pA); + blob_is_init(pB); + szA = blob_size(pA); + szB = blob_size(pB); + if( szA!=szB || szA==0 ) return 1; + + buf1 = (unsigned char*)blob_buffer(pA); + buf2 = (unsigned char*)blob_buffer(pB); + + for( i=0; i<szA; i++ ){ + rc = rc | (buf1[i] ^ buf2[i]); + } + return rc; } /* ** Compare a blob to a string. Return TRUE if they are equal. @@ -305,22 +479,46 @@ ((B)->nUsed==sizeof(S)-1 && memcmp((B)->aData,S,sizeof(S)-1)==0) #endif /* -** Attempt to resize a blob so that its internal buffer is +** Attempt to resize a blob so that its internal buffer is ** nByte in size. The blob is truncated if necessary. */ void blob_resize(Blob *pBlob, unsigned int newSize){ pBlob->xRealloc(pBlob, newSize+1); pBlob->nUsed = newSize; pBlob->aData[newSize] = 0; } + +/* +** Ensures that the given blob has at least the given amount of memory +** allocated to it. Does not modify pBlob->nUsed nor will it reduce +** the currently-allocated amount of memory. +** +** For semantic compatibility with blob_append_full(), if newSize is +** >=0x7fff000 (~2GB) then this function will trigger blob_panic(). If +** it didn't, it would be possible to bypass that hard-coded limit via +** this function. +** +** We've had at least one report: +** https://fossil-scm.org/forum/forumpost/b7bbd28db4 +** which implies that this is unconditionally failing on mingw 32-bit +** builds. +*/ +void blob_reserve(Blob *pBlob, unsigned int newSize){ + if(newSize>=0x7fff0000 ){ + blob_panic(); + }else if(newSize>pBlob->nUsed){ + pBlob->xRealloc(pBlob, newSize); + pBlob->aData[newSize] = 0; + } +} /* ** Make sure a blob is nul-terminated and is not a pointer to unmanaged -** space. Return a pointer to the +** space. Return a pointer to the data. */ char *blob_materialize(Blob *pBlob){ blob_resize(pBlob, pBlob->nUsed); return pBlob->aData; } @@ -341,11 +539,11 @@ ** ** After this call completes, pTo will be an ephemeral blob. */ int blob_extract(Blob *pFrom, int N, Blob *pTo){ blob_is_init(pFrom); - blob_is_reset(pTo); + assert_blob_is_reset(pTo); if( pFrom->iCursor + N > pFrom->nUsed ){ N = pFrom->nUsed - pFrom->iCursor; if( N<=0 ){ blob_zero(pTo); return 0; @@ -364,24 +562,26 @@ ** Rewind the cursor on a blob back to the beginning. */ void blob_rewind(Blob *p){ p->iCursor = 0; } + +/* +** Truncate a blob back to zero length +*/ +void blob_truncate(Blob *p, int sz){ + if( sz>=0 && sz<p->nUsed ) p->nUsed = sz; +} /* ** Seek the cursor in a blob to the indicated offset. */ int blob_seek(Blob *p, int offset, int whence){ if( whence==BLOB_SEEK_SET ){ p->iCursor = offset; }else if( whence==BLOB_SEEK_CUR ){ p->iCursor += offset; - }else if( whence==BLOB_SEEK_END ){ - p->iCursor = p->nUsed + offset - 1; - } - if( p->iCursor<0 ){ - p->iCursor = 0; } if( p->iCursor>p->nUsed ){ p->iCursor = p->nUsed; } return p->iCursor; @@ -393,11 +593,11 @@ int blob_tell(Blob *p){ return p->iCursor; } /* -** Extract a single line of text from pFrom beginning at the current +** Extract a single line of text from pFrom beginning at the current ** cursor location and use that line of text to initialize pTo. ** pTo will include the terminating \n. Return the number of bytes ** in the line including the \n at the end. 0 is returned at ** end-of-file. ** @@ -429,11 +629,11 @@ ** not insert a new zero terminator. */ int blob_trim(Blob *p){ char *z = p->aData; int n = p->nUsed; - while( n>0 && blob_isspace(z[n-1]) ){ n--; } + while( n>0 && fossil_isspace(z[n-1]) ){ n--; } p->nUsed = n; return n; } /* @@ -452,15 +652,53 @@ */ int blob_token(Blob *pFrom, Blob *pTo){ char *aData = pFrom->aData; int n = pFrom->nUsed; int i = pFrom->iCursor; - while( i<n && blob_isspace(aData[i]) ){ i++; } + while( i<n && fossil_isspace(aData[i]) ){ i++; } + pFrom->iCursor = i; + while( i<n && !fossil_isspace(aData[i]) ){ i++; } + blob_extract(pFrom, i-pFrom->iCursor, pTo); + while( i<n && fossil_isspace(aData[i]) ){ i++; } + pFrom->iCursor = i; + return pTo->nUsed; +} + +/* +** Extract a single SQL token from pFrom and use it to initialize pTo. +** Return the number of bytes in the token. If no token is found, +** return 0. +** +** An SQL token consists of one or more non-space characters. If the +** first character is ' then the token is terminated by a matching ' +** (ignoring double '') or by the end of the string +** +** The cursor of pFrom is left pointing at the first character past +** the end of the token. +** +** pTo will be an ephermeral blob. If pFrom changes, it might alter +** pTo as well. +*/ +int blob_sqltoken(Blob *pFrom, Blob *pTo){ + char *aData = pFrom->aData; + int n = pFrom->nUsed; + int i = pFrom->iCursor; + while( i<n && fossil_isspace(aData[i]) ){ i++; } pFrom->iCursor = i; - while( i<n && !blob_isspace(aData[i]) ){ i++; } + if( aData[i]=='\'' ){ + i++; + while( i<n ){ + if( aData[i]=='\'' ){ + if( aData[++i]!='\'' ) break; + } + i++; + } + }else{ + while( i<n && !fossil_isspace(aData[i]) ){ i++; } + } blob_extract(pFrom, i-pFrom->iCursor, pTo); - while( i<n && blob_isspace(aData[i]) ){ i++; } + while( i<n && fossil_isspace(aData[i]) ){ i++; } pFrom->iCursor = i; return pTo->nUsed; } /* @@ -504,18 +742,36 @@ } pFrom->iCursor = i; } /* -** Return true if the blob contains a valid UUID_SIZE-digit base16 identifier. -*/ -int blob_is_uuid(Blob *pBlob){ - return blob_size(pBlob)==UUID_SIZE - && validate16(blob_buffer(pBlob), UUID_SIZE); -} -int blob_is_uuid_n(Blob *pBlob, int n){ - return blob_size(pBlob)==n && validate16(blob_buffer(pBlob), n); +** Ensure that the text in pBlob ends with '\n' +*/ +void blob_add_final_newline(Blob *pBlob){ + if( pBlob->nUsed<=0 ) return; + if( pBlob->aData[pBlob->nUsed-1]!='\n' ){ + blob_append_char(pBlob, '\n'); + } +} + +/* +** Return true if the blob contains a valid base16 identifier artifact hash. +** +** The value returned is actually one of HNAME_SHA1 OR HNAME_K256 if the +** hash is valid. Both of these are non-zero and therefore "true". +** If the hash is not valid, then HNAME_ERROR is returned, which is zero or +** false. +*/ +int blob_is_hname(Blob *pBlob){ + return hname_validate(blob_buffer(pBlob), blob_size(pBlob)); +} + +/* +** Return true if the blob contains a valid filename +*/ +int blob_is_filename(Blob *pBlob){ + return file_is_simple_pathname(blob_str(pBlob), 1); } /* ** Return true if the blob contains a valid 32-bit integer. Store ** the integer value in *pValue. @@ -523,11 +779,32 @@ int blob_is_int(Blob *pBlob, int *pValue){ const char *z = blob_buffer(pBlob); int i, n, c, v; n = blob_size(pBlob); v = 0; - for(i=0; i<n && (c = z[i])!=0 && isdigit(c); i++){ + for(i=0; i<n && (c = z[i])!=0 && c>='0' && c<='9'; i++){ + v = v*10 + c - '0'; + } + if( i==n ){ + *pValue = v; + return 1; + }else{ + return 0; + } +} + +/* +** Return true if the blob contains a valid 64-bit integer. Store +** the integer value in *pValue. +*/ +int blob_is_int64(Blob *pBlob, sqlite3_int64 *pValue){ + const char *z = blob_buffer(pBlob); + int i, n, c; + sqlite3_int64 v; + n = blob_size(pBlob); + v = 0; + for(i=0; i<n && (c = z[i])!=0 && c>='0' && c<='9'; i++){ v = v*10 + c - '0'; } if( i==n ){ *pValue = v; return 1; @@ -559,23 +836,37 @@ return i; } /* ** Do printf-style string rendering and append the results to a blob. +** +** The blob_appendf() version sets the BLOBFLAG_NotSQL bit in Blob.blobFlags +** whereas blob_append_sql() does not. */ void blob_appendf(Blob *pBlob, const char *zFormat, ...){ - va_list ap; - va_start(ap, zFormat); - vxprintf(pBlob, zFormat, ap); - va_end(ap); + if( pBlob ){ + va_list ap; + va_start(ap, zFormat); + vxprintf(pBlob, zFormat, ap); + va_end(ap); + pBlob->blobFlags |= BLOBFLAG_NotSQL; + } +} +void blob_append_sql(Blob *pBlob, const char *zFormat, ...){ + if( pBlob ){ + va_list ap; + va_start(ap, zFormat); + vxprintf(pBlob, zFormat, ap); + va_end(ap); + } } void blob_vappendf(Blob *pBlob, const char *zFormat, va_list ap){ - vxprintf(pBlob, zFormat, ap); + if( pBlob ) vxprintf(pBlob, zFormat, ap); } /* -** Initalize a blob to the data on an input channel. Return +** Initialize a blob to the data on an input channel. Return ** the number of bytes read into the blob. Any prior content ** of the blob is discarded, not freed. */ int blob_read_from_channel(Blob *pBlob, FILE *in, int nToRead){ size_t n; @@ -597,113 +888,146 @@ } /* ** Initialize a blob to be the content of a file. If the filename ** is blank or "-" then read from standard input. +** +** If zFilename is a symbolic link, behavior depends on the eFType +** parameter: +** +** * If eFType is ExtFILE or allow-symlinks is OFF, then the +** pBlob is initialized to the *content* of the object to which +** the zFilename symlink points. +** +** * If eFType is RepoFILE and allow-symlinks is ON, then the +** pBlob is initialized to the *name* of the object to which +** the zFilename symlink points. ** ** Any prior content of the blob is discarded, not freed. ** -** Return the number of bytes read. Return -1 for an error. +** Return the number of bytes read. Calls fossil_fatal() on error (i.e. +** it exit()s and does not return). */ -int blob_read_from_file(Blob *pBlob, const char *zFilename){ - int size, got; +sqlite3_int64 blob_read_from_file( + Blob *pBlob, /* The blob to be initialized */ + const char *zFilename, /* Extract content from this file */ + int eFType /* ExtFILE or RepoFILE - see above */ +){ + sqlite3_int64 size, got; FILE *in; if( zFilename==0 || zFilename[0]==0 || (zFilename[0]=='-' && zFilename[1]==0) ){ return blob_read_from_channel(pBlob, stdin, -1); } - size = file_size(zFilename); + if( file_islink(zFilename) ){ + return blob_read_link(pBlob, zFilename); + } + size = file_size(zFilename, eFType); blob_zero(pBlob); if( size<0 ){ - fossil_panic("no such file: %s", zFilename); + fossil_fatal("no such file: %s", zFilename); } if( size==0 ){ return 0; } blob_resize(pBlob, size); - in = fopen(zFilename, "rb"); + in = fossil_fopen(zFilename, "rb"); if( in==0 ){ - fossil_panic("cannot open %s for reading", zFilename); + fossil_fatal("cannot open %s for reading", zFilename); } got = fread(blob_buffer(pBlob), 1, size, in); fclose(in); if( got<size ){ blob_resize(pBlob, got); } return got; } + +/* +** Reads symlink destination path and puts int into blob. +** Any prior content of the blob is discarded, not freed. +** +** Returns length of destination path. +** +** On windows, zeros blob and returns 0. +*/ +int blob_read_link(Blob *pBlob, const char *zFilename){ +#if !defined(_WIN32) + char zBuf[1024]; + ssize_t len = readlink(zFilename, zBuf, 1023); + if( len < 0 ){ + fossil_fatal("cannot read symbolic link %s", zFilename); + } + zBuf[len] = 0; /* null-terminate */ + blob_zero(pBlob); + blob_appendf(pBlob, "%s", zBuf); + return len; +#else + blob_zero(pBlob); + return 0; +#endif +} /* ** Write the content of a blob into a file. ** ** If the filename is blank or "-" then write to standard output. +** +** This routine always assumes ExtFILE. If zFilename is a symbolic link +** then the content is written into the object that symbolic link points +** to, not into the symbolic link itself. This is true regardless of +** the allow-symlinks setting. ** ** Return the number of bytes written. */ int blob_write_to_file(Blob *pBlob, const char *zFilename){ FILE *out; - int needToClose; - int wrote; + int nWrote; if( zFilename[0]==0 || (zFilename[0]=='-' && zFilename[1]==0) ){ - out = stdout; - needToClose = 0; - }else{ - int i, nName; - char *zName, zBuf[1000]; - - nName = strlen(zFilename); - if( nName>=sizeof(zBuf) ){ - zName = mprintf("%s", zFilename); - }else{ - zName = zBuf; - strcpy(zName, zFilename); - } - nName = file_simplify_name(zName, nName); - for(i=1; i<nName; i++){ - if( zName[i]=='/' ){ - zName[i] = 0; -#ifdef __MINGW32__ - /* - ** 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 && zName[1]==':') ){ -#endif - if( file_mkdir(zName, 1) ){ - fossil_fatal_recursive("unable to create directory %s", zName); - return 0; - } -#ifdef __MINGW32__ - } -#endif - zName[i] = '/'; - } - } - out = fopen(zName, "wb"); - if( out==0 ){ - fossil_fatal_recursive("unable to open file \"%s\" for writing", zName); - return 0; - } - needToClose = 1; - if( zName!=zBuf ) free(zName); - } - blob_is_init(pBlob); - wrote = fwrite(blob_buffer(pBlob), 1, blob_size(pBlob), out); - if( needToClose ) fclose(out); - if( wrote!=blob_size(pBlob) ){ - fossil_fatal_recursive("short write: %d of %d bytes to %s", wrote, - blob_size(pBlob), zFilename); - } - return wrote; + blob_is_init(pBlob); +#if defined(_WIN32) + nWrote = fossil_utf8_to_console(blob_buffer(pBlob), blob_size(pBlob), 0); + if( nWrote>=0 ) return nWrote; + fflush(stdout); + _setmode(_fileno(stdout), _O_BINARY); +#endif + nWrote = fwrite(blob_buffer(pBlob), 1, blob_size(pBlob), stdout); +#if defined(_WIN32) + fflush(stdout); + _setmode(_fileno(stdout), _O_TEXT); +#endif + }else{ + file_mkfolder(zFilename, ExtFILE, 1, 0); + out = fossil_fopen(zFilename, "wb"); + if( out==0 ){ +#if defined(_WIN32) + const char *zReserved = file_is_win_reserved(zFilename); + if( zReserved ){ + fossil_fatal("cannot open \"%s\" because \"%s\" is " + "a reserved name on Windows", zFilename, zReserved); + } +#endif + fossil_fatal_recursive("unable to open file \"%s\" for writing", + zFilename); + return 0; + } + blob_is_init(pBlob); + nWrote = fwrite(blob_buffer(pBlob), 1, blob_size(pBlob), out); + fclose(out); + if( nWrote!=blob_size(pBlob) ){ + fossil_fatal_recursive("short write: %d of %d bytes to %s", nWrote, + blob_size(pBlob), zFilename); + } + } + return nWrote; } /* ** Compress a blob pIn. Store the result in pOut. It is ok for pIn and -** pOut to be the same blob. -** +** pOut to be the same blob. +** ** pOut must either be the same as pIn or else uninitialized. */ void blob_compress(Blob *pIn, Blob *pOut){ unsigned int nIn = blob_size(pIn); unsigned int nOut = 13 + nIn + (nIn+999)/1000; @@ -719,30 +1043,36 @@ outBuf[3] = nIn & 0xff; nOut2 = (long int)nOut; compress(&outBuf[4], &nOut2, (unsigned char*)blob_buffer(pIn), blob_size(pIn)); if( pOut==pIn ) blob_reset(pOut); - blob_is_reset(pOut); + assert_blob_is_reset(pOut); *pOut = temp; blob_resize(pOut, nOut2+4); } /* ** COMMAND: test-compress +** +** Usage: %fossil test-compress INPUTFILE OUTPUTFILE +** +** Run compression on INPUTFILE and write the result into OUTPUTFILE. +** +** This is used to test and debug the blob_compress() routine. */ void compress_cmd(void){ Blob f; if( g.argc!=4 ) usage("INPUTFILE OUTPUTFILE"); - blob_read_from_file(&f, g.argv[2]); + blob_read_from_file(&f, g.argv[2], ExtFILE); blob_compress(&f, &f); blob_write_to_file(&f, g.argv[3]); } /* -** Compress the concatenation of a blobs pIn1 and pIn2. Store the result -** in pOut. -** +** Compress the concatenation of a blobs pIn1 and pIn2. Store the result +** in pOut. +** ** pOut must be either uninitialized or must be the same as either pIn1 or ** pIn2. */ void blob_compress2(Blob *pIn1, Blob *pIn2, Blob *pOut){ unsigned int nIn = blob_size(pIn1) + blob_size(pIn2); @@ -772,22 +1102,29 @@ deflate(&stream, Z_FINISH); blob_resize(&temp, stream.total_out + 4); deflateEnd(&stream); if( pOut==pIn1 ) blob_reset(pOut); if( pOut==pIn2 ) blob_reset(pOut); - blob_is_reset(pOut); + assert_blob_is_reset(pOut); *pOut = temp; } /* ** COMMAND: test-compress-2 +** +** Usage: %fossil test-compress-2 IN1 IN2 OUT +** +** Read files IN1 and IN2, concatenate the content, compress the +** content, then write results into OUT. +** +** This is used to test and debug the blob_compress2() routine. */ void compress2_cmd(void){ Blob f1, f2; if( g.argc!=5 ) usage("INPUTFILE1 INPUTFILE2 OUTPUTFILE"); - blob_read_from_file(&f1, g.argv[2]); - blob_read_from_file(&f2, g.argv[3]); + blob_read_from_file(&f1, g.argv[2], ExtFILE); + blob_read_from_file(&f2, g.argv[3], ExtFILE); blob_compress2(&f1, &f2, &f1); blob_write_to_file(&f1, g.argv[4]); } /* @@ -809,30 +1146,36 @@ inBuf = (unsigned char*)blob_buffer(pIn); nOut = (inBuf[0]<<24) + (inBuf[1]<<16) + (inBuf[2]<<8) + inBuf[3]; blob_zero(&temp); blob_resize(&temp, nOut+1); nOut2 = (long int)nOut; - rc = uncompress((unsigned char*)blob_buffer(&temp), &nOut2, - &inBuf[4], blob_size(pIn)); + rc = uncompress((unsigned char*)blob_buffer(&temp), &nOut2, + &inBuf[4], nIn - 4); if( rc!=Z_OK ){ blob_reset(&temp); return 1; } blob_resize(&temp, nOut2); if( pOut==pIn ) blob_reset(pOut); - blob_is_reset(pOut); + assert_blob_is_reset(pOut); *pOut = temp; return 0; } /* ** COMMAND: test-uncompress +** +** Usage: %fossil test-uncompress IN OUT +** +** Read the content of file IN, uncompress that content, and write the +** result into OUT. This command is intended for testing of the +** blob_compress() function. */ void uncompress_cmd(void){ Blob f; if( g.argc!=4 ) usage("INPUTFILE OUTPUTFILE"); - blob_read_from_file(&f, g.argv[2]); + blob_read_from_file(&f, g.argv[2], ExtFILE); blob_uncompress(&f, &f); blob_write_to_file(&f, g.argv[3]); } /* @@ -843,24 +1186,23 @@ */ void test_cycle_compress(void){ int i; Blob b1, b2, b3; for(i=2; i<g.argc; i++){ - blob_read_from_file(&b1, g.argv[i]); + blob_read_from_file(&b1, g.argv[i], ExtFILE); blob_compress(&b1, &b2); blob_uncompress(&b2, &b3); if( blob_compare(&b1, &b3) ){ - fossil_panic("compress/uncompress cycle failed for %s", g.argv[i]); + fossil_fatal("compress/uncompress cycle failed for %s", g.argv[i]); } blob_reset(&b1); blob_reset(&b2); blob_reset(&b3); } - printf("ok\n"); + fossil_print("ok\n"); } -#ifdef __MINGW32__ /* ** Convert every \n character in the given blob into \r\n. */ void blob_add_cr(Blob *p){ char *z = p->aData; @@ -880,20 +1222,53 @@ if( (z[--j] = z[--i]) =='\n' ){ z[--j] = '\r'; } } } -#endif /* -** Remove every \r character from the given blob. +** Remove every \r character from the given blob, replacing each one with +** a \n character if it was not already part of a \r\n pair. */ -void blob_remove_cr(Blob *p){ +void blob_to_lf_only(Blob *p){ int i, j; - char *z; - blob_materialize(p); - z = p->aData; + char *z = blob_materialize(p); for(i=j=0; z[i]; i++){ if( z[i]!='\r' ) z[j++] = z[i]; + else if( z[i+1]!='\n' ) z[j++] = '\n'; } z[j] = 0; p->nUsed = j; +} + +/* +** Convert blob from cp1252 to UTF-8. As cp1252 is a superset +** of iso8859-1, this is useful on UNIX as well. +** +** This table contains the character translations for 0x80..0xA0. +*/ + +static const unsigned short cp1252[32] = { + 0x20ac, 0x81, 0x201A, 0x0192, 0x201E, 0x2026, 0x2020, 0x2021, + 0x02C6, 0x2030, 0x0160, 0x2039, 0x0152, 0x8D, 0x017D, 0x8F, + 0x90, 0x2018, 0x2019, 0x201C, 0x201D, 0x2022, 0x2013, 0x2014, + 0x2DC, 0x2122, 0x0161, 0x203A, 0x0153, 0x9D, 0x017E, 0x0178 +}; + +void blob_cp1252_to_utf8(Blob *p){ + unsigned char *z = (unsigned char *)p->aData; + int j = p->nUsed; + int i, n; + for(i=n=0; i<j; i++){ + if( z[i]>=0x80 ){ + if( (z[i]<0xa0) && (cp1252[z[i]&0x1f]>=0x800) ){ + n++; + } + n++; + } + } + j += n; + if( j>=p->nAlloc ){ + blob_resize(p, j); + z = (unsigned char *)p->aData; + } + p->nUsed = j; @@ -900,1 +1275,425 @@ + z[j] = 0; + while( j>i ){ + if( z[--i]>=0x80 ){ + if( z[i]<0xa0 ){ + unsigned short sym = cp1252[z[i]&0x1f]; + if( sym>=0x800 ){ + z[--j] = 0x80 | (sym&0x3f); + z[--j] = 0x80 | ((sym>>6)&0x3f); + z[--j] = 0xe0 | (sym>>12); + }else{ + z[--j] = 0x80 | (sym&0x3f); + z[--j] = 0xc0 | (sym>>6); + } + }else{ + z[--j] = 0x80 | (z[i]&0x3f); + z[--j] = 0xC0 | (z[i]>>6); + } + }else{ + z[--j] = z[i]; + } + } +} + +/* +** ASCII (for reference): +** x0 x1 x2 x3 x4 x5 x6 x7 x8 x9 xa xb xc xd xe xf +** 0x ^` ^a ^b ^c ^d ^e ^f ^g \b \t \n () \f \r ^n ^o +** 1x ^p ^q ^r ^s ^t ^u ^v ^w ^x ^y ^z ^{ ^| ^} ^~ ^ +** 2x () ! " # $ % & ' ( ) * + , - . / +** 3x 0 1 2 3 4 5 6 7 8 9 : ; < = > ? +** 4x @ A B C D E F G H I J K L M N O +** 5x P Q R S T U V W X Y Z [ \ ] ^ _ +** 6x ` a b c d e f g h i j k l m n o +** 7x p q r s t u v w x y z { | } ~ ^_ +*/ + +/* +** Meanings for bytes in a filename: +** +** 0 Ordinary character. No encoding required +** 1 Needs to be escaped +** 2 Illegal character. Do not allow in a filename +** 3 First byte of a 2-byte UTF-8 +** 4 First byte of a 3-byte UTF-8 +** 5 First byte of a 4-byte UTF-8 +*/ +static const char aSafeChar[256] = { +#ifdef _WIN32 +/* Windows +** Prohibit: all control characters, including tab, \r and \n +** Escape: (space) " # $ % & ' ( ) * ; < > ? [ ] ^ ` { | } +*/ +/* x0 x1 x2 x3 x4 x5 x6 x7 x8 x9 xa xb xc xd xe xf */ + 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, /* 0x */ + 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, /* 1x */ + 1, 0, 1, 1, 1, 1, 1, 1, 1, 1, 1, 0, 0, 0, 0, 0, /* 2x */ + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 1, 0, 1, 1, /* 3x */ + 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, /* 4x */ + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 0, 1, 1, 0, /* 5x */ + 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, /* 6x */ + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 1, 1, 0, 1, /* 7x */ +#else +/* Unix +** Prohibit: all control characters, including tab, \r and \n +** Escape: (space) ! " # $ % & ' ( ) * ; < > ? [ \ ] ^ ` { | } +*/ +/* x0 x1 x2 x3 x4 x5 x6 x7 x8 x9 xa xb xc xd xe xf */ + 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, /* 0x */ + 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, /* 1x */ + 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 0, 0, 0, 0, 0, /* 2x */ + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 1, 0, 1, 1, /* 3x */ + 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, /* 4x */ + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 1, 1, 1, 0, /* 5x */ + 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, /* 6x */ + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 1, 1, 0, 1, /* 7x */ +#endif + /* all bytes 0x80 through 0xbf are unescaped, being secondary + ** bytes to UTF8 characters. Bytes 0xc0 through 0xff are the + ** first byte of a UTF8 character and do get escaped */ + 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, /* 8x */ + 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, /* 9x */ + 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, /* ax */ + 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, /* bx */ + 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, /* cx */ + 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, /* dx */ + 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, /* ex */ + 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5 /* fx */ +}; + +/* +** pBlob is a shell command under construction. This routine safely +** appends filename argument zIn. +** +** The argument is escaped if it contains white space or other characters +** that need to be escaped for the shell. If zIn contains characters +** that cannot be safely escaped, then throw a fatal error. +** +** If the isFilename argument is true, then the argument is expected +** to be a filename. As shell commands commonly have command-line +** options that begin with "-" and since we do not want an attacker +** to be able to invoke these switches using filenames that begin +** with "-", if zIn begins with "-", prepend an additional "./" +** (or ".\\" on Windows). +*/ +void blob_append_escaped_arg(Blob *pBlob, const char *zIn, int isFilename){ + int i; + unsigned char c; + int needEscape = 0; + int n = blob_size(pBlob); + char *z = blob_buffer(pBlob); + + /* Look for illegal byte-sequences and byte-sequences that require + ** escaping. No control-characters are allowed. All spaces and + ** non-ASCII unicode characters and some punctuation characters require + ** escaping. */ + for(i=0; (c = (unsigned char)zIn[i])!=0; i++){ + if( aSafeChar[c] ){ + unsigned char x = aSafeChar[c]; + needEscape = 1; + if( x==2 ){ + Blob bad; + blob_token(pBlob, &bad); + fossil_fatal("the [%s] argument to the \"%s\" command contains " + "a character (ascii 0x%02x) that is not allowed in " + "filename arguments", + zIn, blob_str(&bad), c); + }else if( x>2 ){ + if( (zIn[i+1]&0xc0)!=0x80 + || (x>=4 && (zIn[i+2]&0xc0)!=0x80) + || (x==5 && (zIn[i+3]&0xc0)!=0x80) + ){ + Blob bad; + blob_token(pBlob, &bad); + fossil_fatal("the [%s] argument to the \"%s\" command contains " + "an illegal UTF-8 character", + zIn, blob_str(&bad)); + } + i += x-2; + } + } + } + + /* Separate from the previous argument by a space */ + if( n>0 && !fossil_isspace(z[n-1]) ){ + blob_append_char(pBlob, ' '); + } + + /* Check for characters that need quoting */ + if( !needEscape ){ + if( isFilename && zIn[0]=='-' ){ + blob_append_char(pBlob, '.'); +#if defined(_WIN32) + blob_append_char(pBlob, '\\'); +#else + blob_append_char(pBlob, '/'); +#endif + } + blob_append(pBlob, zIn, -1); + }else{ +#if defined(_WIN32) + /* Quoting strategy for windows: + ** Put the entire name inside of "...". Any " characters within + ** the name get doubled. + */ + blob_append_char(pBlob, '"'); + if( isFilename && zIn[0]=='-' ){ + blob_append_char(pBlob, '.'); + blob_append_char(pBlob, '\\'); + }else if( zIn[0]=='/' ){ + blob_append_char(pBlob, '.'); + } + for(i=0; (c = (unsigned char)zIn[i])!=0; i++){ + blob_append_char(pBlob, (char)c); + if( c=='"' ) blob_append_char(pBlob, '"'); + } + blob_append_char(pBlob, '"'); +#else + /* Quoting strategy for unix: + ** If the name does not contain ', then surround the whole thing + ** with '...'. If there is one or more ' characters within the + ** name, then put \ before each special character. + */ + if( strchr(zIn,'\'') ){ + if( isFilename && zIn[0]=='-' ){ + blob_append_char(pBlob, '.'); + blob_append_char(pBlob, '/'); + } + for(i=0; (c = (unsigned char)zIn[i])!=0; i++){ + if( aSafeChar[c] && aSafeChar[c]!=2 ) blob_append_char(pBlob, '\\'); + blob_append_char(pBlob, (char)c); + } + }else{ + blob_append_char(pBlob, '\''); + if( isFilename && zIn[0]=='-' ){ + blob_append_char(pBlob, '.'); + blob_append_char(pBlob, '/'); + } + blob_append(pBlob, zIn, -1); + blob_append_char(pBlob, '\''); + } +#endif + } +} + +/* +** COMMAND: test-escaped-arg +** +** Usage %fossil ARGS ... +** +** Run each argument through blob_append_escaped_arg() and show the +** result. Append each argument to "fossil test-echo" and run that +** using fossil_system() to verify that it really does get escaped +** correctly. +** +** Other options: +** +** --filename-args BOOL Subsequent arguments are assumed to be +** filenames if BOOL is true, or not if BOOL +** is false. Defaults on. +** +** --hex HEX Skip the --hex flag and instead decode HEX +** into ascii. This provides a way to insert +** unusual characters as an argument for testing. +** +** --compare HEX ASCII Verify that argument ASCII is identical to +** to decoded HEX. +** +** --fuzz N Run N fuzz cases. Each cases is a call +** to "fossil test-escaped-arg --compare HEX ARG" +** where HEX and ARG are the same argument. +** The argument is chosen at random. +*/ +void test_escaped_arg_command(void){ + int i; + Blob x; + const char *zArg; + int isFilename = 1; + char zBuf[100]; + blob_init(&x, 0, 0); + for(i=2; i<g.argc; i++){ + zArg = g.argv[i]; + if( fossil_strcmp(zArg, "--hex")==0 && i+1<g.argc ){ + size_t n = strlen(g.argv[++i]); + if( n>=(sizeof(zBuf)-1)*2 ){ + fossil_fatal("Argument to --hex is too big"); + } + memset(zBuf, 0, sizeof(zBuf)); + decode16((const unsigned char*)g.argv[i], (unsigned char*)zBuf, (int)n); + zArg = zBuf; + }else if( fossil_strcmp(zArg, "--compare")==0 && i+2<g.argc ){ + size_t n = strlen(g.argv[++i]); + if( n>=(sizeof(zBuf)-1)*2 ){ + fossil_fatal("HEX argument to --compare is too big"); + } + memset(zBuf, 0, sizeof(zBuf)); + if( decode16((const unsigned char*)g.argv[i], (unsigned char*)zBuf, + (int)n) ){ + fossil_fatal("HEX decode of %s failed", g.argv[i]); + } + zArg = g.argv[++i]; + if( zArg[0]=='-' ){ + fossil_fatal("filename argument \"%s\" begins with \"-\"", zArg); + } +#ifdef _WIN32 + if( zBuf[0]=='-' && zArg[0]=='.' && zArg[1]=='\\' ) zArg += 2; +#else + if( zBuf[0]=='-' && zArg[0]=='.' && zArg[1]=='/' ) zArg += 2; +#endif + if( strcmp(zBuf, zArg)!=0 ){ + fossil_fatal("argument disagree: \"%s\" (%s) versus \"%s\"", + zBuf, g.argv[i-1], zArg); + } + continue; + }else if( fossil_strcmp(zArg, "--fuzz")==0 && i+1<g.argc ){ + int n = atoi(g.argv[++i]); + int j; + for(j=0; j<n; j++){ + unsigned char m, k; + int rc; + unsigned char zWord[100]; + sqlite3_randomness(sizeof(m), &m); + m = (m%40)+5; + sqlite3_randomness(m, zWord); /* Between 5 and 45 bytes of randomness */ + for(k=0; k<m; k++){ + unsigned char cx = zWord[k]; + if( cx<0x20 || cx>=0x7f ){ + /* Translate illegal bytes into various non-ASCII unicode + ** characters in order to exercise those code paths */ + unsigned int u; + if( cx>=0x7f ){ + u = cx; + }else if( cx>=0x08 ){ + u = 0x800 + cx; + }else{ + u = 0x10000 + cx; + } + if( u<0x00080 ){ + zWord[k] = u & 0xFF; + }else if( u<0x00800 ){ + zWord[k++] = 0xC0 + (u8)((u>>6)&0x1F); + zWord[k] = 0x80 + (u8)(u & 0x3F); + }else if( u<0x10000 ){ + zWord[k++] = 0xE0 + (u8)((u>>12)&0x0F); + zWord[k++] = 0x80 + (u8)((u>>6) & 0x3F); + zWord[k] = 0x80 + (u8)(u & 0x3F); + }else{ + zWord[k++] = 0xF0 + (u8)((u>>18) & 0x07); + zWord[k++] = 0x80 + (u8)((u>>12) & 0x3F); + zWord[k++] = 0x80 + (u8)((u>>6) & 0x3F); + zWord[k] = 0x80 + (u8)(u & 0x3F); + } + } + } + zWord[k] = 0; + encode16(zWord, (unsigned char*)zBuf, (int)k); + blob_appendf(&x, "%$ test-escaped-arg --compare %s %$", + g.nameOfExe, zBuf,zWord); + rc = fossil_system(blob_str(&x)); + if( rc ) fossil_fatal("failed test (%d): %s\n", rc, blob_str(&x)); + blob_reset(&x); + } + continue; + }else if( fossil_strcmp(zArg, "--filename-args")==0 ){ + if( i+1<g.argc ){ + i++; + isFilename = is_truth(g.argv[i]); + } + continue; + } + fossil_print("%3d [%s]: ", i, zArg); + if( isFilename ){ + blob_appendf(&x, "%$ test-echo %$", g.nameOfExe, zArg); + }else{ + blob_appendf(&x, "%$ test-echo %!$", g.nameOfExe, zArg); + } + fossil_print("%s\n", blob_str(&x)); + fossil_system(blob_str(&x)); + blob_reset(&x); + } +} + +/* +** A read(2)-like impl for the Blob class. Reads (copies) up to nLen +** bytes from pIn, starting at position pIn->iCursor, and copies them +** to pDest (which must be valid memory at least nLen bytes long). +** +** Returns the number of bytes read/copied, which may be less than +** nLen (if end-of-blob is encountered). +** +** Updates pIn's cursor. +** +** Returns 0 if pIn contains no data. +*/ +unsigned int blob_read(Blob *pIn, void * pDest, unsigned int nLen ){ + if( !pIn->aData || (pIn->iCursor >= pIn->nUsed) ){ + return 0; + } else if( (pIn->iCursor + nLen) > (unsigned int)pIn->nUsed ){ + nLen = (unsigned int) (pIn->nUsed - pIn->iCursor); + } + assert( pIn->nUsed > pIn->iCursor ); + assert( (pIn->iCursor+nLen) <= pIn->nUsed ); + if( nLen ){ + memcpy( pDest, pIn->aData, nLen ); + pIn->iCursor += nLen; + } + return nLen; +} + +/* +** Swaps the contents of the given blobs. Results +** are unspecified if either value is NULL or both +** point to the same blob. +*/ +void blob_swap( Blob *pLeft, Blob *pRight ){ + Blob swap = *pLeft; + *pLeft = *pRight; + *pRight = swap; +} + +/* +** Strip a possible byte-order-mark (BOM) from the blob. On Windows, if there +** is either no BOM at all or an (le/be) UTF-16 BOM, a conversion to UTF-8 is +** done. If useMbcs is false and there is no BOM, the input string is assumed +** to be UTF-8 already, so no conversion is done. +*/ +void blob_to_utf8_no_bom(Blob *pBlob, int useMbcs){ + char *zUtf8; + int bomSize = 0; + int bomReverse = 0; + if( starts_with_utf8_bom(pBlob, &bomSize) ){ + struct Blob temp; + zUtf8 = blob_str(pBlob) + bomSize; + blob_zero(&temp); + blob_append(&temp, zUtf8, -1); + blob_swap(pBlob, &temp); + blob_reset(&temp); + }else if( starts_with_utf16_bom(pBlob, &bomSize, &bomReverse) ){ + zUtf8 = blob_buffer(pBlob); + if( bomReverse ){ + /* Found BOM, but with reversed bytes */ + unsigned int i = blob_size(pBlob); + while( i>1 ){ + /* swap bytes of unicode representation */ + char zTemp = zUtf8[--i]; + zUtf8[i] = zUtf8[i-1]; + zUtf8[--i] = zTemp; + } + } + /* Make sure the blob contains two terminating 0-bytes */ + blob_append(pBlob, "\000\000", 3); + zUtf8 = blob_str(pBlob) + bomSize; + zUtf8 = fossil_unicode_to_utf8(zUtf8); + blob_reset(pBlob); + blob_set_dynamic(pBlob, zUtf8); + }else if( useMbcs && invalid_utf8(pBlob) ){ +#if defined(_WIN32) || defined(__CYGWIN__) + zUtf8 = fossil_mbcs_to_utf8(blob_str(pBlob)); + blob_reset(pBlob); + blob_append(pBlob, zUtf8, -1); + fossil_mbcs_free(zUtf8); +#else + blob_cp1252_to_utf8(pBlob); +#endif /* _WIN32 */ + } } Index: src/branch.c ================================================================== --- src/branch.c +++ src/branch.c @@ -20,12 +20,53 @@ #include "config.h" #include "branch.h" #include <assert.h> /* -** fossil branch new BRANCH-NAME ?ORIGIN-CHECK-IN? ?-bgcolor COLOR? -** argv0 argv1 argv2 argv3 argv4 +** Return true if zBr is the branch name associated with check-in with +** blob.uuid value of zUuid +*/ +int branch_includes_uuid(const char *zBr, const char *zUuid){ + return db_exists( + "SELECT 1 FROM tagxref, blob" + " WHERE blob.uuid=%Q AND tagxref.rid=blob.rid" + " AND tagxref.value=%Q AND tagxref.tagtype>0" + " AND tagxref.tagid=%d", + zUuid, zBr, TAG_BRANCH + ); +} + +/* +** If RID refers to a check-in, return the name of the branch for that +** check-in. +** +** Space to hold the returned value is obtained from fossil_malloc() +** and should be freed by the caller. +*/ +char *branch_of_rid(int rid){ + char *zBr = 0; + static Stmt q; + db_static_prepare(&q, + "SELECT value FROM tagxref" + " WHERE rid=$rid AND tagid=%d" + " AND tagtype>0", TAG_BRANCH); + db_bind_int(&q, "$rid", rid); + if( db_step(&q)==SQLITE_ROW ){ + zBr = fossil_strdup(db_column_text(&q,0)); + } + db_reset(&q); + if( zBr==0 ){ + static char *zMain = 0; + if( zMain==0 ) zMain = db_get("main-branch",0); + zBr = fossil_strdup(zMain); + } + return zBr; +} + +/* +** fossil branch new NAME BASIS ?OPTIONS? +** argv0 argv1 argv2 argv3 argv4 */ void branch_new(void){ int rootid; /* RID of the root check-in - what we branch off of */ int brid; /* RID of the branch check-in */ int noSign; /* True if the branch is unsigned */ @@ -35,81 +76,94 @@ const char *zBranch; /* Name of the new branch */ char *zDate; /* Date that branch was created */ char *zComment; /* Check-in comment for the new branch */ const char *zColor; /* Color of the new branch */ Blob branch; /* manifest for the new branch */ - Blob parent; /* root check-in manifest */ - Manifest mParent; /* Parsed parent manifest */ + Manifest *pParent; /* Parsed parent manifest */ Blob mcksum; /* Self-checksum on the manifest */ - + const char *zDateOvrd; /* Override date string */ + const char *zUserOvrd; /* Override user name */ + int isPrivate = 0; /* True if the branch should be private */ + noSign = find_option("nosign","",0)!=0; zColor = find_option("bgcolor","c",1); + isPrivate = find_option("private",0,0)!=0; + zDateOvrd = find_option("date-override",0,1); + zUserOvrd = find_option("user-override",0,1); verify_all_options(); if( g.argc<5 ){ - usage("new BRANCH-NAME CHECK-IN ?-bgcolor COLOR?"); + usage("new BRANCH-NAME BASIS ?OPTIONS?"); } - db_find_and_open_repository(1); - noSign = db_get_int("omitsign", 0)|noSign; - + db_find_and_open_repository(0, 0); + noSign = db_get_boolean("omitsign", 0)|noSign; + if( db_get_boolean("clearsign", 0)==0 ){ noSign = 1; } + /* fossil branch new name */ zBranch = g.argv[3]; if( zBranch==0 || zBranch[0]==0 ){ - fossil_panic("branch name cannot be empty"); + fossil_fatal("branch name cannot be empty"); } if( db_exists( "SELECT 1 FROM tagxref" " WHERE tagtype>0" - " AND tagid=(SELECT tagid FROM tag WHERE tagname='sym-%s')", + " AND tagid=(SELECT tagid FROM tag WHERE tagname='sym-%q')", zBranch)!=0 ){ fossil_fatal("branch \"%s\" already exists", zBranch); } user_select(); db_begin_transaction(); - rootid = name_to_rid(g.argv[4]); + rootid = name_to_typed_rid(g.argv[4], "ci"); if( rootid==0 ){ fossil_fatal("unable to locate check-in off of which to branch"); } + + pParent = manifest_get(rootid, CFTYPE_MANIFEST, 0); + if( pParent==0 ){ + fossil_fatal("%s is not a valid check-in", g.argv[4]); + } /* Create a manifest for the new branch */ blob_zero(&branch); + if( pParent->zBaseline ){ + blob_appendf(&branch, "B %s\n", pParent->zBaseline); + } zComment = mprintf("Create new branch named \"%h\"", zBranch); blob_appendf(&branch, "C %F\n", zComment); - zDate = db_text(0, "SELECT datetime('now')"); - zDate[10] = 'T'; + zDate = date_in_standard_format(zDateOvrd ? zDateOvrd : "now"); blob_appendf(&branch, "D %s\n", zDate); /* Copy all of the content from the parent into the branch */ - content_get(rootid, &parent); - manifest_parse(&mParent, &parent); - if( mParent.type!=CFTYPE_MANIFEST ){ - fossil_fatal("%s is not a valid check-in", g.argv[4]); - } - for(i=0; i<mParent.nFile; ++i){ - if( mParent.aFile[i].zPerm[0] ){ - blob_appendf(&branch, "F %F %s %s\n", - mParent.aFile[i].zName, - mParent.aFile[i].zUuid, - mParent.aFile[i].zPerm); - }else{ - blob_appendf(&branch, "F %F %s\n", - mParent.aFile[i].zName, - mParent.aFile[i].zUuid); - } + for(i=0; i<pParent->nFile; ++i){ + blob_appendf(&branch, "F %F", pParent->aFile[i].zName); + if( pParent->aFile[i].zUuid ){ + blob_appendf(&branch, " %s", pParent->aFile[i].zUuid); + if( pParent->aFile[i].zPerm && pParent->aFile[i].zPerm[0] ){ + blob_appendf(&branch, " %s", pParent->aFile[i].zPerm); + } + } + blob_append(&branch, "\n", 1); } zUuid = db_text(0, "SELECT uuid FROM blob WHERE rid=%d", rootid); blob_appendf(&branch, "P %s\n", zUuid); - blob_appendf(&branch, "R %s\n", mParent.zRepoCksum); - manifest_clear(&mParent); + if( pParent->zRepoCksum ){ + blob_appendf(&branch, "R %s\n", pParent->zRepoCksum); + } + manifest_destroy(pParent); /* Add the symbolic branch name and the "branch" tag to identify ** this as a new branch */ + if( content_is_private(rootid) ) isPrivate = 1; + if( isPrivate && zColor==0 ) zColor = "#fec084"; if( zColor!=0 ){ blob_appendf(&branch, "T *bgcolor * %F\n", zColor); } blob_appendf(&branch, "T *branch * %F\n", zBranch); blob_appendf(&branch, "T *sym-%F *\n", zBranch); + if( isPrivate ){ + noSign = 1; + } /* Cancel all other symbolic tags */ db_prepare(&q, "SELECT tagname FROM tagxref, tag" " WHERE tagxref.rid=%d AND tagxref.tagid=tag.tagid" @@ -119,37 +173,39 @@ while( db_step(&q)==SQLITE_ROW ){ const char *zTag = db_column_text(&q, 0); blob_appendf(&branch, "T -%F *\n", zTag); } db_finalize(&q); - - blob_appendf(&branch, "U %F\n", g.zLogin); + + blob_appendf(&branch, "U %F\n", zUserOvrd ? zUserOvrd : login_name()); md5sum_blob(&branch, &mcksum); blob_appendf(&branch, "Z %b\n", &mcksum); if( !noSign && clearsign(&branch, &branch) ){ Blob ans; - blob_zero(&ans); + char cReply; prompt_user("unable to sign manifest. continue (y/N)? ", &ans); - if( blob_str(&ans)[0]!='y' ){ + cReply = blob_str(&ans)[0]; + if( cReply!='y' && cReply!='Y'){ db_end_transaction(1); - exit(1); + fossil_exit(1); } } - brid = content_put(&branch, 0, 0); + brid = content_put_ex(&branch, 0, 0, 0, isPrivate); if( brid==0 ){ - fossil_panic("trouble committing manifest: %s", g.zErrMsg); + fossil_fatal("trouble committing manifest: %s", g.zErrMsg); } db_multi_exec("INSERT OR IGNORE INTO unsent VALUES(%d)", brid); - if( manifest_crosslink(brid, &branch)==0 ){ - fossil_panic("unable to install new manifest"); + if( manifest_crosslink(brid, &branch, MC_PERMIT_HOOKS)==0 ){ + fossil_fatal("%s", g.zErrMsg); } - content_deltify(rootid, brid, 0); + assert( blob_is_reset(&branch) ); + content_deltify(rootid, &brid, 1, 0); zUuid = db_text(0, "SELECT uuid FROM blob WHERE rid=%d", brid); - printf("New branch: %s\n", zUuid); + fossil_print("New branch: %s\n", zUuid); if( g.argc==3 ){ - printf( + fossil_print( "\n" "Note: the local check-out has not been updated to the new\n" " branch. To begin working on the new branch, do this:\n" "\n" " %s update %s\n", @@ -158,202 +214,504 @@ } /* Commit */ db_end_transaction(0); - + /* Do an autosync push, if requested */ - autosync(AUTOSYNC_PUSH); + if( !isPrivate ) autosync_loop(SYNC_PUSH, db_get_int("autosync-tries",1),0); +} + +/* +** Create a TEMP table named "tmp_brlist" with 7 columns: +** +** name Name of the branch +** mtime Time of last checkin on this branch +** isclosed True if the branch is closed +** mergeto Another branch this branch was merged into +** nckin Number of checkins on this branch +** ckin Hash of the last checkin on this branch +** bgclr Background color for this branch +*/ +static const char createBrlistQuery[] = +@ CREATE TEMP TABLE IF NOT EXISTS tmp_brlist AS +@ SELECT +@ tagxref.value AS name, +@ max(event.mtime) AS mtime, +@ EXISTS(SELECT 1 FROM tagxref AS tx +@ WHERE tx.rid=tagxref.rid +@ AND tx.tagid=(SELECT tagid FROM tag WHERE tagname='closed') +@ AND tx.tagtype>0) AS isclosed, +@ (SELECT tagxref.value +@ FROM plink CROSS JOIN tagxref +@ WHERE plink.pid=event.objid +@ AND tagxref.rid=plink.cid +@ AND tagxref.tagid=(SELECT tagid FROM tag WHERE tagname='branch') +@ AND tagtype>0) AS mergeto, +@ count(*) AS nckin, +@ (SELECT uuid FROM blob WHERE rid=tagxref.rid) AS ckin, +@ event.bgcolor AS bgclr +@ FROM tagxref, tag, event +@ WHERE tagxref.tagid=tag.tagid +@ AND tagxref.tagtype>0 +@ AND tag.tagname='branch' +@ AND event.objid=tagxref.rid +@ GROUP BY 1; +; + +/* Call this routine to create the TEMP table */ +static void brlist_create_temp_table(void){ + db_exec_sql(createBrlistQuery); +} + + +#if INTERFACE +/* +** Allows bits in the mBplqFlags parameter to branch_prepare_list_query(). +*/ +#define BRL_CLOSED_ONLY 0x001 /* Show only closed branches */ +#define BRL_OPEN_ONLY 0x002 /* Show only open branches */ +#define BRL_BOTH 0x003 /* Show both open and closed branches */ +#define BRL_OPEN_CLOSED_MASK 0x003 +#define BRL_ORDERBY_MTIME 0x004 /* Sort by MTIME. (otherwise sort by name)*/ +#define BRL_REVERSE 0x008 /* Reverse the sort order */ + +#endif /* INTERFACE */ + +/* +** Prepare a query that will list branches. +** +** If (which<0) then the query pulls only closed branches. If +** (which>0) then the query pulls all (closed and opened) +** branches. Else the query pulls currently-opened branches. +*/ +void branch_prepare_list_query(Stmt *pQuery, int brFlags, const char *zBrNameGlob){ + Blob sql; + blob_init(&sql, 0, 0); + brlist_create_temp_table(); + switch( brFlags & BRL_OPEN_CLOSED_MASK ){ + case BRL_CLOSED_ONLY: { + blob_append_sql(&sql, + "SELECT name FROM tmp_brlist WHERE isclosed" + ); + break; + } + case BRL_BOTH: { + blob_append_sql(&sql, + "SELECT name FROM tmp_brlist WHERE 1" + ); + break; + } + case BRL_OPEN_ONLY: { + blob_append_sql(&sql, + "SELECT name FROM tmp_brlist WHERE NOT isclosed" + ); + break; + } + } + if(zBrNameGlob) blob_append_sql(&sql, " AND (name GLOB %Q)", zBrNameGlob); + if( brFlags & BRL_ORDERBY_MTIME ){ + blob_append_sql(&sql, " ORDER BY -mtime"); + }else{ + blob_append_sql(&sql, " ORDER BY name COLLATE nocase"); + } + if( brFlags & BRL_REVERSE ){ + blob_append_sql(&sql," DESC"); + } + db_prepare_blob(pQuery, &sql); + blob_reset(&sql); +} + +/* +** If the branch named in the argument is open, return a RID for one of +** the open leaves of that branch. If the branch does not exists or is +** closed, return 0. +*/ +int branch_is_open(const char *zBrName){ + return db_int(0, + "SELECT rid FROM tagxref AS ox" + " WHERE tagid=%d" + " AND tagtype=2" + " AND value=%Q" + " AND rid IN leaf" + " AND NOT EXISTS(SELECT 1 FROM tagxref AS ix" + " WHERE tagid=%d" + " AND tagtype=1" + " AND ox.rid=ix.rid)", + TAG_BRANCH, zBrName, TAG_CLOSED + ); } + /* ** COMMAND: branch ** -** Usage: %fossil branch SUBCOMMAND ... ?-R|--repository FILE? +** Usage: %fossil branch SUBCOMMAND ... ?OPTIONS? ** ** Run various subcommands to manage branches of the open repository or ** of the repository identified by the -R or --repository option. ** -** %fossil branch new BRANCH-NAME BASIS ?-bgcolor COLOR? +** > fossil branch current +** +** Print the name of the branch for the current check-out +** +** > fossil branch info BRANCH-NAME +** +** Print information about a branch +** +** > fossil branch list|ls ?OPTIONS? ?GLOB? +** +** List all branches. Options: +** -a|--all List all branches. Default show only open branches +** -c|--closed List closed branches. +** -r Reverse the sort order +** -t Show recently changed branches first +** +** The current branch is marked with an asterisk. +** +** If GLOB is given, show only branches matching the pattern. +** +** > fossil branch new BRANCH-NAME BASIS ?OPTIONS? ** ** Create a new branch BRANCH-NAME off of check-in BASIS. -** You can optionally give the branch a default color. +** Supported options for this subcommand include: +** --private branch is private (i.e., remains local) +** --bgcolor COLOR use COLOR instead of automatic background +** --nosign do not sign contents on this branch +** --date-override DATE DATE to use instead of 'now' +** --user-override USER USER to use instead of the current default +** +** DATE may be "now" or "YYYY-MM-DDTHH:MM:SS.SSS". If in +** year-month-day form, it may be truncated, the "T" may be +** replaced by a space, and it may also name a timezone offset +** from UTC as "-HH:MM" (westward) or "+HH:MM" (eastward). +** Either no timezone suffix or "Z" means UTC. ** -** %fossil branch list +** Options valid for all subcommands: ** -** List all branches -** +** -R|--repository REPO Run commands on repository REPO */ void branch_cmd(void){ int n; - db_find_and_open_repository(1); - if( g.argc<3 ){ - usage("new|list ..."); - } - n = strlen(g.argv[2]); - if( n>=2 && strncmp(g.argv[2],"new",n)==0 ){ - branch_new(); - }else if( n>=2 && strncmp(g.argv[2],"list",n)==0 ){ + const char *zCmd = "list"; + db_find_and_open_repository(0, 0); + if( g.argc>=3 ) zCmd = g.argv[2]; + n = strlen(zCmd); + if( strncmp(zCmd,"current",n)==0 ){ + if( !g.localOpen ){ + fossil_fatal("not within an open checkout"); + }else{ + int vid = db_lget_int("checkout", 0); + char *zCurrent = db_text(0, "SELECT value FROM tagxref" + " WHERE rid=%d AND tagid=%d", vid, TAG_BRANCH); + fossil_print("%s\n", zCurrent); + fossil_free(zCurrent); + } + }else if( strncmp(zCmd,"info",n)==0 ){ + int i; + for(i=3; i<g.argc; i++){ + const char *zBrName = g.argv[i]; + int rid = branch_is_open(zBrName); + if( rid==0 ){ + fossil_print("%s: not an open branch\n", zBrName); + }else{ + const char *zUuid = db_text(0,"SELECT uuid FROM blob WHERE rid=%d",rid); + const char *zDate = db_text(0, + "SELECT datetime(mtime,toLocal()) FROM event" + " WHERE objid=%d", rid); + fossil_print("%s: open as of %s on %.16s\n", zBrName, zDate, zUuid); + } + } + }else if( (strncmp(zCmd,"list",n)==0)||(strncmp(zCmd, "ls", n)==0) ){ Stmt q; - db_prepare(&q, - "%s" - " AND blob.rid IN (SELECT rid FROM tagxref" - " WHERE tagid=%d AND tagtype==2 AND srcid!=0)" - " ORDER BY event.mtime DESC", - timeline_query_for_tty(), TAG_BRANCH - ); - print_timeline(&q, 2000); + int vid; + char *zCurrent = 0; + const char *zBrNameGlob = 0; + int brFlags = BRL_OPEN_ONLY; + if( find_option("all","a",0)!=0 ) brFlags = BRL_BOTH; + if( find_option("closed","c",0)!=0 ) brFlags = BRL_CLOSED_ONLY; + if( find_option("t",0,0)!=0 ) brFlags |= BRL_ORDERBY_MTIME; + if( find_option("r",0,0)!=0 ) brFlags |= BRL_REVERSE; + if( g.argc >= 4 ) zBrNameGlob = g.argv[3]; + + if( g.localOpen ){ + vid = db_lget_int("checkout", 0); + zCurrent = db_text(0, "SELECT value FROM tagxref" + " WHERE rid=%d AND tagid=%d", vid, TAG_BRANCH); + } + branch_prepare_list_query(&q, brFlags, zBrNameGlob); + while( db_step(&q)==SQLITE_ROW ){ + const char *zBr = db_column_text(&q, 0); + int isCur = zCurrent!=0 && fossil_strcmp(zCurrent,zBr)==0; + fossil_print("%s%s\n", (isCur ? "* " : " "), zBr); + } db_finalize(&q); + }else if( strncmp(zCmd,"new",n)==0 ){ + branch_new(); }else{ - fossil_panic("branch subcommand should be one of: " - "new list"); + fossil_fatal("branch subcommand should be one of: " + "current info list ls new"); + } +} + +/* +** This is the new-style branch-list page that shows the branch names +** together with their ages (time of last check-in) and whether or not +** they are closed or merged to another branch. +** +** Control jumps to this routine from brlist_page() (the /brlist handler) +** if there are no query parameters. +*/ +static void new_brlist_page(void){ + Stmt q; + double rNow; + int show_colors = PB("colors"); + login_check_credentials(); + if( !g.perm.Read ){ login_needed(g.anon.Read); return; } + style_set_current_feature("branch"); + style_header("Branches"); + style_adunit_config(ADUNIT_RIGHT_OK); + style_submenu_checkbox("colors", "Use Branch Colors", 0, 0); + login_anonymous_available(); + + brlist_create_temp_table(); + db_prepare(&q, "SELECT * FROM tmp_brlist ORDER BY mtime DESC"); + rNow = db_double(0.0, "SELECT julianday('now')"); + @ <script id="brlist-data" type="application/json">\ + @ {"timelineUrl":"%R/timeline"}</script> + @ <div class="brlist"> + @ <table class='sortable' data-column-types='tkNtt' data-init-sort='2'> + @ <thead><tr> + @ <th>Branch Name</th> + @ <th>Last Change</th> + @ <th>Check-ins</th> + @ <th>Status</th> + @ <th>Resolution</th> + @ </tr></thead><tbody> + while( db_step(&q)==SQLITE_ROW ){ + const char *zBranch = db_column_text(&q, 0); + double rMtime = db_column_double(&q, 1); + int isClosed = db_column_int(&q, 2); + const char *zMergeTo = db_column_text(&q, 3); + int nCkin = db_column_int(&q, 4); + const char *zLastCkin = db_column_text(&q, 5); + const char *zBgClr = db_column_text(&q, 6); + char *zAge = human_readable_age(rNow - rMtime); + sqlite3_int64 iMtime = (sqlite3_int64)(rMtime*86400.0); + if( zMergeTo && zMergeTo[0]==0 ) zMergeTo = 0; + if( zBgClr == 0 ){ + if( zBranch==0 || strcmp(zBranch,"trunk")==0 ){ + zBgClr = 0; + }else{ + zBgClr = hash_color(zBranch); + } + } + if( zBgClr && zBgClr[0] && show_colors ){ + @ <tr style="background-color:%s(zBgClr)"> + }else{ + @ <tr> + } + @ <td>%z(href("%R/timeline?r=%T",zBranch))%h(zBranch)</a><input + @ type="checkbox" disabled="disabled"/></td> + @ <td data-sortkey="%016llx(iMtime)">%s(zAge)</td> + @ <td>%d(nCkin)</td> + fossil_free(zAge); + @ <td>%s(isClosed?"closed":"")</td> + if( zMergeTo ){ + @ <td>merged into + @ %z(href("%R/timeline?f=%!S",zLastCkin))%h(zMergeTo)</a></td> + }else{ + @ <td></td> + } + @ </tr> } + @ </tbody></table></div> + db_finalize(&q); + builtin_request_js("fossil.page.brlist.js"); + style_table_sorter(); + style_finish_page(); } /* ** WEBPAGE: brlist -** -** Show a timeline of all branches -*/ -void brlist_page(void){ - Stmt q; - int cnt; - - login_check_credentials(); - if( !g.okRead ){ login_needed(); return; } - - style_header("Branches"); - style_submenu_element("Timeline", "Timeline", "brtimeline"); - login_anonymous_available(); - compute_leaves(0, 1); - style_sidebox_begin("Nomenclature:", "33%"); - @ <ol> - @ <li> An <b>open branch</b> is a branch that has one or - @ more <a href="leaves">open leaves.</a> - @ The presence of open leaves presumably means - @ that the branch is still being extended with new check-ins.</li> - @ <li> A <b>closed branch</b> is a branch with only - @ <a href="leaves?closed">closed leaves</a>. - @ Closed branches are fixed and do not change (unless they are first - @ reopened)</li> - @ </ol> - style_sidebox_end(); - - db_prepare(&q, - "SELECT DISTINCT value FROM tagxref" - " WHERE tagid=%d AND value NOT NULL" - " AND rid IN leaves" - " ORDER BY value /*sort*/", - TAG_BRANCH - ); - cnt = 0; - while( db_step(&q)==SQLITE_ROW ){ - const char *zBr = db_column_text(&q, 0); - if( cnt==0 ){ - @ <h2>Open Branches:</h2> - @ <ul> - cnt++; - } - if( g.okHistory ){ - @ <li><a href="%s(g.zBaseURL)/timeline?t=%T(zBr)">%h(zBr)</a></li> - }else{ - @ <li><b>%h(zBr)</b></li> - } - } - db_finalize(&q); - if( cnt ){ - @ </ul> - } - cnt = 0; - db_prepare(&q, - "SELECT value FROM tagxref" - " WHERE tagid=%d AND value NOT NULL" - " EXCEPT " - "SELECT value FROM tagxref" - " WHERE tagid=%d AND value NOT NULL" - " AND rid IN leaves" - " ORDER BY value /*sort*/", - TAG_BRANCH, TAG_BRANCH - ); - while( db_step(&q)==SQLITE_ROW ){ - const char *zBr = db_column_text(&q, 0); - if( cnt==0 ){ - @ <h2>Closed Branches:</h2> - @ <ul> - cnt++; - } - if( g.okHistory ){ - @ <li><a href="%s(g.zBaseURL)/timeline?t=%T(zBr)">%h(zBr)</a></li> - }else{ - @ <li><b>%h(zBr)</b></li> +** Show a list of branches. With no query parameters, a sortable table +** is used to show all branches. If query parameters are present a +** fixed bullet list is shown. +** +** Query parameters: +** +** all Show all branches +** closed Show only closed branches +** open Show only open branches +** colortest Show all branches with automatic color +** +** When there are no query parameters, a new-style /brlist page shows +** all branches in a sortable table. The new-style /brlist page is +** preferred and is the default. +*/ +void brlist_page(void){ + Stmt q; + int cnt; + int showClosed = P("closed")!=0; + int showAll = P("all")!=0; + int showOpen = P("open")!=0; + int colorTest = P("colortest")!=0; + int brFlags = BRL_OPEN_ONLY; + + if( showClosed==0 && showAll==0 && showOpen==0 && colorTest==0 ){ + new_brlist_page(); + return; + } + login_check_credentials(); + if( !g.perm.Read ){ login_needed(g.anon.Read); return; } + if( colorTest ){ + showClosed = 0; + showAll = 1; + } + if( showAll ) brFlags = BRL_BOTH; + if( showClosed ) brFlags = BRL_CLOSED_ONLY; + + style_set_current_feature("branch"); + style_header("%s", showClosed ? "Closed Branches" : + showAll ? "All Branches" : "Open Branches"); + style_submenu_element("Timeline", "brtimeline"); + if( showClosed ){ + style_submenu_element("All", "brlist?all"); + style_submenu_element("Open", "brlist?open"); + }else if( showAll ){ + style_submenu_element("Closed", "brlist?closed"); + style_submenu_element("Open", "brlist"); + }else{ + style_submenu_element("All", "brlist?all"); + style_submenu_element("Closed", "brlist?closed"); + } + if( !colorTest ){ + style_submenu_element("Color-Test", "brlist?colortest"); + }else{ + style_submenu_element("All", "brlist?all"); + } + login_anonymous_available(); +#if 0 + style_sidebox_begin("Nomenclature:", "33%"); + @ <ol> + @ <li> An <div class="sideboxDescribed">%z(href("brlist")) + @ open branch</a></div> is a branch that has one or more + @ <div class="sideboxDescribed">%z(href("leaves"))open leaves.</a></div> + @ The presence of open leaves presumably means + @ that the branch is still being extended with new check-ins.</li> + @ <li> A <div class="sideboxDescribed">%z(href("brlist?closed")) + @ closed branch</a></div> is a branch with only + @ <div class="sideboxDescribed">%z(href("leaves?closed")) + @ closed leaves</a></div>. + @ Closed branches are fixed and do not change (unless they are first + @ reopened).</li> + @ </ol> + style_sidebox_end(); +#endif + + branch_prepare_list_query(&q, brFlags, 0); + cnt = 0; + while( db_step(&q)==SQLITE_ROW ){ + const char *zBr = db_column_text(&q, 0); + if( cnt==0 ){ + if( colorTest ){ + @ <h2>Default background colors for all branches:</h2> + }else if( showClosed ){ + @ <h2>Closed Branches:</h2> + }else if( showAll ){ + @ <h2>All Branches:</h2> + }else{ + @ <h2>Open Branches:</h2> + } + @ <ul> + cnt++; + } + if( colorTest ){ + const char *zColor = hash_color(zBr); + @ <li><span style="background-color: %s(zColor)"> + @ %h(zBr) → %s(zColor)</span></li> + }else{ + @ <li>%z(href("%R/timeline?r=%T",zBr))%h(zBr)</a></li> } } if( cnt ){ @ </ul> } db_finalize(&q); - @ </ul> - @ <br clear="both"> - @ <script> - @ function xin(id){ - @ } - @ function xout(id){ - @ } - @ </script> - style_footer(); + style_finish_page(); } /* ** This routine is called while for each check-in that is rendered by ** the timeline of a "brlist" page. Add some additional hyperlinks ** to the end of the line. */ static void brtimeline_extra(int rid){ Stmt q; - if( !g.okHistory ) return; - db_prepare(&q, + if( !g.perm.Hyperlink ) return; + db_prepare(&q, "SELECT substr(tagname,5) FROM tagxref, tag" " WHERE tagxref.rid=%d" " AND tagxref.tagid=tag.tagid" " AND tagxref.tagtype>0" " AND tag.tagname GLOB 'sym-*'", rid ); while( db_step(&q)==SQLITE_ROW ){ const char *zTagName = db_column_text(&q, 0); - @ <a href="%s(g.zBaseURL)/timeline?t=%T(zTagName)">[timeline]</a> + @ %z(href("%R/timeline?r=%T",zTagName))[timeline]</a> } db_finalize(&q); } /* ** WEBPAGE: brtimeline ** ** Show a timeline of all branches +** +** Query parameters: +** +** ng No graph +** nohidden Hide check-ins with "hidden" tag +** onlyhidden Show only check-ins with "hidden" tag +** brbg Background color by branch name +** ubg Background color by user name */ void brtimeline_page(void){ + Blob sql = empty_blob; Stmt q; + int tmFlags; /* Timeline display flags */ + int fNoHidden = PB("nohidden")!=0; /* The "nohidden" query parameter */ + int fOnlyHidden = PB("onlyhidden")!=0; /* The "onlyhidden" query parameter */ login_check_credentials(); - if( !g.okRead ){ login_needed(); return; } + if( !g.perm.Read ){ login_needed(g.anon.Read); return; } + style_set_current_feature("branch"); style_header("Branches"); - style_submenu_element("List", "List", "brlist"); + style_submenu_element("List", "brlist"); login_anonymous_available(); + timeline_ss_submenu(); @ <h2>The initial check-in for each branch:</h2> - db_prepare(&q, - "%s AND blob.rid IN (SELECT rid FROM tagxref" - " WHERE tagtype>0 AND tagid=%d AND srcid!=0)" - " ORDER BY event.mtime DESC", - timeline_query_for_www(), TAG_BRANCH - ); - www_print_timeline(&q, 0, brtimeline_extra); + blob_append(&sql, timeline_query_for_www(), -1); + blob_append_sql(&sql, + "AND blob.rid IN (SELECT rid FROM tagxref" + " WHERE tagtype>0 AND tagid=%d AND srcid!=0)", TAG_BRANCH); + if( fNoHidden || fOnlyHidden ){ + const char* zUnaryOp = fNoHidden ? "NOT" : ""; + blob_append_sql(&sql, + " AND %s EXISTS(SELECT 1 FROM tagxref" + " WHERE tagid=%d AND tagtype>0 AND rid=blob.rid)\n", + zUnaryOp/*safe-for-%s*/, TAG_HIDDEN); + } + db_prepare(&q, "%s ORDER BY event.mtime DESC", blob_sql_text(&sql)); + blob_reset(&sql); + /* Always specify TIMELINE_DISJOINT, or graph_finish() may fail because of too + ** many descenders to (off-screen) parents. */ + tmFlags = TIMELINE_DISJOINT | TIMELINE_NOSCROLL; + if( PB("ng")==0 ) tmFlags |= TIMELINE_GRAPH; + if( PB("brbg")!=0 ) tmFlags |= TIMELINE_BRCOLOR; + if( PB("ubg")!=0 ) tmFlags |= TIMELINE_UCOLOR; + www_print_timeline(&q, tmFlags, 0, 0, 0, 0, 0, brtimeline_extra); db_finalize(&q); - @ <br clear="both"> - @ <script> - @ function xin(id){ - @ } - @ function xout(id){ - @ } - @ </script> - style_footer(); + style_finish_page(); } Index: src/browse.c ================================================================== --- src/browse.c +++ src/browse.c @@ -20,11 +20,11 @@ #include "config.h" #include "browse.h" #include <assert.h> /* -** This is the implemention of the "pathelement(X,N)" SQL function. +** This is the implementation of the "pathelement(X,N)" SQL function. ** ** If X is a unix-like pathname (with "/" separators) and N is an ** integer, then skip the initial N characters of X and return the ** name of the path component that begins on the N+1th character ** (numbered from 0). If the path component is a directory (if @@ -34,11 +34,11 @@ ** ** pathelement('abc/pqr/xyz', 4) -> '/pqr' ** pathelement('abc/pqr', 4) -> 'pqr' ** pathelement('abc/pqr/xyz', 0) -> '/abc' */ -static void pathelementFunc( +void pathelementFunc( sqlite3_context *context, int argc, sqlite3_value **argv ){ const unsigned char *z; @@ -59,10 +59,18 @@ zOut = sqlite3_mprintf("/%.*s", i-n, &z[n]); sqlite3_result_text(context, zOut, i-n+1, sqlite3_free); } } +/* +** Flag arguments for hyperlinked_path() +*/ +#if INTERFACE +# define LINKPATH_FINFO 0x0001 /* Link final term to /finfo */ +# define LINKPATH_FILE 0x0002 /* Link final term to /file */ +#endif + /* ** Given a pathname which is a relative path from the root of ** the repository to a file or directory, compute a string which ** is an HTML rendering of that path with hyperlinks on each ** directory component of the path where the hyperlink redirects @@ -71,21 +79,41 @@ ** There is no hyperlink on the file element of the path. ** ** The computed string is appended to the pOut blob. pOut should ** have already been initialized. */ -void hyperlinked_path(const char *zPath, Blob *pOut){ +void hyperlinked_path( + const char *zPath, /* Path to render */ + Blob *pOut, /* Write into this blob */ + const char *zCI, /* check-in name, or NULL */ + const char *zURI, /* "dir" or "tree" */ + const char *zREx, /* Extra query parameters */ + unsigned int mFlags /* Extra flags */ +){ int i, j; char *zSep = ""; for(i=0; zPath[i]; i=j){ for(j=i; zPath[j] && zPath[j]!='/'; j++){} - if( zPath[j] && g.okHistory ){ - blob_appendf(pOut, "%s<a href=\"%s/dir?name=%#T\">%#h</a>", - zSep, g.zBaseURL, j, zPath, j-i, &zPath[i]); + if( zPath[j]==0 ){ + if( mFlags & LINKPATH_FILE ){ + zURI = "file"; + }else if( mFlags & LINKPATH_FINFO ){ + zURI = "finfo"; + }else{ + blob_appendf(pOut, "/%h", zPath+i); + break; + } + } + if( zCI ){ + char *zLink = href("%R/%s?name=%#T%s&ci=%T", zURI, j, zPath, zREx,zCI); + blob_appendf(pOut, "%s%z%#h</a>", + zSep, zLink, j-i, &zPath[i]); }else{ - blob_appendf(pOut, "%s%#h", zSep, j-i, &zPath[i]); + char *zLink = href("%R/%s?name=%#T%s", zURI, j, zPath, zREx); + blob_appendf(pOut, "%s%z%#h</a>", + zSep, zLink, j-i, &zPath[i]); } zSep = "/"; while( zPath[j]=='/' ){ j++; } } } @@ -92,153 +120,1075 @@ /* ** WEBPAGE: dir ** +** Show the files and subdirectories within a single directory of the +** source tree. Only files for a single check-in are shown if the ci= +** query parameter is present. If ci= is missing, the union of files +** across all check-ins is shown. +** ** Query parameters: ** -** name=PATH Directory to display. Required. ** ci=LABEL Show only files in this check-in. Optional. +** name=PATH Directory to display. Optional. Top-level if missing +** re=REGEXP Show only files matching REGEXP +** type=TYPE TYPE=flat: use this display +** TYPE=tree: use the /tree display instead +** noreadme Do not attempt to display the README file. */ void page_dir(void){ - const char *zD = P("name"); + char *zD = fossil_strdup(P("name")); + int nD = zD ? strlen(zD)+1 : 0; int mxLen; - int nCol, nRow; - int cnt, i; char *zPrefix; Stmt q; const char *zCI = P("ci"); int rid = 0; - Blob content; - Blob dirname; - Manifest m; + char *zUuid = 0; + Manifest *pM = 0; const char *zSubdirLink; + int linkTrunk = 1; + int linkTip = 1; + HQuery sURI; + int isSymbolicCI = 0; /* ci= is symbolic name, not a hash prefix */ + int isBranchCI = 0; /* True if ci= refers to a branch name */ + char *zHeader = 0; + const char *zRegexp; /* The re= query parameter */ + char *zMatch; /* Extra title text describing the match */ + if( zCI && strlen(zCI)==0 ){ zCI = 0; } + if( strcmp(PD("type","flat"),"tree")==0 ){ page_tree(); return; } login_check_credentials(); - if( !g.okHistory ){ login_needed(); return; } - style_header("File List"); - sqlite3_create_function(g.db, "pathelement", 2, SQLITE_UTF8, 0, - pathelementFunc, 0, 0); + if( !g.perm.Read ){ login_needed(g.anon.Read); return; } + while( nD>1 && zD[nD-2]=='/' ){ zD[(--nD)-1] = 0; } /* If the name= parameter is an empty string, make it a NULL pointer */ if( zD && strlen(zD)==0 ){ zD = 0; } - /* If a specific check-in is requested, fetch and parse it. */ - if( zCI && (rid = name_to_rid(zCI))!=0 && content_get(rid, &content) ){ - if( !manifest_parse(&m, &content) || m.type!=CFTYPE_MANIFEST ){ + /* If a specific check-in is requested, fetch and parse it. If the + ** specific check-in does not exist, clear zCI. zCI==0 will cause all + ** files from all check-ins to be displayed. + */ + if( zCI ){ + pM = manifest_get_by_name(zCI, &rid); + if( pM ){ + int trunkRid = symbolic_name_to_rid("tag:trunk", "ci"); + linkTrunk = trunkRid && rid != trunkRid; + linkTip = rid != symbolic_name_to_rid("tip", "ci"); + zUuid = db_text(0, "SELECT uuid FROM blob WHERE rid=%d", rid); + isSymbolicCI = (sqlite3_strnicmp(zUuid, zCI, strlen(zCI))!=0); + isBranchCI = branch_includes_uuid(zCI, zUuid); + Th_Store("current_checkin", zCI); + }else{ zCI = 0; } } - /* Compute the title of the page */ - blob_zero(&dirname); + assert( isSymbolicCI==0 || (zCI!=0 && zCI[0]!=0) ); + if( zD==0 ){ + if( zCI ){ + zHeader = mprintf("Top-level Files of %s", zCI); + }else{ + zHeader = mprintf("All Top-level Files"); + } + }else{ + if( zCI ){ + zHeader = mprintf("Files in %s/ of %s", zD, zCI); + }else{ + zHeader = mprintf("All File in %s/", zD); + } + } + zRegexp = P("re"); + if( zRegexp ){ + zHeader = mprintf("%z matching \"%s\"", zHeader, zRegexp); + zMatch = mprintf(" matching \"%h\"", zRegexp); + }else{ + zMatch = ""; + } + style_header("%s", zHeader); + fossil_free(zHeader); + style_adunit_config(ADUNIT_RIGHT_OK); + sqlite3_create_function(g.db, "pathelement", 2, SQLITE_UTF8, 0, + pathelementFunc, 0, 0); + url_initialize(&sURI, "dir"); + cgi_query_parameters_to_url(&sURI); + + /* Compute the title of the page */ if( zD ){ - blob_append(&dirname, "in directory ", -1); - hyperlinked_path(zD, &dirname); - zPrefix = mprintf("%h/", zD); + Blob dirname; + blob_init(&dirname, 0, 0); + hyperlinked_path(zD, &dirname, zCI, "dir", "", 0); + @ <h2>Files in directory %s(blob_str(&dirname)) \ + blob_reset(&dirname); + zPrefix = mprintf("%s/", zD); + style_submenu_element("Top-Level", "%s", + url_render(&sURI, "name", 0, 0, 0)); }else{ - blob_append(&dirname, "in the top-level directory", -1); + @ <h2>Files in the top-level directory \ zPrefix = ""; } if( zCI ){ - char *zUuid = db_text(0, "SELECT uuid FROM blob WHERE rid=%d", rid); - char zShort[20]; - memcpy(zShort, zUuid, 10); - zShort[10] = 0; - @ <h2>Files of check-in [<a href="vinfo?name=%T(zUuid)">%s(zShort)</a>] - @ %s(blob_str(&dirname))</h2> - zSubdirLink = mprintf("%s/dir?ci=%S&name=%T", g.zTop, zUuid, zPrefix); - if( zD ){ - style_submenu_element("Top", "Top", "%s/dir?ci=%S", g.zTop, zUuid); - style_submenu_element("All", "All", "%s/dir?name=%t", g.zTop, zD); - }else{ - style_submenu_element("All", "All", "%s/dir", g.zBaseURL); - } - }else{ - @ <h2>The union of all files from all check-ins - @ %s(blob_str(&dirname))</h2> - zSubdirLink = mprintf("%s/dir?name=%T", g.zBaseURL, zPrefix); - if( zD ){ - style_submenu_element("Top", "Top", "%s/dir", g.zBaseURL); - style_submenu_element("Tip", "Tip", "%s/dir?name=%t&ci=tip", - g.zBaseURL, zD); - style_submenu_element("Trunk", "Trunk", "%s/dir?name=%t&ci=trunk", - g.zBaseURL,zD); - }else{ - style_submenu_element("Tip", "Tip", "%s/dir?ci=tip", g.zBaseURL); - style_submenu_element("Trunk", "Trunk", "%s/dir?ci=trunk", g.zBaseURL); - } - } + if( fossil_strcmp(zCI,"tip")==0 ){ + @ from the %z(href("%R/info?name=%T",zCI))latest check-in</a>\ + @ %s(zMatch)</h2> + }else if( isBranchCI ){ + @ from the %z(href("%R/info?name=%T",zCI))latest check-in</a> \ + @ of branch %z(href("%R/timeline?r=%T",zCI))%h(zCI)</a>\ + @ %s(zMatch)</h2> + }else { + @ of check-in %z(href("%R/info?name=%T",zCI))%h(zCI)</a>\ + @ %s(zMatch)</h2> + } + zSubdirLink = mprintf("%R/dir?ci=%T&name=%T", zCI, zPrefix); + if( nD==0 ){ + style_submenu_element("File Ages", "%R/fileage?name=%T", zCI); + } + }else{ + @ in any check-in</h2> + zSubdirLink = mprintf("%R/dir?name=%T", zPrefix); + } + if( linkTrunk ){ + style_submenu_element("Trunk", "%s", + url_render(&sURI, "ci", "trunk", 0, 0)); + } + if( linkTip ){ + style_submenu_element("Tip", "%s", url_render(&sURI, "ci", "tip", 0, 0)); + } + if( zD ){ + style_submenu_element("History","%R/timeline?chng=%T/*", zD); + } + style_submenu_element("All", "%s", url_render(&sURI, "ci", 0, 0, 0)); + style_submenu_element("Tree-View", "%s", + url_render(&sURI, "type", "tree", 0, 0)); /* Compute the temporary table "localfiles" containing the names - ** of all files and subdirectories in the zD[] directory. + ** of all files and subdirectories in the zD[] directory. ** ** Subdirectory names begin with "/". This causes them to sort ** first and it also gives us an easy way to distinguish files ** from directories in the loop that follows. */ db_multi_exec( "CREATE TEMP TABLE localfiles(x UNIQUE NOT NULL, u);" - "CREATE TEMP TABLE allfiles(x UNIQUE NOT NULL, u);" ); if( zCI ){ Stmt ins; - int i; - db_prepare(&ins, "INSERT INTO allfiles VALUES(:x, :u)"); - for(i=0; i<m.nFile; i++){ - db_bind_text(&ins, ":x", m.aFile[i].zName); - db_bind_text(&ins, ":u", m.aFile[i].zUuid); + ManifestFile *pFile; + ManifestFile *pPrev = 0; + int nPrev = 0; + int c; + + db_prepare(&ins, + "INSERT OR IGNORE INTO localfiles VALUES(pathelement(:x,0), :u)" + ); + manifest_file_rewind(pM); + while( (pFile = manifest_file_next(pM,0))!=0 ){ + if( nD>0 + && (fossil_strncmp(pFile->zName, zD, nD-1)!=0 + || pFile->zName[nD-1]!='/') + ){ + continue; + } + if( pPrev + && fossil_strncmp(&pFile->zName[nD],&pPrev->zName[nD],nPrev)==0 + && (pFile->zName[nD+nPrev]==0 || pFile->zName[nD+nPrev]=='/') + ){ + continue; + } + db_bind_text(&ins, ":x", &pFile->zName[nD]); + db_bind_text(&ins, ":u", pFile->zUuid); db_step(&ins); db_reset(&ins); + pPrev = pFile; + for(nPrev=0; (c=pPrev->zName[nD+nPrev]) && c!='/'; nPrev++){} + if( c=='/' ) nPrev++; } db_finalize(&ins); - }else{ - db_multi_exec( - "INSERT INTO allfiles SELECT name, NULL FROM filename" - ); - } - if( zD ){ - db_multi_exec( - "INSERT OR IGNORE INTO localfiles " - " SELECT pathelement(x,%d), u FROM allfiles" - " WHERE x GLOB '%q/*'", - strlen(zD)+1, zD - ); - }else{ - db_multi_exec( - "INSERT OR IGNORE INTO localfiles " - " SELECT pathelement(x,0), u FROM allfiles" + }else if( zD ){ + db_multi_exec( + "INSERT OR IGNORE INTO localfiles" + " SELECT pathelement(name,%d), NULL FROM filename" + " WHERE name GLOB '%q/*'", + nD, zD + ); + }else{ + db_multi_exec( + "INSERT OR IGNORE INTO localfiles" + " SELECT pathelement(name,0), NULL FROM filename" + ); + } + + /* If the re=REGEXP query parameter is present, filter out names that + ** do not match the pattern */ + if( zRegexp ){ + db_multi_exec( + "DELETE FROM localfiles WHERE x NOT REGEXP %Q", zRegexp ); } /* Generate a multi-column table listing the contents of zD[] ** directory. */ mxLen = db_int(12, "SELECT max(length(x)) FROM localfiles /*scan*/"); - cnt = db_int(0, "SELECT count(*) FROM localfiles /*scan*/"); - nCol = 4; - nRow = (cnt+nCol-1)/nCol; + if( mxLen<12 ) mxLen = 12; + mxLen += (mxLen+9)/10; db_prepare(&q, "SELECT x, u FROM localfiles ORDER BY x /*scan*/"); - @ <table border="0" width="100%%"><tr><td valign="top" width="25%%"> - i = 0; + @ <div class="columns files" style="columns: %d(mxLen)ex auto"> + @ <ul class="browser"> while( db_step(&q)==SQLITE_ROW ){ const char *zFN; - if( i==nRow ){ - @ </td><td valign="top" width="25%%"> - i = 0; - } - i++; zFN = db_column_text(&q, 0); if( zFN[0]=='/' ){ zFN++; - @ <li><a href="%s(zSubdirLink)%T(zFN)">%h(zFN)/</a></li> - }else if( zCI ){ - const char *zUuid = db_column_text(&q, 1); - @ <li><a href="%s(g.zBaseURL)/artifact?name=%s(zUuid)">%h(zFN)</a> + @ <li class="dir">%z(href("%s%T",zSubdirLink,zFN))%h(zFN)</a></li> + }else{ + const char *zLink; + if( zCI ){ + zLink = href("%R/file?name=%T%T&ci=%T",zPrefix,zFN,zCI); + }else{ + zLink = href("%R/finfo?name=%T%T",zPrefix,zFN); + } + @ <li class="%z(fileext_class(zFN))">%z(zLink)%h(zFN)</a></li> + } + } + db_finalize(&q); + manifest_destroy(pM); + @ </ul></div> + + /* If the "noreadme" query parameter is present, do not try to + ** show the content of the README file. + */ + if( P("noreadme")!=0 ){ + style_finish_page(); + return; + } + + /* If the directory contains a readme file, then display its content below + ** the list of files + */ + db_prepare(&q, + "SELECT x, u FROM localfiles" + " WHERE x COLLATE nocase IN" + " ('readme','readme.txt','readme.md','readme.wiki','readme.markdown'," + " 'readme.html') ORDER BY x LIMIT 1;" + ); + if( db_step(&q)==SQLITE_ROW ){ + const char *zName = db_column_text(&q,0); + const char *zUuid = db_column_text(&q,1); + if( zUuid ){ + rid = fast_uuid_to_rid(zUuid); + }else{ + if( zD ){ + rid = db_int(0, + "SELECT fid FROM filename, mlink, event" + " WHERE name='%q/%q'" + " AND mlink.fnid=filename.fnid" + " AND event.objid=mlink.mid" + " ORDER BY event.mtime DESC LIMIT 1", + zD, zName + ); + }else{ + rid = db_int(0, + "SELECT fid FROM filename, mlink, event" + " WHERE name='%q'" + " AND mlink.fnid=filename.fnid" + " AND event.objid=mlink.mid" + " ORDER BY event.mtime DESC LIMIT 1", + zName + ); + } + } + if( rid ){ + @ <hr> + if( sqlite3_strlike("readme.html", zName, 0)==0 ){ + if( zUuid==0 ){ + zUuid = db_text(0, "SELECT uuid FROM blob WHERE rid=%d", rid); + } + @ <iframe src="%R/raw/%s(zUuid)" + @ width="100%%" frameborder="0" marginwidth="0" marginheight="0" + @ sandbox="allow-same-origin" + @ onload="this.height=this.contentDocument.documentElement.scrollHeight;"> + @ </iframe> + }else{ + Blob content; + const char *zMime = mimetype_from_name(zName); + content_get(rid, &content); + safe_html_context(DOCSRC_FILE); + wiki_render_by_mimetype(&content, zMime); + document_emit_js(); + } + } + } + db_finalize(&q); + style_finish_page(); +} + +/* +** Objects used by the "tree" webpage. +*/ +typedef struct FileTreeNode FileTreeNode; +typedef struct FileTree FileTree; + +/* +** A single line of the file hierarchy +*/ +struct FileTreeNode { + FileTreeNode *pNext; /* Next entry in an ordered list of them all */ + FileTreeNode *pParent; /* Directory containing this entry */ + FileTreeNode *pSibling; /* Next element in the same subdirectory */ + FileTreeNode *pChild; /* List of child nodes */ + FileTreeNode *pLastChild; /* Last child on the pChild list */ + char *zName; /* Name of this entry. The "tail" */ + char *zFullName; /* Full pathname of this entry */ + char *zUuid; /* Artifact hash of this file. May be NULL. */ + double mtime; /* Modification time for this entry */ + unsigned nFullName; /* Length of zFullName */ + unsigned iLevel; /* Levels of parent directories */ +}; + +/* +** A complete file hierarchy +*/ +struct FileTree { + FileTreeNode *pFirst; /* First line of the list */ + FileTreeNode *pLast; /* Last line of the list */ + FileTreeNode *pLastTop; /* Last top-level node */ +}; + +/* +** Add one or more new FileTreeNodes to the FileTree object so that the +** leaf object zPathname is at the end of the node list. +** +** The caller invokes this routine once for each leaf node (each file +** as opposed to each directory). This routine fills in any missing +** intermediate nodes automatically. +** +** When constructing a list of FileTreeNodes, all entries that have +** a common directory prefix must be added consecutively in order for +** the tree to be constructed properly. +*/ +static void tree_add_node( + FileTree *pTree, /* Tree into which nodes are added */ + const char *zPath, /* The full pathname of file to add */ + const char *zUuid, /* Hash of the file. Might be NULL. */ + double mtime /* Modification time for this entry */ +){ + int i; + FileTreeNode *pParent; /* Parent (directory) of the next node to insert */ + + /* Make pParent point to the most recent ancestor of zPath, or + ** NULL if there are no prior entires that are a container for zPath. + */ + pParent = pTree->pLast; + while( pParent!=0 && + ( strncmp(pParent->zFullName, zPath, pParent->nFullName)!=0 + || zPath[pParent->nFullName]!='/' ) + ){ + pParent = pParent->pParent; + } + i = pParent ? pParent->nFullName+1 : 0; + while( zPath[i] ){ + FileTreeNode *pNew; + int iStart = i; + int nByte; + while( zPath[i] && zPath[i]!='/' ){ i++; } + nByte = sizeof(*pNew) + i + 1; + if( zUuid!=0 && zPath[i]==0 ) nByte += HNAME_MAX+1; + pNew = fossil_malloc( nByte ); + memset(pNew, 0, sizeof(*pNew)); + pNew->zFullName = (char*)&pNew[1]; + memcpy(pNew->zFullName, zPath, i); + pNew->zFullName[i] = 0; + pNew->nFullName = i; + if( zUuid!=0 && zPath[i]==0 ){ + pNew->zUuid = pNew->zFullName + i + 1; + memcpy(pNew->zUuid, zUuid, strlen(zUuid)+1); + } + pNew->zName = pNew->zFullName + iStart; + if( pTree->pLast ){ + pTree->pLast->pNext = pNew; + }else{ + pTree->pFirst = pNew; + } + pTree->pLast = pNew; + pNew->pParent = pParent; + if( pParent ){ + if( pParent->pChild ){ + pParent->pLastChild->pSibling = pNew; + }else{ + pParent->pChild = pNew; + } + pNew->iLevel = pParent->iLevel + 1; + pParent->pLastChild = pNew; + }else{ + if( pTree->pLastTop ) pTree->pLastTop->pSibling = pNew; + pTree->pLastTop = pNew; + } + pNew->mtime = mtime; + while( zPath[i]=='/' ){ i++; } + pParent = pNew; + } + while( pParent && pParent->pParent ){ + if( pParent->pParent->mtime < pParent->mtime ){ + pParent->pParent->mtime = pParent->mtime; + } + pParent = pParent->pParent; + } +} + +/* Comparison function for two FileTreeNode objects. Sort first by +** mtime (larger numbers first) and then by zName (smaller names first). +** +** Return negative if pLeft<pRight. +** Return positive if pLeft>pRight. +** Return zero if pLeft==pRight. +*/ +static int compareNodes(FileTreeNode *pLeft, FileTreeNode *pRight){ + if( pLeft->mtime>pRight->mtime ) return -1; + if( pLeft->mtime<pRight->mtime ) return +1; + return fossil_stricmp(pLeft->zName, pRight->zName); +} + +/* Merge together two sorted lists of FileTreeNode objects */ +static FileTreeNode *mergeNodes(FileTreeNode *pLeft, FileTreeNode *pRight){ + FileTreeNode *pEnd; + FileTreeNode base; + pEnd = &base; + while( pLeft && pRight ){ + if( compareNodes(pLeft,pRight)<=0 ){ + pEnd = pEnd->pSibling = pLeft; + pLeft = pLeft->pSibling; + }else{ + pEnd = pEnd->pSibling = pRight; + pRight = pRight->pSibling; + } + } + if( pLeft ){ + pEnd->pSibling = pLeft; + }else{ + pEnd->pSibling = pRight; + } + return base.pSibling; +} + +/* Sort a list of FileTreeNode objects in mtime order. */ +static FileTreeNode *sortNodesByMtime(FileTreeNode *p){ + FileTreeNode *a[30]; + FileTreeNode *pX; + int i; + + memset(a, 0, sizeof(a)); + while( p ){ + pX = p; + p = pX->pSibling; + pX->pSibling = 0; + for(i=0; i<count(a)-1 && a[i]!=0; i++){ + pX = mergeNodes(a[i], pX); + a[i] = 0; + } + a[i] = mergeNodes(a[i], pX); + } + pX = 0; + for(i=0; i<count(a); i++){ + pX = mergeNodes(a[i], pX); + } + return pX; +} + +/* Sort an entire FileTreeNode tree by mtime +** +** This routine invalidates the following fields: +** +** FileTreeNode.pLastChild +** FileTreeNode.pNext +** +** Use relinkTree to reconnect the pNext pointers. +*/ +static FileTreeNode *sortTreeByMtime(FileTreeNode *p){ + FileTreeNode *pX; + for(pX=p; pX; pX=pX->pSibling){ + if( pX->pChild ) pX->pChild = sortTreeByMtime(pX->pChild); + } + return sortNodesByMtime(p); +} + +/* Reconstruct the FileTree by reconnecting the FileTreeNode.pNext +** fields in sequential order. +*/ +static void relinkTree(FileTree *pTree, FileTreeNode *pRoot){ + while( pRoot ){ + if( pTree->pLast ){ + pTree->pLast->pNext = pRoot; + }else{ + pTree->pFirst = pRoot; + } + pTree->pLast = pRoot; + if( pRoot->pChild ) relinkTree(pTree, pRoot->pChild); + pRoot = pRoot->pSibling; + } + if( pTree->pLast ) pTree->pLast->pNext = 0; +} + + +/* +** WEBPAGE: tree +** +** Show the files using a tree-view. If the ci= query parameter is present +** then show only the files for the check-in identified. If ci= is omitted, +** then show the union of files over all check-ins. +** +** The type=tree query parameter is required or else the /dir format is +** used. +** +** Query parameters: +** +** type=tree Required to prevent use of /dir format +** name=PATH Directory to display. Optional +** ci=LABEL Show only files in this check-in. Optional. +** re=REGEXP Show only files matching REGEXP. Optional. +** expand Begin with the tree fully expanded. +** nofiles Show directories (folders) only. Omit files. +** mtime Order directory elements by decreasing mtime +*/ +void page_tree(void){ + char *zD = fossil_strdup(P("name")); + int nD = zD ? strlen(zD)+1 : 0; + const char *zCI = P("ci"); + int rid = 0; + char *zUuid = 0; + Blob dirname; + Manifest *pM = 0; + double rNow = 0; + char *zNow = 0; + int useMtime = atoi(PD("mtime","0")); + int nFile = 0; /* Number of files (or folders with "nofiles") */ + int linkTrunk = 1; /* include link to "trunk" */ + int linkTip = 1; /* include link to "tip" */ + const char *zRE; /* the value for the re=REGEXP query parameter */ + const char *zObjType; /* "files" by default or "folders" for "nofiles" */ + char *zREx = ""; /* Extra parameters for path hyperlinks */ + ReCompiled *pRE = 0; /* Compiled regular expression */ + FileTreeNode *p; /* One line of the tree */ + FileTree sTree; /* The complete tree of files */ + HQuery sURI; /* Hyperlink */ + int startExpanded; /* True to start out with the tree expanded */ + int showDirOnly; /* Show directories only. Omit files */ + int nDir = 0; /* Number of directories. Used for ID attributes */ + char *zProjectName = db_get("project-name", 0); + int isSymbolicCI = 0; /* ci= is a symbolic name, not a hash prefix */ + int isBranchCI = 0; /* ci= refers to a branch name */ + char *zHeader = 0; + + if( zCI && strlen(zCI)==0 ){ zCI = 0; } + if( strcmp(PD("type","flat"),"flat")==0 ){ page_dir(); return; } + memset(&sTree, 0, sizeof(sTree)); + login_check_credentials(); + if( !g.perm.Read ){ login_needed(g.anon.Read); return; } + while( nD>1 && zD[nD-2]=='/' ){ zD[(--nD)-1] = 0; } + sqlite3_create_function(g.db, "pathelement", 2, SQLITE_UTF8, 0, + pathelementFunc, 0, 0); + url_initialize(&sURI, "tree"); + cgi_query_parameters_to_url(&sURI); + if( PB("nofiles") ){ + showDirOnly = 1; + }else{ + showDirOnly = 0; + } + style_adunit_config(ADUNIT_RIGHT_OK); + if( PB("expand") ){ + startExpanded = 1; + }else{ + startExpanded = 0; + } + + /* If a regular expression is specified, compile it */ + zRE = P("re"); + if( zRE ){ + re_compile(&pRE, zRE, 0); + zREx = mprintf("&re=%T", zRE); + } + + /* If the name= parameter is an empty string, make it a NULL pointer */ + if( zD && strlen(zD)==0 ){ zD = 0; } + + /* If a specific check-in is requested, fetch and parse it. If the + ** specific check-in does not exist, clear zCI. zCI==0 will cause all + ** files from all check-ins to be displayed. + */ + if( zCI ){ + pM = manifest_get_by_name(zCI, &rid); + if( pM ){ + int trunkRid = symbolic_name_to_rid("tag:trunk", "ci"); + linkTrunk = trunkRid && rid != trunkRid; + linkTip = rid != symbolic_name_to_rid("tip", "ci"); + zUuid = db_text(0, "SELECT uuid FROM blob WHERE rid=%d", rid); + rNow = db_double(0.0, "SELECT mtime FROM event WHERE objid=%d", rid); + zNow = db_text("", "SELECT datetime(mtime,toLocal())" + " FROM event WHERE objid=%d", rid); + isSymbolicCI = (sqlite3_strnicmp(zUuid, zCI, strlen(zCI)) != 0); + isBranchCI = branch_includes_uuid(zCI, zUuid); + Th_Store("current_checkin", zCI); + }else{ + zCI = 0; + } + } + if( zCI==0 ){ + rNow = db_double(0.0, "SELECT max(mtime) FROM event"); + zNow = db_text("", "SELECT datetime(max(mtime),toLocal()) FROM event"); + } + + assert( isSymbolicCI==0 || (zCI!=0 && zCI[0]!=0) ); + if( zD==0 ){ + if( zCI ){ + zHeader = mprintf("Top-level Files of %s", zCI); + }else{ + zHeader = mprintf("All Top-level Files"); + } + }else{ + if( zCI ){ + zHeader = mprintf("Files in %s/ of %s", zD, zCI); + }else{ + zHeader = mprintf("All File in %s/", zD); + } + } + style_header("%s", zHeader); + fossil_free(zHeader); + + /* Compute the title of the page */ + blob_zero(&dirname); + if( zD ){ + blob_append(&dirname, "within directory ", -1); + hyperlinked_path(zD, &dirname, zCI, "tree", zREx, 0); + if( zRE ) blob_appendf(&dirname, " matching \"%s\"", zRE); + style_submenu_element("Top-Level", "%s", + url_render(&sURI, "name", 0, 0, 0)); + }else if( zRE ){ + blob_appendf(&dirname, "matching \"%s\"", zRE); + } + style_submenu_binary("mtime","Sort By Time","Sort By Filename", 0); + if( zCI ){ + style_submenu_element("All", "%s", url_render(&sURI, "ci", 0, 0, 0)); + if( nD==0 && !showDirOnly ){ + style_submenu_element("File Ages", "%R/fileage?name=%T", zCI); + } + } + if( linkTrunk ){ + style_submenu_element("Trunk", "%s", + url_render(&sURI, "ci", "trunk", 0, 0)); + } + if( linkTip ){ + style_submenu_element("Tip", "%s", url_render(&sURI, "ci", "tip", 0, 0)); + } + style_submenu_element("Flat-View", "%s", + url_render(&sURI, "type", "flat", 0, 0)); + + /* Compute the file hierarchy. + */ + if( zCI ){ + Stmt q; + compute_fileage(rid, 0); + db_prepare(&q, + "SELECT filename.name, blob.uuid, fileage.mtime\n" + " FROM fileage, filename, blob\n" + " WHERE filename.fnid=fileage.fnid\n" + " AND blob.rid=fileage.fid\n" + " ORDER BY filename.name COLLATE nocase;" + ); + while( db_step(&q)==SQLITE_ROW ){ + const char *zFile = db_column_text(&q,0); + const char *zUuid = db_column_text(&q,1); + double mtime = db_column_double(&q,2); + if( nD>0 && (fossil_strncmp(zFile, zD, nD-1)!=0 || zFile[nD-1]!='/') ){ + continue; + } + if( pRE && re_match(pRE, (const unsigned char*)zFile, -1)==0 ) continue; + tree_add_node(&sTree, zFile, zUuid, mtime); + nFile++; + } + db_finalize(&q); + }else{ + Stmt q; + db_prepare(&q, + "SELECT\n" + " (SELECT name FROM filename WHERE filename.fnid=mlink.fnid),\n" + " (SELECT uuid FROM blob WHERE blob.rid=mlink.fid),\n" + " max(event.mtime)\n" + " FROM mlink JOIN event ON event.objid=mlink.mid\n" + " GROUP BY mlink.fnid\n" + " ORDER BY 1 COLLATE nocase;"); + while( db_step(&q)==SQLITE_ROW ){ + const char *zName = db_column_text(&q, 0); + const char *zUuid = db_column_text(&q,1); + double mtime = db_column_double(&q,2); + if( nD>0 && (fossil_strncmp(zName, zD, nD-1)!=0 || zName[nD-1]!='/') ){ + continue; + } + if( pRE && re_match(pRE, (const u8*)zName, -1)==0 ) continue; + tree_add_node(&sTree, zName, zUuid, mtime); + nFile++; + } + db_finalize(&q); + } + style_submenu_checkbox("nofiles", "Folders Only", 0, 0); + + if( showDirOnly ){ + for(nFile=0, p=sTree.pFirst; p; p=p->pNext){ + if( p->pChild!=0 && p->nFullName>nD ) nFile++; + } + zObjType = "Folders"; + }else{ + zObjType = "Files"; + } + + if( zCI && strcmp(zCI,"tip")==0 ){ + @ <h2>%s(zObjType) in the %z(href("%R/info?name=tip"))latest check-in</a> + }else if( isBranchCI ){ + @ <h2>%s(zObjType) in the %z(href("%R/info?name=%T",zCI))latest check-in\ + @ </a> for branch %z(href("%R/timeline?r=%T",zCI))%h(zCI)</a> + if( blob_size(&dirname) ){ + @ and %s(blob_str(&dirname))</h2> + } + }else if( zCI ){ + @ <h2>%s(zObjType) for check-in \ + @ %z(href("%R/info?name=%T",zCI))%h(zCI)</a></h2> + if( blob_size(&dirname) ){ + @ and %s(blob_str(&dirname))</h2> + } + }else{ + int n = db_int(0, "SELECT count(*) FROM plink"); + @ <h2>%s(zObjType) from all %d(n) check-ins %s(blob_str(&dirname)) + } + if( useMtime ){ + @ sorted by modification time</h2> + }else{ + @ sorted by filename</h2> + } + + + /* Generate tree of lists. + ** + ** Each file and directory is a list element: <li>. Files have class=file + ** and if the filename as the suffix "xyz" the file also has class=file-xyz. + ** Directories have class=dir. The directory specfied by the name= query + ** parameter (or the top-level directory if there is no name= query parameter) + ** adds class=subdir. + ** + ** The <li> element for directories also contains a sublist <ul> + ** for the contents of that directory. + */ + @ <div class="filetree"><ul> + if( nD ){ + @ <li class="dir last"> + }else{ + @ <li class="dir subdir last"> + } + @ <div class="filetreeline"> + @ %z(href("%s",url_render(&sURI,"name",0,0,0)))%h(zProjectName)</a> + if( zNow ){ + @ <div class="filetreeage">%s(zNow)</div> + } + @ </div> + @ <ul> + if( useMtime ){ + p = sortTreeByMtime(sTree.pFirst); + memset(&sTree, 0, sizeof(sTree)); + relinkTree(&sTree, p); + } + for(p=sTree.pFirst, nDir=0; p; p=p->pNext){ + const char *zLastClass = p->pSibling==0 ? " last" : ""; + if( p->pChild ){ + const char *zSubdirClass = p->nFullName==nD-1 ? " subdir" : ""; + @ <li class="dir%s(zSubdirClass)%s(zLastClass)"><div class="filetreeline"> + @ %z(href("%s",url_render(&sURI,"name",p->zFullName,0,0)))%h(p->zName)</a> + if( p->mtime>0.0 ){ + char *zAge = human_readable_age(rNow - p->mtime); + @ <div class="filetreeage">%s(zAge)</div> + } + @ </div> + if( startExpanded || p->nFullName<=nD ){ + @ <ul id="dir%d(nDir)"> + }else{ + @ <ul id="dir%d(nDir)" class="collapsed"> + } + nDir++; + }else if( !showDirOnly ){ + const char *zFileClass = fileext_class(p->zName); + char *zLink; + if( zCI ){ + zLink = href("%R/file?name=%T&ci=%T",p->zFullName,zCI); + }else{ + zLink = href("%R/finfo?name=%T",p->zFullName); + } + @ <li class="%z(zFileClass)%s(zLastClass)"><div class="filetreeline"> + @ %z(zLink)%h(p->zName)</a> + if( p->mtime>0 ){ + char *zAge = human_readable_age(rNow - p->mtime); + @ <div class="filetreeage">%s(zAge)</div> + } + @ </div> + } + if( p->pSibling==0 ){ + int nClose = p->iLevel - (p->pNext ? p->pNext->iLevel : 0); + while( nClose-- > 0 ){ + @ </ul> + } + } + } + @ </ul> + @ </ul></div> + builtin_request_js("tree.js"); + style_finish_page(); + + /* We could free memory used by sTree here if we needed to. But + ** the process is about to exit, so doing so would not really accomplish + ** anything useful. */ +} + +/* +** Return a CSS class name based on the given filename's extension. +** Result must be freed by the caller. +**/ +const char *fileext_class(const char *zFilename){ + char *zClass; + const char *zExt = strrchr(zFilename, '.'); + int isExt = zExt && zExt!=zFilename && zExt[1]; + int i; + for( i=1; isExt && zExt[i]; i++ ) isExt &= fossil_isalnum(zExt[i]); + if( isExt ){ + zClass = mprintf("file file-%s", zExt+1); + for( i=5; zClass[i]; i++ ) zClass[i] = fossil_tolower(zClass[i]); + }else{ + zClass = mprintf("file"); + } + return zClass; +} + +/* +** SQL used to compute the age of all files in check-in :ckin whose +** names match :glob +*/ +static const char zComputeFileAgeSetup[] = +@ CREATE TABLE IF NOT EXISTS temp.fileage( +@ fnid INTEGER PRIMARY KEY, +@ fid INTEGER, +@ mid INTEGER, +@ mtime DATETIME, +@ pathname TEXT +@ ); +@ CREATE VIRTUAL TABLE IF NOT EXISTS temp.foci USING files_of_checkin; +; + +static const char zComputeFileAgeRun[] = +@ WITH RECURSIVE +@ ckin(x) AS (VALUES(:ckin) +@ UNION +@ SELECT plink.pid +@ FROM ckin, plink +@ WHERE plink.cid=ckin.x) +@ INSERT OR IGNORE INTO fileage(fnid, fid, mid, mtime, pathname) +@ SELECT filename.fnid, mlink.fid, mlink.mid, event.mtime, filename.name +@ FROM foci, filename, blob, mlink, event +@ WHERE foci.checkinID=:ckin +@ AND foci.filename GLOB :glob +@ AND filename.name=foci.filename +@ AND blob.uuid=foci.uuid +@ AND mlink.fid=blob.rid +@ AND mlink.fid!=mlink.pid +@ AND mlink.mid IN (SELECT x FROM ckin) +@ AND event.objid=mlink.mid +@ ORDER BY event.mtime ASC; +; + +/* +** Look at all file containing in the version "vid". Construct a +** temporary table named "fileage" that contains the file-id for each +** files, the pathname, the check-in where the file was added, and the +** mtime on that check-in. If zGlob and *zGlob then only files matching +** the given glob are computed. +*/ +int compute_fileage(int vid, const char* zGlob){ + Stmt q; + db_exec_sql(zComputeFileAgeSetup); + db_prepare(&q, zComputeFileAgeRun /*works-like:"constant"*/); + db_bind_int(&q, ":ckin", vid); + db_bind_text(&q, ":glob", zGlob && zGlob[0] ? zGlob : "*"); + db_exec(&q); + db_finalize(&q); + return 0; +} + +/* +** Render the number of days in rAge as a more human-readable time span. +** Different units (seconds, minutes, hours, days, months, years) are +** selected depending on the magnitude of rAge. +** +** The string returned is obtained from fossil_malloc() and should be +** freed by the caller. +*/ +char *human_readable_age(double rAge){ + if( rAge*86400.0<120 ){ + if( rAge*86400.0<1.0 ){ + return mprintf("current"); }else{ - @ <li><a href="%s(g.zBaseURL)/finfo?name=%T(zPrefix)%T(zFN)">%h(zFN)</a> + return mprintf("%d seconds", (int)(rAge*86400.0)); } + }else if( rAge*1440.0<90 ){ + return mprintf("%.1f minutes", rAge*1440.0); + }else if( rAge*24.0<36 ){ + return mprintf("%.1f hours", rAge*24.0); + }else if( rAge<365.0 ){ + return mprintf("%.1f days", rAge); + }else{ + return mprintf("%.2f years", rAge/365.2425); + } +} + +/* +** COMMAND: test-fileage +** +** Usage: %fossil test-fileage CHECKIN +*/ +void test_fileage_cmd(void){ + int mid; + Stmt q; + const char *zGlob = find_option("glob",0,1); + db_find_and_open_repository(0,0); + verify_all_options(); + if( g.argc!=3 ) usage("CHECKIN"); + mid = name_to_typed_rid(g.argv[2],"ci"); + compute_fileage(mid, zGlob); + db_prepare(&q, + "SELECT fid, mid, julianday('now') - mtime, pathname" + " FROM fileage" + ); + while( db_step(&q)==SQLITE_ROW ){ + char *zAge = human_readable_age(db_column_double(&q,2)); + fossil_print("%8d %8d %16s %s\n", + db_column_int(&q,0), + db_column_int(&q,1), + zAge, + db_column_text(&q,3)); + fossil_free(zAge); } db_finalize(&q); - @ </td></tr></table> - style_footer(); +} + +/* +** WEBPAGE: fileage +** +** Show all files in a single check-in (identified by the name= query +** parameter) in order of increasing age. +** +** Parameters: +** name=VERSION Selects the check-in version (default=tip). +** glob=STRING Only shows files matching this glob pattern +** (e.g. *.c or *.txt). +** showid Show RID values for debugging +*/ +void fileage_page(void){ + int rid; + const char *zName; + const char *zGlob; + const char *zUuid; + const char *zNow; /* Time of check-in */ + int isBranchCI; /* name= is a branch name */ + int showId = PB("showid"); + Stmt q1, q2; + double baseTime; + login_check_credentials(); + if( !g.perm.Read ){ login_needed(g.anon.Read); return; } + if( exclude_spiders() ) return; + zName = P("name"); + if( zName==0 ) zName = "tip"; + rid = symbolic_name_to_rid(zName, "ci"); + if( rid==0 ){ + fossil_fatal("not a valid check-in: %s", zName); + } + zUuid = db_text("", "SELECT uuid FROM blob WHERE rid=%d", rid); + isBranchCI = branch_includes_uuid(zName,zUuid); + baseTime = db_double(0.0,"SELECT mtime FROM event WHERE objid=%d", rid); + zNow = db_text("", "SELECT datetime(mtime,toLocal()) FROM event" + " WHERE objid=%d", rid); + style_submenu_element("Tree-View", "%R/tree?ci=%T&mtime=1&type=tree", zName); + style_header("File Ages"); + zGlob = P("glob"); + compute_fileage(rid,zGlob); + db_multi_exec("CREATE INDEX fileage_ix1 ON fileage(mid,pathname);"); + + if( fossil_strcmp(zName,"tip")==0 ){ + @ <h1>Files in the %z(href("%R/info?name=tip"))latest check-in</a> + }else if( isBranchCI ){ + @ <h1>Files in the %z(href("%R/info?name=%T",zName))latest check-in</a> + @ of branch %z(href("%R/timeline?r=%T",zName))%h(zName)</a> + }else{ + @ <h1>Files in check-in %z(href("%R/info?name=%T",zName))%h(zName)</a> + } + if( zGlob && zGlob[0] ){ + @ that match "%h(zGlob)" + } + @ ordered by age</h1> + @ + @ <p>File ages are expressed relative to the check-in time of + @ %z(href("%R/timeline?c=%t",zNow))%s(zNow)</a>.</p> + @ + @ <div class='fileage'><table> + @ <tr><th>Age</th><th>Files</th><th>Check-in</th></tr> + db_prepare(&q1, + "SELECT event.mtime, event.objid, blob.uuid,\n" + " coalesce(event.ecomment,event.comment),\n" + " coalesce(event.euser,event.user),\n" + " coalesce((SELECT value FROM tagxref\n" + " WHERE tagtype>0 AND tagid=%d\n" + " AND rid=event.objid),'trunk')\n" + " FROM event, blob\n" + " WHERE event.objid IN (SELECT mid FROM fileage)\n" + " AND blob.rid=event.objid\n" + " ORDER BY event.mtime DESC;", + TAG_BRANCH + ); + db_prepare(&q2, + "SELECT filename.name, fileage.fid\n" + " FROM fileage, filename\n" + " WHERE fileage.mid=:mid AND filename.fnid=fileage.fnid" + ); + while( db_step(&q1)==SQLITE_ROW ){ + double age = baseTime - db_column_double(&q1, 0); + int mid = db_column_int(&q1, 1); + const char *zUuid = db_column_text(&q1, 2); + const char *zComment = db_column_text(&q1, 3); + const char *zUser = db_column_text(&q1, 4); + const char *zBranch = db_column_text(&q1, 5); + char *zAge = human_readable_age(age); + @ <tr><td>%s(zAge)</td> + @ <td> + db_bind_int(&q2, ":mid", mid); + while( db_step(&q2)==SQLITE_ROW ){ + const char *zFile = db_column_text(&q2,0); + @ %z(href("%R/file?name=%T&ci=%!S",zFile,zUuid))%h(zFile)</a> \ + if( showId ){ + int fid = db_column_int(&q2,1); + @ (%d(fid))<br /> + }else{ + @ </a><br /> + } + } + db_reset(&q2); + @ </td> + @ <td> + @ %W(zComment) + @ (check-in: %z(href("%R/info/%!S",zUuid))%S(zUuid)</a>, + if( showId ){ + @ id: %d(mid) + } + @ user: %z(href("%R/timeline?u=%t&c=%!S&nd",zUser,zUuid))%h(zUser)</a>, + @ branch: \ + @ %z(href("%R/timeline?r=%t&c=%!S&nd",zBranch,zUuid))%h(zBranch)</a>) + @ </td></tr> + @ + fossil_free(zAge); + } + @ </table></div> + db_finalize(&q1); + db_finalize(&q2); + style_finish_page(); } ADDED src/builtin.c Index: src/builtin.c ================================================================== --- /dev/null +++ src/builtin.c @@ -0,0 +1,884 @@ +/* +** 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 file contains built-in string and BLOB resources packaged as +** byte arrays. +*/ +#include "config.h" +#include "builtin.h" +#include <assert.h> + +/* +** The resources provided by this file are packaged by the "mkbuiltin.c" +** utility program during the built process and stored in the +** builtin_data.h file. Include that information here: +*/ +#include "builtin_data.h" + +/* +** Return the index in the aBuiltinFiles[] array for the file +** whose name is zFilename. Or return -1 if the file is not +** found. +*/ +static int builtin_file_index(const char *zFilename){ + int lwr, upr, i, c; + lwr = 0; + upr = count(aBuiltinFiles) - 1; + while( upr>=lwr ){ + i = (upr+lwr)/2; + c = strcmp(aBuiltinFiles[i].zName,zFilename); + if( c<0 ){ + lwr = i+1; + }else if( c>0 ){ + upr = i-1; + }else{ + return i; + } + } + return -1; +} + +/* +** Return a pointer to built-in content +*/ +const unsigned char *builtin_file(const char *zFilename, int *piSize){ + int i = builtin_file_index(zFilename); + if( i>=0 ){ + if( piSize ) *piSize = aBuiltinFiles[i].nByte; + return aBuiltinFiles[i].pData; + }else{ + if( piSize ) *piSize = 0; + return 0; + } +} +const char *builtin_text(const char *zFilename){ + return (char*)builtin_file(zFilename, 0); +} + +/* +** COMMAND: test-builtin-list +** +** If -verbose is used, it outputs a line at the end +** with the total item count and size. +** +** List the names and sizes of all built-in resources. +*/ +void test_builtin_list(void){ + int i, size = 0;; + for(i=0; i<count(aBuiltinFiles); i++){ + const int n = aBuiltinFiles[i].nByte; + fossil_print("%3d. %-45s %6d\n", i+1, aBuiltinFiles[i].zName,n); + size += n; + } + if(find_option("verbose","v",0)!=0){ + fossil_print("%d entries totaling %d bytes\n", i, size); + } +} + +/* +** WEBPAGE: test-builtin-files +** +** Show all built-in text files. +*/ +void test_builtin_list_page(void){ + int i; + style_set_current_feature("test"); + style_header("Built-in Text Files"); + @ <ol> + for(i=0; i<count(aBuiltinFiles); i++){ + const char *z = aBuiltinFiles[i].zName; + char *zUrl = href("%R/builtin?name=%T&id=%.8s&mimetype=text/plain", + z,fossil_exe_id()); + @ <li>%z(zUrl)%h(z)</a> + } + @ </ol> + style_finish_page(); +} + +/* +** COMMAND: test-builtin-get +** +** Usage: %fossil test-builtin-get NAME ?OUTPUT-FILE? +*/ +void test_builtin_get(void){ + const unsigned char *pData; + int nByte; + Blob x; + if( g.argc!=3 && g.argc!=4 ){ + usage("NAME ?OUTPUT-FILE?"); + } + pData = builtin_file(g.argv[2], &nByte); + if( pData==0 ){ + fossil_fatal("no such built-in file: [%s]", g.argv[2]); + } + blob_init(&x, (const char*)pData, nByte); + blob_write_to_file(&x, g.argc==4 ? g.argv[3] : "-"); + blob_reset(&x); +} + +/* +** Input zList is a list of numeric identifiers for files in +** aBuiltinFiles[]. Return the concatenation of all of those +** files using mimetype zType, or as application/javascript if +** zType is 0. +*/ +static void builtin_deliver_multiple_js_files( + const char *zList, /* List of numeric identifiers */ + const char *zType /* Override mimetype */ +){ + Blob *pOut; + if( zType==0 ) zType = "application/javascript"; + cgi_set_content_type(zType); + pOut = cgi_output_blob(); + while( zList[0] ){ + int i = atoi(zList); + if( i>0 && i<=count(aBuiltinFiles) ){ + blob_appendf(pOut, "/* %s */\n", aBuiltinFiles[i-1].zName); + blob_append(pOut, (const char*)aBuiltinFiles[i-1].pData, + aBuiltinFiles[i-1].nByte); + } + while( fossil_isdigit(zList[0]) ) zList++; + if( zList[0]==',' ) zList++; + } + return; +} + +/* +** WEBPAGE: builtin +** +** Return one of many built-in content files. Query parameters: +** +** name=FILENAME Return the single file whose name is FILENAME. +** mimetype=TYPE Override the mimetype in the returned file to +** be TYPE. If this query parameter is omitted +** (the usual case) then the mimetype is inferred +** from the suffix on FILENAME +** m=IDLIST IDLIST is a comma-separated list of integers +** that specify multiple javascript files to be +** concatenated and returned all at once. +** id=UNIQUEID Version number of the "builtin" files. Used +** for cache control only. +** +** At least one of the name= or m= query parameters must be present. +** +** If the id= query parameter is present, then Fossil assumes that the +** result is immutable and sets a very large cache retention time (1 year). +*/ +void builtin_webpage(void){ + Blob out; + const char *zName = P("name"); + const char *zContent = 0; + int nContent = 0; + const char *zId = P("id"); + const char *zType = P("mimetype"); + int nId; + if( zName ) zContent = (const char *)builtin_file(zName, &nContent); + if( zContent==0 ){ + const char *zM = P("m"); + if( zM ){ + if( zId && (nId = (int)strlen(zId))>=8 + && strncmp(zId,fossil_exe_id(),nId)==0 + ){ + g.isConst = 1; + } + etag_check(0,0); + builtin_deliver_multiple_js_files(zM, zType); + return; + } + cgi_set_status(404, "Not Found"); + @ File "%h(zName)" not found + return; + } + if( zType==0 ){ + if( sqlite3_strglob("*.js", zName)==0 ){ + zType = "application/javascript"; + }else{ + zType = mimetype_from_name(zName); + } + } + cgi_set_content_type(zType); + if( zId + && (nId = (int)strlen(zId))>=8 + && strncmp(zId,fossil_exe_id(),nId)==0 + ){ + g.isConst = 1; + } + etag_check(0,0); + blob_init(&out, zContent, nContent); + cgi_set_content(&out); +} + +/* Variables controlling the JS cache. +*/ +static struct { + int aReq[30]; /* Indexes of all requested built-in JS files */ + int nReq; /* Number of slots in aReq[] currently used */ + int nSent; /* Number of slots in aReq[] fulfilled */ + int eDelivery; /* Delivery mechanism */ +} builtin; + +#if INTERFACE +/* Various delivery mechanisms. The 0 option is the default. +*/ +#define JS_INLINE 0 /* inline, batched together at end of file */ +#define JS_SEPARATE 1 /* Separate HTTP request for each JS file */ +#define JS_BUNDLED 2 /* One HTTP request to load all JS files */ + /* concatenated together into a bundle */ +#endif /* INTERFACE */ + +/* +** The argument is a request to change the javascript delivery mode. +** The argument is a string which is a command-line option or CGI +** parameter. Try to match it against one of the delivery options +** and set things up accordingly. Throw an error if no match unless +** bSilent is true. +*/ +void builtin_set_js_delivery_mode(const char *zMode, int bSilent){ + if( zMode==0 ) return; + if( strcmp(zMode, "inline")==0 ){ + builtin.eDelivery = JS_INLINE; + }else + if( strcmp(zMode, "separate")==0 ){ + builtin.eDelivery = JS_SEPARATE; + }else + if( strcmp(zMode, "bundled")==0 ){ + builtin.eDelivery = JS_BUNDLED; + }else if( !bSilent ){ + fossil_fatal("unknown javascript delivery mode \"%s\" - should be" + " one of: inline separate bundled", zMode); + } +} + +/* +** Returns the current JS delivery mode: one of JS_INLINE, +** JS_SEPARATE, JS_BUNDLED. +*/ +int builtin_get_js_delivery_mode(void){ + return builtin.eDelivery; +} + +/* +** The caller wants the Javascript file named by zFilename to be +** included in the generated page. Add the file to the queue of +** requested javascript resources, if it is not there already. +** +** The current implementation queues the file to be included in the +** output later. However, the caller should not depend on that +** behavior. In the future, this routine might decide to insert +** the requested javascript inline, immedaitely, or to insert +** a <script src=..> element to reference the javascript as a +** separate resource. The exact behavior might change in the future +** so pages that use this interface must not rely on any particular +** behavior. +** +** All this routine guarantees is that the named javascript file +** will be requested by the browser at some point. This routine +** does not guarantee when the javascript will be included, and it +** does not guarantee whether the javascript will be added inline or +** delivered as a separate resource. +*/ +void builtin_request_js(const char *zFilename){ + int i = builtin_file_index(zFilename); + int j; + if( i<0 ){ + fossil_panic("unknown javascript file: \"%s\"", zFilename); + } + for(j=0; j<builtin.nReq; j++){ + if( builtin.aReq[j]==i ) return; /* Already queued or sent */ + } + if( builtin.nReq>=count(builtin.aReq) ){ + fossil_panic("too many javascript files requested"); + } + builtin.aReq[builtin.nReq++] = i; +} + +/* +** Fulfill all pending requests for javascript files. +** +** The current implementation delivers all javascript in-line. However, +** the caller should not depend on this. Future changes to this routine +** might choose to deliver javascript as separate resources. +*/ +void builtin_fulfill_js_requests(void){ + if( builtin.nSent>=builtin.nReq ) return; /* nothing to do */ + switch( builtin.eDelivery ){ + case JS_INLINE: { + CX("<script nonce='%h'>\n",style_nonce()); + do{ + int i = builtin.aReq[builtin.nSent++]; + CX("/* %s %.60c*/\n", aBuiltinFiles[i].zName, '*'); + cgi_append_content((const char*)aBuiltinFiles[i].pData, + aBuiltinFiles[i].nByte); + }while( builtin.nSent<builtin.nReq ); + CX("</script>\n"); + break; + } + case JS_BUNDLED: { + if( builtin.nSent+1<builtin.nReq ){ + Blob aList; + blob_init(&aList,0,0); + while( builtin.nSent<builtin.nReq ){ + blob_appendf(&aList, ",%d", builtin.aReq[builtin.nSent++]+1); + } + CX("<script src='%R/builtin?m=%s&id=%.8s'></script>\n", + blob_str(&aList)+1, fossil_exe_id()); + blob_reset(&aList); + break; + } + /* If there is only one JS file, fall through into the + ** JS_SEPARATE case below. */ + /*FALLTHROUGH*/ + } + case JS_SEPARATE: { + /* Each JS file as a separate resource */ + while( builtin.nSent<builtin.nReq ){ + int i = builtin.aReq[builtin.nSent++]; + CX("<script src='%R/builtin?name=%t&id=%.8s'></script>\n", + aBuiltinFiles[i].zName, fossil_exe_id()); + } + break; + } + } +} + +/***************************************************************************** +** A virtual table for accessing the information in aBuiltinFiles[]. +*/ + +/* builtinVtab_vtab is a subclass of sqlite3_vtab which is +** underlying representation of the virtual table +*/ +typedef struct builtinVtab_vtab builtinVtab_vtab; +struct builtinVtab_vtab { + sqlite3_vtab base; /* Base class - must be first */ + /* Add new fields here, as necessary */ +}; + +/* builtinVtab_cursor is a subclass of sqlite3_vtab_cursor which will +** serve as the underlying representation of a cursor that scans +** over rows of the result +*/ +typedef struct builtinVtab_cursor builtinVtab_cursor; +struct builtinVtab_cursor { + sqlite3_vtab_cursor base; /* Base class - must be first */ + /* Insert new fields here. For this builtinVtab we only keep track + ** of the rowid */ + sqlite3_int64 iRowid; /* The rowid */ +}; + +/* +** The builtinVtabConnect() method is invoked to create a new +** builtin virtual table. +** +** Think of this routine as the constructor for builtinVtab_vtab objects. +** +** All this routine needs to do is: +** +** (1) Allocate the builtinVtab_vtab object and initialize all fields. +** +** (2) Tell SQLite (via the sqlite3_declare_vtab() interface) what the +** result set of queries against the virtual table will look like. +*/ +static int builtinVtabConnect( + sqlite3 *db, + void *pAux, + int argc, const char *const*argv, + sqlite3_vtab **ppVtab, + char **pzErr +){ + builtinVtab_vtab *pNew; + int rc; + + rc = sqlite3_declare_vtab(db, + "CREATE TABLE x(name,size,data)" + ); + if( rc==SQLITE_OK ){ + pNew = sqlite3_malloc( sizeof(*pNew) ); + *ppVtab = (sqlite3_vtab*)pNew; + if( pNew==0 ) return SQLITE_NOMEM; + memset(pNew, 0, sizeof(*pNew)); + } + return rc; +} + +/* +** This method is the destructor for builtinVtab_vtab objects. +*/ +static int builtinVtabDisconnect(sqlite3_vtab *pVtab){ + builtinVtab_vtab *p = (builtinVtab_vtab*)pVtab; + sqlite3_free(p); + return SQLITE_OK; +} + +/* +** Constructor for a new builtinVtab_cursor object. +*/ +static int builtinVtabOpen(sqlite3_vtab *p, sqlite3_vtab_cursor **ppCursor){ + builtinVtab_cursor *pCur; + pCur = sqlite3_malloc( sizeof(*pCur) ); + if( pCur==0 ) return SQLITE_NOMEM; + memset(pCur, 0, sizeof(*pCur)); + *ppCursor = &pCur->base; + return SQLITE_OK; +} + +/* +** Destructor for a builtinVtab_cursor. +*/ +static int builtinVtabClose(sqlite3_vtab_cursor *cur){ + builtinVtab_cursor *pCur = (builtinVtab_cursor*)cur; + sqlite3_free(pCur); + return SQLITE_OK; +} + + +/* +** Advance a builtinVtab_cursor to its next row of output. +*/ +static int builtinVtabNext(sqlite3_vtab_cursor *cur){ + builtinVtab_cursor *pCur = (builtinVtab_cursor*)cur; + pCur->iRowid++; + return SQLITE_OK; +} + +/* +** Return values of columns for the row at which the builtinVtab_cursor +** is currently pointing. +*/ +static int builtinVtabColumn( + sqlite3_vtab_cursor *cur, /* The cursor */ + sqlite3_context *ctx, /* First argument to sqlite3_result_...() */ + int i /* Which column to return */ +){ + builtinVtab_cursor *pCur = (builtinVtab_cursor*)cur; + const struct BuiltinFileTable *pFile = aBuiltinFiles + pCur->iRowid; + switch( i ){ + case 0: /* name */ + sqlite3_result_text(ctx, pFile->zName, -1, SQLITE_STATIC); + break; + case 1: /* size */ + sqlite3_result_int(ctx, pFile->nByte); + break; + case 2: /* data */ + sqlite3_result_blob(ctx, pFile->pData, pFile->nByte, SQLITE_STATIC); + break; + } + return SQLITE_OK; +} + +/* +** Return the rowid for the current row. In this implementation, the +** rowid is the same as the output value. +*/ +static int builtinVtabRowid(sqlite3_vtab_cursor *cur, sqlite_int64 *pRowid){ + builtinVtab_cursor *pCur = (builtinVtab_cursor*)cur; + *pRowid = pCur->iRowid; + return SQLITE_OK; +} + +/* +** Return TRUE if the cursor has been moved off of the last +** row of output. +*/ +static int builtinVtabEof(sqlite3_vtab_cursor *cur){ + builtinVtab_cursor *pCur = (builtinVtab_cursor*)cur; + return pCur->iRowid>=count(aBuiltinFiles); +} + +/* +** This method is called to "rewind" the builtinVtab_cursor object back +** to the first row of output. This method is always called at least +** once prior to any call to builtinVtabColumn() or builtinVtabRowid() or +** builtinVtabEof(). +*/ +static int builtinVtabFilter( + sqlite3_vtab_cursor *pVtabCursor, + int idxNum, const char *idxStr, + int argc, sqlite3_value **argv +){ + builtinVtab_cursor *pCur = (builtinVtab_cursor *)pVtabCursor; + pCur->iRowid = 1; + return SQLITE_OK; +} + +/* +** SQLite will invoke this method one or more times while planning a query +** that uses the virtual table. This routine needs to create +** a query plan for each invocation and compute an estimated cost for that +** plan. +*/ +static int builtinVtabBestIndex( + sqlite3_vtab *tab, + sqlite3_index_info *pIdxInfo +){ + pIdxInfo->estimatedCost = (double)count(aBuiltinFiles); + pIdxInfo->estimatedRows = count(aBuiltinFiles); + return SQLITE_OK; +} + +/* +** This following structure defines all the methods for the +** virtual table. +*/ +static sqlite3_module builtinVtabModule = { + /* iVersion */ 0, + /* xCreate */ 0, /* The builtin vtab is eponymous and read-only */ + /* xConnect */ builtinVtabConnect, + /* xBestIndex */ builtinVtabBestIndex, + /* xDisconnect */ builtinVtabDisconnect, + /* xDestroy */ 0, + /* xOpen */ builtinVtabOpen, + /* xClose */ builtinVtabClose, + /* xFilter */ builtinVtabFilter, + /* xNext */ builtinVtabNext, + /* xEof */ builtinVtabEof, + /* xColumn */ builtinVtabColumn, + /* xRowid */ builtinVtabRowid, + /* xUpdate */ 0, + /* xBegin */ 0, + /* xSync */ 0, + /* xCommit */ 0, + /* xRollback */ 0, + /* xFindMethod */ 0, + /* xRename */ 0, + /* xSavepoint */ 0, + /* xRelease */ 0, + /* xRollbackTo */ 0, + /* xShadowName */ 0 +}; + + +/* +** Register the builtin virtual table +*/ +int builtin_vtab_register(sqlite3 *db){ + int rc = sqlite3_create_module(db, "builtin", &builtinVtabModule, 0); + return rc; +} +/* End of the builtin virtual table +******************************************************************************/ + + +/* +** The first time this is called, it emits code to install and +** bootstrap the window.fossil object, using the built-in file +** fossil.bootstrap.js (not to be confused with bootstrap.js). +** +** Subsequent calls are no-ops. +** +** It emits 2 parts: +** +** 1) window.fossil core object, some of which depends on C-level +** runtime data. That part of the script is always emitted inline. If +** addScriptTag is true then it is wrapped in its own SCRIPT tag, else +** it is assumed that the caller already opened a tag. +** +** 2) Emits the static fossil.bootstrap.js using builtin_request_js(). +*/ +void builtin_emit_script_fossil_bootstrap(int addScriptTag){ + static int once = 0; + if(0==once++){ + char * zName; + /* Set up the generic/app-agnostic parts of window.fossil + ** which require C-level state... */ + if(addScriptTag!=0){ + style_script_begin(__FILE__,__LINE__); + } + CX("(function(){\n"); + CX(/*MSIE NodeList.forEach polyfill, courtesy of Mozilla: + https://developer.mozilla.org/en-US/docs/Web/API/NodeList/forEach#Polyfill + */ + "if(window.NodeList && !NodeList.prototype.forEach){" + "NodeList.prototype.forEach = Array.prototype.forEach;" + "}\n"); + CX("if(!window.fossil) window.fossil={};\n" + "window.fossil.version = %!j;\n" + /* fossil.rootPath is the top-most CGI/server path, + ** including a trailing slash. */ + "window.fossil.rootPath = %!j+'/';\n", + get_version(), g.zTop); + /* fossil.config = {...various config-level options...} */ + CX("window.fossil.config = {"); + zName = db_get("project-name", ""); + CX("projectName: %!j,\n", zName); + fossil_free(zName); + zName = db_get("short-project-name", ""); + CX("shortProjectName: %!j,\n", zName); + fossil_free(zName); + zName = db_get("project-code", ""); + CX("projectCode: %!j,\n", zName); + fossil_free(zName); + CX("/* Length of UUID hashes for display purposes. */"); + CX("hashDigits: %d, hashDigitsUrl: %d,\n", + hash_digits(0), hash_digits(1)); + CX("editStateMarkers: {" + "/*Symbolic markers to denote certain edit states.*/" + "isNew:'[+]', isModified:'[*]', isDeleted:'[-]'},\n"); + CX("confirmerButtonTicks: 3 " + "/*default fossil.confirmer tick count.*/,\n"); + /* Inject certain info about the current skin... */ + CX("skin:{"); + /* can leak a local filesystem path: + CX("name: %!j,", skin_in_use());*/ + CX("isDark: %s" + "/*true if the current skin has the 'white-foreground' detail*/", + skin_detail_boolean("white-foreground") ? "true" : "false"); + CX("}\n"/*fossil.config.skin*/); + CX("};\n"/* fossil.config */); + CX("window.fossil.user = {"); + CX("name: %!j,", (g.zLogin&&*g.zLogin) ? g.zLogin : "guest"); + CX("isAdmin: %s", (g.perm.Admin || g.perm.Setup) ? "true" : "false"); + CX("};\n"/*fossil.user*/); + CX("if(fossil.config.skin.isDark) " + "document.body.classList.add('fossil-dark-style');\n"); +#if 0 + /* Is it safe to emit the CSRF token here? Some pages add it + ** as a hidden form field. */ + if(g.zCsrfToken[0]!=0){ + CX("window.fossil.csrfToken = %!j;\n", + g.zCsrfToken); + } +#endif + /* + ** fossil.page holds info about the current page. This is also + ** where the current page "should" store any of its own + ** page-specific state, and it is reserved for that purpose. + */ + CX("window.fossil.page = {" + "name:\"%T\"" + "};\n", g.zPath); + CX("})();\n"); + if(addScriptTag!=0){ + style_script_end(); + } + /* The remaining window.fossil bootstrap code is not dependent on + ** C-runtime state... */ + builtin_request_js("fossil.bootstrap.js"); + } +} + +/* +** Given the NAME part of fossil.NAME.js, this function checks whether +** that module has been emitted by this function before. If it has, +** it returns -1 with no side effects. If it has not, it queues up +** (via builtin_request_js()) an emit of the module via and all of its +** known (by this function) fossil.XYZ.js dependencies (in their +** dependency order) and returns 1. If it does not find the given +** module name it returns 0. +** +** As a special case, if passed 0 then it queues up all known modules +** and returns -1. +** +** The very first time this is called, it unconditionally calls +** builtin_emit_script_fossil_bootstrap(). +** +** Any given module is only queued once, whether it is explicitly +** passed to the function or resolved as a dependency. Any attempts to +** re-queue them later are harmless no-ops. +*/ +static int builtin_emit_fossil_js_once(const char * zName){ + static int once = 0; + int i; + static struct FossilJs { + const char * zName; /* NAME part of fossil.NAME.js */ + int emitted; /* True if already emitted. */ + const char * zDeps; /* \0-delimited list of other FossilJs + ** entries: all known deps of this one. Each + ** REQUIRES an EXPLICIT trailing \0, including + ** the final one! */ + } fjs[] = { + /* This list ordering isn't strictly important. */ + {"confirmer", 0, 0}, + {"copybutton", 0, "dom\0"}, + {"dom", 0, 0}, + {"fetch", 0, 0}, + {"info-diff", 0, "dom\0"}, + {"numbered-lines", 0, "popupwidget\0copybutton\0"}, + {"pikchr", 0, "dom\0"}, + {"popupwidget", 0, "dom\0"}, + {"storage", 0, 0}, + {"tabs", 0, "dom\0"} + }; + const int nFjs = sizeof(fjs) / sizeof(fjs[0]); + if(0==once){ + ++once; + builtin_emit_script_fossil_bootstrap(1); + } + if(0==zName){ + for( i = 0; i < nFjs; ++i ){ + builtin_emit_fossil_js_once(fjs[i].zName); + } + return -1; + } + for( i = 0; i < nFjs; ++i ){ + if(0==strcmp(zName, fjs[i].zName)){ + if(fjs[i].emitted){ + return -1; + }else{ + char nameBuffer[50]; + if(fjs[i].zDeps){ + const char * zDep = fjs[i].zDeps; + while(*zDep!=0){ + builtin_emit_fossil_js_once(zDep); + zDep += strlen(zDep)+1/*NUL delimiter*/; + } + } + sqlite3_snprintf(sizeof(nameBuffer)-1, nameBuffer, + "fossil.%s.js", fjs[i].zName); + builtin_request_js(nameBuffer); + fjs[i].emitted = 1; + return 1; + } + } + } + return 0; +} + +/* +** COMMAND: test-js-once +** +** Tester for builtin_emit_fossil_js_once(). +** +** Usage: %fossil test-js-once filename +*/ +void test_js_once(void){ + int i; + if(g.argc<2){ + usage("?FILENAME...?"); + } + if(2==g.argc){ + builtin_emit_fossil_js_once(0); + assert(builtin.nReq>8); + }else{ + for(i = 2; i < g.argc; ++i){ + builtin_emit_fossil_js_once(g.argv[i]); + } + assert(builtin.nReq>1 && "don't forget implicit fossil.bootstrap.js"); + } + for(i = 0; i < builtin.nReq; ++i){ + fossil_print("ndx#%d = %d = %s\n", i, builtin.aReq[i], + aBuiltinFiles[builtin.aReq[i]].zName); + } +} + +/* +** Convenience wrapper which calls builtin_request_js() for a series +** of builtin scripts named fossil.NAME.js. The first time it is +** called, it also calls builtin_emit_script_fossil_bootstrap() to +** initialize the window.fossil JS API. The first argument is the NAME +** part of the first API to emit. All subsequent arguments must be +** strings of the NAME part of additional fossil.NAME.js files, +** followed by a NULL argument to terminate the list. +** +** e.g. pass it ("fetch", "dom", "tabs", NULL) to load those 3 APIs (or +** pass it ("fetch","tabs",NULL), as "dom" is a dependency of "tabs", so +** it will be automatically loaded). Do not forget the trailing NULL, +** and do not pass 0 instead, since that isn't always equivalent to NULL +** in this context. +** +** If it is JS_BUNDLED then this routine queues up an emit of ALL of +** the JS fossil.XYZ.js APIs which are not strictly specific to a +** single page, and then calls builtin_fulfill_js_requests(). The idea +** is that we can get better bundle caching and reduced HTTP requests +** by including all JS, rather than creating separate bundles on a +** per-page basis. In this case, all arguments are ignored! +** +** This function has an internal mapping of the dependencies for each +** of the known fossil.XYZ.js modules and ensures that the +** dependencies also get queued (recursively) and that each module is +** queued only once. +** +** If passed a name which is not a base fossil module name then it +** will fail fatally! +** +** DO NOT use this for loading fossil.page.*.js: use +** builtin_request_js() for those. +** +** If the current JS delivery mode is *not* JS_BUNDLED then this +** function queues up a request for each given module and its known +** dependencies, but does not immediately fulfill the request, thus it +** can be called multiple times. +** +** If a given module is ever passed to this more than once, either in +** a single invocation or multiples, it is only queued for emit a +** single time (i.e. the 2nd and subsequent ones become +** no-ops). Likewise, if a module is requested but was already +** automatically queued to fulfill a dependency, the explicit request +** becomes a no-op. +** +** Bundled mode is the only mode in which this API greatly improves +** aggregate over-the-wire and HTTP request costs. For other modes, +** reducing the inclusion of fossil.XYZ APIs to their bare minimum +** provides the lowest aggregate costs. For debate and details, see +** the discussion at: +** +** https://fossil-scm.org/forum/forumpost/3fa2633f3e +** +** In practice it is normally necessary (or preferred) to call +** builtin_fulfill_js_requests() after calling this, before proceeding +** to call builtin_request_js() for page-specific JS, in order to +** improve cachability. +** +** Minor caveat: the purpose of emitting all of the fossil.XYZ JS APIs +** at once is to reduce over-the-wire transfers by enabling cross-page +** caching, but if there are other JS scripts pending via +** builtin_request_js() when this is called then they will be included +** in the JS request emitted by this routine, resulting in a different +** script URL than if they were not included. Thus, if a given page +** has its own scripts to install via builtin_request_js(), they +** should, if possible, be delayed until after this is called OR the +** page should call builtin_fulfill_js_requests() to flush the request +** queue before calling this routine. +** +** Achtung: the fossil.page.XYZ.js files are page-specific, containing +** the app-level logic for that specific page, and loading more than +** one of them in a single page will break that page. Each of those +** expects to "own" the page it is loaded in, and it should be loaded +** as late in the JS-loading process as feasible, ideally bundled (via +** builtin_request_js()) with any other app-/page-specific JS it may +** need. +** +** Example usage: +** +** builtin_fossil_js_bundle_or("dom", "fetch", NULL); +** +** In bundled mode, that will (the first time it is called) emit all +** builtin fossil JS APIs and "fulfill" the queue immediately. In +** non-bundled mode it will queue up the "dom" and "fetch" APIs to be +** emitted the next time builtin_fulfill_js_requests() is called. +*/ +NULL_SENTINEL void builtin_fossil_js_bundle_or( const char * zApi, ... ) { + static int bundled = 0; + const char *zArg; + va_list vargs; + + if(JS_BUNDLED == builtin_get_js_delivery_mode()){ + if(!bundled){ + bundled = 1; + builtin_emit_fossil_js_once(0); + builtin_fulfill_js_requests(); + } + return; + } + va_start(vargs,zApi); + for( zArg = zApi; zArg!=NULL; (zArg = va_arg (vargs, const char *))){ + if(0==builtin_emit_fossil_js_once(zArg)){ + fossil_fatal("Unknown fossil JS module: %s\n", zArg); + } + } + va_end(vargs); +} ADDED src/bundle.c Index: src/bundle.c ================================================================== --- /dev/null +++ src/bundle.c @@ -0,0 +1,799 @@ +/* +** 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 file contains code used to implement and manage a "bundle" file. +*/ +#include "config.h" +#include "bundle.h" +#include <assert.h> + +/* +** SQL code used to initialize the schema of a bundle. +** +** The bblob.delta field can be an integer, a text string, or NULL. +** If an integer, then the corresponding blobid is the delta basis. +** If a text string, then that string is a SHA1 hash for the delta +** basis, which is presumably in the main repository. If NULL, then +** data contains content without delta compression. +*/ +static const char zBundleInit[] = +@ CREATE TABLE IF NOT EXISTS "%w".bconfig( +@ bcname TEXT, +@ bcvalue ANY +@ ); +@ CREATE TABLE IF NOT EXISTS "%w".bblob( +@ blobid INTEGER PRIMARY KEY, -- Blob ID +@ uuid TEXT NOT NULL, -- hash of expanded blob +@ sz INT NOT NULL, -- Size of blob after expansion +@ delta ANY, -- Delta compression basis, or NULL +@ notes TEXT, -- Description of content +@ data BLOB -- compressed content +@ ); +; + +/* +** Attach a bundle file to the current database connection using the +** attachment name zBName. +*/ +static void bundle_attach_file( + const char *zFile, /* Name of the file that contains the bundle */ + const char *zBName, /* Attachment name */ + int doInit /* Initialize a new bundle, if true */ +){ + int rc; + char *zErrMsg = 0; + char *zSql; + if( !doInit && file_size(zFile, ExtFILE)<0 ){ + fossil_fatal("no such file: %s", zFile); + } + assert( g.db ); + zSql = sqlite3_mprintf("ATTACH %Q AS %Q", zFile, zBName); + if( zSql==0 ) fossil_fatal("out of memory"); + rc = sqlite3_exec(g.db, zSql, 0, 0, &zErrMsg); + sqlite3_free(zSql); + if( rc!=SQLITE_OK || zErrMsg ){ + if( zErrMsg==0 ) zErrMsg = (char*)sqlite3_errmsg(g.db); + fossil_fatal("not a valid bundle: %s", zFile); + } + if( doInit ){ + db_multi_exec(zBundleInit /*works-like:"%w%w"*/, zBName, zBName); + }else{ + sqlite3_stmt *pStmt; + zSql = sqlite3_mprintf("SELECT bcname, bcvalue" + " FROM \"%w\".bconfig", zBName); + if( zSql==0 ) fossil_fatal("out of memory"); + rc = sqlite3_prepare(g.db, zSql, -1, &pStmt, 0); + if( rc ) fossil_fatal("not a valid bundle: %s", zFile); + sqlite3_free(zSql); + sqlite3_finalize(pStmt); + zSql = sqlite3_mprintf("SELECT blobid, uuid, sz, delta, notes, data" + " FROM \"%w\".bblob", zBName); + if( zSql==0 ) fossil_fatal("out of memory"); + rc = sqlite3_prepare(g.db, zSql, -1, &pStmt, 0); + if( rc ) fossil_fatal("not a valid bundle: %s", zFile); + sqlite3_free(zSql); + sqlite3_finalize(pStmt); + } +} + +/* +** fossil bundle ls BUNDLE ?OPTIONS? +** +** Display the content of a bundle in human-readable form. +*/ +static void bundle_ls_cmd(void){ + Stmt q; + sqlite3_int64 sumSz = 0; + sqlite3_int64 sumLen = 0; + int bDetails = find_option("details","l",0)!=0; + verify_all_options(); + if( g.argc!=4 ) usage("ls BUNDLE ?OPTIONS?"); + bundle_attach_file(g.argv[3], "b1", 0); + db_prepare(&q, + "SELECT bcname, bcvalue FROM bconfig" + " WHERE typeof(bcvalue)='text'" + " AND bcvalue NOT GLOB char(0x2a,0x0a,0x2a);" + ); + while( db_step(&q)==SQLITE_ROW ){ + fossil_print("%s: %s\n", db_column_text(&q,0), db_column_text(&q,1)); + } + db_finalize(&q); + fossil_print("%.78c\n",'-'); + if( bDetails ){ + db_prepare(&q, + "SELECT blobid, substr(uuid,1,10), coalesce(substr(delta,1,10),'')," + " sz, length(data), notes" + " FROM bblob" + ); + while( db_step(&q)==SQLITE_ROW ){ + fossil_print("%4d %10s %10s %8d %8d %s\n", + db_column_int(&q,0), + db_column_text(&q,1), + db_column_text(&q,2), + db_column_int(&q,3), + db_column_int(&q,4), + db_column_text(&q,5)); + sumSz += db_column_int(&q,3); + sumLen += db_column_int(&q,4); + } + db_finalize(&q); + fossil_print("%27s %8lld %8lld\n", "Total:", sumSz, sumLen); + }else{ + db_prepare(&q, + "SELECT substr(uuid,1,16), notes FROM bblob" + ); + while( db_step(&q)==SQLITE_ROW ){ + fossil_print("%16s %s\n", + db_column_text(&q,0), + db_column_text(&q,1)); + } + db_finalize(&q); + } +} + +/* +** Implement the "fossil bundle append BUNDLE FILE..." command. Add +** the named files into the BUNDLE. Create the BUNDLE if it does not +** alraedy exist. +*/ +static void bundle_append_cmd(void){ + Blob content, hash; + int i; + Stmt q; + + verify_all_options(); + bundle_attach_file(g.argv[3], "b1", 1); + db_prepare(&q, + "INSERT INTO bblob(blobid, uuid, sz, delta, data, notes) " + "VALUES(NULL, $uuid, $sz, NULL, $data, $filename)"); + db_begin_transaction(); + for(i=4; i<g.argc; i++){ + int sz; + blob_read_from_file(&content, g.argv[i], ExtFILE); + sz = blob_size(&content); + sha1sum_blob(&content, &hash); + blob_compress(&content, &content); + db_bind_text(&q, "$uuid", blob_str(&hash)); + db_bind_int(&q, "$sz", sz); + db_bind_blob(&q, "$data", &content); + db_bind_text(&q, "$filename", g.argv[i]); + db_step(&q); + db_reset(&q); + blob_reset(&content); + blob_reset(&hash); + } + db_end_transaction(0); + db_finalize(&q); +} + +/* +** Identify a subsection of the check-in tree using command-line switches. +** There must be one of the following switch available: +** +** --branch BRANCHNAME All check-ins on the most recent +** instance of BRANCHNAME +** --from TAG1 [--to TAG2] Check-in TAG1 and all primary descendants +** up to and including TAG2 +** --checkin TAG Check-in TAG only +** +** Store the RIDs for all applicable check-ins in the zTab table that +** should already exist. Invoke fossil_fatal() if any kind of error is +** seen. +*/ +void subtree_from_arguments(const char *zTab){ + const char *zBr; + const char *zFrom; + const char *zTo; + const char *zCkin; + int rid = 0, endRid; + + zBr = find_option("branch",0,1); + zFrom = find_option("from",0,1); + zTo = find_option("to",0,1); + zCkin = find_option("checkin",0,1); + if( zCkin ){ + if( zFrom ) fossil_fatal("cannot use both --checkin and --from"); + if( zBr ) fossil_fatal("cannot use both --checkin and --branch"); + rid = symbolic_name_to_rid(zCkin, "ci"); + endRid = rid; + }else{ + endRid = zTo ? name_to_typed_rid(zTo, "ci") : 0; + } + if( zFrom ){ + rid = name_to_typed_rid(zFrom, "ci"); + }else if( zBr ){ + rid = name_to_typed_rid(zBr, "br"); + }else if( zCkin==0 ){ + fossil_fatal("need one of: --branch, --from, --checkin"); + } + db_multi_exec("INSERT OR IGNORE INTO \"%w\" VALUES(%d)", zTab, rid); + if( rid!=endRid ){ + Blob sql; + blob_zero(&sql); + blob_appendf(&sql, + "WITH RECURSIVE child(rid) AS (VALUES(%d) UNION ALL " + " SELECT cid FROM plink, child" + " WHERE plink.pid=child.rid" + " AND plink.isPrim", rid); + if( endRid>0 ){ + double endTime = db_double(0.0, "SELECT mtime FROM event WHERE objid=%d", + endRid); + blob_appendf(&sql, + " AND child.rid!=%d" + " AND (SELECT mtime FROM event WHERE objid=plink.cid)<=%.17g", + endRid, endTime + ); + } + if( zBr ){ + blob_appendf(&sql, + " AND EXISTS(SELECT 1 FROM tagxref" + " WHERE tagid=%d AND tagtype>0" + " AND value=%Q and rid=plink.cid)", + TAG_BRANCH, zBr); + } + blob_appendf(&sql, ") INSERT OR IGNORE INTO \"%w\" SELECT rid FROM child;", + zTab); + db_multi_exec("%s", blob_str(&sql)/*safe-for-%s*/); + } +} + +/* +** COMMAND: test-subtree +** +** Usage: %fossil test-subtree ?OPTIONS? +** +** Show the subset of check-ins that match the supplied options. This +** command is used to test the subtree_from_options() subroutine in the +** implementation and does not really have any other practical use that +** we know of. +** +** Options: +** --branch BRANCH Include only check-ins on BRANCH +** --from TAG Start the subtree at TAG +** --to TAG End the subtree at TAG +** --checkin TAG The subtree is the single check-in TAG +** --all Include FILE and TAG artifacts +** --exclusive Include FILES exclusively on check-ins +*/ +void test_subtree_cmd(void){ + int bAll = find_option("all",0,0)!=0; + int bExcl = find_option("exclusive",0,0)!=0; + db_find_and_open_repository(0,0); + db_begin_transaction(); + db_multi_exec("CREATE TEMP TABLE tobundle(rid INTEGER PRIMARY KEY);"); + subtree_from_arguments("tobundle"); + verify_all_options(); + if( bAll ) find_checkin_associates("tobundle",bExcl); + describe_artifacts_to_stdout("IN tobundle", 0); + db_end_transaction(1); +} + +/* fossil bundle export BUNDLE ?OPTIONS? +** +** OPTIONS: +** --branch BRANCH --from TAG --to TAG +** --checkin TAG +** --standalone +*/ +static void bundle_export_cmd(void){ + int bStandalone = find_option("standalone",0,0)!=0; + int mnToBundle; /* Minimum RID in the bundle */ + Stmt q; + + /* Decode the arguments (like --branch) that specify which artifacts + ** should be in the bundle */ + db_multi_exec("CREATE TEMP TABLE tobundle(rid INTEGER PRIMARY KEY);"); + subtree_from_arguments("tobundle"); + find_checkin_associates("tobundle", 0); + verify_all_options(); + describe_artifacts("IN tobundle"); + + if( g.argc!=4 ) usage("export BUNDLE ?OPTIONS?"); + /* Create the new bundle */ + bundle_attach_file(g.argv[3], "b1", 1); + db_begin_transaction(); + + /* Add 'mtime' and 'project-code' entries to the bconfig table */ + db_multi_exec( + "INSERT INTO bconfig(bcname,bcvalue)" + " VALUES('mtime',datetime('now'));" + ); + db_multi_exec( + "INSERT INTO bconfig(bcname,bcvalue)" + " SELECT name, value FROM config" + " WHERE name IN ('project-code','parent-project-code');" + ); + + /* Directly copy content from the repository into the bundle as long + ** as the repository content is a delta from some other artifact that + ** is also in the bundle. + */ + db_multi_exec( + "REPLACE INTO bblob(blobid,uuid,sz,delta,data,notes) " + " SELECT" + " tobundle.rid," + " blob.uuid," + " blob.size," + " delta.srcid," + " blob.content," + " (SELECT summary FROM description WHERE rid=blob.rid)" + " FROM tobundle, blob, delta" + " WHERE blob.rid=tobundle.rid" + " AND delta.rid=tobundle.rid" + " AND delta.srcid IN tobundle;" + ); + + /* For all the remaining artifacts, we need to construct their deltas + ** manually. + */ + mnToBundle = db_int(0,"SELECT min(rid) FROM tobundle"); + db_prepare(&q, + "SELECT rid FROM tobundle" + " WHERE rid NOT IN (SELECT blobid FROM bblob)" + " ORDER BY +rid;" + ); + while( db_step(&q)==SQLITE_ROW ){ + Blob content; + int rid = db_column_int(&q,0); + int deltaFrom = 0; + + /* Get the raw, uncompressed content of the artifact into content */ + content_get(rid, &content); + + /* Try to find another artifact, not within the bundle, that is a + ** plausible candidate for being a delta basis for the content. Set + ** deltaFrom to the RID of that other artifact. Leave deltaFrom set + ** to zero if the content should not be delta-compressed + */ + if( !bStandalone ){ + if( db_exists("SELECT 1 FROM plink WHERE cid=%d",rid) ){ + deltaFrom = db_int(0, + "SELECT max(cid) FROM plink" + " WHERE cid<%d", mnToBundle); + }else{ + deltaFrom = db_int(0, + "SELECT max(fid) FROM mlink" + " WHERE fnid=(SELECT fnid FROM mlink WHERE fid=%d)" + " AND fid<%d", rid, mnToBundle); + } + } + + /* Try to insert the artifact as a delta + */ + if( deltaFrom ){ + Blob basis, delta; + content_get(deltaFrom, &basis); + blob_delta_create(&basis, &content, &delta); + if( blob_size(&delta)>0.9*blob_size(&content) ){ + deltaFrom = 0; + }else{ + Stmt ins; + blob_compress(&delta, &delta); + db_prepare(&ins, + "REPLACE INTO bblob(blobid,uuid,sz,delta,data,notes)" + " SELECT %d, uuid, size, (SELECT uuid FROM blob WHERE rid=%d)," + " :delta, (SELECT summary FROM description WHERE rid=blob.rid)" + " FROM blob WHERE rid=%d", rid, deltaFrom, rid); + db_bind_blob(&ins, ":delta", &delta); + db_step(&ins); + db_finalize(&ins); + } + blob_reset(&basis); + blob_reset(&delta); + } + + /* If unable to insert the artifact as a delta, insert full-text */ + if( deltaFrom==0 ){ + Stmt ins; + blob_compress(&content, &content); + db_prepare(&ins, + "REPLACE INTO bblob(blobid,uuid,sz,delta,data,notes)" + " SELECT rid, uuid, size, NULL, :content," + " (SELECT summary FROM description WHERE rid=blob.rid)" + " FROM blob WHERE rid=%d", rid); + db_bind_blob(&ins, ":content", &content); + db_step(&ins); + db_finalize(&ins); + } + blob_reset(&content); + } + db_finalize(&q); + + db_end_transaction(0); +} + + +/* +** There is a TEMP table bix(blobid,delta) containing a set of purgeitems +** that need to be transferred to the BLOB table. This routine does +** all items that have srcid=iSrc. The pBasis blob holds the content +** of the source document if iSrc>0. +*/ +static void bundle_import_elements(int iSrc, Blob *pBasis, int isPriv){ + Stmt q; + static Bag busy; + assert( pBasis!=0 || iSrc==0 ); + if( iSrc>0 ){ + if( bag_find(&busy, iSrc) ){ + fossil_fatal("delta loop while uncompressing bundle artifacts"); + } + bag_insert(&busy, iSrc); + } + db_prepare(&q, + "SELECT uuid, data, bblob.delta, bix.blobid" + " FROM bix, bblob" + " WHERE bix.delta=%d" + " AND bix.blobid=bblob.blobid;", + iSrc + ); + while( db_step(&q)==SQLITE_ROW ){ + Blob h1, c1, c2; + int rid; + blob_zero(&h1); + db_column_blob(&q, 0, &h1); + blob_zero(&c1); + db_column_blob(&q, 1, &c1); + blob_uncompress(&c1, &c1); + blob_zero(&c2); + if( db_column_type(&q,2)==SQLITE_TEXT && db_column_bytes(&q,2)>=HNAME_MIN ){ + Blob basis; + rid = db_int(0,"SELECT rid FROM blob WHERE uuid=%Q", + db_column_text(&q,2)); + content_get(rid, &basis); + blob_delta_apply(&basis, &c1, &c2); + blob_reset(&basis); + blob_reset(&c1); + }else if( pBasis ){ + blob_delta_apply(pBasis, &c1, &c2); + blob_reset(&c1); + }else{ + c2 = c1; + } + if( hname_verify_hash(&c2, blob_buffer(&h1), blob_size(&h1))==0 ){ + fossil_fatal("artifact hash error on %b", &h1); + } + rid = content_put_ex(&c2, blob_str(&h1), 0, 0, isPriv); + if( rid==0 ){ + fossil_fatal("%s", g.zErrMsg); + }else{ + if( !isPriv ) content_make_public(rid); + content_get(rid, &c1); + manifest_crosslink(rid, &c1, MC_NO_ERRORS); + db_multi_exec("INSERT INTO got(rid) VALUES(%d)",rid); + } + bundle_import_elements(db_column_int(&q,3), &c2, isPriv); + blob_reset(&c2); + } + db_finalize(&q); + if( iSrc>0 ) bag_remove(&busy, iSrc); +} + +/* +** Extract an item from content from the bundle +*/ +static void bundle_extract_item( + int blobid, /* ID of the item to extract */ + Blob *pOut /* Write the content into this blob */ +){ + Stmt q; + Blob x, basis, h1; + static Bag busy; + + db_prepare(&q, "SELECT uuid, delta, data FROM bblob" + " WHERE blobid=%d", blobid); + if( db_step(&q)!=SQLITE_ROW ){ + db_finalize(&q); + fossil_fatal("no such item: %d", blobid); + } + if( bag_find(&busy, blobid) ) fossil_fatal("delta loop"); + blob_zero(&x); + db_column_blob(&q, 2, &x); + blob_uncompress(&x, &x); + if( db_column_type(&q,1)==SQLITE_INTEGER ){ + bundle_extract_item(db_column_int(&q,1), &basis); + blob_delta_apply(&basis, &x, pOut); + blob_reset(&basis); + blob_reset(&x); + }else if( db_column_type(&q,1)==SQLITE_TEXT ){ + int rid = db_int(0, "SELECT rid FROM blob WHERE uuid=%Q", + db_column_text(&q,1)); + if( rid==0 ){ + fossil_fatal("cannot find delta basis %s", db_column_text(&q,1)); + } + content_get(rid, &basis); + db_column_blob(&q, 2, &x); + blob_delta_apply(&basis, &x, pOut); + blob_reset(&basis); + blob_reset(&x); + }else{ + *pOut = x; + } + blob_zero(&h1); + db_column_blob(&q, 0, &h1); + if( hname_verify_hash(pOut, blob_buffer(&h1), blob_size(&h1))==0 ){ + fossil_fatal("incorrect hash for artifact %b", &h1); + } + blob_reset(&h1); + bag_remove(&busy, blobid); + db_finalize(&q); +} + +/* fossil bundle cat BUNDLE HASH... +** +** Write elements of a bundle on standard output +*/ +static void bundle_cat_cmd(void){ + int i; + Blob x; + verify_all_options(); + if( g.argc<5 ) usage("cat BUNDLE HASH..."); + bundle_attach_file(g.argv[3], "b1", 1); + blob_zero(&x); + for(i=4; i<g.argc; i++){ + int blobid = db_int(0,"SELECT blobid FROM bblob WHERE uuid LIKE '%q%%'", + g.argv[i]); + if( blobid==0 ){ + fossil_fatal("no such artifact in bundle: %s", g.argv[i]); + } + bundle_extract_item(blobid, &x); + blob_write_to_file(&x, "-"); + blob_reset(&x); + } +} + + +/* fossil bundle import BUNDLE ?OPTIONS? +** +** Attempt to import the changes contained in BUNDLE. Make the change +** private so that they do not sync. +** +** OPTIONS: +** --force Import even if the project-code does not match +** --publish Imported changes are not private +*/ +static void bundle_import_cmd(void){ + int forceFlag = find_option("force","f",0)!=0; + int isPriv = find_option("publish",0,0)==0; + char *zMissingDeltas; + verify_all_options(); + if ( g.argc!=4 ) usage("import BUNDLE ?OPTIONS?"); + bundle_attach_file(g.argv[3], "b1", 1); + + /* Only import a bundle that was generated from a repo with the same + ** project code, unless the --force flag is true */ + if( !forceFlag ){ + if( !db_exists("SELECT 1 FROM config, bconfig" + " WHERE config.name='project-code'" + " AND bconfig.bcname='project-code'" + " AND config.value=bconfig.bcvalue;") + ){ + fossil_fatal("project-code in the bundle does not match the " + "repository project code. (override with --force)."); + } + } + + /* If the bundle contains deltas with a basis that is external to the + ** bundle and those external basis files are missing from the local + ** repo, then the delta encodings cannot be decoded and the bundle cannot + ** be extracted. */ + zMissingDeltas = db_text(0, + "SELECT group_concat(substr(delta,1,10),' ')" + " FROM bblob" + " WHERE typeof(delta)='text' AND length(delta)>=%d" + " AND NOT EXISTS(SELECT 1 FROM blob WHERE uuid=bblob.delta)", + HNAME_MIN); + if( zMissingDeltas && zMissingDeltas[0] ){ + fossil_fatal("delta basis artifacts not found in repository: %s", + zMissingDeltas); + } + + db_begin_transaction(); + db_multi_exec( + "CREATE TEMP TABLE bix(" + " blobid INTEGER PRIMARY KEY," + " delta INTEGER" + ");" + "CREATE INDEX bixdelta ON bix(delta);" + "INSERT INTO bix(blobid,delta)" + " SELECT blobid," + " CASE WHEN typeof(delta)=='integer'" + " THEN delta ELSE 0 END" + " FROM bblob" + " WHERE NOT EXISTS(SELECT 1 FROM blob WHERE uuid=bblob.uuid AND size>=0);" + "CREATE TEMP TABLE got(rid INTEGER PRIMARY KEY ON CONFLICT IGNORE);" + ); + manifest_crosslink_begin(); + bundle_import_elements(0, 0, isPriv); + manifest_crosslink_end(0); + describe_artifacts_to_stdout("IN got", "Imported content:"); + db_end_transaction(0); +} + +/* fossil bundle purge BUNDLE +** +** Try to undo a prior "bundle import BUNDLE". +** +** If the --force option is omitted, then this will only work if +** there have been no check-ins or tags added that use the import. +** +** This routine never removes content that is not already in the bundle +** so the bundle serves as a backup. The purge can be undone using +** "fossil bundle import BUNDLE". +*/ +static void bundle_purge_cmd(void){ + int bForce = find_option("force",0,0)!=0; + int bTest = find_option("test",0,0)!=0; /* Undocumented --test option */ + const char *zFile = g.argv[3]; + verify_all_options(); + if ( g.argc!=4 ) usage("purge BUNDLE ?OPTIONS?"); + bundle_attach_file(zFile, "b1", 0); + db_begin_transaction(); + + /* Find all check-ins of the bundle */ + db_multi_exec( + "CREATE TEMP TABLE ok(rid INTEGER PRIMARY KEY);" + "INSERT OR IGNORE INTO ok SELECT blob.rid FROM bblob, blob, plink" + " WHERE bblob.uuid=blob.uuid" + " AND plink.cid=blob.rid;" + ); + + /* Check to see if new check-ins have been committed to check-ins in + ** the bundle. Do not allow the purge if that is true and if --force + ** is omitted. + */ + if( !bForce ){ + Stmt q; + int n = 0; + db_prepare(&q, + "SELECT cid FROM plink WHERE pid IN ok AND cid NOT IN ok" + ); + while( db_step(&q)==SQLITE_ROW ){ + whatis_rid(db_column_int(&q,0),0); + fossil_print("%.78c\n", '-'); + n++; + } + db_finalize(&q); + if( n>0 ){ + fossil_fatal("check-ins above are derived from check-ins in the bundle."); + } + } + + /* Find all files associated with those check-ins that are used + ** nowhere else. */ + find_checkin_associates("ok", 1); + + /* Check to see if any associated files are not in the bundle. Issue + ** an error if there are any, unless --force is used. + */ + if( !bForce ){ + db_multi_exec( + "CREATE TEMP TABLE err1(rid INTEGER PRIMARY KEY);" + "INSERT INTO err1 " + " SELECT blob.rid FROM ok CROSS JOIN blob" + " WHERE blob.rid=ok.rid" + " AND blob.uuid NOT IN (SELECT uuid FROM bblob);" + ); + if( db_changes() ){ + describe_artifacts_to_stdout("IN err1", 0); + fossil_fatal("artifacts above associated with bundle check-ins " + " are not in the bundle"); + }else{ + db_multi_exec("DROP TABLE err1;"); + } + } + + if( bTest ){ + describe_artifacts_to_stdout( + "IN (SELECT blob.rid FROM ok, blob, bblob" + " WHERE blob.rid=ok.rid AND blob.uuid=bblob.uuid)", + "Purged artifacts found in the bundle:"); + describe_artifacts_to_stdout( + "IN (SELECT blob.rid FROM ok, blob" + " WHERE blob.rid=ok.rid " + " AND blob.uuid NOT IN (SELECT uuid FROM bblob))", + "Purged artifacts NOT in the bundle:"); + describe_artifacts_to_stdout( + "IN (SELECT blob.rid FROM bblob, blob" + " WHERE blob.uuid=bblob.uuid " + " AND blob.rid NOT IN ok)", + "Artifacts in the bundle but not purged:"); + }else{ + purge_artifact_list("ok",0,0); + } + db_end_transaction(0); +} + +/* +** COMMAND: bundle* +** +** Usage: %fossil bundle SUBCOMMAND ARGS... +** +** > fossil bundle append BUNDLE FILE... +** +** Add files named on the command line to BUNDLE. This subcommand has +** little practical use and is mostly intended for testing. +** +** > fossil bundle cat BUNDLE HASH... +** +** Extract one or more artifacts from the bundle and write them +** consecutively on standard output. This subcommand was designed +** for testing and introspection of bundles and is not something +** commonly used. +** +** > fossil bundle export BUNDLE ?OPTIONS? +** +** Generate a new bundle, in the file named BUNDLE, that contains a +** subset of the check-ins in the repository (usually a single branch) +** described by the --branch, --from, --to, and/or --checkin options, +** at least one of which is required. If BUNDLE already exists, the +** specified content is added to the bundle. +** +** --branch BRANCH Package all check-ins on BRANCH +** --from TAG1 --to TAG2 Package check-ins between TAG1 and TAG2 +** --checkin TAG Package the single check-in TAG +** --standalone Do no use delta-encoding against +** artifacts not in the bundle +** +** > fossil bundle extend BUNDLE +** +** The BUNDLE must already exist. This subcommand adds to the bundle +** any check-ins that are descendants of check-ins already in the bundle, +** and any tags that apply to artifacts in the bundle. +** +** > fossil bundle import BUNDLE ?--publish? +** +** Import all content from BUNDLE into the repository. By default, the +** imported files are private and will not sync. Use the --publish +** option to make the import public. +** +** > fossil bundle ls BUNDLE +** +** List the contents of BUNDLE on standard output +** +** > fossil bundle purge BUNDLE +** +** Remove from the repository all files that are used exclusively +** by check-ins in BUNDLE. This has the effect of undoing a +** "fossil bundle import". +** +** See also: [[publish]] +*/ +void bundle_cmd(void){ + const char *zSubcmd; + int n; + if( g.argc<4 ) usage("SUBCOMMAND BUNDLE ?OPTIONS?"); + zSubcmd = g.argv[2]; + db_find_and_open_repository(0,0); + n = (int)strlen(zSubcmd); + if( strncmp(zSubcmd, "append", n)==0 ){ + bundle_append_cmd(); + }else if( strncmp(zSubcmd, "cat", n)==0 ){ + bundle_cat_cmd(); + }else if( strncmp(zSubcmd, "export", n)==0 ){ + bundle_export_cmd(); + }else if( strncmp(zSubcmd, "extend", n)==0 ){ + fossil_fatal("not yet implemented"); + }else if( strncmp(zSubcmd, "import", n)==0 ){ + bundle_import_cmd(); + }else if( strncmp(zSubcmd, "ls", n)==0 ){ + bundle_ls_cmd(); + }else if( strncmp(zSubcmd, "purge", n)==0 ){ + bundle_purge_cmd(); + }else{ + fossil_fatal("unknown subcommand for bundle: %s", zSubcmd); + } +} ADDED src/cache.c Index: src/cache.c ================================================================== --- /dev/null +++ src/cache.c @@ -0,0 +1,485 @@ +/* +** 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@sqlite.org +** +******************************************************************************* +** +** This file implements a cache for expense operations such as +** /zip and /tarball. +*/ +#include "config.h" +#include <sqlite3.h> +#include "cache.h" + +/* +** Construct the name of the repository cache file +*/ +static char *cacheName(void){ + int i; + int n; + + if( g.zRepositoryName==0 ) return 0; + n = (int)strlen(g.zRepositoryName); + for(i=n-1; i>=0; i--){ + if( g.zRepositoryName[i]=='/' ){ i = n; break; } + if( g.zRepositoryName[i]=='.' ) break; + } + if( i<0 ) i = n; + return mprintf("%.*s.cache", i, g.zRepositoryName); +} + +/* +** Attempt to open the cache database, if such a database exists. +** Make sure the cache table exists within that database. +*/ +static sqlite3 *cacheOpen(int bForce){ + char *zDbName; + sqlite3 *db = 0; + int rc; + i64 sz; + + zDbName = cacheName(); + if( zDbName==0 ) return 0; + if( bForce==0 ){ + sz = file_size(zDbName, ExtFILE); + if( sz<=0 ){ + fossil_free(zDbName); + return 0; + } + } + rc = sqlite3_open(zDbName, &db); + fossil_free(zDbName); + if( rc ){ + sqlite3_close(db); + return 0; + } + sqlite3_busy_timeout(db, 5000); + if( sqlite3_table_column_metadata(db,0,"blob","key",0,0,0,0,0)!=SQLITE_OK ){ + rc = sqlite3_exec(db, + "PRAGMA page_size=8192;" + "CREATE TABLE IF NOT EXISTS blob(id INTEGER PRIMARY KEY, data BLOB);" + "CREATE TABLE IF NOT EXISTS cache(" + "key TEXT PRIMARY KEY," /* Key used to access the cache */ + "id INT REFERENCES blob," /* The cache content */ + "sz INT," /* Size of content in bytes */ + "tm INT," /* Last access time (unix timestampe) */ + "nref INT" /* Number of uses */ + ");" + "CREATE TRIGGER IF NOT EXISTS cacheDel AFTER DELETE ON cache BEGIN" + " DELETE FROM blob WHERE id=OLD.id;" + "END;", + 0, 0, 0 + ); + if( rc!=SQLITE_OK ){ + sqlite3_close(db); + return 0; + } + } + return db; +} + +/* +** Attempt to construct a prepared statement for the cache database. +*/ +static sqlite3_stmt *cacheStmt(sqlite3 *db, const char *zSql){ + sqlite3_stmt *pStmt = 0; + int rc; + + rc = sqlite3_prepare_v2(db, zSql, -1, &pStmt, 0); + if( rc ){ + sqlite3_finalize(pStmt); + pStmt = 0; + } + return pStmt; +} + +/* +** This routine implements an SQL function that renders a large integer +** compactly: ex: 12.3MB +*/ +static void cache_sizename( + sqlite3_context *context, + int argc, + sqlite3_value **argv +){ + char zBuf[30]; + double v, x; + assert( argc==1 ); + v = sqlite3_value_double(argv[0]); + x = v<0.0 ? -v : v; + if( x>=1e9 ){ + sqlite3_snprintf(sizeof(zBuf), zBuf, "%.1fGB", v/1e9); + }else if( x>=1e6 ){ + sqlite3_snprintf(sizeof(zBuf), zBuf, "%.1fMB", v/1e6); + }else if( x>=1e3 ){ + sqlite3_snprintf(sizeof(zBuf), zBuf, "%.1fKB", v/1e3); + }else{ + sqlite3_snprintf(sizeof(zBuf), zBuf, "%gB", v); + } + sqlite3_result_text(context, zBuf, -1, SQLITE_TRANSIENT); +} + +/* +** Register the sizename() SQL function with the SQLite database +** connection. +*/ +static void cache_register_sizename(sqlite3 *db){ + sqlite3_create_function(db, "sizename", 1, SQLITE_UTF8, 0, + cache_sizename, 0, 0); +} + +/* +** Attempt to write pContent into the cache. If the cache file does +** not exist, then this routine is a no-op. Older cache entries might +** be deleted. +*/ +void cache_write(Blob *pContent, const char *zKey){ + sqlite3 *db; + sqlite3_stmt *pStmt; + int rc = 0; + int nKeep; + + db = cacheOpen(0); + if( db==0 ) return; + sqlite3_busy_timeout(db, 10000); + sqlite3_exec(db, "BEGIN IMMEDIATE", 0, 0, 0); + pStmt = cacheStmt(db, "INSERT INTO blob(data) VALUES(?1)"); + if( pStmt==0 ) goto cache_write_end; + sqlite3_bind_blob(pStmt, 1, blob_buffer(pContent), blob_size(pContent), + SQLITE_STATIC); + if( sqlite3_step(pStmt)!=SQLITE_DONE ) goto cache_write_end; + sqlite3_finalize(pStmt); + pStmt = cacheStmt(db, + "INSERT OR IGNORE INTO cache(key,sz,tm,nref,id)" + "VALUES(?1,?2,strftime('%s','now'),1,?3)" + ); + if( pStmt==0 ) goto cache_write_end; + sqlite3_bind_text(pStmt, 1, zKey, -1, SQLITE_STATIC); + sqlite3_bind_int(pStmt, 2, blob_size(pContent)); + sqlite3_bind_int(pStmt, 3, sqlite3_last_insert_rowid(db)); + if( sqlite3_step(pStmt)!=SQLITE_DONE) goto cache_write_end; + rc = sqlite3_changes(db); + + /* If the write was successful, truncate the cache to keep at most + ** max-cache-entry entries in the cache. + ** + ** The cache entry replacement algorithm is approximately LRU + ** (least recently used). However, each access of an entry buys + ** that entry an extra hour of grace, so that more commonly accessed + ** entries are held in cache longer. The extra "grace" allotted to + ** an entry is limited to 2 days worth. + */ + if( rc ){ + nKeep = db_get_int("max-cache-entry",10); + sqlite3_finalize(pStmt); + pStmt = cacheStmt(db, + "DELETE FROM cache WHERE rowid IN (" + "SELECT rowid FROM cache" + " ORDER BY (tm + 3600*min(nRef,48)) DESC" + " LIMIT -1 OFFSET ?1)"); + if( pStmt ){ + sqlite3_bind_int(pStmt, 1, nKeep); + sqlite3_step(pStmt); + } + } + +cache_write_end: + sqlite3_finalize(pStmt); + sqlite3_exec(db, rc ? "COMMIT" : "ROLLBACK", 0, 0, 0); + sqlite3_close(db); +} + +/* +** SETTING: max-cache-entry width=10 default=10 +** +** This is the maximum number of entries to allow in the web-cache +** for tarballs, ZIP-archives, and SQL-archives. +*/ + +/* +** Attempt to read content out of the cache with the given zKey. Return +** non-zero on success and zero if unable to locate the content. +** +** Possible reasons for returning zero: +** (1) This server does not implement a cache +** (2) The requested element is not in the cache +*/ +int cache_read(Blob *pContent, const char *zKey){ + sqlite3 *db; + sqlite3_stmt *pStmt; + int rc = 0; + + db = cacheOpen(0); + if( db==0 ) return 0; + sqlite3_busy_timeout(db, 10000); + sqlite3_exec(db, "BEGIN IMMEDIATE", 0, 0, 0); + pStmt = cacheStmt(db, + "SELECT blob.data FROM cache, blob" + " WHERE cache.key=?1 AND cache.id=blob.id"); + if( pStmt==0 ) goto cache_read_done; + sqlite3_bind_text(pStmt, 1, zKey, -1, SQLITE_STATIC); + if( sqlite3_step(pStmt)==SQLITE_ROW ){ + blob_append(pContent, sqlite3_column_blob(pStmt, 0), + sqlite3_column_bytes(pStmt, 0)); + rc = 1; + sqlite3_reset(pStmt); + pStmt = cacheStmt(db, + "UPDATE cache SET nref=nref+1, tm=strftime('%s','now')" + " WHERE key=?1"); + if( pStmt ){ + sqlite3_bind_text(pStmt, 1, zKey, -1, SQLITE_STATIC); + sqlite3_step(pStmt); + } + } + sqlite3_finalize(pStmt); +cache_read_done: + sqlite3_exec(db, "COMMIT", 0, 0, 0); + sqlite3_close(db); + return rc; +} + +/* +** Create a cache database for the current repository if no such +** database already exists. +*/ +void cache_initialize(void){ + sqlite3_close(cacheOpen(1)); +} + +/* +** COMMAND: cache* +** +** Usage: %fossil cache SUBCOMMAND +** +** Manage the cache used for potentially expensive web pages such as +** /zip and /tarball. SUBCOMMAND can be: +** +** clear Remove all entries from the cache. +** +** init Create the cache file if it does not already exist. +** +** list|ls List the keys and content sizes and other stats for +** all entries currently in the cache. +** +** size ?N? Query or set the maximum number of entries in the cache. +** +** status Show a summary of the cache status. +** +** The cache is stored in a file that is distinct from the repository +** but that is held in the same directory as the repository. The cache +** file can be deleted in order to completely disable the cache. +*/ +void cache_cmd(void){ + const char *zCmd; + int nCmd; + sqlite3 *db; + sqlite3_stmt *pStmt; + + db_find_and_open_repository(0,0); + zCmd = g.argc>=3 ? g.argv[2] : ""; + nCmd = (int)strlen(zCmd); + if( nCmd<=1 ){ + fossil_fatal("Usage: %s cache SUBCOMMAND", g.argv[0]); + } + if( strncmp(zCmd, "init", nCmd)==0 ){ + db = cacheOpen(0); + sqlite3_close(db); + if( db ){ + fossil_print("cache already exists in file %z\n", cacheName()); + }else{ + db = cacheOpen(1); + sqlite3_close(db); + if( db ){ + fossil_print("cache created in file %z\n", cacheName()); + }else{ + fossil_fatal("unable to create cache file %z", cacheName()); + } + } + }else if( strncmp(zCmd, "clear", nCmd)==0 ){ + db = cacheOpen(0); + if( db ){ + sqlite3_exec(db, "DELETE FROM cache; DELETE FROM blob; VACUUM;",0,0,0); + sqlite3_close(db); + fossil_print("cache cleared\n"); + }else{ + fossil_print("nothing to clear; cache does not exist\n"); + } + }else if( strncmp(zCmd, "list", nCmd)==0 + || strncmp(zCmd, "ls", nCmd)==0 + || strncmp(zCmd, "status", nCmd)==0 + ){ + db = cacheOpen(0); + if( db==0 ){ + fossil_print("cache does not exist\n"); + }else{ + int nEntry = 0; + char *zDbName = cacheName(); + cache_register_sizename(db); + pStmt = cacheStmt(db, + "SELECT key, sizename(sz), nRef, datetime(tm,'unixepoch')" + " FROM cache" + " ORDER BY tm DESC" + ); + if( pStmt ){ + while( sqlite3_step(pStmt)==SQLITE_ROW ){ + if( zCmd[0]=='l' ){ + fossil_print("%s %4d %8s %s\n", + sqlite3_column_text(pStmt, 3), + sqlite3_column_int(pStmt, 2), + sqlite3_column_text(pStmt, 1), + sqlite3_column_text(pStmt, 0)); + } + nEntry++; + } + sqlite3_finalize(pStmt); + } + sqlite3_close(db); + fossil_print( + "Filename: %s\n" + "Entries: %d\n" + "max-cache-entry: %d\n" + "Cache-file Size: %,lld\n", + zDbName, + nEntry, + db_get_int("max-cache-entry",10), + file_size(zDbName, ExtFILE) + ); + fossil_free(zDbName); + } + }else if( strncmp(zCmd, "size", nCmd)==0 ){ + if( g.argc>=4 ){ + int n = atoi(g.argv[3]); + if( n>=5 ) db_set_int("max-cache-entry",n,0); + } + fossil_print("max-cache-entry: %d\n", db_get_int("max-cache-entry",10)); + }else{ + fossil_fatal("Unknown subcommand \"%s\"." + " Should be one of: clear init list size status", zCmd); + } +} + +/* +** Given a cache key, find the check-in hash and return it as a separate +** string. The returned string is obtained from fossil_malloc() and must +** be freed by the caller. +** +** Return NULL if not found. +** +** The key is usually in a format like these: +** +** /tarball/HASH/NAME +** /zip/HASH/NAME +** /sqlar/HASH/NAME +*/ +static char *cache_hash_of_key(const char *zKey){ + int i; + if( zKey==0 ) return 0; + if( zKey[0]!='/' ) return 0; + zKey++; + while( zKey[0] && zKey[0]!='/' ) zKey++; + if( zKey[0]==0 ) return 0; + zKey++; + for(i=0; zKey[i] && zKey[i]!='/'; i++){} + if( !validate16(zKey, i) ) return 0; + return fossil_strndup(zKey, i); +} + + +/* +** WEBPAGE: cachestat +** +** Show information about the webpage cache. Requires Setup privilege. +*/ +void cache_page(void){ + sqlite3 *db; + sqlite3_stmt *pStmt; + char zBuf[100]; + + login_check_credentials(); + if( !g.perm.Setup ){ login_needed(0); return; } + style_set_current_feature("cache"); + style_header("Web Cache Status"); + db = cacheOpen(0); + if( db==0 ){ + @ The web-page cache is disabled for this repository + }else{ + char *zDbName = cacheName(); + cache_register_sizename(db); + pStmt = cacheStmt(db, + "SELECT key, sz, nRef, datetime(tm,'unixepoch')" + " FROM cache" + " ORDER BY (tm + 3600*min(nRef,48)) DESC" + ); + if( pStmt ){ + @ <ol> + while( sqlite3_step(pStmt)==SQLITE_ROW ){ + const unsigned char *zName = sqlite3_column_text(pStmt,0); + char *zHash = cache_hash_of_key((const char*)zName); + @ <li><p>%z(href("%R/cacheget?key=%T",zName))%h(zName)</a><br /> + @ size: %,lld(sqlite3_column_int64(pStmt,1)) + @ hit-count: %d(sqlite3_column_int(pStmt,2)) + @ last-access: %s(sqlite3_column_text(pStmt,3)) \ + if( zHash ){ + @ %z(href("%R/timeline?c=%S",zHash))check-in</a>\ + fossil_free(zHash); + } + @ </p></li> + + } + sqlite3_finalize(pStmt); + @ </ol> + } + zDbName = cacheName(); + bigSizeName(sizeof(zBuf), zBuf, file_size(zDbName, ExtFILE)); + @ <p> + @ cache-file name: %h(zDbName)<br> + @ cache-file size: %s(zBuf)<br> + @ max-cache-entry: %d(db_get_int("max-cache-entry",10)) + @ </p> + @ <p> + @ Use the "<a href="%R/help?cmd=cache">fossil cache</a>" command + @ on the command-line to create and configure the web-cache. + @ </p> + fossil_free(zDbName); + sqlite3_close(db); + } + style_finish_page(); +} + +/* +** WEBPAGE: cacheget +** +** Usage: /cacheget?key=KEY +** +** Download a single entry for the cache, identified by KEY. +** This page is normally a hyperlink from the /cachestat page. +** Requires Admin privilege. +*/ +void cache_getpage(void){ + const char *zKey; + Blob content; + + login_check_credentials(); + if( !g.perm.Setup ){ login_needed(0); return; } + zKey = PD("key",""); + blob_zero(&content); + if( cache_read(&content, zKey)==0 ){ + style_set_current_feature("cache"); + style_header("Cache Download Error"); + @ The cache does not contain any entry with this key: "%h(zKey)" + style_finish_page(); + return; + } + cgi_set_content(&content); + cgi_set_content_type("application/x-compressed"); +} ADDED src/capabilities.c Index: src/capabilities.c ================================================================== --- /dev/null +++ src/capabilities.c @@ -0,0 +1,479 @@ +/* +** Copyright (c) 2018 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 code used managing user capability strings. +*/ +#include "config.h" +#include "capabilities.h" +#include <assert.h> + +#if INTERFACE +/* +** A capability string object holds all defined capabilities in a +** vector format that is subject to boolean operations. +*/ +struct CapabilityString { + unsigned char x[128]; +}; +#endif + +/* +** Add capabilities to a CapabilityString. If pIn is NULL, then create +** a new capability string. +** +** Call capability_free() on the allocated CapabilityString object to +** deallocate. +*/ +CapabilityString *capability_add(CapabilityString *pIn, const char *zCap){ + int c; + int i; + if( pIn==0 ){ + pIn = fossil_malloc( sizeof(*pIn) ); + memset(pIn, 0, sizeof(*pIn)); + } + if( zCap ){ + for(i=0; (c = zCap[i])!=0; i++){ + if( c>='0' && c<='z' ) pIn->x[c] = 1; + } + } + return pIn; +} + +/* +** Remove capabilities from a CapabilityString. +*/ +CapabilityString *capability_remove(CapabilityString *pIn, const char *zCap){ + int c; + int i; + if( pIn==0 ){ + pIn = fossil_malloc( sizeof(*pIn) ); + memset(pIn, 0, sizeof(*pIn)); + } + if( zCap ){ + for(i=0; (c = zCap[i])!=0; i++){ + if( c>='0' && c<='z' ) pIn->x[c] = 0; + } + } + return pIn; +} + +/* +** Return true if any of the capabilities in zNeeded are found in pCap +*/ +int capability_has_any(CapabilityString *p, const char *zNeeded){ + if( p==0 ) return 0; + if( zNeeded==0 ) return 0; + while( zNeeded[0] ){ + int c = zNeeded[0]; + if( fossil_isalnum(c) && p->x[c] ) return 1; + zNeeded++; + } + return 0; +} + +/* +** Delete a CapabilityString object. +*/ +void capability_free(CapabilityString *p){ + fossil_free(p); +} + +/* +** Expand the capability string by including all capabilities for +** special users "nobody" and "anonymous". Also include "reader" +** if "u" is present and "developer" if "v" is present. +*/ +void capability_expand(CapabilityString *pIn){ + static char *zNobody = 0; + static char *zAnon = 0; + static char *zReader = 0; + static char *zDev = 0; + static char *zAdmin = "bcdefghijklmnopqrtwz234567AD"; + int doneV = 0; + + if( pIn==0 ){ + fossil_free(zNobody); zNobody = 0; + fossil_free(zAnon); zAnon = 0; + fossil_free(zReader); zReader = 0; + fossil_free(zDev); zDev = 0; + return; + } + if( zNobody==0 ){ + zNobody = db_text(0, "SELECT cap FROM user WHERE login='nobody'"); + zAnon = db_text(0, "SELECT cap FROM user WHERE login='anonymous'"); + zReader = db_text(0, "SELECT cap FROM user WHERE login='reader'"); + zDev = db_text(0, "SELECT cap FROM user WHERE login='developer'"); + } + pIn = capability_add(pIn, zAnon); + pIn = capability_add(pIn, zNobody); + if( pIn->x['a'] || pIn->x['s'] ){ + pIn = capability_add(pIn, zAdmin); + } + if( pIn->x['v'] ){ + pIn = capability_add(pIn, zDev); + doneV = 1; + } + if( pIn->x['u'] ){ + pIn = capability_add(pIn, zReader); + if( pIn->x['v'] && !doneV ){ + pIn = capability_add(pIn, zDev); + } + } +} + +/* +** Render a capability string in canonical string format. Space to hold +** the returned string is obtained from fossil_malloc() can should be freed +** by the caller. +*/ +char *capability_string(CapabilityString *p){ + Blob out; + int i; + int j = 0; + char buf[100]; + blob_init(&out, 0, 0); + for(i='a'; i<='z'; i++){ + if( p->x[i] ) buf[j++] = i; + } + for(i='0'; i<='9'; i++){ + if( p->x[i] ) buf[j++] = i; + } + for(i='A'; i<='Z'; i++){ + if( p->x[i] ) buf[j++] = i; + } + buf[j] = 0; + return fossil_strdup(buf); +} + +/* +** The next two routines implement an aggregate SQL function that +** takes multiple capability strings and in the end returns their +** union. Example usage: +** +** SELECT capunion(cap) FROM user WHERE login IN ('nobody','anonymous'); +*/ +void capability_union_step( + sqlite3_context *context, + int argc, + sqlite3_value **argv +){ + CapabilityString *p; + const char *zIn; + + zIn = (const char*)sqlite3_value_text(argv[0]); + if( zIn==0 ) return; + p = (CapabilityString*)sqlite3_aggregate_context(context, sizeof(*p)); + p = capability_add(p, zIn); +} +void capability_union_finalize(sqlite3_context *context){ + CapabilityString *p; + p = sqlite3_aggregate_context(context, 0); + if( p ){ + char *zOut = capability_string(p); + sqlite3_result_text(context, zOut, -1, fossil_free); + } +} + +/* +** The next routines takes the raw USER.CAP field and expands it with +** capabilities from special users. Example: +** +** SELECT fullcap(cap) FROM user WHERE login=?1 +*/ +void capability_fullcap( + sqlite3_context *context, + int argc, + sqlite3_value **argv +){ + CapabilityString *p; + const char *zIn; + char *zOut; + + zIn = (const char*)sqlite3_value_text(argv[0]); + if( zIn==0 ) zIn = ""; + p = capability_add(0, zIn); + capability_expand(p); + zOut = capability_string(p); + sqlite3_result_text(context, zOut, -1, fossil_free); + capability_free(p); +} + +#if INTERFACE +/* +** Capabilities are grouped into "classes" as follows: +*/ +#define CAPCLASS_CODE 0x0001 +#define CAPCLASS_WIKI 0x0002 +#define CAPCLASS_TKT 0x0004 +#define CAPCLASS_FORUM 0x0008 +#define CAPCLASS_DATA 0x0010 +#define CAPCLASS_ALERT 0x0020 +#define CAPCLASS_OTHER 0x0040 +#define CAPCLASS_SUPER 0x0080 +#define CAPCLASS_ALL 0xffff +#endif /* INTERFACE */ + + +/* +** The following structure holds descriptions of the various capabilities. +*/ +static struct Caps { + char cCap; /* The capability letter */ + unsigned short eClass; /* The "class" for this capability */ + unsigned nUser; /* Number of users with this capability */ + char *zAbbrev; /* Abbreviated mnemonic name */ + char *zOneLiner; /* One-line summary */ +} aCap[] = { + { 'a', CAPCLASS_SUPER, 0, + "Admin", "Create and delete users" }, + { 'b', CAPCLASS_WIKI|CAPCLASS_TKT, 0, + "Attach", "Add attachments to wiki or tickets" }, + { 'c', CAPCLASS_TKT, 0, + "Append-Tkt", "Append to existing tickets" }, + /* + ** d unused since fork from CVSTrac; + ** see https://fossil-scm.org/forum/forumpost/43c78f4bef + */ + { 'e', CAPCLASS_DATA, 0, + "View-PII", "View sensitive info such as email addresses" }, + { 'f', CAPCLASS_WIKI, 0, + "New-Wiki", "Create new wiki pages" }, + { 'g', CAPCLASS_DATA, 0, + "Clone", "Clone the repository" }, + { 'h', CAPCLASS_OTHER, 0, + "Hyperlinks", "Show hyperlinks to detailed repository history" }, + { 'i', CAPCLASS_CODE, 0, + "Check-In", "Check-in code changes" }, + { 'j', CAPCLASS_WIKI, 0, + "Read-Wiki", "View wiki pages" }, + { 'k', CAPCLASS_WIKI, 0, + "Write-Wiki", "Edit wiki pages" }, + { 'l', CAPCLASS_WIKI|CAPCLASS_SUPER, 0, + "Mod-Wiki", "Moderator for wiki pages" }, + { 'm', CAPCLASS_WIKI, 0, + "Append-Wiki", "Append to wiki pages" }, + { 'n', CAPCLASS_TKT, 0, + "New-Tkt", "Create new tickets" }, + { 'o', CAPCLASS_CODE, 0, + "Check-Out", "Check out code" }, + { 'p', CAPCLASS_OTHER, 0, + "Password", "Change your own password" }, + { 'q', CAPCLASS_TKT|CAPCLASS_SUPER, 0, + "Mod-Tkt", "Moderate tickets" }, + { 'r', CAPCLASS_TKT, 0, + "Read-Tkt", "View tickets" }, + { 's', CAPCLASS_SUPER, 0, + "Superuser", "Setup and configure the respository" }, + { 't', CAPCLASS_TKT, 0, + "Reports", "Create new ticket report formats" }, + { 'u', CAPCLASS_OTHER, 0, + "Reader", "Inherit all the capabilities of the \"reader\" user" }, + { 'v', CAPCLASS_OTHER, 0, + "Developer", "Inherit all capabilities of the \"developer\" user" }, + { 'w', CAPCLASS_TKT, 0, + "Write-Tkt", "Edit tickets" }, + { 'x', CAPCLASS_DATA, 0, + "Private", "Push and/or pull private branches" }, + { 'y', CAPCLASS_SUPER, 0, + "Write-UV", "Push unversioned content" }, + { 'z', CAPCLASS_CODE, 0, + "Zip-Download", "Download a ZIP archive, tarball, or SQL archive" }, + { '2', CAPCLASS_FORUM, 0, + "Forum-Read", "Read forum posts by others" }, + { '3', CAPCLASS_FORUM, 0, + "Forum-Write", "Create new forum messages" }, + { '4', CAPCLASS_FORUM, 0, + "Forum-Trusted", "Create forum messages that bypass moderation" }, + { '5', CAPCLASS_FORUM|CAPCLASS_SUPER, 0, + "Forum-Mod", "Moderator for forum messages" }, + { '6', CAPCLASS_FORUM|CAPCLASS_SUPER, 0, + "Forum-Admin", "Grant capability '4' to other users" }, + { '7', CAPCLASS_ALERT, 0, + "Alerts", "Sign up for email alerts" }, + { 'A', CAPCLASS_ALERT|CAPCLASS_SUPER, 0, + "Announce", "Send announcements to all subscribers" }, + { 'C', CAPCLASS_FORUM, 0, + "Chat", "Read and/or writes messages in the chatroom" }, + { 'D', CAPCLASS_OTHER, 0, + "Debug", "Enable debugging features" }, +}; + +/* +** Populate the aCap[].nUser values based on the current content +** of the USER table. +*/ +void capabilities_count(void){ + int i; + static int done = 0; + Stmt q; + if( done ) return; + db_prepare(&q, "SELECT fullcap(cap) FROM user"); + while( db_step(&q)==SQLITE_ROW ){ + const char *zCap = db_column_text(&q, 0); + if( zCap==0 || zCap[0]==0 ) continue; + for(i=0; i<sizeof(aCap)/sizeof(aCap[0]); i++){ + if( strchr(zCap, aCap[i].cCap) ) aCap[i].nUser++; + } + } + db_finalize(&q); + done = 1; +} + + +/* +** Generate HTML that lists all of the capability letters together with +** a brief summary of what each letter means. +*/ +void capabilities_table(unsigned mClass){ + int i; + if( g.perm.Admin ) capabilities_count(); + @ <table> + @ <tbody> + for(i=0; i<sizeof(aCap)/sizeof(aCap[0]); i++){ + int n; + if( (aCap[i].eClass & mClass)==0 ) continue; + @ <tr><th valign="top">%c(aCap[i].cCap)</th> + @ <td>%h(aCap[i].zAbbrev)</td><td>%h(aCap[i].zOneLiner)</td>\ + n = aCap[i].nUser; + if( n && g.perm.Admin ){ + @ <td><a href="%R/setup_ulist?with=%c(aCap[i].cCap)">\ + @ %d(n) user%s(n>1?"s":"")</a></td>\ + } + @ </tr> + } + @ </tbody> + @ </table> +} + +/* +** Generate a "capability summary table" that shows the major capabilities +** against the various user categories. +*/ +void capability_summary(void){ + Stmt q; + CapabilityString *pCap; + char *zSelfCap; + char *zPubPages = db_get("public-pages",0); + int hasPubPages = zPubPages && zPubPages[0]; + + pCap = capability_add(0, db_get("default-perms","u")); + capability_expand(pCap); + zSelfCap = capability_string(pCap); + capability_free(pCap); + + db_prepare(&q, + "WITH t(id,seq) AS (VALUES('nobody',1),('anonymous',2),('reader',3)," + "('developer',4))" + " SELECT id, CASE WHEN user.login='nobody' THEN user.cap" + " ELSE fullcap(user.cap) END,seq,1" + " FROM t LEFT JOIN user ON t.id=user.login" + " UNION ALL" + " SELECT 'Public Pages', %Q, 100, %d" + " UNION ALL" + " SELECT 'New User Default', %Q, 110, 1" + " UNION ALL" + " SELECT 'Regular User', fullcap(capunion(cap)), 200, count(*) FROM user" + " WHERE cap NOT GLOB '*[as]*' AND login NOT IN (SELECT id FROM t)" + " UNION ALL" + " SELECT 'Adminstrator', fullcap(capunion(cap)), 300, count(*) FROM user" + " WHERE cap GLOB '*[as]*'" + " ORDER BY 3 ASC", + zSelfCap, hasPubPages, zSelfCap + ); + @ <table id='capabilitySummary' cellpadding="0" cellspacing="0" border="1"> + @ <tr><th> <th>Code<th>Forum<th>Tickets<th>Wiki<th>Chat\ + @ <th>Unversioned Content</th></tr> + while( db_step(&q)==SQLITE_ROW ){ + const char *zId = db_column_text(&q, 0); + const char *zCap = db_column_text(&q, 1); + int n = db_column_int(&q, 3); + int eType; + static const char *const azType[] = { "off", "read", "write" }; + static const char *const azClass[] = + { "capsumOff", "capsumRead", "capsumWrite" }; + + if( n==0 ) continue; + + /* Code */ + if( db_column_int(&q,2)<10 ){ + @ <tr><th align="right"><tt>"%h(zId)"</tt></th> + }else if( n>1 ){ + @ <tr><th align="right">%d(n) %h(zId)s</th> + }else{ + @ <tr><th align="right">%h(zId)</th> + } + if( sqlite3_strglob("*[asi]*",zCap)==0 ){ + eType = 2; + }else if( sqlite3_strglob("*[oz]*",zCap)==0 ){ + eType = 1; + }else{ + eType = 0; + } + @ <td class="%s(azClass[eType])">%s(azType[eType])</td> + + /* Forum */ + if( sqlite3_strglob("*[as3456]*",zCap)==0 ){ + eType = 2; + }else if( sqlite3_strglob("*2*",zCap)==0 ){ + eType = 1; + }else{ + eType = 0; + } + @ <td class="%s(azClass[eType])">%s(azType[eType])</td> + + /* Ticket */ + if( sqlite3_strglob("*[ascnqtw]*",zCap)==0 ){ + eType = 2; + }else if( sqlite3_strglob("*r*",zCap)==0 ){ + eType = 1; + }else{ + eType = 0; + } + @ <td class="%s(azClass[eType])">%s(azType[eType])</td> + + /* Wiki */ + if( sqlite3_strglob("*[asdfklm]*",zCap)==0 ){ + eType = 2; + }else if( sqlite3_strglob("*j*",zCap)==0 ){ + eType = 1; + }else{ + eType = 0; + } + @ <td class="%s(azClass[eType])">%s(azType[eType])</td> + + /* Chat */ + if( sqlite3_strglob("*C*",zCap)==0 ){ + eType = 2; + }else{ + eType = 0; + } + @ <td class="%s(azClass[eType])">%s(azType[eType])</td> + + /* Unversioned */ + if( sqlite3_strglob("*y*",zCap)==0 ){ + eType = 2; + }else if( sqlite3_strglob("*[ioas]*",zCap)==0 ){ + eType = 1; + }else{ + eType = 0; + } + @ <td class="%s(azClass[eType])">%s(azType[eType])</td> + + } + db_finalize(&q); + @ </table> +} Index: src/captcha.c ================================================================== --- src/captcha.c +++ src/captcha.c @@ -13,26 +13,26 @@ ** drh@hwaci.com ** http://www.hwaci.com/drh/ ** ******************************************************************************* ** -** This file contains code to a simple text-based CAPTCHA. Though eaily +** This file contains code to a simple text-based CAPTCHA. Though easily ** defeated by a sophisticated attacker, this CAPTCHA does at least make ** scripting attacks more difficult. */ -#include <assert.h> #include "config.h" +#include <assert.h> #include "captcha.h" #if INTERFACE #define CAPTCHA 3 /* Which captcha rendering to use */ #endif /* ** Convert a hex digit into a value between 0 and 15 */ -static int hexValue(char c){ +int hex_digit_value(char c){ if( c>='0' && c<='9' ){ return c - '0'; }else if( c>='a' && c<='f' ){ return c - 'a' + 10; }else if( c>='A' && c<='F' ){ @@ -69,17 +69,17 @@ ** Render an 8-character hexadecimal string as ascii art. ** Space to hold the result is obtained from malloc() and should be freed ** by the caller. */ char *captcha_render(const char *zPw){ - char *z = malloc( 500 ); + char *z = fossil_malloc( 9*6*strlen(zPw) + 7 ); int i, j, k, m; k = 0; for(i=0; i<6; i++){ - for(j=0; j<8; j++){ - unsigned char v = hexValue(zPw[j]); + for(j=0; zPw[j]; j++){ + unsigned char v = hex_digit_value(zPw[j]); v = (aFont1[v] >> ((5-i)*4)) & 0xf; for(m=8; m>=1; m = m>>1){ if( v & m ){ z[k++] = 'X'; z[k++] = 'X'; @@ -92,17 +92,17 @@ z[k++] = ' '; } z[k++] = '\n'; } z[k] = 0; - return z; + return z; } #endif /* CAPTCHA==1 */ #if CAPTCHA==2 -static const char *azFont2[] = { +static const char *const azFont2[] = { /* 0 */ " __ ", " / \\ ", "| () |", " \\__/ ", @@ -148,11 +148,11 @@ "|__ |", " / / ", " /_/ ", /* 8 */ - " ___ ", + " ___ ", "( _ )", "/ _ \\", "\\___/", /* 9 */ @@ -202,152 +202,152 @@ ** Render an 8-digit hexadecimal string as ascii arg. ** Space to hold the result is obtained from malloc() and should be freed ** by the caller. */ char *captcha_render(const char *zPw){ - char *z = malloc( 300 ); + char *z = fossil_malloc( 7*4*strlen(zPw) + 5 ); int i, j, k, m; const char *zChar; k = 0; for(i=0; i<4; i++){ - for(j=0; j<8; j++){ - unsigned char v = hexValue(zPw[j]); + for(j=0; zPw[j]; j++){ + unsigned char v = hex_digit_value(zPw[j]); zChar = azFont2[4*v + i]; for(m=0; zChar[m]; m++){ z[k++] = zChar[m]; } } z[k++] = '\n'; } z[k] = 0; - return z; + return z; } #endif /* CAPTCHA==2 */ #if CAPTCHA==3 -static const char *azFont3[] = { +static const char *const azFont3[] = { /* 0 */ " ___ ", " / _ \\ ", "| | | |", "| | | |", "| |_| |", " \\___/ ", - + /* 1 */ " __ ", "/_ |", " | |", " | |", " | |", " |_|", - + /* 2 */ " ___ ", "|__ \\ ", " ) |", " / / ", " / /_ ", "|____|", - + /* 3 */ " ____ ", "|___ \\ ", " __) |", " |__ < ", " ___) |", "|____/ ", - + /* 4 */ " _ _ ", "| || | ", "| || |_ ", "|__ _|", " | | ", " |_| ", - + /* 5 */ " _____ ", "| ____|", "| |__ ", "|___ \\ ", " ___) |", "|____/ ", - + /* 6 */ " __ ", " / / ", " / /_ ", "| '_ \\ ", "| (_) |", " \\___/ ", - + /* 7 */ " ______ ", "|____ |", " / / ", " / / ", " / / ", " /_/ ", - + /* 8 */ " ___ ", " / _ \\ ", "| (_) |", " > _ < ", "| (_) |", " \\___/ ", - + /* 9 */ " ___ ", " / _ \\ ", "| (_) |", " \\__, |", " / / ", " /_/ ", - + /* A */ " ", " /\\ ", " / \\ ", " / /\\ \\ ", " / ____ \\ ", "/_/ \\_\\", - + /* B */ " ____ ", "| _ \\ ", "| |_) |", "| _ < ", "| |_) |", "|____/ ", - + /* C */ " _____ ", " / ____|", "| | ", "| | ", "| |____ ", " \\_____|", - + /* D */ " _____ ", "| __ \\ ", "| | | |", "| | | |", "| |__| |", "|_____/ ", - + /* E */ " ______ ", "| ____|", "| |__ ", "| __| ", "| |____ ", "|______|", - + /* F */ " ______ ", "| ____|", "| |__ ", "| __| ", @@ -359,51 +359,86 @@ ** Render an 8-digit hexadecimal string as ascii arg. ** Space to hold the result is obtained from malloc() and should be freed ** by the caller. */ char *captcha_render(const char *zPw){ - char *z = malloc( 600 ); + char *z = fossil_malloc( 10*6*strlen(zPw) + 7 ); int i, j, k, m; const char *zChar; + unsigned char x; + int y; k = 0; for(i=0; i<6; i++){ - for(j=0; j<8; j++){ - unsigned char v = hexValue(zPw[j]); + x = 0; + for(j=0; zPw[j]; j++){ + unsigned char v = hex_digit_value(zPw[j]); + x = (x<<4) + v; + switch( x ){ + case 0x7a: + case 0xfa: + y = 3; + break; + case 0x47: + y = 2; + break; + case 0xf6: + case 0xa9: + case 0xa4: + case 0xa1: + case 0x9a: + case 0x76: + case 0x61: + case 0x67: + case 0x69: + case 0x41: + case 0x42: + case 0x43: + case 0x4a: + y = 1; + break; + default: + y = 0; + break; + } zChar = azFont3[6*v + i]; + while( y && zChar[0]==' ' ){ y--; zChar++; } + while( y && z[k-1]==' ' ){ y--; k--; } for(m=0; zChar[m]; m++){ z[k++] = zChar[m]; } } z[k++] = '\n'; } z[k] = 0; - return z; + return z; } #endif /* CAPTCHA==3 */ /* ** COMMAND: test-captcha +** +** Render an ASCII-art captcha for numbers given on the command line. */ void test_captcha(void){ int i; unsigned int v; char *z; for(i=2; i<g.argc; i++){ char zHex[30]; v = (unsigned int)atoi(g.argv[i]); - sprintf(zHex, "%x", v); + sqlite3_snprintf(sizeof(zHex), zHex, "%x", v); z = captcha_render(zHex); - printf("%s:\n%s", zHex, z); + fossil_print("%s:\n%s", zHex, z); free(z); } } /* ** Compute a seed value for a captcha. The seed is public and is sent -** has a hidden parameter with the page that contains the captcha. Knowledge +** as a hidden parameter with the page that contains the captcha. Knowledge ** of the seed is insufficient for determining the captcha without additional ** information held only on the server and never revealed. */ unsigned int captcha_seed(void){ unsigned int x; @@ -412,23 +447,27 @@ return x; } /* ** Translate a captcha seed value into the captcha password string. +** The returned string is static and overwritten on each call to +** this function. */ -char *captcha_decode(unsigned int seed){ +const char *captcha_decode(unsigned int seed){ const char *zSecret; const char *z; Blob b; static char zRes[20]; zSecret = db_get("captcha-secret", 0); if( zSecret==0 ){ + db_unprotect(PROTECT_CONFIG); db_multi_exec( "REPLACE INTO config(name,value)" " VALUES('captcha-secret', lower(hex(randomblob(20))));" ); + db_protect_pop(); zSecret = db_get("captcha-secret", 0); assert( zSecret!=0 ); } blob_init(&b, 0, 0); blob_appendf(&b, "%s-%x", zSecret, seed); @@ -436,5 +475,236 @@ z = blob_buffer(&b); memcpy(zRes, z, 8); zRes[8] = 0; return zRes; } + +/* +** Return true if a CAPTCHA is required for editing wiki or tickets or for +** adding attachments. +** +** A CAPTCHA is required in those cases if the user is not logged in (if they +** are user "nobody") and if the "require-captcha" setting is true. The +** "require-captcha" setting is controlled on the Admin/Access page. It +** defaults to true. +*/ +int captcha_needed(void){ + return login_is_nobody() && db_get_boolean("require-captcha", 1); +} + +/* +** If a captcha is required but the correct captcha code is not supplied +** in the query parameters, then return false (0). +** +** If no captcha is required or if the correct captcha is supplied, return +** true (non-zero). +** +** The query parameters examined are "captchaseed" for the seed value and +** "captcha" for text that the user types in response to the captcha prompt. +*/ +int captcha_is_correct(int bAlwaysNeeded){ + const char *zSeed; + const char *zEntered; + const char *zDecode; + char z[30]; + int i; + if( !bAlwaysNeeded && !captcha_needed() ){ + return 1; /* No captcha needed */ + } + zSeed = P("captchaseed"); + if( zSeed==0 ) return 0; + zEntered = P("captcha"); + if( zEntered==0 || strlen(zEntered)!=8 ) return 0; + zDecode = captcha_decode((unsigned int)atoi(zSeed)); + assert( strlen(zDecode)==8 ); + for(i=0; i<8; i++){ + char c = zEntered[i]; + if( c>='A' && c<='F' ) c += 'a' - 'A'; + if( c=='O' ) c = '0'; + z[i] = c; + } + if( strncmp(zDecode,z,8)!=0 ) return 0; + return 1; +} + +/* +** Generate a captcha display together with the necessary hidden parameter +** for the seed and the entry box into which the user will type the text of +** the captcha. This is typically done at the very bottom of a form. +** +** This routine is a no-op if no captcha is required. +*/ +void captcha_generate(int showButton){ + unsigned int uSeed; + const char *zDecoded; + char *zCaptcha; + + if( !captcha_needed() ) return; + uSeed = captcha_seed(); + zDecoded = captcha_decode(uSeed); + zCaptcha = captcha_render(zDecoded); + @ <div class="captcha"><table class="captcha"><tr><td><pre class="captcha"> + @ %h(zCaptcha) + @ </pre> + @ Enter security code shown above: + @ <input type="hidden" name="captchaseed" value="%u(uSeed)" /> + @ <input type="text" name="captcha" size=8 /> + if( showButton ){ + @ <input type="submit" value="Submit"> + } + @ <br/>\ + captcha_speakit_button(uSeed, 0); + @ </td></tr></table></div> +} + +/* +** Add a "Speak the captcha" button. +*/ +void captcha_speakit_button(unsigned int uSeed, const char *zMsg){ + if( zMsg==0 ) zMsg = "Speak the text"; + @ <input aria-label="%h(zMsg)" type="button" value="%h(zMsg)" \ + @ id="speakthetext"> + @ <script nonce="%h(style_nonce())">/* captcha_speakit_button() */ + @ document.getElementById("speakthetext").onclick = function(){ + @ var audio = window.fossilAudioCaptcha \ + @ || new Audio("%R/captcha-audio/%u(uSeed)"); + @ window.fossilAudioCaptcha = audio; + @ audio.currentTime = 0; + @ audio.play(); + @ } + @ </script> +} + +/* +** WEBPAGE: test-captcha +** Test the captcha-generator by rendering the value of the name= query +** parameter using ascii-art. If name= is omitted, show a random 16-digit +** hexadecimal number. +*/ +void captcha_test(void){ + const char *zPw = P("name"); + if( zPw==0 || zPw[0]==0 ){ + u64 x; + sqlite3_randomness(sizeof(x), &x); + zPw = mprintf("%016llx", x); + } + style_set_current_feature("test"); + style_header("Captcha Test"); + @ <pre> + @ %s(captcha_render(zPw)) + @ </pre> + style_finish_page(); +} + +/* +** Check to see if the current request is coming from an agent that might +** be a spider. If the agent is not a spider, then return 0 without doing +** anything. But if the user agent appears to be a spider, offer +** a captcha challenge to allow the user agent to prove that it is human +** and return non-zero. +*/ +int exclude_spiders(void){ + const char *zCookieValue; + char *zCookieName; + if( g.isHuman ) return 0; +#if 0 + { + const char *zReferer = P("HTTP_REFERER"); + if( zReferer && strncmp(g.zBaseURL, zReferer, strlen(g.zBaseURL))==0 ){ + return 0; + } + } +#endif + zCookieName = mprintf("fossil-cc-%.10s", db_get("project-code","x")); + zCookieValue = P(zCookieName); + if( zCookieValue && atoi(zCookieValue)==1 ) return 0; + if( captcha_is_correct(0) ){ + cgi_set_cookie(zCookieName, "1", login_cookie_path(), 8*3600); + return 0; + } + + /* This appears to be a spider. Offer the captcha */ + style_set_current_feature("captcha"); + style_header("Verification"); + @ <form method='POST' action='%s(g.zPath)'> + cgi_query_parameters_to_hidden(); + @ <p>Please demonstrate that you are human, not a spider or robot</p> + captcha_generate(1); + @ </form> + style_finish_page(); + return 1; +} + +/* +** Generate a WAV file that reads aloud the hex digits given by +** zHex. +*/ +static void captcha_wav(const char *zHex, Blob *pOut){ + int i; + const int szWavHdr = 44; + blob_init(pOut, 0, 0); + blob_resize(pOut, szWavHdr); /* Space for the WAV header */ + pOut->nUsed = szWavHdr; + memset(pOut->aData, 0, szWavHdr); + if( zHex==0 || zHex[0]==0 ) zHex = "0"; + for(i=0; zHex[i]; i++){ + int v = hex_digit_value(zHex[i]); + int sz; + int nData; + const unsigned char *pData; + char zSoundName[50]; + sqlite3_snprintf(sizeof(zSoundName),zSoundName,"sounds/%c.wav", + "0123456789abcdef"[v]); + /* Extra silence in between letters */ + if( i>0 ){ + int nQuiet = 3000; + blob_resize(pOut, pOut->nUsed+nQuiet); + memset(pOut->aData+pOut->nUsed-nQuiet, 0x80, nQuiet); + } + pData = builtin_file(zSoundName, &sz); + nData = sz - szWavHdr; + blob_resize(pOut, pOut->nUsed+nData); + memcpy(pOut->aData+pOut->nUsed-nData, pData+szWavHdr, nData); + if( zHex[i+1]==0 ){ + int len = pOut->nUsed + 36; + memcpy(pOut->aData, pData, szWavHdr); + pOut->aData[4] = (char)(len&0xff); + pOut->aData[5] = (char)((len>>8)&0xff); + pOut->aData[6] = (char)((len>>16)&0xff); + pOut->aData[7] = (char)((len>>24)&0xff); + len = pOut->nUsed; + pOut->aData[40] = (char)(len&0xff); + pOut->aData[41] = (char)((len>>8)&0xff); + pOut->aData[42] = (char)((len>>16)&0xff); + pOut->aData[43] = (char)((len>>24)&0xff); + } + } +} + +/* +** WEBPAGE: /captcha-audio +** +** Return a WAV file that pronounces the digits of the captcha that +** is determined by the seed given in the name= query parameter. +*/ +void captcha_wav_page(void){ + const char *zSeed = P("name"); + const char *zDecode = captcha_decode((unsigned int)atoi(zSeed)); + Blob audio; + captcha_wav(zDecode, &audio); + cgi_set_content_type("audio/wav"); + cgi_set_content(&audio); +} + +/* +** WEBPAGE: /test-captcha-audio +** +** Return a WAV file that pronounces the hex digits of the name= +** query parameter. +*/ +void captcha_test_wav_page(void){ + const char *zSeed = P("name"); + Blob audio; + captcha_wav(zSeed, &audio); + cgi_set_content_type("audio/wav"); + cgi_set_content(&audio); +} Index: src/cgi.c ================================================================== --- src/cgi.c +++ src/cgi.c @@ -2,11 +2,11 @@ ** Copyright (c) 2006 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: @@ -13,22 +13,28 @@ ** drh@hwaci.com ** http://www.hwaci.com/drh/ ** ******************************************************************************* ** -** This file contains C functions and procedures that provide useful -** services to CGI programs. There are procedures for parsing and -** dispensing QUERY_STRING parameters and cookies, the "mprintf()" -** formatting function and its cousins, and routines to encode and -** decode strings in HTML or HTTP. +** This file contains C functions and procedures used by CGI programs +** (Fossil launched as CGI) to interpret CGI environment variables, +** gather the results, and send they reply back to the CGI server. +** This file also contains routines for running a simple web-server +** (the "fossil ui" or "fossil server" command) and launching subprocesses +** to handle each inbound HTTP request using CGI. +** +** This file contains routines used by Fossil when it is acting as a +** CGI client. For the code used by Fossil when it is acting as a +** CGI server (for the /ext webpage) see the "extcgi.c" source file. */ #include "config.h" -#ifdef __MINGW32__ -# include <windows.h> /* for Sleep once server works again */ -# include <winsock2.h> /* socket operations */ -# define sleep Sleep /* windows does not have sleep, but Sleep */ -# include <ws2tcpip.h> +#ifdef _WIN32 +# if !defined(_WIN32_WINNT) +# define _WIN32_WINNT 0x0501 +# endif +# include <winsock2.h> +# include <ws2tcpip.h> #else # include <sys/socket.h> # include <netinet/in.h> # include <arpa/inet.h> # include <sys/times.h> @@ -42,40 +48,62 @@ #include <time.h> #include <stdio.h> #include <stdlib.h> #include <unistd.h> #include "cgi.h" +#include "cygsup.h" #if INTERFACE /* ** Shortcuts for cgi_parameter. P("x") returns the value of query parameter ** or cookie "x", or NULL if there is no such parameter or cookie. PD("x","y") ** does the same except "y" is returned in place of NULL if there is not match. */ #define P(x) cgi_parameter((x),0) #define PD(x,y) cgi_parameter((x),(y)) -#define QP(x) quotable_string(cgi_parameter((x),0)) -#define QPD(x,y) quotable_string(cgi_parameter((x),(y))) +#define PT(x) cgi_parameter_trimmed((x),0) +#define PDT(x,y) cgi_parameter_trimmed((x),(y)) +#define PB(x) cgi_parameter_boolean(x) +#define PCK(x) cgi_parameter_checked(x,1) +#define PIF(x,y) cgi_parameter_checked(x,y) +/* +** Shortcut for the cgi_printf() routine. Instead of using the +** +** @ ... +** +** notation provided by the translate.c utility, you can also +** optionally use: +** +** CX(...) +*/ +#define CX cgi_printf /* ** Destinations for output text. */ #define CGI_HEADER 0 #define CGI_BODY 1 +/* +** Flags for SSH HTTP clients +*/ +#define CGI_SSH_CLIENT 0x0001 /* Client is SSH */ +#define CGI_SSH_COMPAT 0x0002 /* Compat for old SSH transport */ +#define CGI_SSH_FOSSIL 0x0004 /* Use new Fossil SSH transport */ + #endif /* INTERFACE */ /* ** The HTTP reply is generated in two pieces: the header and the body. -** These pieces are generated separately because they are not necessary +** These pieces are generated separately because they are not necessarily ** produced in order. Parts of the header might be built after all or ** part of the body. The header and body are accumulated in separate ** Blob structures then output sequentially once everything has been ** built. ** -** The cgi_destination() interface switch between the buffers. +** The cgi_destination() interface switches between the buffers. */ static Blob cgiContent[2] = { BLOB_INITIALIZER, BLOB_INITIALIZER }; static Blob *pContent = &cgiContent[0]; /* @@ -94,10 +122,21 @@ default: { cgi_panic("bad destination"); } } } + +/* +** Check to see if the header contains the zNeedle string. Return true +** if it does and false if it does not. +*/ +int cgi_header_contains(const char *zNeedle){ + return strstr(blob_str(&cgiContent[0]), zNeedle)!=0; +} +int cgi_body_contains(const char *zNeedle){ + return strstr(blob_str(&cgiContent[1]), zNeedle)!=0; +} /* ** Append reply content to what already exists. */ void cgi_append_content(const char *zData, int nAmt){ @@ -116,10 +155,17 @@ ** Return a pointer to the CGI output blob. */ Blob *cgi_output_blob(void){ return pContent; } + +/* +** Return complete text of the output header +*/ +const char *cgi_header(void){ + return blob_str(&cgiContent[0]); +} /* ** Combine the header and body of the CGI into a single string. */ static void cgi_combine_header_and_body(void){ @@ -131,28 +177,30 @@ } /* ** Return a pointer to the HTTP reply text. */ -char *cgi_extract_content(int *pnAmt){ +char *cgi_extract_content(void){ cgi_combine_header_and_body(); return blob_buffer(&cgiContent[0]); } /* ** Additional information used to form the HTTP reply */ -static char *zContentType = "text/html"; /* Content type of the reply */ -static char *zReplyStatus = "OK"; /* Reply status description */ +static const char *zContentType = "text/html"; /* Content type of the reply */ +static const char *zReplyStatus = "OK"; /* Reply status description */ static int iReplyStatus = 200; /* Reply status code */ static Blob extraHeader = BLOB_INITIALIZER; /* Extra header text */ +static int rangeStart = 0; /* Start of Range: */ +static int rangeEnd = 0; /* End of Range: plus 1 */ /* ** Set the reply content type */ void cgi_set_content_type(const char *zType){ - zContentType = mprintf("%s", zType); + zContentType = fossil_strdup(zType); } /* ** Set the reply content to the specified BLOB. */ @@ -165,97 +213,73 @@ /* ** Set the reply status code */ void cgi_set_status(int iStat, const char *zStat){ - zReplyStatus = mprintf("%s", zStat); + zReplyStatus = fossil_strdup(zStat); iReplyStatus = iStat; } /* ** Append text to the header of an HTTP reply */ void cgi_append_header(const char *zLine){ blob_append(&extraHeader, zLine, -1); } +void cgi_printf_header(const char *zLine, ...){ + va_list ap; + va_start(ap, zLine); + blob_vappendf(&extraHeader, zLine, ap); + va_end(ap); +} /* -** Set a cookie. +** Set a cookie by queuing up the appropriate HTTP header output. If +** !g.isHTTP, this is a no-op. ** -** Zero lifetime implies a session cookie. +** Zero lifetime implies a session cookie. A negative one expires +** the cookie immediately. */ void cgi_set_cookie( const char *zName, /* Name of the cookie */ const char *zValue, /* Value of the cookie. Automatically escaped */ const char *zPath, /* Path cookie applies to. NULL means "/" */ int lifetime /* Expiration of the cookie in seconds from now */ ){ - if( zPath==0 ) zPath = g.zTop; - if( lifetime>0 ){ - lifetime += (int)time(0); + char const *zSecure = ""; + if(!g.isHTTP) return /* e.g. JSON CLI mode, where g.zTop is not set */; + else if( zPath==0 ){ + zPath = g.zTop; + if( zPath[0]==0 ) zPath = "/"; + } + if( g.zBaseURL!=0 && strncmp(g.zBaseURL, "https:", 6)==0 ){ + zSecure = " secure;"; + } + if( lifetime!=0 ){ blob_appendf(&extraHeader, - "Set-Cookie: %s=%t; Path=%s; expires=%z; Version=1\r\n", - zName, zValue, zPath, cgi_rfc822_datestamp(lifetime)); + "Set-Cookie: %s=%t; Path=%s; max-age=%d; HttpOnly; " + "%s Version=1\r\n", + zName, lifetime>0 ? zValue : "null", zPath, lifetime, zSecure); }else{ blob_appendf(&extraHeader, - "Set-Cookie: %s=%t; Path=%s; Version=1\r\n", - zName, zValue, zPath); - } -} - -#if 0 -/* -** Add an ETag header line -*/ -static char *cgi_add_etag(char *zTxt, int nLen){ - MD5Context ctx; - unsigned char digest[16]; - int i, j; - char zETag[64]; - - MD5Init(&ctx); - MD5Update(&ctx,zTxt,nLen); - MD5Final(digest,&ctx); - for(j=i=0; i<16; i++,j+=2){ - bprintf(&zETag[j],sizeof(zETag)-j,"%02x",(int)digest[i]); - } - blob_appendf(&extraHeader, "ETag: %s\r\n", zETag); - return strdup(zETag); -} - -/* -** Do some cache control stuff. First, we generate an ETag and include it in -** the response headers. Second, we do whatever is necessary to determine if -** the request was asking about caching and whether we need to send back the -** response body. If we shouldn't send a body, return non-zero. -** -** Currently, we just check the ETag against any If-None-Match header. -** -** FIXME: In some cases (attachments, file contents) we could check -** If-Modified-Since headers and always include Last-Modified in responses. -*/ -static int check_cache_control(void){ - /* FIXME: there's some gotchas wth cookies and some headers. */ - char *zETag = cgi_add_etag(blob_buffer(&cgiContent),blob_size(&cgiContent)); - char *zMatch = P("HTTP_IF_NONE_MATCH"); - - if( zETag!=0 && zMatch!=0 ) { - char *zBuf = strdup(zMatch); - if( zBuf!=0 ){ - char *zTok = 0; - char *zPos; - for( zTok = strtok_r(zBuf, ",\"",&zPos); - zTok && strcasecmp(zTok,zETag); - zTok = strtok_r(0, ",\"",&zPos)){} - free(zBuf); - if(zTok) return 1; - } - } - - return 0; -} -#endif + "Set-Cookie: %s=%t; Path=%s; HttpOnly; " + "%s Version=1\r\n", + zName, zValue, zPath, zSecure); + } +} + + +/* +** Return true if the response should be sent with Content-Encoding: gzip. +*/ +static int is_gzippable(void){ + if( g.fNoHttpCompress ) return 0; + if( strstr(PD("HTTP_ACCEPT_ENCODING", ""), "gzip")==0 ) return 0; + return strncmp(zContentType, "text/", 5)==0 + || sqlite3_strglob("application/*xml", zContentType)==0 + || sqlite3_strglob("application/*javascript", zContentType)==0; +} /* ** Do a normal HTTP reply */ void cgi_reply(void){ @@ -263,104 +287,228 @@ if( iReplyStatus<=0 ){ iReplyStatus = 200; zReplyStatus = "OK"; } -#if 0 - if( iReplyStatus==200 && check_cache_control() ) { - /* change the status to "unchanged" and we can skip sending the - ** actual response body. Obviously we only do this when we _have_ a - ** body (code 200). - */ - iReplyStatus = 304; - zReplyStatus = "Not Modified"; - } -#endif - if( g.fullHttpReply ){ + if( rangeEnd>0 + && iReplyStatus==200 + && fossil_strcmp(P("REQUEST_METHOD"),"GET")==0 + ){ + iReplyStatus = 206; + zReplyStatus = "Partial Content"; + } fprintf(g.httpOut, "HTTP/1.0 %d %s\r\n", iReplyStatus, zReplyStatus); fprintf(g.httpOut, "Date: %s\r\n", cgi_rfc822_datestamp(time(0))); fprintf(g.httpOut, "Connection: close\r\n"); + fprintf(g.httpOut, "X-UA-Compatible: IE=edge\r\n"); }else{ + assert( rangeEnd==0 ); fprintf(g.httpOut, "Status: %d %s\r\n", iReplyStatus, zReplyStatus); } + if( etag_tag()[0]!=0 ){ + fprintf(g.httpOut, "ETag: %s\r\n", etag_tag()); + fprintf(g.httpOut, "Cache-Control: max-age=%d\r\n", etag_maxage()); + if( etag_mtime()>0 ){ + fprintf(g.httpOut, "Last-Modified: %s\r\n", + cgi_rfc822_datestamp(etag_mtime())); + } + }else if( g.isConst ){ + /* isConst means that the reply is guaranteed to be invariant, even + ** after configuration changes and/or Fossil binary recompiles. */ + fprintf(g.httpOut, "Cache-Control: max-age=31536000\r\n"); + }else{ + fprintf(g.httpOut, "Cache-control: no-cache\r\n"); + } if( blob_size(&extraHeader)>0 ){ fprintf(g.httpOut, "%s", blob_buffer(&extraHeader)); } - if( g.isConst ){ - /* constant means that the input URL will _never_ generate anything - ** else. In the case of attachments, the contents won't change because - ** an attempt to change them generates a new attachment number. In the - ** case of most /getfile calls for specific versions, the only way the - ** content changes is if someone breaks the SCM. And if that happens, a - ** stale cache is the least of the problem. So we provide an Expires - ** header set to a reasonable period (default: one week). - */ - /*time_t expires = time(0) + atoi(db_config("constant_expires","604800"));*/ - time_t expires = time(0) + 604800; - fprintf(g.httpOut, "Expires: %s\r\n", cgi_rfc822_datestamp(expires)); - }else{ - fprintf(g.httpOut, "Cache-control: no-cache, no-store\r\n"); - } + /* Add headers to turn on useful security options in browsers. */ + fprintf(g.httpOut, "X-Frame-Options: SAMEORIGIN\r\n"); + /* This stops fossil pages appearing in frames or iframes, preventing + ** click-jacking attacks on supporting browsers. + ** + ** Other good headers would be + ** Strict-Transport-Security: max-age=62208000 + ** if we're using https. However, this would break sites which serve different + ** content on http and https protocols. Also, + ** X-Content-Security-Policy: allow 'self' + ** would help mitigate some XSS and data injection attacks, but will break + ** deliberate inclusion of external resources, such as JavaScript syntax + ** highlighter scripts. + ** + ** These headers are probably best added by the web server hosting fossil as + ** a CGI script. + */ /* Content intended for logged in users should only be cached in ** the browser, not some shared location. */ - fprintf(g.httpOut, "Content-Type: %s; charset=utf-8\r\n", zContentType); - if( strcmp(zContentType,"application/x-fossil")==0 ){ - cgi_combine_header_and_body(); - blob_compress(&cgiContent[0], &cgiContent[0]); - } + if( iReplyStatus!=304 ) { + fprintf(g.httpOut, "Content-Type: %s; charset=utf-8\r\n", zContentType); + if( fossil_strcmp(zContentType,"application/x-fossil")==0 ){ + cgi_combine_header_and_body(); + blob_compress(&cgiContent[0], &cgiContent[0]); + } - if( iReplyStatus != 304 ) { + if( is_gzippable() && iReplyStatus!=206 ){ + int i; + gzip_begin(0); + for( i=0; i<2; i++ ){ + int size = blob_size(&cgiContent[i]); + if( size>0 ) gzip_step(blob_buffer(&cgiContent[i]), size); + blob_reset(&cgiContent[i]); + } + gzip_finish(&cgiContent[0]); + fprintf(g.httpOut, "Content-Encoding: gzip\r\n"); + fprintf(g.httpOut, "Vary: Accept-Encoding\r\n"); + } total_size = blob_size(&cgiContent[0]) + blob_size(&cgiContent[1]); + if( iReplyStatus==206 ){ + fprintf(g.httpOut, "Content-Range: bytes %d-%d/%d\r\n", + rangeStart, rangeEnd-1, total_size); + total_size = rangeEnd - rangeStart; + } fprintf(g.httpOut, "Content-Length: %d\r\n", total_size); }else{ total_size = 0; } fprintf(g.httpOut, "\r\n"); - if( total_size>0 && iReplyStatus != 304 ){ + if( total_size>0 + && iReplyStatus!=304 + && fossil_strcmp(P("REQUEST_METHOD"),"HEAD")!=0 + ){ int i, size; for(i=0; i<2; i++){ size = blob_size(&cgiContent[i]); - if( size>0 ){ - fwrite(blob_buffer(&cgiContent[i]), 1, size, g.httpOut); + if( size<=rangeStart ){ + rangeStart -= size; + }else{ + int n = size - rangeStart; + if( n>total_size ){ + n = total_size; + } + fwrite(blob_buffer(&cgiContent[i])+rangeStart, 1, n, g.httpOut); + rangeStart = 0; + total_size -= n; } } } - CGIDEBUG(("DONE\n")); + fflush(g.httpOut); + CGIDEBUG(("-------- END cgi ---------\n")); + + /* After the webpage has been sent, do any useful background + ** processing. + */ + g.cgiOutput = 2; + if( g.db!=0 && iReplyStatus==200 ){ + backoffice_check_if_needed(); + } } /* ** Do a redirect request to the URL given in the argument. ** ** The URL must be relative to the base of the fossil server. */ -void cgi_redirect(const char *zURL){ +NORETURN void cgi_redirect_with_status( + const char *zURL, + int iStat, + const char *zStat +){ char *zLocation; CGIDEBUG(("redirect to %s\n", zURL)); - if( strncmp(zURL,"http:",5)==0 || strncmp(zURL,"https:",6)==0 || *zURL=='/' ){ + if( strncmp(zURL,"http:",5)==0 || strncmp(zURL,"https:",6)==0 ){ zLocation = mprintf("Location: %s\r\n", zURL); + }else if( *zURL=='/' ){ + int n1 = (int)strlen(g.zBaseURL); + int n2 = (int)strlen(g.zTop); + if( g.zBaseURL[n1-1]=='/' ) zURL++; + zLocation = mprintf("Location: %.*s%s\r\n", n1-n2, g.zBaseURL, zURL); }else{ zLocation = mprintf("Location: %s/%s\r\n", g.zBaseURL, zURL); } cgi_append_header(zLocation); cgi_reset_content(); - cgi_printf("<html>\n<p>Redirect to %h</p>\n</html>\n", zURL); - cgi_set_status(302, "Moved Temporarily"); + cgi_printf("<html>\n<p>Redirect to %h</p>\n</html>\n", zLocation); + cgi_set_status(iStat, zStat); free(zLocation); cgi_reply(); - exit(0); + fossil_exit(0); +} +NORETURN void cgi_redirect(const char *zURL){ + cgi_redirect_with_status(zURL, 302, "Moved Temporarily"); +} +NORETURN void cgi_redirect_with_method(const char *zURL){ + cgi_redirect_with_status(zURL, 307, "Temporary Redirect"); } -void cgi_redirectf(const char *zFormat, ...){ +NORETURN void cgi_redirectf(const char *zFormat, ...){ va_list ap; va_start(ap, zFormat); cgi_redirect(vmprintf(zFormat, ap)); va_end(ap); } + +/* +** Add a "Content-disposition: attachment; filename=%s" header to the reply. +*/ +void cgi_content_disposition_filename(const char *zFilename){ + char *z; + int i, n; + + /* 0123456789 123456789 123456789 123456789 123456*/ + z = mprintf("Content-Disposition: attachment; filename=\"%s\";\r\n", + file_tail(zFilename)); + n = (int)strlen(z); + for(i=43; i<n-4; i++){ + char c = z[i]; + if( fossil_isalnum(c) ) continue; + if( c=='.' || c=='-' || c=='/' ) continue; + z[i] = '_'; + } + cgi_append_header(z); + fossil_free(z); +} + +/* +** Return the URL for the caller. This is obtained from either the +** referer CGI parameter, if it exists, or the HTTP_REFERER HTTP parameter. +** If neither exist, return zDefault. +*/ +const char *cgi_referer(const char *zDefault){ + const char *zRef = P("referer"); + if( zRef==0 ){ + zRef = P("HTTP_REFERER"); + if( zRef==0 ) zRef = zDefault; + } + return zRef; +} + +/* +** Return true if the current request appears to be safe from a +** Cross-Site Request Forgery (CSRF) attack. Conditions that must +** be met: +** +** * The HTTP_REFERER must have the same origin +** * The REQUEST_METHOD must be POST - or requirePost==0 +*/ +int cgi_csrf_safe(int requirePost){ + const char *zRef = P("HTTP_REFERER"); + int nBase; + if( zRef==0 ) return 0; + if( requirePost ){ + const char *zMethod = P("REQUEST_METHOD"); + if( zMethod==0 ) return 0; + if( strcmp(zMethod,"POST")!=0 ) return 0; + } + nBase = (int)strlen(g.zBaseURL); + if( strncmp(g.zBaseURL,zRef,nBase)!=0 ) return 0; + if( zRef[nBase]!=0 && zRef[nBase]!='/' ) return 0; + return 1; +} /* ** Information about all query parameters and cookies are stored ** in these variables. */ @@ -370,10 +518,12 @@ static int seqQP = 0; /* Sequence numbers */ static struct QParam { /* One entry for each query parameter or cookie */ const char *zName; /* Parameter or cookie name */ const char *zValue; /* Value of the query parameter or cookie */ int seq; /* Order of insertion */ + char isQP; /* True for query parameters */ + char cTag; /* Tag on query parameters */ } *aParamQP; /* An array of all parameters and cookies */ /* ** Add another query parameter or cookie to the parameter set. ** zName is the name of the query parameter or cookie and zValue @@ -380,59 +530,132 @@ ** is its fully decoded value. ** ** zName and zValue are not copied and must not change or be ** deallocated after this routine returns. */ -void cgi_set_parameter_nocopy(const char *zName, const char *zValue){ +void cgi_set_parameter_nocopy(const char *zName, const char *zValue, int isQP){ if( nAllocQP<=nUsedQP ){ nAllocQP = nAllocQP*2 + 10; - aParamQP = realloc( aParamQP, nAllocQP*sizeof(aParamQP[0]) ); - if( aParamQP==0 ) exit(1); + if( nAllocQP>1000 ){ + /* Prevent a DOS service attack against the framework */ + fossil_fatal("Too many query parameters"); + } + aParamQP = fossil_realloc( aParamQP, nAllocQP*sizeof(aParamQP[0]) ); } aParamQP[nUsedQP].zName = zName; aParamQP[nUsedQP].zValue = zValue; if( g.fHttpTrace ){ fprintf(stderr, "# cgi: %s = [%s]\n", zName, zValue); } aParamQP[nUsedQP].seq = seqQP++; + aParamQP[nUsedQP].isQP = isQP; + aParamQP[nUsedQP].cTag = 0; nUsedQP++; sortQP = 1; } + +/* +** Add another query parameter or cookie to the parameter set. +** zName is the name of the query parameter or cookie and zValue +** is its fully decoded value. zName will be modified to be an +** all lowercase string. +** +** zName and zValue are not copied and must not change or be +** deallocated after this routine returns. This routine changes +** all ASCII alphabetic characters in zName to lower case. The +** caller must not change them back. +*/ +void cgi_set_parameter_nocopy_tolower( + char *zName, + const char *zValue, + int isQP +){ + int i; + for(i=0; zName[i]; i++){ zName[i] = fossil_tolower(zName[i]); } + cgi_set_parameter_nocopy(zName, zValue, isQP); +} /* ** Add another query parameter or cookie to the parameter set. ** zName is the name of the query parameter or cookie and zValue ** is its fully decoded value. ** ** Copies are made of both the zName and zValue parameters. */ void cgi_set_parameter(const char *zName, const char *zValue){ - cgi_set_parameter_nocopy(mprintf("%s",zName), mprintf("%s",zValue)); + cgi_set_parameter_nocopy(fossil_strdup(zName),fossil_strdup(zValue), 0); +} +void cgi_set_query_parameter(const char *zName, const char *zValue){ + cgi_set_parameter_nocopy(fossil_strdup(zName),fossil_strdup(zValue), 1); } /* ** Replace a parameter with a new value. */ void cgi_replace_parameter(const char *zName, const char *zValue){ int i; for(i=0; i<nUsedQP; i++){ - if( strcmp(aParamQP[i].zName,zName)==0 ){ + if( fossil_strcmp(aParamQP[i].zName,zName)==0 ){ + aParamQP[i].zValue = zValue; + return; + } + } + cgi_set_parameter_nocopy(zName, zValue, 0); +} +void cgi_replace_query_parameter(const char *zName, const char *zValue){ + int i; + for(i=0; i<nUsedQP; i++){ + if( fossil_strcmp(aParamQP[i].zName,zName)==0 ){ aParamQP[i].zValue = zValue; + assert( aParamQP[i].isQP ); + return; + } + } + cgi_set_parameter_nocopy(zName, zValue, 1); +} +void cgi_replace_query_parameter_tolower(char *zName, const char *zValue){ + int i; + for(i=0; zName[i]; i++){ zName[i] = fossil_tolower(zName[i]); } + cgi_replace_query_parameter(zName, zValue); +} + +/* +** Delete a parameter. +*/ +void cgi_delete_parameter(const char *zName){ + int i; + for(i=0; i<nUsedQP; i++){ + if( fossil_strcmp(aParamQP[i].zName,zName)==0 ){ + --nUsedQP; + if( i<nUsedQP ){ + memmove(aParamQP+i, aParamQP+i+1, sizeof(*aParamQP)*(nUsedQP-i)); + } + return; + } + } +} +void cgi_delete_query_parameter(const char *zName){ + int i; + for(i=0; i<nUsedQP; i++){ + if( fossil_strcmp(aParamQP[i].zName,zName)==0 ){ + assert( aParamQP[i].isQP ); + --nUsedQP; + if( i<nUsedQP ){ + memmove(aParamQP+i, aParamQP+i+1, sizeof(*aParamQP)*(nUsedQP-i)); + } return; } } - cgi_set_parameter_nocopy(zName, zValue); } /* ** Add a query parameter. The zName portion is fixed but a copy ** must be made of zValue. */ void cgi_setenv(const char *zName, const char *zValue){ - cgi_set_parameter_nocopy(zName, mprintf("%s",zValue)); + cgi_set_parameter_nocopy(zName, fossil_strdup(zValue), 0); } - /* ** Add a list of query parameters or cookies to the parameter set. ** ** Each parameter is of the form NAME=VALUE. Both the NAME and the @@ -447,23 +670,29 @@ ** are ignored. ** ** * it is impossible for a cookie or query parameter to ** override the value of an environment variable since ** environment variables always have uppercase names. +** +** 2018-03-29: Also ignore the entry if NAME that contains any characters +** other than [a-zA-Z0-9_]. There are no known exploits involving unusual +** names that contain characters outside that set, but it never hurts to +** be extra cautious when sanitizing inputs. ** ** Parameters are separated by the "terminator" character. Whitespace ** before the NAME is ignored. ** ** The input string "z" is modified but no copies is made. "z" ** should not be deallocated or changed again after this routine ** returns or it will corrupt the parameter table. */ static void add_param_list(char *z, int terminator){ + int isQP = terminator=='&'; while( *z ){ char *zName; char *zValue; - while( isspace(*z) ){ z++; } + while( fossil_isspace(*z) ){ z++; } zName = z; while( *z && *z!='=' && *z!=terminator ){ z++; } if( *z=='=' ){ *z = 0; z++; @@ -476,13 +705,20 @@ dehttpize(zValue); }else{ if( *z ){ *z++ = 0; } zValue = ""; } - if( islower(zName[0]) ){ - cgi_set_parameter_nocopy(zName, zValue); + if( zName[0] && fossil_no_strange_characters(zName+1) ){ + if( fossil_islower(zName[0]) ){ + cgi_set_parameter_nocopy(zName, zValue, isQP); + }else if( fossil_isupper(zName[0]) ){ + cgi_set_parameter_nocopy_tolower(zName, zValue, isQP); + } } +#ifdef FOSSIL_ENABLE_JSON + json_setenv( zName, cson_value_new_string(zValue,strlen(zValue)) ); +#endif /* FOSSIL_ENABLE_JSON */ } } /* ** *pz is a string that consists of multiple lines of text. This @@ -511,42 +747,42 @@ return z; } /* ** The input *pz points to content that is terminated by a "\r\n" -** followed by the boundry marker zBoundry. An extra "--" may or -** may not be appended to the boundry marker. There are *pLen characters +** followed by the boundary marker zBoundary. An extra "--" may or +** may not be appended to the boundary marker. There are *pLen characters ** in *pz. ** ** This routine adds a "\000" to the end of the content (overwriting ** the "\r\n") and returns a pointer to the content. The *pz input -** is adjusted to point to the first line following the boundry. +** is adjusted to point to the first line following the boundary. ** The length of the content is stored in *pnContent. */ static char *get_bounded_content( char **pz, /* Content taken from here */ int *pLen, /* Number of bytes of data in (*pz)[] */ - char *zBoundry, /* Boundry text marking the end of content */ + char *zBoundary, /* Boundary text marking the end of content */ int *pnContent /* Write the size of the content here */ ){ char *z = *pz; int len = *pLen; int i; - int nBoundry = strlen(zBoundry); + int nBoundary = strlen(zBoundary); *pnContent = len; for(i=0; i<len; i++){ - if( z[i]=='\n' && strncmp(zBoundry, &z[i+1], nBoundry)==0 ){ + if( z[i]=='\n' && strncmp(zBoundary, &z[i+1], nBoundary)==0 ){ if( i>0 && z[i-1]=='\r' ) i--; z[i] = 0; *pnContent = i; - i += nBoundry; + i += nBoundary; break; } } *pz = &z[i]; get_line_from_string(pz, pLen); - return z; + return z; } /* ** Tokenize a line of text into as many as nArg tokens. Make ** azArg[] point to the start of each token. @@ -571,11 +807,11 @@ ** in the example above. */ static int tokenize_line(char *z, int mxArg, char **azArg){ int i = 0; while( *z ){ - while( isspace(*z) || *z==';' ){ z++; } + while( fossil_isspace(*z) || *z==';' ){ z++; } if( *z=='"' && z[1] ){ *z = 0; z++; if( i<mxArg-1 ){ azArg[i++] = z; } while( *z && *z!='"' ){ z++; } @@ -582,11 +818,11 @@ if( *z==0 ) break; *z = 0; z++; }else{ if( i<mxArg-1 ){ azArg[i++] = z; } - while( *z && !isspace(*z) && *z!=';' && *z!='"' ){ z++; } + while( *z && !fossil_isspace(*z) && *z!=';' && *z!='"' ){ z++; } if( *z && *z!='"' ){ *z = 0; z++; } } @@ -605,103 +841,404 @@ ** table. */ static void process_multipart_form_data(char *z, int len){ char *zLine; int nArg, i; - char *zBoundry; + char *zBoundary; char *zValue; char *zName = 0; int showBytes = 0; char *azArg[50]; - zBoundry = get_line_from_string(&z, &len); - if( zBoundry==0 ) return; + zBoundary = get_line_from_string(&z, &len); + if( zBoundary==0 ) return; while( (zLine = get_line_from_string(&z, &len))!=0 ){ if( zLine[0]==0 ){ int nContent = 0; - zValue = get_bounded_content(&z, &len, zBoundry, &nContent); - if( zName && zValue && islower(zName[0]) ){ - cgi_set_parameter_nocopy(zName, zValue); - if( showBytes ){ - cgi_set_parameter_nocopy(mprintf("%s:bytes", zName), - mprintf("%d",nContent)); + zValue = get_bounded_content(&z, &len, zBoundary, &nContent); + if( zName && zValue ){ + if( fossil_islower(zName[0]) ){ + cgi_set_parameter_nocopy(zName, zValue, 1); + if( showBytes ){ + cgi_set_parameter_nocopy(mprintf("%s:bytes", zName), + mprintf("%d",nContent), 1); + } + }else if( fossil_isupper(zName[0]) ){ + cgi_set_parameter_nocopy_tolower(zName, zValue, 1); + if( showBytes ){ + cgi_set_parameter_nocopy_tolower(mprintf("%s:bytes", zName), + mprintf("%d",nContent), 1); + } } } zName = 0; showBytes = 0; }else{ - nArg = tokenize_line(zLine, sizeof(azArg)/sizeof(azArg[0]), azArg); + nArg = tokenize_line(zLine, count(azArg), azArg); for(i=0; i<nArg; i++){ - int c = tolower(azArg[i][0]); + int c = fossil_tolower(azArg[i][0]); int n = strlen(azArg[i]); if( c=='c' && sqlite3_strnicmp(azArg[i],"content-disposition:",n)==0 ){ i++; }else if( c=='n' && sqlite3_strnicmp(azArg[i],"name=",n)==0 ){ zName = azArg[++i]; }else if( c=='f' && sqlite3_strnicmp(azArg[i],"filename=",n)==0 ){ char *z = azArg[++i]; - if( zName && z && islower(zName[0]) ){ - cgi_set_parameter_nocopy(mprintf("%s:filename",zName), z); + if( zName && z ){ + if( fossil_islower(zName[0]) ){ + cgi_set_parameter_nocopy(mprintf("%s:filename",zName), z, 1); + }else if( fossil_isupper(zName[0]) ){ + cgi_set_parameter_nocopy_tolower(mprintf("%s:filename",zName), + z, 1); + } } showBytes = 1; }else if( c=='c' && sqlite3_strnicmp(azArg[i],"content-type:",n)==0 ){ char *z = azArg[++i]; - if( zName && z && islower(zName[0]) ){ - cgi_set_parameter_nocopy(mprintf("%s:mimetype",zName), z); + if( zName && z ){ + if( fossil_islower(zName[0]) ){ + cgi_set_parameter_nocopy(mprintf("%s:mimetype",zName), z, 1); + }else if( fossil_isupper(zName[0]) ){ + cgi_set_parameter_nocopy_tolower(mprintf("%s:mimetype",zName), + z, 1); + } } } } } - } + } +} + + +#ifdef FOSSIL_ENABLE_JSON +/* +** Internal helper for cson_data_source_FILE_n(). +*/ +typedef struct CgiPostReadState_ { + FILE * fh; + unsigned int len; + unsigned int pos; +} CgiPostReadState; + +/* +** cson_data_source_f() impl which reads only up to +** a specified amount of data from its input FILE. +** state MUST be a full populated (CgiPostReadState*). +*/ +static int cson_data_source_FILE_n( void * state, + void * dest, + unsigned int * n ){ + if( ! state || !dest || !n ) return cson_rc.ArgError; + else { + CgiPostReadState * st = (CgiPostReadState *)state; + if( st->pos >= st->len ){ + *n = 0; + return 0; + }else if( !*n || ((st->pos + *n) > st->len) ){ + return cson_rc.RangeError; + }else{ + unsigned int rsz = (unsigned int)fread( dest, 1, *n, st->fh ); + if( ! rsz ){ + *n = rsz; + return feof(st->fh) ? 0 : cson_rc.IOError; + }else{ + *n = rsz; + st->pos += *n; + return 0; + } + } + } +} + +/* +** Reads a JSON object from the first contentLen bytes of zIn. On +** g.json.post is updated to hold the content. On error a +** FSL_JSON_E_INVALID_REQUEST response is output and fossil_exit() is +** called (in HTTP mode exit code 0 is used). +** +** If contentLen is 0 then the whole file is read. +*/ +void cgi_parse_POST_JSON( FILE * zIn, unsigned int contentLen ){ + cson_value * jv = NULL; + int rc; + CgiPostReadState state; + cson_parse_opt popt = cson_parse_opt_empty; + cson_parse_info pinfo = cson_parse_info_empty; + assert(g.json.gc.a && "json_bootstrap_early() was not called!"); + popt.maxDepth = 15; + state.fh = zIn; + state.len = contentLen; + state.pos = 0; + rc = cson_parse( &jv, + contentLen ? cson_data_source_FILE_n : cson_data_source_FILE, + contentLen ? (void *)&state : (void *)zIn, &popt, &pinfo ); + if(rc){ + goto invalidRequest; + }else{ + json_gc_add( "POST.JSON", jv ); + g.json.post.v = jv; + g.json.post.o = cson_value_get_object( jv ); + if( !g.json.post.o ){ /* we don't support non-Object (Array) requests */ + goto invalidRequest; + } + } + return; + invalidRequest: + cgi_set_content_type(json_guess_content_type()); + if(0 != pinfo.errorCode){ /* fancy error message */ + char * msg = mprintf("JSON parse error at line %u, column %u, " + "byte offset %u: %s", + pinfo.line, pinfo.col, pinfo.length, + cson_rc_string(pinfo.errorCode)); + json_err( FSL_JSON_E_INVALID_REQUEST, msg, 1 ); + free(msg); + }else if(jv && !g.json.post.o){ + json_err( FSL_JSON_E_INVALID_REQUEST, + "Request envelope must be a JSON Object (not array).", 1 ); + }else{ /* generic error message */ + json_err( FSL_JSON_E_INVALID_REQUEST, NULL, 1 ); + } + fossil_exit( g.isHTTP ? 0 : 1); +} +#endif /* FOSSIL_ENABLE_JSON */ + +/* +** Log HTTP traffic to a file. Begin the log on first use. Close the log +** when the argument is NULL. +*/ +void cgi_trace(const char *z){ + static FILE *pLog = 0; + if( g.fHttpTrace==0 ) return; + if( z==0 ){ + if( pLog ) fclose(pLog); + pLog = 0; + return; + } + if( pLog==0 ){ + char zFile[50]; +#if defined(_WIN32) + unsigned r; + sqlite3_randomness(sizeof(r), &r); + sqlite3_snprintf(sizeof(zFile), zFile, "httplog-%08x.txt", r); +#else + sqlite3_snprintf(sizeof(zFile), zFile, "httplog-%05d.txt", getpid()); +#endif + pLog = fossil_fopen(zFile, "wb"); + if( pLog ){ + fprintf(stderr, "# open log on %s\n", zFile); + }else{ + fprintf(stderr, "# failed to open %s\n", zFile); + return; + } + } + fputs(z, pLog); } +/* Forward declaration */ +static NORETURN void malformed_request(const char *zMsg); + /* ** Initialize the query parameter database. Information is pulled from ** the QUERY_STRING environment variable (if it exists), from standard ** input if there is POST data, and from HTTP_COOKIE. +** +** REQUEST_URI, PATH_INFO, and SCRIPT_NAME are related as follows: +** +** REQUEST_URI == SCRIPT_NAME + PATH_INFO +** +** Where "+" means concatenate. Fossil requires SCRIPT_NAME. If +** REQUEST_URI is provided but PATH_INFO is not, then PATH_INFO is +** computed from REQUEST_URI and SCRIPT_NAME. If PATH_INFO is provided +** but REQUEST_URI is not, then compute REQUEST_URI from PATH_INFO and +** SCRIPT_NAME. If neither REQUEST_URI nor PATH_INFO are provided, then +** assume that PATH_INFO is an empty string and set REQUEST_URI equal +** to PATH_INFO. +** +** Sometimes PATH_INFO is missing and SCRIPT_NAME is not a prefix of +** REQUEST_URI. (See https://fossil-scm.org/forum/forumpost/049e8650ed) +** In that case, truncate SCRIPT_NAME so that it is a proper prefix +** of REQUEST_URI. +** +** SCGI typically omits PATH_INFO. CGI sometimes omits REQUEST_URI and +** PATH_INFO when it is empty. +** +** CGI Parameter quick reference: +** +** REQUEST_URI +** _____________|____________ +** / \ +** https://www.fossil-scm.org/forum/info/12736b30c072551a?t=c +** \________________/\____/\____________________/ \_/ +** | | | | +** HTTP_HOST | PATH_INFO QUERY_STRING +** SCRIPT_NAME */ void cgi_init(void){ char *z; const char *zType; + char *zSemi; int len; + const char *zRequestUri = cgi_parameter("REQUEST_URI",0); + const char *zScriptName = cgi_parameter("SCRIPT_NAME",0); + const char *zPathInfo = cgi_parameter("PATH_INFO",0); +#ifdef _WIN32 + const char *zServerSoftware = cgi_parameter("SERVER_SOFTWARE",0); +#endif + +#ifdef FOSSIL_ENABLE_JSON + const int noJson = P("no_json")!=0; +#endif + g.isHTTP = 1; cgi_destination(CGI_BODY); + + /* We must have SCRIPT_NAME. If the web server did not supply it, try + ** to compute it from REQUEST_URI and PATH_INFO. */ + if( zScriptName==0 ){ + size_t nRU, nPI; + if( zRequestUri==0 || zPathInfo==0 ){ + malformed_request("missing SCRIPT_NAME"); /* Does not return */ + } + nRU = strlen(zRequestUri); + nPI = strlen(zPathInfo); + if( nRU<nPI ){ + malformed_request("PATH_INFO is longer than REQUEST_URI"); + } + zScriptName = fossil_strndup(zRequestUri,(int)(nRU-nPI)); + cgi_set_parameter("SCRIPT_NAME", zScriptName); + } + +#ifdef _WIN32 + /* The Microsoft IIS web server does not define REQUEST_URI, instead it uses + ** PATH_INFO for virtually the same purpose. Define REQUEST_URI the same as + ** PATH_INFO and redefine PATH_INFO with SCRIPT_NAME removed from the + ** beginning. */ + if( zServerSoftware && strstr(zServerSoftware, "Microsoft-IIS") ){ + int i, j; + cgi_set_parameter("REQUEST_URI", zPathInfo); + for(i=0; zPathInfo[i]==zScriptName[i] && zPathInfo[i]; i++){} + for(j=i; zPathInfo[j] && zPathInfo[j]!='?'; j++){} + zPathInfo = fossil_strndup(zPathInfo+i, j-i); + cgi_replace_parameter("PATH_INFO", zPathInfo); + } +#endif + if( zRequestUri==0 ){ + const char *z = zPathInfo; + if( zPathInfo==0 ){ + malformed_request("missing PATH_INFO and/or REQUEST_URI"); + } + if( z[0]=='/' ) z++; + zRequestUri = mprintf("%s/%s", zScriptName, z); + cgi_set_parameter("REQUEST_URI", zRequestUri); + } + if( zPathInfo==0 ){ + int i, j; + for(i=0; zRequestUri[i]==zScriptName[i] && zRequestUri[i]; i++){} + for(j=i; zRequestUri[j] && zRequestUri[j]!='?'; j++){} + zPathInfo = fossil_strndup(zRequestUri+i, j-i); + cgi_set_parameter_nocopy("PATH_INFO", zPathInfo, 0); + if( j>i && zScriptName[i]!=0 ){ + /* If SCRIPT_NAME is not a prefix of REQUEST_URI, truncate it so + ** that it is. See https://fossil-scm.org/forum/forumpost/049e8650ed + */ + char *zNew = fossil_strndup(zScriptName, i); + cgi_replace_parameter("SCRIPT_NAME", zNew); + } + } +#ifdef FOSSIL_ENABLE_JSON + if(noJson==0 && json_request_is_json_api(zPathInfo)){ + /* We need to change some following behaviour depending on whether + ** we are operating in JSON mode or not. We cannot, however, be + ** certain whether we should/need to be in JSON mode until the + ** PATH_INFO is set up. + */ + g.json.isJsonMode = 1; + json_bootstrap_early(); + }else{ + assert(!g.json.isJsonMode && + "Internal misconfiguration of g.json.isJsonMode"); + } +#endif + z = (char*)P("HTTP_COOKIE"); + if( z ){ + z = fossil_strdup(z); + add_param_list(z, ';'); + z = (char*)cookie_value("skin",0); + if(z){ + skin_use_alternative(z, 2); + } + } + z = (char*)P("QUERY_STRING"); if( z ){ - z = mprintf("%s",z); + z = fossil_strdup(z); add_param_list(z, '&'); + z = (char*)P("skin"); + if(z){ + char *zErr = skin_use_alternative(z, 2); + if(!zErr && !P("once")){ + cookie_write_parameter("skin","skin",z); + } + fossil_free(zErr); + } } z = (char*)P("REMOTE_ADDR"); - if( z ) g.zIpAddr = mprintf("%s", z); - - len = atoi(PD("CONTENT_LENGTH", "0")); - g.zContentType = zType = P("CONTENT_TYPE"); - if( len>0 && zType ){ - blob_zero(&g.cgiIn); - if( strcmp(zType,"application/x-www-form-urlencoded")==0 - || strncmp(zType,"multipart/form-data",19)==0 ){ - z = malloc( len+1 ); - if( z==0 ) exit(1); - len = fread(z, 1, len, g.httpIn); - z[len] = 0; - if( zType[0]=='a' ){ - add_param_list(z, '&'); - }else{ - process_multipart_form_data(z, len); - } - }else if( strcmp(zType, "application/x-fossil")==0 ){ - blob_read_from_channel(&g.cgiIn, g.httpIn, len); - blob_uncompress(&g.cgiIn, &g.cgiIn); - }else if( strcmp(zType, "application/x-fossil-debug")==0 ){ - blob_read_from_channel(&g.cgiIn, g.httpIn, len); - } - } - - z = (char*)P("HTTP_COOKIE"); - if( z ){ - z = mprintf("%s",z); - add_param_list(z, ';'); + if( z ){ + g.zIpAddr = fossil_strdup(z); + } + + len = atoi(PD("CONTENT_LENGTH", "0")); + zType = P("CONTENT_TYPE"); + zSemi = zType ? strchr(zType, ';') : 0; + if( zSemi ){ + g.zContentType = fossil_strndup(zType, (int)(zSemi-zType)); + zType = g.zContentType; + }else{ + g.zContentType = zType; + } + blob_zero(&g.cgiIn); + if( len>0 && zType ){ + if( fossil_strcmp(zType, "application/x-fossil")==0 ){ + blob_read_from_channel(&g.cgiIn, g.httpIn, len); + blob_uncompress(&g.cgiIn, &g.cgiIn); + } +#ifdef FOSSIL_ENABLE_JSON + else if( noJson==0 && g.json.isJsonMode!=0 + && json_can_consume_content_type(zType)!=0 ){ + cgi_parse_POST_JSON(g.httpIn, (unsigned int)len); + /* + Potential TODOs: + + 1) If parsing fails, immediately return an error response + without dispatching the ostensibly-upcoming JSON API. + */ + cgi_set_content_type(json_guess_content_type()); + } +#endif /* FOSSIL_ENABLE_JSON */ + else{ + blob_read_from_channel(&g.cgiIn, g.httpIn, len); + } + } +} + +/* +** Decode POST parameter information in the cgiIn content, if any. +*/ +void cgi_decode_post_parameters(void){ + int len = blob_size(&g.cgiIn); + if( len==0 ) return; + if( fossil_strcmp(g.zContentType,"application/x-www-form-urlencoded")==0 + || strncmp(g.zContentType,"multipart/form-data",19)==0 + ){ + char *z = blob_str(&g.cgiIn); + cgi_trace(z); + if( g.zContentType[0]=='a' ){ + add_param_list(z, '&'); + }else{ + process_multipart_form_data(z, len); + } + blob_init(&g.cgiIn, 0, 0); } } /* ** This is the comparison function used to sort the aParamQP[] array of @@ -709,11 +1246,11 @@ */ static int qparam_compare(const void *a, const void *b){ struct QParam *pA = (struct QParam*)a; struct QParam *pB = (struct QParam*)b; int c; - c = strcmp(pA->zName, pB->zName); + c = fossil_strcmp(pA->zName, pB->zName); if( c==0 ){ c = pA->seq - pB->seq; } return c; } @@ -738,27 +1275,31 @@ /* After sorting, remove duplicate parameters. The secondary sort ** key is aParamQP[].seq and we keep the first entry. That means ** with duplicate calls to cgi_set_parameter() the second and ** subsequent calls are effectively no-ops. */ for(i=j=1; i<nUsedQP; i++){ - if( strcmp(aParamQP[i].zName,aParamQP[i-1].zName)==0 ){ + if( fossil_strcmp(aParamQP[i].zName,aParamQP[i-1].zName)==0 ){ continue; } if( j<i ){ memcpy(&aParamQP[j], &aParamQP[i], sizeof(aParamQP[j])); } j++; } nUsedQP = j; } + + /* Invoking with a NULL zName is just a way to cause the parameters + ** to be sorted. So go ahead and bail out in that case */ + if( zName==0 || zName[0]==0 ) return 0; /* Do a binary search for a matching query parameter */ lo = 0; hi = nUsedQP-1; while( lo<=hi ){ mid = (lo+hi)/2; - c = strcmp(aParamQP[mid].zName, zName); + c = fossil_strcmp(aParamQP[mid].zName, zName); if( c==0 ){ CGIDEBUG(("mem-match [%s] = [%s]\n", zName, aParamQP[mid].zValue)); return aParamQP[mid].zValue; }else if( c>0 ){ hi = mid-1; @@ -769,25 +1310,80 @@ /* If no match is found and the name begins with an upper-case ** letter, then check to see if there is an environment variable ** with the given name. */ - if( isupper(zName[0]) ){ - const char *zValue = getenv(zName); + if( fossil_isupper(zName[0]) ){ + const char *zValue = fossil_getenv(zName); if( zValue ){ - cgi_set_parameter_nocopy(zName, zValue); + cgi_set_parameter_nocopy(zName, zValue, 0); CGIDEBUG(("env-match [%s] = [%s]\n", zName, zValue)); return zValue; } } CGIDEBUG(("no-match [%s]\n", zName)); return zDefault; } + +/* +** Return the value of a CGI parameter with leading and trailing +** spaces removed and with internal \r\n changed to just \n +*/ +char *cgi_parameter_trimmed(const char *zName, const char *zDefault){ + const char *zIn; + char *zOut, c; + int i, j; + zIn = cgi_parameter(zName, 0); + if( zIn==0 ) zIn = zDefault; + if( zIn==0 ) return 0; + while( fossil_isspace(zIn[0]) ) zIn++; + zOut = fossil_strdup(zIn); + for(i=j=0; (c = zOut[i])!=0; i++){ + if( c=='\r' && zOut[i+1]=='\n' ) continue; + zOut[j++] = c; + } + zOut[j] = 0; + while( j>0 && fossil_isspace(zOut[j-1]) ) zOut[--j] = 0; + return zOut; +} + +/* +** Return true if the CGI parameter zName exists and is not equal to 0, +** or "no" or "off". +*/ +int cgi_parameter_boolean(const char *zName){ + const char *zIn = cgi_parameter(zName, 0); + if( zIn==0 ) return 0; + return zIn[0]==0 || is_truth(zIn); +} + +/* +** Return either an empty string "" or the string "checked" depending +** on whether or not parameter zName has value iValue. If parameter +** zName does not exist, that is assumed to be the same as value 0. +** +** This routine implements the PCK(x) and PIF(x,y) macros. The PIF(x,y) +** macro generateds " checked" if the value of parameter x equals integer y. +** PCK(x) is the same as PIF(x,1). These macros are used to generate +** the "checked" attribute on checkbox and radio controls of forms. +*/ +const char *cgi_parameter_checked(const char *zName, int iValue){ + const char *zIn = cgi_parameter(zName,0); + int x; + if( zIn==0 ){ + x = 0; + }else if( !fossil_isdigit(zIn[0]) ){ + x = is_truth(zIn); + }else{ + x = atoi(zIn); + } + return x==iValue ? "checked" : ""; +} /* ** Return the name of the i-th CGI parameter. Return NULL if there -** are fewer than i registered CGI parmaeters. +** are fewer than i registered CGI parameters. */ const char *cgi_parameter_name(int i){ if( i>=0 && i<nUsedQP ){ return aParamQP[i].zName; }else{ @@ -839,155 +1435,141 @@ va_end(ap); return 1; } /* -** Print all query parameters on standard output. Format the -** parameters as HTML. This is used for testing and debugging. +** Load all relevant environment variables into the parameter buffer. +** Invoke this routine prior to calling cgi_print_all() in order to see +** the full CGI environment. This routine intended for debugging purposes +** only. +*/ +void cgi_load_environment(void){ + /* The following is a list of environment variables that Fossil considers + ** to be "relevant". */ + static const char *const azCgiVars[] = { + "COMSPEC", "DOCUMENT_ROOT", "GATEWAY_INTERFACE", "SCGI", + "HTTP_ACCEPT", "HTTP_ACCEPT_CHARSET", "HTTP_ACCEPT_ENCODING", + "HTTP_ACCEPT_LANGUAGE", "HTTP_AUTHENICATION", + "HTTP_CONNECTION", "HTTP_HOST", + "HTTP_IF_NONE_MATCH", "HTTP_IF_MODIFIED_SINCE", + "HTTP_USER_AGENT", "HTTP_REFERER", "PATH_INFO", "PATH_TRANSLATED", + "QUERY_STRING", "REMOTE_ADDR", "REMOTE_PORT", + "REMOTE_USER", "REQUEST_METHOD", "REQUEST_SCHEME", + "REQUEST_URI", "SCRIPT_FILENAME", "SCRIPT_NAME", "SERVER_NAME", + "SERVER_PROTOCOL", "HOME", "FOSSIL_HOME", "USERNAME", "USER", + "FOSSIL_USER", "SQLITE_TMPDIR", "TMPDIR", + "TEMP", "TMP", "FOSSIL_VFS", + "FOSSIL_FORCE_TICKET_MODERATION", "FOSSIL_FORCE_WIKI_MODERATION", + "FOSSIL_TCL_PATH", "TH1_DELETE_INTERP", "TH1_ENABLE_DOCS", + "TH1_ENABLE_HOOKS", "TH1_ENABLE_TCL", "REMOTE_HOST", + }; + int i; + for(i=0; i<count(azCgiVars); i++) (void)P(azCgiVars[i]); +} + +/* +** Print all query parameters on standard output. +** This is used for testing and debugging. +** +** Omit the values of the cookies unless showAll is true. +** +** The eDest parameter determines where the output is shown: +** +** eDest==0: Rendering as HTML into the CGI reply +** eDest==1: Written to stderr +** eDest==2: Written to cgi_debug */ -void cgi_print_all(void){ +void cgi_print_all(int showAll, unsigned int eDest){ int i; cgi_parameter("",""); /* Force the parameters into sorted order */ for(i=0; i<nUsedQP; i++){ - cgi_printf("%s = %s <br />\n", - htmlize(aParamQP[i].zName, -1), htmlize(aParamQP[i].zValue, -1)); - } -} - -/* -** Write HTML text for an option menu to standard output. zParam -** is the query parameter that the option menu sets. zDflt is the -** initial value of the option menu. Addition arguments are name/value -** pairs that define values on the menu. The list is terminated with -** a single NULL argument. -*/ -void cgi_optionmenu(int in, const char *zP, const char *zD, ...){ - va_list ap; - char *zName, *zVal; - int dfltSeen = 0; - cgi_printf("%*s<select size=1 name=\"%s\">\n", in, "", zP); - va_start(ap, zD); - while( (zName = va_arg(ap, char*))!=0 && (zVal = va_arg(ap, char*))!=0 ){ - if( strcmp(zVal,zD)==0 ){ dfltSeen = 1; break; } - } - va_end(ap); - if( !dfltSeen ){ - if( zD[0] ){ - cgi_printf("%*s<option value=\"%h\" selected>%h</option>\n", - in+2, "", zD, zD); - }else{ - cgi_printf("%*s<option value=\"\" selected> </option>\n", in+2, ""); - } - } - va_start(ap, zD); - while( (zName = va_arg(ap, char*))!=0 && (zVal = va_arg(ap, char*))!=0 ){ - if( zName[0] ){ - cgi_printf("%*s<option value=\"%h\"%s>%h</option>\n", - in+2, "", - zVal, - strcmp(zVal, zD) ? "" : " selected", - zName - ); - }else{ - cgi_printf("%*s<option value=\"\"%s> </option>\n", - in+2, "", - strcmp(zVal, zD) ? "" : " selected" - ); - } - } - va_end(ap); - cgi_printf("%*s</select>\n", in, ""); -} - -/* -** This routine works a lot like cgi_optionmenu() except that the list of -** values is contained in an array. Also, the values are just values, not -** name/value pairs as in cgi_optionmenu. -*/ -void cgi_v_optionmenu( - int in, /* Indent by this amount */ - const char *zP, /* The query parameter name */ - const char *zD, /* Default value */ - const char **az /* NULL-terminated list of allowed values */ -){ - const char *zVal; - int i; - cgi_printf("%*s<select size=1 name=\"%s\">\n", in, "", zP); - for(i=0; az[i]; i++){ - if( strcmp(az[i],zD)==0 ) break; - } - if( az[i]==0 ){ - if( zD[0]==0 ){ - cgi_printf("%*s<option value=\"\" selected> </option>\n", - in+2, ""); - }else{ - cgi_printf("%*s<option value=\"%h\" selected>%h</option>\n", - in+2, "", zD, zD); - } - } - while( (zVal = *(az++))!=0 ){ - if( zVal[0] ){ - cgi_printf("%*s<option value=\"%h\"%s>%h</option>\n", - in+2, "", - zVal, - strcmp(zVal, zD) ? "" : " selected", - zVal - ); - }else{ - cgi_printf("%*s<option value=\"\"%s> </option>\n", - in+2, "", - strcmp(zVal, zD) ? "" : " selected" - ); - } - } - cgi_printf("%*s</select>\n", in, ""); -} - -/* -** This routine works a lot like cgi_v_optionmenu() except that the list -** is a list of pairs. The first element of each pair is the value used -** internally and the second element is the value displayed to the user. -*/ -void cgi_v_optionmenu2( - int in, /* Indent by this amount */ - const char *zP, /* The query parameter name */ - const char *zD, /* Default value */ - const char **az /* NULL-terminated list of allowed values */ -){ - const char *zVal; - int i; - cgi_printf("%*s<select size=1 name=\"%s\">\n", in, "", zP); - for(i=0; az[i]; i+=2){ - if( strcmp(az[i],zD)==0 ) break; - } - if( az[i]==0 ){ - if( zD[0]==0 ){ - cgi_printf("%*s<option value=\"\" selected> </option>\n", - in+2, ""); - }else{ - cgi_printf("%*s<option value=\"%h\" selected>%h</option>\n", - in+2, "", zD, zD); - } - } - while( (zVal = *(az++))!=0 ){ - const char *zName = *(az++); - if( zName[0] ){ - cgi_printf("%*s<option value=\"%h\"%s>%h</option>\n", - in+2, "", - zVal, - strcmp(zVal, zD) ? "" : " selected", - zName - ); - }else{ - cgi_printf("%*s<option value=\"%h\"%s> </option>\n", - in+2, "", - zVal, - strcmp(zVal, zD) ? "" : " selected" - ); - } - } - cgi_printf("%*s</select>\n", in, ""); + const char *zName = aParamQP[i].zName; + if( !showAll ){ + if( fossil_stricmp("HTTP_COOKIE",zName)==0 ) continue; + if( fossil_strnicmp("fossil-",zName,7)==0 ) continue; + } + switch( eDest ){ + case 0: { + cgi_printf("%h = %h <br />\n", zName, aParamQP[i].zValue); + break; + } + case 1: { + fossil_trace("%s = %s\n", zName, aParamQP[i].zValue); + break; + } + case 2: { + cgi_debug("%s = %s\n", zName, aParamQP[i].zValue); + break; + } + } + } +} + +/* +** Put information about the N-th parameter into arguments. +** Return non-zero on success, and return 0 if there is no N-th parameter. +*/ +int cgi_param_info( + int N, + const char **pzName, + const char **pzValue, + int *pbIsQP +){ + if( N>=0 && N<nUsedQP ){ + *pzName = aParamQP[N].zName; + *pzValue = aParamQP[N].zValue; + *pbIsQP = aParamQP[N].isQP; + return 1; + }else{ + *pzName = 0; + *pzValue = 0; + *pbIsQP = 0; + return 0; + } +} + +/* +** Export all untagged query parameters (but not cookies or environment +** variables) as hidden values of a form. +*/ +void cgi_query_parameters_to_hidden(void){ + int i; + const char *zN, *zV; + for(i=0; i<nUsedQP; i++){ + if( aParamQP[i].isQP==0 || aParamQP[i].cTag ) continue; + zN = aParamQP[i].zName; + zV = aParamQP[i].zValue; + @ <input type="hidden" name="%h(zN)" value="%h(zV)"> + } +} + +/* +** Export all untagged query parameters (but not cookies or environment +** variables) to the HQuery object. +*/ +void cgi_query_parameters_to_url(HQuery *p){ + int i; + for(i=0; i<nUsedQP; i++){ + if( aParamQP[i].isQP==0 || aParamQP[i].cTag ) continue; + url_add_parameter(p, aParamQP[i].zName, aParamQP[i].zValue); + } +} + +/* +** Tag query parameter zName so that it is not exported by +** cgi_query_parameters_to_hidden(). Or if zName==0, then +** untag all query parameters. +*/ +void cgi_tag_query_parameter(const char *zName){ + int i; + if( zName==0 ){ + for(i=0; i<nUsedQP; i++) aParamQP[i].cTag = 0; + }else{ + for(i=0; i<nUsedQP; i++){ + if( strcmp(zName,aParamQP[i].zName)==0 ) aParamQP[i].cTag = 1; + } + } } /* ** This routine works like "printf" except that it has the ** extra formatting capabilities such as %h and %t. @@ -1009,35 +1591,64 @@ /* ** Send a reply indicating that the HTTP request was malformed */ -static void malformed_request(void){ +static NORETURN void malformed_request(const char *zMsg){ cgi_set_status(501, "Not Implemented"); cgi_printf( - "<html><body>Unrecognized HTTP Request</body></html>\n" + "<html><body><p>Bad Request: %s</p></body></html>\n", zMsg ); cgi_reply(); - exit(0); + fossil_exit(0); } /* ** Panic and die while processing a webpage. */ -void cgi_panic(const char *zFormat, ...){ +NORETURN void cgi_panic(const char *zFormat, ...){ va_list ap; cgi_reset_content(); - cgi_set_status(500, "Internal Server Error"); - cgi_printf( - "<html><body><h1>Internal Server Error</h1>\n" - "<plaintext>" - ); - va_start(ap, zFormat); - vxprintf(pContent,zFormat,ap); - va_end(ap); - cgi_reply(); - exit(1); +#ifdef FOSSIL_ENABLE_JSON + if( g.json.isJsonMode ){ + char * zMsg; + va_start(ap, zFormat); + zMsg = vmprintf(zFormat,ap); + va_end(ap); + json_err( FSL_JSON_E_PANIC, zMsg, 1 ); + free(zMsg); + fossil_exit( g.isHTTP ? 0 : 1 ); + }else +#endif /* FOSSIL_ENABLE_JSON */ + { + cgi_set_status(500, "Internal Server Error"); + cgi_printf( + "<html><body><h1>Internal Server Error</h1>\n" + "<plaintext>" + ); + va_start(ap, zFormat); + vxprintf(pContent,zFormat,ap); + va_end(ap); + cgi_reply(); + fossil_exit(1); + } +} + +/* z[] is the value of an X-FORWARDED-FOR: line in an HTTP header. +** Return a pointer to a string containing the real IP address, or a +** NULL pointer to stick with the IP address previously computed and +** loaded into g.zIpAddr. +*/ +static const char *cgi_accept_forwarded_for(const char *z){ + int i; + if( !cgi_is_loopback(g.zIpAddr) ){ + /* Only accept X-FORWARDED-FOR if input coming from the local machine */ + return 0; + } + i = strlen(z)-1; + while( i>=0 && z[i]!=',' && !fossil_isspace(z[i]) ) i--; + return &z[++i]; } /* ** Remove the first space-delimited token from a string and return ** a pointer to it. Add a NULL to the string to terminate the token. @@ -1047,112 +1658,447 @@ char *zResult = 0; if( zInput==0 ){ if( zLeftOver ) *zLeftOver = 0; return 0; } - while( isspace(*zInput) ){ zInput++; } + while( fossil_isspace(*zInput) ){ zInput++; } zResult = zInput; - while( *zInput && !isspace(*zInput) ){ zInput++; } + while( *zInput && !fossil_isspace(*zInput) ){ zInput++; } if( *zInput ){ *zInput = 0; zInput++; - while( isspace(*zInput) ){ zInput++; } + while( fossil_isspace(*zInput) ){ zInput++; } } if( zLeftOver ){ *zLeftOver = zInput; } return zResult; } + +/* +** Determine the IP address on the other side of a connection. +** Return a pointer to a string. Or return 0 if unable. +** +** The string is held in a static buffer that is overwritten on +** each call. +*/ +char *cgi_remote_ip(int fd){ +#if 0 + static char zIp[100]; + struct sockaddr_in6 addr; + socklen_t sz = sizeof(addr); + if( getpeername(fd, &addr, &sz) ) return 0; + zIp[0] = 0; + if( inet_ntop(AF_INET6, &addr, zIp, sizeof(zIp))==0 ){ + return 0; + } + return zIp; +#else + struct sockaddr_in remoteName; + socklen_t size = sizeof(struct sockaddr_in); + if( getpeername(fd, (struct sockaddr*)&remoteName, &size) ) return 0; + return inet_ntoa(remoteName.sin_addr); +#endif +} /* ** This routine handles a single HTTP request which is coming in on -** standard input and which replies on standard output. +** g.httpIn and which replies on g.httpOut ** -** The HTTP request is read from standard input and is used to initialize -** environment variables as per CGI. The cgi_init() routine to complete +** The HTTP request is read from g.httpIn and is used to initialize +** entries in the cgi_parameter() hash, as if those entries were +** environment variables. A call to cgi_init() completes ** the setup. Once all the setup is finished, this procedure returns ** and subsequent code handles the actual generation of the webpage. */ void cgi_handle_http_request(const char *zIpAddr){ char *z, *zToken; int i; - struct sockaddr_in remoteName; - size_t size = sizeof(struct sockaddr_in); + const char *zScheme = "http"; char zLine[2000]; /* A single line of input. */ - g.fullHttpReply = 1; if( fgets(zLine, sizeof(zLine),g.httpIn)==0 ){ - malformed_request(); + malformed_request("missing HTTP header"); } + blob_append(&g.httpHeader, zLine, -1); + cgi_trace(zLine); zToken = extract_token(zLine, &z); if( zToken==0 ){ - malformed_request(); + malformed_request("malformed HTTP header"); } - if( strcmp(zToken,"GET")!=0 && strcmp(zToken,"POST")!=0 - && strcmp(zToken,"HEAD")!=0 ){ - malformed_request(); + if( fossil_strcmp(zToken,"GET")!=0 && fossil_strcmp(zToken,"POST")!=0 + && fossil_strcmp(zToken,"HEAD")!=0 ){ + malformed_request("unsupported HTTP method"); } cgi_setenv("GATEWAY_INTERFACE","CGI/1.0"); cgi_setenv("REQUEST_METHOD",zToken); zToken = extract_token(z, &z); if( zToken==0 ){ - malformed_request(); + malformed_request("malformed URL in HTTP header"); } cgi_setenv("REQUEST_URI", zToken); + cgi_setenv("SCRIPT_NAME", ""); for(i=0; zToken[i] && zToken[i]!='?'; i++){} if( zToken[i] ) zToken[i++] = 0; cgi_setenv("PATH_INFO", zToken); cgi_setenv("QUERY_STRING", &zToken[i]); - if( zIpAddr==0 && - getpeername(fileno(g.httpIn), (struct sockaddr*)&remoteName, - (socklen_t*)&size)>=0 - ){ - zIpAddr = inet_ntoa(remoteName.sin_addr); + if( zIpAddr==0 ){ + zIpAddr = cgi_remote_ip(fileno(g.httpIn)); } - if( zIpAddr ){ + if( zIpAddr ){ cgi_setenv("REMOTE_ADDR", zIpAddr); - g.zIpAddr = mprintf("%s", zIpAddr); + g.zIpAddr = fossil_strdup(zIpAddr); + } + + + /* Get all the optional fields that follow the first line. + */ + while( fgets(zLine,sizeof(zLine),g.httpIn) ){ + char *zFieldName; + char *zVal; + + cgi_trace(zLine); + blob_append(&g.httpHeader, zLine, -1); + zFieldName = extract_token(zLine,&zVal); + if( zFieldName==0 || *zFieldName==0 ) break; + while( fossil_isspace(*zVal) ){ zVal++; } + i = strlen(zVal); + while( i>0 && fossil_isspace(zVal[i-1]) ){ i--; } + zVal[i] = 0; + for(i=0; zFieldName[i]; i++){ + zFieldName[i] = fossil_tolower(zFieldName[i]); + } + if( fossil_strcmp(zFieldName,"accept-encoding:")==0 ){ + cgi_setenv("HTTP_ACCEPT_ENCODING", zVal); + }else if( fossil_strcmp(zFieldName,"content-length:")==0 ){ + cgi_setenv("CONTENT_LENGTH", zVal); + }else if( fossil_strcmp(zFieldName,"content-type:")==0 ){ + cgi_setenv("CONTENT_TYPE", zVal); + }else if( fossil_strcmp(zFieldName,"cookie:")==0 ){ + cgi_setenv("HTTP_COOKIE", zVal); + }else if( fossil_strcmp(zFieldName,"https:")==0 ){ + cgi_setenv("HTTPS", zVal); + zScheme = "https"; + }else if( fossil_strcmp(zFieldName,"host:")==0 ){ + char *z; + cgi_setenv("HTTP_HOST", zVal); + z = strchr(zVal, ':'); + if( z ) z[0] = 0; + cgi_setenv("SERVER_NAME", zVal); + }else if( fossil_strcmp(zFieldName,"if-none-match:")==0 ){ + cgi_setenv("HTTP_IF_NONE_MATCH", zVal); + }else if( fossil_strcmp(zFieldName,"if-modified-since:")==0 ){ + cgi_setenv("HTTP_IF_MODIFIED_SINCE", zVal); + }else if( fossil_strcmp(zFieldName,"referer:")==0 ){ + cgi_setenv("HTTP_REFERER", zVal); + }else if( fossil_strcmp(zFieldName,"user-agent:")==0 ){ + cgi_setenv("HTTP_USER_AGENT", zVal); + }else if( fossil_strcmp(zFieldName,"authorization:")==0 ){ + cgi_setenv("HTTP_AUTHORIZATION", zVal); + }else if( fossil_strcmp(zFieldName,"x-forwarded-for:")==0 ){ + const char *zIpAddr = cgi_accept_forwarded_for(zVal); + if( zIpAddr!=0 ){ + g.zIpAddr = fossil_strdup(zIpAddr); + cgi_replace_parameter("REMOTE_ADDR", g.zIpAddr); + } + }else if( fossil_strcmp(zFieldName,"range:")==0 ){ + int x1 = 0; + int x2 = 0; + if( sscanf(zVal,"bytes=%d-%d",&x1,&x2)==2 && x1>=0 && x1<=x2 ){ + rangeStart = x1; + rangeEnd = x2+1; + } + } + } + cgi_setenv("REQUEST_SCHEME",zScheme); + cgi_init(); + cgi_trace(0); +} + +/* +** This routine handles a single HTTP request from an SSH client which is +** coming in on g.httpIn and which replies on g.httpOut +** +** Once all the setup is finished, this procedure returns +** and subsequent code handles the actual generation of the webpage. +** +** It is called in a loop so some variables will need to be replaced +*/ +void cgi_handle_ssh_http_request(const char *zIpAddr){ + static int nCycles = 0; + static char *zCmd = 0; + char *z, *zToken; + const char *zType = 0; + int i, content_length = 0; + char zLine[2000]; /* A single line of input. */ + +#ifdef FOSSIL_ENABLE_JSON + if( nCycles==0 ){ json_bootstrap_early(); } +#endif + if( zIpAddr ){ + if( nCycles==0 ){ + cgi_setenv("REMOTE_ADDR", zIpAddr); + g.zIpAddr = fossil_strdup(zIpAddr); + } + }else{ + fossil_fatal("missing SSH IP address"); + } + if( fgets(zLine, sizeof(zLine),g.httpIn)==0 ){ + malformed_request("missing HTTP header"); + } + cgi_trace(zLine); + zToken = extract_token(zLine, &z); + if( zToken==0 ){ + malformed_request("malformed HTTP header"); + } + + if( fossil_strcmp(zToken, "echo")==0 ){ + /* start looking for probes to complete transport_open */ + zCmd = cgi_handle_ssh_probes(zLine, sizeof(zLine), z, zToken); + if( fgets(zLine, sizeof(zLine),g.httpIn)==0 ){ + malformed_request("missing HTTP header"); + } + cgi_trace(zLine); + zToken = extract_token(zLine, &z); + if( zToken==0 ){ + malformed_request("malformed HTTP header"); + } + }else if( zToken && strlen(zToken)==0 && zCmd ){ + /* transport_flip request and continued transport_open */ + cgi_handle_ssh_transport(zCmd); + if( fgets(zLine, sizeof(zLine),g.httpIn)==0 ){ + malformed_request("missing HTTP header"); + } + cgi_trace(zLine); + zToken = extract_token(zLine, &z); + if( zToken==0 ){ + malformed_request("malformed HTTP header"); + } + } + + if( fossil_strcmp(zToken,"GET")!=0 && fossil_strcmp(zToken,"POST")!=0 + && fossil_strcmp(zToken,"HEAD")!=0 ){ + malformed_request("unsupported HTTP method"); + } + + if( nCycles==0 ){ + cgi_setenv("GATEWAY_INTERFACE","CGI/1.0"); + cgi_setenv("REQUEST_METHOD",zToken); + } + + zToken = extract_token(z, &z); + if( zToken==0 ){ + malformed_request("malformed URL in HTTP header"); + } + if( nCycles==0 ){ + cgi_setenv("REQUEST_URI", zToken); + cgi_setenv("SCRIPT_NAME", ""); + } + + for(i=0; zToken[i] && zToken[i]!='?'; i++){} + if( zToken[i] ) zToken[i++] = 0; + if( nCycles==0 ){ + cgi_setenv("PATH_INFO", zToken); + }else{ + cgi_replace_parameter("PATH_INFO", fossil_strdup(zToken)); } - + /* Get all the optional fields that follow the first line. */ while( fgets(zLine,sizeof(zLine),g.httpIn) ){ char *zFieldName; char *zVal; + cgi_trace(zLine); zFieldName = extract_token(zLine,&zVal); if( zFieldName==0 || *zFieldName==0 ) break; - while( isspace(*zVal) ){ zVal++; } + while( fossil_isspace(*zVal) ){ zVal++; } i = strlen(zVal); - while( i>0 && isspace(zVal[i-1]) ){ i--; } + while( i>0 && fossil_isspace(zVal[i-1]) ){ i--; } zVal[i] = 0; - for(i=0; zFieldName[i]; i++){ zFieldName[i] = tolower(zFieldName[i]); } - if( strcmp(zFieldName,"user-agent:")==0 ){ - cgi_setenv("HTTP_USER_AGENT", zVal); - }else if( strcmp(zFieldName,"content-length:")==0 ){ - cgi_setenv("CONTENT_LENGTH", zVal); - }else if( strcmp(zFieldName,"referer:")==0 ){ - cgi_setenv("HTTP_REFERER", zVal); - }else if( strcmp(zFieldName,"host:")==0 ){ - cgi_setenv("HTTP_HOST", zVal); - }else if( strcmp(zFieldName,"content-type:")==0 ){ - cgi_setenv("CONTENT_TYPE", zVal); - }else if( strcmp(zFieldName,"cookie:")==0 ){ - cgi_setenv("HTTP_COOKIE", zVal); - }else if( strcmp(zFieldName,"if-none-match:")==0 ){ - cgi_setenv("HTTP_IF_NONE_MATCH", zVal); - }else if( strcmp(zFieldName,"if-modified-since:")==0 ){ - cgi_setenv("HTTP_IF_MODIFIED_SINCE", zVal); - } - } - + for(i=0; zFieldName[i]; i++){ + zFieldName[i] = fossil_tolower(zFieldName[i]); + } + if( fossil_strcmp(zFieldName,"content-length:")==0 ){ + content_length = atoi(zVal); + }else if( fossil_strcmp(zFieldName,"content-type:")==0 ){ + g.zContentType = zType = fossil_strdup(zVal); + }else if( fossil_strcmp(zFieldName,"host:")==0 ){ + if( nCycles==0 ){ + cgi_setenv("HTTP_HOST", zVal); + } + }else if( fossil_strcmp(zFieldName,"user-agent:")==0 ){ + if( nCycles==0 ){ + cgi_setenv("HTTP_USER_AGENT", zVal); + } + }else if( fossil_strcmp(zFieldName,"x-fossil-transport:")==0 ){ + if( fossil_strnicmp(zVal, "ssh", 3)==0 ){ + if( nCycles==0 ){ + g.fSshClient |= CGI_SSH_FOSSIL; + g.fullHttpReply = 0; + } + } + } + } + + if( nCycles==0 ){ + if( ! ( g.fSshClient & CGI_SSH_FOSSIL ) ){ + /* did not find new fossil ssh transport */ + g.fSshClient &= ~CGI_SSH_CLIENT; + g.fullHttpReply = 1; + cgi_replace_parameter("REMOTE_ADDR", "127.0.0.1"); + } + } + + cgi_reset_content(); + cgi_destination(CGI_BODY); + + if( content_length>0 && zType ){ + blob_zero(&g.cgiIn); + if( fossil_strcmp(zType, "application/x-fossil")==0 ){ + blob_read_from_channel(&g.cgiIn, g.httpIn, content_length); + blob_uncompress(&g.cgiIn, &g.cgiIn); + }else if( fossil_strcmp(zType, "application/x-fossil-debug")==0 ){ + blob_read_from_channel(&g.cgiIn, g.httpIn, content_length); + }else if( fossil_strcmp(zType, "application/x-fossil-uncompressed")==0 ){ + blob_read_from_channel(&g.cgiIn, g.httpIn, content_length); + } + } + cgi_trace(0); + nCycles++; +} + +/* +** This routine handles the old fossil SSH probes +*/ +char *cgi_handle_ssh_probes(char *zLine, int zSize, char *z, char *zToken){ + /* Start looking for probes */ + while( fossil_strcmp(zToken, "echo")==0 ){ + zToken = extract_token(z, &z); + if( zToken==0 ){ + malformed_request("malformed probe"); + } + if( fossil_strncmp(zToken, "test", 4)==0 || + fossil_strncmp(zToken, "probe-", 6)==0 ){ + fprintf(g.httpOut, "%s\n", zToken); + fflush(g.httpOut); + }else{ + malformed_request("malformed probe"); + } + if( fgets(zLine, zSize, g.httpIn)==0 ){ + malformed_request("malformed probe"); + } + cgi_trace(zLine); + zToken = extract_token(zLine, &z); + if( zToken==0 ){ + malformed_request("malformed probe"); + } + } + + /* Got all probes now first transport_open is completed + ** so return the command that was requested + */ + g.fSshClient |= CGI_SSH_COMPAT; + return fossil_strdup(zToken); +} + +/* +** This routine handles the old fossil SSH transport_flip +** and transport_open communications if detected. +*/ +void cgi_handle_ssh_transport(const char *zCmd){ + char *z, *zToken; + char zLine[2000]; /* A single line of input. */ + + /* look for second newline of transport_flip */ + if( fgets(zLine, sizeof(zLine),g.httpIn)==0 ){ + malformed_request("incorrect transport_flip"); + } + cgi_trace(zLine); + zToken = extract_token(zLine, &z); + if( zToken && strlen(zToken)==0 ){ + /* look for path to fossil */ + if( fgets(zLine, sizeof(zLine),g.httpIn)==0 ){ + if( zCmd==0 ){ + malformed_request("missing fossil command"); + }else{ + /* no new command so exit */ + fossil_exit(0); + } + } + cgi_trace(zLine); + zToken = extract_token(zLine, &z); + if( zToken==0 ){ + malformed_request("malformed fossil command"); + } + /* see if we've seen the command */ + if( zCmd && zCmd[0] && fossil_strcmp(zToken, zCmd)==0 ){ + return; + }else{ + malformed_request("transport_open failed"); + } + }else{ + malformed_request("transport_flip failed"); + } +} + +/* +** This routine handles a single SCGI request which is coming in on +** g.httpIn and which replies on g.httpOut +** +** The SCGI request is read from g.httpIn and is used to initialize +** entries in the cgi_parameter() hash, as if those entries were +** environment variables. A call to cgi_init() completes +** the setup. Once all the setup is finished, this procedure returns +** and subsequent code handles the actual generation of the webpage. +*/ +void cgi_handle_scgi_request(void){ + char *zHdr; + char *zToFree; + int nHdr = 0; + int nRead; + int c, n, m; + + while( (c = fgetc(g.httpIn))!=EOF && fossil_isdigit((char)c) ){ + nHdr = nHdr*10 + (char)c - '0'; + } + if( nHdr<16 ) malformed_request("SCGI header too short"); + zToFree = zHdr = fossil_malloc(nHdr); + nRead = (int)fread(zHdr, 1, nHdr, g.httpIn); + if( nRead<nHdr ) malformed_request("cannot read entire SCGI header"); + nHdr = nRead; + while( nHdr ){ + for(n=0; n<nHdr && zHdr[n]; n++){} + for(m=n+1; m<nHdr && zHdr[m]; m++){} + if( m>=nHdr ) malformed_request("SCGI header formatting error"); + cgi_set_parameter(zHdr, zHdr+n+1); + zHdr += m+1; + nHdr -= m+1; + } + fossil_free(zToFree); + fgetc(g.httpIn); /* Read past the "," separating header from content */ cgi_init(); } + +#if INTERFACE +/* +** Bitmap values for the flags parameter to cgi_http_server(). +*/ +#define HTTP_SERVER_LOCALHOST 0x0001 /* Bind to 127.0.0.1 only */ +#define HTTP_SERVER_SCGI 0x0002 /* SCGI instead of HTTP */ +#define HTTP_SERVER_HAD_REPOSITORY 0x0004 /* Was the repository open? */ +#define HTTP_SERVER_HAD_CHECKOUT 0x0008 /* Was a checkout open? */ +#define HTTP_SERVER_REPOLIST 0x0010 /* Allow repo listing */ + +#endif /* INTERFACE */ + /* ** Maximum number of child processes that we can have running -** at one time before we start slowing things down. +** at one time. Set this to 0 for "no limit". */ -#define MAX_PARALLEL 2 +#ifndef FOSSIL_MAX_CONNECTIONS +# define FOSSIL_MAX_CONNECTIONS 1000 +#endif /* ** Implement an HTTP server daemon listening on port iPort. ** ** As new connections arrive, fork a child and let child return @@ -1160,19 +2106,25 @@ ** The parent never returns from this procedure. ** ** Return 0 to each child as it runs. If unable to establish a ** listening socket, return non-zero. */ -int cgi_http_server(int mnPort, int mxPort, char *zBrowser){ -#ifdef __MINGW32__ +int cgi_http_server( + int mnPort, int mxPort, /* Range of TCP ports to try */ + const char *zBrowser, /* Run this browser, if not NULL */ + const char *zIpAddr, /* Bind to this IP address, if not null */ + int flags /* HTTP_SERVER_* flags */ +){ +#if defined(_WIN32) /* Use win32_http_server() instead */ - exit(1); + fossil_exit(1); #else int listener = -1; /* The server socket */ int connection; /* A socket for each individual connection */ + int nRequest = 0; /* Number of requests handled so far */ fd_set readfds; /* Set of file descriptors for select() */ - size_t lenaddr; /* Length of the inaddr structure */ + socklen_t lenaddr; /* Length of the inaddr structure */ int child; /* PID of the child process */ int nchildren = 0; /* Number of child processes */ struct timeval delay; /* How long to wait inside select() */ struct sockaddr_in inaddr; /* The socket address */ int opt = 1; /* setsockopt flag */ @@ -1179,11 +2131,20 @@ int iPort = mnPort; while( iPort<=mxPort ){ memset(&inaddr, 0, sizeof(inaddr)); inaddr.sin_family = AF_INET; - inaddr.sin_addr.s_addr = INADDR_ANY; + if( zIpAddr ){ + inaddr.sin_addr.s_addr = inet_addr(zIpAddr); + if( inaddr.sin_addr.s_addr == (-1) ){ + fossil_fatal("not a valid IP address: %s", zIpAddr); + } + }else if( flags & HTTP_SERVER_LOCALHOST ){ + inaddr.sin_addr.s_addr = htonl(INADDR_LOOPBACK); + }else{ + inaddr.sin_addr.s_addr = htonl(INADDR_ANY); + } inaddr.sin_port = htons(iPort); listener = socket(AF_INET, SOCK_STREAM, 0); if( listener<0 ){ iPort++; continue; @@ -1207,76 +2168,110 @@ " port in the range %d..%d", mnPort, mxPort); } } if( iPort>mxPort ) return 1; listen(listener,10); - if( iPort>mnPort ){ - printf("Listening for HTTP requests on TCP port %d\n", iPort); - fflush(stdout); - } + fossil_print("Listening for %s requests on TCP port %d\n", + (flags & HTTP_SERVER_SCGI)!=0?"SCGI":"HTTP", iPort); + fflush(stdout); if( zBrowser ){ - zBrowser = mprintf(zBrowser, iPort); - system(zBrowser); + assert( strstr(zBrowser,"%d")!=0 ); + zBrowser = mprintf(zBrowser /*works-like:"%d"*/, iPort); +#if defined(__CYGWIN__) + /* On Cygwin, we can do better than "echo" */ + if( strncmp(zBrowser, "echo ", 5)==0 ){ + wchar_t *wUrl = fossil_utf8_to_unicode(zBrowser+5); + wUrl[wcslen(wUrl)-2] = 0; /* Strip terminating " &" */ + if( (size_t)ShellExecuteW(0, L"open", wUrl, 0, 0, 1)<33 ){ + fossil_warning("cannot start browser\n"); + } + }else +#endif + if( fossil_system(zBrowser)<0 ){ + fossil_warning("cannot start browser: %s\n", zBrowser); + } } while( 1 ){ - if( nchildren>MAX_PARALLEL ){ - /* Slow down if connections are arriving too fast */ - sleep( nchildren-MAX_PARALLEL ); +#if FOSSIL_MAX_CONNECTIONS>0 + while( nchildren>=FOSSIL_MAX_CONNECTIONS ){ + if( wait(0)>=0 ) nchildren--; } - delay.tv_sec = 60; - delay.tv_usec = 0; +#endif + delay.tv_sec = 0; + delay.tv_usec = 100000; FD_ZERO(&readfds); + assert( listener>=0 ); FD_SET( listener, &readfds); - if( select( listener+1, &readfds, 0, 0, &delay) ){ + select( listener+1, &readfds, 0, 0, &delay); + if( FD_ISSET(listener, &readfds) ){ lenaddr = sizeof(inaddr); - connection = accept(listener, (struct sockaddr*)&inaddr, - (socklen_t*) &lenaddr); + connection = accept(listener, (struct sockaddr*)&inaddr, &lenaddr); if( connection>=0 ){ child = fork(); if( child!=0 ){ - if( child>0 ) nchildren++; + if( child>0 ){ + nchildren++; + nRequest++; + } close(connection); }else{ + int nErr = 0, fd; close(0); - dup(connection); + fd = dup(connection); + if( fd!=0 ) nErr++; close(1); - dup(connection); - if( !g.fHttpTrace && !g.fSqlTrace ){ + fd = dup(connection); + if( fd!=1 ) nErr++; + if( 0 && !g.fAnyTrace ){ close(2); - dup(connection); + fd = dup(connection); + if( fd!=2 ) nErr++; } close(connection); - return 0; + g.nPendingRequest = nchildren+1; + g.nRequest = nRequest+1; + return nErr; } } } /* Bury dead children */ - while( waitpid(0, 0, WNOHANG)>0 ){ - nchildren--; + if( nchildren ){ + while(1){ + int iStatus = 0; + pid_t x = waitpid(-1, &iStatus, WNOHANG); + if( x<=0 ) break; + if( WIFSIGNALED(iStatus) && g.fAnyTrace ){ + fprintf(stderr, "/***** Child %d exited on signal %d (%s) *****/\n", + x, WTERMSIG(iStatus), strsignal(WTERMSIG(iStatus))); + } + nchildren--; + } } } - /* NOT REACHED */ - exit(1); + /* NOT REACHED */ + fossil_exit(1); #endif + /* NOT REACHED */ + return 0; } /* ** Name of days and months. */ -static const char *azDays[] = +static const char *const azDays[] = {"Sun", "Mon", "Tue", "Wed", "Thu", "Fri", "Sat", 0}; -static const char *azMonths[] = +static const char *const azMonths[] = {"Jan", "Feb", "Mar", "Apr", "May", "Jun", "Jul", "Aug", "Sep", "Oct", "Nov", "Dec", 0}; /* ** Returns an RFC822-formatted time string suitable for HTTP headers. ** The timezone is always GMT. The value returned is always a -** string obtained from mprintf() and must be freed using free() to -** avoid a memory leak. +** string obtained from mprintf() and must be freed using fossil_free() +** to avoid a memory leak. ** ** See http://www.faqs.org/rfcs/rfc822.html, section 5 ** and http://www.faqs.org/rfcs/rfc2616.html, section 3.3. */ char *cgi_rfc822_datestamp(time_t now){ @@ -1283,69 +2278,76 @@ struct tm *pTm; pTm = gmtime(&now); if( pTm==0 ){ return mprintf(""); }else{ - return mprintf("%s, %d %s %02d %02d:%02d:%02d GMT", + return mprintf("%s, %d %s %02d %02d:%02d:%02d +0000", azDays[pTm->tm_wday], pTm->tm_mday, azMonths[pTm->tm_mon], pTm->tm_year+1900, pTm->tm_hour, pTm->tm_min, pTm->tm_sec); } } + +/* +** Returns an ISO8601-formatted time string suitable for debugging +** purposes. +** +** The value returned is always a string obtained from mprintf() and must +** be freed using fossil_free() to avoid a memory leak. +*/ +char *cgi_iso8601_datestamp(void){ + struct tm *pTm; + time_t now = time(0); + pTm = gmtime(&now); + if( pTm==0 ){ + return mprintf(""); + }else{ + return mprintf("%04d-%02d-%02d %02d:%02d:%02d", + pTm->tm_year+1900, pTm->tm_mon, pTm->tm_mday, + pTm->tm_hour, pTm->tm_min, pTm->tm_sec); + } +} /* ** Parse an RFC822-formatted timestamp as we'd expect from HTTP and return ** a Unix epoch time. <= zero is returned on failure. ** ** Note that this won't handle all the _allowed_ HTTP formats, just the ** most popular one (the one generated by cgi_rfc822_datestamp(), actually). */ time_t cgi_rfc822_parsedate(const char *zDate){ - struct tm t; - char zIgnore[16]; - char zMonth[16]; - - memset(&t, 0, sizeof(t)); - if( 7==sscanf(zDate, "%12[A-Za-z,] %d %12[A-Za-z] %d %d:%d:%d", zIgnore, - &t.tm_mday, zMonth, &t.tm_year, &t.tm_hour, &t.tm_min, - &t.tm_sec)){ - - if( t.tm_year > 1900 ) t.tm_year -= 1900; - for(t.tm_mon=0; azMonths[t.tm_mon]; t.tm_mon++){ - if( !strncasecmp( azMonths[t.tm_mon], zMonth, 3 )){ - return mkgmtime(&t); - } - } - } - - return 0; -} - -/* -** Convert a struct tm* that represents a moment in UTC into the number -** of seconds in 1970, UTC. -*/ -time_t mkgmtime(struct tm *p){ - time_t t; - int nDay; - int isLeapYr; - /* Days in each month: 31, 28, 31, 30, 31, 30, 31, 31, 30, 31, 30, 31 */ - static int priorDays[] = { 0, 31, 59, 90,120,151,181,212,243,273,304,334 }; - if( p->tm_mon<0 ){ - int nYear = (11 - p->tm_mon)/12; - p->tm_year -= nYear; - p->tm_mon += nYear*12; - }else if( p->tm_mon>11 ){ - p->tm_year += p->tm_mon/12; - p->tm_mon %= 12; - } - isLeapYr = p->tm_year%4==0 && (p->tm_year%100!=0 || (p->tm_year+300)%400==0); - p->tm_yday = priorDays[p->tm_mon] + p->tm_mday - 1; - if( isLeapYr && p->tm_mon>1 ) p->tm_yday++; - nDay = (p->tm_year-70)*365 + (p->tm_year-69)/4 -p->tm_year/100 + - (p->tm_year+300)/400 + p->tm_yday; - t = ((nDay*24 + p->tm_hour)*60 + p->tm_min)*60 + p->tm_sec; - return t; + int mday, mon, year, yday, hour, min, sec; + char zIgnore[4]; + char zMonth[4]; + static const char *const azMonths[] = + {"Jan", "Feb", "Mar", "Apr", "May", "Jun", + "Jul", "Aug", "Sep", "Oct", "Nov", "Dec", 0}; + if( 7==sscanf(zDate, "%3[A-Za-z], %d %3[A-Za-z] %d %d:%d:%d", zIgnore, + &mday, zMonth, &year, &hour, &min, &sec)){ + if( year > 1900 ) year -= 1900; + for(mon=0; azMonths[mon]; mon++){ + if( !strncmp( azMonths[mon], zMonth, 3 )){ + int nDay; + int isLeapYr; + static int priorDays[] = + { 0, 31, 59, 90,120,151,181,212,243,273,304,334 }; + if( mon<0 ){ + int nYear = (11 - mon)/12; + year -= nYear; + mon += nYear*12; + }else if( mon>11 ){ + year += mon/12; + mon %= 12; + } + isLeapYr = year%4==0 && (year%100!=0 || (year+300)%400==0); + yday = priorDays[mon] + mday - 1; + if( isLeapYr && mon>1 ) yday++; + nDay = (year-70)*365 + (year-69)/4 - year/100 + (year+300)/400 + yday; + return ((time_t)(nDay*24 + hour)*60 + min)*60 + sec; + } + } + } + return 0; } /* ** Check the objectTime against the If-Modified-Since request header. If the ** object time isn't any newer than the header, we immediately send back @@ -1356,7 +2358,47 @@ if( zIf==0 ) return; if( objectTime > cgi_rfc822_parsedate(zIf) ) return; cgi_set_status(304,"Not Modified"); cgi_reset_content(); cgi_reply(); - exit(0); + fossil_exit(0); +} + +/* +** Check to see if the remote client is SSH and return +** its IP or return default +*/ +const char *cgi_ssh_remote_addr(const char *zDefault){ + char *zIndex; + const char *zSshConn = fossil_getenv("SSH_CONNECTION"); + + if( zSshConn && zSshConn[0] ){ + char *zSshClient = fossil_strdup(zSshConn); + if( (zIndex = strchr(zSshClient,' '))!=0 ){ + zSshClient[zIndex-zSshClient] = '\0'; + return zSshClient; + } + } + return zDefault; +} + +/* +** Return true if information is coming from the loopback network. +*/ +int cgi_is_loopback(const char *zIpAddr){ + return fossil_strcmp(zIpAddr, "127.0.0.1")==0 || + fossil_strcmp(zIpAddr, "::ffff:127.0.0.1")==0 || + fossil_strcmp(zIpAddr, "::1")==0; +} + +/* +** Return true if the HTTP request is likely to be from a small-screen +** mobile device. +** +** The returned value is a guess. Use it only for setting up defaults. +*/ +int cgi_from_mobile(void){ + const char *zAgent = P("HTTP_USER_AGENT"); + if( zAgent==0 ) return 0; + if( sqlite3_strglob("*iPad*", zAgent)==0 ) return 0; + return sqlite3_strlike("%mobile%", zAgent, 0)==0; } ADDED src/chat.c Index: src/chat.c ================================================================== --- /dev/null +++ src/chat.c @@ -0,0 +1,886 @@ +/* +** Copyright (c) 2020 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 code used to implement the Fossil chatroom. +** +** Initial design goals: +** +** * Keep it simple. This chatroom is not intended as a competitor +** or replacement for IRC, Discord, Telegram, Slack, etc. The goal +** is zero- or near-zero-configuration, not an abundance of features. +** +** * Intended as a place for insiders to have ephemeral conversations +** about a project. This is not a public gather place. Think +** "boardroom", not "corner pub". +** +** * One chatroom per repository. +** +** * Chat content lives in a single repository. It is never synced. +** Content expires and is deleted after a set interval (a week or so). +** +** Notification is accomplished using the "hanging GET" or "long poll" design +** in which a GET request is issued but the server does not send a reply until +** new content arrives. Newer Web Sockets and Server Sent Event protocols are +** more elegant, but are not compatible with CGI, and would thus complicate +** configuration. +*/ +#include "config.h" +#include <assert.h> +#include "chat.h" + +/* +** Outputs JS code to initialize a list of chat alert audio files for +** use by the chat front-end client. A handful of builtin files +** (from alerts/\*.wav) and all unversioned files matching +** alert-sounds/\*.{mp3,ogg,wav} are included. +*/ +static void chat_emit_alert_list(void){ + /*Stmt q = empty_Stmt;*/ + unsigned int i; + const char * azBuiltins[] = { + "builtin/alerts/plunk.wav", + "builtin/alerts/b-flat.wav" + }; + CX("window.fossil.config.chat.alerts = [\n"); + for(i=0; i < sizeof(azBuiltins)/sizeof(azBuiltins[0]); ++i){ + CX("%s%!j", i ? ", " : "", azBuiltins[i]); + } +#if 0 + /* + ** 2021-01-05 temporarily disabled until we decide whether we're + ** going to keep configurable audio files or not. If we do, this + ** code needs to check whether the [unversioned] table exists before + ** querying it. + */ + db_prepare(&q, "SELECT 'uv/'||name FROM unversioned " + "WHERE content IS NOT NULL " + "AND (name LIKE 'alert-sounds/%%.wav' " + "OR name LIKE 'alert-sounds/%%.mp3' " + "OR name LIKE 'alert-sounds/%%.ogg')"); + while(SQLITE_ROW==db_step(&q)){ + CX(", %!j", db_column_text(&q, 0)); + } + db_finalize(&q); +#endif + CX("\n];\n"); +} + +/* Settings that can be used to control chat */ +/* +** SETTING: chat-initial-history width=10 default=50 +** +** If this setting has an integer value of N, then when /chat first +** starts up it initializes the screen with the N most recent chat +** messages. If N is zero, then all chat messages are loaded. +*/ +/* +** SETTING: chat-keep-count width=10 default=50 +** +** When /chat is cleaning up older messages, it will always keep +** the most recent chat-keep-count messages, even if some of those +** messages are older than the discard threshold. If this value +** is zero, then /chat is free to delete all historic messages once +** they are old enough. +*/ +/* +** SETTING: chat-keep-days width=10 default=7 +** +** The /chat subsystem will try to discard messages that are older then +** chat-keep-days. The value of chat-keep-days can be a floating point +** number. So, for example, if you only want to keep chat messages for +** 12 hours, set this value to 0.5. +** +** A value of 0.0 or less means that messages are retained forever. +*/ +/* +** SETTING: chat-inline-images boolean default=on +** +** Specifies whether posted images in /chat should default to being +** displayed inline or as downloadable links. Each chat user can +** change this value for their current chat session in the UI. +*/ +/* +** SETTING: chat-poll-timeout width=10 default=420 +** +** On an HTTP request to /chat-poll, if there is no new content available, +** the reply is delayed waiting for new content to arrive. (This is the +** "long poll" strategy of event delivery to the client.) This setting +** determines approximately how long /chat-poll will delay before giving +** up and returning an empty reply. The default value is about 7 minutes, +** which works well for Fossil behind the althttpd web server. Other +** server environments may choose a longer or shorter delay. +** +** For maximum efficiency, it is best to choose the longest delay that +** does not cause timeouts in intermediate proxies or web server. +*/ +/* +** SETTING: chat-alert-sound width=10 +** +** This is the name of the builtin sound file to use for the alert tone. +** The value must be the name of one of a builtin WAV file. +*/ +/* +** WEBPAGE: chat +** +** Start up a browser-based chat session. +** +** This is the main page that humans use to access the chatroom. Simply +** point a web-browser at /chat and the screen fills with the latest +** chat messages, and waits for new one. +** +** Other /chat-OP pages are used by XHR requests from this page to +** send new chat message, delete older messages, or poll for changes. +*/ +void chat_webpage(void){ + char *zAlert; + char *zProjectName; + login_check_credentials(); + if( !g.perm.Chat ){ + login_needed(g.anon.Chat); + return; + } + zAlert = mprintf("%s/builtin/%s", g.zBaseURL, + db_get("chat-alert-sound","alerts/plunk.wav")); + zProjectName = db_get("project-name","Unnamed project"); + style_set_current_feature("chat"); + style_header("Chat"); + @ <form accept-encoding="utf-8" id="chat-form" autocomplete="off"> + @ <div id='chat-input-area'> + @ <div id='chat-input-line'> + @ <input type="text" name="msg" id="chat-input-single" \ + @ placeholder="Type message for %h(zProjectName)." autocomplete="off"> + @ <textarea rows="8" id="chat-input-multi" \ + @ placeholder="Type message for %h(zProjectName). Ctrl-Enter sends it." \ + @ class="hidden"></textarea> + @ <input type="submit" value="Send" id="chat-message-submit"> + @ <span id="chat-settings-button" class="settings-icon" \ + @ aria-label="Settings..." aria-haspopup="true" ></span> + @ </div> + @ <div id='chat-input-file-area'> + @ <div class='file-selection-wrapper'> + @ <div class='help-buttonlet'> + @ Select a file to upload, drag/drop a file into this spot, + @ or paste an image from the clipboard if supported by + @ your environment. + @ </div> + @ <input type="file" name="file" id="chat-input-file"> + @ </div> + @ <div id="chat-drop-details"></div> + @ </div> + @ </div> + @ </form> + @ <div id='chat-messages-wrapper'> + /* New chat messages get inserted immediately after this element */ + @ <span id='message-inject-point'></span> + @ </div> + fossil_free(zProjectName); + builtin_fossil_js_bundle_or("popupwidget", "storage", "fetch", NULL); + /* Always in-line the javascript for the chat page */ + @ <script nonce="%h(style_nonce())">/* chat.c:%d(__LINE__) */ + /* We need an onload handler to ensure that window.fossil is + initialized before the chat init code runs. */ + @ window.addEventListener('load', function(){ + @ document.body.classList.add('chat') + @ /*^^^for skins which add their own BODY tag */; + @ window.fossil.config.chat = { + @ fromcli: %h(PB("cli")?"true":"false"), + @ alertSound: "%h(zAlert)", + @ initSize: %d(db_get_int("chat-initial-history",50)), + @ imagesInline: !!%d(db_get_boolean("chat-inline-images",1)) + @ }; + chat_emit_alert_list(); + cgi_append_content(builtin_text("chat.js"),-1); + @ }, false); + @ </script> + + style_finish_page(); +} + +/* Definition of repository tables used by chat +*/ +static const char zChatSchema1[] = +@ CREATE TABLE repository.chat( +@ msgid INTEGER PRIMARY KEY AUTOINCREMENT, +@ mtime JULIANDAY, -- Time for this entry - Julianday Zulu +@ lmtime TEXT, -- Client YYYY-MM-DDZHH:MM:SS when message originally sent +@ xfrom TEXT, -- Login of the sender +@ xmsg TEXT, -- Raw, unformatted text of the message +@ fname TEXT, -- Filename of the uploaded file, or NULL +@ fmime TEXT, -- MIMEType of the upload file, or NULL +@ mdel INT, -- msgid of another message to delete +@ file BLOB -- Text of the uploaded file, or NULL +@ ); +; + + +/* +** Make sure the repository data tables used by chat exist. Create them +** if they do not. +*/ +static void chat_create_tables(void){ + if( !db_table_exists("repository","chat") ){ + db_multi_exec(zChatSchema1/*works-like:""*/); + }else if( !db_table_has_column("repository","chat","lmtime") ){ + if( !db_table_has_column("repository","chat","mdel") ){ + db_multi_exec("ALTER TABLE chat ADD COLUMN mdel INT"); + } + db_multi_exec("ALTER TABLE chat ADD COLUMN lmtime TEXT"); + } +} + +/* +** Delete old content from the chat table. +*/ +static void chat_purge(void){ + int mxCnt = db_get_int("chat-keep-count",50); + double mxDays = atof(db_get("chat-keep-days","7")); + double rAge; + int msgid; + rAge = db_double(0.0, "SELECT julianday('now')-mtime FROM chat" + " ORDER BY msgid LIMIT 1"); + if( rAge>mxDays ){ + msgid = db_int(0, "SELECT msgid FROM chat" + " ORDER BY msgid DESC LIMIT 1 OFFSET %d", mxCnt); + if( msgid>0 ){ + Stmt s; + db_multi_exec("PRAGMA secure_delete=ON;"); + db_prepare(&s, + "DELETE FROM chat WHERE mtime<julianday('now')-:mxage" + " AND msgid<%d", msgid); + db_bind_double(&s, ":mxage", mxDays); + db_step(&s); + db_finalize(&s); + } + } +} + +/* +** Sets the current CGI response type to application/json then emits a +** JSON-format error message object. If fAsMessageList is true then +** the object is output using the list format described for chat-poll, +** else it is emitted as a single object in that same format. +*/ +static void chat_emit_permissions_error(int fAsMessageList){ + char * zTime = cgi_iso8601_datestamp(); + cgi_set_content_type("application/json"); + if(fAsMessageList){ + CX("{\"msgs\":[{"); + }else{ + CX("{"); + } + CX("\"isError\": true, \"xfrom\": null,"); + CX("\"mtime\": %!j, \"lmtime\": %!j,", zTime, zTime); + CX("\"xmsg\": \"Missing permissions or not logged in. " + "Try <a href='%R/login?g=%R/chat'>logging in</a>.\""); + if(fAsMessageList){ + CX("}]}"); + }else{ + CX("}"); + } + fossil_free(zTime); +} + +/* +** WEBPAGE: chat-send +** +** This page receives (via XHR) a new chat-message and/or a new file +** to be entered into the chat history. +** +** On success it responds with an empty response: the new message +** should be fetched via /chat-poll. On error, e.g. login expiry, +** it emits a JSON response in the same form as described for +** /chat-poll errors, but as a standalone object instead of a +** list of objects. +*/ +void chat_send_webpage(void){ + int nByte; + const char *zMsg; + const char *zUserName; + login_check_credentials(); + if( !g.perm.Chat ) { + chat_emit_permissions_error(0); + return; + } + chat_create_tables(); + zUserName = (g.zLogin && g.zLogin[0]) ? g.zLogin : "nobody"; + nByte = atoi(PD("file:bytes","0")); + zMsg = PD("msg",""); + db_begin_write(); + chat_purge(); + if( nByte==0 ){ + if( zMsg[0] ){ + db_multi_exec( + "INSERT INTO chat(mtime,lmtime,xfrom,xmsg)" + "VALUES(julianday('now'),%Q,%Q,%Q)", + P("lmtime"), zUserName, zMsg + ); + } + }else{ + Stmt q; + Blob b; + db_prepare(&q, + "INSERT INTO chat(mtime,lmtime,xfrom,xmsg,file,fname,fmime)" + "VALUES(julianday('now'),%Q,%Q,%Q,:file,%Q,%Q)", + P("lmtime"), zUserName, zMsg, PD("file:filename",""), + PD("file:mimetype","application/octet-stream")); + blob_init(&b, P("file"), nByte); + db_bind_blob(&q, ":file", &b); + db_step(&q); + db_finalize(&q); + blob_reset(&b); + } + db_commit_transaction(); +} + +/* +** This routine receives raw (user-entered) message text and transforms +** it into HTML that is safe to insert using innerHTML. +** +** * HTML in the original text is escaped. +** +** * Hyperlinks are identified and tagged. Hyperlinks are: +** +** - Undelimited text of the form https:... or http:... +** - Any text enclosed within [...] +** +** Space to hold the returned string is obtained from fossil_malloc() +** and must be freed by the caller. +*/ +static char *chat_format_to_html(const char *zMsg){ + char *zSafe = mprintf("%h", zMsg); + int i, j, k; + Blob out; + char zClose[20]; + blob_init(&out, 0, 0); + for(i=j=0; zSafe[i]; i++){ + if( zSafe[i]=='[' ){ + for(k=i+1; zSafe[k] && zSafe[k]!=']'; k++){} + if( zSafe[k]==']' ){ + zSafe[k] = 0; + if( j<i ){ + blob_append(&out, zSafe + j, i-j); + j = i; + } + blob_append_char(&out, '['); + wiki_resolve_hyperlink(&out, + WIKI_NOBADLINKS|WIKI_TARGET_BLANK|WIKI_NOBRACKET, + zSafe+i+1, zClose, sizeof(zClose), zSafe, 0); + zSafe[k] = ']'; + j++; + blob_append(&out, zSafe + j, k - j); + blob_append(&out, zClose, -1); + blob_append_char(&out, ']'); + i = k; + j = k+1; + continue; + } + }else if( zSafe[i]=='h' + && (strncmp(zSafe+i,"http:",5)==0 + || strncmp(zSafe+i,"https:",6)==0) ){ + for(k=i+1; zSafe[k] && !fossil_isspace(zSafe[k]); k++){} + if( k>i+7 ){ + char c = zSafe[k]; + if( !fossil_isalnum(zSafe[k-1]) && zSafe[k-1]!='/' ){ + k--; + c = zSafe[k]; + } + if( j<i ){ + blob_append(&out, zSafe + j, i-j); + j = i; + } + zSafe[k] = 0; + wiki_resolve_hyperlink(&out, WIKI_NOBADLINKS|WIKI_TARGET_BLANK, + zSafe+i, zClose, sizeof(zClose), zSafe, 0); + zSafe[k] = c; + blob_append(&out, zSafe + j, k - j); + blob_append(&out, zClose, -1); + i = j = k; + continue; + } + } + } + if( j<i ){ + blob_append(&out, zSafe+j, j-i); + } + fossil_free(zSafe); + return blob_str(&out); +} + +/* +** COMMAND: test-chat-formatter +** +** Usage: %fossil test-chat-formatter STRING ... +** +** Transform each argument string into HTML that will display the +** chat message. This is used to test the formatter and to verify +** that a malicious message text will not cause HTML or JS injection +** into the chat display in a browser. +*/ +void chat_test_formatter_cmd(void){ + int i; + char *zOut; + db_find_and_open_repository(0,0); + g.perm.Hyperlink = 1; + for(i=0; i<g.argc; i++){ + zOut = chat_format_to_html(g.argv[i]); + fossil_print("[%d]: %s\n", i, zOut); + fossil_free(zOut); + } +} + +/* +** WEBPAGE: chat-poll +** +** The chat page generated by /chat using an XHR to this page to +** request new chat content. A typical invocation is: +** +** /chat-poll/N +** /chat-poll?name=N +** +** The "name" argument should begin with an integer which is the largest +** "msgid" that the chat page currently holds. If newer content is +** available, this routine returns that content straight away. If no new +** content is available, this webpage blocks until the new content becomes +** available. In this way, the system implements "hanging-GET" or "long-poll" +** style event notification. If no new content arrives after a delay of +** approximately chat-poll-timeout seconds (default: 420), then reply is +** sent with an empty "msg": field. +** +** If N is negative, then the return value is the N most recent messages. +** Hence a request like /chat-poll/-100 can be used to initialize a new +** chat session to just the most recent messages. +** +** Some webservers (althttpd) do not allow a term of the URL path to +** begin with "-". Then /chat-poll/-100 cannot be used. Instead you +** have to say "/chat-poll?name=-100". +** +** If the integer parameter "before" is passed in, it is assumed that +** the client is requesting older messages, up to (but not including) +** that message ID, in which case the next-oldest "n" messages +** (default=chat-initial-history setting, equivalent to n=0) are +** returned (negative n fetches all older entries). The client then +** needs to take care to inject them at the end of the history rather +** than the same place new messages go. +** +** If "before" is provided, "name" is ignored. +** +** The reply from this webpage is JSON that describes the new content. +** Format of the json: +** +** | { +** | "msgs":[ +** | { +** | "msgid": integer // message id +** | "mtime": text // When sent: YYYY-MM-DDTHH:MM:SSZ +** | "lmtime: text // Sender's client-side YYYY-MM-DDTHH:MM:SS +** | "xfrom": text // Login name of sender +** | "uclr": text // Color string associated with the user +** | "xmsg": text // HTML text of the message +** | "fsize": integer // file attachment size in bytes +** | "fname": text // Name of file attachment +** | "fmime": text // MIME-type of file attachment +** | "mdel": integer // message id of prior message to delete +** | } +** | ] +** | } +** +** The "fname" and "fmime" fields are only present if "fsize" is greater +** than zero. The "xmsg" field may be an empty string if "fsize" is zero. +** +** The "msgid" values will be in increasing order. +** +** The "mdel" will only exist if "xmsg" is an empty string and "fsize" is zero. +** +** The "lmtime" value might be unknown, in which case it is omitted. +** +** The messages are ordered oldest first unless "before" is provided, in which +** case they are sorted newest first (to facilitate the client-side UI update). +** +** As a special case, if this routine encounters an error, e.g. the user's +** permissions cannot be verified because their login cookie expired, the +** request returns a slightly modified structure: +** +** | { +** | "msgs":[ +** | { +** | "isError": true, +** | "xfrom": null, +** | "xmsg": "error details" +** | "mtime": as above, +** | "ltime": same as mtime +** | } +** | ] +** | } +** +** If the client gets such a response, it should display the message +** in a prominent manner and then stop polling for new messages. +*/ +void chat_poll_webpage(void){ + Blob json; /* The json to be constructed and returned */ + sqlite3_int64 dataVersion; /* Data version. Used for polling. */ + const int iDelay = 1000; /* Delay until next poll (milliseconds) */ + int nDelay; /* Maximum delay.*/ + int msgid = atoi(PD("name","0")); + const int msgBefore = atoi(PD("before","0")); + int nLimit = msgBefore>0 ? atoi(PD("n","0")) : 0; + Blob sql = empty_blob; + Stmt q1; + nDelay = db_get_int("chat-poll-timeout",420); /* Default about 7 minutes */ + login_check_credentials(); + if( !g.perm.Chat ) { + chat_emit_permissions_error(1); + return; + } + chat_create_tables(); + cgi_set_content_type("application/json"); + dataVersion = db_int64(0, "PRAGMA data_version"); + blob_append_sql(&sql, + "SELECT msgid, datetime(mtime), xfrom, xmsg, length(file)," + " fname, fmime, %s, lmtime" + " FROM chat ", + msgBefore>0 ? "0 as mdel" : "mdel"); + if( msgid<=0 || msgBefore>0 ){ + db_begin_write(); + chat_purge(); + db_commit_transaction(); + } + if(msgBefore>0){ + if(0==nLimit){ + nLimit = db_get_int("chat-initial-history",50); + } + blob_append_sql(&sql, + " WHERE msgid<%d" + " ORDER BY msgid DESC " + "LIMIT %d", + msgBefore, nLimit>0 ? nLimit : -1 + ); + }else{ + if( msgid<0 ){ + msgid = db_int(0, + "SELECT msgid FROM chat WHERE mdel IS NOT true" + " ORDER BY msgid DESC LIMIT 1 OFFSET %d", -msgid); + } + blob_append_sql(&sql, + " WHERE msgid>%d" + " ORDER BY msgid", + msgid + ); + } + db_prepare(&q1, "%s", blob_sql_text(&sql)); + blob_reset(&sql); + blob_init(&json, "{\"msgs\":[\n", -1); + while( nDelay>0 ){ + int cnt = 0; + while( db_step(&q1)==SQLITE_ROW ){ + int id = db_column_int(&q1, 0); + const char *zDate = db_column_text(&q1, 1); + const char *zFrom = db_column_text(&q1, 2); + const char *zRawMsg = db_column_text(&q1, 3); + int nByte = db_column_int(&q1, 4); + const char *zFName = db_column_text(&q1, 5); + const char *zFMime = db_column_text(&q1, 6); + int iToDel = db_column_int(&q1, 7); + const char *zLMtime = db_column_text(&q1, 8); + char *zMsg; + if(cnt++){ + blob_append(&json, ",\n", 2); + } + blob_appendf(&json, "{\"msgid\":%d,", id); + blob_appendf(&json, "\"mtime\":\"%.10sT%sZ\",", zDate, zDate+11); + if( zLMtime && zLMtime[0] ){ + blob_appendf(&json, "\"lmtime\":%!j,", zLMtime); + } + blob_append(&json, "\"xfrom\":", -1); + if(zFrom){ + blob_appendf(&json, "%!j,", zFrom); + }else{ + /* see https://fossil-scm.org/forum/forumpost/e0be0eeb4c */ + blob_appendf(&json, "null,"); + } + blob_appendf(&json, "\"uclr\":%!j,", + user_color(zFrom ? zFrom : "nobody")); + + zMsg = chat_format_to_html(zRawMsg ? zRawMsg : ""); + blob_appendf(&json, "\"xmsg\":%!j,", zMsg); + fossil_free(zMsg); + + if( nByte==0 ){ + blob_appendf(&json, "\"fsize\":0"); + }else{ + blob_appendf(&json, "\"fsize\":%d,\"fname\":%!j,\"fmime\":%!j", + nByte, zFName, zFMime); + } + + if( iToDel ){ + blob_appendf(&json, ",\"mdel\":%d}", iToDel); + }else{ + blob_append(&json, "}", 1); + } + } + db_reset(&q1); + if( cnt || msgBefore>0 ){ + break; + } + sqlite3_sleep(iDelay); nDelay--; + while( nDelay>0 ){ + sqlite3_int64 newDataVers = db_int64(0,"PRAGMA repository.data_version"); + if( newDataVers!=dataVersion ){ + dataVersion = newDataVers; + break; + } + sqlite3_sleep(iDelay); nDelay--; + } + } /* Exit by "break" */ + db_finalize(&q1); + blob_append(&json, "\n]}", 3); + cgi_set_content(&json); + return; +} + +/* +** WEBPAGE: chat-download +** +** Download the CHAT.FILE attachment associated with a single chat +** entry. The "name" query parameter begins with an integer that +** identifies the particular chat message. The integer may be followed +** by a / and a filename, which will indicate to the browser to use +** the indicated name when saving the file. +*/ +void chat_download_webpage(void){ + int msgid; + Blob r; + const char *zMime; + login_check_credentials(); + if( !g.perm.Chat ){ + style_header("Chat Not Authorized"); + @ <h1>Not Authorized</h1> + @ <p>You do not have permission to use the chatroom on this + @ repository.</p> + style_finish_page(); + return; + } + chat_create_tables(); + msgid = atoi(PD("name","0")); + blob_zero(&r); + zMime = db_text(0, "SELECT fmime FROM chat wHERE msgid=%d", msgid); + if( zMime==0 ) return; + db_blob(&r, "SELECT file FROM chat WHERE msgid=%d", msgid); + cgi_set_content_type(zMime); + cgi_set_content(&r); +} + + +/* +** WEBPAGE: chat-delete +** +** Delete the chat entry identified by the name query parameter. +** Invoking fetch("chat-delete/"+msgid) from javascript in the client +** will delete a chat entry from the CHAT table. +** +** This routine both deletes the identified chat entry and also inserts +** a new entry with the current timestamp and with: +** +** * xmsg = NULL +** * file = NULL +** * mdel = The msgid of the row that was deleted +** +** This new entry will then be propagated to all listeners so that they +** will know to delete their copies of the message too. +*/ +void chat_delete_webpage(void){ + int mdel; + char *zOwner; + login_check_credentials(); + if( !g.perm.Chat ) return; + chat_create_tables(); + mdel = atoi(PD("name","0")); + zOwner = db_text(0, "SELECT xfrom FROM chat WHERE msgid=%d", mdel); + if( zOwner==0 ) return; + if( fossil_strcmp(zOwner, g.zLogin)!=0 && !g.perm.Admin ) return; + db_multi_exec( + "PRAGMA secure_delete=ON;\n" + "BEGIN;\n" + "DELETE FROM chat WHERE msgid=%d;\n" + "INSERT INTO chat(mtime, xfrom, mdel)" + " VALUES(julianday('now'), %Q, %d);\n" + "COMMIT;", + mdel, g.zLogin, mdel + ); +} + +/* +** COMMAND: chat +** +** Usage: %fossil chat [SUBCOMMAND] [--remote URL] [ARGS...] +** +** This command performs actions associated with the /chat instance +** on the default remote Fossil repository (the Fossil repository whose +** URL shows when you run the "fossil remote" command) or to the URL +** specified by the --remote option. If there is no default remote +** Fossil repository and the --remote option is omitted, then this +** command fails with an error. +** +** When there is no SUBCOMMAND (when this command is simply "fossil chat") +** the response is to bring up a web-browser window to the chatroom +** on the default system web-browser. You can accomplish the same by +** typing the appropriate URL into the web-browser yourself. This +** command is merely a convenience for command-line oriented people. +** +** The following subcommands are supported: +** +** > fossil chat send [ARGUMENTS] +** +** This command sends a new message to the chatroom. The message +** to be sent is determined by arguments as follows: +** +** -f|--file FILENAME File to attach to the message +** -m|--message TEXT Text of the chat message +** --unsafe Allow the use of unencrypted http:// +** +** Additional subcommands may be added in the future. +*/ +void chat_command(void){ + const char *zUrl = find_option("remote",0,1); + int urlFlags = 0; + int isDefaultUrl = 0; + int i; + + db_find_and_open_repository(0,0); + if( zUrl ){ + urlFlags = URL_PROMPT_PW; + }else{ + zUrl = db_get("last-sync-url",0); + if( zUrl==0 ){ + fossil_fatal("no \"remote\" repository defined"); + }else{ + isDefaultUrl = 1; + } + } + url_parse(zUrl, urlFlags); + if( g.url.isFile || g.url.isSsh ){ + fossil_fatal("chat only works for http:// and https:// URLs"); + } + i = (int)strlen(g.url.path); + while( i>0 && g.url.path[i-1]=='/' ) i--; + if( g.url.port==g.url.dfltPort ){ + zUrl = mprintf( + "%s://%T%.*T", + g.url.protocol, g.url.name, i, g.url.path + ); + }else{ + zUrl = mprintf( + "%s://%T:%d%.*T", + g.url.protocol, g.url.name, g.url.port, i, g.url.path + ); + } + if( g.argc==2 ){ + const char *zBrowser = fossil_web_browser(); + char *zCmd; + verify_all_options(); + if( zBrowser==0 ) return; +#ifdef _WIN32 + zCmd = mprintf("%s %s/chat?cli &", zBrowser, zUrl); +#else + zCmd = mprintf("%s \"%s/chat?cli\" &", zBrowser, zUrl); +#endif + fossil_system(zCmd); + }else if( strcmp(g.argv[2],"send")==0 ){ + const char *zFilename = find_option("file","r",1); + const char *zMsg = find_option("message","m",1); + int allowUnsafe = find_option("unsafe",0,0)!=0; + const int mFlags = HTTP_GENERIC | HTTP_QUIET | HTTP_NOCOMPRESS; + int i; + const char *zPw; + char *zLMTime; + Blob up, down, fcontent; + char zBoundary[80]; + sqlite3_uint64 r[3]; + if( zFilename==0 && zMsg==0 ){ + fossil_fatal("must have --message or --file or both"); + } + if( !g.url.isHttps && !allowUnsafe ){ + fossil_fatal("URL \"%s\" is unencrypted. Use https:// instead", zUrl); + } + verify_all_options(); + if( g.argc>3 ){ + fossil_fatal("unknown extra argument: \"%s\"", g.argv[3]); + } + i = (int)strlen(g.url.path); + while( i>0 && g.url.path[i-1]=='/' ) i--; + g.url.path = mprintf("%.*s/chat-send", i, g.url.path); + blob_init(&up, 0, 0); + blob_init(&down, 0, 0); + sqlite3_randomness(sizeof(r),r); + sqlite3_snprintf(sizeof(zBoundary),zBoundary, + "--------%016llu%016llu%016llu", r[0], r[1], r[2]); + blob_appendf(&up, "%s", zBoundary); + zLMTime = db_text(0, + "SELECT strftime('%%Y-%%m-%%dT%%H:%%M:%%S','now','localtime')"); + if( zLMTime ){ + blob_appendf(&up,"\r\nContent-Disposition: form-data; name=\"lmtime\"\r\n" + "\r\n%z\r\n%s", zLMTime, zBoundary); + } + if( g.url.user && g.url.user[0] ){ + blob_appendf(&up,"\r\nContent-Disposition: form-data; name=\"resid\"\r\n" + "\r\n%z\r\n%s", obscure(g.url.user), zBoundary); + } + zPw = g.url.passwd; + if( zPw==0 && isDefaultUrl ) zPw = unobscure(db_get("last-sync-pw", 0)); + if( zPw && zPw[0] ){ + blob_appendf(&up,"\r\nContent-Disposition: form-data; name=\"token\"\r\n" + "\r\n%z\r\n%s", obscure(zPw), zBoundary); + } + if( zMsg && zMsg[0] ){ + blob_appendf(&up,"\r\nContent-Disposition: form-data; name=\"msg\"\r\n" + "\r\n%s\r\n%s", zMsg, zBoundary); + } + if( zFilename && blob_read_from_file(&fcontent, zFilename, ExtFILE)>0 ){ + char *zFN = mprintf("%s", file_tail(zFilename)); + int i; + const char *zMime = mimetype_from_name(zFilename); + for(i=0; zFN[i]; i++){ + char c = zFN[i]; + if( fossil_isalnum(c) ) continue; + if( c=='.' ) continue; + if( c=='-' ) continue; + zFN[i] = '_'; + } + blob_appendf(&up,"\r\nContent-Disposition: form-data; name=\"file\";" + " filename=\"%s\"\r\n", zFN); + blob_appendf(&up,"Content-Type: %s\r\n\r\n", zMime); + blob_append(&up, fcontent.aData, fcontent.nUsed); + blob_appendf(&up,"\r\n%s", zBoundary); + } + blob_append(&up,"--\r\n", 4); + http_exchange(&up, &down, mFlags, 4, "multipart/form-data"); + blob_reset(&up); + if( sqlite3_strglob("{\"isError\": true,*", blob_str(&down))==0 ){ + if( strstr(blob_str(&down), "not logged in")!=0 ){ + fossil_print("ERROR: username and/or password is incorrect\n"); + }else{ + fossil_print("ERROR: %s\n", blob_str(&down)); + } + fossil_fatal("unable to send the chat message"); + } + blob_reset(&down); + }else if( strcmp(g.argv[2],"url")==0 ){ + /* Undocumented command. Show the URL to access chat. */ + fossil_print("%s/chat\n", zUrl); + }else{ + fossil_fatal("no such subcommand \"%s\". Use --help for help", g.argv[2]); + } +} ADDED src/chat.js Index: src/chat.js ================================================================== --- /dev/null +++ src/chat.js @@ -0,0 +1,1269 @@ +/** + This file contains the client-side implementation of fossil's /chat + application. +*/ +(function(){ + const F = window.fossil, D = F.dom; + const E1 = function(selector){ + const e = document.querySelector(selector); + if(!e) throw new Error("missing required DOM element: "+selector); + return e; + }; + /** + Returns true if e is entirely within the bounds of the window's viewport. + */ + const isEntirelyInViewport = function(e) { + const rect = e.getBoundingClientRect(); + return ( + rect.top >= 0 && + rect.left >= 0 && + rect.bottom <= (window.innerHeight || document.documentElement.clientHeight) && + rect.right <= (window.innerWidth || document.documentElement.clientWidth) + ); + }; + + /** + Returns true if e's on-screen position vertically overlaps that + of element v's. Horizontal overlap is ignored (we don't need it + for our case). + */ + const overlapsElemView = function(e,v) { + const r1 = e.getBoundingClientRect(), + r2 = v.getBoundingClientRect(); + if(r1.top<=r2.bottom && r1.top>=r2.top) return true; + else if(r1.bottom<=r2.bottom && r1.bottom>=r2.top) return true; + return false; + }; + + (function(){ + let dbg = document.querySelector('#debugMsg'); + if(dbg){ + /* This can inadvertently influence our flexbox layouts, so move + it out of the way. */ + D.append(document.body,dbg); + } + })(); + const ForceResizeKludge = 0 ? function(){} : (function f(){ + /* Workaround for Safari mayhem regarding use of vh CSS units.... + We tried to use vh units to set the content area size for the + chat layout, but Safari chokes on that, so we calculate that + height here: 85% when in "normal" mode and 95% in chat-only + mode. Larger than ~95% is too big for Firefox on Android, + causing the input area to move off-screen. */ + if(!f.elemsToCount){ + f.elemsToCount = [ + document.querySelector('body > div.header'), + document.querySelector('body > div.mainmenu'), + document.querySelector('body > #hbdrop'), + document.querySelector('body > div.footer') + ]; + f.contentArea = E1('div.content'); + } + const bcl = document.body.classList; + const resized = function(){ + const wh = window.innerHeight, + com = bcl.contains('chat-only-mode'); + var ht; + var extra = 0; + if(com){ + ht = wh; + }else{ + f.elemsToCount.forEach((e)=>e ? extra += D.effectiveHeight(e) : false); + ht = wh - extra; + } + f.contentArea.style.height = + f.contentArea.style.maxHeight = [ + "calc(", (ht>=100 ? ht : 100), "px", + " - 1em"/*fudge value*/,")" + /* ^^^^ hypothetically not needed, but both Chrome/FF on + Linux will force scrollbars on the body if this value is + too small (<0.75em in my tests). */ + ].join(''); + if(false){ + console.debug("resized.",wh, extra, ht, + window.getComputedStyle(f.contentArea).maxHeight, + f.contentArea); + } + }; + var doit; + window.addEventListener('resize',function(ev){ + clearTimeout(doit); + doit = setTimeout(resized, 100); + }, false); + resized(); + return resized; + })(); + fossil.FRK = ForceResizeKludge/*for debugging*/; + const Chat = (function(){ + const cs = { + verboseErrors: false /* if true then certain, mostly extraneous, + error messages may be sent to the console. */, + e:{/*map of certain DOM elements.*/ + messageInjectPoint: E1('#message-inject-point'), + pageTitle: E1('head title'), + loadOlderToolbar: undefined /* the load-posts toolbar (dynamically created) */, + inputWrapper: E1("#chat-input-area"), + fileSelectWrapper: E1('#chat-input-file-area'), + messagesWrapper: E1('#chat-messages-wrapper'), + inputForm: E1('#chat-form'), + btnSubmit: E1('#chat-message-submit'), + inputSingle: E1('#chat-input-single'), + inputMulti: E1('#chat-input-multi'), + inputCurrent: undefined/*one of inputSingle or inputMulti*/, + inputFile: E1('#chat-input-file'), + contentDiv: E1('div.content') + }, + me: F.user.name, + mxMsg: F.config.chat.initSize ? -F.config.chat.initSize : -50, + mnMsg: undefined/*lowest message ID we've seen so far (for history loading)*/, + pageIsActive: 'visible'===document.visibilityState, + changesSincePageHidden: 0, + notificationBubbleColor: 'white', + totalMessageCount: 0, // total # of inbound messages + //! Number of messages to load for the history buttons + loadMessageCount: Math.abs(F.config.chat.initSize || 20), + ajaxInflight: 0, + /** Gets (no args) or sets (1 arg) the current input text field value, + taking into account single- vs multi-line input. The getter returns + a string and the setter returns this object. */ + inputValue: function(){ + const e = this.inputElement(); + if(arguments.length){ + e.value = arguments[0]; + return this; + } + return e.value; + }, + /** Asks the current user input field to take focus. Returns this. */ + inputFocus: function(){ + this.inputElement().focus(); + return this; + }, + /** Returns the current message input element. */ + inputElement: function(){ + return this.e.inputCurrent; + }, + /** Toggles between single- and multi-line edit modes. Returns this. */ + inputToggleSingleMulti: function(){ + const old = this.e.inputCurrent; + if(this.e.inputCurrent === this.e.inputSingle){ + this.e.inputCurrent = this.e.inputMulti; + }else{ + this.e.inputCurrent = this.e.inputSingle; + } + const m = this.e.messagesWrapper, + sTop = m.scrollTop, + mh1 = m.clientHeight; + D.addClass(old, 'hidden'); + D.removeClass(this.e.inputCurrent, 'hidden'); + const mh2 = m.clientHeight; + m.scrollTo(0, sTop + (mh1-mh2)); + this.e.inputCurrent.value = old.value; + old.value = ''; + return this; + }, + /** + If passed true or no arguments, switches to multi-line mode + if currently in single-line mode. If passed false, switches + to single-line mode if currently in multi-line mode. Returns + this. + */ + inputMultilineMode: function(yes){ + if(!arguments.length) yes = true; + if(yes && this.e.inputCurrent === this.e.inputMulti) return this; + else if(!yes && this.e.inputCurrent === this.e.inputSingle) return this; + else return this.inputToggleSingleMulti(); + }, + /** Enables (if yes is truthy) or disables all elements in + * this.disableDuringAjax. */ + enableAjaxComponents: function(yes){ + D[yes ? 'enable' : 'disable'](this.disableDuringAjax); + return this; + }, + /* Must be called before any API is used which starts ajax traffic. + If this call represents the currently only in-flight ajax request, + all DOM elements in this.disableDuringAjax are disabled. + We cannot do this via a central API because (1) window.fetch()'s + Promise-based API seemingly makes that impossible and (2) the polling + technique holds ajax requests open for as long as possible. A call + to this method obligates the caller to also call ajaxEnd(). + + This must NOT be called for the chat-polling API except, as a + special exception, the very first one which fetches the + initial message list. + */ + ajaxStart: function(){ + if(1===++this.ajaxInflight){ + this.enableAjaxComponents(false); + } + }, + /* Must be called after any ajax-related call for which + ajaxStart() was called, regardless of success or failure. If + it was the last such call (as measured by calls to + ajaxStart() and ajaxEnd()), elements disabled by a prior call + to ajaxStart() will be re-enabled. */ + ajaxEnd: function(){ + if(0===--this.ajaxInflight){ + this.enableAjaxComponents(true); + } + }, + disableDuringAjax: [ + /* List of DOM elements disable while ajax traffic is in + transit. Must be populated before ajax starts. We do this + to avoid various race conditions in the UI and long-running + network requests. */ + ], + /** Either scrolls .message-widget element eMsg into view + immediately or, if it represents an inlined image, delays + the scroll until the image is loaded, at which point it will + scroll to either the newest message, if one is set or to + eMsg (the liklihood is good, at least on initial page load, + that the the image won't be loaded until other messages have + been injected). */ + scheduleScrollOfMsg: function(eMsg){ + if(1===+eMsg.dataset.hasImage){ + eMsg.querySelector('img').addEventListener( + 'load', ()=>(this.e.newestMessage || eMsg).scrollIntoView(false) + ); + }else{ + eMsg.scrollIntoView(false); + } + return this; + }, + /* Injects DOM element e as a new row in the chat, at the oldest + end of the list if atEnd is truthy, else at the newest end of + the list. */ + injectMessageElem: function f(e, atEnd){ + const mip = atEnd ? this.e.loadOlderToolbar : this.e.messageInjectPoint, + holder = this.e.messagesWrapper, + prevMessage = this.e.newestMessage; + if(atEnd){ + const fe = mip.nextElementSibling; + if(fe) mip.parentNode.insertBefore(e, fe); + else D.append(mip.parentNode, e); + }else{ + D.append(holder,e); + this.e.newestMessage = e; + } + if(!atEnd && !this._isBatchLoading + && e.dataset.xfrom!==this.me + && (prevMessage + ? !this.messageIsInView(prevMessage) + : false)){ + /* If a new non-history message arrives while the user is + scrolled elsewhere, do not scroll to the latest + message, but gently alert the user that a new message + has arrived. */ + if(!f.btnDown){ + f.btnDown = D.button("⇣⇣⇣"); + f.btnDown.addEventListener('click',()=>this.scrollMessagesTo(1),false); + } + F.toast.message(f.btnDown," New message has arrived."); + }else if(!this._isBatchLoading && e.dataset.xfrom===Chat.me){ + this.scheduleScrollOfMsg(e); + }else if(!this._isBatchLoading){ + /* When a message from someone else arrives, we have to + figure out whether or not to scroll it into view. Ideally + we'd just stuff it in the UI and let the flexbox layout + DTRT, but Safari has expressed, in no uncertain terms, + some disappointment with that approach, so we'll + heuristicize it: if the previous last message is in view, + assume the user is at or near the input element and + scroll this one into view. If that message is NOT in + view, assume the user is up reading history somewhere and + do NOT scroll because doing so would interrupt + them. There are middle grounds here where the user will + experience a slight UI jolt, but this heuristic mostly + seems to work out okay. If there was no previous message, + assume we don't have any messages yet and go ahead and + scroll this message into view (noting that that scrolling + is hypothetically a no-op in such cases). + + The wrench in these works are posts with IMG tags, as + those images are loaded async and the element does not + yet have enough information to know how far to scroll! + For such cases we have to delay the scroll until the + image loads (and we hope it does so before another + message arrives). + */ + if(1===+e.dataset.hasImage){ + e.querySelector('img').addEventListener('load',()=>e.scrollIntoView()); + }else if(!prevMessage || (prevMessage && isEntirelyInViewport(prevMessage))){ + e.scrollIntoView(false); + } + } + }, + /** Returns true if chat-only mode is enabled. */ + isChatOnlyMode: ()=>document.body.classList.contains('chat-only-mode'), + /** + Enters (if passed a truthy value or no arguments) or leaves + "chat-only" mode. That mode hides the page's header and + footer, leaving only the chat application visible to the + user. + */ + chatOnlyMode: function f(yes){ + if(undefined === f.elemsToToggle){ + f.elemsToToggle = []; + document.querySelectorAll( + ["body > div.header", + "body > div.mainmenu", + "body > div.footer", + "#debugMsg" + ].join(',') + ).forEach((e)=>f.elemsToToggle.push(e)); + } + if(!arguments.length) yes = true; + if(yes === this.isChatOnlyMode()) return this; + if(yes){ + D.addClass(f.elemsToToggle, 'hidden'); + D.addClass(document.body, 'chat-only-mode'); + document.body.scroll(0,document.body.height); + }else{ + D.removeClass(f.elemsToToggle, 'hidden'); + D.removeClass(document.body, 'chat-only-mode'); + } + ForceResizeKludge(); + return this; + }, + /** Tries to scroll the message area to... + <0 = top of the message list, >0 = bottom of the message list, + 0 == the newest message (normally the same position as >1). + */ + scrollMessagesTo: function(where){ + if(where<0){ + Chat.e.messagesWrapper.scrollTop = 0; + }else if(where>0){ + Chat.e.messagesWrapper.scrollTop = Chat.e.messagesWrapper.scrollHeight; + }else if(Chat.e.newestMessage){ + Chat.e.newestMessage.scrollIntoView(false); + } + }, + toggleChatOnlyMode: function(){ + return this.chatOnlyMode(!this.isChatOnlyMode()); + }, + messageIsInView: function(e){ + return e ? overlapsElemView(e, this.e.messagesWrapper) : false; + }, + settings:{ + get: (k,dflt)=>F.storage.get(k,dflt), + getBool: (k,dflt)=>F.storage.getBool(k,dflt), + set: (k,v)=>F.storage.set(k,v), + /* Toggles the boolean setting specified by k. Returns the + new value.*/ + toggle: function(k){ + const v = this.getBool(k); + this.set(k, !v); + return !v; + }, + defaults:{ + "images-inline": !!F.config.chat.imagesInline, + "edit-multiline": false, + "monospace-messages": false, + "chat-only-mode": false, + "audible-alert": true + } + }, + /** Plays a new-message notification sound IF the audible-alert + setting is true, else this is a no-op. Returns this. + */ + playNewMessageSound: function f(){ + if(f.uri){ + try{ + if(!f.audio) f.audio = new Audio(f.uri); + f.audio.currentTime = 0; + f.audio.play(); + }catch(e){ + console.error("Audio playblack failed.",e); + } + } + return this; + }, + /** + Sets the current new-message audio alert URI (must be a + repository-relative path which responds with an audio + file). Pass a falsy value to disable audio alerts. Returns + this. + */ + setNewMessageSound: function f(uri){ + delete this.playNewMessageSound.audio; + this.playNewMessageSound.uri = uri; + this.settings.set('audible-alert', !!uri); + return this; + } + }; + F.fetch.beforesend = ()=>cs.ajaxStart(); + F.fetch.aftersend = ()=>cs.ajaxEnd(); + cs.e.inputCurrent = cs.e.inputSingle; + /* Install default settings... */ + Object.keys(cs.settings.defaults).forEach(function(k){ + const v = cs.settings.get(k,cs); + if(cs===v) cs.settings.set(k,cs.settings.defaults[k]); + }); + if(window.innerWidth<window.innerHeight){ + /* Alignment of 'my' messages: right alignment is conventional + for mobile chat apps but can be difficult to read in wide + windows (desktop/tablet landscape mode), so we default to a + layout based on the apparent "orientation" of the window: + tall vs wide. Can be toggled via settings popup. */ + document.body.classList.add('my-messages-right'); + } + if(cs.settings.getBool('monospace-messages',false)){ + document.body.classList.add('monospace-messages'); + } + cs.inputMultilineMode(cs.settings.getBool('edit-multiline',false)); + cs.chatOnlyMode(cs.settings.getBool('chat-only-mode')); + cs.pageTitleOrig = cs.e.pageTitle.innerText; + const qs = (e)=>document.querySelector(e); + const argsToArray = function(args){ + return Array.prototype.slice.call(args,0); + }; + /** + Reports an error via console.error() and as a toast message. + Accepts any argument types valid for fossil.toast.error(). + */ + cs.reportError = function(/*msg args*/){ + const args = argsToArray(arguments); + console.error("chat error:",args); + F.toast.error.apply(F.toast, args); + }; + /** + Reports an error in the form of a new message in the chat + feed. All arguments are appended to the message's content area + using fossil.dom.append(), so may be of any type supported by + that function. + */ + cs.reportErrorAsMessage = function(/*msg args*/){ + const args = argsToArray(arguments); + console.error("chat error:",args); + const d = new Date().toISOString(), + mw = new this.MessageWidget({ + isError: true, + xfrom: null, + msgid: -1, + mtime: d, + lmtime: d, + xmsg: args + }); + this.injectMessageElem(mw.e.body); + mw.scrollIntoView(); + }; + + cs.getMessageElemById = function(id){ + return qs('[data-msgid="'+id+'"]'); + }; + + /** Finds the last .message-widget element and returns it or + the undefined value if none are found. */ + cs.fetchLastMessageElem = function(){ + const msgs = document.querySelectorAll('.message-widget'); + var rc; + if(msgs.length){ + rc = this.e.newestMessage = msgs[msgs.length-1]; + } + return rc; + }; + + /** + LOCALLY deletes a message element by the message ID or passing + the .message-row element. Returns true if it removes an element, + else false. + */ + cs.deleteMessageElem = function(id){ + var e; + if(id instanceof HTMLElement){ + e = id; + id = e.dataset.msgid; + }else{ + e = this.getMessageElemById(id); + } + if(e && id){ + D.remove(e); + if(e===this.e.newestMessage){ + this.fetchLastMessageElem(); + } + F.toast.message("Deleted message "+id+"."); + } + return !!e; + }; + + /** Given a .message-row element, this function returns whethe the + current user may, at least hypothetically, delete the message + globally. A user may always delete a local copy of a + post. The server may trump this, e.g. if the login has been + cancelled after this page was loaded. + */ + cs.userMayDelete = function(eMsg){ + return +eMsg.dataset.msgid>0 + && (this.me === eMsg.dataset.xfrom + || F.user.isAdmin/*will be confirmed server-side*/); + }; + + /** Returns a new Error() object encapsulating state from the given + fetch() response promise. */ + cs._newResponseError = function(response){ + return new Error([ + "HTTP status ", response.status,": ",response.url,": ", + response.statusText].join('')); + }; + + /** Helper for reporting HTTP-level response errors via fetch(). + If response.ok then response.json() is returned, else an Error + is thrown. */ + cs._fetchJsonOrError = function(response){ + if(response.ok) return response.json(); + else throw cs._newResponseError(response); + }; + + /** + Removes the given message ID from the local chat record and, if + the message was posted by this user OR this user in an + admin/setup, also submits it for removal on the remote. + + id may optionally be a DOM element, in which case it must be a + .message-row element. + */ + cs.deleteMessage = function(id){ + var e; + if(id instanceof HTMLElement){ + e = id; + id = e.dataset.msgid; + }else{ + e = this.getMessageElemById(id); + } + if(!(e instanceof HTMLElement)) return; + if(this.userMayDelete(e)){ + this.ajaxStart(); + F.fetch("chat-delete/" + id, { + responseType: 'json', + onload:(r)=>this.deleteMessageElem(r), + onerror:(err)=>this.reportErrorAsMessage(err) + }); + }else{ + this.deleteMessageElem(id); + } + }; + document.addEventListener('visibilitychange', function(ev){ + cs.pageIsActive = ('visible' === document.visibilityState); + if(cs.pageIsActive){ + cs.e.pageTitle.innerText = cs.pageTitleOrig; + } + }, true); + return cs; + })()/*Chat initialization*/; + + /** + Custom widget type for rendering messages (one message per + instance). These are modelled after FIELDSET elements but we + don't use FIELDSET because of cross-browser inconsistencies in + features of the FIELDSET/LEGEND combination, e.g. inability to + align legends via CSS in Firefox and clicking-related + deficiencies in Safari. + */ + Chat.MessageWidget = (function(){ + /** + Constructor. If passed an argument, it is passed to + this.setMessage() after initialization. + */ + const cf = function(){ + this.e = { + body: D.addClass(D.div(), 'message-widget'), + tab: D.addClass(D.span(), 'message-widget-tab'), + content: D.addClass(D.div(), 'message-widget-content') + }; + D.append(this.e.body, this.e.tab, this.e.content); + this.e.tab.setAttribute('role', 'button'); + if(arguments.length){ + this.setMessage(arguments[0]); + } + }; + /* Left-zero-pad a number to at least 2 digits */ + const pad2 = (x)=>(''+x).length>1 ? x : '0'+x; + const dowMap = { + /* Map of Date.getDay() values to weekday names. */ + 0: "Sunday", 1: "Monday", 2: "Tuesday", + 3: "Wednesday", 4: "Thursday", 5: "Friday", + 6: "Saturday" + }; + /* Given a Date, returns the timestamp string used in the + "tab" part of message widgets. */ + const theTime = function(d){ + return [ + //d.getFullYear(),'-',pad2(d.getMonth()+1/*sigh*/), + //'-',pad2(d.getDate()), ' ', + d.getHours(),":", + (d.getMinutes()+100).toString().slice(1,3), + ' ', dowMap[d.getDay()] + ].join(''); + }; + /** Returns the local time string of Date object d, defaulting + to the current time. */ + const localTimeString = function ff(d){ + d || (d = new Date()); + return [ + d.getFullYear(),'-',pad2(d.getMonth()+1/*sigh*/), + '-',pad2(d.getDate()), + ' ',pad2(d.getHours()),':',pad2(d.getMinutes()), + ':',pad2(d.getSeconds()) + ].join(''); + }; + cf.prototype = { + scrollIntoView: function(){ + this.e.content.scrollIntoView(); + }, + setMessage: function(m){ + const ds = this.e.body.dataset; + ds.timestamp = m.mtime; + ds.lmtime = m.lmtime; + ds.msgid = m.msgid; + ds.xfrom = m.xfrom || ''; + if(m.xfrom === Chat.me){ + D.addClass(this.e.body, 'mine'); + } + if(m.uclr){ + this.e.content.style.backgroundColor = m.uclr; + this.e.tab.style.backgroundColor = m.uclr; + } + const d = new Date(m.mtime); + D.clearElement(this.e.tab); + var contentTarget = this.e.content; + var eXFrom /* element holding xfrom name */; + if(m.xfrom){ + eXFrom = D.append(D.addClass(D.span(), 'xfrom'), m.xfrom); + D.append( + this.e.tab, eXFrom, + D.text(" #",(m.msgid||'???'),' @ ',theTime(d)) + ); + }else{/*notification*/ + D.addClass(this.e.body, 'notification'); + if(m.isError){ + D.addClass([contentTarget, this.e.tab], 'error'); + } + D.append( + this.e.tab, + D.text('notification @ ',theTime(d)) + ); + } + if( m.xfrom && m.fsize>0 ){ + if( m.fmime + && m.fmime.startsWith("image/") + && Chat.settings.getBool('images-inline',true) + ){ + contentTarget.appendChild(D.img("chat-download/" + m.msgid)); + ds.hasImage = 1; + }else{ + const a = D.a( + window.fossil.rootPath+ + 'chat-download/' + m.msgid+'/'+encodeURIComponent(m.fname), + // ^^^ add m.fname to URL to cause downloaded file to have that name. + "(" + m.fname + " " + m.fsize + " bytes)" + ) + D.attr(a,'target','_blank'); + contentTarget.appendChild(a); + } + } + if(m.xmsg){ + if(m.fsize>0){ + /* We have file/image content, so need another element for + the message text. */ + contentTarget = D.div(); + D.append(this.e.content, contentTarget); + } + // The m.xmsg text comes from the same server as this script and + // is guaranteed by that server to be "safe" HTML - safe in the + // sense that it is not possible for a malefactor to inject HTML + // or javascript or CSS. The m.xmsg content might contain + // hyperlinks, but otherwise it will be markup-free. See the + // chat_format_to_html() routine in the server for details. + // + // Hence, even though innerHTML is normally frowned upon, it is + // perfectly safe to use in this context. + if(m.xmsg instanceof Array){ + // Used by Chat.reportErrorAsMessage() + D.append(contentTarget, m.xmsg); + }else{ + contentTarget.innerHTML = m.xmsg; + } + } + this.e.tab.addEventListener('click', this._handleLegendClicked, false); + if(eXFrom){ + eXFrom.addEventListener('click', ()=>this.e.tab.click(), false); + } + return this; + }, + /* Event handler for clicking .message-user elements to show their + timestamps. */ + _handleLegendClicked: function f(ev){ + if(!f.popup){ + /* Timestamp popup widget */ + f.popup = new F.PopupWidget({ + cssClass: ['fossil-tooltip', 'chat-message-popup'], + refresh:function(){ + const eMsg = this._eMsg; + if(!eMsg) return; + D.clearElement(this.e); + const d = new Date(eMsg.dataset.timestamp); + if(d.getMinutes().toString()!=="NaN"){ + // Date works, render informative timestamps + const xfrom = eMsg.dataset.xfrom || 'server'; + D.append(this.e, + D.append(D.span(), localTimeString(d)," ",Chat.me," time"), + D.append(D.span(), iso8601ish(d))); + if(eMsg.dataset.lmtime && xfrom!==Chat.me){ + D.append(this.e, + D.append(D.span(), localTime8601( + new Date(eMsg.dataset.lmtime) + ).replace('T',' ')," ",xfrom," time")); + } + }else{ + /* This might not be necessary any more: it was + initially caused by Safari being stricter than + other browsers on its time string input, and that + has since been resolved by emiting a stricter + format. */ + // Date doesn't work, so dumb it down... + D.append(this.e, D.append(D.span(), eMsg.dataset.timestamp," zulu")); + } + const toolbar = D.addClass(D.div(), 'toolbar'); + D.append(this.e, toolbar); + const btnDeleteLocal = D.button("Delete locally"); + D.append(toolbar, btnDeleteLocal); + const self = this; + btnDeleteLocal.addEventListener('click', function(){ + self.hide(); + Chat.deleteMessageElem(eMsg); + }); + if(Chat.userMayDelete(eMsg)){ + const btnDeleteGlobal = D.button("Delete globally"); + D.append(toolbar, btnDeleteGlobal); + btnDeleteGlobal.addEventListener('click', function(){ + self.hide(); + Chat.deleteMessage(eMsg); + }); + } + if(eMsg.dataset.xfrom){ + /* Add a link to the /timeline filtered on this user. */ + const toolbar2 = D.addClass(D.div(), 'toolbar'); + D.append(this.e, toolbar2); + const timelineLink = D.attr( + D.a(F.repoUrl('timeline',{ + u: eMsg.dataset.xfrom, + y: 'a' + }), "User's Timeline"), + 'target', '_blank' + ); + D.append(toolbar2, timelineLink); + } + }/*refresh()*/ + }); + f.popup.installHideHandlers(); + f.popup.hide = function(){ + delete this._eMsg; + D.clearElement(this.e); + return this.show(false); + }; + }/*end static init*/ + const rect = ev.target.getBoundingClientRect(); + const eMsg = ev.target.parentNode/*the owning .message-widget element*/; + f.popup._eMsg = eMsg; + let x = rect.left, y = rect.topm; + f.popup.show(ev.target)/*so we can get its computed size*/; + if(eMsg.dataset.xfrom===Chat.me + && document.body.classList.contains('my-messages-right')){ + // Shift popup to the left for right-aligned messages to avoid + // truncation off the right edge of the page. + const pRect = f.popup.e.getBoundingClientRect(); + x = rect.right - pRect.width; + } + f.popup.show(x, y); + }/*_handleLegendClicked()*/ + }; + return cf; + })()/*MessageWidget*/; + + const BlobXferState = (function(){/*drag/drop bits...*/ + /* State for paste and drag/drop */ + const bxs = { + dropDetails: document.querySelector('#chat-drop-details'), + blob: undefined, + clear: function(){ + this.blob = undefined; + D.clearElement(this.dropDetails); + Chat.e.inputFile.value = ""; + } + }; + /** Updates the paste/drop zone with details of the pasted/dropped + data. The argument must be a Blob or Blob-like object (File) or + it can be falsy to reset/clear that state.*/ + const updateDropZoneContent = function(blob){ + const dd = bxs.dropDetails; + bxs.blob = blob; + D.clearElement(dd); + if(!blob){ + Chat.e.inputFile.value = ''; + return; + } + D.append(dd, "Name: ", blob.name, + D.br(), "Size: ",blob.size); + if(blob.type && blob.type.startsWith("image/")){ + const img = D.img(); + D.append(dd, D.br(), img); + const reader = new FileReader(); + reader.onload = (e)=>img.setAttribute('src', e.target.result); + reader.readAsDataURL(blob); + } + const btn = D.button("Cancel"); + D.append(dd, D.br(), btn); + btn.addEventListener('click', ()=>updateDropZoneContent(), false); + }; + Chat.e.inputFile.addEventListener('change', function(ev){ + updateDropZoneContent(this.files && this.files[0] ? this.files[0] : undefined) + }); + /* Handle image paste from clipboard. TODO: figure out how we can + paste non-image binary data as if it had been selected via the + file selection element. */ + document.addEventListener('paste', function(event){ + const items = event.clipboardData.items, + item = items[0]; + if(!item || !item.type) return; + else if('file'===item.kind){ + updateDropZoneContent(false/*clear prev state*/); + updateDropZoneContent(items[0].getAsFile()); + } + }, false); + /* Add help button for drag/drop/paste zone */ + Chat.e.inputFile.parentNode.insertBefore( + F.helpButtonlets.create( + Chat.e.fileSelectWrapper.querySelector('.help-buttonlet') + ), Chat.e.inputFile + ); + //////////////////////////////////////////////////////////// + // File drag/drop visual notification. + const dropHighlight = Chat.e.inputFile /* target zone */; + const dropEvents = { + drop: function(ev){ + D.removeClass(dropHighlight, 'dragover'); + }, + dragenter: function(ev){ + ev.preventDefault(); + ev.dataTransfer.dropEffect = "copy"; + D.addClass(dropHighlight, 'dragover'); + }, + dragleave: function(ev){ + D.removeClass(dropHighlight, 'dragover'); + }, + dragend: function(ev){ + D.removeClass(dropHighlight, 'dragover'); + } + }; + Object.keys(dropEvents).forEach( + (k)=>Chat.e.inputFile.addEventListener(k, dropEvents[k], true) + ); + return bxs; + })()/*drag/drop*/; + + const tzOffsetToString = function(off){ + const hours = Math.round(off/60), min = Math.round(off % 30); + return ''+(hours + (min ? '.5' : '')); + }; + const pad2 = (x)=>('0'+x).substr(-2); + const localTime8601 = function(d){ + return [ + d.getYear()+1900, '-', pad2(d.getMonth()+1), '-', pad2(d.getDate()), + 'T', pad2(d.getHours()),':', pad2(d.getMinutes()),':',pad2(d.getSeconds()) + ].join(''); + }; + + Chat.submitMessage = function(){ + const fd = new FormData(this.e.inputForm) + /* ^^^^ we don't really want/need the FORM element, but when + FormData() is default-constructed here then the server + segfaults, and i have no clue why! */; + const msg = this.inputValue().trim(); + if(msg) fd.set('msg',msg); + const file = BlobXferState.blob || this.e.inputFile.files[0]; + if(file) fd.set("file", file); + if( !msg && !file ) return; + const self = this; + fd.set("lmtime", localTime8601(new Date())); + F.fetch("chat-send",{ + payload: fd, + responseType: 'text', + onerror:(err)=>this.reportErrorAsMessage(err), + onload:function(txt){ + if(!txt) return/*success response*/; + try{ + const json = JSON.parse(txt); + self.newContent({msgs:[json]}); + }catch(e){ + self.reportError(e); + return; + } + } + }); + BlobXferState.clear(); + Chat.inputValue("").inputFocus(); + }; + + Chat.e.inputSingle.addEventListener('keydown',function(ev){ + if(13===ev.keyCode/*ENTER*/){ + ev.preventDefault(); + ev.stopPropagation(); + Chat.submitMessage(); + return false; + } + }, false); + Chat.e.inputMulti.addEventListener('keydown',function(ev){ + if(ev.ctrlKey && 13 === ev.keyCode){ + ev.preventDefault(); + ev.stopPropagation(); + Chat.submitMessage(); + return false; + } + }, false); + Chat.e.btnSubmit.addEventListener('click',(e)=>{ + e.preventDefault(); + Chat.submitMessage(); + return false; + }); + + /* Returns an almost-ISO8601 form of Date object d. */ + const iso8601ish = function(d){ + return d.toISOString() + .replace('T',' ').replace(/\.\d+/,'').replace('Z', ' zulu'); + }; + + (function(){/*Set up #chat-settings-button */ + const settingsButton = document.querySelector('#chat-settings-button'); + var popupSize = undefined/*placement workaround*/; + const settingsPopup = new F.PopupWidget({ + cssClass: ['fossil-tooltip', 'chat-settings-popup'] + }); + /* Settings menu entries... */ + const settingsOps = [{ + label: "Multi-line input", + boolValue: ()=>Chat.inputElement()===Chat.e.inputMulti, + persistentSetting: 'edit-multiline', + callback: function(){ + Chat.inputToggleSingleMulti(); + } + },{ + label: "Monospace message font", + boolValue: ()=>document.body.classList.contains('monospace-messages'), + persistentSetting: 'monospace-messages', + callback: function(){ + document.body.classList.toggle('monospace-messages'); + } + },{ + label: "Chat-only mode", + boolValue: ()=>Chat.isChatOnlyMode(), + persistentSetting: 'chat-only-mode', + callback: function(){ + Chat.toggleChatOnlyMode(); + } + },{ + label: "Left-align my posts", + boolValue: ()=>!document.body.classList.contains('my-messages-right'), + callback: function f(){ + document.body.classList.toggle('my-messages-right'); + } + },{ + label: "Images inline", + boolValue: ()=>Chat.settings.getBool('images-inline'), + callback: function(){ + const v = Chat.settings.toggle('images-inline'); + F.toast.message("Image mode set to "+(v ? "inline" : "hyperlink")+"."); + } + }]; + + /** Set up selection list of notification sounds. */ + if(true/*flip this to false to enable selection of audio files*/){ + settingsOps.push({ + label: "Audible alerts", + boolValue: ()=>Chat.settings.getBool('audible-alert'), + callback: function(){ + const v = Chat.settings.toggle('audible-alert'); + Chat.setNewMessageSound(v ? F.config.chat.alertSound : false); + if(v) setTimeout(()=>Chat.playNewMessageSound(), 50); + F.toast.message("Audio notifications "+(v ? "enabled" : "disabled")+"."); + } + }); + Chat.setNewMessageSound( + Chat.settings.getBool('audible-alert') ? F.config.chat.alertSound : false + ); + }else{ + /* Disabled per chatroom discussion: selection list of audio files for + chat notification. */ + const selectSound = settingsOps.selectSound = D.addClass(D.select(), 'menu-entry'); + D.disable(D.option(selectSound, "0", "Audible alert...")); + D.option(selectSound, "", "(no audio)"); + F.config.chat.alerts.forEach(function(a){ + D.option(selectSound, a); + }); + if(true===Chat.settings.getBool('audible-alert')){ + selectSound.selectedIndex = 2/*first audio file in the list*/; + }else{ + selectSound.value = Chat.settings.get('audible-alert',''); + if(selectSound.selectedIndex<0){ + /*Missing file - removed after this setting was applied. Fall back + to the first sound in the list. */ + selectSound.selectedIndex = 2; + } + } + selectSound.addEventListener('change',function(){ + const v = this.selectedIndex>1 ? this.value : ''; + Chat.setNewMessageSound(v); + F.toast.message("Audio notifications "+(v ? "enabled" : "disabled")+"."); + if(v) setTimeout(()=>Chat.playNewMessageSound(), 50); + settingsPopup.hide(); + }, false); + Chat.setNewMessageSound(selectSound.value); + }/*audio notification config*/ + /** + Rebuild the menu each time it's shown so that the toggles can + show their current values. + */ + settingsPopup.options.refresh = function(){ + D.clearElement(this.e); + settingsOps.forEach(function(op){ + const line = D.addClass(D.span(), 'menu-entry'); + const btn = D.append(D.addClass(D.span(), 'button'), op.label); + const callback = function(ev){ + settingsPopup.hide(); + op.callback(ev); + if(op.persistentSetting){ + Chat.settings.set(op.persistentSetting, op.boolValue()); + } + }; + D.append(line, btn); + if(op.hasOwnProperty('boolValue')){ + const check = D.attr(D.checkbox(1, op.boolValue()), + 'aria-label', op.label); + D.append(line, check); + } + D.append(settingsPopup.e, line); + line.addEventListener('click', callback); + }); + if(settingsOps.selectSound){ + D.append(settingsPopup.e, settingsOps.selectSound); + } + }; + settingsPopup.installHideHandlers( + false, settingsOps.selectSound ? false : true, + true) + /** Reminder: click-to-hide interferes with "?" embedded within + the popup, so cannot be used together with those. Enabling + this means, however, that tapping the menu button to toggle + the menu cannot work because tapping the menu button while the + menu is opened will, because of the click-to-hide handler, + hide the menu before the button gets an event saying to toggle + it. + + Reminder: because we need a SELECT element for the audio file + selection (since that list can be arbitrarily long), we have + to disable tap-outside-the-popup-to-close-it via passing false + as the 2nd argument to installHideHandlers(). If we don't, + tapping on the select element is unreliable on desktop + browsers and doesn't seem to work at all on mobile. */; + D.attr(settingsButton, 'role', 'button'); + settingsButton.addEventListener('click',function(ev){ + //ev.preventDefault(); + if(settingsPopup.isShown()) settingsPopup.hide(); + else settingsPopup.show(settingsButton); + /* Reminder: we cannot toggle the visibility from her + */ + }, false); + + /* Find an ideal X/Y position for the popup, directly above the settings + button, based on the size of the popup... */ + settingsPopup.show(document.body); + popupSize = settingsPopup.e.getBoundingClientRect(); + settingsPopup.hide(); + settingsPopup.options.adjustX = function(x){ + const rect = settingsButton.getBoundingClientRect(); + return rect.right - popupSize.width; + }; + settingsPopup.options.adjustY = function(y){ + const rect = settingsButton.getBoundingClientRect(); + return rect.top - popupSize.height -2; + }; + })()/*#chat-settings-button setup*/; + + /** Callback for poll() to inject new content into the page. jx == + the response from /chat-poll. If atEnd is true, the message is + appended to the end of the chat list (for loading older + messages), else the beginning (the default). */ + const newcontent = function f(jx,atEnd){ + if(!f.processPost){ + /** Processes chat message m, placing it either the start (if atEnd + is falsy) or end (if atEnd is truthy) of the chat history. atEnd + should only be true when loading older messages. */ + f.processPost = function(m,atEnd){ + ++Chat.totalMessageCount; + if( m.msgid>Chat.mxMsg ) Chat.mxMsg = m.msgid; + if( !Chat.mnMsg || m.msgid<Chat.mnMsg) Chat.mnMsg = m.msgid; + if( m.mdel ){ + /* A record deletion notice. */ + Chat.deleteMessageElem(m.mdel); + return; + } + if(!Chat._isBatchLoading /*&& Chat.me!==m.xfrom*/ && Chat.playNewMessageSound){ + Chat.playNewMessageSound(); + } + const row = new Chat.MessageWidget(m); + Chat.injectMessageElem(row.e.body,atEnd); + if(m.isError){ + Chat._gotServerError = m; + } + }/*processPost()*/; + }/*end static init*/ + jx.msgs.forEach((m)=>f.processPost(m,atEnd)); + if('visible'===document.visibilityState){ + if(Chat.changesSincePageHidden){ + Chat.changesSincePageHidden = 0; + Chat.e.pageTitle.innerText = Chat.pageTitleOrig; + } + }else{ + Chat.changesSincePageHidden += jx.msgs.length; + if(jx.msgs.length){ + Chat.e.pageTitle.innerText = '[*] '+Chat.pageTitleOrig; + } + } + }/*newcontent()*/; + Chat.newContent = newcontent; + + (function(){ + /** Add toolbar for loading older messages. We use a FIELDSET here + because a fieldset is the only parent element type which can + automatically enable/disable its children by + enabling/disabling the parent element. */ + const loadLegend = D.legend("Load..."); + const toolbar = Chat.e.loadOlderToolbar = D.attr( + D.fieldset(loadLegend), "id", "load-msg-toolbar" + ); + Chat.disableDuringAjax.push(toolbar); + /* Loads the next n oldest messages, or all previous history if n is negative. */ + const loadOldMessages = function(n){ + Chat.e.messagesWrapper.classList.add('loading'); + Chat._isBatchLoading = true; + const scrollHt = Chat.e.messagesWrapper.scrollHeight, + scrollTop = Chat.e.messagesWrapper.scrollTop; + F.fetch("chat-poll",{ + urlParams:{ + before: Chat.mnMsg, + n: n + }, + responseType: 'json', + onerror:function(err){ + Chat.reportErrorAsMessage(err); + Chat._isBatchLoading = false; + }, + onload:function(x){ + let gotMessages = x.msgs.length; + newcontent(x,true); + Chat._isBatchLoading = false; + if(Chat._gotServerError){ + Chat._gotServerError = false; + return; + } + if(n<0/*we asked for all history*/ + || 0===gotMessages/*we found no history*/ + || (n>0 && gotMessages<n /*we got fewer history entries than requested*/) + || (n===0 && gotMessages<Chat.loadMessageCount + /*we asked for default amount and got fewer than that.*/)){ + /* We've loaded all history. Permanently disable the + history-load toolbar and keep it from being re-enabled + via the ajaxStart()/ajaxEnd() mechanism... */ + const div = Chat.e.loadOlderToolbar.querySelector('div'); + D.append(D.clearElement(div), "All history has been loaded."); + D.addClass(Chat.e.loadOlderToolbar, 'all-done'); + const ndx = Chat.disableDuringAjax.indexOf(Chat.e.loadOlderToolbar); + if(ndx>=0) Chat.disableDuringAjax.splice(ndx,1); + Chat.e.loadOlderToolbar.disabled = true; + } + if(gotMessages > 0){ + F.toast.message("Loaded "+gotMessages+" older messages."); + /* Return scroll position to where it was when the history load + was requested, per user request */ + Chat.e.messagesWrapper.scrollTo( + 0, Chat.e.messagesWrapper.scrollHeight - scrollHt + scrollTop + ); + } + }, + aftersend:function(){ + Chat.e.messagesWrapper.classList.remove('loading'); + Chat.ajaxEnd(); + } + }); + }; + const wrapper = D.div(); /* browsers don't all properly handle >1 child in a fieldset */; + D.append(toolbar, wrapper); + var btn = D.button("Previous "+Chat.loadMessageCount+" messages"); + D.append(wrapper, btn); + btn.addEventListener('click',()=>loadOldMessages(Chat.loadMessageCount)); + btn = D.button("All previous messages"); + D.append(wrapper, btn); + btn.addEventListener('click',()=>loadOldMessages(-1)); + D.append(Chat.e.messagesWrapper, toolbar); + toolbar.disabled = true /*will be enabled when msg load finishes */; + })()/*end history loading widget setup*/; + + const afterFetch = function f(){ + if(true===f.isFirstCall){ + f.isFirstCall = false; + Chat.ajaxEnd(); + Chat.e.messagesWrapper.classList.remove('loading'); + setTimeout(function(){ + Chat.scrollMessagesTo(1); + }, 250); + } + if(Chat._gotServerError && Chat.intervalTimer){ + clearInterval(Chat.intervalTimer); + Chat.reportErrorAsMessage( + "Shutting down chat poller due to server-side error. ", + "Reload this page to reactivate it."); + delete Chat.intervalTimer; + } + poll.running = false; + }; + afterFetch.isFirstCall = true; + const poll = async function f(){ + if(f.running) return; + f.running = true; + Chat._isBatchLoading = f.isFirstCall; + if(true===f.isFirstCall){ + f.isFirstCall = false; + Chat.ajaxStart(); + Chat.e.messagesWrapper.classList.add('loading'); + } + F.fetch("chat-poll",{ + timeout: 420 * 1000/*FIXME: get the value from the server*/, + urlParams:{ + name: Chat.mxMsg + }, + responseType: "json", + // Disable the ajax start/end handling for this long-polling op: + beforesend: function(){}, + aftersend: function(){}, + onerror:function(err){ + Chat._isBatchLoading = false; + if(Chat.verboseErrors) console.error(err); + /* ^^^ we don't use Chat.reportError() here b/c the polling + fails exepectedly when it times out, but is then immediately + resumed, and reportError() produces a loud error message. */ + afterFetch(); + }, + onload:function(y){ + newcontent(y); + Chat._isBatchLoading = false; + afterFetch(); + } + }); + }; + poll.isFirstCall = true; + Chat._gotServerError = poll.running = false; + if( window.fossil.config.chat.fromcli ){ + Chat.chatOnlyMode(true); + } + Chat.intervalTimer = setInterval(poll, 1000); + F.page.chat = Chat/* enables testing the APIs via the dev tools */; +})(); Index: src/checkin.c ================================================================== --- src/checkin.c +++ src/checkin.c @@ -2,11 +2,11 @@ ** Copyright (c) 2007 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: @@ -21,326 +21,1243 @@ #include "config.h" #include "checkin.h" #include <assert.h> /* -** Generate text describing all changes. Prepend zPrefix to each line -** of output. +** Change filter options. +*/ +enum { + /* Zero-based bit indexes. */ + CB_EDITED , CB_UPDATED , CB_CHANGED, CB_MISSING , CB_ADDED, CB_DELETED, + CB_RENAMED, CB_CONFLICT, CB_META , CB_UNCHANGED, CB_EXTRA, CB_MERGE , + CB_RELPATH, CB_CLASSIFY, CB_MTIME , CB_SIZE , CB_FATAL, CB_COMMENT, + + /* Bitmask values. */ + C_EDITED = 1 << CB_EDITED, /* Edited, merged, and conflicted files. */ + C_UPDATED = 1 << CB_UPDATED, /* Files updated by merge/integrate. */ + C_CHANGED = 1 << CB_CHANGED, /* Treated the same as the above two. */ + C_MISSING = 1 << CB_MISSING, /* Missing and non- files. */ + C_ADDED = 1 << CB_ADDED, /* Added files. */ + C_DELETED = 1 << CB_DELETED, /* Deleted files. */ + C_RENAMED = 1 << CB_RENAMED, /* Renamed files. */ + C_CONFLICT = 1 << CB_CONFLICT, /* Files having merge conflicts. */ + C_META = 1 << CB_META, /* Files with metadata changes. */ + C_UNCHANGED = 1 << CB_UNCHANGED, /* Unchanged files. */ + C_EXTRA = 1 << CB_EXTRA, /* Unmanaged files. */ + C_MERGE = 1 << CB_MERGE, /* Merge contributors. */ + C_FILTER = C_EDITED | C_UPDATED | C_CHANGED | C_MISSING | C_ADDED + | C_DELETED | C_RENAMED | C_CONFLICT | C_META | C_UNCHANGED + | C_EXTRA | C_MERGE, /* All filter bits. */ + C_ALL = C_FILTER & ~(C_EXTRA | C_MERGE),/* All managed files. */ + C_DIFFER = C_FILTER & ~(C_UNCHANGED | C_MERGE),/* All differences. */ + C_RELPATH = 1 << CB_RELPATH, /* Show relative paths. */ + C_CLASSIFY = 1 << CB_CLASSIFY, /* Show file change types. */ + C_DEFAULT = (C_ALL & ~C_UNCHANGED) | C_MERGE | C_CLASSIFY, + C_MTIME = 1 << CB_MTIME, /* Show file modification time. */ + C_SIZE = 1 << CB_SIZE, /* Show file size in bytes. */ + C_FATAL = 1 << CB_FATAL, /* Fail on MISSING/NOT_A_FILE. */ + C_COMMENT = 1 << CB_COMMENT, /* Precede each line with "# ". */ +}; + +/* +** Create a TEMP table named SFILE and add all unmanaged files named on +** the command-line to that table. If directories are named, then add +** all unmanaged files contained underneath those directories. If there +** are no files or directories named on the command-line, then add all +** unmanaged files anywhere in the checkout. +** +** This routine never follows symlinks. It always treats symlinks as +** object unto themselves. +*/ +static void locate_unmanaged_files( + int argc, /* Number of command-line arguments to examine */ + char **argv, /* values of command-line arguments */ + unsigned scanFlags, /* Zero or more SCAN_xxx flags */ + Glob *pIgnore /* Do not add files that match this GLOB */ +){ + Blob name; /* Name of a candidate file or directory */ + char *zName; /* Name of a candidate file or directory */ + int isDir; /* 1 for a directory, 0 if doesn't exist, 2 for anything else */ + int i; /* Loop counter */ + int nRoot; /* length of g.zLocalRoot */ + + db_multi_exec("CREATE TEMP TABLE sfile(pathname TEXT PRIMARY KEY %s," + " mtime INTEGER, size INTEGER)", filename_collation()); + nRoot = (int)strlen(g.zLocalRoot); + if( argc==0 ){ + blob_init(&name, g.zLocalRoot, nRoot - 1); + vfile_scan(&name, blob_size(&name), scanFlags, pIgnore, 0, SymFILE); + blob_reset(&name); + }else{ + for(i=0; i<argc; i++){ + file_canonical_name(argv[i], &name, 0); + zName = blob_str(&name); + isDir = file_isdir(zName, SymFILE); + if( isDir==1 ){ + vfile_scan(&name, nRoot-1, scanFlags, pIgnore, 0, SymFILE); + }else if( isDir==0 ){ + fossil_warning("not found: %s", &zName[nRoot]); + }else if( file_access(zName, R_OK) ){ + fossil_fatal("cannot open %s", &zName[nRoot]); + }else{ + db_multi_exec( + "INSERT OR IGNORE INTO sfile(pathname) VALUES(%Q)", + &zName[nRoot] + ); + } + blob_reset(&name); + } + } +} + +/* +** Generate text describing all changes. ** ** We assume that vfile_check_signature has been run. -** -** If missingIsFatal is true, then any files that are missing or which -** are not true files results in a fatal error. */ static void status_report( Blob *report, /* Append the status report here */ - const char *zPrefix, /* Prefix on each line of the report */ - int missingIsFatal /* MISSING and NOT_A_FILE are fatal errors */ + unsigned flags /* Filter and other configuration flags */ ){ Stmt q; - int nPrefix = strlen(zPrefix); int nErr = 0; - db_prepare(&q, - "SELECT pathname, deleted, chnged, rid, coalesce(origname!=pathname,0)" - " FROM vfile " - " WHERE file_is_selected(id)" - " AND (chnged OR deleted OR rid=0 OR pathname!=origname) ORDER BY 1" - ); + Blob rewrittenPathname; + Blob sql = BLOB_INITIALIZER, where = BLOB_INITIALIZER; + const char *zName; + int i; + + /* Skip the file report if no files are requested at all. */ + if( !(flags & (C_ALL | C_EXTRA)) ){ + goto skipFiles; + } + + /* Assemble the path-limiting WHERE clause, if any. */ + blob_zero(&where); + for(i=2; i<g.argc; i++){ + Blob fname; + file_tree_name(g.argv[i], &fname, 0, 1); + zName = blob_str(&fname); + if( fossil_strcmp(zName, ".")==0 ){ + blob_reset(&where); + break; + } + blob_append_sql(&where, + " %s (pathname=%Q %s) " + "OR (pathname>'%q/' %s AND pathname<'%q0' %s)", + (blob_size(&where)>0) ? "OR" : "AND", zName, + filename_collation(), zName, filename_collation(), + zName, filename_collation() + ); + } + + /* Obtain the list of managed files if appropriate. */ + blob_zero(&sql); + if( flags & C_ALL ){ + /* Start with a list of all managed files. */ + blob_append_sql(&sql, + "SELECT pathname, %s as mtime, %s as size, deleted, chnged, rid," + " coalesce(origname!=pathname,0) AS renamed, 1 AS managed" + " FROM vfile LEFT JOIN blob USING (rid)" + " WHERE is_selected(id)%s", + flags & C_MTIME ? "datetime(checkin_mtime(:vid, rid), " + "'unixepoch', toLocal())" : "''" /*safe-for-%s*/, + flags & C_SIZE ? "coalesce(blob.size, 0)" : "0" /*safe-for-%s*/, + blob_sql_text(&where)); + + /* Exclude unchanged files unless requested. */ + if( !(flags & C_UNCHANGED) ){ + blob_append_sql(&sql, + " AND (chnged OR deleted OR rid=0 OR pathname!=origname)"); + } + } + + /* If C_EXTRA, add unmanaged files to the query result too. */ + if( flags & C_EXTRA ){ + if( blob_size(&sql) ){ + blob_append_sql(&sql, " UNION ALL"); + } + blob_append_sql(&sql, + " SELECT pathname, %s, %s, 0, 0, 0, 0, 0" + " FROM sfile WHERE pathname NOT IN (%s)%s", + flags & C_MTIME ? "datetime(mtime, 'unixepoch', toLocal())" : "''", + flags & C_SIZE ? "size" : "0", + fossil_all_reserved_names(0), blob_sql_text(&where)); + } + blob_reset(&where); + + /* Pre-create the "ok" temporary table so the checkin_mtime() SQL function + * does not lead to SQLITE_ABORT_ROLLBACK during execution of the OP_OpenRead + * SQLite opcode. checkin_mtime() calls mtime_of_manifest_file() which + * creates a temporary table if it doesn't already exist, thus invalidating + * the prepared statement in the middle of its execution. */ + db_multi_exec("CREATE TEMP TABLE IF NOT EXISTS ok(rid INTEGER PRIMARY KEY)"); + + /* Append an ORDER BY clause then compile the query. */ + blob_append_sql(&sql, " ORDER BY pathname"); + db_prepare(&q, "%s", blob_sql_text(&sql)); + blob_reset(&sql); + + /* Bind the checkout version ID to the query if needed. */ + if( (flags & C_ALL) && (flags & C_MTIME) ){ + db_bind_int(&q, ":vid", db_lget_int("checkout", 0)); + } + + /* Execute the query and assemble the report. */ + blob_zero(&rewrittenPathname); while( db_step(&q)==SQLITE_ROW ){ - const char *zPathname = db_column_text(&q,0); - int isDeleted = db_column_int(&q, 1); - int isChnged = db_column_int(&q,2); - int isNew = db_column_int(&q,3)==0; - int isRenamed = db_column_int(&q,4); - char *zFullName = mprintf("%s/%s", g.zLocalRoot, zPathname); - blob_append(report, zPrefix, nPrefix); + const char *zPathname = db_column_text(&q, 0); + const char *zClass = 0; + int isManaged = db_column_int(&q, 7); + const char *zMtime = db_column_text(&q, 1); + int size = db_column_int(&q, 2); + int isDeleted = db_column_int(&q, 3); + int isChnged = db_column_int(&q, 4); + int isNew = isManaged && !db_column_int(&q, 5); + int isRenamed = db_column_int(&q, 6); + char *zFullName = mprintf("%s%s", g.zLocalRoot, zPathname); + int isMissing = !file_isfile_or_link(zFullName); + + /* Determine the file change classification, if any. */ if( isDeleted ){ - blob_appendf(report, "DELETED %s\n", zPathname); - }else if( !file_isfile(zFullName) ){ - if( access(zFullName, 0)==0 ){ - blob_appendf(report, "NOT_A_FILE %s\n", zPathname); - if( missingIsFatal ){ - fossil_warning("not a file: %s", zPathname); + if( flags & C_DELETED ){ + zClass = "DELETED"; + } + }else if( isMissing ){ + if( file_access(zFullName, F_OK)==0 ){ + if( flags & C_MISSING ){ + zClass = "NOT_A_FILE"; + } + if( flags & C_FATAL ){ + fossil_warning("not a file: %s", zFullName); nErr++; } }else{ - blob_appendf(report, "MISSING %s\n", zPathname); - if( missingIsFatal ){ - fossil_warning("missing file: %s", zPathname); + if( flags & C_MISSING ){ + zClass = "MISSING"; + } + if( flags & C_FATAL ){ + fossil_warning("missing file: %s", zFullName); nErr++; } } }else if( isNew ){ - blob_appendf(report, "ADDED %s\n", zPathname); - }else if( isDeleted ){ - blob_appendf(report, "DELETED %s\n", zPathname); - }else if( isChnged==2 ){ - blob_appendf(report, "UPDATED_BY_MERGE %s\n", zPathname); - }else if( isChnged==3 ){ - blob_appendf(report, "ADDED_BY_MERGE %s\n", zPathname); - }else if( isChnged==1 ){ - blob_appendf(report, "EDITED %s\n", zPathname); - }else if( isRenamed ){ - blob_appendf(report, "RENAMED %s\n", zPathname); - } - free(zFullName); - } - db_finalize(&q); - db_prepare(&q, "SELECT uuid FROM vmerge JOIN blob ON merge=rid" - " WHERE id=0"); - while( db_step(&q)==SQLITE_ROW ){ - blob_append(report, zPrefix, nPrefix); - blob_appendf(report, "MERGED_WITH %s\n", db_column_text(&q, 0)); - } - db_finalize(&q); + if( flags & C_ADDED ){ + zClass = "ADDED"; + } + }else if( (flags & (C_UPDATED | C_CHANGED)) && isChnged==2 ){ + zClass = "UPDATED_BY_MERGE"; + }else if( (flags & C_ADDED) && isChnged==3 ){ + zClass = "ADDED_BY_MERGE"; + }else if( (flags & (C_UPDATED | C_CHANGED)) && isChnged==4 ){ + zClass = "UPDATED_BY_INTEGRATE"; + }else if( (flags & C_ADDED) && isChnged==5 ){ + zClass = "ADDED_BY_INTEGRATE"; + }else if( (flags & C_META) && isChnged==6 ){ + zClass = "EXECUTABLE"; + }else if( (flags & C_META) && isChnged==7 ){ + zClass = "SYMLINK"; + }else if( (flags & C_META) && isChnged==8 ){ + zClass = "UNEXEC"; + }else if( (flags & C_META) && isChnged==9 ){ + zClass = "UNLINK"; + }else if( (flags & C_CONFLICT) && isChnged && !file_islink(zFullName) + && file_contains_merge_marker(zFullName) ){ + zClass = "CONFLICT"; + }else if( (flags & (C_EDITED | C_CHANGED)) && isChnged + && (isChnged<2 || isChnged>9) ){ + zClass = "EDITED"; + }else if( (flags & C_RENAMED) && isRenamed ){ + zClass = "RENAMED"; + }else if( (flags & C_UNCHANGED) && isManaged && !isNew + && !isChnged && !isRenamed ){ + zClass = "UNCHANGED"; + }else if( (flags & C_EXTRA) && !isManaged ){ + zClass = "EXTRA"; + } + + /* Only report files for which a change classification was determined. */ + if( zClass ){ + if( flags & C_COMMENT ){ + blob_append(report, "# ", 2); + } + if( flags & C_CLASSIFY ){ + blob_appendf(report, "%-10s ", zClass); + } + if( flags & C_MTIME ){ + blob_append(report, zMtime, -1); + blob_append(report, " ", 2); + } + if( flags & C_SIZE ){ + blob_appendf(report, "%7d ", size); + } + if( flags & C_RELPATH ){ + /* If C_RELPATH, display paths relative to current directory. */ + const char *zDisplayName; + file_relative_name(zFullName, &rewrittenPathname, 0); + zDisplayName = blob_str(&rewrittenPathname); + if( zDisplayName[0]=='.' && zDisplayName[1]=='/' ){ + zDisplayName += 2; /* no unnecessary ./ prefix */ + } + blob_append(report, zDisplayName, -1); + }else{ + /* If not C_RELPATH, display paths relative to project root. */ + blob_append(report, zPathname, -1); + } + blob_append(report, "\n", 1); + } + free(zFullName); + } + blob_reset(&rewrittenPathname); + db_finalize(&q); + + /* If C_MERGE, put merge contributors at the end of the report. */ +skipFiles: + if( flags & C_MERGE ){ + db_prepare(&q, "SELECT mhash, id FROM vmerge WHERE id<=0" ); + while( db_step(&q)==SQLITE_ROW ){ + if( flags & C_COMMENT ){ + blob_append(report, "# ", 2); + } + if( flags & C_CLASSIFY ){ + const char *zClass; + switch( db_column_int(&q, 1) ){ + case -1: zClass = "CHERRYPICK" ; break; + case -2: zClass = "BACKOUT" ; break; + case -4: zClass = "INTEGRATE" ; break; + default: zClass = "MERGED_WITH"; break; + } + blob_appendf(report, "%-10s ", zClass); + } + blob_append(report, db_column_text(&q, 0), -1); + blob_append(report, "\n", 1); + } + db_finalize(&q); + } if( nErr ){ fossil_fatal("aborting due to prior errors"); } } /* -** COMMAND: changes -** -** Usage: %fossil changes -** -** Report on the edit status of all files in the current checkout. -** See also the "status" and "extra" commands. +** Use the "relative-paths" setting and the --abs-paths and +** --rel-paths command line options to determine whether the +** status report should be shown relative to the current +** working directory. */ -void changes_cmd(void){ - Blob report; - int vid; - db_must_be_within_tree(); - blob_zero(&report); - vid = db_lget_int("checkout", 0); - vfile_check_signature(vid, 0); - status_report(&report, "", 0); - blob_write_to_file(&report, "-"); +static int determine_cwd_relative_option() +{ + int relativePaths = db_get_boolean("relative-paths", 1); + int absPathOption = find_option("abs-paths", 0, 0)!=0; + int relPathOption = find_option("rel-paths", 0, 0)!=0; + if( absPathOption ){ relativePaths = 0; } + if( relPathOption ){ relativePaths = 1; } + return relativePaths; } /* +** COMMAND: changes ** COMMAND: status ** -** Usage: %fossil status +** Usage: %fossil changes|status ?OPTIONS? ?PATHS ...? +** +** Report the change status of files in the current checkout. If one or +** more PATHS are specified, only changes among the named files and +** directories are reported. Directories are searched recursively. +** +** The status command is similar to the changes command, except it lacks +** several of the options supported by changes and it has its own header +** and footer information. The header information is a subset of that +** shown by the info command, and the footer shows if there are any forks. +** Change type classification is always enabled for the status command. +** +** Each line of output is the name of a changed file, with paths shown +** according to the "relative-paths" setting, unless overridden by the +** --abs-paths or --rel-paths options. +** +** By default, all changed files are selected for display. This behavior +** can be overridden by using one or more filter options (listed below), +** in which case only files with the specified change type(s) are shown. +** As a special case, the --no-merge option does not inhibit this default. +** This default shows exactly the set of changes that would be checked +** in by the commit command. +** +** If no filter options are used, or if the --merge option is used, the +** artifact hash of each merge contributor check-in version is displayed at +** the end of the report. The --no-merge option is useful to display the +** default set of changed files without the merge contributors. +** +** If change type classification is enabled, each output line starts with +** a code describing the file's change type, e.g. EDITED or RENAMED. It +** is enabled by default unless exactly one change type is selected. For +** the purposes of determining the default, --changed counts as selecting +** one change type. The default can be overridden by the --classify or +** --no-classify options. +** +** --edited and --updated produce disjoint sets. --updated shows a file +** only when it is identical to that of its merge contributor, and the +** change type classification is UPDATED_BY_MERGE or UPDATED_BY_INTEGRATE. +** If the file had to be merged with any other changes, it is considered +** to be merged or conflicted and therefore will be shown by --edited, not +** --updated, with types EDITED or CONFLICT. The --changed option can be +** used to display the union of --edited and --updated. +** +** --differ is so named because it lists all the differences between the +** checked-out version and the checkout directory. In addition to the +** default changes (excluding --merge), it lists extra files which (if +** ignore-glob is set correctly) may be worth adding. Prior to doing a +** commit, it is good practice to check --differ to see not only which +** changes would be committed but also if any files should be added. +** +** If both --merge and --no-merge are used, --no-merge has priority. The +** same is true of --classify and --no-classify. +** +** The "fossil changes --extra" command is equivalent to "fossil extras". +** +** General options: +** --abs-paths Display absolute pathnames. +** --rel-paths Display pathnames relative to the current working +** directory. +** --hash Verify file status using hashing rather than +** relying on file mtimes. +** --case-sensitive BOOL Override case-sensitive setting. +** --dotfiles Include unmanaged files beginning with a dot. +** --ignore <CSG> Ignore unmanaged files matching CSG glob patterns. +** +** Options specific to the changes command: +** --header Identify the repository if report is non-empty. +** -v|--verbose Say "(none)" if the change report is empty. +** --classify Start each line with the file's change type. +** --no-classify Do not print file change types. +** +** Filter options: +** --edited Display edited, merged, and conflicted files. +** --updated Display files updated by merge/integrate. +** --changed Combination of the above two options. +** --missing Display missing files. +** --added Display added files. +** --deleted Display deleted files. +** --renamed Display renamed files. +** --conflict Display files having merge conflicts. +** --meta Display files with metadata changes. +** --unchanged Display unchanged files. +** --all Display all managed files, i.e. all of the above. +** --extra Display unmanaged files. +** --differ Display modified and extra files. +** --merge Display merge contributors. +** --no-merge Do not display merge contributors. ** -** Report on the status of the current checkout. +** See also: [[extras]], [[ls]] */ void status_cmd(void){ - int vid; + /* Affirmative and negative flag option tables. */ + static const struct { + const char *option; /* Flag name. */ + unsigned mask; /* Flag bits. */ + } flagDefs[] = { + {"edited" , C_EDITED }, {"updated" , C_UPDATED }, + {"changed" , C_CHANGED }, {"missing" , C_MISSING }, + {"added" , C_ADDED }, {"deleted" , C_DELETED }, + {"renamed" , C_RENAMED }, {"conflict" , C_CONFLICT }, + {"meta" , C_META }, {"unchanged" , C_UNCHANGED}, + {"all" , C_ALL }, {"extra" , C_EXTRA }, + {"differ" , C_DIFFER }, {"merge" , C_MERGE }, + {"classify", C_CLASSIFY}, + }, noFlagDefs[] = { + {"no-merge", C_MERGE }, {"no-classify", C_CLASSIFY }, + }; + + Blob report = BLOB_INITIALIZER; + enum {CHANGES, STATUS} command = *g.argv[1]=='s' ? STATUS : CHANGES; + /* --sha1sum is an undocumented alias for --hash for backwards compatiblity */ + int useHash = find_option("hash",0,0)!=0 || find_option("sha1sum",0,0)!=0; + int showHdr = command==CHANGES && find_option("header", 0, 0); + int verboseFlag = command==CHANGES && find_option("verbose", "v", 0); + const char *zIgnoreFlag = find_option("ignore", 0, 1); + unsigned scanFlags = 0; + unsigned flags = 0; + int vid, i; + + fossil_pledge("stdio rpath wpath cpath fattr id flock tty chown"); + + /* Load affirmative flag options. */ + for( i=0; i<count(flagDefs); ++i ){ + if( (command==CHANGES || !(flagDefs[i].mask & C_CLASSIFY)) + && find_option(flagDefs[i].option, 0, 0) ){ + flags |= flagDefs[i].mask; + } + } + + /* If no filter options are specified, enable defaults. */ + if( !(flags & C_FILTER) ){ + flags |= C_DEFAULT; + } + + /* If more than one filter is enabled, enable classification. This is tricky. + * Having one filter means flags masked by C_FILTER is a power of two. If a + * number masked by one less than itself is zero, it's either zero or a power + * of two. It's already known to not be zero because of the above defaults. + * Unlike --all, --changed is a single filter, i.e. it sets only one bit. + * Also force classification for the status command. */ + if( command==STATUS || (flags & (flags-1) & C_FILTER) ){ + flags |= C_CLASSIFY; + } + + /* Negative flag options override defaults applied above. */ + for( i=0; i<count(noFlagDefs); ++i ){ + if( (command==CHANGES || !(noFlagDefs[i].mask & C_CLASSIFY)) + && find_option(noFlagDefs[i].option, 0, 0) ){ + flags &= ~noFlagDefs[i].mask; + } + } + + /* Confirm current working directory is within checkout. */ db_must_be_within_tree(); - /* 012345678901234 */ - printf("repository: %s\n", db_lget("repository","")); - printf("local-root: %s\n", g.zLocalRoot); - printf("server-code: %s\n", db_get("server-code", "")); + + /* Get checkout version. l*/ vid = db_lget_int("checkout", 0); - if( vid ){ - show_common_info(vid, "checkout:", 0); + + /* Relative path flag determination is done by a shared function. */ + if( determine_cwd_relative_option() ){ + flags |= C_RELPATH; } - changes_cmd(); + + /* If --ignore is not specified, use the ignore-glob setting. */ + if( !zIgnoreFlag ){ + zIgnoreFlag = db_get("ignore-glob", 0); + } + + /* Get the --dotfiles argument, or read it from the dotfiles setting. */ + if( find_option("dotfiles", 0, 0) || db_get_boolean("dotfiles", 0) ){ + scanFlags = SCAN_ALL; + } + + /* We should be done with options. */ + verify_all_options(); + + /* Check for changed files. */ + vfile_check_signature(vid, useHash ? CKSIG_HASH : 0); + + /* Search for unmanaged files if requested. */ + if( flags & C_EXTRA ){ + Glob *pIgnore = glob_create(zIgnoreFlag); + locate_unmanaged_files(g.argc-2, g.argv+2, scanFlags, pIgnore); + glob_free(pIgnore); + } + + /* The status command prints general information before the change list. */ + if( command==STATUS ){ + fossil_print("repository: %s\n", db_repository_filename()); + fossil_print("local-root: %s\n", g.zLocalRoot); + if( g.zConfigDbName ){ + fossil_print("config-db: %s\n", g.zConfigDbName); + } + if( vid ){ + show_common_info(vid, "checkout:", 1, 1); + } + db_record_repository_filename(0); + } + + /* Find and print all requested changes. */ + blob_zero(&report); + status_report(&report, flags); + if( blob_size(&report) ){ + if( showHdr ){ + fossil_print( + "Changes for %s at %s:\n", db_get("project-name", "<unnamed>"), + g.zLocalRoot); + } + blob_write_to_file(&report, "-"); + }else if( verboseFlag ){ + fossil_print(" (none)\n"); + } + blob_reset(&report); + + /* The status command ends with warnings about ambiguous leaves (forks). */ + if( command==STATUS ){ + leaf_ambiguity_warning(vid, vid); + } +} + +/* +** Take care of -r version of ls command +*/ +static void ls_cmd_rev( + const char *zRev, /* Revision string given */ + int verboseFlag, /* Verbose flag given */ + int showAge, /* Age flag given */ + int timeOrder /* Order by time flag given */ +){ + Stmt q; + char *zOrderBy = "pathname COLLATE nocase"; + char *zName; + Blob where; + int rid; + int i; + + /* Handle given file names */ + blob_zero(&where); + for(i=2; i<g.argc; i++){ + Blob fname; + file_tree_name(g.argv[i], &fname, 0, 1); + zName = blob_str(&fname); + if( fossil_strcmp(zName, ".")==0 ){ + blob_reset(&where); + break; + } + blob_append_sql(&where, + " %s (pathname=%Q %s) " + "OR (pathname>'%q/' %s AND pathname<'%q0' %s)", + (blob_size(&where)>0) ? "OR" : "AND (", zName, + filename_collation(), zName, filename_collation(), + zName, filename_collation() + ); + } + if( blob_size(&where)>0 ){ + blob_append_sql(&where, ")"); + } + + rid = symbolic_name_to_rid(zRev, "ci"); + if( rid==0 ){ + fossil_fatal("not a valid check-in: %s", zRev); + } + + if( timeOrder ){ + zOrderBy = "mtime DESC"; + } + + compute_fileage(rid,0); + db_prepare(&q, + "SELECT datetime(fileage.mtime, toLocal()), fileage.pathname,\n" + " blob.size\n" + " FROM fileage, blob\n" + " WHERE blob.rid=fileage.fid %s\n" + " ORDER BY %s;", blob_sql_text(&where), zOrderBy /*safe-for-%s*/ + ); + blob_reset(&where); + + while( db_step(&q)==SQLITE_ROW ){ + const char *zTime = db_column_text(&q,0); + const char *zFile = db_column_text(&q,1); + int size = db_column_int(&q,2); + if( verboseFlag ){ + fossil_print("%s %7d %s\n", zTime, size, zFile); + }else if( showAge ){ + fossil_print("%s %s\n", zTime, zFile); + }else{ + fossil_print("%s\n", zFile); + } + } + db_finalize(&q); } /* ** COMMAND: ls ** -** Usage: %fossil ls [-l] +** Usage: %fossil ls ?OPTIONS? ?PATHS ...? +** +** List all files in the current checkout. If PATHS is included, only the +** named files (or their children if directories) are shown. +** +** The ls command is essentially two related commands in one, depending on +** whether or not the -r option is given. -r selects a specific check-in +** version to list, in which case -R can be used to select the repository. +** The fine behavior of the --age, -v, and -t options is altered by the -r +** option as well, as explained below. +** +** The --age option displays file commit times. Like -r, --age has the +** side effect of making -t sort by commit time, not modification time. +** +** The -v option provides extra information about each file. Without -r, +** -v displays the change status, in the manner of the changes command. +** With -r, -v shows the commit time and size of the checked-in files. +** +** The -t option changes the sort order. Without -t, files are sorted by +** path and name (case insensitive sort if -r). If neither --age nor -r +** are used, -t sorts by modification time, otherwise by commit time. +** +** Options: +** --age Show when each file was committed. +** -v|--verbose Provide extra information about each file. +** -t Sort output in time order. +** -r VERSION The specific check-in to list. +** -R|--repository REPO Extract info from repository REPO. +** --hash With -v, verify file status using hashing +** rather than relying on file sizes and mtimes. ** -** Show the names of all files in the current checkout. The -l provides -** extra information about each file. +** See also: [[changes]], [[extras]], [[status]] */ void ls_cmd(void){ int vid; Stmt q; - int isBrief; + int verboseFlag; + int showAge; + int timeOrder; + char *zOrderBy = "pathname"; + Blob where; + int i; + int useHash = 0; + const char *zName; + const char *zRev; - isBrief = find_option("l","l", 0)==0; + verboseFlag = find_option("verbose","v", 0)!=0; + if( !verboseFlag ){ + verboseFlag = find_option("l","l", 0)!=0; /* deprecated */ + } + showAge = find_option("age",0,0)!=0; + zRev = find_option("r","r",1); + timeOrder = find_option("t","t",0)!=0; + if( verboseFlag ){ + useHash = find_option("hash",0,0)!=0; + } + + if( zRev!=0 ){ + db_find_and_open_repository(0, 0); + verify_all_options(); + ls_cmd_rev(zRev,verboseFlag,showAge,timeOrder); + return; + }else if( find_option("R",0,1)!=0 ){ + fossil_fatal("the -r is required in addition to -R"); + } + db_must_be_within_tree(); vid = db_lget_int("checkout", 0); - vfile_check_signature(vid, 0); - db_prepare(&q, - "SELECT pathname, deleted, rid, chnged, coalesce(origname!=pathname,0)" - " FROM vfile" - " ORDER BY 1" - ); + if( timeOrder ){ + if( showAge ){ + zOrderBy = mprintf("checkin_mtime(%d,rid) DESC", vid); + }else{ + zOrderBy = "mtime DESC"; + } + } + verify_all_options(); + blob_zero(&where); + for(i=2; i<g.argc; i++){ + Blob fname; + file_tree_name(g.argv[i], &fname, 0, 1); + zName = blob_str(&fname); + if( fossil_strcmp(zName, ".")==0 ){ + blob_reset(&where); + break; + } + blob_append_sql(&where, + " %s (pathname=%Q %s) " + "OR (pathname>'%q/' %s AND pathname<'%q0' %s)", + (blob_size(&where)>0) ? "OR" : "WHERE", zName, + filename_collation(), zName, filename_collation(), + zName, filename_collation() + ); + } + vfile_check_signature(vid, useHash ? CKSIG_HASH : 0); + if( showAge ){ + db_prepare(&q, + "SELECT pathname, deleted, rid, chnged, coalesce(origname!=pathname,0)," + " datetime(checkin_mtime(%d,rid),'unixepoch',toLocal())" + " FROM vfile %s" + " ORDER BY %s", + vid, blob_sql_text(&where), zOrderBy /*safe-for-%s*/ + ); + }else{ + db_prepare(&q, + "SELECT pathname, deleted, rid, chnged," + " coalesce(origname!=pathname,0), islink" + " FROM vfile %s" + " ORDER BY %s", blob_sql_text(&where), zOrderBy /*safe-for-%s*/ + ); + } + blob_reset(&where); while( db_step(&q)==SQLITE_ROW ){ const char *zPathname = db_column_text(&q,0); int isDeleted = db_column_int(&q, 1); int isNew = db_column_int(&q,2)==0; int chnged = db_column_int(&q,3); int renamed = db_column_int(&q,4); - char *zFullName = mprintf("%s/%s", g.zLocalRoot, zPathname); - if( isBrief ){ - printf("%s\n", zPathname); - }else if( isNew ){ - printf("ADDED %s\n", zPathname); - }else if( !file_isfile(zFullName) ){ - if( access(zFullName, 0)==0 ){ - printf("NOT_A_FILE %s\n", zPathname); - }else{ - printf("MISSING %s\n", zPathname); - } - }else if( isDeleted ){ - printf("DELETED %s\n", zPathname); - }else if( chnged ){ - printf("EDITED %s\n", zPathname); - }else if( renamed ){ - printf("RENAMED %s\n", zPathname); - }else{ - printf("UNCHANGED %s\n", zPathname); - } - free(zFullName); - } - db_finalize(&q); -} - -/* -** Construct and return a string which is an SQL expression that will -** be TRUE if value zVal matches any of the GLOB expressions in the list -** zGlobList. For example: -** -** zVal: "x" -** zGlobList: "*.o,*.obj" -** -** Result: "(x GLOB '*.o' OR x GLOB '*.obj')" -** -** Each element of the GLOB list may optionally be enclosed in either '...' -** or "...". This allows commas in the expression. Whitespace at the -** beginning and end of each GLOB pattern is ignored, except when enclosed -** within '...' or "...". -** -** This routine makes no effort to free the memory space it uses. -*/ -char *glob_expr(const char *zVal, const char *zGlobList){ - Blob expr; - char *zSep = "("; - int nTerm = 0; - int i; - int cTerm; - - if( zGlobList==0 || zGlobList[0]==0 ) return "0"; - blob_zero(&expr); - while( zGlobList[0] ){ - while( isspace(zGlobList[0]) || zGlobList[0]==',' ) zGlobList++; - if( zGlobList[0]==0 ) break; - if( zGlobList[0]=='\'' || zGlobList[0]=='"' ){ - cTerm = zGlobList[0]; - zGlobList++; - }else{ - cTerm = ','; - } - for(i=0; zGlobList[i] && zGlobList[i]!=cTerm; i++){} - if( cTerm==',' ){ - while( i>0 && isspace(zGlobList[i-1]) ){ i--; } - } - blob_appendf(&expr, "%s%s GLOB '%.*q'", zSep, zVal, i, zGlobList); - zSep = " OR "; - if( cTerm!=',' && zGlobList[i] ) i++; - zGlobList += i; - if( zGlobList[0] ) zGlobList++; - nTerm++; - } - if( nTerm ){ - blob_appendf(&expr, ")"); - return blob_str(&expr); - }else{ - return "0"; - } + int isLink = db_column_int(&q,5); + char *zFullName = mprintf("%s%s", g.zLocalRoot, zPathname); + const char *type = ""; + if( verboseFlag ){ + if( isNew ){ + type = "ADDED "; + }else if( isDeleted ){ + type = "DELETED "; + }else if( !file_isfile_or_link(zFullName) ){ + if( file_access(zFullName, F_OK)==0 ){ + type = "NOT_A_FILE "; + }else{ + type = "MISSING "; + } + }else if( chnged ){ + if( chnged==2 ){ + type = "UPDATED_BY_MERGE "; + }else if( chnged==3 ){ + type = "ADDED_BY_MERGE "; + }else if( chnged==4 ){ + type = "UPDATED_BY_INTEGRATE "; + }else if( chnged==5 ){ + type = "ADDED_BY_INTEGRATE "; + }else if( !isLink && file_contains_merge_marker(zFullName) ){ + type = "CONFLICT "; + }else{ + type = "EDITED "; + } + }else if( renamed ){ + type = "RENAMED "; + }else{ + type = "UNCHANGED "; + } + } + if( showAge ){ + fossil_print("%s%s %s\n", type, db_column_text(&q, 5), zPathname); + }else{ + fossil_print("%s%s\n", type, zPathname); + } + free(zFullName); + } + db_finalize(&q); } /* ** COMMAND: extras -** Usage: %fossil extras ?--dotfiles? ?--ignore GLOBPATTERN? +** +** Usage: %fossil extras ?OPTIONS? ?PATH1 ...? ** -** Print a list of all files in the source tree that are not part of -** the current checkout. See also the "clean" command. +** Print a list of all files in the source tree that are not part of the +** current checkout. See also the "clean" command. If paths are specified, +** only files in the given directories will be listed. ** ** Files and subdirectories whose names begin with "." are normally ** ignored but can be included by adding the --dotfiles option. +** +** Files whose names match any of the glob patterns in the "ignore-glob" +** setting are ignored. This setting can be overridden by the --ignore +** option, whose CSG argument is a comma-separated list of glob patterns. +** +** Pathnames are displayed according to the "relative-paths" setting, +** unless overridden by the --abs-paths or --rel-paths options. +** +** Options: +** --abs-paths Display absolute pathnames +** --case-sensitive BOOL Override case-sensitive setting +** --dotfiles Include files beginning with a dot (".") +** --header Identify the repository if there are extras +** --ignore CSG Ignore files matching patterns from the argument +** --rel-paths Display pathnames relative to the current working +** directory +** +** See also: [[changes]], [[clean]], [[status]] */ -void extra_cmd(void){ - Blob path; - Blob repo; - Stmt q; - int n; +void extras_cmd(void){ + Blob report = BLOB_INITIALIZER; const char *zIgnoreFlag = find_option("ignore",0,1); - int allFlag = find_option("dotfiles",0,0)!=0; + unsigned scanFlags = find_option("dotfiles",0,0)!=0 ? SCAN_ALL : 0; + unsigned flags = C_EXTRA; + int showHdr = find_option("header",0,0)!=0; + Glob *pIgnore; + if( find_option("temp",0,0)!=0 ) scanFlags |= SCAN_TEMP; db_must_be_within_tree(); - db_multi_exec("CREATE TEMP TABLE sfile(x TEXT PRIMARY KEY)"); - n = strlen(g.zLocalRoot); - blob_init(&path, g.zLocalRoot, n-1); + + if( determine_cwd_relative_option() ){ + flags |= C_RELPATH; + } + + if( db_get_boolean("dotfiles", 0) ) scanFlags |= SCAN_ALL; + + /* We should be done with options.. */ + verify_all_options(); + if( zIgnoreFlag==0 ){ zIgnoreFlag = db_get("ignore-glob", 0); } - vfile_scan(0, &path, blob_size(&path), allFlag); - db_prepare(&q, - "SELECT x FROM sfile" - " WHERE x NOT IN ('manifest','manifest.uuid','_FOSSIL_'," - "'_FOSSIL_-journal','.fos','.fos-journal')" - " AND NOT %s" - " ORDER BY 1", - glob_expr("x", zIgnoreFlag) - ); - if( file_tree_name(g.zRepositoryName, &repo, 0) ){ - db_multi_exec("DELETE FROM sfile WHERE x=%B", &repo); - } - while( db_step(&q)==SQLITE_ROW ){ - printf("%s\n", db_column_text(&q, 0)); - } - db_finalize(&q); + pIgnore = glob_create(zIgnoreFlag); + locate_unmanaged_files(g.argc-2, g.argv+2, scanFlags, pIgnore); + glob_free(pIgnore); + + blob_zero(&report); + status_report(&report, flags); + if( blob_size(&report) ){ + if( showHdr ){ + fossil_print("Extras for %s at %s:\n", db_get("project-name","<unnamed>"), + g.zLocalRoot); + } + blob_write_to_file(&report, "-"); + } + blob_reset(&report); } /* ** COMMAND: clean -** Usage: %fossil clean ?--force? ?--dotfiles? -** -** Delete all "extra" files in the source tree. "Extra" files are -** files that are not officially part of the checkout. See also -** the "extra" command. This operation cannot be undone. -** -** You will be prompted before removing each file. If you are -** sure you wish to remove all "extra" files you can specify the -** optional --force flag and no prompts will be issued. -** -** Files and subdirectories whose names begin with "." are -** normally ignored. They are included if the "--dotfiles" option -** is used. +** +** Usage: %fossil clean ?OPTIONS? ?PATH ...? +** +** Delete all "extra" files in the source tree. "Extra" files are files +** that are not officially part of the checkout. If one or more PATH +** arguments appear, then only the files named, or files contained with +** directories named, will be removed. +** +** If the --prompt option is used, prompts are issued to confirm the +** permanent removal of each file. Otherwise, files are backed up to the +** undo buffer prior to removal, and prompts are issued only for files +** whose removal cannot be undone due to their large size or due to +** --disable-undo being used. +** +** The --force option treats all prompts as having been answered yes, +** whereas --no-prompt treats them as having been answered no. +** +** Files matching any glob pattern specified by the --clean option are +** deleted without prompting, and the removal cannot be undone. +** +** No file that matches glob patterns specified by --ignore or --keep will +** ever be deleted. Files and subdirectories whose names begin with "." +** are automatically ignored unless the --dotfiles option is used. +** +** The default values for --clean, --ignore, and --keep are determined by +** the (versionable) clean-glob, ignore-glob, and keep-glob settings. +** +** The --verily option ignores the keep-glob and ignore-glob settings and +** turns on --force, --emptydirs, --dotfiles, and --disable-undo. Use the +** --verily option when you really want to clean up everything. Extreme +** care should be exercised when using the --verily option. +** +** Options: +** --allckouts Check for empty directories within any checkouts +** that may be nested within the current one. This +** option should be used with great care because the +** empty-dirs setting (and other applicable settings) +** belonging to the other repositories, if any, will +** not be checked. +** --case-sensitive BOOL Override case-sensitive setting +** --dirsonly Only remove empty directories. No files will +** be removed. Using this option will automatically +** enable the --emptydirs option as well. +** --disable-undo WARNING: This option disables use of the undo +** mechanism for this clean operation and should be +** used with extreme caution. +** --dotfiles Include files beginning with a dot (".") +** --emptydirs Remove any empty directories that are not +** explicitly exempted via the empty-dirs setting +** or another applicable setting or command line +** argument. Matching files, if any, are removed +** prior to checking for any empty directories; +** therefore, directories that contain only files +** that were removed will be removed as well. +** -f|--force Remove files without prompting +** -i|--prompt Prompt before removing each file. This option +** implies the --disable-undo option. +** -x|--verily WARNING: Removes everything that is not a managed +** file or the repository itself. This option +** implies the --force, --emptydirs, --dotfiles, and +** --disable-undo options. Furthermore, it +** completely disregards the keep-glob +** and ignore-glob settings. However, it does honor +** the --ignore and --keep options. +** --clean CSG WARNING: Never prompt to delete any files matching +** this comma separated list of glob patterns. Also, +** deletions of any files matching this pattern list +** cannot be undone. +** --ignore CSG Ignore files matching patterns from the +** comma separated list of glob patterns +** --keep <CSG> Keep files matching this comma separated +** list of glob patterns +** -n|--dry-run Delete nothing, but display what would have been +** deleted +** --no-prompt Do not prompt the user for input and assume an +** answer of 'No' for every question +** --temp Remove only Fossil-generated temporary files +** -v|--verbose Show all files as they are removed +** +** See also: [[addremove]], [[extras]], [[status]] */ void clean_cmd(void){ - int allFlag; - int dotfilesFlag; - Blob path, repo; - Stmt q; - int n; - allFlag = find_option("force","f",0)!=0; - dotfilesFlag = find_option("dotfiles",0,0)!=0; + int allFileFlag, allDirFlag, dryRunFlag, verboseFlag; + int emptyDirsFlag, dirsOnlyFlag; + int disableUndo, noPrompt; + int alwaysPrompt = 0; + unsigned scanFlags = 0; + int verilyFlag = 0; + const char *zIgnoreFlag, *zKeepFlag, *zCleanFlag; + Glob *pIgnore, *pKeep, *pClean; + int nRoot; + +#ifndef UNDO_SIZE_LIMIT /* TODO: Setting? */ +#define UNDO_SIZE_LIMIT (10*1024*1024) /* 10MiB */ +#endif + + undo_capture_command_line(); + dryRunFlag = find_option("dry-run","n",0)!=0; + if( !dryRunFlag ){ + dryRunFlag = find_option("test",0,0)!=0; /* deprecated */ + } + if( !dryRunFlag ){ + dryRunFlag = find_option("whatif",0,0)!=0; + } + disableUndo = find_option("disable-undo",0,0)!=0; + noPrompt = find_option("no-prompt",0,0)!=0; + alwaysPrompt = find_option("prompt","i",0)!=0; + allFileFlag = allDirFlag = find_option("force","f",0)!=0; + dirsOnlyFlag = find_option("dirsonly",0,0)!=0; + emptyDirsFlag = find_option("emptydirs","d",0)!=0 || dirsOnlyFlag; + if( find_option("dotfiles",0,0)!=0 ) scanFlags |= SCAN_ALL; + if( find_option("temp",0,0)!=0 ) scanFlags |= SCAN_TEMP; + if( find_option("allckouts",0,0)!=0 ) scanFlags |= SCAN_NESTED; + zIgnoreFlag = find_option("ignore",0,1); + verboseFlag = find_option("verbose","v",0)!=0; + zKeepFlag = find_option("keep",0,1); + zCleanFlag = find_option("clean",0,1); db_must_be_within_tree(); - db_multi_exec("CREATE TEMP TABLE sfile(x TEXT PRIMARY KEY)"); - n = strlen(g.zLocalRoot); - blob_init(&path, g.zLocalRoot, n-1); - vfile_scan(0, &path, blob_size(&path), dotfilesFlag); - db_prepare(&q, - "SELECT %Q || x FROM sfile" - " WHERE x NOT IN ('manifest','manifest.uuid','_FOSSIL_'," - "'_FOSSIL_-journal','.fos','.fos-journal')" - " ORDER BY 1", g.zLocalRoot); - if( file_tree_name(g.zRepositoryName, &repo, 0) ){ - db_multi_exec("DELETE FROM sfile WHERE x=%B", &repo); - } - while( db_step(&q)==SQLITE_ROW ){ - if( allFlag ){ - unlink(db_column_text(&q, 0)); + if( find_option("verily","x",0)!=0 ){ + verilyFlag = allFileFlag = allDirFlag = 1; + emptyDirsFlag = 1; + disableUndo = 1; + scanFlags |= SCAN_ALL; + zCleanFlag = 0; + } + if( zIgnoreFlag==0 && !verilyFlag ){ + zIgnoreFlag = db_get("ignore-glob", 0); + } + if( zKeepFlag==0 && !verilyFlag ){ + zKeepFlag = db_get("keep-glob", 0); + } + if( zCleanFlag==0 && !verilyFlag ){ + zCleanFlag = db_get("clean-glob", 0); + } + if( db_get_boolean("dotfiles", 0) ) scanFlags |= SCAN_ALL; + verify_all_options(); + pIgnore = glob_create(zIgnoreFlag); + pKeep = glob_create(zKeepFlag); + pClean = glob_create(zCleanFlag); + nRoot = (int)strlen(g.zLocalRoot); + if( !dirsOnlyFlag ){ + Stmt q; + Blob repo; + if( !dryRunFlag && !disableUndo ) undo_begin(); + locate_unmanaged_files(g.argc-2, g.argv+2, scanFlags, pIgnore); + db_prepare(&q, + "SELECT %Q || pathname FROM sfile" + " WHERE pathname NOT IN (%s)" + " ORDER BY 1", + g.zLocalRoot, fossil_all_reserved_names(0) + ); + if( file_tree_name(g.zRepositoryName, &repo, 0, 0) ){ + db_multi_exec("DELETE FROM sfile WHERE pathname=%B", &repo); + } + db_multi_exec("DELETE FROM sfile WHERE pathname IN" + " (SELECT pathname FROM vfile)"); + while( db_step(&q)==SQLITE_ROW ){ + const char *zName = db_column_text(&q, 0); + if( glob_match(pKeep, zName+nRoot) ){ + if( verboseFlag ){ + fossil_print("KEPT file \"%s\" not removed (due to --keep" + " or \"keep-glob\")\n", zName+nRoot); + } + continue; + } + if( !dryRunFlag && !glob_match(pClean, zName+nRoot) ){ + char *zPrompt = 0; + char cReply; + Blob ans = empty_blob; + int undoRc = UNDO_NONE; + if( alwaysPrompt ){ + zPrompt = mprintf("Remove unmanaged file \"%s\" (a=all/y/N)? ", + zName+nRoot); + prompt_user(zPrompt, &ans); + fossil_free(zPrompt); + cReply = fossil_toupper(blob_str(&ans)[0]); + blob_reset(&ans); + if( cReply=='N' ) continue; + if( cReply=='A' ){ + allFileFlag = 1; + alwaysPrompt = 0; + }else{ + undoRc = UNDO_SAVED_OK; + } + }else if( !disableUndo ){ + undoRc = undo_maybe_save(zName+nRoot, UNDO_SIZE_LIMIT); + } + if( undoRc!=UNDO_SAVED_OK ){ + if( allFileFlag ){ + cReply = 'Y'; + }else if( !noPrompt ){ + Blob ans; + zPrompt = mprintf("\nWARNING: Deletion of this file will " + "not be undoable via the 'undo'\n" + " command because %s.\n\n" + "Remove unmanaged file \"%s\" (a=all/y/N)? ", + undo_save_message(undoRc), zName+nRoot); + prompt_user(zPrompt, &ans); + fossil_free(zPrompt); + cReply = blob_str(&ans)[0]; + blob_reset(&ans); + }else{ + cReply = 'N'; + } + if( cReply=='a' || cReply=='A' ){ + allFileFlag = 1; + }else if( cReply!='y' && cReply!='Y' ){ + continue; + } + } + } + if( dryRunFlag || file_delete(zName)==0 ){ + if( verboseFlag || dryRunFlag ){ + fossil_print("Removed unmanaged file: %s\n", zName+nRoot); + } + }else{ + fossil_print("Could not remove file: %s\n", zName+nRoot); + } + } + db_finalize(&q); + if( !dryRunFlag && !disableUndo ) undo_finish(); + } + if( emptyDirsFlag ){ + Glob *pEmptyDirs = glob_create(db_get("empty-dirs", 0)); + Stmt q; + Blob root; + blob_init(&root, g.zLocalRoot, nRoot - 1); + vfile_dir_scan(&root, blob_size(&root), scanFlags, pIgnore, + pEmptyDirs, RepoFILE); + blob_reset(&root); + db_prepare(&q, + "SELECT %Q || x FROM dscan_temp" + " WHERE x NOT IN (%s) AND y = 0" + " ORDER BY 1 DESC", + g.zLocalRoot, fossil_all_reserved_names(0) + ); + while( db_step(&q)==SQLITE_ROW ){ + const char *zName = db_column_text(&q, 0); + if( glob_match(pKeep, zName+nRoot) ){ + if( verboseFlag ){ + fossil_print("KEPT directory \"%s\" not removed (due to --keep" + " or \"keep-glob\")\n", zName+nRoot); + } + continue; + } + if( !allDirFlag && !dryRunFlag && !glob_match(pClean, zName+nRoot) ){ + char cReply; + if( !noPrompt ){ + Blob ans; + char *prompt = mprintf("Remove empty directory \"%s\" (a=all/y/N)? ", + zName+nRoot); + prompt_user(prompt, &ans); + cReply = blob_str(&ans)[0]; + fossil_free(prompt); + blob_reset(&ans); + }else{ + cReply = 'N'; + } + if( cReply=='a' || cReply=='A' ){ + allDirFlag = 1; + }else if( cReply!='y' && cReply!='Y' ){ + continue; + } + } + if( dryRunFlag || file_rmdir(zName)==0 ){ + if( verboseFlag || dryRunFlag ){ + fossil_print("Removed unmanaged directory: %s\n", zName+nRoot); + } + }else if( verboseFlag ){ + fossil_print("Could not remove directory: %s\n", zName+nRoot); + } + } + db_finalize(&q); + glob_free(pEmptyDirs); + } + glob_free(pClean); + glob_free(pKeep); + glob_free(pIgnore); +} + +/* +** Prompt the user for a check-in or stash comment (given in pPrompt), +** gather the response, then return the response in pComment. +** +** Lines of the prompt that begin with # are discarded. Excess whitespace +** is removed from the reply. +** +** Appropriate encoding translations are made on windows. +*/ +void prompt_for_user_comment(Blob *pComment, Blob *pPrompt){ + const char *zEditor; + char *zCmd; + char *zFile; + Blob reply, line; + char *zComment; + int i; + + zEditor = fossil_text_editor(); + if( zEditor==0 ){ + if( blob_size(pPrompt)>0 ){ + blob_append(pPrompt, + "#\n" + "# Since no default text editor is set using EDITOR or VISUAL\n" + "# environment variables or the \"fossil set editor\" command,\n" + "# and because no comment was specified using the \"-m\" or \"-M\"\n" + "# command-line options, you will need to enter the comment below.\n" + "# Type \".\" on a line by itself when you are done:\n", -1); + } + zFile = mprintf("-"); + }else{ + Blob fname; + blob_zero(&fname); + if( g.zLocalRoot!=0 ){ + file_relative_name(g.zLocalRoot, &fname, 1); + zFile = db_text(0, "SELECT '%qci-comment-'||hex(randomblob(6))||'.txt'", + blob_str(&fname)); }else{ - Blob ans; - char *prompt = mprintf("remove unmanaged file \"%s\" (y/N)? ", - db_column_text(&q, 0)); - blob_zero(&ans); - prompt_user(prompt, &ans); - if( blob_str(&ans)[0]=='y' ){ - unlink(db_column_text(&q, 0)); - } + file_tempname(&fname, "ci-comment",0); + zFile = mprintf("%s", blob_str(&fname)); + } + blob_reset(&fname); + } +#if defined(_WIN32) + blob_add_cr(pPrompt); +#endif + if( blob_size(pPrompt)>0 ) blob_write_to_file(pPrompt, zFile); + if( zEditor ){ + char *z, *zEnd; + zCmd = mprintf("%s %$", zEditor, zFile); + fossil_print("%s\n", zCmd); + if( fossil_system(zCmd) ){ + fossil_fatal("editor aborted: \"%s\"", zCmd); + } + blob_read_from_file(&reply, zFile, ExtFILE); + z = blob_str(&reply); + zEnd = strstr(z, "##########"); + if( zEnd ){ + /* Truncate the reply at any sequence of 10 or more # characters. + ** The diff for the -v option occurs after such a sequence. */ + blob_resize(&reply, (int)(zEnd - z)); + } + }else{ + char zIn[300]; + blob_zero(&reply); + while( fgets(zIn, sizeof(zIn), stdin)!=0 ){ + if( zIn[0]=='.' && (zIn[1]==0 || zIn[1]=='\r' || zIn[1]=='\n') ){ + break; + } + blob_append(&reply, zIn, -1); + } + } + blob_to_utf8_no_bom(&reply, 1); + blob_to_lf_only(&reply); + file_delete(zFile); + free(zFile); + blob_zero(pComment); + while( blob_line(&reply, &line) ){ + int i, n; + char *z; + n = blob_size(&line); + z = blob_buffer(&line); + for(i=0; i<n && fossil_isspace(z[i]); i++){} + if( i<n && z[i]=='#' ) continue; + if( i<n || blob_size(pComment)>0 ){ + blob_appendf(pComment, "%b", &line); } } - db_finalize(&q); + blob_reset(&reply); + zComment = blob_str(pComment); + i = strlen(zComment); + while( i>0 && fossil_isspace(zComment[i-1]) ){ i--; } + blob_resize(pComment, i); } /* ** Prepare a commit comment. Let the user modify it using the ** editor specified in the global_config table or either @@ -359,88 +1276,176 @@ ** parent_rid is the recordid of the parent check-in. */ static void prepare_commit_comment( Blob *pComment, char *zInit, - const char *zBranch, - int parent_rid + CheckinInfo *p, + int parent_rid, + int dryRunFlag ){ - const char *zEditor; - char *zCmd; - char *zFile; - Blob text, line; - char *zComment; - int i; - blob_init(&text, zInit, -1); - blob_append(&text, + Blob prompt; +#if defined(_WIN32) || defined(__CYGWIN__) + int bomSize; + const unsigned char *bom = get_utf8_bom(&bomSize); + blob_init(&prompt, (const char *) bom, bomSize); + if( zInit && zInit[0]){ + blob_append(&prompt, zInit, -1); + } +#else + blob_init(&prompt, zInit, -1); +#endif + blob_append(&prompt, "\n" - "# Enter comments on this check-in. Lines beginning with # are ignored.\n" - "# The check-in comment follows wiki formatting rules.\n" + "# Enter a commit message for this check-in." + " Lines beginning with # are ignored.\n" "#\n", -1 ); - if( zBranch && zBranch[0] ){ - blob_appendf(&text, "# tags: %s\n#\n", zBranch); + if( dryRunFlag ){ + blob_appendf(&prompt, "# DRY-RUN: This is a test commit. No changes " + "will be made to the repository\n#\n"); + } + blob_appendf(&prompt, "# user: %s\n", + p->zUserOvrd ? p->zUserOvrd : login_name()); + if( p->zBranch && p->zBranch[0] ){ + blob_appendf(&prompt, "# tags: %s\n#\n", p->zBranch); }else{ char *zTags = info_tags_of_checkin(parent_rid, 1); - if( zTags ) blob_appendf(&text, "# tags: %z\n#\n", zTags); + if( zTags || p->azTag ){ + blob_append(&prompt, "# tags: ", 8); + if(zTags){ + blob_appendf(&prompt, "%z%s", zTags, p->azTag ? ", " : ""); + } + if(p->azTag){ + int i = 0; + for( ; p->azTag[i]; ++i ){ + blob_appendf(&prompt, "%s%s", p->azTag[i], + p->azTag[i+1] ? ", " : ""); + } + } + blob_appendf(&prompt, "\n#\n"); + } } + status_report(&prompt, C_DEFAULT | C_FATAL | C_COMMENT); if( g.markPrivate ){ - blob_append(&text, + blob_append(&prompt, "# PRIVATE BRANCH: This check-in will be private and will not sync to\n" "# repositories.\n" "#\n", -1 ); } - status_report(&text, "# ", 1); - zEditor = db_get("editor", 0); - if( zEditor==0 ){ - zEditor = getenv("VISUAL"); - } - if( zEditor==0 ){ - zEditor = getenv("EDITOR"); - } - if( zEditor==0 ){ -#ifdef __MINGW32__ - zEditor = "notepad"; -#else - zEditor = "ed"; -#endif - } - zFile = db_text(0, "SELECT '%qci-comment-' || hex(randomblob(6)) || '.txt'", - g.zLocalRoot); -#ifdef __MINGW32__ - blob_add_cr(&text); -#endif - blob_write_to_file(&text, zFile); - zCmd = mprintf("%s \"%s\"", zEditor, zFile); - printf("%s\n", zCmd); - if( portable_system(zCmd) ){ - fossil_panic("editor aborted"); - } - blob_reset(&text); - blob_read_from_file(&text, zFile); - blob_remove_cr(&text); - unlink(zFile); - free(zFile); - blob_zero(pComment); - while( blob_line(&text, &line) ){ - int i, n; - char *z; - n = blob_size(&line); - z = blob_buffer(&line); - for(i=0; i<n && isspace(z[i]); i++){} - if( i<n && z[i]=='#' ) continue; - if( i<n || blob_size(pComment)>0 ){ - blob_appendf(pComment, "%b", &line); - } - } - blob_reset(&text); - zComment = blob_str(pComment); - i = strlen(zComment); - while( i>0 && isspace(zComment[i-1]) ){ i--; } - blob_resize(pComment, i); -} + if( p->integrateFlag ){ + blob_append(&prompt, + "#\n" + "# All merged-in branches will be closed due to the --integrate flag\n" + "#\n", -1 + ); + } + if( p->verboseFlag ){ + blob_appendf(&prompt, + "#\n%.78c\n" + "# The following diff is excluded from the commit message:\n#\n", + '#' + ); + if( g.aCommitFile ){ + FileDirList *diffFiles; + int i; + diffFiles = fossil_malloc_zero((g.argc-1) * sizeof(*diffFiles)); + for( i=0; g.aCommitFile[i]!=0; ++i ){ + diffFiles[i].zName = db_text(0, + "SELECT pathname FROM vfile WHERE id=%d", g.aCommitFile[i]); + if( fossil_strcmp(diffFiles[i].zName, "." )==0 ){ + diffFiles[0].zName[0] = '.'; + diffFiles[0].zName[1] = 0; + break; + } + diffFiles[i].nName = strlen(diffFiles[i].zName); + diffFiles[i].nUsed = 0; + } + diff_against_disk(0, 0, diff_get_binary_glob(), + db_get_boolean("diff-binary", 1), + DIFF_VERBOSE, diffFiles, &prompt); + for( i=0; diffFiles[i].zName; ++i ){ + fossil_free(diffFiles[i].zName); + } + fossil_free(diffFiles); + }else{ + diff_against_disk(0, 0, diff_get_binary_glob(), + db_get_boolean("diff-binary", 1), + DIFF_VERBOSE, 0, &prompt); + } + } + prompt_for_user_comment(pComment, &prompt); + blob_reset(&prompt); +} + +/* +** Prepare text that describes a pending commit and write it into +** a file at the root of the check-in. Return the name of that file. +** +** Space to hold the returned filename is obtained from fossil_malloc() +** and should be freed by the caller. The caller should also unlink +** the file when it is done. +*/ +static char *prepare_commit_description_file( + CheckinInfo *p, /* Information about this commit */ + int parent_rid, /* parent check-in */ + Blob *pComment, /* Check-in comment */ + int dryRunFlag /* True for a dry-run only */ +){ + Blob *pDesc; + char *zTags; + char *zFilename; + Blob desc; + blob_init(&desc, 0, 0); + pDesc = &desc; + blob_appendf(pDesc, "checkout %s\n", g.zLocalRoot); + blob_appendf(pDesc, "repository %s\n", g.zRepositoryName); + blob_appendf(pDesc, "user %s\n", + p->zUserOvrd ? p->zUserOvrd : login_name()); + blob_appendf(pDesc, "branch %s\n", + (p->zBranch && p->zBranch[0]) ? p->zBranch : "trunk"); + zTags = info_tags_of_checkin(parent_rid, 1); + if( zTags || p->azTag ){ + blob_append(pDesc, "tags ", -1); + if(zTags){ + blob_appendf(pDesc, "%z%s", zTags, p->azTag ? ", " : ""); + } + if(p->azTag){ + int i = 0; + for( ; p->azTag[i]; ++i ){ + blob_appendf(pDesc, "%s%s", p->azTag[i], + p->azTag[i+1] ? ", " : ""); + } + } + blob_appendf(pDesc, "\n"); + } + status_report(pDesc, C_DEFAULT | C_FATAL); + if( g.markPrivate ){ + blob_append(pDesc, "private-branch\n", -1); + } + if( p->integrateFlag ){ + blob_append(pDesc, "integrate\n", -1); + } + if( pComment && blob_size(pComment)>0 ){ + blob_appendf(pDesc, "checkin-comment\n%s\n", blob_str(pComment)); + } + if( dryRunFlag ){ + zFilename = 0; + fossil_print("******* Commit Description *******\n%s" + "***** End Commit Description *****\n", + blob_str(pDesc)); + }else{ + unsigned int r[2]; + sqlite3_randomness(sizeof(r), r); + zFilename = mprintf("%scommit-description-%08x%08x.txt", + g.zLocalRoot, r[0], r[1]); + blob_write_to_file(pDesc, zFilename); + } + blob_reset(pDesc); + return zFilename; +} + /* ** Populate the Global.aCommitFile[] based on the command line arguments ** to a [commit] command. Global.aCommitFile is an array of integers ** sized at (N+1), where N is the number of arguments passed to [commit]. @@ -451,168 +1456,903 @@ ** of the array. ** ** If there were no arguments passed to [commit], aCommitFile is not ** allocated and remains NULL. Other parts of the code interpret this ** to mean "all files". +** +** Returns 1 if there was a warning, 0 otherwise. */ -void select_commit_files(void){ +int select_commit_files(void){ + int result = 0; + assert( g.aCommitFile==0 ); if( g.argc>2 ){ - int ii; - Blob b; - blob_zero(&b); - g.aCommitFile = malloc(sizeof(int)*(g.argc-1)); + int ii, jj=0; + Blob fname; + Stmt q; + Bag toCommit; + blob_zero(&fname); + bag_init(&toCommit); for(ii=2; ii<g.argc; ii++){ - int iId; - file_tree_name(g.argv[ii], &b, 1); - iId = db_int(-1, "SELECT id FROM vfile WHERE pathname=%Q", blob_str(&b)); - if( iId<0 ){ - fossil_fatal("fossil knows nothing about: %s", g.argv[ii]); - } - g.aCommitFile[ii-2] = iId; - blob_reset(&b); - } - g.aCommitFile[ii-2] = 0; - } + int cnt = 0; + file_tree_name(g.argv[ii], &fname, 0, 1); + if( fossil_strcmp(blob_str(&fname),".")==0 ){ + bag_clear(&toCommit); + return result; + } + db_prepare(&q, + "SELECT id FROM vfile WHERE pathname=%Q %s" + " OR (pathname>'%q/' %s AND pathname<'%q0' %s)", + blob_str(&fname), filename_collation(), blob_str(&fname), + filename_collation(), blob_str(&fname), filename_collation()); + while( db_step(&q)==SQLITE_ROW ){ + cnt++; + bag_insert(&toCommit, db_column_int(&q, 0)); + } + db_finalize(&q); + if( cnt==0 ){ + fossil_warning("fossil knows nothing about: %s", g.argv[ii]); + result = 1; + } + blob_reset(&fname); + } + g.aCommitFile = fossil_malloc( (bag_count(&toCommit)+1) * + sizeof(g.aCommitFile[0]) ); + for(ii=bag_first(&toCommit); ii>0; ii=bag_next(&toCommit, ii)){ + g.aCommitFile[jj++] = ii; + } + g.aCommitFile[jj] = 0; + bag_clear(&toCommit); + } + return result; } /* -** Return true if the check-in with RID=rid is a leaf. -** A leaf has no children in the same branch. +** Returns true if the checkin identified by the first parameter is +** older than the given (valid) date/time string, else returns false. +** Also returns true if rid does not refer to a checkin, but it is not +** intended to be used for that case. */ -int is_a_leaf(int rid){ - int rc; - static const char zSql[] = - @ SELECT 1 FROM plink - @ WHERE pid=%d - @ 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') - ; - rc = db_int(0, zSql, rid, TAG_BRANCH, TAG_BRANCH); - return rc==0; +int checkin_is_younger( + int rid, /* The record ID of the ancestor */ + const char *zDate /* Date & time of the current check-in */ +){ + return db_exists( + "SELECT 1 FROM event" + " WHERE datetime(mtime)>=%Q" + " AND type='ci' AND objid=%d", + zDate, rid + ) ? 0 : 1; } /* ** Make sure the current check-in with timestamp zDate is younger than its ** ancestor identified rid and zUuid. Throw a fatal error if not. */ static void checkin_verify_younger( int rid, /* The record ID of the ancestor */ - const char *zUuid, /* The artifact ID of the ancestor */ + const char *zUuid, /* The artifact hash of the ancestor */ const char *zDate /* Date & time of the current check-in */ ){ #ifndef FOSSIL_ALLOW_OUT_OF_ORDER_DATES - int b; - b = db_exists( - "SELECT 1 FROM event" - " WHERE datetime(mtime)>=%Q" - " AND type='ci' AND objid=%d", - zDate, rid - ); - if( b ){ - fossil_fatal("ancestor check-in [%.10s] (%s) is younger (clock skew?)", - zUuid, zDate); - } + if(checkin_is_younger(rid,zDate)==0){ + fossil_fatal("ancestor check-in [%S] (%s) is not older (clock skew?)" + " Use --allow-older to override.", zUuid, zDate); + } +#endif +} + + + +/* +** zDate should be a valid date string. Convert this string into the +** format YYYY-MM-DDTHH:MM:SS. If the string is not a valid date, +** print a fatal error and quit. +*/ +char *date_in_standard_format(const char *zInputDate){ + char *zDate; + if( g.perm.Setup && fossil_strcmp(zInputDate,"now")==0 ){ + zInputDate = PD("date_override","now"); + } + zDate = db_text(0, "SELECT strftime('%%Y-%%m-%%dT%%H:%%M:%%f',%Q)", + zInputDate); + if( zDate[0]==0 ){ + fossil_fatal( + "unrecognized date format (%s): use \"YYYY-MM-DD HH:MM:SS.SSS\"", + zInputDate + ); + } + return zDate; +} + +/* +** COMMAND: test-date-format +** +** Usage: %fossil test-date-format DATE-STRING... +** +** Convert the DATE-STRING into the standard format used in artifacts +** and display the result. +*/ +void test_date_format(void){ + int i; + db_find_and_open_repository(OPEN_ANY_SCHEMA, 0); + for(i=2; i<g.argc; i++){ + fossil_print("%s -> %s\n", g.argv[i], date_in_standard_format(g.argv[i])); + } +} + +#if INTERFACE +/* +** The following structure holds some of the information needed to construct a +** check-in manifest. +*/ +struct CheckinInfo { + Blob *pComment; /* Check-in comment text */ + const char *zMimetype; /* Mimetype of check-in command. May be NULL */ + int verifyDate; /* Verify that child is younger */ + int closeFlag; /* Close the branch being committed */ + int integrateFlag; /* Close merged-in branches */ + int verboseFlag; /* Show diff in editor for check-in comment */ + Blob *pCksum; /* Repository checksum. May be 0 */ + const char *zDateOvrd; /* Date override. If 0 then use 'now' */ + const char *zUserOvrd; /* User override. If 0 then use login_name() */ + const char *zBranch; /* Branch name. May be 0 */ + const char *zColor; /* One-time background color. May be 0 */ + const char *zBrClr; /* Persistent branch color. May be 0 */ + const char **azTag; /* Tags to apply to this check-in */ +}; +#endif /* INTERFACE */ + +/* +** Create a manifest. +*/ +static void create_manifest( + Blob *pOut, /* Write the manifest here */ + const char *zBaselineUuid, /* Hash of baseline, or zero */ + Manifest *pBaseline, /* Make it a delta manifest if not zero */ + int vid, /* BLOB.id for the parent check-in */ + CheckinInfo *p, /* Information about the check-in */ + int *pnFBcard /* OUT: Number of generated B- and F-cards */ +){ + char *zDate; /* Date of the check-in */ + char *zParentUuid = 0; /* Hash of parent check-in */ + Blob filename; /* A single filename */ + int nBasename; /* Size of base filename */ + Stmt q; /* Various queries */ + Blob mcksum; /* Manifest checksum */ + ManifestFile *pFile; /* File from the baseline */ + int nFBcard = 0; /* Number of B-cards and F-cards */ + int i; /* Loop counter */ + const char *zColor; /* Modified value of p->zColor */ + + assert( pBaseline==0 || pBaseline->zBaseline==0 ); + assert( pBaseline==0 || zBaselineUuid!=0 ); + blob_zero(pOut); + if( vid ){ + zParentUuid = db_text(0, "SELECT uuid FROM blob WHERE rid=%d AND " + "EXISTS(SELECT 1 FROM event WHERE event.type='ci' and event.objid=%d)", + vid, vid); + if( !zParentUuid ){ + fossil_fatal("Could not find a valid check-in for RID %d. " + "Possible checkout/repo mismatch.", vid); + } + } + if( pBaseline ){ + blob_appendf(pOut, "B %s\n", zBaselineUuid); + manifest_file_rewind(pBaseline); + pFile = manifest_file_next(pBaseline, 0); + nFBcard++; + }else{ + pFile = 0; + } + if( blob_size(p->pComment)!=0 ){ + blob_appendf(pOut, "C %F\n", blob_str(p->pComment)); + }else{ + blob_append(pOut, "C (no\\scomment)\n", 16); + } + zDate = date_in_standard_format(p->zDateOvrd ? p->zDateOvrd : "now"); + blob_appendf(pOut, "D %s\n", zDate); + zDate[10] = ' '; + db_prepare(&q, + "SELECT pathname, uuid, origname, blob.rid, isexe, islink," + " is_selected(vfile.id)" + " FROM vfile JOIN blob ON vfile.mrid=blob.rid" + " WHERE (NOT deleted OR NOT is_selected(vfile.id))" + " AND vfile.vid=%d" + " ORDER BY if_selected(vfile.id, pathname, origname)", + vid); + blob_zero(&filename); + blob_appendf(&filename, "%s", g.zLocalRoot); + nBasename = blob_size(&filename); + while( db_step(&q)==SQLITE_ROW ){ + const char *zName = db_column_text(&q, 0); + const char *zUuid = db_column_text(&q, 1); + const char *zOrig = db_column_text(&q, 2); + int frid = db_column_int(&q, 3); + int isExe = db_column_int(&q, 4); + int isLink = db_column_int(&q, 5); + int isSelected = db_column_int(&q, 6); + const char *zPerm; + int cmp; + + blob_resize(&filename, nBasename); + blob_append(&filename, zName, -1); + +#if !defined(_WIN32) + /* For unix, extract the "executable" and "symlink" permissions + ** directly from the filesystem. On windows, permissions are + ** unchanged from the original. However, only do this if the file + ** itself is actually selected to be part of this check-in. + */ + if( isSelected ){ + int mPerm; + + mPerm = file_perm(blob_str(&filename), RepoFILE); + isExe = ( mPerm==PERM_EXE ); + isLink = ( mPerm==PERM_LNK ); + } +#endif + if( isExe ){ + zPerm = " x"; + }else if( isLink ){ + zPerm = " l"; /* note: symlinks don't have executable bit on unix */ + }else{ + zPerm = ""; + } + if( !g.markPrivate ) content_make_public(frid); + while( pFile && fossil_strcmp(pFile->zName,zName)<0 ){ + blob_appendf(pOut, "F %F\n", pFile->zName); + pFile = manifest_file_next(pBaseline, 0); + nFBcard++; + } + cmp = 1; + if( pFile==0 + || (cmp = fossil_strcmp(pFile->zName,zName))!=0 + || fossil_strcmp(pFile->zUuid, zUuid)!=0 + ){ + if( zOrig && !isSelected ){ zName = zOrig; zOrig = 0; } + if( zOrig==0 || fossil_strcmp(zOrig,zName)==0 ){ + blob_appendf(pOut, "F %F %s%s\n", zName, zUuid, zPerm); + }else{ + if( zPerm[0]==0 ){ zPerm = " w"; } + blob_appendf(pOut, "F %F %s%s %F\n", zName, zUuid, zPerm, zOrig); + } + nFBcard++; + } + if( cmp==0 ) pFile = manifest_file_next(pBaseline,0); + } + blob_reset(&filename); + db_finalize(&q); + while( pFile ){ + blob_appendf(pOut, "F %F\n", pFile->zName); + pFile = manifest_file_next(pBaseline, 0); + nFBcard++; + } + if( p->zMimetype && p->zMimetype[0] ){ + blob_appendf(pOut, "N %F\n", p->zMimetype); + } + if( vid ){ + blob_appendf(pOut, "P %s", zParentUuid); + if( p->verifyDate ) checkin_verify_younger(vid, zParentUuid, zDate); + free(zParentUuid); + db_prepare(&q, "SELECT merge FROM vmerge WHERE id=0 OR id<-2"); + while( db_step(&q)==SQLITE_ROW ){ + char *zMergeUuid; + int mid = db_column_int(&q, 0); + if( (!g.markPrivate && content_is_private(mid)) || (mid == vid) ){ + continue; + } + zMergeUuid = rid_to_uuid(mid); + if( zMergeUuid ){ + blob_appendf(pOut, " %s", zMergeUuid); + if( p->verifyDate ) checkin_verify_younger(mid, zMergeUuid, zDate); + free(zMergeUuid); + } + } + db_finalize(&q); + blob_appendf(pOut, "\n"); + } + free(zDate); + + db_prepare(&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( db_step(&q)==SQLITE_ROW ){ + const char *zCherrypickUuid = db_column_text(&q, 0); + int mid = db_column_int(&q, 1); + if( mid != vid ){ + blob_appendf(pOut, "Q %s\n", zCherrypickUuid); + } + } + db_finalize(&q); + + if( p->pCksum ) blob_appendf(pOut, "R %b\n", p->pCksum); + zColor = p->zColor; + if( p->zBranch && p->zBranch[0] ){ + /* Set tags for the new branch */ + if( p->zBrClr && p->zBrClr[0] ){ + zColor = 0; + blob_appendf(pOut, "T *bgcolor * %F\n", p->zBrClr); + } + blob_appendf(pOut, "T *branch * %F\n", p->zBranch); + blob_appendf(pOut, "T *sym-%F *\n", p->zBranch); + } + if( zColor && zColor[0] ){ + /* One-time background color */ + blob_appendf(pOut, "T +bgcolor * %F\n", zColor); + } + if( p->closeFlag ){ + blob_appendf(pOut, "T +closed *\n"); + } + db_prepare(&q, "SELECT mhash,merge FROM vmerge" + " WHERE id %s ORDER BY 1", + p->integrateFlag ? "IN(0,-4)" : "=(-4)"); + while( db_step(&q)==SQLITE_ROW ){ + const char *zIntegrateUuid = db_column_text(&q, 0); + int rid = db_column_int(&q, 1); + if( is_a_leaf(rid) && !db_exists("SELECT 1 FROM tagxref " + " WHERE tagid=%d AND rid=%d AND tagtype>0", TAG_CLOSED, rid)){ +#if 0 + /* Make sure the check-in manifest of the resulting merge child does not + ** include a +close tag referring to the leaf check-in on a private + ** branch, so as not to generate a missing artifact reference on + ** repository clones without that private branch. The merge command + ** should have dropped the --integrate option, at this point. */ + assert( !content_is_private(rid) ); #endif + blob_appendf(pOut, "T +closed %s\n", zIntegrateUuid); + } + } + db_finalize(&q); + + if( p->azTag ){ + for(i=0; p->azTag[i]; i++){ + /* Add a symbolic tag to this check-in. The tag names have already + ** been sorted and converted using the %F format */ + assert( i==0 || strcmp(p->azTag[i-1], p->azTag[i])<=0 ); + blob_appendf(pOut, "T +sym-%s *\n", p->azTag[i]); + } + } + if( p->zBranch && p->zBranch[0] ){ + /* For a new branch, cancel all prior propagating tags */ + db_prepare(&q, + "SELECT tagname FROM tagxref, tag" + " WHERE tagxref.rid=%d AND tagxref.tagid=tag.tagid" + " AND tagtype==2 AND tagname GLOB 'sym-*'" + " AND tagname!='sym-'||%Q" + " ORDER BY tagname", + vid, p->zBranch); + while( db_step(&q)==SQLITE_ROW ){ + const char *zBrTag = db_column_text(&q, 0); + blob_appendf(pOut, "T -%F *\n", zBrTag); + } + db_finalize(&q); + } + blob_appendf(pOut, "U %F\n", p->zUserOvrd ? p->zUserOvrd : login_name()); + md5sum_blob(pOut, &mcksum); + blob_appendf(pOut, "Z %b\n", &mcksum); + if( pnFBcard ) *pnFBcard = nFBcard; +} + +/* +** Issue a warning and give the user an opportunity to abandon out +** if a Unicode (UTF-16) byte-order-mark (BOM) or a \r\n line ending +** is seen in a text file. +** +** Return 1 if the user pressed 'c'. In that case, the file will have +** been converted to UTF-8 (if it was UTF-16) with LF line-endings, +** and the original file will have been renamed to "<filename>-original". +*/ +static int commit_warning( + Blob *pContent, /* The content of the file being committed. */ + int crlfOk, /* Non-zero if CR/LF warnings should be disabled. */ + int binOk, /* Non-zero if binary warnings should be disabled. */ + int encodingOk, /* Non-zero if encoding warnings should be disabled. */ + int noPrompt, /* 0 to always prompt, 1 for 'N', 2 for 'Y'. */ + const char *zFilename, /* The full name of the file being committed. */ + Blob *pReason /* Reason for warning, if any (non-fatal only). */ +){ + int bReverse; /* UTF-16 byte order is reversed? */ + int fUnicode; /* return value of could_be_utf16() */ + int fBinary; /* does the blob content appear to be binary? */ + int lookFlags; /* output flags from looks_like_utf8/utf16() */ + int fHasAnyCr; /* the blob contains one or more CR chars */ + int fHasLoneCrOnly; /* all detected line endings are CR only */ + int fHasCrLfOnly; /* all detected line endings are CR/LF pairs */ + int fHasInvalidUtf8 = 0;/* contains invalid UTF-8 */ + char *zMsg; /* Warning message */ + Blob fname; /* Relative pathname of the file */ + static int allOk = 0; /* Set to true to disable this routine */ + + if( allOk ) return 0; + fUnicode = could_be_utf16(pContent, &bReverse); + if( fUnicode ){ + lookFlags = looks_like_utf16(pContent, bReverse, LOOK_NUL); + }else{ + lookFlags = looks_like_utf8(pContent, LOOK_NUL); + if( !(lookFlags & LOOK_BINARY) && invalid_utf8(pContent) ){ + fHasInvalidUtf8 = 1; + } + } + fHasAnyCr = (lookFlags & LOOK_CR); + fBinary = (lookFlags & LOOK_BINARY); + fHasLoneCrOnly = ((lookFlags & LOOK_EOL) == LOOK_LONE_CR); + fHasCrLfOnly = ((lookFlags & LOOK_EOL) == LOOK_CRLF); + if( fUnicode || fHasAnyCr || fBinary || fHasInvalidUtf8 ){ + const char *zWarning; + const char *zDisable; + const char *zConvert = "c=convert/"; + Blob ans; + char cReply; + + if( fBinary ){ + int fHasNul = (lookFlags & LOOK_NUL); /* contains NUL chars? */ + int fHasLong = (lookFlags & LOOK_LONG); /* overly long line? */ + if( binOk ){ + return 0; /* We don't want binary warnings for this file. */ + } + if( !fHasNul && fHasLong ){ + zWarning = "long lines"; + zConvert = ""; /* We cannot convert overlong lines. */ + }else{ + zWarning = "binary data"; + zConvert = ""; /* We cannot convert binary files. */ + } + zDisable = "\"binary-glob\" setting"; + }else if( fUnicode && fHasAnyCr ){ + if( crlfOk && encodingOk ){ + return 0; /* We don't want CR/LF and Unicode warnings for this file. */ + } + if( fHasLoneCrOnly ){ + zWarning = "CR line endings and Unicode"; + }else if( fHasCrLfOnly ){ + zWarning = "CR/LF line endings and Unicode"; + }else{ + zWarning = "mixed line endings and Unicode"; + } + zDisable = "\"crlf-glob\" and \"encoding-glob\" settings"; + }else if( fHasInvalidUtf8 ){ + if( encodingOk ){ + return 0; /* We don't want encoding warnings for this file. */ + } + zWarning = "invalid UTF-8"; + zDisable = "\"encoding-glob\" setting"; + }else if( fHasAnyCr ){ + if( crlfOk ){ + return 0; /* We don't want CR/LF warnings for this file. */ + } + if( fHasLoneCrOnly ){ + zWarning = "CR line endings"; + }else if( fHasCrLfOnly ){ + zWarning = "CR/LF line endings"; + }else{ + zWarning = "mixed line endings"; + } + zDisable = "\"crlf-glob\" setting"; + }else{ + if( encodingOk ){ + return 0; /* We don't want encoding warnings for this file. */ + } + zWarning = "Unicode"; + zDisable = "\"encoding-glob\" setting"; + } + file_relative_name(zFilename, &fname, 0); + zMsg = mprintf( + "%s contains %s. Use --no-warnings or the %s to" + " disable this warning.\n" + "Commit anyhow (a=all/%sy/N)? ", + blob_str(&fname), zWarning, zDisable, zConvert); + if( noPrompt==0 ){ + prompt_user(zMsg, &ans); + cReply = blob_str(&ans)[0]; + blob_reset(&ans); + }else if( noPrompt==2 ){ + cReply = 'Y'; + }else{ + cReply = 'N'; + } + fossil_free(zMsg); + if( cReply=='a' || cReply=='A' ){ + allOk = 1; + }else if( *zConvert && (cReply=='c' || cReply=='C') ){ + char *zOrig = file_newname(zFilename, "original", 1); + FILE *f; + blob_write_to_file(pContent, zOrig); + fossil_free(zOrig); + f = fossil_fopen(zFilename, "wb"); + if( f==0 ){ + fossil_warning("cannot open %s for writing", zFilename); + }else{ + if( fUnicode ){ + int bomSize; + const unsigned char *bom = get_utf8_bom(&bomSize); + fwrite(bom, 1, bomSize, f); + blob_to_utf8_no_bom(pContent, 0); + }else if( fHasInvalidUtf8 ){ + blob_cp1252_to_utf8(pContent); + } + if( fHasAnyCr ){ + blob_to_lf_only(pContent); + } + fwrite(blob_buffer(pContent), 1, blob_size(pContent), f); + fclose(f); + } + return 1; + }else if( cReply!='y' && cReply!='Y' ){ + fossil_fatal("Abandoning commit due to %s in %s", + zWarning, blob_str(&fname)); + }else if( noPrompt==2 ){ + if( pReason ){ + blob_append(pReason, zWarning, -1); + } + return 1; + } + blob_reset(&fname); + } + return 0; +} + +/* +** COMMAND: test-commit-warning +** +** Usage: %fossil test-commit-warning ?OPTIONS? +** +** Check each file in the checkout, including unmodified ones, using all +** the pre-commit checks. +** +** Options: +** --no-settings Do not consider any glob settings. +** -v|--verbose Show per-file results for all pre-commit checks. +** +** See also: commit, extras +*/ +void test_commit_warning(void){ + int rc = 0; + int noSettings; + int verboseFlag; + Stmt q; + noSettings = find_option("no-settings",0,0)!=0; + verboseFlag = find_option("verbose","v",0)!=0; + verify_all_options(); + db_must_be_within_tree(); + db_prepare(&q, + "SELECT %Q || pathname, pathname, %s, %s, %s FROM vfile" + " WHERE NOT deleted", + g.zLocalRoot, + glob_expr("pathname", noSettings ? 0 : db_get("crlf-glob", + db_get("crnl-glob",""))), + glob_expr("pathname", noSettings ? 0 : db_get("binary-glob","")), + glob_expr("pathname", noSettings ? 0 : db_get("encoding-glob","")) + ); + while( db_step(&q)==SQLITE_ROW ){ + const char *zFullname; + const char *zName; + Blob content; + Blob reason; + int crlfOk, binOk, encodingOk; + int fileRc; + + zFullname = db_column_text(&q, 0); + zName = db_column_text(&q, 1); + crlfOk = db_column_int(&q, 2); + binOk = db_column_int(&q, 3); + encodingOk = db_column_int(&q, 4); + blob_zero(&content); + blob_read_from_file(&content, zFullname, RepoFILE); + blob_zero(&reason); + fileRc = commit_warning(&content, crlfOk, binOk, encodingOk, 2, + zFullname, &reason); + if( fileRc || verboseFlag ){ + fossil_print("%d\t%s\t%s\n", fileRc, zName, blob_str(&reason)); + } + blob_reset(&reason); + rc |= fileRc; + } + db_finalize(&q); + fossil_print("%d\n", rc); +} + +/* +** qsort() comparison routine for an array of pointers to strings. +*/ +static int tagCmp(const void *a, const void *b){ + char **pA = (char**)a; + char **pB = (char**)b; + return fossil_strcmp(pA[0], pB[0]); } /* -** COMMAND: ci +** COMMAND: ci* ** COMMAND: commit ** ** Usage: %fossil commit ?OPTIONS? ?FILE...? +** or: %fossil ci ?OPTIONS? ?FILE...? ** ** Create a new version containing all of the changes in the current ** checkout. You will be prompted to enter a check-in comment unless -** the comment has been specified on the command-line using "-m". -** The editor defined in the "editor" fossil option (see %fossil help set) -** will be used, or from the "VISUAL" or "EDITOR" environment variables -** (in that order) if no editor is set. -** -** You will be prompted for your GPG passphrase in order to sign the -** new manifest unless the "--nosign" options is used. All files that -** have changed will be committed unless some subset of files is -** specified on the command line. -** -** The --branch option followed by a branch name cases the new check-in -** to be placed in the named branch. The --bgcolor option can be followed -** by a color name (ex: '#ffc0c0') to specify the background color of -** entries in the new branch when shown in the web timeline interface. -** -** A check-in is not permitted to fork unless the --force or -f -** option appears. A check-in is not allowed against a closed check-in. +** the comment has been specified on the command-line using "-m" or a +** file containing the comment using -M. The editor defined in the +** "editor" fossil option (see %fossil help set) will be used, or from +** the "VISUAL" or "EDITOR" environment variables (in that order) if +** no editor is set. +** +** All files that have changed will be committed unless some subset of +** files is specified on the command line. +** +** The --branch option followed by a branch name causes the new +** check-in to be placed in a newly-created branch with the name +** passed to the --branch option. +** +** Use the --branchcolor option followed by a color name (ex: +** '#ffc0c0') to specify the background color of entries in the new +** branch when shown in the web timeline interface. The use of +** the --branchcolor option is not recommended. Instead, let Fossil +** choose the branch color automatically. +** +** The --bgcolor option works like --branchcolor but only sets the +** background color for a single check-in. Subsequent check-ins revert +** to the default color. +** +** A check-in is not permitted to fork unless the --allow-fork option +** appears. An empty check-in (i.e. with nothing changed) is not +** allowed unless the --allow-empty option appears. A check-in may not +** be older than its ancestor unless the --allow-older option appears. +** If any of files in the check-in appear to contain unresolved merge +** conflicts, the check-in will not be allowed unless the +** --allow-conflict option is present. In addition, the entire +** check-in process may be aborted if a file contains content that +** appears to be binary, Unicode text, or text with CR/LF line endings +** unless the interactive user chooses to proceed. If there is no +** interactive user or these warnings should be skipped for some other +** reason, the --no-warnings option may be used. A check-in is not +** allowed against a closed leaf. +** +** If a commit message is blank, you will be prompted: +** ("continue (y/N)?") to confirm you really want to commit with a +** blank commit message. The default value is "N", do not commit. ** ** The --private option creates a private check-in that is never synced. ** Children of private check-ins are automatically private. ** +** The --tag option applies the symbolic tag name to the check-in. +** +** The --hash option detects edited files by computing each file's +** artifact hash rather than just checking for changes to its size or mtime. +** ** Options: -** -** --comment|-m COMMENT-TEXT -** --branch NEW-BRANCH-NAME -** --bgcolor COLOR -** --nosign -** --force|-f -** --private -** +** --allow-conflict allow unresolved merge conflicts +** --allow-empty allow a commit with no changes +** --allow-fork allow the commit to fork +** --allow-older allow a commit older than its ancestor +** --baseline use a baseline manifest in the commit process +** --bgcolor COLOR apply COLOR to this one check-in only +** --branch NEW-BRANCH-NAME check in to this new branch +** --branchcolor COLOR apply given COLOR to the branch +** --close close the branch being committed +** --date-override DATETIME DATE to use instead of 'now' +** --delta use a delta manifest in the commit process +** --hash verify file status using hashing rather +** than relying on file mtimes +** --integrate close all merged-in branches +** -m|--comment COMMENT-TEXT use COMMENT-TEXT as commit comment +** -M|--message-file FILE read the commit comment from given file +** --mimetype MIMETYPE mimetype of check-in comment +** -n|--dry-run If given, display instead of run actions +** -v|--verbose Show a diff in the commit message prompt +** --no-prompt This option disables prompting the user for +** input and assumes an answer of 'No' for every +** question. +** --no-warnings omit all warnings about file contents +** --no-verify do not run before-commit hooks +** --nosign do not attempt to sign this commit with gpg +** --override-lock allow a check-in even though parent is locked +** --private do not sync changes and their descendants +** --tag TAG-NAME assign given tag TAG-NAME to the check-in +** --trace debug tracing. +** --user-override USER USER to use instead of the current default +** +** DATETIME may be "now" or "YYYY-MM-DDTHH:MM:SS.SSS". If in +** year-month-day form, it may be truncated, the "T" may be replaced by +** a space, and it may also name a timezone offset from UTC as "-HH:MM" +** (westward) or "+HH:MM" (eastward). Either no timezone suffix or "Z" +** means UTC. +** +** See also: [[branch]], [[changes]], [[update]], [[extras]], [[sync]] */ void commit_cmd(void){ - int rc; - int vid, nrid, nvid; - Blob comment; - const char *zComment; - Stmt q; - Stmt q2; - char *zUuid, *zDate; + int hasChanges; /* True if unsaved changes exist */ + int vid; /* blob-id of parent version */ + int nrid; /* blob-id of a modified file */ + int nvid; /* Blob-id of the new check-in */ + Blob comment; /* Check-in comment */ + const char *zComment; /* Check-in comment */ + Stmt q; /* Various queries */ + char *zUuid; /* Hash of the new check-in */ + int useHash = 0; /* True to verify file status using hashing */ int noSign = 0; /* True to omit signing the manifest using GPG */ + int privateFlag = 0; /* True if the --private option is present */ + int privateParent = 0; /* True if the parent check-in is private */ int isAMerge = 0; /* True if checking in a merge */ - int forceFlag = 0; /* Force a fork */ + int noWarningFlag = 0; /* True if skipping all warnings */ + int noVerify = 0; /* Do not run before-commit hooks */ + int bTrace = 0; /* Debug tracing */ + int noPrompt = 0; /* True if skipping all prompts */ + int forceFlag = 0; /* Undocumented: Disables all checks */ + int forceDelta = 0; /* Force a delta-manifest */ + int forceBaseline = 0; /* Force a baseline-manifest */ + int allowConflict = 0; /* Allow unresolve merge conflicts */ + int allowEmpty = 0; /* Allow a commit with no changes */ + int allowFork = 0; /* Allow the commit to fork */ + int allowOlder = 0; /* Allow a commit older than its ancestor */ char *zManifestFile; /* Name of the manifest file */ - int nBasename; /* Length of "g.zLocalRoot/" */ - const char *zBranch; /* Create a new branch with this name */ - const char *zBgColor; /* Set background color when branching */ - const char *zDateOvrd; /* Override date string */ - const char *zUserOvrd; /* Override user name */ + int useCksum; /* True if checksums should be computed and verified */ + int outputManifest; /* True to output "manifest" and "manifest.uuid" */ + int dryRunFlag; /* True for a test run. Debugging only */ + CheckinInfo sCiInfo; /* Information about this check-in */ const char *zComFile; /* Read commit message from this file */ - Blob filename; /* complete filename */ - Blob manifest; + int nTag = 0; /* Number of --tag arguments */ + const char *zTag; /* A single --tag argument */ + ManifestFile *pFile; /* File structure in the manifest */ + Manifest *pManifest; /* Manifest structure */ + Blob manifest; /* Manifest in baseline form */ Blob muuid; /* Manifest uuid */ - Blob mcksum; /* Self-checksum on the manifest */ Blob cksum1, cksum2; /* Before and after commit checksums */ Blob cksum1b; /* Checksum recorded in the manifest */ - + int szD; /* Size of the delta manifest */ + int szB; /* Size of the baseline manifest */ + int nConflict = 0; /* Number of unresolved merge conflicts */ + int abortCommit = 0; /* Abort the commit due to text format conversions */ + Blob ans; /* Answer to continuation prompts */ + char cReply; /* First character of ans */ + int bRecheck = 0; /* Repeat fork and closed-branch checks*/ + + memset(&sCiInfo, 0, sizeof(sCiInfo)); url_proxy_options(); + /* --sha1sum is an undocumented alias for --hash for backwards compatiblity */ + useHash = find_option("hash",0,0)!=0 || find_option("sha1sum",0,0)!=0; noSign = find_option("nosign",0,0)!=0; + privateFlag = find_option("private",0,0)!=0; + forceDelta = find_option("delta",0,0)!=0; + forceBaseline = find_option("baseline",0,0)!=0; + if( forceDelta ){ + if( forceBaseline ){ + fossil_fatal("cannot use --delta and --baseline together"); + } + if( db_get_boolean("forbid-delta-manifests",0) ){ + fossil_fatal("delta manifests are prohibited in this repository"); + } + } + dryRunFlag = find_option("dry-run","n",0)!=0; + if( !dryRunFlag ){ + dryRunFlag = find_option("test",0,0)!=0; /* deprecated */ + } zComment = find_option("comment","m",1); forceFlag = find_option("force", "f", 0)!=0; - zBranch = find_option("branch","b",1); - zBgColor = find_option("bgcolor",0,1); + allowConflict = find_option("allow-conflict",0,0)!=0; + allowEmpty = find_option("allow-empty",0,0)!=0; + allowFork = find_option("allow-fork",0,0)!=0; + if( find_option("override-lock",0,0)!=0 ) allowFork = 1; + allowOlder = find_option("allow-older",0,0)!=0; + noPrompt = find_option("no-prompt", 0, 0)!=0; + noWarningFlag = find_option("no-warnings", 0, 0)!=0; + noVerify = find_option("no-verify",0,0)!=0; + bTrace = find_option("trace",0,0)!=0; + sCiInfo.zBranch = find_option("branch","b",1); + sCiInfo.zColor = find_option("bgcolor",0,1); + sCiInfo.zBrClr = find_option("branchcolor",0,1); + sCiInfo.closeFlag = find_option("close",0,0)!=0; + sCiInfo.integrateFlag = find_option("integrate",0,0)!=0; + sCiInfo.zMimetype = find_option("mimetype",0,1); + sCiInfo.verboseFlag = find_option("verbose", "v", 0)!=0; + while( (zTag = find_option("tag",0,1))!=0 ){ + if( zTag[0]==0 ) continue; + sCiInfo.azTag = fossil_realloc((void*)sCiInfo.azTag, + sizeof(char*)*(nTag+2)); + sCiInfo.azTag[nTag++] = zTag; + sCiInfo.azTag[nTag] = 0; + } zComFile = find_option("message-file", "M", 1); - if( find_option("private",0,0) ){ - g.markPrivate = 1; - if( zBranch==0 ) zBranch = "private"; - if( zBgColor==0 ) zBgColor = "#fec084"; /* Orange */ - } - zDateOvrd = find_option("date-override",0,1); - zUserOvrd = find_option("user-override",0,1); + sCiInfo.zDateOvrd = find_option("date-override",0,1); + sCiInfo.zUserOvrd = find_option("user-override",0,1); db_must_be_within_tree(); noSign = db_get_boolean("omitsign", 0)|noSign; if( db_get_boolean("clearsign", 0)==0 ){ noSign = 1; } + useCksum = db_get_boolean("repo-cksum", 1); + outputManifest = db_get_manifest_setting(); verify_all_options(); /* Get the ID of the parent manifest artifact */ vid = db_lget_int("checkout", 0); - if( content_is_private(vid) ){ - g.markPrivate = 1; + if( vid==0 ){ + useCksum = 1; + if( privateFlag==0 && sCiInfo.zBranch==0 ) { + sCiInfo.zBranch=db_get("main-branch", 0); + } + }else{ + privateParent = content_is_private(vid); + } + + /* Track the "private" status */ + g.markPrivate = privateFlag || privateParent; + if( privateFlag && !privateParent ){ + /* Apply default branch name ("private") and color ("orange") if not + ** specified otherwise on the command-line, and if the parent is not + ** already private. */ + if( sCiInfo.zBranch==0 ) sCiInfo.zBranch = "private"; + if( sCiInfo.zBrClr==0 && sCiInfo.zColor==0 ) sCiInfo.zBrClr = "#fec084"; + } + + /* Do not allow the creation of a new branch using an existing open + ** branch name unless the --force flag is used */ + if( sCiInfo.zBranch!=0 + && !forceFlag + && fossil_strcmp(sCiInfo.zBranch,"private")!=0 + && branch_is_open(sCiInfo.zBranch) + ){ + fossil_fatal("an open branch named \"%s\" already exists - use --force" + " to override", sCiInfo.zBranch); + } + + /* Escape special characters in tags and put all tags in sorted order */ + if( nTag ){ + int i; + for(i=0; i<nTag; i++) sCiInfo.azTag[i] = mprintf("%F", sCiInfo.azTag[i]); + qsort((void*)sCiInfo.azTag, nTag, sizeof(sCiInfo.azTag[0]), tagCmp); } /* ** Autosync if autosync is enabled and this is not a private check-in. */ if( !g.markPrivate ){ - autosync(AUTOSYNC_PULL); + int syncFlags = SYNC_PULL; + if( vid!=0 && !allowFork && !forceFlag ){ + syncFlags |= SYNC_CKIN_LOCK; + } + if( autosync_loop(syncFlags, db_get_int("autosync-tries", 1), 1) ){ + fossil_exit(1); + } + } + + /* So that older versions of Fossil (that do not understand delta- + ** manifest) can continue to use this repository, do not create a new + ** delta-manifest unless this repository already contains one or more + ** delta-manifests, or unless the delta-manifest is explicitly requested + ** by the --delta option. + ** + ** The forbid-delta-manifests setting prevents new delta manifests. + ** + ** If the remote repository sent an avoid-delta-manifests pragma on + ** the autosync above, then also try to avoid deltas, unless the + ** --delta option is specified. The remote repo will send the + ** avoid-delta-manifests pragma if it has its "forbid-delta-manifests" + ** setting is enabled. + */ + if( !db_get_boolean("seen-delta-manifest",0) + || db_get_boolean("forbid-delta-manifests",0) + || g.bAvoidDeltaManifests + ){ + if( !forceDelta ) forceBaseline = 1; + } + + + /* Require confirmation to continue with the check-in if there is + ** clock skew + */ + if( g.clockSkewSeen ){ + if( !noPrompt ){ + prompt_user("continue in spite of time skew (y/N)? ", &ans); + cReply = blob_str(&ans)[0]; + blob_reset(&ans); + }else{ + fossil_print("Abandoning commit due to time skew\n"); + cReply = 'N'; + } + if( cReply!='y' && cReply!='Y' ){ + fossil_exit(1); + } } /* There are two ways this command may be executed. If there are ** no arguments following the word "commit", then all modified files ** in the checked out directory are committed. If one or more arguments @@ -621,289 +2361,473 @@ ** After the following function call has returned, the Global.aCommitFile[] ** array is allocated to contain the "id" field from the vfile table ** for each file to be committed. Or, if aCommitFile is NULL, all files ** should be committed. */ - select_commit_files(); - isAMerge = db_exists("SELECT 1 FROM vmerge"); + if( select_commit_files() ){ + if( !noPrompt ){ + prompt_user("continue (y/N)? ", &ans); + cReply = blob_str(&ans)[0]; + blob_reset(&ans); + }else{ + cReply = 'N'; + } + if( cReply!='y' && cReply!='Y' ){ + fossil_exit(1); + } + } + isAMerge = db_exists("SELECT 1 FROM vmerge WHERE id=0 OR id<-2"); if( g.aCommitFile && isAMerge ){ fossil_fatal("cannot do a partial commit of a merge"); } + + /* Doing "fossil mv fileA fileB; fossil add fileA; fossil commit fileA" + ** will generate a manifest that has two fileA entries, which is illegal. + ** When you think about it, the sequence above makes no sense. So detect + ** it and disallow it. Ticket [0ff64b0a5fc8]. + */ + if( g.aCommitFile ){ + db_prepare(&q, + "SELECT v1.pathname, v2.pathname" + " FROM vfile AS v1, vfile AS v2" + " WHERE is_selected(v1.id)" + " AND v2.origname IS NOT NULL" + " AND v2.origname=v1.pathname" + " AND NOT is_selected(v2.id)"); + if( db_step(&q)==SQLITE_ROW ){ + const char *zFrom = db_column_text(&q, 0); + const char *zTo = db_column_text(&q, 1); + fossil_fatal("cannot do a partial commit of '%s' without '%s' because " + "'%s' was renamed to '%s'", zFrom, zTo, zFrom, zTo); + } + db_finalize(&q); + } user_select(); /* ** Check that the user exists. */ if( !db_exists("SELECT 1 FROM user WHERE login=%Q", g.zLogin) ){ fossil_fatal("no such user: %s", g.zLogin); } - - db_begin_transaction(); - db_record_repository_filename(0); - rc = unsaved_changes(); - if( rc==0 && !isAMerge && !forceFlag ){ - fossil_panic("nothing has changed"); - } - - /* If one or more files that were named on the command line have not - ** been modified, bail out now. - */ - if( g.aCommitFile ){ - Blob unmodified; - memset(&unmodified, 0, sizeof(Blob)); - blob_init(&unmodified, 0, 0); - db_blob(&unmodified, - "SELECT pathname FROM vfile WHERE chnged = 0 AND file_is_selected(id)" - ); - if( strlen(blob_str(&unmodified)) ){ - fossil_panic("file %s has not changed", blob_str(&unmodified)); - } - } - - /* - ** Do not allow a commit that will cause a fork unless the --force flag - ** is used or unless this is a private check-in. - */ - if( zBranch==0 && forceFlag==0 && g.markPrivate==0 && !is_a_leaf(vid) ){ - fossil_fatal("would fork. \"update\" first or use -f or --force."); - } - - /* - ** Do not allow a commit against a closed leaf - */ - if( db_exists("SELECT 1 FROM tagxref" - " WHERE tagid=%d AND rid=%d AND tagtype>0", - TAG_CLOSED, vid) ){ - fossil_fatal("cannot commit against a closed leaf"); - } - - vfile_aggregate_checksum_disk(vid, &cksum1); - if( zComment ){ - blob_zero(&comment); - blob_append(&comment, zComment, -1); - }else if( zComFile ){ - blob_zero(&comment); - blob_read_from_file(&comment, zComFile); - }else{ - char *zInit = db_text(0, "SELECT value FROM vvar WHERE name='ci-comment'"); - prepare_commit_comment(&comment, zInit, zBranch, vid); - free(zInit); - } - if( blob_size(&comment)==0 ){ - Blob ans; - blob_zero(&ans); - prompt_user("empty check-in comment. continue (y/N)? ", &ans); - if( blob_str(&ans)[0]!='y' ){ - db_end_transaction(1); - exit(1); - } - }else{ - db_multi_exec("REPLACE INTO vvar VALUES('ci-comment',%B)", &comment); - db_end_transaction(0); - db_begin_transaction(); - } - - /* Step 1: Insert records for all modified files into the blob - ** table. If there were arguments passed to this command, only - ** the identified fils are inserted (if they have been modified). - */ - db_prepare(&q, - "SELECT id, %Q || pathname, mrid FROM vfile " - "WHERE chnged==1 AND NOT deleted AND file_is_selected(id)" - , g.zLocalRoot + + hasChanges = unsaved_changes(useHash ? CKSIG_HASH : 0); + db_begin_transaction(); + db_record_repository_filename(0); + if( hasChanges==0 && !isAMerge && !allowEmpty && !forceFlag ){ + fossil_fatal("nothing has changed; use --allow-empty to override"); + } + + /* If none of the files that were named on the command line have + ** been modified, bail out now unless the --allow-empty or --force + ** flags is used. + */ + if( g.aCommitFile + && !allowEmpty + && !forceFlag + && !db_exists( + "SELECT 1 FROM vfile " + " WHERE is_selected(id)" + " AND (chnged OR deleted OR rid=0 OR pathname!=origname)") + ){ + fossil_fatal("none of the selected files have changed; use " + "--allow-empty to override."); + } + + /* This loop checks for potential forks and for check-ins against a + ** closed branch. The checks are repeated once after interactive + ** check-in comment editing. + */ + do{ + /* + ** Do not allow a commit that will cause a fork unless the --allow-fork + ** or --force flags is used, or unless this is a private check-in. + ** The initial commit MUST have tags "trunk" and "sym-trunk". + */ + if( sCiInfo.zBranch==0 + && allowFork==0 + && forceFlag==0 + && g.markPrivate==0 + && (vid==0 || !is_a_leaf(vid) || g.ckinLockFail) + ){ + if( g.ckinLockFail ){ + fossil_fatal("Might fork due to a check-in race with user \"%s\"\n" + "Try \"update\" first, or --branch, or " + "use --override-lock", + g.ckinLockFail); + }else{ + fossil_fatal("Would fork. \"update\" first or use --branch or " + "--allow-fork."); + } + } + + /* + ** Do not allow a commit against a closed leaf unless the commit + ** ends up on a different branch. + */ + if( + /* parent check-in has the "closed" tag... */ + leaf_is_closed(vid) + /* ... and the new check-in has no --branch option or the --branch + ** option does not actually change the branch */ + && (sCiInfo.zBranch==0 + || db_exists("SELECT 1 FROM tagxref" + " WHERE tagid=%d AND rid=%d AND tagtype>0" + " AND value=%Q", TAG_BRANCH, vid, sCiInfo.zBranch)) + ){ + fossil_fatal("cannot commit against a closed leaf"); + } + + /* Always exit the loop on the second pass */ + if( bRecheck ) break; + + + /* Get the check-in comment. This might involve prompting the + ** user for the check-in comment, in which case we should resync + ** to renew the check-in lock and repeat the checks for conflicts. + */ + if( zComment ){ + blob_zero(&comment); + blob_append(&comment, zComment, -1); + }else if( zComFile ){ + blob_zero(&comment); + blob_read_from_file(&comment, zComFile, ExtFILE); + blob_to_utf8_no_bom(&comment, 1); + }else if( !noPrompt ){ + char *zInit = db_text(0,"SELECT value FROM vvar WHERE name='ci-comment'"); + prepare_commit_comment(&comment, zInit, &sCiInfo, vid, dryRunFlag); + if( zInit && zInit[0] && fossil_strcmp(zInit, blob_str(&comment))==0 ){ + prompt_user("unchanged check-in comment. continue (y/N)? ", &ans); + cReply = blob_str(&ans)[0]; + blob_reset(&ans); + if( cReply!='y' && cReply!='Y' ){ + fossil_fatal("Commit aborted."); + } + } + free(zInit); + db_multi_exec("REPLACE INTO vvar VALUES('ci-comment',%B)", &comment); + db_end_transaction(0); + db_begin_transaction(); + if( !g.markPrivate && vid!=0 && !allowFork && !forceFlag ){ + /* Do another auto-pull, renewing the check-in lock. Then set + ** bRecheck so that we loop back above to verify that the check-in + ** is still not against a closed branch and still won't fork. */ + int syncFlags = SYNC_PULL|SYNC_CKIN_LOCK; + if( autosync_loop(syncFlags, db_get_int("autosync-tries", 1), 1) ){ + fossil_fatal("Auto-pull failed. Commit aborted."); + } + bRecheck = 1; + } + }else{ + blob_zero(&comment); + } + }while( bRecheck ); + + if( blob_size(&comment)==0 ){ + if( !dryRunFlag ){ + if( !noPrompt ){ + prompt_user("empty check-in comment. continue (y/N)? ", &ans); + cReply = blob_str(&ans)[0]; + blob_reset(&ans); + }else{ + cReply = 'N'; + } + if( cReply!='y' && cReply!='Y' ){ + fossil_fatal("Abandoning commit due to empty check-in comment\n"); + } + } + } + + if( !noVerify && hook_exists("before-commit") ){ + /* Run before-commit hooks */ + char *zAuxFile; + zAuxFile = prepare_commit_description_file( + &sCiInfo, vid, &comment, dryRunFlag); + if( zAuxFile ){ + int rc = hook_run("before-commit",zAuxFile,bTrace); + file_delete(zAuxFile); + fossil_free(zAuxFile); + if( rc ){ + fossil_fatal("Before-commit hook failed\n"); + } + } + } + + /* + ** Step 1: Compute an aggregate MD5 checksum over the disk image + ** of every file in vid. The file names are part of the checksum. + ** The resulting checksum is the same as is expected on the R-card + ** of a manifest. + */ + if( useCksum ) vfile_aggregate_checksum_disk(vid, &cksum1); + + /* Step 2: Insert records for all modified files into the blob + ** table. If there were arguments passed to this command, only + ** the identified files are inserted (if they have been modified). + */ + db_prepare(&q, + "SELECT id, %Q || pathname, mrid, %s, %s, %s FROM vfile " + "WHERE chnged IN (1, 7, 9) AND NOT deleted AND is_selected(id)", + g.zLocalRoot, + glob_expr("pathname", db_get("crlf-glob",db_get("crnl-glob",""))), + glob_expr("pathname", db_get("binary-glob","")), + glob_expr("pathname", db_get("encoding-glob","")) ); while( db_step(&q)==SQLITE_ROW ){ int id, rid; const char *zFullname; Blob content; + int crlfOk, binOk, encodingOk; id = db_column_int(&q, 0); zFullname = db_column_text(&q, 1); rid = db_column_int(&q, 2); + crlfOk = db_column_int(&q, 3); + binOk = db_column_int(&q, 4); + encodingOk = db_column_int(&q, 5); blob_zero(&content); - blob_read_from_file(&content, zFullname); - nrid = content_put(&content, 0, 0); + blob_read_from_file(&content, zFullname, RepoFILE); + /* Do not emit any warnings when they are disabled. */ + if( !noWarningFlag ){ + abortCommit |= commit_warning(&content, crlfOk, binOk, + encodingOk, noPrompt, + zFullname, 0); + } + if( contains_merge_marker(&content) ){ + Blob fname; /* Relative pathname of the file */ + + nConflict++; + file_relative_name(zFullname, &fname, 0); + fossil_print("possible unresolved merge conflict in %s\n", + blob_str(&fname)); + blob_reset(&fname); + } + nrid = content_put(&content); blob_reset(&content); if( rid>0 ){ - content_deltify(rid, nrid, 0); + content_deltify(rid, &nrid, 1, 0); } - db_multi_exec("UPDATE vfile SET mrid=%d, rid=%d WHERE id=%d", nrid,nrid,id); + db_multi_exec("UPDATE vfile SET mrid=%d, rid=%d, mhash=NULL WHERE id=%d", + nrid,nrid,id); db_multi_exec("INSERT OR IGNORE INTO unsent VALUES(%d)", nrid); } db_finalize(&q); - - /* Create the manifest */ - blob_zero(&manifest); - if( blob_size(&comment)==0 ){ - blob_append(&comment, "(no comment)", -1); - } - blob_appendf(&manifest, "C %F\n", blob_str(&comment)); - zDate = db_text(0, "SELECT datetime('%q')", zDateOvrd ? zDateOvrd : "now"); - zDate[10] = 'T'; - blob_appendf(&manifest, "D %s\n", zDate); - zDate[10] = ' '; - db_prepare(&q, - "SELECT pathname, uuid, origname, blob.rid, isexe" - " FROM vfile JOIN blob ON vfile.mrid=blob.rid" - " WHERE NOT deleted AND vfile.vid=%d" - " ORDER BY 1", vid); - blob_zero(&filename); - blob_appendf(&filename, "%s", g.zLocalRoot); - nBasename = blob_size(&filename); - while( db_step(&q)==SQLITE_ROW ){ - const char *zName = db_column_text(&q, 0); - const char *zUuid = db_column_text(&q, 1); - const char *zOrig = db_column_text(&q, 2); - int frid = db_column_int(&q, 3); - int isexe = db_column_int(&q, 4); - const char *zPerm; - blob_append(&filename, zName, -1); -#ifndef __MINGW32__ - /* For unix, extract the "executable" permission bit directly from - ** the filesystem. On windows, the "executable" bit is retained - ** unchanged from the original. */ - isexe = file_isexe(blob_str(&filename)); -#endif - if( isexe ){ - zPerm = " x"; - }else{ - zPerm = ""; - } - blob_resize(&filename, nBasename); - if( zOrig==0 || strcmp(zOrig,zName)==0 ){ - blob_appendf(&manifest, "F %F %s%s\n", zName, zUuid, zPerm); - }else{ - if( zPerm[0]==0 ){ zPerm = " w"; } - blob_appendf(&manifest, "F %F %s%s %F\n", zName, zUuid, zPerm, zOrig); - } - if( !g.markPrivate ) content_make_public(frid); - } - blob_reset(&filename); - db_finalize(&q); - zUuid = db_text(0, "SELECT uuid FROM blob WHERE rid=%d", vid); - blob_appendf(&manifest, "P %s", zUuid); - checkin_verify_younger(vid, zUuid, zDate); - - db_prepare(&q2, "SELECT merge FROM vmerge WHERE id=:id"); - db_bind_int(&q2, ":id", 0); - while( db_step(&q2)==SQLITE_ROW ){ - int mid = db_column_int(&q2, 0); - if( !g.markPrivate && content_is_private(mid) ) continue; - zUuid = db_text(0, "SELECT uuid FROM blob WHERE rid=%d", mid); - if( zUuid ){ - blob_appendf(&manifest, " %s", zUuid); - checkin_verify_younger(mid, zUuid, zDate); - free(zUuid); - } - } - db_reset(&q2); - - blob_appendf(&manifest, "\n"); - blob_appendf(&manifest, "R %b\n", &cksum1); - if( zBranch && zBranch[0] ){ - Stmt q; - if( zBgColor && zBgColor[0] ){ - blob_appendf(&manifest, "T *bgcolor * %F\n", zBgColor); - } - blob_appendf(&manifest, "T *branch * %F\n", zBranch); - blob_appendf(&manifest, "T *sym-%F *\n", zBranch); - - /* Cancel all other symbolic tags */ - db_prepare(&q, - "SELECT tagname FROM tagxref, tag" - " WHERE tagxref.rid=%d AND tagxref.tagid=tag.tagid" - " AND tagtype>0 AND tagname GLOB 'sym-*'" - " AND tagname!='sym-'||%Q" - " ORDER BY tagname", - vid, zBranch); - while( db_step(&q)==SQLITE_ROW ){ - const char *zTag = db_column_text(&q, 0); - blob_appendf(&manifest, "T -%F *\n", zTag); - } - db_finalize(&q); - } - blob_appendf(&manifest, "U %F\n", zUserOvrd ? zUserOvrd : g.zLogin); - md5sum_blob(&manifest, &mcksum); - blob_appendf(&manifest, "Z %b\n", &mcksum); - zManifestFile = mprintf("%smanifest", g.zLocalRoot); - if( !noSign && !g.markPrivate && clearsign(&manifest, &manifest) ){ - Blob ans; - blob_zero(&ans); - prompt_user("unable to sign manifest. continue (y/N)? ", &ans); - if( blob_str(&ans)[0]!='y' ){ - db_end_transaction(1); - exit(1); - } - } - blob_write_to_file(&manifest, zManifestFile); - blob_reset(&manifest); - blob_read_from_file(&manifest, zManifestFile); - free(zManifestFile); - nvid = content_put(&manifest, 0, 0); - if( nvid==0 ){ - fossil_panic("trouble committing manifest: %s", g.zErrMsg); - } - db_multi_exec("INSERT OR IGNORE INTO unsent VALUES(%d)", nvid); - manifest_crosslink(nvid, &manifest); - content_deltify(vid, nvid, 0); - zUuid = db_text(0, "SELECT uuid FROM blob WHERE rid=%d", nvid); - printf("New_Version: %s\n", zUuid); - zManifestFile = mprintf("%smanifest.uuid", g.zLocalRoot); - blob_zero(&muuid); - blob_appendf(&muuid, "%s\n", zUuid); - blob_write_to_file(&muuid, zManifestFile); - free(zManifestFile); - blob_reset(&muuid); - - + if( nConflict && !allowConflict ){ + fossil_fatal("abort due to unresolved merge conflicts; " + "use --allow-conflict to override"); + }else if( abortCommit ){ + fossil_fatal("one or more files were converted on your request; " + "please re-test before committing"); + } + + /* Create the new manifest */ + sCiInfo.pComment = &comment; + sCiInfo.pCksum = useCksum ? &cksum1 : 0; + sCiInfo.verifyDate = !allowOlder && !forceFlag; + if( forceDelta ){ + blob_zero(&manifest); + }else{ + create_manifest(&manifest, 0, 0, vid, &sCiInfo, &szB); + } + + /* See if a delta-manifest would be more appropriate */ + if( !forceBaseline ){ + const char *zBaselineUuid; + Manifest *pParent; + Manifest *pBaseline; + pParent = manifest_get(vid, CFTYPE_MANIFEST, 0); + if( pParent && pParent->zBaseline ){ + zBaselineUuid = pParent->zBaseline; + pBaseline = manifest_get_by_name(zBaselineUuid, 0); + }else{ + zBaselineUuid = db_text(0, "SELECT uuid FROM blob WHERE rid=%d", vid); + pBaseline = pParent; + } + if( pBaseline ){ + Blob delta; + create_manifest(&delta, zBaselineUuid, pBaseline, vid, &sCiInfo, &szD); + /* + ** At this point, two manifests have been constructed, either of + ** which would work for this check-in. 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. + */ + if( forceDelta || (szD*szD)<(szB*3-9) ){ + blob_reset(&manifest); + manifest = delta; + }else{ + blob_reset(&delta); + } + }else if( forceDelta ){ + fossil_fatal("unable to find a baseline-manifest for the delta"); + } + } + if( !noSign && !g.markPrivate && clearsign(&manifest, &manifest) ){ + if( !noPrompt ){ + prompt_user("unable to sign manifest. continue (y/N)? ", &ans); + cReply = blob_str(&ans)[0]; + blob_reset(&ans); + }else{ + cReply = 'N'; + } + if( cReply!='y' && cReply!='Y' ){ + fossil_fatal("Abandoning commit due to manifest signing failure\n"); + } + } + + /* If the -n|--dry-run option is specified, output the manifest file + ** and rollback the transaction. + */ + if( dryRunFlag ){ + blob_write_to_file(&manifest, ""); + } + if( outputManifest & MFESTFLG_RAW ){ + zManifestFile = mprintf("%smanifest", g.zLocalRoot); + blob_write_to_file(&manifest, zManifestFile); + blob_reset(&manifest); + blob_read_from_file(&manifest, zManifestFile, ExtFILE); + free(zManifestFile); + } + + nvid = content_put(&manifest); + if( nvid==0 ){ + fossil_fatal("trouble committing manifest: %s", g.zErrMsg); + } + db_multi_exec("INSERT OR IGNORE INTO unsent VALUES(%d)", nvid); + if( manifest_crosslink(nvid, &manifest, + dryRunFlag ? MC_NONE : MC_PERMIT_HOOKS)==0 ){ + fossil_fatal("%s", g.zErrMsg); + } + assert( blob_is_reset(&manifest) ); + content_deltify(vid, &nvid, 1, 0); + zUuid = db_text(0, "SELECT uuid FROM blob WHERE rid=%d", nvid); + + db_prepare(&q, "SELECT mhash,merge FROM vmerge WHERE id=-4"); + while( db_step(&q)==SQLITE_ROW ){ + const char *zIntegrateUuid = db_column_text(&q, 0); + if( is_a_leaf(db_column_int(&q, 1)) ){ + fossil_print("Closed: %s\n", zIntegrateUuid); + }else{ + fossil_print("Not_Closed: %s (not a leaf any more)\n", zIntegrateUuid); + } + } + db_finalize(&q); + + fossil_print("New_Version: %s\n", zUuid); + if( outputManifest & MFESTFLG_UUID ){ + zManifestFile = mprintf("%smanifest.uuid", g.zLocalRoot); + blob_zero(&muuid); + blob_appendf(&muuid, "%s\n", zUuid); + blob_write_to_file(&muuid, zManifestFile); + free(zManifestFile); + blob_reset(&muuid); + } + /* Update the vfile and vmerge tables */ db_multi_exec( - "DELETE FROM vfile WHERE (vid!=%d OR deleted) AND file_is_selected(id);" - "DELETE FROM vmerge WHERE file_is_selected(id) OR id=0;" + "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 file_is_selected(id);" + "UPDATE vfile SET rid=mrid, mhash=NULL, chnged=0, deleted=0, origname=NULL" + " WHERE is_selected(id);" , vid, nvid ); - db_lset_int("checkout", nvid); - - /* Verify that the repository checksum matches the expected checksum - ** calculated before the checkin started (and stored as the R record - ** of the manifest file). - */ - vfile_aggregate_checksum_repository(nvid, &cksum2); - if( blob_compare(&cksum1, &cksum2) ){ - fossil_panic("tree checksum does not match repository after commit"); - } - - /* Verify that the manifest checksum matches the expected checksum */ - vfile_aggregate_checksum_manifest(nvid, &cksum2, &cksum1b); - if( blob_compare(&cksum1, &cksum1b) ){ - fossil_panic("manifest checksum does not agree with manifest: " - "%b versus %b", &cksum1, &cksum1b); - } - if( blob_compare(&cksum1, &cksum2) ){ - fossil_panic("tree checksum does not match manifest after commit: " - "%b versus %b", &cksum1, &cksum2); - } - - /* Verify that the commit did not modify any disk images. */ - vfile_aggregate_checksum_disk(nvid, &cksum2); - if( blob_compare(&cksum1, &cksum2) ){ - fossil_panic("tree checksums before and after commit do not match"); + db_set_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); + manifest_destroy(pManifest); + + if( useCksum ){ + /* Verify that the repository checksum matches the expected checksum + ** calculated before the check-in started (and stored as the R record + ** of the manifest file). + */ + vfile_aggregate_checksum_repository(nvid, &cksum2); + if( blob_compare(&cksum1, &cksum2) ){ + vfile_compare_repository_to_disk(nvid); + fossil_fatal("working checkout does not match what would have ended " + "up in the repository: %b versus %b", + &cksum1, &cksum2); + } + + /* Verify that the manifest checksum matches the expected checksum */ + vfile_aggregate_checksum_manifest(nvid, &cksum2, &cksum1b); + if( blob_compare(&cksum1, &cksum1b) ){ + fossil_fatal("manifest checksum self-test failed: " + "%b versus %b", &cksum1, &cksum1b); + } + if( blob_compare(&cksum1, &cksum2) ){ + fossil_fatal( + "working checkout does not match manifest after commit: " + "%b versus %b", &cksum1, &cksum2); + } + + /* Verify that the commit did not modify any disk images. */ + vfile_aggregate_checksum_disk(nvid, &cksum2); + if( blob_compare(&cksum1, &cksum2) ){ + fossil_fatal("working checkout before and after commit does not match"); + } } /* Clear the undo/redo stack */ undo_reset(); /* Commit */ db_multi_exec("DELETE FROM vvar WHERE name='ci-comment'"); + db_multi_exec("PRAGMA repository.application_id=252006673;"); + db_multi_exec("PRAGMA localdb.application_id=252006674;"); + if( dryRunFlag ){ + db_end_transaction(1); + return; + } db_end_transaction(0); + + if( outputManifest & MFESTFLG_TAGS ){ + Blob tagslist; + zManifestFile = mprintf("%smanifest.tags", g.zLocalRoot); + blob_zero(&tagslist); + get_checkin_taglist(nvid, &tagslist); + blob_write_to_file(&tagslist, zManifestFile); + blob_reset(&tagslist); + free(zManifestFile); + } if( !g.markPrivate ){ - autosync(AUTOSYNC_PUSH); + int syncFlags = SYNC_PUSH | SYNC_PULL | SYNC_IFABLE; + int nTries = db_get_int("autosync-tries",1); + autosync_loop(syncFlags, nTries, 0); } if( count_nonbranch_children(vid)>1 ){ - printf("**** warning: a fork has occurred *****\n"); + fossil_print("**** warning: a fork has occurred *****\n"); + }else{ + leaf_ambiguity_warning(nvid,nvid); } } Index: src/checkout.c ================================================================== --- src/checkout.c +++ src/checkout.c @@ -26,154 +26,297 @@ ** Check to see if there is an existing checkout that has been ** modified. Return values: ** ** 0: There is an existing checkout but it is unmodified ** 1: There is a modified checkout - there are unsaved changes -** 2: There is no existing checkout */ -int unsaved_changes(void){ +int unsaved_changes(unsigned int cksigFlags){ int vid; db_must_be_within_tree(); vid = db_lget_int("checkout",0); - if( vid==0 ) return 2; - vfile_check_signature(vid, 1); + vfile_check_signature(vid, cksigFlags|CKSIG_ENOTFILE); return db_exists("SELECT 1 FROM vfile WHERE chnged" " OR coalesce(origname!=pathname,0)"); } /* ** Undo the current check-out. Unlink all files from the disk. ** Clear the VFILE table. +** +** Also delete any directory that becomes empty as a result of deleting +** files due to this operation, as long as that directory is not the +** current working directory and is not on the empty-dirs list. */ void uncheckout(int vid){ - if( vid==0 ) return; - vfile_unlink(vid); + char *zPwd; + if( vid<=0 ) return; + sqlite3_create_function(g.db, "dirname",1,SQLITE_UTF8,0, + file_dirname_sql_function, 0, 0); + sqlite3_create_function(g.db, "unlink",1,SQLITE_UTF8|SQLITE_DIRECTONLY,0, + file_delete_sql_function, 0, 0); + sqlite3_create_function(g.db, "rmdir", 1, SQLITE_UTF8|SQLITE_DIRECTONLY, 0, + file_rmdir_sql_function, 0, 0); + db_multi_exec( + "CREATE TEMP TABLE dir_to_delete(name TEXT %s PRIMARY KEY)WITHOUT ROWID", + filename_collation() + ); + db_multi_exec( + "INSERT OR IGNORE INTO dir_to_delete(name)" + " SELECT dirname(pathname) FROM vfile" + " WHERE vid=%d AND mrid>0", + vid + ); + do{ + db_multi_exec( + "INSERT OR IGNORE INTO dir_to_delete(name)" + " SELECT dirname(name) FROM dir_to_delete;" + ); + }while( db_changes() ); + db_multi_exec( + "SELECT unlink(%Q||pathname) FROM vfile" + " WHERE vid=%d AND mrid>0;", + g.zLocalRoot, vid + ); + ensure_empty_dirs_created(1); + zPwd = file_getcwd(0,0); + db_multi_exec( + "SELECT rmdir(%Q||name) FROM dir_to_delete" + " WHERE (%Q||name)<>%Q ORDER BY name DESC", + g.zLocalRoot, g.zLocalRoot, zPwd + ); + fossil_free(zPwd); db_multi_exec("DELETE FROM vfile WHERE vid=%d", vid); } /* -** Given the abbreviated UUID name of a version, load the content of that +** Given the abbreviated hash of a version, load the content of that ** version in the VFILE table. Return the VID for the version. ** ** If anything goes wrong, panic. */ -int load_vfile(const char *zName){ +int load_vfile(const char *zName, int forceMissingFlag){ Blob uuid; int vid; blob_init(&uuid, zName, -1); - if( name_to_uuid(&uuid, 1) ){ - fossil_panic(g.zErrMsg); + if( name_to_uuid(&uuid, 1, "ci") ){ + fossil_fatal("%s", g.zErrMsg); } vid = db_int(0, "SELECT rid FROM blob WHERE uuid=%B", &uuid); if( vid==0 ){ fossil_fatal("no such check-in: %s", g.argv[2]); } if( !is_a_version(vid) ){ - fossil_fatal("object [%.10s] is not a check-in", blob_str(&uuid)); - } - load_vfile_from_rid(vid); - return vid; -} - -/* -** Load a vfile from a record ID. -*/ -void load_vfile_from_rid(int vid){ - Blob manifest; - - if( db_exists("SELECT 1 FROM vfile WHERE vid=%d", vid) ){ - return; - } - content_get(vid, &manifest); - vfile_build(vid, &manifest); - blob_reset(&manifest); + fossil_fatal("object [%S] is not a check-in", blob_str(&uuid)); + } + if( load_vfile_from_rid(vid) && !forceMissingFlag ){ + fossil_fatal("missing content, unable to checkout"); + }; + return vid; } /* ** Set or clear the vfile.isexe flag for a file. */ static void set_or_clear_isexe(const char *zFilename, int vid, int onoff){ - db_multi_exec("UPDATE vfile SET isexe=%d WHERE vid=%d and pathname=%Q", - onoff, vid, zFilename); + static Stmt s; + db_static_prepare(&s, + "UPDATE vfile SET isexe=:isexe" + " WHERE vid=:vid AND pathname=:path AND isexe!=:isexe" + ); + db_bind_int(&s, ":isexe", onoff); + db_bind_int(&s, ":vid", vid); + db_bind_text(&s, ":path", zFilename); + db_step(&s); + db_reset(&s); +} + +/* +** Set or clear the execute permission bit (as appropriate) for all +** files in the current check-out, and replace files that have +** symlink bit with actual symlinks. +*/ +void checkout_set_all_exe(int vid){ + Blob filename; + int baseLen; + Manifest *pManifest; + ManifestFile *pFile; + + /* Check the EXE permission status of all files + */ + pManifest = manifest_get(vid, CFTYPE_MANIFEST, 0); + if( pManifest==0 ) return; + blob_zero(&filename); + blob_appendf(&filename, "%s", g.zLocalRoot); + baseLen = blob_size(&filename); + manifest_file_rewind(pManifest); + while( (pFile = manifest_file_next(pManifest, 0))!=0 ){ + int isExe; + blob_append(&filename, pFile->zName, -1); + isExe = pFile->zPerm && strstr(pFile->zPerm, "x"); + file_setexe(blob_str(&filename), isExe); + set_or_clear_isexe(pFile->zName, vid, isExe); + blob_resize(&filename, baseLen); + } + blob_reset(&filename); + manifest_destroy(pManifest); } + /* -** Read the manifest file given by vid out of the repository -** and store it in the root of the local check-out. +** If the "manifest" setting is true, then automatically generate +** files named "manifest" and "manifest.uuid" containing, respectively, +** the text of the manifest and the artifact ID of the manifest. +** If the manifest setting is set, but is not a boolean value, then treat +** each character as a flag to enable writing "manifest", "manifest.uuid" or +** "manifest.tags". */ void manifest_to_disk(int vid){ char *zManFile; Blob manifest; - Blob hash; - Blob filename; - int baseLen; - int i; - Manifest m; - - blob_zero(&manifest); - zManFile = mprintf("%smanifest", g.zLocalRoot); - content_get(vid, &manifest); - blob_write_to_file(&manifest, zManFile); - free(zManFile); - blob_zero(&hash); - sha1sum_blob(&manifest, &hash); - zManFile = mprintf("%smanifest.uuid", g.zLocalRoot); - blob_append(&hash, "\n", 1); - blob_write_to_file(&hash, zManFile); - free(zManFile); - blob_reset(&hash); - manifest_parse(&m, &manifest); - blob_zero(&filename); - blob_appendf(&filename, "%s/", g.zLocalRoot); - baseLen = blob_size(&filename); - for(i=0; i<m.nFile; i++){ - int isExe; - blob_append(&filename, m.aFile[i].zName, -1); - isExe = m.aFile[i].zPerm && strstr(m.aFile[i].zPerm, "x"); - file_setexe(blob_str(&filename), isExe); - set_or_clear_isexe(m.aFile[i].zName, vid, isExe); - blob_resize(&filename, baseLen); - } - blob_reset(&filename); - manifest_clear(&m); -} - -/* -** COMMAND: checkout -** -** Usage: %fossil checkout VERSION ?-f|--force? ?--keep? -** -** Check out a version specified on the command-line. This command -** will abort if there are edited files in the current checkout unless -** the --force option appears on the command-line. The --keep option + Blob taglist; + int flg; + + flg = db_get_manifest_setting(); + + if( flg & MFESTFLG_RAW ){ + blob_zero(&manifest); + content_get(vid, &manifest); + sterilize_manifest(&manifest, CFTYPE_MANIFEST); + zManFile = mprintf("%smanifest", g.zLocalRoot); + blob_write_to_file(&manifest, zManFile); + free(zManFile); + }else{ + if( !db_exists("SELECT 1 FROM vfile WHERE pathname='manifest'") ){ + zManFile = mprintf("%smanifest", g.zLocalRoot); + file_delete(zManFile); + free(zManFile); + } + } + if( flg & MFESTFLG_UUID ){ + Blob hash; + zManFile = mprintf("%smanifest.uuid", g.zLocalRoot); + blob_set_dynamic(&hash, rid_to_uuid(vid)); + blob_append(&hash, "\n", 1); + blob_write_to_file(&hash, zManFile); + free(zManFile); + blob_reset(&hash); + }else{ + if( !db_exists("SELECT 1 FROM vfile WHERE pathname='manifest.uuid'") ){ + zManFile = mprintf("%smanifest.uuid", g.zLocalRoot); + file_delete(zManFile); + free(zManFile); + } + } + if( flg & MFESTFLG_TAGS ){ + blob_zero(&taglist); + zManFile = mprintf("%smanifest.tags", g.zLocalRoot); + get_checkin_taglist(vid, &taglist); + blob_write_to_file(&taglist, zManFile); + free(zManFile); + blob_reset(&taglist); + }else{ + if( !db_exists("SELECT 1 FROM vfile WHERE pathname='manifest.tags'") ){ + zManFile = mprintf("%smanifest.tags", g.zLocalRoot); + file_delete(zManFile); + free(zManFile); + } + } +} + +/* +** Find the branch name and all symbolic tags for a particular check-in +** identified by "rid". +** +** The branch name is actually only extracted if this procedure is run +** from within a local check-out. And the branch name is not the branch +** name for "rid" but rather the branch name for the current check-out. +** It is unclear if the rid parameter is always the same as the current +** check-out. +*/ +void get_checkin_taglist(int rid, Blob *pOut){ + Stmt stmt; + char *zCurrent; + blob_reset(pOut); + zCurrent = db_text(0, "SELECT value FROM tagxref" + " WHERE rid=%d AND tagid=%d", rid, TAG_BRANCH); + blob_appendf(pOut, "branch %s\n", zCurrent); + db_prepare(&stmt, "SELECT substr(tagname, 5)" + " FROM tagxref, tag" + " WHERE tagxref.rid=%d" + " AND tagxref.tagtype>0" + " AND tag.tagid=tagxref.tagid" + " AND tag.tagname GLOB 'sym-*'", rid); + while( db_step(&stmt)==SQLITE_ROW ){ + const char *zName; + zName = db_column_text(&stmt, 0); + blob_appendf(pOut, "tag %s\n", zName); + } + db_reset(&stmt); + db_finalize(&stmt); +} + + +/* +** COMMAND: checkout* +** COMMAND: co* +** +** Usage: %fossil checkout ?VERSION | --latest? ?OPTIONS? +** or: %fossil co ?VERSION | --latest? ?OPTIONS? +** +** NOTE: Most people use "fossil update" instead of "fossil checkout" for +** day-to-day operations. If you are new to Fossil and trying to learn your +** way around, it is recommended that you become familiar with the +** "fossil update" command first. +** +** This command changes the current check-out to the version specified +** as an argument. The command aborts if there are edited files in the +** current checkout unless the --force option is used. The --keep option ** leaves files on disk unchanged, except the manifest and manifest.uuid ** files. ** ** The --latest flag can be used in place of VERSION to checkout the ** latest version in the repository. ** -** See also the "update" command. +** Options: +** --force Ignore edited files in the current checkout +** --keep Only update the manifest and manifest.uuid files +** --force-missing Force checkout even if content is missing +** --setmtime Set timestamps of all files to match their SCM-side +** times (the timestamp of the last checkin which modified +** them) +** +** See also: [[update]] */ void checkout_cmd(void){ int forceFlag; /* Force checkout even if edits exist */ + int forceMissingFlag; /* Force checkout even if missing content */ int keepFlag; /* Do not change any files on disk */ int latestFlag; /* Checkout the latest version */ char *zVers; /* Version to checkout */ + int promptFlag; /* True to prompt before overwriting */ int vid, prior; + int setmtimeFlag; /* --setmtime. Set mtimes on files */ Blob cksum1, cksum1b, cksum2; - + db_must_be_within_tree(); db_begin_transaction(); forceFlag = find_option("force","f",0)!=0; + forceMissingFlag = find_option("force-missing",0,0)!=0; keepFlag = find_option("keep",0,0)!=0; latestFlag = find_option("latest",0,0)!=0; + promptFlag = find_option("prompt",0,0)!=0 || forceFlag==0; + setmtimeFlag = find_option("setmtime",0,0)!=0; + + /* We should be done with options.. */ + verify_all_options(); + if( (latestFlag!=0 && g.argc!=2) || (latestFlag==0 && g.argc!=3) ){ usage("VERSION|--latest ?--force? ?--keep?"); } - if( !forceFlag && unsaved_changes()==1 ){ + if( !forceFlag && unsaved_changes(0) ){ fossil_fatal("there are unsaved changes in the current checkout"); } if( forceFlag ){ db_multi_exec("DELETE FROM vfile"); prior = 0; @@ -189,74 +332,99 @@ zVers = db_text(0, "SELECT uuid FROM event, blob" " WHERE event.objid=blob.rid AND event.type='ci'" " ORDER BY event.mtime DESC"); } if( zVers==0 ){ - fossil_fatal("cannot locate \"latest\" checkout"); + db_end_transaction(0); + return; } }else{ zVers = g.argv[2]; } - vid = load_vfile(zVers); + vid = load_vfile(zVers, forceMissingFlag); if( prior==vid ){ + if( setmtimeFlag ) vfile_check_signature(vid, CKSIG_SETMTIME); + db_end_transaction(0); return; } if( !keepFlag ){ uncheckout(prior); } db_multi_exec("DELETE FROM vfile WHERE vid!=%d", vid); if( !keepFlag ){ - vfile_to_disk(vid, 0, 1); + vfile_to_disk(vid, 0, !g.fQuiet, promptFlag); } + checkout_set_all_exe(vid); manifest_to_disk(vid); - db_lset_int("checkout", vid); + ensure_empty_dirs_created(0); + db_set_checkout(vid); undo_reset(); db_multi_exec("DELETE FROM vmerge"); - if( !keepFlag ){ + if( !keepFlag && db_get_boolean("repo-cksum",1) ){ vfile_aggregate_checksum_manifest(vid, &cksum1, &cksum1b); vfile_aggregate_checksum_disk(vid, &cksum2); if( blob_compare(&cksum1, &cksum2) ){ - printf("WARNING: manifest checksum does not agree with disk\n"); + fossil_print("WARNING: manifest checksum does not agree with disk\n"); } - if( blob_compare(&cksum1, &cksum1b) ){ - printf("WARNING: manifest checksum does not agree with manifest\n"); + if( blob_size(&cksum1b) && blob_compare(&cksum1, &cksum1b) ){ + fossil_print("WARNING: manifest checksum does not agree with manifest\n"); } } + if( setmtimeFlag ) vfile_check_signature(vid, CKSIG_SETMTIME); db_end_transaction(0); } /* ** Unlink the local database file */ -void unlink_local_database(void){ - static const char *azFile[] = { - "%s_FOSSIL_", - "%s_FOSSIL_-journal", - "%s.fos", - "%s.fos-journal", - }; +static void unlink_local_database(int manifestOnly){ + const char *zReserved; int i; - for(i=0; i<sizeof(azFile)/sizeof(azFile[0]); i++){ - char *z = mprintf(azFile[i], g.zLocalRoot); - unlink(z); - free(z); + for(i=0; (zReserved = fossil_reserved_name(i, 1))!=0; i++){ + if( manifestOnly==0 || zReserved[0]=='m' ){ + char *z; + z = mprintf("%s%s", g.zLocalRoot, zReserved); + file_delete(z); + free(z); + } } } /* -** COMMAND: close +** COMMAND: close* +** +** Usage: %fossil close ?OPTIONS? +** +** The opposite of "[[open]]". Close the current database connection. +** Require a -f or --force flag if there are unsaved changes in the +** current check-out or if there is non-empty stash. ** -** Usage: %fossil close ?-f|--force? +** Options: +** -f|--force necessary to close a check out with uncommitted changes ** -** The opposite of "open". Close the current database connection. -** Require a -f or --force flag if there are unsaved changed in the -** current check-out. +** See also: [[open]] */ void close_cmd(void){ int forceFlag = find_option("force","f",0)!=0; db_must_be_within_tree(); - if( !forceFlag && unsaved_changes()==1 ){ + + /* We should be done with options.. */ + verify_all_options(); + + if( !forceFlag && unsaved_changes(0) ){ fossil_fatal("there are unsaved changes in the current checkout"); } - db_close(); - unlink_local_database(); + if( !forceFlag + && db_table_exists("localdb","stash") + && db_exists("SELECT 1 FROM localdb.stash") + ){ + fossil_fatal("closing the checkout will delete your stash"); + } + if( db_is_writeable("repository") ){ + char *zUnset = mprintf("ckout:%q", g.zLocalRoot); + db_unset(zUnset, 1); + fossil_free(zUnset); + } + unlink_local_database(1); + db_close(1); + unlink_local_database(0); } ADDED src/ci_edit.js Index: src/ci_edit.js ================================================================== --- /dev/null +++ src/ci_edit.js @@ -0,0 +1,34 @@ +/* Javascript used to make the check-in edit screen more interactive. +*/ +function chgcbn(){ + var newbr = document.getElementById('newbr'); + var brname = document.getElementById('brname'); + var checked = newbr.checked; + var x = brname.value.trim(); + if( !x || !newbr.checked ) x = newbr.getAttribute('data-branch'); + if( newbr.checked ) brname.select(); + document.getElementById('hbranch').textContent = x; + cidbrid = document.getElementById('cbranch'); + if( cidbrid ) cidbrid.textContent = x; +} +function chgbn(){ + var newbr = document.getElementById('newbr'); + var brname = document.getElementById('brname'); + var x = brname.value.trim(); + var br = newbr.getAttribute('data-branch'); + if( !x ) x = br; + newbr.checked = (x!=br); + document.getElementById('hbranch').textContent = x; + cidbrid = document.getElementById('cbranch'); + if( cidbrid ) cidbrid.textContent = x; +} +function chgtn(){ + var newtag = document.getElementById('newtag'); + var tagname = document.getElementById('tagname'); + newtag.checked=!!tagname.value; +} +(function(){ + document.getElementById('newbr').onchange = chgcbn; + document.getElementById('brname').onkeyup = chgbn; + document.getElementById('tagname').onkeyup = chgtn; +}()); Index: src/clearsign.c ================================================================== --- src/clearsign.c +++ src/clearsign.c @@ -39,24 +39,24 @@ zRand = db_text(0, "SELECT hex(randomblob(10))"); zOut = mprintf("out-%s", zRand); zIn = mprintf("in-%z", zRand); blob_write_to_file(pIn, zOut); zCmd = mprintf("%s %s %s", zBase, zIn, zOut); - rc = portable_system(zCmd); + rc = fossil_system(zCmd); free(zCmd); if( rc==0 ){ if( pOut==pIn ){ blob_reset(pIn); } blob_zero(pOut); - blob_read_from_file(pOut, zIn); + blob_read_from_file(pOut, zIn, ExtFILE); }else{ if( pOut!=pIn ){ blob_copy(pOut, pIn); } } - unlink(zOut); - unlink(zIn); + file_delete(zOut); + file_delete(zIn); free(zOut); free(zIn); return rc; } Index: src/clone.c ================================================================== --- src/clone.c +++ src/clone.c @@ -19,95 +19,405 @@ */ #include "config.h" #include "clone.h" #include <assert.h> - - -/* -** COMMAND: clone -** -** Usage: %fossil clone ?OPTIONS? URL FILENAME -** -** Make a clone of a repository specified by URL in the local -** file named FILENAME. -** -** By default, your current login name is used to create the default -** admin user. This can be overridden using the -A|--admin-user -** parameter. +/* +** If there are public BLOBs that deltas from private BLOBs, then +** undeltify the public BLOBs so that the private BLOBs may be safely +** deleted. +*/ +void fix_private_blob_dependencies(int showWarning){ + Bag toUndelta; + Stmt q; + int rid; + + /* Careful: We are about to delete all BLOB entries that are private. + ** So make sure that any no public BLOBs are deltas from a private BLOB. + ** Otherwise after the deletion, we won't be able to recreate the public + ** BLOBs. + */ + db_prepare(&q, + "SELECT " + " rid, (SELECT uuid FROM blob WHERE rid=delta.rid)," + " srcid, (SELECT uuid FROM blob WHERE rid=delta.srcid)" + " FROM delta" + " WHERE srcid in private AND rid NOT IN private" + ); + bag_init(&toUndelta); + while( db_step(&q)==SQLITE_ROW ){ + int rid = db_column_int(&q, 0); + const char *zId = db_column_text(&q, 1); + int srcid = db_column_int(&q, 2); + const char *zSrc = db_column_text(&q, 3); + if( showWarning ){ + fossil_warning( + "public artifact %S (%d) is a delta from private artifact %S (%d)", + zId, rid, zSrc, srcid + ); + } + bag_insert(&toUndelta, rid); + } + db_finalize(&q); + while( (rid = bag_first(&toUndelta))>0 ){ + content_undelta(rid); + bag_remove(&toUndelta, rid); + } + bag_clear(&toUndelta); +} + +/* +** Delete all private content from a repository. +*/ +void delete_private_content(void){ + fix_private_blob_dependencies(1); + db_multi_exec( + "DELETE FROM blob WHERE rid IN private;" + "DELETE FROM delta WHERE rid IN private;" + "DELETE FROM private;" + "DROP TABLE IF EXISTS modreq;" + ); +} + + +/* +** COMMAND: clone +** +** Usage: %fossil clone ?OPTIONS? URI ?FILENAME? +** +** Make a clone of a repository specified by URI in the local +** file named FILENAME. If FILENAME is omitted, then an appropriate +** filename is deduced from last element of the path in the URL. +** +** URI may be one of the following forms ([...] denotes optional elements): +** +** * HTTP/HTTPS protocol: +** +** http[s]://[userid[:password]@]host[:port][/path] +** +** * SSH protocol: +** +** ssh://[userid@]host[:port]/path/to/repo.fossil[?fossil=path/fossil.exe] +** +** * Filesystem: +** +** [file://]path/to/repo.fossil +** +** For ssh and filesystem, path must have an extra leading +** '/' to use an absolute path. +** +** Use %HH escapes for special characters in the userid and +** password. For example "%40" in place of "@", "%2f" in place +** of "/", and "%3a" in place of ":". +** +** Note that in Fossil (in contrast to some other DVCSes) a repository +** is distinct from a checkout. Cloning a repository is not the same thing +** as opening a repository. This command always clones the repository. This +** command might also open the repository, but only if the --no-open option +** is omitted and either the --workdir option is included or the FILENAME +** argument is omitted. Use the separate [[open]] command to open a +** repository that was previously cloned and already exists on the +** local machine. +** +** By default, the current login name is used to create the default +** admin user for the new clone. This can be overridden using +** the -A|--admin-user parameter. ** ** Options: -** -** --admin-user|-A USERNAME +** -A|--admin-user USERNAME Make USERNAME the administrator +** -B|--httpauth USER:PASS Add HTTP Basic Authorization to requests +** --nested Allow opening a repository inside an opened +** checkout +** --nocompress Omit extra delta compression +** --no-open Clone only. Do not open a check-out. +** --once Don't remember the URI. +** --private Also clone private branches +** --save-http-password Remember the HTTP password without asking +** --ssh-command|-c SSH Use SSH as the "ssh" command +** --ssl-identity FILENAME Use the SSL identity if requested by the server +** -u|--unversioned Also sync unversioned content +** -v|--verbose Show more statistics in output +** --workdir DIR Also open a checkout in DIR ** +** See also: [[init]], [[open]] */ void clone_cmd(void){ char *zPassword; const char *zDefaultUser; /* Optional name of the default user */ - - url_proxy_options(); - if( g.argc < 4 ){ - usage("?OPTIONS? FILE-OR-URL NEW-REPOSITORY"); - } - db_open_config(0); - if( file_size(g.argv[3])>0 ){ - fossil_panic("file already exists: %s", g.argv[3]); - } - + const char *zHttpAuth; /* HTTP Authorization user:pass information */ + int nErr = 0; + int urlFlags = URL_PROMPT_PW | URL_REMEMBER; + int syncFlags = SYNC_CLONE; + int noCompress = find_option("nocompress",0,0)!=0; + int noOpen = find_option("no-open",0,0)!=0; + int allowNested = find_option("nested",0,0)!=0; /* Used by open */ + const char *zRepo = 0; /* Name of the new local repository file */ + const char *zWorkDir = 0; /* Open in this directory, if not zero */ + + + /* Also clone private branches */ + if( find_option("private",0,0)!=0 ) syncFlags |= SYNC_PRIVATE; + if( find_option("once",0,0)!=0) urlFlags &= ~URL_REMEMBER; + if( find_option("save-http-password",0,0)!=0 ){ + urlFlags &= ~URL_PROMPT_PW; + urlFlags |= URL_REMEMBER_PW; + } + if( find_option("verbose","v",0)!=0) syncFlags |= SYNC_VERBOSE; + if( find_option("unversioned","u",0)!=0 ) syncFlags |= SYNC_UNVERSIONED; + zHttpAuth = find_option("httpauth","B",1); zDefaultUser = find_option("admin-user","A",1); - - url_parse(g.argv[2]); - if( g.urlIsFile ){ - file_copy(g.urlName, g.argv[3]); - db_close(); - db_open_repository(g.argv[3]); - db_record_repository_filename(g.argv[3]); - db_multi_exec( - "REPLACE INTO config(name,value)" - " VALUES('server-code', lower(hex(randomblob(20))));" - "REPLACE INTO config(name,value)" - " VALUES('last-sync-url', '%q');", - g.urlCanonical - ); - db_multi_exec( - "DELETE FROM blob WHERE rid IN private;" - "DELETE FROM delta wHERE rid IN private;" - "DELETE FROM private;" - ); + zWorkDir = find_option("workdir", 0, 1); + clone_ssh_find_options(); + url_proxy_options(); + + /* We should be done with options.. */ + verify_all_options(); + + if( g.argc < 3 ){ + usage("?OPTIONS? FILE-OR-URL ?NEW-REPOSITORY?"); + } + db_open_config(0, 0); + if( g.argc==4 ){ + zRepo = g.argv[3]; + }else{ + char *zBase = url_to_repo_basename(g.argv[2]); + if( zBase==0 ){ + fossil_fatal( + "unable to guess a repository name from the url \"%s\".\n" + "give the repository filename as an additional argument.", + g.argv[2]); + } + zRepo = mprintf("./%s.fossil", zBase); + if( zWorkDir==0 ){ + zWorkDir = mprintf("./%s", zBase); + } + fossil_free(zBase); + } + if( -1 != file_size(zRepo, ExtFILE) ){ + fossil_fatal("file already exists: %s", zRepo); + } + /* Fail before clone if open will fail because inside an open checkout */ + if( zWorkDir!=0 && zWorkDir[0]!=0 && !noOpen ){ + if( db_open_local_v2(0, allowNested) ){ + fossil_fatal("there is already an open tree at %s", g.zLocalRoot); + } + } + url_parse(g.argv[2], urlFlags); + if( zDefaultUser==0 && g.url.user!=0 ) zDefaultUser = g.url.user; + if( g.url.isFile ){ + file_copy(g.url.name, zRepo); + db_close(1); + db_open_repository(zRepo); + db_open_config(1,0); + db_record_repository_filename(zRepo); + url_remember(); + if( !(syncFlags & SYNC_PRIVATE) ) delete_private_content(); shun_artifacts(); - g.zLogin = db_text(0, "SELECT login FROM user WHERE cap LIKE '%%s%%'"); - if( g.zLogin==0 ){ - db_create_default_users(1,zDefaultUser); + db_create_default_users(1, zDefaultUser); + if( zDefaultUser ){ + g.zLogin = zDefaultUser; + }else{ + g.zLogin = db_text(0, "SELECT login FROM user WHERE cap LIKE '%%s%%'"); } - printf("Repository cloned into %s\n", g.argv[3]); + fossil_print("Repository cloned into %s\n", zRepo); }else{ - db_create_repository(g.argv[3]); - db_open_repository(g.argv[3]); + db_close_config(); + db_create_repository(zRepo); + db_open_repository(zRepo); + db_open_config(0,0); db_begin_transaction(); - db_record_repository_filename(g.argv[3]); - db_initial_setup(0, zDefaultUser, 0); + db_record_repository_filename(zRepo); + db_initial_setup(0, 0, zDefaultUser); user_select(); db_set("content-schema", CONTENT_SCHEMA, 0); - db_set("aux-schema", AUX_SCHEMA, 0); - db_set("last-sync-url", g.argv[2], 0); + db_set("aux-schema", AUX_SCHEMA_MAX, 0); + db_set("rebuilt", get_version(), 0); + db_unset("hash-policy", 0); + remember_or_get_http_auth(zHttpAuth, urlFlags & URL_REMEMBER, g.argv[2]); + url_remember(); + if( g.zSSLIdentity!=0 ){ + /* If the --ssl-identity option was specified, store it as a setting */ + Blob fn; + blob_zero(&fn); + file_canonical_name(g.zSSLIdentity, &fn, 0); + db_unprotect(PROTECT_ALL); + db_set("ssl-identity", blob_str(&fn), 0); + db_protect_pop(); + blob_reset(&fn); + } + db_unprotect(PROTECT_CONFIG); db_multi_exec( - "REPLACE INTO config(name,value)" - " VALUES('server-code', lower(hex(randomblob(20))));" + "REPLACE INTO config(name,value,mtime)" + " VALUES('server-code', lower(hex(randomblob(20))), now());" + "DELETE FROM config WHERE name='project-code';" ); + db_protect_pop(); url_enable_proxy(0); + clone_ssh_db_set_options(); + url_get_password_if_needed(); g.xlinkClusterOnly = 1; - client_sync(0,0,1,CONFIGSET_ALL,0); + nErr = client_sync(syncFlags,CONFIGSET_ALL,0,0); g.xlinkClusterOnly = 0; verify_cancel(); db_end_transaction(0); - db_close(); - db_open_repository(g.argv[3]); + db_close(1); + if( nErr ){ + file_delete(zRepo); + fossil_fatal("server returned an error - clone aborted"); + } + db_open_repository(zRepo); } db_begin_transaction(); - printf("Rebuilding repository meta-data...\n"); - rebuild_db(0, 1); - printf("project-id: %s\n", db_get("project-code", 0)); - printf("server-id: %s\n", db_get("server-code", 0)); - zPassword = db_text(0, "SELECT pw FROM user WHERE login=%Q", g.zLogin); - printf("admin-user: %s (password is \"%s\")\n", g.zLogin, zPassword); + fossil_print("Rebuilding repository meta-data...\n"); + rebuild_db(0, 1, 0); + if( !noCompress ){ + fossil_print("Extra delta compression... "); fflush(stdout); + extra_deltification(); + fossil_print("\n"); + } db_end_transaction(0); + fossil_print("Vacuuming the database... "); fflush(stdout); + if( db_int(0, "PRAGMA page_count")>1000 + && db_int(0, "PRAGMA page_size")<8192 ){ + db_multi_exec("PRAGMA page_size=8192;"); + } + db_unprotect(PROTECT_ALL); + db_multi_exec("VACUUM"); + db_protect_pop(); + fossil_print("\nproject-id: %s\n", db_get("project-code", 0)); + fossil_print("server-id: %s\n", db_get("server-code", 0)); + zPassword = db_text(0, "SELECT pw FROM user WHERE login=%Q", g.zLogin); + fossil_print("admin-user: %s (password is \"%s\")\n", g.zLogin, zPassword); + if( zWorkDir!=0 && zWorkDir[0]!=0 && !noOpen ){ + Blob cmd; + fossil_print("opening the new %s repository in directory %s...\n", + zRepo, zWorkDir); + blob_init(&cmd, 0, 0); + blob_append_escaped_arg(&cmd, g.nameOfExe, 1); + blob_append(&cmd, " open ", -1); + blob_append_escaped_arg(&cmd, zRepo, 1); + blob_append(&cmd, " --workdir ", -1); + blob_append_escaped_arg(&cmd, zWorkDir, 1); + if( allowNested ){ + blob_append(&cmd, " --nested", -1); + } + fossil_system(blob_str(&cmd)); + blob_reset(&cmd); + } +} + +/* +** If user chooses to use HTTP Authentication over unencrypted HTTP, +** remember decision. Otherwise, if the URL is being changed and no +** preference has been indicated, err on the safe side and revert the +** decision. Set the global preference if the URL is not being changed. +*/ +void remember_or_get_http_auth( + const char *zHttpAuth, /* Credentials in the form "user:password" */ + int fRemember, /* True to remember credentials for later reuse */ + const char *zUrl /* URL for which these credentials apply */ +){ + char *zKey = mprintf("http-auth:%s", g.url.canonical); + if( zHttpAuth && zHttpAuth[0] ){ + g.zHttpAuth = mprintf("%s", zHttpAuth); + } + if( fRemember ){ + if( g.zHttpAuth && g.zHttpAuth[0] ){ + set_httpauth(g.zHttpAuth); + }else if( zUrl && zUrl[0] ){ + db_unset(zKey, 0); + }else{ + g.zHttpAuth = get_httpauth(); + } + }else if( g.zHttpAuth==0 && zUrl==0 ){ + g.zHttpAuth = get_httpauth(); + } + free(zKey); +} + +/* +** Get the HTTP Authorization preference from db. +*/ +char *get_httpauth(void){ + char *zKey = mprintf("http-auth:%s", g.url.canonical); + char * rc = unobscure(db_get(zKey, 0)); + free(zKey); + return rc; +} + +/* +** Set the HTTP Authorization preference in db. +*/ +void set_httpauth(const char *zHttpAuth){ + char *zKey = mprintf("http-auth:%s", g.url.canonical); + db_set(zKey, obscure(zHttpAuth), 0); + free(zKey); +} + +/* +** Look for SSH clone command line options and setup in globals. +*/ +void clone_ssh_find_options(void){ + const char *zSshCmd; /* SSH command string */ + + zSshCmd = find_option("ssh-command","c",1); + if( zSshCmd && zSshCmd[0] ){ + g.zSshCmd = mprintf("%s", zSshCmd); + } +} + +/* +** Set SSH options discovered in global variables (set from command line +** options). +*/ +void clone_ssh_db_set_options(void){ + if( g.zSshCmd && g.zSshCmd[0] ){ + db_set("ssh-command", g.zSshCmd, 0); + } +} + +/* +** WEBPAGE: download +** +** Provide a simple page that enables newbies to download the latest tarball or +** ZIP archive, and provides instructions on how to clone. +*/ +void download_page(void){ + login_check_credentials(); + style_header("Download Page"); + if( !g.perm.Zip ){ + @ <p>Bummer. You do not have permission to download. + if( g.zLogin==0 || g.zLogin[0]==0 ){ + @ Maybe it would work better if you + @ %z(href("%R/login"))logged in</a>. + }else{ + @ Contact the site administrator and ask them to give + @ you "Download Zip" privileges. + } + }else{ + const char *zDLTag = db_get("download-tag","trunk"); + const char *zNm = db_get("short-project-name","download"); + char *zUrl = href("%R/zip/%t/%t.zip", zDLTag, zNm); + @ <p>ZIP Archive: %z(zUrl)%h(zNm).zip</a> + zUrl = href("%R/tarball/%t/%t.tar.gz", zDLTag, zNm); + @ <p>Tarball: %z(zUrl)%h(zNm).tar.gz</a> + zUrl = href("%R/sqlar/%t/%t.sqlar", zDLTag, zNm); + @ <p>SQLite Archive: %z(zUrl)%h(zNm).sqlar</a> + } + if( !g.perm.Clone ){ + @ <p>You are not authorized to clone this repository. + if( g.zLogin==0 || g.zLogin[0]==0 ){ + @ Maybe you would be able to clone if you + @ %z(href("%R/login"))logged in</a>. + }else{ + @ Contact the site administrator and ask them to give + @ you "Clone" privileges in order to clone. + } + }else{ + const char *zNm = db_get("short-project-name","clone"); + @ <p>Clone the repository using this command: + @ <blockquote><pre> + @ fossil clone %s(g.zBaseURL) %h(zNm).fossil + @ </pre></blockquote> + } + style_finish_page(); } ADDED src/codecheck1.c Index: src/codecheck1.c ================================================================== --- /dev/null +++ src/codecheck1.c @@ -0,0 +1,661 @@ +/* +** 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> + +/* +** Debugging switch +*/ +static int eVerbose = 0; + +/* +** 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; + } + if( z[0]=='\\' && (z[1]=='\n' || (z[1]=='\r' && z[2]=='\n')) ){ + *pType = TK_SPACE; + return 1; + } + *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; +} + +/* +** Remove excess whitespace and nested "()" from string z. +*/ +static char *simplify_expr(char *z){ + int n = (int)strlen(z); + while( n>0 ){ + if( isspace(z[0]) ){ + z++; + n--; + continue; + } + if( z[0]=='(' && z[n-1]==')' ){ + z++; + n -= 2; + continue; + } + break; + } + z[n] = 0; + 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); + if( strcmp(z, "NULL")==0 ) return 1; + 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", + "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_sql_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; +} + +/* +** Return true if the input is an argument that is never safe for use +** with %s. +*/ +static int never_safe(const char *z){ + if( strstr(z,"/*safe-for-%s*/")!=0 ) return 0; + if( z[0]=='P' ){ + if( strncmp(z,"PIF(",4)==0 ) return 0; + if( strncmp(z,"PCK(",4)==0 ) return 0; + return 1; + } + if( strncmp(z,"cgi_param",9)==0 ) return 1; + return 0; +} + +/* +** Processing flags +*/ +#define FMT_SQL 0x00001 /* Generator for SQL text */ +#define FMT_HTML 0x00002 /* Generator for HTML text */ +#define FMT_URL 0x00004 /* Generator for URLs */ +#define FMT_SAFE 0x00008 /* Generator for human-readable text */ + +/* +** A list of internal Fossil interfaces that take a printf-style format +** string. +*/ +struct FmtFunc { + const char *zFName; /* Name of the function */ + int iFmtArg; /* Index of format argument. Leftmost is 1. */ + unsigned fmtFlags; /* Processing flags */ +} aFmtFunc[] = { + { "admin_log", 1, FMT_SAFE }, + { "audit_append", 3, FMT_SAFE }, + { "backofficeTrace", 1, FMT_SAFE }, + { "blob_append_sql", 2, FMT_SQL }, + { "blob_appendf", 2, FMT_SAFE }, + { "cgi_debug", 1, FMT_SAFE }, + { "cgi_panic", 1, FMT_SAFE }, + { "cgi_printf", 1, FMT_HTML }, + { "cgi_printf_header", 1, FMT_HTML }, + { "cgi_redirectf", 1, FMT_URL }, + { "chref", 2, FMT_URL }, + { "CX", 1, FMT_HTML }, + { "db_blob", 2, FMT_SQL }, + { "db_debug", 1, FMT_SQL }, + { "db_double", 2, FMT_SQL }, + { "db_err", 1, FMT_SAFE }, + { "db_exists", 1, FMT_SQL }, + { "db_get_mprintf", 2, FMT_SAFE }, + { "db_int", 2, FMT_SQL }, + { "db_int64", 2, FMT_SQL }, + { "db_multi_exec", 1, FMT_SQL }, + { "db_optional_sql", 2, FMT_SQL }, + { "db_prepare", 2, FMT_SQL }, + { "db_prepare_ignore_error", 2, FMT_SQL }, + { "db_set_mprintf", 3, FMT_SAFE }, + { "db_static_prepare", 2, FMT_SQL }, + { "db_text", 2, FMT_SQL }, + { "db_unset_mprintf", 2, FMT_SAFE }, + { "emailerError", 2, FMT_SAFE }, + { "fileedit_ajax_error", 2, FMT_SAFE }, + { "form_begin", 2, FMT_URL }, + { "fossil_error", 2, FMT_SAFE }, + { "fossil_errorlog", 1, FMT_SAFE }, + { "fossil_fatal", 1, FMT_SAFE }, + { "fossil_fatal_recursive", 1, FMT_SAFE }, + { "fossil_panic", 1, FMT_SAFE }, + { "fossil_print", 1, FMT_SAFE }, + { "fossil_trace", 1, FMT_SAFE }, + { "fossil_warning", 1, FMT_SAFE }, + { "href", 1, FMT_URL }, + { "json_new_string_f", 1, FMT_SAFE }, + { "json_set_err", 2, FMT_SAFE }, + { "json_warn", 2, FMT_SAFE }, + { "mprintf", 1, FMT_SAFE }, + { "pop3_print", 2, FMT_SAFE }, + { "smtp_send_line", 2, FMT_SAFE }, + { "smtp_server_send", 2, FMT_SAFE }, + { "socket_set_errmsg", 1, FMT_SAFE }, + { "ssl_set_errmsg", 1, FMT_SAFE }, + { "style_header", 1, FMT_HTML }, + { "style_set_current_page", 1, FMT_URL }, + { "style_submenu_element", 2, FMT_URL }, + { "style_submenu_sql", 3, FMT_SQL }, + { "webpage_error", 1, FMT_SAFE }, + { "xhref", 2, FMT_URL }, +}; + +/* +** Comparison function for two FmtFunc entries +*/ +static int fmtfunc_cmp(const void *pAA, const void *pBB){ + const struct FmtFunc *pA = (const struct FmtFunc*)pAA; + const struct FmtFunc *pB = (const struct FmtFunc*)pBB; + return strcmp(pA->zFName, pB->zFName); +} + +/* +** 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] ){ + char cEnd; + len = distance_to(z, ','); + cEnd = z[len]; + z[len] = 0; + azArg = safe_realloc((char*)azArg, (sizeof(azArg[0])+1)*(nArg+1)); + azArg[nArg++] = simplify_expr(z); + if( cEnd==0 ) break; + 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 on arg[%d]\n", + zFilename, lnFCall, szFName, zFCall, fmtArg-1); + 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_SAFE)==0 ){ + for(i=0; i<nArg && i<k; i++){ + if( (acType[i]=='s' || acType[i]=='z' || acType[i]=='b') ){ + const char *zExpr = azArg[fmtArg+i]; + if( never_safe(zExpr) ){ + printf("%s:%d: Argument %d to %.*s() is not safe for" + " a query parameter\n", + zFilename, lnFCall, i+fmtArg, szFName, zFCall); + nErr++; + + }else if( (fmtFlags & FMT_SQL)!=0 && !is_sql_safe(zExpr) ){ + 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]); + } + }else if( eVerbose>1 ){ + printf("%s:%d: %.*s() ok for %d arguments\n", + zFilename, lnFCall, szFName, zFCall, nArg); + } + free((char*)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 = 0; + int ePrev = 0; + int szPrev = 0; + int lnPrev = 0; + 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. +** +** The eVerbose global variable is incremented with each "-v" argument. +*/ +int main(int argc, char **argv){ + int i; + int nErr = 0; + qsort(aFmtFunc, sizeof(aFmtFunc)/sizeof(aFmtFunc[0]), + sizeof(aFmtFunc[0]), fmtfunc_cmp); + for(i=1; i<argc; i++){ + char *zFile; + if( strcmp(argv[i],"-v")==0 ){ + eVerbose++; + continue; + } + if( eVerbose>0 ) printf("Processing %s...\n", argv[i]); + zFile = read_file(argv[i]); + nErr += scan_file(argv[i], zFile); + free(zFile); + } + return nErr; +} ADDED src/color.c Index: src/color.c ================================================================== --- /dev/null +++ src/color.c @@ -0,0 +1,180 @@ +/* +** Copyright (c) 2007 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 code used to select colors based on branch and +** user names. +** +*/ +#include "config.h" +#include <string.h> +#include "color.h" + +/* +** Hash a string and use the hash to determine a background color. +** +** This value returned is in static space and is overwritten with +** each subsequent call. +*/ +char *hash_color(const char *z){ + int i; /* Loop counter */ + unsigned int h = 0; /* Hash on the branch name */ + int r, g, b; /* Values for red, green, and blue */ + int h1, h2, h3, h4; /* Elements of the hash value */ + int mx, mn; /* Components of HSV */ + static char zColor[10]; /* The resulting color */ + static int ix[3] = {0,0}; /* Color chooser parameters */ + + if( ix[0]==0 ){ + if( skin_detail_boolean("white-foreground") ){ + ix[0] = 0x50; + ix[1] = 0x20; + }else{ + ix[0] = 0xf8; + ix[1] = 0x20; + } + } + for(i=0; z[i]; i++ ){ + h = (h<<11) ^ (h<<1) ^ (h>>3) ^ z[i]; + } + h1 = h % 6; h /= 6; + h3 = h % 10; h /= 10; + h4 = h % 10; h /= 10; + mx = ix[0] - h3; + mn = mx - h4 - ix[1]; + h2 = (h%(mx - mn)) + mn; + switch( h1 ){ + case 0: r = mx; g = h2, b = mn; break; + case 1: r = h2; g = mx, b = mn; break; + case 2: r = mn; g = mx, b = h2; break; + case 3: r = mn; g = h2, b = mx; break; + case 4: r = h2; g = mn, b = mx; break; + default: r = mx; g = mn, b = h2; break; + } + sqlite3_snprintf(8, zColor, "#%02x%02x%02x", r,g,b); + return zColor; +} + +/* +** Determine a color for users based on their login string. +** +** SETTING: user-color-map width=40 block-text +** +** The user-color-map setting can be used to override user color choices. +** The setting is a list of space-separated words pairs. The first word +** of each pair is a login name. The second word is an alternative name +** used by the color chooser algorithm. +** +** This list is intended to be relatively short. The idea is to only use +** this map to resolve color collisions between common users. +** +** Visit /test-hash-color?rand for a list of suggested names for the +** second word of each pair in the list. +*/ +char *user_color(const char *zLogin){ + static int once = 0; + static int nMap = 0; + static char **azMap = 0; + static int *anMap = 0; + int i; + if( !once ){ + char *zMap = (char*)db_get("user-color-map",0); + once = 1; + if( zMap && zMap[0] ){ + if( !g.interp ) Th_FossilInit(0); + Th_SplitList(g.interp, zMap, (int)strlen(zMap), + &azMap, &anMap, &nMap); + for(i=0; i<nMap; i++) azMap[i][anMap[i]] = 0; + } + } + for(i=0; i<nMap-1; i+=2){ + if( strcmp(zLogin, azMap[i])==0 ) return hash_color(azMap[i+1]); + } + return hash_color(zLogin); +} + +/* +** COMMAND: test-hash-color +** +** Usage: %fossil test-hash-color TAG ... +** +** Print out the color names associated with each tag. Used for +** testing the hash_color() function. +*/ +void test_hash_color(void){ + int i; + for(i=2; i<g.argc; i++){ + fossil_print("%20s: %s\n", g.argv[i], hash_color(g.argv[i])); + } +} + +/* +** WEBPAGE: hash-color-test +** +** Print out the color names associated with each tag. Used for +** testing the hash_color() function. +*/ +void test_hash_color_page(void){ + const char *zBr; + char zNm[10]; + int i, cnt; + login_check_credentials(); + if( P("rand")!=0 ){ + int j; + for(i=0; i<10; i++){ + sqlite3_uint64 u; + char zClr[10]; + sqlite3_randomness(sizeof(u), &u); + cnt = 3+(u%2); + u /= 2; + for(j=0; j<cnt; j++){ + zClr[j] = 'a' + (u%26); + u /= 26; + } + zClr[j] = 0; + sqlite3_snprintf(sizeof(zNm),zNm,"b%d",i); + cgi_replace_parameter(fossil_strdup(zNm), fossil_strdup(zClr)); + } + } + style_set_current_feature("test"); + style_header("Hash Color Test"); + for(i=cnt=0; i<10; i++){ + sqlite3_snprintf(sizeof(zNm),zNm,"b%d",i); + zBr = P(zNm); + if( zBr && zBr[0] ){ + @ <p style='border:1px solid;background-color:%s(hash_color(zBr));'> + @ %h(zBr) - %s(hash_color(zBr)) - + @ Omnes nos quasi oves erravimus unusquisque in viam + @ suam declinavit.</p> + cnt++; + } + } + if( cnt ){ + @ <hr /> + } + @ <form method="POST"> + @ <p>Enter candidate branch names below and see them displayed in their + @ default background colors above.</p> + for(i=0; i<10; i++){ + sqlite3_snprintf(sizeof(zNm),zNm,"b%d",i); + zBr = P(zNm); + @ <input type="text" size="30" name='%s(zNm)' value='%h(PD(zNm,""))'><br /> + } + @ <input type="submit" value="Submit"> + @ <input type="submit" name="rand" value="Random"> + @ </form> + style_finish_page(); +} Index: src/comformat.c ================================================================== --- src/comformat.c +++ src/comformat.c @@ -2,11 +2,11 @@ ** Copyright (c) 2007 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: @@ -19,82 +19,617 @@ ** text on a TTY. */ #include "config.h" #include "comformat.h" #include <assert.h> + +#if INTERFACE +#define COMMENT_PRINT_NONE ((u32)0x00000000) /* No flags = non-legacy. */ +#define COMMENT_PRINT_LEGACY ((u32)0x00000001) /* Use legacy algorithm. */ +#define COMMENT_PRINT_TRIM_CRLF ((u32)0x00000002) /* Trim leading CR/LF. */ +#define COMMENT_PRINT_TRIM_SPACE ((u32)0x00000004) /* Trim leading/trailing. */ +#define COMMENT_PRINT_WORD_BREAK ((u32)0x00000008) /* Break lines on words. */ +#define COMMENT_PRINT_ORIG_BREAK ((u32)0x00000010) /* Break before original. */ +#define COMMENT_PRINT_DEFAULT (COMMENT_PRINT_LEGACY) /* Defaults. */ +#define COMMENT_PRINT_UNSET (-1) /* Not initialized. */ +#endif + +/* +** This is the previous value used by most external callers when they +** needed to specify a default maximum line length to be used with the +** comment_print() function. +*/ +#ifndef COMMENT_LEGACY_LINE_LENGTH +# define COMMENT_LEGACY_LINE_LENGTH (78) +#endif + +/* +** This is the number of spaces to print when a tab character is seen. +*/ +#ifndef COMMENT_TAB_WIDTH +# define COMMENT_TAB_WIDTH (8) +#endif + +/* +** This function sets the maximum number of characters to print per line +** based on the detected terminal line width, if available; otherwise, it +** uses the legacy default terminal line width minus the amount to indent. +** +** Zero is returned to indicate any failure. One is returned to indicate +** the successful detection of the terminal line width. Negative one is +** returned to indicate the terminal line width is using the hard-coded +** legacy default value. +*/ +static int comment_set_maxchars( + int indent, + int *pMaxChars +){ + struct TerminalSize ts; + if ( !terminal_get_size(&ts) ){ + return 0; + } + + if( ts.nColumns ){ + *pMaxChars = ts.nColumns - indent; + return 1; + }else{ + /* + ** Fallback to using more-or-less the "legacy semantics" of hard-coding + ** the maximum line length to a value reasonable for the vast majority + ** of supported systems. + */ + *pMaxChars = COMMENT_LEGACY_LINE_LENGTH - indent; + return -1; + } +} + +/* +** This function checks the current line being printed against the original +** comment text. Upon matching, it updates the provided character and line +** counts, if applicable. The caller needs to emit a new line, if desired. +*/ +static int comment_check_orig( + const char *zOrigText, /* [in] Original comment text ONLY, may be NULL. */ + const char *zLine, /* [in] The comment line to print. */ + int *pCharCnt, /* [in/out] Pointer to the line character count. */ + int *pLineCnt /* [in/out] Pointer to the total line count. */ +){ + if( zOrigText && fossil_strcmp(zLine, zOrigText)==0 ){ + if( pCharCnt ) *pCharCnt = 0; + if( pLineCnt ) (*pLineCnt)++; + return 1; + } + return 0; +} + +/* +** This function scans the specified comment line starting just after the +** initial index and returns the index of the next spacing character -OR- +** zero if such a character cannot be found. For the purposes of this +** algorithm, the NUL character is treated the same as a spacing character. +*/ +static int comment_next_space( + const char *zLine, /* [in] The comment line being printed. */ + int index, /* [in] The current character index being handled. */ + int *distUTF8 /* [out] Distance to next space in UTF-8 sequences. */ +){ + int nextIndex = index + 1; + int fNonASCII=0; + for(;;){ + char c = zLine[nextIndex]; + if( (c&0x80)==0x80 ) fNonASCII=1; + if( c==0 || fossil_isspace(c) ){ + if( distUTF8 ){ + if( fNonASCII!=0 ){ + *distUTF8 = strlen_utf8(&zLine[index], nextIndex-index); + }else{ + *distUTF8 = nextIndex-index; + } + } + return nextIndex; + } + nextIndex++; + } + return 0; /* NOT REACHED */ +} + +/* +** Count the number of UTF-8 sequences in a string. Incomplete, ill-formed and +** overlong sequences are counted as one sequence. The invalid lead bytes 0xC0 +** to 0xC1 and 0xF5 to 0xF7 are allowed to initiate (ill-formed) 2- and 4-byte +** sequences, respectively, the other invalid lead bytes 0xF8 to 0xFF are +** treated as invalid 1-byte sequences (as lone trail bytes). +** Combining characters and East Asian Wide and Fullwidth characters are counted +** as one, so this function does not calculate the effective "display width". +*/ +int strlen_utf8(const char *zString, int lengthBytes){ + int i; /* Counted bytes. */ + int lengthUTF8; /* Counted UTF-8 sequences. */ +#if 0 + assert( lengthBytes>=0 ); +#endif + for(i=0, lengthUTF8=0; i<lengthBytes; i++, lengthUTF8++){ + char c = zString[i]; + int cchUTF8=1; /* Code units consumed. */ + int maxUTF8=1; /* Expected sequence length. */ + if( (c&0xe0)==0xc0 )maxUTF8=2; /* UTF-8 lead byte 110vvvvv */ + else if( (c&0xf0)==0xe0 )maxUTF8=3; /* UTF-8 lead byte 1110vvvv */ + else if( (c&0xf8)==0xf0 )maxUTF8=4; /* UTF-8 lead byte 11110vvv */ + while( cchUTF8<maxUTF8 && + i<lengthBytes-1 && + (zString[i+1]&0xc0)==0x80 ){ /* UTF-8 trail byte 10vvvvvv */ + cchUTF8++; + i++; + } + } + return lengthUTF8; +} + +/* +** This function is called when printing a logical comment line to calculate +** the necessary indenting. The caller needs to emit the indenting spaces. +*/ +static void comment_calc_indent( + const char *zLine, /* [in] The comment line being printed. */ + int indent, /* [in] Number of spaces to indent, zero for none. */ + int trimCrLf, /* [in] Non-zero to trim leading/trailing CR/LF. */ + int trimSpace, /* [in] Non-zero to trim leading/trailing spaces. */ + int *piIndex /* [in/out] Pointer to first non-space character. */ +){ + if( zLine && piIndex ){ + int index = *piIndex; + if( trimCrLf ){ + while( zLine[index]=='\r' || zLine[index]=='\n' ){ index++; } + } + if( trimSpace ){ + while( fossil_isspace(zLine[index]) ){ index++; } + } + *piIndex = index; + } +} + +/* +** This function prints one logical line of a comment, stopping when it hits +** a new line -OR- runs out of space on the logical line. +*/ +static void comment_print_line( + const char *zOrigText, /* [in] Original comment text ONLY, may be NULL. */ + const char *zLine, /* [in] The comment line to print. */ + int origIndent, /* [in] Number of spaces to indent before the original + ** comment. */ + int indent, /* [in] Number of spaces to indent, before the line + ** to print. */ + int lineChars, /* [in] Maximum number of characters to print. */ + int trimCrLf, /* [in] Non-zero to trim leading/trailing CR/LF. */ + int trimSpace, /* [in] Non-zero to trim leading/trailing spaces. */ + int wordBreak, /* [in] Non-zero to try breaking on word boundaries. */ + int origBreak, /* [in] Non-zero to break before original comment. */ + int *pLineCnt, /* [in/out] Pointer to the total line count. */ + const char **pzLine /* [out] Pointer to the end of the logical line. */ +){ + int index = 0, charCnt = 0, lineCnt = 0, maxChars, i; + char zBuf[400]; int iBuf=0; /* Output buffer and counter. */ + int cchUTF8, maxUTF8; /* Helper variables to count UTF-8 sequences. */ + if( !zLine ) return; + if( lineChars<=0 ) return; +#if 0 + assert( indent<sizeof(zBuf)-5 ); /* See following comments to explain */ + assert( origIndent<sizeof(zBuf)-5 ); /* these limits. */ +#endif + if( indent>sizeof(zBuf)-6 ){ + /* Limit initial indent to fit output buffer. */ + indent = sizeof(zBuf)-6; + } + comment_calc_indent(zLine, indent, trimCrLf, trimSpace, &index); + if( indent>0 ){ + for(i=0; i<indent; i++){ + zBuf[iBuf++] = ' '; + } + } + if( origIndent>sizeof(zBuf)-6 ){ + /* Limit line indent to fit output buffer. */ + origIndent = sizeof(zBuf)-6; + } + maxChars = lineChars; + for(;;){ + int useChars = 1; + char c = zLine[index]; + /* Flush the output buffer if there's no space left for at least one more + ** (potentially 4-byte) UTF-8 sequence, one level of indentation spaces, + ** a new line, and a terminating NULL. */ + if( iBuf>sizeof(zBuf)-origIndent-6 ){ + zBuf[iBuf]=0; + iBuf=0; + fossil_print("%s", zBuf); + } + if( c==0 ){ + break; + }else{ + if( origBreak && index>0 ){ + const char *zCurrent = &zLine[index]; + if( comment_check_orig(zOrigText, zCurrent, &charCnt, &lineCnt) ){ + zBuf[iBuf++] = '\n'; + comment_calc_indent(zLine, origIndent, trimCrLf, trimSpace, &index); + for( i=0; i<origIndent; i++ ){ + zBuf[iBuf++] = ' '; + } + maxChars = lineChars; + } + } + index++; + } + if( c=='\n' ){ + lineCnt++; + charCnt = 0; + useChars = 0; + }else if( c=='\t' ){ + int distUTF8; + int nextIndex = comment_next_space(zLine, index, &distUTF8); + if( nextIndex<=0 || distUTF8>maxChars ){ + break; + } + charCnt++; + useChars = COMMENT_TAB_WIDTH; + if( maxChars<useChars ){ + zBuf[iBuf++] = ' '; + break; + } + }else if( wordBreak && fossil_isspace(c) ){ + int distUTF8; + int nextIndex = comment_next_space(zLine, index, &distUTF8); + if( nextIndex<=0 || distUTF8>maxChars ){ + break; + } + charCnt++; + }else{ + charCnt++; + } + assert( c!='\n' || charCnt==0 ); + zBuf[iBuf++] = c; + /* Skip over UTF-8 sequences, see comment on strlen_utf8() for details. */ + cchUTF8=1; /* Code units consumed. */ + maxUTF8=1; /* Expected sequence length. */ + if( (c&0xe0)==0xc0 )maxUTF8=2; /* UTF-8 lead byte 110vvvvv */ + else if( (c&0xf0)==0xe0 )maxUTF8=3; /* UTF-8 lead byte 1110vvvv */ + else if( (c&0xf8)==0xf0 )maxUTF8=4; /* UTF-8 lead byte 11110vvv */ + while( cchUTF8<maxUTF8 && + (zLine[index]&0xc0)==0x80 ){ /* UTF-8 trail byte 10vvvvvv */ + cchUTF8++; + zBuf[iBuf++] = zLine[index++]; + } + maxChars -= useChars; + if( maxChars<=0 ) break; + if( c=='\n' ) break; + } + if( charCnt>0 ){ + zBuf[iBuf++] = '\n'; + lineCnt++; + } + /* Flush the remaining output buffer. */ + if( iBuf>0 ){ + zBuf[iBuf]=0; + iBuf=0; + fossil_print("%s", zBuf); + } + if( pLineCnt ){ + *pLineCnt += lineCnt; + } + if( pzLine ){ + *pzLine = zLine + index; + } +} /* -** Given a comment string zText, format that string for printing -** on a TTY. Assume that the output cursors is indent spaces from -** the left margin and that a single line can contain no more than -** lineLength characters. Indent all subsequent lines by indent. -** -** lineLength must be less than 400. -** -** Return the number of newlines that are output. +** This is the legacy comment printing algorithm. It is being retained +** for backward compatibility. +** +** Given a comment string, format that string for printing on a TTY. +** Assume that the output cursors is indent spaces from the left margin +** and that a single line can contain no more than 'width' characters. +** Indent all subsequent lines by 'indent'. +** +** Returns the number of new lines emitted. */ -int comment_print(const char *zText, int indent, int lineLength){ - int tlen = lineLength - indent; - int si, sk, i, k; +static int comment_print_legacy( + const char *zText, /* The comment text to be printed. */ + int indent, /* Number of spaces to indent each non-initial line. */ + int width /* Maximum number of characters per line. */ +){ + int maxChars = width - indent; + int si, sk, i, k, kc; int doIndent = 0; - char zBuf[400]; - int lineCnt = 0; + char *zBuf; + char zBuffer[400]; + int lineCnt = 0; + int cchUTF8, maxUTF8; /* Helper variables to count UTF-8 sequences. */ + if( width<0 ){ + comment_set_maxchars(indent, &maxChars); + } + if( zText==0 ) zText = "(NULL)"; + if( maxChars<=0 ){ + maxChars = strlen(zText); + } + /* Ensure the buffer can hold the longest-possible UTF-8 sequences. */ + if( maxChars >= (sizeof(zBuffer)/4-1) ){ + zBuf = fossil_malloc(maxChars*4+1); + }else{ + zBuf = zBuffer; + } for(;;){ - while( isspace(zText[0]) ){ zText++; } + while( fossil_isspace(zText[0]) ){ zText++; } if( zText[0]==0 ){ if( doIndent==0 ){ - printf("\n"); + fossil_print("\n"); lineCnt = 1; } + if( zBuf!=zBuffer) fossil_free(zBuf); return lineCnt; } - for(sk=si=i=k=0; zText[i] && k<tlen; i++){ + for(sk=si=i=k=kc=0; zText[i] && kc<maxChars; i++){ char c = zText[i]; - if( isspace(c) ){ + kc++; /* Count complete UTF-8 sequences. */ + /* Skip over UTF-8 sequences, see comment on strlen_utf8() for details. */ + cchUTF8=1; /* Code units consumed. */ + maxUTF8=1; /* Expected sequence length. */ + if( (c&0xe0)==0xc0 )maxUTF8=2; /* UTF-8 lead byte 110vvvvv */ + else if( (c&0xf0)==0xe0 )maxUTF8=3; /* UTF-8 lead byte 1110vvvv */ + else if( (c&0xf8)==0xf0 )maxUTF8=4; /* UTF-8 lead byte 11110vvv */ + if( maxUTF8>1 ){ + zBuf[k++] = c; + while( cchUTF8<maxUTF8 && + (zText[i+1]&0xc0)==0x80 ){ /* UTF-8 trail byte 10vvvvvv */ + cchUTF8++; + zBuf[k++] = zText[++i]; + } + } + else if( fossil_isspace(c) ){ si = i; sk = k; if( k==0 || zBuf[k-1]!=' ' ){ zBuf[k++] = ' '; } }else{ zBuf[k] = c; - if( c=='-' && k>0 && isalpha(zBuf[k-1]) ){ + if( c=='-' && k>0 && fossil_isalpha(zBuf[k-1]) ){ si = i+1; sk = k+1; } k++; } } if( doIndent ){ - printf("%*s", indent, ""); + fossil_print("%*s", indent, ""); } doIndent = 1; if( sk>0 && zText[i] ){ zText += si; - zBuf[sk++] = '\n'; zBuf[sk] = 0; - printf("%s", zBuf); }else{ zText += i; - zBuf[k++] = '\n'; zBuf[k] = 0; - printf("%s", zBuf); } + fossil_print("%s\n", zBuf); + lineCnt++; + } +} + +/* +** This is the comment printing function. The comment printing algorithm +** contained within it attempts to preserve the formatting present within +** the comment string itself while honoring line width limitations. There +** are several flags that modify the default behavior of this function: +** +** COMMENT_PRINT_LEGACY: Forces use of the legacy comment printing +** algorithm. For backward compatibility, +** this is the default. +** +** COMMENT_PRINT_TRIM_CRLF: Trims leading and trailing carriage-returns +** and line-feeds where they do not materially +** impact pre-existing formatting (i.e. at the +** start of the comment string -AND- right +** before line indentation). This flag does +** not apply to the legacy comment printing +** algorithm. This flag may be combined with +** COMMENT_PRINT_TRIM_SPACE. +** +** COMMENT_PRINT_TRIM_SPACE: Trims leading and trailing spaces where they +** do not materially impact the pre-existing +** formatting (i.e. at the start of the comment +** string -AND- right before line indentation). +** This flag does not apply to the legacy +** comment printing algorithm. This flag may +** be combined with COMMENT_PRINT_TRIM_CRLF. +** +** COMMENT_PRINT_WORD_BREAK: Attempts to break lines on word boundaries +** while honoring the logical line length. +** If this flag is not specified, honoring the +** logical line length may result in breaking +** lines in the middle of words. This flag +** does not apply to the legacy comment +** printing algorithm. +** +** COMMENT_PRINT_ORIG_BREAK: Looks for the original comment text within +** the text being printed. Upon matching, a +** new line will be emitted, thus preserving +** more of the pre-existing formatting. +** +** Given a comment string, format that string for printing on a TTY. +** Assume that the output cursors is indent spaces from the left margin +** and that a single line can contain no more than 'width' characters. +** Indent all subsequent lines by 'indent'. +** +** Returns the number of new lines emitted. +*/ +int comment_print( + const char *zText, /* The comment text to be printed. */ + const char *zOrigText, /* Original comment text ONLY, may be NULL. */ + int indent, /* Spaces to indent each non-initial line. */ + int width, /* Maximum number of characters per line. */ + int flags /* Zero or more "COMMENT_PRINT_*" flags. */ +){ + int maxChars = width - indent; + int legacy = flags & COMMENT_PRINT_LEGACY; + int trimCrLf = flags & COMMENT_PRINT_TRIM_CRLF; + int trimSpace = flags & COMMENT_PRINT_TRIM_SPACE; + int wordBreak = flags & COMMENT_PRINT_WORD_BREAK; + int origBreak = flags & COMMENT_PRINT_ORIG_BREAK; + int lineCnt = 0; + const char *zLine; + + if( legacy ){ + return comment_print_legacy(zText, indent, width); + } + if( width<0 ){ + comment_set_maxchars(indent, &maxChars); + } + if( zText==0 ) zText = "(NULL)"; + if( maxChars<=0 ){ + maxChars = strlen(zText); + } + if( trimSpace ){ + while( fossil_isspace(zText[0]) ){ zText++; } + } + if( zText[0]==0 ){ + fossil_print("\n"); lineCnt++; + return lineCnt; + } + zLine = zText; + for(;;){ + comment_print_line(zOrigText, zLine, indent, zLine>zText ? indent : 0, + maxChars, trimCrLf, trimSpace, wordBreak, origBreak, + &lineCnt, &zLine); + if( !zLine || !zLine[0] ) break; + } + return lineCnt; +} + +/* +** Return the "COMMENT_PRINT_*" flags specified by the following sources, +** evaluated in the following cascading order: +** +** 1. The global --comfmtflags (alias --comment-format) command-line option. +** 2. The local (per-repository) "comment-format" setting. +** 3. The global (all-repositories) "comment-format" setting. +** 4. The default value COMMENT_PRINT_DEFAULT. +*/ +int get_comment_format(){ + int comFmtFlags; + /* The global command-line option is present, or the value has been cached. */ + if( g.comFmtFlags!=COMMENT_PRINT_UNSET ){ + comFmtFlags = g.comFmtFlags; + return comFmtFlags; + } + /* Load the local (per-repository) or global (all-repositories) value, and use + ** g.comFmtFlags as a cache. */ + comFmtFlags = db_get_int("comment-format", COMMENT_PRINT_UNSET); + if( comFmtFlags!=COMMENT_PRINT_UNSET ){ + g.comFmtFlags = comFmtFlags; + return comFmtFlags; } + /* Fallback to the default value. */ + comFmtFlags = COMMENT_PRINT_DEFAULT; + return comFmtFlags; } /* -** Test the comment printing ** ** COMMAND: test-comment-format +** +** Usage: %fossil test-comment-format ?OPTIONS? PREFIX TEXT ?ORIGTEXT? +** +** Test comment formatting and printing. Use for testing only. +** +** Options: +** --file The comment text is really just a file name to +** read it from. +** --decode Decode the text using the same method used when +** handling the value of a C-card from a manifest. +** --legacy Use the legacy comment printing algorithm. +** --trimcrlf Enable trimming of leading/trailing CR/LF. +** --trimspace Enable trimming of leading/trailing spaces. +** --wordbreak Attempt to break lines on word boundaries. +** --origbreak Attempt to break when the original comment text +** is detected. +** --indent Number of spaces to indent (default (-1) is to +** auto-detect). Zero means no indent. +** -W|--width NUM Width of lines (default (-1) is to auto-detect). +** Zero means no limit. */ void test_comment_format(void){ - int indent; - if( g.argc!=4 ){ - usage("PREFIX TEXT"); + const char *zWidth; + const char *zIndent; + const char *zPrefix; + char *zText; + char *zOrigText; + int indent, width; + int fromFile = find_option("file", 0, 0)!=0; + int decode = find_option("decode", 0, 0)!=0; + int flags = COMMENT_PRINT_NONE; + if( find_option("legacy", 0, 0) ){ + flags |= COMMENT_PRINT_LEGACY; + } + if( find_option("trimcrlf", 0, 0) ){ + flags |= COMMENT_PRINT_TRIM_CRLF; + } + if( find_option("trimspace", 0, 0) ){ + flags |= COMMENT_PRINT_TRIM_SPACE; + } + if( find_option("wordbreak", 0, 0) ){ + flags |= COMMENT_PRINT_WORD_BREAK; + } + if( find_option("origbreak", 0, 0) ){ + flags |= COMMENT_PRINT_ORIG_BREAK; + } + zWidth = find_option("width","W",1); + if( zWidth ){ + width = atoi(zWidth); + }else{ + width = -1; /* automatic */ + } + zIndent = find_option("indent",0,1); + if( zIndent ){ + indent = atoi(zIndent); + }else{ + indent = -1; /* automatic */ + } + if( g.argc!=4 && g.argc!=5 ){ + usage("?OPTIONS? PREFIX TEXT ?ORIGTEXT?"); + } + zPrefix = g.argv[2]; + zText = g.argv[3]; + if( g.argc==5 ){ + zOrigText = g.argv[4]; + }else{ + zOrigText = 0; + } + if( fromFile ){ + Blob fileData; + blob_read_from_file(&fileData, zText, ExtFILE); + zText = mprintf("%s", blob_str(&fileData)); + blob_reset(&fileData); + if( zOrigText ){ + blob_read_from_file(&fileData, zOrigText, ExtFILE); + zOrigText = mprintf("%s", blob_str(&fileData)); + blob_reset(&fileData); + } + } + if( decode ){ + zText = mprintf(fromFile?"%z":"%s" /*works-like:"%s"*/, zText); + defossilize(zText); + if( zOrigText ){ + zOrigText = mprintf(fromFile?"%z":"%s" /*works-like:"%s"*/, zOrigText); + defossilize(zOrigText); + } + } + if( indent<0 ){ + indent = strlen(zPrefix); + } + if( zPrefix && *zPrefix ){ + fossil_print("%s", zPrefix); } - indent = strlen(g.argv[2]) + 1; - printf("%s ", g.argv[2]); - printf("(%d lines output)\n", comment_print(g.argv[3], indent, 79)); + fossil_print("(%d lines output)\n", + comment_print(zText, zOrigText, indent, width, flags)); + if( zOrigText && zOrigText!=g.argv[4] ) fossil_free(zOrigText); + if( zText && zText!=g.argv[3] ) fossil_free(zText); } Index: src/config.h ================================================================== --- src/config.h +++ src/config.h @@ -16,96 +16,266 @@ ******************************************************************************* ** ** A common header file used by all modules. */ +/* The following macros are necessary for large-file support under +** some linux distributions, and possibly other unixes as well. +*/ +#define _LARGE_FILE 1 +#ifndef _FILE_OFFSET_BITS +# define _FILE_OFFSET_BITS 64 +#endif +#define _LARGEFILE_SOURCE 1 + +/* Needed for various definitions... */ +#ifndef _GNU_SOURCE +# define _GNU_SOURCE +#endif + +/* Make sure that in Win32 MinGW builds, _USE_32BIT_TIME_T is always defined. */ +#if defined(_WIN32) && !defined(_WIN64) && !defined(_MSC_VER) && !defined(_USE_32BIT_TIME_T) +# define _USE_32BIT_TIME_T +#endif + +#ifdef HAVE_AUTOCONFIG_H +#include "autoconfig.h" +#endif + +/* Enable the hardened SHA1 implemenation by default */ +#ifndef FOSSIL_HARDENED_SHA1 +# define FOSSIL_HARDENED_SHA1 1 +#endif + +#ifndef _RC_COMPILE_ + /* ** System header files used by all modules */ #include <unistd.h> #include <stdio.h> #include <stdlib.h> -#include <ctype.h> +/* #include <ctype.h> // do not use - causes problems */ #include <string.h> #include <stdarg.h> #include <assert.h> -#ifdef __MINGW32__ -# include <windows.h> + +#endif + +#if defined( __MINGW32__) || defined(__DMC__) || defined(_MSC_VER) || defined(__POCC__) +# if defined(__DMC__) || defined(_MSC_VER) || defined(__POCC__) + typedef int socklen_t; +# endif +# ifndef _WIN32 +# define _WIN32 +# endif +# include <io.h> +# include <fcntl.h> +# undef popen +# define popen _popen +# undef pclose +# define pclose _pclose #else +# include <sys/types.h> +# include <signal.h> # include <pwd.h> #endif +/* +** Utility macro to wrap an argument with double quotes. +*/ +#if !defined(COMPILER_STRINGIFY) +# define COMPILER_STRINGIFY(x) COMPILER_STRINGIFY1(x) +# define COMPILER_STRINGIFY1(x) #x +#endif + +/* +** Define the compiler variant, used to compile the project +*/ +#if !defined(COMPILER_NAME) +# if defined(__DMC__) +# if defined(COMPILER_VERSION) && !defined(NO_COMPILER_VERSION) +# define COMPILER_NAME "dmc-" COMPILER_VERSION +# else +# define COMPILER_NAME "dmc" +# endif +# elif defined(__POCC__) +# if defined(_M_X64) +# if defined(COMPILER_VERSION) && !defined(NO_COMPILER_VERSION) +# define COMPILER_NAME "pellesc64-" COMPILER_VERSION +# else +# define COMPILER_NAME "pellesc64" +# endif +# else +# if defined(COMPILER_VERSION) && !defined(NO_COMPILER_VERSION) +# define COMPILER_NAME "pellesc32-" COMPILER_VERSION +# else +# define COMPILER_NAME "pellesc32" +# endif +# endif +# elif defined(__clang__) +# if !defined(COMPILER_VERSION) +# if defined(__clang_version__) +# define COMPILER_VERSION __clang_version__ +# endif +# endif +# if defined(COMPILER_VERSION) && !defined(NO_COMPILER_VERSION) +# define COMPILER_NAME "clang-" COMPILER_VERSION +# else +# define COMPILER_NAME "clang" +# endif +# elif defined(_MSC_VER) +# if !defined(COMPILER_VERSION) +# define COMPILER_VERSION COMPILER_STRINGIFY(_MSC_VER) +# endif +# if defined(COMPILER_VERSION) && !defined(NO_COMPILER_VERSION) +# define COMPILER_NAME "msc-" COMPILER_VERSION +# else +# define COMPILER_NAME "msc" +# endif +# elif defined(__MINGW32__) +# if !defined(COMPILER_VERSION) +# if defined(__MINGW_VERSION) +# if defined(__GNUC__) +# if defined(__VERSION__) +# define COMPILER_VERSION COMPILER_STRINGIFY(__MINGW_VERSION) "-gcc-" __VERSION__ +# else +# define COMPILER_VERSION COMPILER_STRINGIFY(__MINGW_VERSION) "-gcc" +# endif +# else +# define COMPILER_VERSION COMPILER_STRINGIFY(__MINGW_VERSION) +# endif +# elif defined(__MINGW32_VERSION) +# if defined(__GNUC__) +# if defined(__VERSION__) +# define COMPILER_VERSION COMPILER_STRINGIFY(__MINGW32_VERSION) "-gcc-" __VERSION__ +# else +# define COMPILER_VERSION COMPILER_STRINGIFY(__MINGW32_VERSION) "-gcc" +# endif +# else +# define COMPILER_VERSION COMPILER_STRINGIFY(__MINGW32_VERSION) +# endif +# endif +# endif +# if defined(COMPILER_VERSION) && !defined(NO_COMPILER_VERSION) +# define COMPILER_NAME "mingw32-" COMPILER_VERSION +# else +# define COMPILER_NAME "mingw32" +# endif +# elif defined(__GNUC__) +# if !defined(COMPILER_VERSION) +# if defined(__VERSION__) +# define COMPILER_VERSION __VERSION__ +# endif +# endif +# if defined(COMPILER_VERSION) && !defined(NO_COMPILER_VERSION) +# define COMPILER_NAME "gcc-" COMPILER_VERSION +# else +# define COMPILER_NAME "gcc" +# endif +# elif defined(_WIN32) +# define COMPILER_NAME "win32" +# else +# define COMPILER_NAME "unknown" +# endif +#endif + +#if !defined(_RC_COMPILE_) && !defined(SQLITE_AMALGAMATION) + +/* +** MSVC does not include the "stdint.h" header file until 2010. +*/ +#if defined(_MSC_VER) && _MSC_VER<1600 + typedef __int8 int8_t; + typedef unsigned __int8 uint8_t; + typedef __int32 int32_t; + typedef unsigned __int32 uint32_t; + typedef __int64 int64_t; + typedef unsigned __int64 uint64_t; +#else +# include <stdint.h> +#endif + +#if USE_SEE && !defined(SQLITE_HAS_CODEC) +# define SQLITE_HAS_CODEC +#endif #include "sqlite3.h" +/* +** On Solaris, getpass() will only return up to 8 characters. getpassphrase() returns up to 257. +*/ +#if HAVE_GETPASSPHRASE + #define getpass getpassphrase +#endif + /* ** Typedef for a 64-bit integer */ -typedef sqlite_int64 i64; -typedef sqlite_uint64 u64; +typedef sqlite3_int64 i64; +typedef sqlite3_uint64 u64; /* -** Unsigned character type +** 8-bit types */ typedef unsigned char u8; - -/* -** Standard colors. These colors can also be changed using a stylesheet. -*/ - -/* A blue border and background. Used for the title bar and for dates -** in a timeline. -*/ -#define BORDER1 "#a0b5f4" /* Stylesheet class: border1 */ -#define BG1 "#d0d9f4" /* Stylesheet class: bkgnd1 */ - -/* A red border and background. Use for releases in the timeline. -*/ -#define BORDER2 "#ec9898" /* Stylesheet class: border2 */ -#define BG2 "#f7c0c0" /* Stylesheet class: bkgnd2 */ - -/* A gray background. Used for column headers in the Wiki Table of Contents -** and to highlight ticket properties. -*/ -#define BG3 "#d0d0d0" /* Stylesheet class: bkgnd3 */ - -/* A light-gray background. Used for title bar, menus, and rlog alternation -*/ -#define BG4 "#f0f0f0" /* Stylesheet class: bkgnd4 */ - -/* A deeper gray background. Used for branches -*/ -#define BG5 "#dddddd" /* Stylesheet class: bkgnd5 */ - -/* Default HTML page header */ -#define HEADER "<html>\n" \ - "<head>\n" \ - "<link rel=\"alternate\" type=\"application/rss+xml\"\n" \ - " title=\"%N Timeline Feed\" href=\"%B/timeline.rss\">\n" \ - "<title>%N: %T\n\n" \ - "" - -/* Default HTML page footer */ -#define FOOTER "
\n" \ - "Fossil version %V\n" \ - "
\n" \ - "\n" +typedef signed char i8; /* In the timeline, check-in messages are truncated at the first space ** that is more than MX_CKIN_MSG from the beginning, or at the first ** paragraph break that is more than MN_CKIN_MSG from the beginning. */ #define MN_CKIN_MSG 100 #define MX_CKIN_MSG 300 -/* Unset the following to disable internationalization code. */ -#ifndef FOSSIL_I18N -# define FOSSIL_I18N 1 +/* +** 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. +*/ +#if defined(__PTRDIFF_TYPE__) /* This case should work for GCC */ +# define FOSSIL_INT_TO_PTR(X) ((void*)(__PTRDIFF_TYPE__)(X)) +# define FOSSIL_PTR_TO_INT(X) ((int)(__PTRDIFF_TYPE__)(X)) +#elif !defined(__GNUC__) /* Works for compilers other than LLVM */ +# define FOSSIL_INT_TO_PTR(X) ((void*)&((char*)0)[X]) +# define FOSSIL_PTR_TO_INT(X) ((int)(((char*)X)-(char*)0)) +#else /* Generates a warning - but it always works */ +# define FOSSIL_INT_TO_PTR(X) ((void*)(X)) +# define FOSSIL_PTR_TO_INT(X) ((int)(X)) +#endif + +/* +** A marker for functions that never return. +*/ +#if defined(__GNUC__) || defined(__clang__) +# define NORETURN __attribute__((__noreturn__)) +# define NULL_SENTINEL __attribute__((sentinel)) +#elif defined(_MSC_VER) && (_MSC_VER >= 1310) +# define NORETURN __declspec(noreturn) +# define NULL_SENTINEL +#else +# define NORETURN +# define NULL_SENTINEL +#endif + +/* +** Number of elements in an array +*/ +#define count(X) (int)(sizeof(X)/sizeof(X[0])) +#define ArraySize(X) (int)(sizeof(X)/sizeof(X[0])) + +/* +** The pledge() interface is currently only available on OpenBSD 5.9 +** and later. Make calls to fossil_pledge() no-ops on all platforms +** that omit the HAVE_PLEDGE configuration parameter. +*/ +#if !defined(HAVE_PLEDGE) +# define fossil_pledge(A) #endif -#if FOSSIL_I18N -# include -# include -#endif -#ifndef CODESET -# undef FOSSIL_I18N -# define FOSSIL_I18N 0 -#endif + +#endif /* _RC_COMPILE_ */ Index: src/configure.c ================================================================== --- src/configure.c +++ src/configure.c @@ -2,11 +2,11 @@ ** Copyright (c) 2008 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: @@ -14,11 +14,12 @@ ** http://www.hwaci.com/drh/ ** ******************************************************************************* ** ** This file contains code used to manage repository configurations. -** By "responsitory configure" we mean the local state of a repository +** +** By "repository configure" we mean the local state of a repository ** distinct from the versioned files. */ #include "config.h" #include "configure.h" #include @@ -26,18 +27,31 @@ #if INTERFACE /* ** Configuration transfers occur in groups. These are the allowed ** groupings: */ -#define CONFIGSET_SKIN 0x000001 /* WWW interface appearance */ -#define CONFIGSET_TKT 0x000002 /* Ticket configuration */ -#define CONFIGSET_PROJ 0x000004 /* Project name */ -#define CONFIGSET_SHUN 0x000008 /* Shun settings */ -#define CONFIGSET_USER 0x000010 /* The USER table */ -#define CONFIGSET_ADDR 0x000020 /* The CONCEALED table */ - -#define CONFIGSET_ALL 0xffffff /* Everything */ +#define CONFIGSET_CSS 0x000001 /* Style sheet only */ +#define CONFIGSET_SKIN 0x000002 /* WWW interface appearance */ +#define CONFIGSET_TKT 0x000004 /* Ticket configuration */ +#define CONFIGSET_PROJ 0x000008 /* Project name */ +#define CONFIGSET_SHUN 0x000010 /* Shun settings */ +#define CONFIGSET_USER 0x000020 /* The USER table */ +#define CONFIGSET_ADDR 0x000040 /* The CONCEALED table */ +#define CONFIGSET_XFER 0x000080 /* Transfer configuration */ +#define CONFIGSET_ALIAS 0x000100 /* URL Aliases */ +#define CONFIGSET_SCRIBER 0x000200 /* Email subscribers */ +#define CONFIGSET_IWIKI 0x000400 /* Interwiki codes */ +#define CONFIGSET_ALL 0x0007ff /* Everything */ + +#define CONFIGSET_OVERWRITE 0x100000 /* Causes overwrite instead of merge */ + +/* +** This mask is used for the common TH1 configuration settings (i.e. those +** that are not specific to one particular subsystem, such as the transfer +** subsystem). +*/ +#define CONFIGSET_TH1 (CONFIGSET_SKIN|CONFIGSET_TKT|CONFIGSET_XFER) #endif /* INTERFACE */ /* ** Names of the configuration sets @@ -45,56 +59,133 @@ static struct { const char *zName; /* Name of the configuration set */ int groupMask; /* Mask for that configuration set */ const char *zHelp; /* What it does */ } aGroupName[] = { - { "email", CONFIGSET_ADDR, "Concealed email addresses in tickets" }, - { "project", CONFIGSET_PROJ, "Project name and description" }, - { "skin", CONFIGSET_SKIN, "Web interface apparance settings" }, - { "shun", CONFIGSET_SHUN, "List of shunned artifacts" }, - { "ticket", CONFIGSET_TKT, "Ticket setup", }, - { "user", CONFIGSET_USER, "Users and privilege settings" }, - { "all", CONFIGSET_ALL, "All of the above" }, + { "/email", CONFIGSET_ADDR, "Concealed email addresses in tickets" }, + { "/project", CONFIGSET_PROJ, "Project name and description" }, + { "/skin", CONFIGSET_SKIN | CONFIGSET_CSS, + "Web interface appearance settings" }, + { "/css", CONFIGSET_CSS, "Style sheet" }, + { "/shun", CONFIGSET_SHUN, "List of shunned artifacts" }, + { "/ticket", CONFIGSET_TKT, "Ticket setup", }, + { "/user", CONFIGSET_USER, "Users and privilege settings" }, + { "/xfer", CONFIGSET_XFER, "Transfer setup", }, + { "/alias", CONFIGSET_ALIAS, "URL Aliases", }, + { "/subscriber", CONFIGSET_SCRIBER, "Email notification subscriber list" }, + { "/interwiki", CONFIGSET_IWIKI, "Inter-wiki link prefixes" }, + { "/all", CONFIGSET_ALL, "All of the above" }, }; /* ** The following is a list of settings that we are willing to -** transfer. +** 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 */ } aConfig[] = { - { "css", CONFIGSET_SKIN }, + { "css", CONFIGSET_CSS }, { "header", CONFIGSET_SKIN }, + { "mainmenu", CONFIGSET_SKIN }, { "footer", CONFIGSET_SKIN }, + { "details", CONFIGSET_SKIN }, + { "js", CONFIGSET_SKIN }, { "logo-mimetype", CONFIGSET_SKIN }, { "logo-image", CONFIGSET_SKIN }, - { "project-name", CONFIGSET_PROJ }, - { "project-description", CONFIGSET_PROJ }, - { "index-page", CONFIGSET_SKIN }, + { "background-mimetype", CONFIGSET_SKIN }, + { "background-image", CONFIGSET_SKIN }, + { "icon-mimetype", CONFIGSET_SKIN }, + { "icon-image", CONFIGSET_SKIN }, { "timeline-block-markup", CONFIGSET_SKIN }, + { "timeline-date-format", CONFIGSET_SKIN }, + { "timeline-default-style", CONFIGSET_SKIN }, + { "timeline-dwelltime", CONFIGSET_SKIN }, + { "timeline-closetime", CONFIGSET_SKIN }, { "timeline-max-comment", CONFIGSET_SKIN }, + { "timeline-plaintext", CONFIGSET_SKIN }, + { "timeline-truncate-at-blank", CONFIGSET_SKIN }, + { "timeline-tslink-info", CONFIGSET_SKIN }, + { "timeline-utc", CONFIGSET_SKIN }, + { "adunit", CONFIGSET_SKIN }, + { "adunit-omit-if-admin", CONFIGSET_SKIN }, + { "adunit-omit-if-user", CONFIGSET_SKIN }, + { "default-csp", CONFIGSET_SKIN }, + { "sitemap-extra", CONFIGSET_SKIN }, + { "safe-html", CONFIGSET_SKIN }, + +#ifdef FOSSIL_ENABLE_TH1_DOCS + { "th1-docs", CONFIGSET_TH1 }, +#endif +#ifdef FOSSIL_ENABLE_TH1_HOOKS + { "th1-hooks", CONFIGSET_TH1 }, +#endif + { "th1-setup", CONFIGSET_TH1 }, + { "th1-uri-regexp", CONFIGSET_TH1 }, + +#ifdef FOSSIL_ENABLE_TCL + { "tcl", CONFIGSET_TH1 }, + { "tcl-setup", CONFIGSET_TH1 }, +#endif + + { "project-name", CONFIGSET_PROJ }, + { "short-project-name", CONFIGSET_PROJ }, + { "project-description", CONFIGSET_PROJ }, + { "index-page", CONFIGSET_PROJ }, + { "manifest", CONFIGSET_PROJ }, + { "binary-glob", CONFIGSET_PROJ }, + { "clean-glob", CONFIGSET_PROJ }, + { "ignore-glob", CONFIGSET_PROJ }, + { "keep-glob", CONFIGSET_PROJ }, + { "crlf-glob", CONFIGSET_PROJ }, + { "crnl-glob", CONFIGSET_PROJ }, + { "encoding-glob", CONFIGSET_PROJ }, + { "empty-dirs", CONFIGSET_PROJ }, + { "dotfiles", CONFIGSET_PROJ }, + { "parent-project-code", CONFIGSET_PROJ }, + { "parent-project-name", CONFIGSET_PROJ }, + { "hash-policy", CONFIGSET_PROJ }, + { "comment-format", CONFIGSET_PROJ }, + { "mimetypes", CONFIGSET_PROJ }, + { "forbid-delta-manifests", CONFIGSET_PROJ }, + { "mv-rm-files", CONFIGSET_PROJ }, { "ticket-table", CONFIGSET_TKT }, { "ticket-common", CONFIGSET_TKT }, + { "ticket-change", CONFIGSET_TKT }, { "ticket-newpage", CONFIGSET_TKT }, { "ticket-viewpage", CONFIGSET_TKT }, { "ticket-editpage", CONFIGSET_TKT }, { "ticket-reportlist", CONFIGSET_TKT }, { "ticket-report-template", CONFIGSET_TKT }, { "ticket-key-template", CONFIGSET_TKT }, { "ticket-title-expr", CONFIGSET_TKT }, { "ticket-closed-expr", CONFIGSET_TKT }, { "@reportfmt", CONFIGSET_TKT }, + { "@user", CONFIGSET_USER }, + { "user-color-map", CONFIGSET_USER }, + { "@concealed", CONFIGSET_ADDR }, + { "@shun", CONFIGSET_SHUN }, + + { "@alias", CONFIGSET_ALIAS }, + + { "@subscriber", CONFIGSET_SCRIBER }, + + { "@interwiki", CONFIGSET_IWIKI }, + + { "xfer-common-script", CONFIGSET_XFER }, + { "xfer-push-script", CONFIGSET_XFER }, + { "xfer-commit-script", CONFIGSET_XFER }, + { "xfer-ticket-script", CONFIGSET_XFER }, + }; static int iConfig = 0; /* ** Return name of first configuration property matching the given mask. @@ -102,403 +193,905 @@ const char *configure_first_name(int iMask){ iConfig = 0; return configure_next_name(iMask); } const char *configure_next_name(int iMask){ - while( iConfig2 && zName[0]=='\'' && zName[n-1]=='\'' ){ + zName++; + n -= 2; + } for(i=0; i=count(aType) ) return; + + while( blob_token(pContent, &name) && blob_sqltoken(pContent, &value) ){ + char *z = blob_terminate(&name); + if( !safeSql(z) ) return; + if( nToken>0 ){ + for(jj=0; jj=aType[ii].nField ) continue; + }else{ + if( !safeInt(z) ) return; + } + azToken[nToken++] = z; + azToken[nToken++] = z = blob_terminate(&value); + if( !safeSql(z) ) return; + if( nToken>=count(azToken)-1 ) break; + } + if( nToken<2 ) return; + if( aType[ii].zName[0]=='/' ){ + thisMask = configure_is_exportable(azToken[1]); + }else{ + thisMask = configure_is_exportable(aType[ii].zName); + } + if( (thisMask & groupMask)==0 ) return; + if( (thisMask & checkMask)!=0 ){ + if( (thisMask & CONFIGSET_SCRIBER)!=0 ){ + alert_schema(1); + } + checkMask &= ~thisMask; + } + + blob_zero(&sql); + if( groupMask & CONFIGSET_OVERWRITE ){ + if( (thisMask & configHasBeenReset)==0 && aType[ii].zName[0]!='/' ){ + db_multi_exec("DELETE FROM \"%w\"", &aType[ii].zName[1]); + configHasBeenReset |= thisMask; + } + blob_append_sql(&sql, "REPLACE INTO "); + }else{ + blob_append_sql(&sql, "INSERT OR IGNORE INTO "); + } + blob_append_sql(&sql, "\"%w\"(\"%w\",mtime", + &zName[1], aType[ii].zPrimKey); + if( fossil_stricmp(zName,"/subscriber")==0 ) alert_schema(0); + for(jj=2; jj=%lld", iStart); + while( db_step(&q)==SQLITE_ROW ){ + blob_appendf(&rec,"%s %s scom %s", db_column_text(&q, 0), db_column_text(&q, 1), db_column_text(&q, 2) ); + blob_appendf(pOut, "config /shun %d\n%s\n", + blob_size(&rec), blob_str(&rec)); + nCard++; + blob_reset(&rec); + } + db_finalize(&q); + } + if( groupMask & CONFIGSET_USER ){ + db_prepare(&q, "SELECT mtime, quote(login), quote(pw), quote(cap)," + " quote(info), quote(photo) FROM user" + " WHERE mtime>=%lld", iStart); + while( db_step(&q)==SQLITE_ROW ){ + blob_appendf(&rec,"%s %s pw %s cap %s info %s photo %s", + db_column_text(&q, 0), + db_column_text(&q, 1), + db_column_text(&q, 2), + db_column_text(&q, 3), + db_column_text(&q, 4), + db_column_text(&q, 5) + ); + blob_appendf(pOut, "config /user %d\n%s\n", + blob_size(&rec), blob_str(&rec)); + nCard++; + blob_reset(&rec); } db_finalize(&q); - }else if( strcmp(zName, "@user")==0 ){ - db_prepare(&q, - "SELECT login, CASE WHEN length(pw)==40 THEN pw END," - " cap, info, quote(photo) FROM user"); + } + if( groupMask & CONFIGSET_TKT ){ + db_prepare(&q, "SELECT mtime, quote(title), quote(owner), quote(cols)," + " quote(sqlcode) FROM reportfmt" + " WHERE mtime>=%lld", iStart); while( db_step(&q)==SQLITE_ROW ){ - blob_appendf(pOut, "INSERT INTO _xfer_user(login,pw,cap,info,photo)" - " VALUES(%Q,%Q,%Q,%Q,%s);\n", + blob_appendf(&rec,"%s %s owner %s cols %s sqlcode %s", db_column_text(&q, 0), db_column_text(&q, 1), db_column_text(&q, 2), db_column_text(&q, 3), db_column_text(&q, 4) ); - } - db_finalize(&q); - }else if( strcmp(zName, "@concealed")==0 ){ - db_prepare(&q, "SELECT hash, content FROM concealed"); - while( db_step(&q)==SQLITE_ROW ){ - blob_appendf(pOut, "INSERT OR IGNORE INTO concealed(hash,content)" - " VALUES(%Q,%Q);\n", - db_column_text(&q, 0), - db_column_text(&q, 1) - ); - } - db_finalize(&q); - } -} - -/* -** Two SQL functions: -** -** flag_test(int) -** flag_clear(int) -** -** The flag_test() function takes the integer valued argument and -** ANDs it against the static variable "flag_value" below. The -** function returns TRUE or false depending on the result. The -** flag_clear() function masks off the bits from "flag_value" that -** are given in the argument. -** -** These functions are used below in the WHEN clause of a trigger to -** get the trigger to fire exactly once. -*/ -static int flag_value = 0xffff; -static void flag_test_function( - sqlite3_context *context, - int argc, - sqlite3_value **argv -){ - int m = sqlite3_value_int(argv[0]); - sqlite3_result_int(context, (flag_value&m)!=0 ); -} -static void flag_clear_function( - sqlite3_context *context, - int argc, - sqlite3_value **argv -){ - int m = sqlite3_value_int(argv[0]); - flag_value &= ~m; -} - -/* -** Create the temporary _xfer_reportfmt and _xfer_user tables that are -** necessary in order to evalute the SQL text generated by the -** configure_render_special_name() routine. -** -** If replaceFlag is true, then the setup is such that the content in -** the SQL text will completely replace the current repository configuration. -** If replaceFlag is false, then the SQL text will be merged with the -** existing configuration. When merging, existing values take priority -** over SQL text values. -*/ -void configure_prepare_to_receive(int replaceFlag){ - static const char zSQL1[] = - @ CREATE TEMP TABLE _xfer_reportfmt( - @ rn integer primary key, -- Report number - @ owner text, -- Owner of this report format (not used) - @ title text UNIQUE ON CONFLICT IGNORE, -- Title of this report - @ cols text, -- A color-key specification - @ sqlcode text -- An SQL SELECT statement for this report - @ ); - @ CREATE TEMP TABLE _xfer_user( - @ uid INTEGER PRIMARY KEY, -- User ID - @ login TEXT UNIQUE ON CONFLICT IGNORE, -- 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 - @ photo BLOB -- JPEG image of this user - @ ); - @ INSERT INTO _xfer_reportfmt SELECT * FROM reportfmt; - @ INSERT INTO _xfer_user SELECT * FROM user; - ; - db_multi_exec(zSQL1); - - /* When the replace flag is set, add triggers that run the first time - ** that new data is seen. The triggers run only once and delete all the - ** existing data. - */ - if( replaceFlag ){ - static const char zSQL2[] = - @ CREATE TRIGGER _xfer_r1 BEFORE INSERT ON _xfer_reportfmt - @ WHEN flag_test(1) BEGIN - @ DELETE FROM _xfer_reportfmt; - @ SELECT flag_clear(1); - @ END; - @ CREATE TRIGGER _xfer_r2 BEFORE INSERT ON _xfer_user - @ WHEN flag_test(2) BEGIN - @ DELETE FROM _xfer_user; - @ SELECT flag_clear(2); - @ END; - @ CREATE TEMP TRIGGER _xfer_r3 BEFORE INSERT ON shun - @ WHEN flag_test(4) BEGIN - @ DELETE FROM shun; - @ SELECT flag_clear(4); - @ END; - ; - sqlite3_create_function(g.db, "flag_test", 1, SQLITE_UTF8, 0, - flag_test_function, 0, 0); - sqlite3_create_function(g.db, "flag_clear", 1, SQLITE_UTF8, 0, - flag_clear_function, 0, 0); - flag_value = 0xffff; - db_multi_exec(zSQL2); - } -} - -/* -** After receiving configuration data, call this routine to transfer -** the results into the main database. -*/ -void configure_finalize_receive(void){ - static const char zSQL[] = - @ DELETE FROM user; - @ INSERT INTO user SELECT * FROM _xfer_user; - @ DELETE FROM reportfmt; - @ INSERT INTO reportfmt SELECT * FROM _xfer_reportfmt; - @ DROP TABLE _xfer_user; - @ DROP TABLE _xfer_reportfmt; - ; - db_multi_exec(zSQL); + blob_appendf(pOut, "config /reportfmt %d\n%s\n", + blob_size(&rec), blob_str(&rec)); + nCard++; + blob_reset(&rec); + } + db_finalize(&q); + } + if( groupMask & CONFIGSET_ADDR ){ + db_prepare(&q, "SELECT mtime, quote(hash), quote(content) FROM concealed" + " WHERE mtime>=%lld", iStart); + while( db_step(&q)==SQLITE_ROW ){ + blob_appendf(&rec,"%s %s content %s", + db_column_text(&q, 0), + db_column_text(&q, 1), + db_column_text(&q, 2) + ); + blob_appendf(pOut, "config /concealed %d\n%s\n", + blob_size(&rec), blob_str(&rec)); + nCard++; + blob_reset(&rec); + } + db_finalize(&q); + } + if( groupMask & CONFIGSET_ALIAS ){ + db_prepare(&q, "SELECT mtime, quote(name), quote(value) FROM config" + " WHERE name GLOB 'walias:/*' AND mtime>=%lld", iStart); + while( db_step(&q)==SQLITE_ROW ){ + blob_appendf(&rec,"%s %s value %s", + db_column_text(&q, 0), + db_column_text(&q, 1), + db_column_text(&q, 2) + ); + blob_appendf(pOut, "config /config %d\n%s\n", + blob_size(&rec), blob_str(&rec)); + nCard++; + blob_reset(&rec); + } + db_finalize(&q); + } + if( groupMask & CONFIGSET_IWIKI ){ + db_prepare(&q, "SELECT mtime, quote(name), quote(value) FROM config" + " WHERE name GLOB 'interwiki:*' AND mtime>=%lld", iStart); + while( db_step(&q)==SQLITE_ROW ){ + blob_appendf(&rec,"%s %s value %s", + db_column_text(&q, 0), + db_column_text(&q, 1), + db_column_text(&q, 2) + ); + blob_appendf(pOut, "config /config %d\n%s\n", + blob_size(&rec), blob_str(&rec)); + nCard++; + blob_reset(&rec); + } + db_finalize(&q); + } + if( (groupMask & CONFIGSET_SCRIBER)!=0 + && db_table_exists("repository","subscriber") + ){ + db_prepare(&q, "SELECT mtime, quote(semail)," + " quote(suname), quote(sdigest)," + " quote(sdonotcall), quote(ssub)," + " quote(sctime), quote(smip)" + " FROM subscriber WHERE sverified" + " AND mtime>=%lld", iStart); + while( db_step(&q)==SQLITE_ROW ){ + blob_appendf(&rec, + "%lld %s suname %s sdigest %s sdonotcall %s ssub %s" + " sctime %s smip %s", + db_column_int64(&q, 0), /* mtime */ + db_column_text(&q, 1), /* semail (PK) */ + db_column_text(&q, 2), /* suname */ + db_column_text(&q, 3), /* sdigest */ + db_column_text(&q, 4), /* sdonotcall */ + db_column_text(&q, 5), /* ssub */ + db_column_text(&q, 6), /* sctime */ + db_column_text(&q, 7) /* smip */ + ); + blob_appendf(pOut, "config /subscriber %d\n%s\n", + blob_size(&rec), blob_str(&rec)); + nCard++; + blob_reset(&rec); + } + db_finalize(&q); + } + db_prepare(&q, "SELECT mtime, quote(name), quote(value) FROM config" + " WHERE name=:name AND mtime>=%lld", iStart); + for(ii=0; ii fossil configuration export AREA FILENAME +** +** Write to FILENAME exported configuration information for AREA. +** AREA can be one of: +** +** all email interwiki project shun skin +** ticket user alias subscriber +** +** > fossil configuration import FILENAME ** ** Read a configuration from FILENAME, overwriting the current ** configuration. ** -** %fossil configuration merge FILENAME +** > fossil configuration merge FILENAME ** ** Read a configuration from FILENAME and merge its values into ** the current configuration. Existing values take priority over ** values read from FILENAME. ** -** %fossil configuration pull AREA ?URL? +** > fossil configuration pull AREA ?URL? ** ** Pull and install the configuration from a different server ** identified by URL. If no URL is specified, then the default -** server is used. +** server is used. Use the --overwrite flag to completely +** replace local settings with content received from URL. ** -** %fossil configuration push AREA ?URL? +** > fossil configuration push AREA ?URL? ** ** Push the local configuration into the remote server identified ** by URL. Admin privilege is required on the remote server for -** this to work. +** this to work. When the same record exists both locally and on +** the remote end, the one that was most recently changed wins. ** -** %fossil configuration reset AREA +** > fossil configuration reset AREA ** ** Restore the configuration to the default. AREA as above. ** -** WARNING: Do not import, merge, or pull configurations from an untrusted -** source. The inbound configuration is not checked for safety and can -** introduce security vulnerabilities. +** > fossil configuration sync AREA ?URL? +** +** Synchronize configuration changes in the local repository with +** the remote repository at URL. +** +** Options: +** -R|--repository REPO Extract info from repository REPO +** +** See also: [[settings]], [[unset]] */ void configuration_cmd(void){ int n; const char *zMethod; + db_find_and_open_repository(0, 0); + db_open_config(0, 0); if( g.argc<3 ){ - usage("export|import|merge|pull|reset ..."); + usage("SUBCOMMAND ..."); } - db_find_and_open_repository(1); zMethod = g.argv[2]; n = strlen(zMethod); if( strncmp(zMethod, "export", n)==0 ){ int mask; + const char *zSince = find_option("since",0,1); + sqlite3_int64 iStart; if( g.argc!=5 ){ usage("export AREA FILENAME"); } - mask = find_area(g.argv[3]); - export_config(mask, g.argv[3], g.argv[4]); + mask = configure_name_to_mask(g.argv[3], 1); + if( zSince ){ + iStart = db_multi_exec( + "SELECT coalesce(strftime('%%s',%Q),strftime('%%s','now',%Q))+0", + zSince, zSince + ); + }else{ + iStart = 0; + } + export_config(mask, g.argv[3], iStart, g.argv[4]); }else - if( strncmp(zMethod, "import", n)==0 + if( strncmp(zMethod, "import", n)==0 || strncmp(zMethod, "merge", n)==0 ){ Blob in; + int groupMask; if( g.argc!=4 ) usage(mprintf("%s FILENAME",zMethod)); - blob_read_from_file(&in, g.argv[3]); + blob_read_from_file(&in, g.argv[3], ExtFILE); db_begin_transaction(); - configure_prepare_to_receive(zMethod[0]=='i'); - db_multi_exec("%s", blob_str(&in)); - configure_finalize_receive(); + if( zMethod[0]=='i' ){ + groupMask = CONFIGSET_ALL | CONFIGSET_OVERWRITE; + }else{ + groupMask = CONFIGSET_ALL; + } + configure_receive_all(&in, groupMask); db_end_transaction(0); }else - if( strncmp(zMethod, "pull", n)==0 || strncmp(zMethod, "push", n)==0 ){ + if( strncmp(zMethod, "pull", n)==0 + || strncmp(zMethod, "push", n)==0 + || strncmp(zMethod, "sync", n)==0 + ){ int mask; - const char *zServer; - const char *zPw; + const char *zServer = 0; + int overwriteFlag = 0; + + if( strncmp(zMethod,"pull",n)==0 ){ + overwriteFlag = find_option("overwrite",0,0)!=0; + } url_proxy_options(); if( g.argc!=4 && g.argc!=5 ){ - usage("pull AREA ?URL?"); + usage(mprintf("%s AREA ?URL?", zMethod)); } - mask = find_area(g.argv[3]); + mask = configure_name_to_mask(g.argv[3], 1); if( g.argc==5 ){ zServer = g.argv[4]; - zPw = 0; - g.dontKeepUrl = 1; - }else{ - zServer = db_get("last-sync-url", 0); - if( zServer==0 ){ - fossil_fatal("no server specified"); - } - zPw = db_get("last-sync-pw", 0); - } - url_parse(zServer); - if( g.urlPasswd==0 && zPw ) g.urlPasswd = mprintf("%s", zPw); + } + url_parse(zServer, URL_PROMPT_PW); + if( g.url.protocol==0 ) fossil_fatal("no server URL specified"); user_select(); + url_enable_proxy("via proxy: "); + if( overwriteFlag ) mask |= CONFIGSET_OVERWRITE; if( strncmp(zMethod, "push", n)==0 ){ - client_sync(0,0,0,0,mask); + client_sync(0,0,(unsigned)mask,0); + }else if( strncmp(zMethod, "pull", n)==0 ){ + client_sync(0,(unsigned)mask,0,0); }else{ - client_sync(0,0,0,mask,0); + client_sync(0,(unsigned)mask,(unsigned)mask,0); } }else if( strncmp(zMethod, "reset", n)==0 ){ int mask, i; char *zBackup; if( g.argc!=4 ) usage("reset AREA"); - mask = find_area(g.argv[3]); - zBackup = db_text(0, + mask = configure_name_to_mask(g.argv[3], 1); + zBackup = db_text(0, "SELECT strftime('config-backup-%%Y%%m%%d%%H%%M%%f','now')"); db_begin_transaction(); - export_config(mask, g.argv[3], zBackup); + export_config(mask, g.argv[3], 0, zBackup); for(i=0; i=3 ){ + zPattern = g.argv[2]; + } + blob_init(&sql,0,0); + blob_appendf(&sql, "SELECT name, value, datetime(mtime,'unixepoch')" + " FROM config"); + if( zPattern ){ + blob_appendf(&sql, " WHERE name GLOB %Q", zPattern); + } + if( showMtime ){ + blob_appendf(&sql, " ORDER BY mtime, name"); + }else{ + blob_appendf(&sql, " ORDER BY name"); + } + db_prepare(&q, "%s", blob_str(&sql)/*safe-for-%s*/); + blob_reset(&sql); +#define MX_VAL 40 +#define MX_NM 28 +#define MX_LONGNM 60 + while( db_step(&q)==SQLITE_ROW ){ + const char *zName = db_column_text(&q,0); + int nName = db_column_bytes(&q,0); + const char *zValue = db_column_text(&q,1); + int szValue = db_column_bytes(&q,1); + const char *zMTime = db_column_text(&q,2); + for(i=j=0; j=' ' && c<='~' ){ + zTrans[j++] = c; + }else{ + zTrans[j++] = '\\'; + if( c=='\n' ){ + zTrans[j++] = 'n'; + }else if( c=='\r' ){ + zTrans[j++] = 'r'; + }else if( c=='\t' ){ + zTrans[j++] = 't'; + }else{ + zTrans[j++] = '0' + ((c>>6)&7); + zTrans[j++] = '0' + ((c>>3)&7); + zTrans[j++] = '0' + (c&7); + } + } + } + zTrans[j] = 0; + if( i=4 ? g.argv[3] : "-"; + n = db_int(0, "SELECT count(*) FROM config WHERE name GLOB %Q", zVar); + if( n==0 ){ + fossil_fatal("no match for %Q", zVar); + } + if( n>1 ){ + fossil_fatal("multiple matches: %s", + db_text(0, "SELECT group_concat(quote(name),', ') FROM (" + " SELECT name FROM config WHERE name GLOB %Q ORDER BY 1)", + zVar)); + } + blob_init(&x,0,0); + db_blob(&x, "SELECT value FROM config WHERE name GLOB %Q", zVar); + blob_write_to_file(&x, zFile); +} + +/* +** COMMAND: test-var-set +** +** Usage: %fossil test-var-set VAR ?VALUE? ?--file FILE? +** +** Store VALUE or the content of FILE (exactly one of which must be +** supplied) into variable VAR. Use a FILE of "-" to read from +** standard input. +** +** WARNING: changing the value of a variable can interfere with the +** operation of Fossil. Be sure you know what you are doing. +** +** Use "--blob FILE" instead of "--file FILE" to load a binary blob +** such as a GIF. +*/ +void test_var_set_cmd(void){ + const char *zVar; + const char *zFile; + const char *zBlob; + Blob x; + Stmt ins; + zFile = find_option("file",0,1); + zBlob = find_option("blob",0,1); + db_find_and_open_repository(OPEN_ANY_SCHEMA, 0); + verify_all_options(); + if( g.argc<3 || (zFile==0 && zBlob==0 && g.argc<4) ){ + usage("VAR ?VALUE? ?--file FILE?"); + } + zVar = g.argv[2]; + if( zFile ){ + if( zBlob ) fossil_fatal("cannot do both --file or --blob"); + blob_read_from_file(&x, zFile, ExtFILE); + }else if( zBlob ){ + blob_read_from_file(&x, zBlob, ExtFILE); + }else{ + blob_init(&x,g.argv[3],-1); + } + db_unprotect(PROTECT_CONFIG); + db_prepare(&ins, + "REPLACE INTO config(name,value,mtime)" + "VALUES(%Q,:val,now())", zVar); + if( zBlob ){ + db_bind_blob(&ins, ":val", &x); + }else{ + db_bind_text(&ins, ":val", blob_str(&x)); + } + db_step(&ins); + db_finalize(&ins); + db_protect_pop(); + blob_reset(&x); } Index: src/content.c ================================================================== --- src/content.c +++ src/content.c @@ -2,11 +2,11 @@ ** Copyright (c) 2006 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: @@ -20,65 +20,115 @@ #include "config.h" #include "content.h" #include /* -** Macros for debugging -*/ -#if 0 -# define CONTENT_TRACE(X) printf X; -#else -# define CONTENT_TRACE(X) -#endif - -/* -** The artifact retrival cache -*/ -#define MX_CACHE_CNT 50 /* Maximum number of positive cache entries */ -#define EXPELL_INTERVAL 5 /* How often to expell from a full cache */ +** The artifact retrieval cache +*/ static struct { - int n; /* Current number of positive cache entries */ + i64 szTotal; /* Total size of all entries in the cache */ + int n; /* Current number of cache entries */ + int nAlloc; /* Number of slots allocated in a[] */ int nextAge; /* Age counter for implementing LRU */ - int skipCnt; /* Used to limit entries expelled from cache */ - struct { /* One instance of this for each cache entry */ + struct cacheLine { /* One instance of this for each cache entry */ int rid; /* Artifact id */ int age; /* Age. Newer is larger */ Blob content; /* Content of the artifact */ - } a[MX_CACHE_CNT]; /* The positive cache */ + } *a; /* The positive cache */ + Bag inCache; /* Set of artifacts currently in cache */ /* ** The missing artifact cache. ** ** Artifacts whose record ID are in missingCache cannot be retrieved ** either because they are phantoms or because they are a delta that ** depends on a phantom. Artifacts whose content we are certain is ** available are in availableCache. If an artifact is in neither cache - ** then its current availablity is unknown. + ** then its current availability is unknown. */ Bag missing; /* Cache of artifacts that are incomplete */ Bag available; /* Cache of artifacts that are complete */ } contentCache; +/* +** Remove the oldest element from the content cache +*/ +static void content_cache_expire_oldest(void){ + int i; + int mnAge = contentCache.nextAge; + int mn = -1; + for(i=0; i=0 ){ + bag_remove(&contentCache.inCache, contentCache.a[mn].rid); + contentCache.szTotal -= blob_size(&contentCache.a[mn].content); + blob_reset(&contentCache.a[mn].content); + contentCache.n--; + contentCache.a[mn] = contentCache.a[contentCache.n]; + } +} + +/* +** Add an entry to the content cache. +** +** This routines hands responsibility for the artifact over to the cache. +** The cache will deallocate memory when it has finished with it. +*/ +void content_cache_insert(int rid, Blob *pBlob){ + struct cacheLine *p; + if( contentCache.n>500 || contentCache.szTotal>50000000 ){ + i64 szBefore; + do{ + szBefore = contentCache.szTotal; + content_cache_expire_oldest(); + }while( contentCache.szTotal>50000000 && contentCache.szTotal=contentCache.nAlloc ){ + contentCache.nAlloc = contentCache.nAlloc*2 + 10; + contentCache.a = fossil_realloc(contentCache.a, + contentCache.nAlloc*sizeof(contentCache.a[0])); + } + p = &contentCache.a[contentCache.n++]; + p->rid = rid; + p->age = contentCache.nextAge++; + contentCache.szTotal += blob_size(pBlob); + p->content = *pBlob; + blob_zero(pBlob); + bag_insert(&contentCache.inCache, rid); +} /* -** Clear the content cache. +** Clear the content cache. If it is passed true, it +** also frees all associated memory, otherwise it may +** retain parts for future uses of the cache. */ -void content_clear_cache(void){ +void content_clear_cache(int bFreeIt){ int i; for(i=0; i=0 ){ - contentCache.a[i].content = src; - contentCache.a[i].age = contentCache.nextAge++; - contentCache.a[i].rid = srcid; - CONTENT_TRACE(("%*sadd %d to cache\n", - bag_count(&inProcess), "", srcid)) - }else{ - blob_reset(&src); - } - } - bag_remove(&inProcess, srcid); - }else{ - /* No delta required. Read content directly from the database */ - if( content_of_blob(rid, pBlob) ){ - rc = 1; - } + if( bag_find(&contentCache.inCache, rid) ){ + for(i=0; i0 ){ + n++; + if( n>=nAlloc ){ + if( n>db_int(0, "SELECT max(rid) FROM blob") ){ + fossil_panic("infinite loop in DELTA table"); + } + nAlloc = nAlloc*2 + 10; + a = fossil_realloc(a, nAlloc*sizeof(a[0])); + } + a[n] = nextRid; + } + mx = n; + rc = content_get(a[n], pBlob); + n--; + while( rc && n>=0 ){ + rc = content_of_blob(a[n], &delta); + if( rc ){ + if( blob_delta_apply(pBlob, &delta, &next)<0 ){ + rc = 1; + }else{ + blob_reset(&delta); + if( (mx-n)%8==0 ){ + content_cache_insert(a[n+1], pBlob); + }else{ + blob_reset(pBlob); + } + *pBlob = next; + } + } + n--; + } + free(a); + if( !rc ) blob_reset(pBlob); } if( rc==0 ){ bag_insert(&contentCache.missing, rid); }else{ bag_insert(&contentCache.available, rid); @@ -275,32 +315,40 @@ } return rc; } /* -** COMMAND: artifact +** COMMAND: artifact* ** -** Usage: %fossil artifact ARTIFACT-ID ?OUTPUT-FILENAME? +** Usage: %fossil artifact ARTIFACT-ID ?OUTPUT-FILENAME? ?OPTIONS? ** -** Extract an artifact by its SHA1 hash and write the results on +** Extract an artifact by its artifact hash and write the results on ** standard output, or if the optional 4th argument is given, in ** the named output file. +** +** Options: +** -R|--repository REPO Extract artifacts from repository REPO +** +** See also: [[finfo]] */ void artifact_cmd(void){ int rid; Blob content; const char *zFile; - if( g.argc!=4 && g.argc!=3 ) usage("RECORDID ?FILENAME?"); + db_find_and_open_repository(OPEN_ANY_SCHEMA, 0); + if( g.argc!=4 && g.argc!=3 ) usage("ARTIFACT-ID ?FILENAME? ?OPTIONS?"); zFile = g.argc==4 ? g.argv[3] : "-"; - db_must_be_within_tree(); rid = name_to_rid(g.argv[2]); + if( rid==0 ){ + fossil_fatal("%s",g.zErrMsg); + } content_get(rid, &content); blob_write_to_file(&content, zFile); } /* -** COMMAND: test-content-rawget +** COMMAND: test-content-rawget ** ** Extract a blob from the database and write it into a file. This ** version does not expand the delta. */ void test_content_rawget_cmd(void){ @@ -315,97 +363,223 @@ db_blob(&content, "SELECT content FROM blob WHERE rid=%d", rid); blob_uncompress(&content, &content); blob_write_to_file(&content, zFile); } +/* +** The following flag is set to disable the automatic calls to +** manifest_crosslink() when a record is dephantomized. This +** flag can be set (for example) when doing a clone when we know +** that rebuild will be run over all records at the conclusion +** of the operation. +*/ +static int ignoreDephantomizations = 0; + /* ** 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 manifest_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 manifest_crosslink() on the delta-manifests +** associated with that baseline. +** +** Tail recursion is used to minimize stack depth. */ void after_dephantomize(int rid, int linkFlag){ Stmt q; - db_prepare(&q, "SELECT rid FROM delta WHERE srcid=%d", rid); - while( db_step(&q)==SQLITE_ROW ){ - int tid = db_column_int(&q, 0); - after_dephantomize(tid, 1); - } - db_finalize(&q); - if( linkFlag ){ - Blob content; - content_get(rid, &content); - manifest_crosslink(rid, &content); - blob_reset(&content); + int nChildAlloc = 0; + int *aChild = 0; + Blob content; + + if( ignoreDephantomizations ) return; + while( rid ){ + int nChildUsed = 0; + int i; + + /* Parse the object rid itself */ + if( linkFlag ){ + content_get(rid, &content); + manifest_crosslink(rid, &content, MC_NONE); + assert( blob_is_reset(&content) ); + } + + /* Parse all delta-manifests that depend on baseline-manifest rid */ + db_prepare(&q, "SELECT rid FROM orphan WHERE baseline=%d", rid); + while( db_step(&q)==SQLITE_ROW ){ + int child = db_column_int(&q, 0); + if( nChildUsed>=nChildAlloc ){ + nChildAlloc = nChildAlloc*2 + 10; + aChild = fossil_realloc(aChild, nChildAlloc*sizeof(aChild)); + } + aChild[nChildUsed++] = child; + } + db_finalize(&q); + for(i=0; i=nChildAlloc ){ + nChildAlloc = nChildAlloc*2 + 10; + aChild = fossil_realloc(aChild, nChildAlloc*sizeof(aChild)); + } + aChild[nChildUsed++] = child; + } + db_finalize(&q); + for(i=1; i0 ? aChild[0] : 0; + linkFlag = 1; + } + free(aChild); +} + +/* +** Turn dephantomization processing on or off. +*/ +void content_enable_dephantomize(int onoff){ + ignoreDephantomizations = !onoff; +} + +/* +** Make sure the g.rcvid global variable has been initialized. +** +** If the g.zIpAddr variable has not been set when this routine is +** called, use zSrc as the source of content for the rcvfrom +** table entry. +*/ +void content_rcvid_init(const char *zSrc){ + if( g.rcvid==0 ){ + user_select(); + if( g.zIpAddr ) zSrc = g.zIpAddr; + db_multi_exec( + "INSERT INTO rcvfrom(uid, mtime, nonce, ipaddr)" + "VALUES(%d, julianday('now'), %Q, %Q)", + g.userUid, g.zNonce, zSrc + ); + g.rcvid = db_last_insert_rowid(); } } /* ** Write content into the database. Return the record ID. If the ** content is already in the database, just return the record ID. ** ** If srcId is specified, then pBlob is delta content from ** the srcId record. srcId might be a phantom. +** +** pBlob is normally uncompressed text. But if nBlob>0 then the +** pBlob value has already been compressed and nBlob is its uncompressed +** size. If nBlob>0 then zUuid must be valid. ** ** zUuid is the UUID of the artifact, if it is specified. 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 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. */ -int content_put(Blob *pBlob, const char *zUuid, int srcId){ +int content_put_ex( + Blob *pBlob, /* Content to add to the repository */ + const char *zUuid, /* artifact hash of reconstructed pBlob */ + int srcId, /* pBlob is a delta from this entry */ + int nBlob, /* pBlob is compressed. Original size is this */ + int isPrivate /* The content should be marked private */ +){ int size; int rid; Stmt s1; Blob cmpr; Blob hash; int markAsUnclustered = 0; int isDephantomize = 0; - + assert( g.repositoryOpen ); assert( pBlob!=0 ); assert( srcId==0 || zUuid!=0 ); + db_begin_transaction(); if( zUuid==0 ){ - assert( pBlob!=0 ); - sha1sum_blob(pBlob, &hash); + assert( nBlob==0 ); + /* First check the auxiliary hash to see if there is already an artifact + ** that uses the auxiliary hash name */ + hname_hash(pBlob, 1, &hash); + rid = fast_uuid_to_rid(blob_str(&hash)); + if( rid==0 ){ + /* No existing artifact with the auxiliary hash name. Therefore, use + ** the primary hash name. */ + blob_reset(&hash); + hname_hash(pBlob, 0, &hash); + } }else{ blob_init(&hash, zUuid, -1); } - size = blob_size(pBlob); - db_begin_transaction(); + if( g.eHashPolicy==HPOLICY_AUTO && blob_size(&hash)>HNAME_LEN_SHA1 ){ + g.eHashPolicy = HPOLICY_SHA3; + db_set_int("hash-policy", HPOLICY_SHA3, 0); + } + if( nBlob ){ + size = nBlob; + }else{ + size = blob_size(pBlob); + if( srcId ){ + size = delta_output_size(blob_buffer(pBlob), size); + } + } /* Check to see if the entry already exists and if it does whether ** or not the entry is a phantom */ db_prepare(&s1, "SELECT rid, size FROM blob WHERE uuid=%B", &hash); if( db_step(&s1)==SQLITE_ROW ){ rid = db_column_int(&s1, 0); - if( db_column_int(&s1, 1)>=0 || pBlob==0 ){ - /* Either the entry is not a phantom or it is a phantom but we - ** have no data with which to dephantomize it. In either case, - ** there is nothing for us to do other than return the RID. */ + if( db_column_int(&s1, 1)>=0 ){ + /* The entry is not a phantom. There is nothing for us to do + ** other than return the RID. */ db_finalize(&s1); db_end_transaction(0); return rid; } }else{ - rid = 0; /* No entry with the same UUID currently exists */ + rid = 0; /* No entry with the same hash currently exists */ markAsUnclustered = 1; } db_finalize(&s1); /* Construct a received-from ID if we do not already have one */ - if( g.rcvid==0 ){ - db_multi_exec( - "INSERT INTO rcvfrom(uid, mtime, nonce, ipaddr)" - "VALUES(%d, julianday('now'), %Q, %Q)", - g.userUid, g.zNonce, g.zIpAddr - ); - g.rcvid = db_last_insert_rowid(); - } - - blob_compress(pBlob, &cmpr); + content_rcvid_init(0); + + if( nBlob ){ + cmpr = pBlob[0]; + }else{ + blob_compress(pBlob, &cmpr); + } if( rid>0 ){ /* We are just adding data to a phantom */ db_prepare(&s1, "UPDATE blob SET rcvid=%d, size=%d, content=:data WHERE rid=%d", g.rcvid, size, rid @@ -419,40 +593,40 @@ } }else{ /* We are creating a new entry */ db_prepare(&s1, "INSERT INTO blob(rcvid,size,uuid,content)" - "VALUES(%d,%d,'%b',:data)", - g.rcvid, size, &hash + "VALUES(%d,%d,'%q',:data)", + g.rcvid, size, blob_str(&hash) ); db_bind_blob(&s1, ":data", &cmpr); db_exec(&s1); rid = db_last_insert_rowid(); if( !pBlob ){ db_multi_exec("INSERT OR IGNORE INTO phantom VALUES(%d)", rid); } - if( g.markPrivate ){ - db_multi_exec("INSERT INTO private VALUES(%d)", rid); - markAsUnclustered = 0; - } + } + if( g.markPrivate || isPrivate ){ + db_multi_exec("INSERT OR IGNORE INTO private VALUES(%d)", rid); + markAsUnclustered = 0; } - blob_reset(&cmpr); + if( nBlob==0 ) blob_reset(&cmpr); /* If the srcId is specified, then the data we just added is ** really a delta. Record this fact in the delta table. */ if( srcId ){ db_multi_exec("REPLACE INTO delta(rid,srcid) VALUES(%d,%d)", rid, srcId); } - if( !isDephantomize && bag_find(&contentCache.missing, rid) && + if( !isDephantomize && bag_find(&contentCache.missing, rid) && (srcId==0 || content_is_available(srcId)) ){ content_mark_available(rid); } if( isDephantomize ){ after_dephantomize(rid, 0); } - + /* Add the element to the unclustered table if has never been ** previously seen. */ if( markAsUnclustered ){ db_multi_exec("INSERT OR IGNORE INTO unclustered VALUES(%d)", rid); @@ -468,16 +642,32 @@ verify_before_commit(rid); return rid; } /* -** Create a new phantom with the given UUID and return its artifact ID. +** This is the simple common case for inserting content into the +** repository. pBlob is the content to be inserted. +** +** pBlob is uncompressed and is not deltaed. It is exactly the content +** to be inserted. +** +** The original content of pBlob is not disturbed. The caller continues +** to be responsible for pBlob. This routine does *not* take over +** responsiblity for freeing pBlob. +*/ +int content_put(Blob *pBlob){ + return content_put_ex(pBlob, 0, 0, 0, 0); +} + + +/* +** Create a new phantom with the given hash and return its artifact ID. */ -int content_new(const char *zUuid){ +int content_new(const char *zUuid, int isPrivate){ int rid; static Stmt s1, s2, s3; - + assert( g.repositoryOpen ); db_begin_transaction(); if( uuid_is_shunned(zUuid) ){ db_end_transaction(0); return 0; @@ -492,11 +682,11 @@ db_static_prepare(&s2, "INSERT INTO phantom VALUES(:rid)" ); db_bind_int(&s2, ":rid", rid); db_exec(&s2); - if( g.markPrivate ){ + if( g.markPrivate || isPrivate ){ db_multi_exec("INSERT INTO private VALUES(%d)", rid); }else{ db_static_prepare(&s3, "INSERT INTO unclustered VALUES(:rid)" ); @@ -508,31 +698,34 @@ return rid; } /* -** COMMAND: test-content-put +** COMMAND: test-content-put +** +** Usage: %fossil test-content-put FILE ** -** Extract a blob from the database and write it into a file. +** Read the content of FILE and add it to the Blob table as a new +** artifact using a direct call to content_put(). */ void test_content_put_cmd(void){ int rid; Blob content; if( g.argc!=3 ) usage("FILENAME"); db_must_be_within_tree(); user_select(); - blob_read_from_file(&content, g.argv[2]); - rid = content_put(&content, 0, 0); - printf("inserted as record %d\n", rid); + blob_read_from_file(&content, g.argv[2], ExtFILE); + rid = content_put(&content); + fossil_print("inserted as record %d\n", rid); } /* ** Make sure the content at rid is the original content and is not a ** delta. */ void content_undelta(int rid){ - if( findSrcid(rid)>0 ){ + if( delta_source_rid(rid)>0 ){ Blob x; if( content_get(rid, &x) ){ Stmt s; db_prepare(&s, "UPDATE blob SET content=:c, size=%d WHERE rid=%d", blob_size(&x), rid); @@ -545,17 +738,17 @@ } } } /* -** COMMAND: test-content-undelta +** COMMAND: test-content-undelta ** ** Make sure the content at RECORDID is not a delta */ void test_content_undelta_cmd(void){ int rid; - if( g.argc!=2 ) usage("RECORDID"); + if( g.argc!=3 ) usage("RECORDID"); db_must_be_within_tree(); rid = atoi(g.argv[2]); content_undelta(rid); } @@ -569,15 +762,15 @@ "SELECT 1 FROM private WHERE rid=:rid" ); db_bind_int(&s1, ":rid", rid); rc = db_step(&s1); db_reset(&s1); - return rc==SQLITE_ROW; + return rc==SQLITE_ROW; } /* -** Make sure an artifact is public. +** Make sure an artifact is public. */ void content_make_public(int rid){ static Stmt s1; db_static_prepare(&s1, "DELETE FROM private WHERE rid=:rid" @@ -585,79 +778,530 @@ db_bind_int(&s1, ":rid", rid); db_exec(&s1); } /* -** Change the storage of rid so that it is a delta of srcid. +** Make sure an artifact is private +*/ +void content_make_private(int rid){ + static Stmt s1; + db_static_prepare(&s1, + "INSERT OR IGNORE INTO private(rid) VALUES(:rid)" + ); + db_bind_int(&s1, ":rid", rid); + db_exec(&s1); +} + +/* +** Try to change the storage of rid so that it is a delta from one +** of the artifacts given in aSrc[0]..aSrc[nSrc-1]. The aSrc[*] that +** gives the smallest delta is choosen. ** ** If rid is already a delta from some other place then no -** conversion occurs and this is a no-op unless force==1. +** conversion occurs and this is a no-op unless force==1. If force==1, +** then nSrc must also be 1. +** +** If rid refers to a phantom, no delta is created. ** ** Never generate 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 and ** public->private and public->public. Just no private->public delta. ** -** If srcid is a delta that depends on rid, then srcid is -** converted to undeltaed text. +** If aSrc[bestSrc] is already a delta that depends on rid, then it is +** converted to undeltaed text before the aSrc[bestSrc]->rid delta is +** created, in order to prevent a delta loop. +** +** If either rid or aSrc[i] contain less than 50 bytes, or if the +** resulting delta does not achieve a compression of at least 25% +** the rid is left untouched. ** -** If either rid or srcid contain less than 50 bytes, or if the -** resulting delta does not achieve a compression of at least 25% on -** its own the rid is left untouched. +** Return 1 if a delta is made and 0 if no delta occurs. */ -void content_deltify(int rid, int srcid, int force){ +int content_deltify(int rid, int *aSrc, int nSrc, int force){ int s; - Blob data, src, delta; - Stmt s1, s2; - if( srcid==rid ) return; - if( !force && findSrcid(rid)>0 ) return; - if( content_is_private(srcid) && !content_is_private(rid) ){ - return; - } - s = srcid; - while( (s = findSrcid(s))>0 ){ - if( s==rid ){ - content_undelta(srcid); - break; - } - } - content_get(srcid, &src); - if( blob_size(&src)<50 ){ - blob_reset(&src); - return; - } + Blob data; /* Content of rid */ + Blob src; /* Content of aSrc[i] */ + Blob delta; /* Delta from aSrc[i] to rid */ + Blob bestDelta; /* Best delta seen so far */ + int bestSrc = 0; /* Which aSrc is the source of the best delta */ + int rc = 0; /* Value to return */ + int i; /* Loop variable for aSrc[] */ + + /* + ** Historically this routine gracefully ignored the rid 0, but the + ** addition of a call to content_is_available() in [188ffef2] caused + ** rid 0 to trigger an assert via bag_find(). Rather than track down + ** all such calls (e.g. the one via /technoteedit), we'll continue + ** to gracefully ignore rid 0 here. + */ + if( 0==rid ) return 0; + + /* If rid is already a child (a delta) of some other artifact, return + ** immediately if the force flags is false + */ + if( !force && delta_source_rid(rid)>0 ) return 0; + + /* If rid refers to a phantom, skip deltification. */ + if( 0==content_is_available(rid) ) return 0; + + /* Get the complete content of the object to be delta-ed. If the size + ** is less than 50 bytes, then there really is no point in trying to do + ** a delta, so return immediately + */ content_get(rid, &data); if( blob_size(&data)<50 ){ - blob_reset(&src); + /* Do not try to create a delta for objects smaller than 50 bytes */ blob_reset(&data); - return; + return 0; + } + blob_init(&bestDelta, 0, 0); + + /* Loop over all candidate delta sources */ + for(i=0; i0 ){ + if( s==rid ){ + content_undelta(srcid); + break; + } + } + if( s!=0 ) continue; + + content_get(srcid, &src); + if( blob_size(&src)<50 ){ + /* The source is smaller then 50 bytes, so don't bother trying to use it*/ + blob_reset(&src); + continue; + } + blob_delta_create(&src, &data, &delta); + if( blob_size(&delta) < blob_size(&data)*0.75 + && (bestSrc<=0 || blob_size(&delta)0 ){ + Stmt s1, s2; /* Statements used to create the delta */ + blob_compress(&bestDelta, &bestDelta); db_prepare(&s1, "UPDATE blob SET content=:data WHERE rid=%d", rid); - db_prepare(&s2, "REPLACE INTO delta(rid,srcid)VALUES(%d,%d)", rid, srcid); - db_bind_blob(&s1, ":data", &delta); + db_prepare(&s2, "REPLACE INTO delta(rid,srcid)VALUES(%d,%d)", rid, bestSrc); + db_bind_blob(&s1, ":data", &bestDelta); db_begin_transaction(); db_exec(&s1); db_exec(&s2); db_end_transaction(0); db_finalize(&s1); db_finalize(&s2); verify_before_commit(rid); + rc = 1; } - blob_reset(&src); blob_reset(&data); - blob_reset(&delta); + blob_reset(&bestDelta); + return rc; +} + +/* +** COMMAND: test-content-deltify +** +** Usage: %fossil RID SRCID SRCID ... [-force] +** +** Convert the content at RID into a delta one of the from SRCIDs. +*/ +void test_content_deltify_cmd(void){ + int nSrc; + int *aSrc; + int i; + int bForce = find_option("force",0,0)!=0; + if( g.argc<3 ) usage("[--force] RID SRCID SRCID..."); + aSrc = fossil_malloc( (g.argc-2)*sizeof(aSrc[0]) ); + nSrc = 0; + for(i=2; i'Z' || z[1]!=' ' || z[0]=='I' ) return 0; + if( z[n-1]!='\n' ) return 0; + return 1; +} + +/* +** COMMAND: test-integrity +** +** Verify that all content can be extracted from the BLOB table correctly. +** If the BLOB table is correct, then the repository can always be +** successfully reconstructed using "fossil rebuild". +** +** Options: +** +** -d|--db-only Run "PRAGMA integrity_check" on the database only. +** No other validation is performed. +** +** --parse Parse all manifests, wikis, tickets, events, and +** so forth, reporting any errors found. +** +** -q|--quick Run "PRAGMA quick_check" on the database only. +** No other validation is performed. +*/ +void test_integrity(void){ + Stmt q; + Blob content; + int n1 = 0; + int n2 = 0; + int nErr = 0; + int total; + int nCA = 0; + int anCA[10]; + int bParse = find_option("parse",0,0)!=0; + int bDbOnly = find_option("db-only","d",0)!=0; + int bQuick = find_option("quick","q",0)!=0; + db_find_and_open_repository(OPEN_ANY_SCHEMA, 2); + if( bDbOnly || bQuick ){ + const char *zType = bQuick ? "quick" : "integrity"; + char *zRes; + zRes = db_text(0,"PRAGMA repository.%s_check", zType/*safe-for-%s*/); + if( fossil_strcmp(zRes,"ok")!=0 ){ + fossil_print("%s_check failed!\n", zType); + exit(1); + }else{ + fossil_print("ok\n"); + } + return; + } + memset(anCA, 0, sizeof(anCA)); + + /* Make sure no public artifact is a delta from a private artifact */ + db_prepare(&q, + "SELECT " + " rid, (SELECT uuid FROM blob WHERE rid=delta.rid)," + " srcid, (SELECT uuid FROM blob WHERE rid=delta.srcid)" + " FROM delta" + " WHERE srcid in private AND rid NOT IN private" + ); + while( db_step(&q)==SQLITE_ROW ){ + int rid = db_column_int(&q, 0); + const char *zId = db_column_text(&q, 1); + int srcid = db_column_int(&q, 2); + const char *zSrc = db_column_text(&q, 3); + fossil_print( + "public artifact %S (%d) is a delta from private artifact %S (%d)\n", + zId, rid, zSrc, srcid + ); + nErr++; + } + db_finalize(&q); + + db_prepare(&q, "SELECT rid, uuid, size FROM blob ORDER BY rid"); + total = db_int(0, "SELECT max(rid) FROM blob"); + while( db_step(&q)==SQLITE_ROW ){ + int rid = db_column_int(&q, 0); + const char *zUuid = db_column_text(&q, 1); + int nUuid = db_column_bytes(&q, 1); + int size = db_column_int(&q, 2); + n1++; + fossil_print(" %d/%d\r", n1, total); + fflush(stdout); + if( size<0 ){ + fossil_print("skip phantom %d %s\n", rid, zUuid); + continue; /* Ignore phantoms */ + } + content_get(rid, &content); + if( blob_size(&content)!=size ){ + fossil_print("size mismatch on artifact %d: wanted %d but got %d\n", + rid, size, blob_size(&content)); + nErr++; + } + if( !hname_verify_hash(&content, zUuid, nUuid) ){ + fossil_print("wrong hash on artifact %d\n",rid); + nErr++; + } + if( bParse && looks_like_control_artifact(&content) ){ + Blob err; + int i, n; + char *z; + Manifest *p; + char zFirstLine[400]; + blob_zero(&err); + + z = blob_buffer(&content); + n = blob_size(&content); + for(i=0; itype]++; + manifest_destroy(p); + nCA++; + } + blob_reset(&err); + }else{ + blob_reset(&content); + } + n2++; + } + db_finalize(&q); + fossil_print("%d non-phantom blobs (out of %d total) checked: %d errors\n", + n2, n1, nErr); + if( bParse ){ + static const char *const azType[] = { 0, "manifest", "cluster", + "control", "wiki", "ticket", "attachment", "event" }; + int i; + fossil_print("%d total control artifacts\n", nCA); + for(i=1; i0;" /* Tags */ + "INSERT INTO used SELECT rid FROM tagxref;" /* Wiki & tickets */ + "INSERT INTO used SELECT rid FROM attachment JOIN blob ON src=uuid;" + "INSERT INTO used SELECT attachid FROM attachment;" + "INSERT INTO used SELECT objid FROM event;" + ); + db_prepare(&q, "SELECT rid, uuid, size FROM blob WHERE rid NOT IN used"); + while( db_step(&q)==SQLITE_ROW ){ + fossil_print("%7d %s size: %d\n", + db_column_int(&q, 0), + db_column_text(&q, 1), + db_column_int(&q,2)); + cnt++; + } + db_finalize(&q); + fossil_print("%d orphans\n", cnt); +} + +/* Allowed flags for check_exists */ +#define MISSING_SHUNNED 0x0001 /* Do not report shunned artifacts */ + +/* This is a helper routine for test-artifacts. +** +** Check to see that the artifact hash referenced by zUuid exists in the +** repository. If it does, return 0. If it does not, generate an error +** message and return 1. +*/ +static int check_exists( + const char *zUuid, /* Hash of the artifact we are checking for */ + unsigned flags, /* Flags */ + Manifest *p, /* The control artifact that references zUuid */ + const char *zRole, /* Role of zUuid in p */ + const char *zDetail /* Additional information, such as a filename */ +){ + static Stmt q; + int rc = 0; + + db_static_prepare(&q, "SELECT size FROM blob WHERE uuid=:uuid"); + if( zUuid==0 || zUuid[0]==0 ) return 0; + db_bind_text(&q, ":uuid", zUuid); + if( db_step(&q)==SQLITE_ROW ){ + int size = db_column_int(&q, 0); + if( size<0 ) rc = 2; + }else{ + rc = 1; + } + db_reset(&q); + if( rc ){ + const char *zCFType = "control artifact"; + char *zSrc; + char *zDate; + const char *zErrType = "MISSING"; + if( db_exists("SELECT 1 FROM shun WHERE uuid=%Q", zUuid) ){ + if( flags & MISSING_SHUNNED ) return 0; + zErrType = "SHUNNED"; + } + switch( p->type ){ + case CFTYPE_MANIFEST: zCFType = "check-in"; break; + case CFTYPE_CLUSTER: zCFType = "cluster"; break; + case CFTYPE_CONTROL: zCFType = "tag"; break; + case CFTYPE_WIKI: zCFType = "wiki"; break; + case CFTYPE_TICKET: zCFType = "ticket"; break; + case CFTYPE_ATTACHMENT: zCFType = "attachment"; break; + case CFTYPE_EVENT: zCFType = "event"; break; + } + zSrc = db_text(0, "SELECT uuid FROM blob WHERE rid=%d", p->rid); + if( p->rDate>0.0 ){ + zDate = db_text(0, "SELECT datetime(%.17g)", p->rDate); + }else{ + zDate = db_text(0, + "SELECT datetime(rcvfrom.mtime)" + " FROM blob, rcvfrom" + " WHERE blob.rcvid=rcvfrom.rcvid" + " AND blob.rid=%d", p->rid); + } + fossil_print("%s: %s\n %s %s %S (%d) %s\n", + zErrType, zUuid, zRole, zCFType, zSrc, p->rid, zDate); + if( zDetail && zDetail[0] ){ + fossil_print(" %s\n", zDetail); + } + fossil_free(zSrc); + fossil_free(zDate); + rc = 1; + } + return rc; +} + +/* +** COMMAND: test-missing +** +** Usage: %fossil test-missing +** +** Look at every artifact in the repository and verify that +** all references are satisfied. Report any referenced artifacts +** that are missing or shunned. +** +** Options: +** +** --notshunned Do not report shunned artifacts +** --quiet Only show output if there are errors +*/ +void test_missing(void){ + Stmt q; + Blob content; + int nErr = 0; + int nArtifact = 0; + int i; + Manifest *p; + unsigned flags = 0; + int quietFlag; + + if( find_option("notshunned", 0, 0)!=0 ) flags |= MISSING_SHUNNED; + quietFlag = find_option("quiet","q",0)!=0; + db_find_and_open_repository(OPEN_ANY_SCHEMA, 0); + db_prepare(&q, + "SELECT mid FROM mlink UNION " + "SELECT srcid FROM tagxref WHERE srcid>0 UNION " + "SELECT rid FROM tagxref UNION " + "SELECT rid FROM attachment JOIN blob ON src=uuid UNION " + "SELECT objid FROM event"); + while( db_step(&q)==SQLITE_ROW ){ + int rid = db_column_int(&q, 0); + content_get(rid, &content); + p = manifest_parse(&content, rid, 0); + if( p ){ + nArtifact++; + nErr += check_exists(p->zBaseline, flags, p, "baseline of", 0); + nErr += check_exists(p->zAttachSrc, flags, p, "file of", 0); + for(i=0; inFile; i++){ + nErr += check_exists(p->aFile[i].zUuid, flags, p, "file of", + p->aFile[i].zName); + } + for(i=0; inParent; i++){ + nErr += check_exists(p->azParent[i], flags, p, "parent of", 0); + } + for(i=0; inCherrypick; i++){ + nErr += check_exists(p->aCherrypick[i].zCPTarget+1, flags, p, + "cherry-pick target of", 0); + nErr += check_exists(p->aCherrypick[i].zCPBase, flags, p, + "cherry-pick baseline of", 0); + } + for(i=0; inCChild; i++){ + nErr += check_exists(p->azCChild[i], flags, p, "in", 0); + } + for(i=0; inTag; i++){ + nErr += check_exists(p->aTag[i].zUuid, flags, p, "target of", 0); + } + manifest_destroy(p); + } + } + db_finalize(&q); + if( nErr>0 || quietFlag==0 ){ + fossil_print("%d missing or shunned references in %d control artifacts\n", + nErr, nArtifact); + } } /* -** COMMAND: test-content-deltify +** COMMAND: test-content-erase +** +** Usage: %fossil test-content-erase RID .... +** +** Remove all traces of one or more artifacts from the local repository. +** +** WARNING: This command destroys data and can cause you to lose work. +** Make sure you have a backup copy before using this command! +** +** WARNING: You must run "fossil rebuild" after this command to rebuild +** the metadata. ** -** Convert the content at RID into a delta from SRCID. +** Note that the arguments are the integer raw RID values from the BLOB table, +** not artifact hashes or labels. */ -void test_content_deltify_cmd(void){ - if( g.argc!=5 ) usage("RID SRCID FORCE"); - db_must_be_within_tree(); - content_deltify(atoi(g.argv[2]), atoi(g.argv[3]), atoi(g.argv[4])); +void test_content_erase(void){ + int i; + Blob x; + char c; + Stmt q; + prompt_user("This command erases information from the repository and\n" + "might irrecoverably damage the repository. Make sure you\n" + "have a backup copy!\n" + "Continue? (y/N)? ", &x); + c = blob_str(&x)[0]; + blob_reset(&x); + if( c!='y' && c!='Y' ) return; + db_find_and_open_repository(OPEN_ANY_SCHEMA, 0); + db_begin_transaction(); + db_prepare(&q, "SELECT rid FROM delta WHERE srcid=:rid"); + for(i=2; i +#include + +#if INTERFACE +/* the standard name of the display settings cookie for fossil */ +# define DISPLAY_SETTINGS_COOKIE "fossil_display_settings" +#endif + + +/* +** State information private to this module +*/ +#define COOKIE_NPARAM 10 +static struct { + char *zCookieValue; /* Value of the user preferences cookie */ + int bChanged; /* True if any value has changed */ + int bIsInit; /* True after initialization */ + int nParam; /* Number of parameters in the cookie */ + struct { + const char *zPName; /* Name of a parameter */ + char *zPValue; /* Value of that parameter */ + } aParam[COOKIE_NPARAM]; +} cookies; + +/* Initialize this module by parsing the content of the cookie named +** by DISPLAY_SETTINGS_COOKIE +*/ +void cookie_parse(void){ + char *z; + if( cookies.bIsInit ) return; + z = (char*)P(DISPLAY_SETTINGS_COOKIE); + if( z==0 ) z = ""; + cookies.zCookieValue = z = mprintf("%s", z); + cookies.bIsInit = 1; + while( cookies.nParam0 ) blob_append(&new, ",", 1); + blob_appendf(&new, "%s=%T", + cookies.aParam[i].zPName, cookies.aParam[i].zPValue); + } + cgi_set_cookie(DISPLAY_SETTINGS_COOKIE, blob_str(&new), 0, 31536000); + } + cookies.bIsInit = 0; +} + +/* Return the value of a preference cookie. +*/ +const char *cookie_value(const char *zPName, const char *zDefault){ + int i; + assert( zPName!=0 ); + cookie_parse(); + for(i=0; i + @
    + for(i=0; cgi_param_info(i, &zName, &zValue, &isQP); i++){ + char *zDel; + if( isQP ) continue; + if( fossil_isupper(zName[0]) ) continue; + zDel = mprintf("del%s",zName); + if( P(zDel)!=0 ){ + cgi_set_cookie(zName, "", 0, -1); + cgi_redirect("cookies"); + } + nCookie++; + @
  1. %h(zName): %h(zValue) + @ + if( fossil_strcmp(zName, DISPLAY_SETTINGS_COOKIE)==0 && cookies.nParam>0 ){ + int j; + @

      + for(j=0; j%h(cookies.aParam[j].zPName): "%h(cookies.aParam[j].zPValue)" + } + @
    + } + fossil_free(zDel); + } + @
+ @ + if( nCookie==0 ){ + @

No cookies for this website

+ } + style_finish_page(); +} ADDED src/copybtn.js Index: src/copybtn.js ================================================================== --- /dev/null +++ src/copybtn.js @@ -0,0 +1,99 @@ +/* Manage "Copy Buttons" linked to target elements, to copy the text (or, parts +** thereof) of the target elements to the clipboard. +** +** Newly created buttons are elements with an SVG background icon, +** defined by the "copy-button" class in the default CSS style sheet, and are +** assigned the element ID "copy-". +** +** To simplify customization, the only properties modified for HTML-defined +** buttons are the "onclick" handler, and the "transition" and "opacity" styles +** (used for animation). +** +** For HTML-defined buttons, either initCopyButtonById(), or initCopyButton(), +** needs to be called to attach the "onclick" handler (done automatically from +** a handler attached to the "DOMContentLoaded" event). +** +** The initialization functions do not overwrite the "data-copytarget" and +** "data-copylength" attributes with empty or null values for and +** , respectively. Set to "-1" to explicitly remove the +** previous copy length limit. +** +** HTML snippet for statically created buttons: +** +** +*/ +function makeCopyButton(idTarget,bFlipped,cchLength){ + var elButton = document.createElement("span"); + elButton.className = "copy-button"; + if( bFlipped ) elButton.className += " copy-button-flipped"; + elButton.id = "copy-" + idTarget; + initCopyButton(elButton,idTarget,cchLength); + return elButton; +} +function initCopyButtonById(idButton,idTarget,cchLength){ + idButton = idButton || "copy-" + idTarget; + var elButton = document.getElementById(idButton); + if( elButton ) initCopyButton(elButton,idTarget,cchLength); + return elButton; +} +function initCopyButton(elButton,idTarget,cchLength){ + elButton.style.transition = ""; + elButton.style.opacity = 1; + if( idTarget ) elButton.setAttribute("data-copytarget",idTarget); + if( cchLength ) elButton.setAttribute("data-copylength",cchLength); + elButton.onclick = clickCopyButton; + return elButton; +} +setTimeout(function(){ + var aButtons = document.getElementsByClassName("copy-button"); + for ( var i=0; i and
+ style_finish_page(); +} + +/* +** Add a new tech note to the repository. The timestamp is +** given by the zETime parameter. rid must be zero to create +** a new page. If no previous page with the name zPageName exists +** and isNew is false, then this routine throws an error. +*/ +void event_cmd_commit( + char *zETime, /* timestamp */ + int rid, /* Artifact id of the tech note */ + Blob *pContent, /* content of the new page */ + const char *zMimeType, /* mimetype of the content */ + const char *zComment, /* comment to go on the timeline */ + const char *zTags, /* tags */ + const char *zClr /* background color */ +){ + const char *zId; /* id of the tech note */ + + if ( rid==0 ){ + zId = db_text(0, "SELECT lower(hex(randomblob(20)))"); + }else{ + zId = db_text(0, + "SELECT substr(tagname,7) FROM tag" + " WHERE tagid=(SELECT tagid FROM event WHERE objid='%d')", + rid + ); + } + + user_select(); + if (event_commit_common(rid, zId, blob_str(pContent), zETime, + zMimeType, zComment, zTags, zClr)==0 ){ +#ifdef FOSSIL_ENABLE_JSON + g.json.resultCode = FSL_JSON_E_ASSERT; +#endif + fossil_panic("Internal error: Fossil tried to make an " + "invalid artifact for the technote."); + } +} ADDED src/export.c Index: src/export.c ================================================================== --- /dev/null +++ src/export.c @@ -0,0 +1,1854 @@ +/* +** 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@sqlite.org +** +******************************************************************************* +** +** This file contains code used to export the content of a Fossil +** repository in the git-fast-import format. +*/ +#include "config.h" +#include "export.h" +#include + +/* +** State information common to all export types. +*/ +static struct { + const char *zTrunkName; /* Name of trunk branch */ +} gexport; + +#if INTERFACE +/* +** Each line in a git-fast-export "marK" file is an instance of +** this object. +*/ +struct mark_t { + char *name; /* Name of the mark. Also starts with ":" */ + int rid; /* Corresponding object in the BLOB table */ + char uuid[65]; /* The GIT hash name for this object */ +}; +#endif + +/* +** Output a "committer" record for the given user. +** NOTE: the given user name may be an email itself. +*/ +static void print_person(const char *zUser){ + static Stmt q; + const char *zContact; + char *zName; + char *zEmail; + int i, j; + int isBracketed, atEmailFirst, atEmailLast; + + if( zUser==0 ){ + printf(" "); + return; + } + db_static_prepare(&q, "SELECT info FROM user WHERE login=:user"); + db_bind_text(&q, ":user", zUser); + if( db_step(&q)!=SQLITE_ROW ){ + db_reset(&q); + zName = mprintf("%s", zUser); + for(i=j=0; zName[i]; i++){ + if( zName[i]!='<' && zName[i]!='>' && zName[i]!='"' ){ + zName[j++] = zName[i]; + } + } + zName[j] = 0; + printf(" %s <%s>", zName, zName); + free(zName); + return; + } + + /* + ** We have contact information. + ** It may or may not contain an email address. + ** + ** ASSUME: + ** - General case:"Name Unicoded" other info + ** - If contact information contains more than an email address, + ** then the email address is enclosed between <> + ** - When only email address is specified, then it's stored verbatim + ** - When name part is absent or all-blanks, use zUser instead + */ + zName = NULL; + zEmail = NULL; + zContact = db_column_text(&q, 0); + atEmailFirst = -1; + atEmailLast = -1; + isBracketed = 0; + for(i=0; zContact[i] && zContact[i]!='@'; i++){ + if( zContact[i]=='<' ){ + isBracketed = 1; + atEmailFirst = i+1; + } + else if( zContact[i]=='>' ){ + isBracketed = 0; + atEmailFirst = i+1; + } + else if( zContact[i]==' ' && !isBracketed ){ + atEmailFirst = i+1; + } + } + if( zContact[i]==0 ){ + /* No email address found. Take as user info if not empty */ + zName = mprintf("%s", zContact[0] ? zContact : zUser); + for(i=j=0; zName[i]; i++){ + if( zName[i]!='<' && zName[i]!='>' && zName[i]!='"' ){ + zName[j++] = zName[i]; + } + } + zName[j] = 0; + + printf(" %s <%s>", zName, zName); + free(zName); + db_reset(&q); + return; + } + for(j=i+1; zContact[j] && zContact[j]!=' '; j++){ + if( zContact[j]=='>' ) + atEmailLast = j-1; + } + if ( atEmailLast==-1 ) atEmailLast = j-1; + if ( atEmailFirst==-1 ) atEmailFirst = 0; /* Found only email */ + + /* + ** Found beginning and end of email address. + ** Extract the address (trimmed and sanitized). + */ + for(j=atEmailFirst; zContact[j] && zContact[j]==' '; j++){} + zEmail = mprintf("%.*s", atEmailLast-j+1, &zContact[j]); + + for(i=j=0; zEmail[i]; i++){ + if( zEmail[i]!='<' && zEmail[i]!='>' ){ + zEmail[j++] = zEmail[i]; + } + } + zEmail[j] = 0; + + /* + ** When bracketed email, extract the string _before_ + ** email as user name (may be enquoted). + ** If missing or all-blank name, use zUser. + */ + if( isBracketed && (atEmailFirst-1) > 0){ + for(i=atEmailFirst-2; i>=0 && zContact[i] && zContact[i]==' '; i--){} + if( i>=0 ){ + for(j=0; j", zName, zEmail); + free(zName); + free(zEmail); + db_reset(&q); +} + +#define REFREPLACEMENT '_' + +/* +** Output a sanitized git named reference. +** https://git-scm.com/docs/git-check-ref-format +** This implementation assumes we are only printing +** the branch or tag part of the reference. +*/ +static void print_ref(const char *zRef){ + char *zEncoded = mprintf("%s", zRef); + int i, w; + if (zEncoded[0]=='@' && zEncoded[1]=='\0'){ + putchar(REFREPLACEMENT); + return; + } + for(i=0, w=0; zEncoded[i]; i++, w++){ + if( i!=0 ){ /* Two letter tests */ + if( (zEncoded[i-1]=='.' && zEncoded[i]=='.') || + (zEncoded[i-1]=='@' && zEncoded[i]=='{') ){ + zEncoded[w]=zEncoded[w-1]=REFREPLACEMENT; + continue; + } + if( zEncoded[i-1]=='/' && zEncoded[i]=='/' ){ + w--; /* Normalise to a single / by rolling back w */ + continue; + } + } + /* No control characters */ + if( (unsigned)zEncoded[i]<0x20 || zEncoded[i]==0x7f ){ + zEncoded[w]=REFREPLACEMENT; + continue; + } + switch( zEncoded[i] ){ + case ' ': + case '^': + case ':': + case '?': + case '*': + case '[': + case '\\': + zEncoded[w]=REFREPLACEMENT; + break; + } + } + /* Cannot begin with a . or / */ + if( zEncoded[0]=='.' || zEncoded[0] == '/' ) zEncoded[0]=REFREPLACEMENT; + if( i>0 ){ + i--; w--; + /* Or end with a . or / */ + if( zEncoded[i]=='.' || zEncoded[i] == '/' ) zEncoded[w]=REFREPLACEMENT; + /* Cannot end with .lock */ + if ( i>4 && strcmp((zEncoded+i)-5, ".lock")==0 ) + memset((zEncoded+w)-5, REFREPLACEMENT, 5); + } + printf("%s", zEncoded); + free(zEncoded); +} + +#define BLOBMARK(rid) ((rid) * 2) +#define COMMITMARK(rid) ((rid) * 2 + 1) + +/* +** insert_commit_xref() +** Insert a new (mark,rid,uuid) entry into the 'xmark' table. +** zName and zUuid must be non-null and must point to NULL-terminated strings. +*/ +void insert_commit_xref(int rid, const char *zName, const char *zUuid){ + db_multi_exec( + "INSERT OR IGNORE INTO xmark(tname, trid, tuuid)" + "VALUES(%Q,%d,%Q)", + zName, rid, zUuid + ); +} + +/* +** create_mark() +** Create a new (mark,rid,uuid) entry for the given rid in the 'xmark' table, +** and return that information as a struct mark_t in *mark. +** *unused_mark is a value representing a mark that is free for use--that is, +** it does not appear in the marks file, and has not been used during this +** export run. Specifically, it is the supremum of the set of used marks +** plus one. +** This function returns -1 in the case where 'rid' does not exist, otherwise +** it returns 0. +** mark->name is dynamically allocated and is owned by the caller upon return. +*/ +int create_mark(int rid, struct mark_t *mark, unsigned int *unused_mark){ + char sid[13]; + char *zUuid = rid_to_uuid(rid); + if( !zUuid ){ + fossil_trace("Undefined rid=%d\n", rid); + return -1; + } + mark->rid = rid; + sqlite3_snprintf(sizeof(sid), sid, ":%d", *unused_mark); + *unused_mark += 1; + mark->name = fossil_strdup(sid); + sqlite3_snprintf(sizeof(mark->uuid), mark->uuid, "%s", zUuid); + free(zUuid); + insert_commit_xref(mark->rid, mark->name, mark->uuid); + return 0; +} + +/* +** mark_name_from_rid() +** Find the mark associated with the given rid. Mark names always start +** with ':', and are pulled from the 'xmark' temporary table. +** If the given rid doesn't have a mark associated with it yet, one is +** created with a value of *unused_mark. +** *unused_mark functions exactly as in create_mark(). +** This function returns NULL if the rid does not have an associated UUID, +** (i.e. is not valid). Otherwise, it returns the name of the mark, which is +** dynamically allocated and is owned by the caller of this function. +*/ +char * mark_name_from_rid(int rid, unsigned int *unused_mark){ + char *zMark = db_text(0, "SELECT tname FROM xmark WHERE trid=%d", rid); + if( zMark==NULL ){ + struct mark_t mark; + if( create_mark(rid, &mark, unused_mark)==0 ){ + zMark = mark.name; + }else{ + return NULL; + } + } + return zMark; +} + +/* +** Parse a single line of the mark file. Store the result in the mark object. +** +** "line" is a single line of input. +** This function returns -1 in the case that the line is blank, malformed, or +** the rid/uuid named in 'line' does not match what is in the repository +** database. Otherwise, 0 is returned. +** +** mark->name is dynamically allocated, and owned by the caller. +*/ +int parse_mark(char *line, struct mark_t *mark){ + char *cur_tok; + char type_; + cur_tok = strtok(line, " \t"); + if( !cur_tok || strlen(cur_tok)<2 ){ + return -1; + } + mark->rid = atoi(&cur_tok[1]); + type_ = cur_tok[0]; + if( type_!='c' && type_!='b' ){ + /* This is probably a blob mark */ + mark->name = NULL; + return 0; + } + + cur_tok = strtok(NULL, " \t"); + if( !cur_tok ){ + /* This mark was generated by an older version of Fossil and doesn't + ** include the mark name and uuid. create_mark() will name the new mark + ** exactly as it was when exported to git, so that we should have a + ** valid mapping from git hash<->mark name<->fossil hash. */ + unsigned int mid; + if( type_=='c' ){ + mid = COMMITMARK(mark->rid); + } + else{ + mid = BLOBMARK(mark->rid); + } + return create_mark(mark->rid, mark, &mid); + }else{ + mark->name = fossil_strdup(cur_tok); + } + + cur_tok = strtok(NULL, "\n"); + if( !cur_tok || (strlen(cur_tok)!=40 && strlen(cur_tok)!=64) ){ + free(mark->name); + fossil_trace("Invalid SHA-1/SHA-3 in marks file: %s\n", cur_tok); + return -1; + }else{ + sqlite3_snprintf(sizeof(mark->uuid), mark->uuid, "%s", cur_tok); + } + + /* make sure that rid corresponds to UUID */ + if( fast_uuid_to_rid(mark->uuid)!=mark->rid ){ + free(mark->name); + fossil_trace("Non-existent SHA-1/SHA-3 in marks file: %s\n", mark->uuid); + return -1; + } + + /* insert a cross-ref into the 'xmark' table */ + insert_commit_xref(mark->rid, mark->name, mark->uuid); + return 0; +} + +/* +** Import the marks specified in file 'f'; +** If 'blobs' is non-null, insert all blob marks into it. +** If 'vers' is non-null, insert all commit marks into it. +** If 'unused_marks' is non-null, upon return of this function, all values +** x >= *unused_marks are free to use as marks, i.e. they do not clash with +** any marks appearing in the marks file. +** +** Each line in the file must be at most 100 characters in length. This +** seems like a reasonable maximum for a 40-character uuid, and 1-13 +** character rid. +** +** The function returns -1 if any of the lines in file 'f' are malformed, +** or the rid/uuid information doesn't match what is in the repository +** database. Otherwise, 0 is returned. +*/ +int import_marks(FILE* f, Bag *blobs, Bag *vers, unsigned int *unused_mark){ + char line[101]; + while(fgets(line, sizeof(line), f)){ + struct mark_t mark; + if( strlen(line)==100 && line[99]!='\n' ){ + /* line too long */ + return -1; + } + if( parse_mark(line, &mark)<0 ){ + return -1; + }else if( line[0]=='b' ){ + if( blobs!=NULL ){ + bag_insert(blobs, mark.rid); + } + }else{ + if( vers!=NULL ){ + bag_insert(vers, mark.rid); + } + } + if( unused_mark!=NULL ){ + unsigned int mid = atoi(mark.name + 1); + if( mid>=*unused_mark ){ + *unused_mark = mid + 1; + } + } + free(mark.name); + } + return 0; +} + +void export_mark(FILE* f, int rid, char obj_type) +{ + unsigned int z = 0; + char *zUuid = rid_to_uuid(rid); + char *zMark; + if( zUuid==NULL ){ + fossil_trace("No uuid matching rid=%d when exporting marks\n", rid); + return; + } + /* Since rid is already in the 'xmark' table, the value of z won't be + ** used, but pass in a valid pointer just to be safe. */ + zMark = mark_name_from_rid(rid, &z); + fprintf(f, "%c%d %s %s\n", obj_type, rid, zMark, zUuid); + free(zMark); + free(zUuid); +} + +/* +** If 'blobs' is non-null, it must point to a Bag of blob rids to be +** written to disk. Blob rids are written as 'b'. +** If 'vers' is non-null, it must point to a Bag of commit rids to be +** written to disk. Commit rids are written as 'c : '. +** All commit (mark,rid,uuid) tuples are stored in 'xmark' table. +** This function does not fail, but may produce errors if a uuid cannot +** be found for an rid in 'vers'. +*/ +void export_marks(FILE* f, Bag *blobs, Bag *vers){ + int rid; + + if( blobs!=NULL ){ + rid = bag_first(blobs); + if( rid!=0 ){ + do{ + export_mark(f, rid, 'b'); + }while( (rid = bag_next(blobs, rid))!=0 ); + } + } + if( vers!=NULL ){ + rid = bag_first(vers); + if( rid!=0 ){ + do{ + export_mark(f, rid, 'c'); + }while( (rid = bag_next(vers, rid))!=0 ); + } + } +} + +/* This is the original header command (and hence documentation) for +** the "fossil export" command: +** +** Usage: %fossil export --git ?OPTIONS? ?REPOSITORY? +** +** Write an export of all check-ins to standard output. The export is +** written in the git-fast-export file format assuming the --git option is +** provided. The git-fast-export format is currently the only VCS +** interchange format supported, though other formats may be added in +** the future. +** +** Run this command within a checkout. Or use the -R or --repository +** option to specify a Fossil repository to be exported. +** +** Only check-ins are exported using --git. Git does not support tickets +** or wiki or tech notes or attachments, so none of those are exported. +** +** If the "--import-marks FILE" option is used, it contains a list of +** rids to skip. +** +** If the "--export-marks FILE" option is used, the rid of all commits and +** blobs written on exit for use with "--import-marks" on the next run. +** +** Options: +** --export-marks FILE Export rids of exported data to FILE +** --import-marks FILE Read rids of data to ignore from FILE +** --rename-trunk NAME Use NAME as name of exported trunk branch +** -R|--repository REPO Export the given REPOSITORY +** +** See also: import +*/ +/* +** COMMAND: export* +** +** This command is deprecated. Use "fossil git export" instead. +*/ +void export_cmd(void){ + Stmt q, q2, q3; + Bag blobs, vers; + unsigned int unused_mark = 1; + const char *markfile_in; + const char *markfile_out; + + bag_init(&blobs); + bag_init(&vers); + + find_option("git", 0, 0); /* Ignore the --git option for now */ + markfile_in = find_option("import-marks", 0, 1); + markfile_out = find_option("export-marks", 0, 1); + + if( !(gexport.zTrunkName = find_option("rename-trunk", 0, 1)) ){ + gexport.zTrunkName = "trunk"; + } + + db_find_and_open_repository(0, 2); + verify_all_options(); + if( g.argc!=2 && g.argc!=3 ){ usage("--git ?REPOSITORY?"); } + + db_multi_exec("CREATE TEMPORARY TABLE oldblob(rid INTEGER PRIMARY KEY)"); + db_multi_exec("CREATE TEMPORARY TABLE oldcommit(rid INTEGER PRIMARY KEY)"); + db_multi_exec("CREATE TEMP TABLE xmark(tname TEXT UNIQUE, trid INT," + " tuuid TEXT)"); + db_multi_exec("CREATE INDEX xmark_trid ON xmark(trid)"); + if( markfile_in!=0 ){ + Stmt qb,qc; + FILE *f; + int rid; + + f = fossil_fopen(markfile_in, "r"); + if( f==0 ){ + fossil_fatal("cannot open %s for reading", markfile_in); + } + if( import_marks(f, &blobs, &vers, &unused_mark)<0 ){ + fossil_fatal("error importing marks from file: %s", markfile_in); + } + db_prepare(&qb, "INSERT OR IGNORE INTO oldblob VALUES (:rid)"); + db_prepare(&qc, "INSERT OR IGNORE INTO oldcommit VALUES (:rid)"); + rid = bag_first(&blobs); + if( rid!=0 ){ + do{ + db_bind_int(&qb, ":rid", rid); + db_step(&qb); + db_reset(&qb); + }while((rid = bag_next(&blobs, rid))!=0); + } + rid = bag_first(&vers); + if( rid!=0 ){ + do{ + db_bind_int(&qc, ":rid", rid); + db_step(&qc); + db_reset(&qc); + }while((rid = bag_next(&vers, rid))!=0); + } + db_finalize(&qb); + db_finalize(&qc); + fclose(f); + } + + /* Step 1: Generate "blob" records for every artifact that is part + ** of a check-in + */ + fossil_binary_mode(stdout); + db_multi_exec("CREATE TEMP TABLE newblob(rid INTEGER KEY, srcid INTEGER)"); + db_multi_exec("CREATE INDEX newblob_src ON newblob(srcid)"); + db_multi_exec( + "INSERT INTO newblob" + " SELECT DISTINCT fid," + " CASE WHEN EXISTS(SELECT 1 FROM delta" + " WHERE rid=fid" + " AND NOT EXISTS(SELECT 1 FROM oldblob" + " WHERE srcid=fid))" + " THEN (SELECT srcid FROM delta WHERE rid=fid)" + " ELSE 0" + " END" + " FROM mlink" + " WHERE fid>0 AND NOT EXISTS(SELECT 1 FROM oldblob WHERE rid=fid)"); + db_prepare(&q, + "SELECT DISTINCT fid FROM mlink" + " WHERE fid>0 AND NOT EXISTS(SELECT 1 FROM oldblob WHERE rid=fid)"); + db_prepare(&q2, "INSERT INTO oldblob VALUES (:rid)"); + db_prepare(&q3, "SELECT rid FROM newblob WHERE srcid= (:srcid)"); + while( db_step(&q)==SQLITE_ROW ){ + int rid = db_column_int(&q, 0); + Blob content; + + while( !bag_find(&blobs, rid) ){ + char *zMark; + content_get(rid, &content); + db_bind_int(&q2, ":rid", rid); + db_step(&q2); + db_reset(&q2); + zMark = mark_name_from_rid(rid, &unused_mark); + printf("blob\nmark %s\ndata %d\n", zMark, blob_size(&content)); + free(zMark); + bag_insert(&blobs, rid); + fwrite(blob_buffer(&content), 1, blob_size(&content), stdout); + printf("\n"); + blob_reset(&content); + + db_bind_int(&q3, ":srcid", rid); + if( db_step(&q3) != SQLITE_ROW ){ + db_reset(&q3); + break; + } + rid = db_column_int(&q3, 0); + db_reset(&q3); + } + } + db_finalize(&q); + db_finalize(&q2); + db_finalize(&q3); + + /* Output the commit records. + */ + topological_sort_checkins(0); + db_prepare(&q, + "SELECT strftime('%%s',mtime), objid, coalesce(ecomment,comment)," + " coalesce(euser,user)," + " (SELECT value FROM tagxref WHERE rid=objid AND tagid=%d)" + " FROM toponode, event" + " WHERE toponode.tid=event.objid" + " AND event.type='ci'" + " AND NOT EXISTS (SELECT 1 FROM oldcommit WHERE toponode.tid=rid)" + " ORDER BY toponode.tseq ASC", + TAG_BRANCH + ); + db_prepare(&q2, "INSERT INTO oldcommit VALUES (:rid)"); + while( db_step(&q)==SQLITE_ROW ){ + Stmt q4; + const char *zSecondsSince1970 = db_column_text(&q, 0); + int ckinId = db_column_int(&q, 1); + const char *zComment = db_column_text(&q, 2); + const char *zUser = db_column_text(&q, 3); + const char *zBranch = db_column_text(&q, 4); + char *zMark; + + bag_insert(&vers, ckinId); + db_bind_int(&q2, ":rid", ckinId); + db_step(&q2); + db_reset(&q2); + if( zBranch==0 || fossil_strcmp(zBranch, "trunk")==0 ){ + zBranch = gexport.zTrunkName; + } + zMark = mark_name_from_rid(ckinId, &unused_mark); + printf("commit refs/heads/"); + print_ref(zBranch); + printf("\nmark %s\n", zMark); + free(zMark); + printf("committer"); + print_person(zUser); + printf(" %s +0000\n", zSecondsSince1970); + if( zComment==0 ) zComment = "null comment"; + printf("data %d\n%s\n", (int)strlen(zComment), zComment); + db_prepare(&q3, + "SELECT pid FROM plink" + " WHERE cid=%d AND isprim" + " AND pid IN (SELECT objid FROM event)", + ckinId + ); + if( db_step(&q3) == SQLITE_ROW ){ + int pid = db_column_int(&q3, 0); + zMark = mark_name_from_rid(pid, &unused_mark); + printf("from %s\n", zMark); + free(zMark); + db_prepare(&q4, + "SELECT pid FROM plink" + " WHERE cid=%d AND NOT isprim" + " AND NOT EXISTS(SELECT 1 FROM phantom WHERE rid=pid)" + " ORDER BY pid", + ckinId); + while( db_step(&q4)==SQLITE_ROW ){ + zMark = mark_name_from_rid(db_column_int(&q4, 0), &unused_mark); + printf("merge %s\n", zMark); + free(zMark); + } + db_finalize(&q4); + }else{ + printf("deleteall\n"); + } + + db_prepare(&q4, + "SELECT filename.name, mlink.fid, mlink.mperm FROM mlink" + " JOIN filename ON filename.fnid=mlink.fnid" + " WHERE mlink.mid=%d", + ckinId + ); + while( db_step(&q4)==SQLITE_ROW ){ + const char *zName = db_column_text(&q4,0); + int zNew = db_column_int(&q4,1); + int mPerm = db_column_int(&q4,2); + if( zNew==0 ){ + printf("D %s\n", zName); + }else if( bag_find(&blobs, zNew) ){ + const char *zPerm; + zMark = mark_name_from_rid(zNew, &unused_mark); + switch( mPerm ){ + case PERM_LNK: zPerm = "120000"; break; + case PERM_EXE: zPerm = "100755"; break; + default: zPerm = "100644"; break; + } + printf("M %s %s %s\n", zPerm, zMark, zName); + free(zMark); + } + } + db_finalize(&q4); + db_finalize(&q3); + printf("\n"); + } + db_finalize(&q2); + db_finalize(&q); + manifest_cache_clear(); + + + /* Output tags */ + db_prepare(&q, + "SELECT tagname, rid, strftime('%%s',mtime)," + " (SELECT coalesce(euser, user) FROM event WHERE objid=rid)," + " value" + " FROM tagxref JOIN tag USING(tagid)" + " WHERE tagtype=1 AND tagname GLOB 'sym-*'" + ); + while( db_step(&q)==SQLITE_ROW ){ + const char *zTagname = db_column_text(&q, 0); + int rid = db_column_int(&q, 1); + char *zMark = mark_name_from_rid(rid, &unused_mark); + const char *zSecSince1970 = db_column_text(&q, 2); + const char *zUser = db_column_text(&q, 3); + const char *zValue = db_column_text(&q, 4); + if( rid==0 || !bag_find(&vers, rid) ) continue; + zTagname += 4; + printf("tag "); + print_ref(zTagname); + printf("\nfrom %s\n", zMark); + free(zMark); + printf("tagger"); + print_person(zUser); + printf(" %s +0000\n", zSecSince1970); + printf("data %d\n", zValue==NULL?0:(int)strlen(zValue)+1); + if( zValue!=NULL ) printf("%s\n",zValue); + } + db_finalize(&q); + + if( markfile_out!=0 ){ + FILE *f; + f = fossil_fopen(markfile_out, "w"); + if( f == 0 ){ + fossil_fatal("cannot open %s for writing", markfile_out); + } + export_marks(f, &blobs, &vers); + if( ferror(f)!=0 || fclose(f)!=0 ){ + fossil_fatal("error while writing %s", markfile_out); + } + } + bag_clear(&blobs); + bag_clear(&vers); +} + +/* +** Construct the temporary table toposort as follows: +** +** CREATE TEMP TABLE toponode( +** tid INTEGER PRIMARY KEY, -- Check-in id +** tseq INT -- integer total order on check-ins. +** ); +** +** This table contains all check-ins of the repository in topological +** order. "Topological order" means that every parent check-in comes +** before all of its children. Topological order is *almost* the same +** thing as "ORDER BY event.mtime". Differences only arise when there +** are timewarps. In as much as Git hates timewarps, we have to compute +** a correct topological order when doing an export. +** +** Since mtime is a usually already nearly in topological order, the +** algorithm is to start with mtime, then make adjustments as necessary +** for timewarps. This is not a great algorithm for the general case, +** but it is very fast for the overwhelmingly common case where there +** are few timewarps. +*/ +int topological_sort_checkins(int bVerbose){ + int nChange = 0; + Stmt q1; + Stmt chng; + db_multi_exec( + "CREATE TEMP TABLE toponode(\n" + " tid INTEGER PRIMARY KEY,\n" + " tseq INT\n" + ");\n" + "INSERT INTO toponode(tid,tseq) " + " SELECT objid, CAST(mtime*8640000 AS int) FROM event WHERE type='ci';\n" + "CREATE TEMP TABLE topolink(\n" + " tparent INT,\n" + " tchild INT,\n" + " PRIMARY KEY(tparent,tchild)\n" + ") WITHOUT ROWID;" + "INSERT INTO topolink(tparent,tchild)" + " SELECT pid, cid FROM plink;\n" + "CREATE INDEX topolink_child ON topolink(tchild);\n" + ); + + /* Find a timewarp instance */ + db_prepare(&q1, + "SELECT P.tseq, C.tid, C.tseq\n" + " FROM toponode P, toponode C, topolink X\n" + " WHERE X.tparent=P.tid\n" + " AND X.tchild=C.tid\n" + " AND P.tseq>=C.tseq;" + ); + + /* Update the timestamp on :tid to have value :tseq */ + db_prepare(&chng, + "UPDATE toponode SET tseq=:tseq WHERE tid=:tid" + ); + + while( db_step(&q1)==SQLITE_ROW ){ + i64 iParentTime = db_column_int64(&q1, 0); + int iChild = db_column_int(&q1, 1); + i64 iChildTime = db_column_int64(&q1, 2); + nChange++; + if( nChange>10000 ){ + fossil_fatal("failed to fix all timewarps after 100000 attempts"); + } + db_reset(&q1); + db_bind_int64(&chng, ":tid", iChild); + db_bind_int64(&chng, ":tseq", iParentTime+1); + db_step(&chng); + db_reset(&chng); + if( bVerbose ){ + fossil_print("moving %d from %lld to %lld\n", + iChild, iChildTime, iParentTime+1); + } + } + + db_finalize(&q1); + db_finalize(&chng); + return nChange; +} + +/* +** COMMAND: test-topological-sort +** +** Invoke the topological_sort_checkins() interface for testing +** purposes. +*/ +void test_topological_sort(void){ + int n; + db_find_and_open_repository(0, 0); + n = topological_sort_checkins(1); + fossil_print("%d reorderings required\n", n); +} + +/*************************************************************************** +** Implementation of the "fossil git" command follows. We hope that the +** new code that follows will largely replace the legacy "fossil export" +** and "fossil import" code above. +*/ + +/* Verbosity level. Higher means more output. +** +** 0 print nothing at all +** 1 Errors only +** 2 Progress information (This is the default) +** 3 Extra details +*/ +#define VERB_ERROR 1 +#define VERB_NORMAL 2 +#define VERB_EXTRA 3 +static int gitmirror_verbosity = VERB_NORMAL; + +/* The main branch in the Git repository. The "trunk" branch of +** Fossil is renamed to be this branch name. +*/ +static const char *gitmirror_mainbranch = 0; + +/* +** Output routine that depends on verbosity +*/ +static void gitmirror_message(int iLevel, const char *zFormat, ...){ + va_list ap; + if( iLevel>gitmirror_verbosity ) return; + va_start(ap, zFormat); + fossil_vprint(zFormat, ap); + va_end(ap); +} + +/* +** Convert characters of z[] that are not allowed to be in branch or +** tag names into "_". +*/ +static void gitmirror_sanitize_name(char *z){ + static unsigned char aSafe[] = { + /* x0 x1 x2 x3 x4 x5 x6 x7 x8 x9 xA xB xC xD xE xF */ + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, /* 0x */ + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, /* 1x */ + 0, 1, 0, 1, 0, 1, 1, 0, 1, 1, 0, 1, 1, 1, 1, 1, /* 2x */ + 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 0, 0, 1, 1, 1, 0, /* 3x */ + 0, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, /* 4x */ + 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 0, 0, 1, 0, 1, /* 5x */ + 0, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, /* 6x */ + 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 0, 0, /* 7x */ + }; + unsigned char *zu = (unsigned char*)z; + int i; + for(i=0; zu[i]; i++){ + if( zu[i]>0x7f || !aSafe[zu[i]] ){ + zu[i] = '_'; + }else if( zu[i]=='/' && (i==0 || zu[i+1]==0 || zu[i+1]=='/') ){ + zu[i] = '_'; + }else if( zu[i]=='.' && (zu[i+1]==0 || zu[i+1]=='.' + || (i>0 && zu[i-1]=='.')) ){ + zu[i] = '_'; + } + } +} + +/* +** COMMAND: test-sanitize-name +** +** Usage: %fossil ARG... +** +** This sanitizes each argument and make it part of an "echo" command +** run by the shell. +*/ +void test_sanitize_name_cmd(void){ + sqlite3_str *pStr; + int i; + char *zCmd; + pStr = sqlite3_str_new(0); + sqlite3_str_appendall(pStr, "echo"); + for(i=2; inParent; i++){ + char *zPMark = gitmirror_find_mark(pMan->azParent[i], 0, 0); + if( zPMark==0 ){ + int prid = db_int(0, "SELECT rid FROM blob WHERE uuid=%Q", + pMan->azParent[i]); + int rc = gitmirror_send_checkin(xCmd, prid, pMan->azParent[i], + pnLimit, fManifest); + if( rc || *pnLimit<=0 ){ + manifest_destroy(pMan); + return 1; + } + } + fossil_free(zPMark); + } + + /* Ignore phantom files on check-ins that are over one year old */ + bPhantomOk = db_int(0, "SELECT %.6frDate); + + /* Make sure all necessary files have been exported */ + db_prepare(&q, + "SELECT uuid FROM files_of_checkin(%Q)" + " WHERE uuid NOT IN (SELECT uuid FROM mirror.mmark)", + zUuid + ); + while( db_step(&q)==SQLITE_ROW ){ + const char *zFUuid = db_column_text(&q, 0); + int n = gitmirror_send_file(xCmd, zFUuid, bPhantomOk); + nErr += n; + if( n ) gitmirror_message(VERB_ERROR, "missing file: %s\n", zFUuid); + } + db_finalize(&q); + + /* If some required files could not be exported, abandon the check-in + ** export */ + if( nErr ){ + gitmirror_message(VERB_ERROR, + "export of %s abandoned due to missing files\n", zUuid); + *pnLimit = 0; + manifest_destroy(pMan); + return 1; + } + + /* Figure out which branch this check-in is a member of */ + zBranch = db_text(0, + "SELECT value FROM tagxref WHERE tagid=%d AND tagtype>0 AND rid=%d", + TAG_BRANCH, rid + ); + if( fossil_strcmp(zBranch,"trunk")==0 ){ + assert( gitmirror_mainbranch!=0 ); + fossil_free(zBranch); + zBranch = mprintf("%s",gitmirror_mainbranch); + }else if( zBranch==0 ){ + zBranch = mprintf("unknown"); + }else{ + gitmirror_sanitize_name(zBranch); + } + + /* Export the check-in */ + fprintf(xCmd, "commit refs/heads/%s\n", zBranch); + fossil_free(zBranch); + zMark = gitmirror_find_mark(zUuid,0,1); + fprintf(xCmd, "mark %s\n", zMark); + fossil_free(zMark); + sqlite3_snprintf(sizeof(buf), buf, "%lld", + (sqlite3_int64)((pMan->rDate-2440587.5)*86400.0) + ); + + /* + ** Check for 'fx_' table from previous Git import, otherwise take contact info + ** from user table for in committer field. If no emailaddr, check + ** if username is in email form, otherwise use generic 'username@noemail.net'. + */ + if (db_table_exists("repository", "fx_git")) { + zEmail = db_text(0, "SELECT email FROM fx_git WHERE user=%Q", pMan->zUser); + } else { + zEmail = db_text(0, "SELECT info FROM user WHERE login=%Q", pMan->zUser); + } + + /* Some repo 'info' fields return an empty string hence the second check */ + if( zEmail==0 ){ + /* If username is in emailaddr form, don't append '@noemail.net' */ + if( pMan->zUser==0 || strchr(pMan->zUser, '@')==0 ){ + zEmail = mprintf("%s@noemail.net", pMan->zUser); + } else { + zEmail = fossil_strdup(pMan->zUser); + } + }else{ + char *zTmp = strchr(zEmail, '<'); + if( zTmp ){ + char *zTmpEnd = strchr(zTmp+1, '>'); + char *zNew; + int i; + if( zTmpEnd ) *(zTmpEnd) = 0; + zNew = fossil_strdup(zTmp+1); + fossil_free(zEmail); + zEmail = zNew; + for(i=0; zEmail[i] && !fossil_isspace(zEmail[i]); i++){} + zEmail[i] = 0; + } + } + fprintf(xCmd, "# rid=%d\n", rid); + fprintf(xCmd, "committer %s <%s> %s +0000\n", pMan->zUser, zEmail, buf); + fossil_free(zEmail); + blob_init(&comment, pMan->zComment, -1); + if( blob_size(&comment)==0 ){ + blob_append(&comment, "(no comment)", -1); + } + blob_appendf(&comment, "\n\nFossilOrigin-Name: %s", zUuid); + fprintf(xCmd, "data %d\n%s\n", blob_strlen(&comment), blob_str(&comment)); + blob_reset(&comment); + iParent = -1; /* Which ancestor is the primary parent */ + for(i=0; inParent; i++){ + char *zOther = gitmirror_find_mark(pMan->azParent[i],0,0); + if( zOther==0 ) continue; + if( iParent<0 ){ + iParent = i; + fprintf(xCmd, "from %s\n", zOther); + }else{ + fprintf(xCmd, "merge %s\n", zOther); + } + fossil_free(zOther); + } + if( iParent>=0 ){ + db_prepare(&q, + "SELECT filename FROM files_of_checkin(%Q)" + " EXCEPT SELECT filename FROM files_of_checkin(%Q)", + pMan->azParent[iParent], zUuid + ); + while( db_step(&q)==SQLITE_ROW ){ + fprintf(xCmd, "D %s\n", db_column_text(&q,0)); + } + db_finalize(&q); + } + blob_init(&sql, 0, 0); + blob_append_sql(&sql, + "SELECT filename, uuid, perm FROM files_of_checkin(%Q)", + zUuid + ); + if( pMan->nParent ){ + blob_append_sql(&sql, + " EXCEPT SELECT filename, uuid, perm FROM files_of_checkin(%Q)", + pMan->azParent[0]); + } + db_prepare(&q, + "SELECT x.filename, x.perm," + " coalesce(mmark.githash,printf(':%%d',mmark.id))" + " FROM (%s) AS x, mirror.mmark" + " WHERE mmark.uuid=x.uuid AND isfile", + blob_sql_text(&sql) + ); + blob_reset(&sql); + while( db_step(&q)==SQLITE_ROW ){ + const char *zFilename = db_column_text(&q,0); + const char *zMode = db_column_text(&q,1); + const char *zMark = db_column_text(&q,2); + const char *zGitMode = "100644"; + char *zFNQuoted = 0; + if( zMode ){ + if( strchr(zMode,'x') ) zGitMode = "100755"; + if( strchr(zMode,'l') ) zGitMode = "120000"; + } + zFNQuoted = gitmirror_quote_filename_if_needed(zFilename); + fprintf(xCmd,"M %s %s %s\n", zGitMode, zMark, zFNQuoted); + fossil_free(zFNQuoted); + } + db_finalize(&q); + manifest_destroy(pMan); + pMan = 0; + + /* Include Fossil-generated auxiliary files in the check-in */ + if( fManifest & MFESTFLG_RAW ){ + Blob manifest; + content_get(rid, &manifest); + fprintf(xCmd,"M 100644 inline manifest\ndata %d\n%s\n", + blob_strlen(&manifest), blob_str(&manifest)); + blob_reset(&manifest); + } + if( fManifest & MFESTFLG_UUID ){ + int n = (int)strlen(zUuid); + fprintf(xCmd,"M 100644 inline manifest.uuid\ndata %d\n%s\n", n, zUuid); + } + if( fManifest & MFESTFLG_TAGS ){ + Blob tagslist; + blob_init(&tagslist, 0, 0); + get_checkin_taglist(rid, &tagslist); + fprintf(xCmd,"M 100644 inline manifest.tags\ndata %d\n%s\n", + blob_strlen(&tagslist), blob_str(&tagslist)); + blob_reset(&tagslist); + } + + /* The check-in is finished, so decrement the counter */ + (*pnLimit)--; + return 0; +} + +/* +** Create a new Git repository at zMirror to use as the mirror. +** Try to make zMainBr be the main branch for the new repository. +** +** A side-effect of this routine is that current-working directory +** is changed to zMirror. +** +** If zMainBr is initially NULL, then the return value will be the +** name of the default branch to be used by Git. If zMainBr is +** initially non-NULL, then the return value will be a copy of zMainBr. +*/ +static char *gitmirror_init( + const char *zMirror, + char *zMainBr +){ + char *zCmd; + int rc; + + /* Create a new Git repository at zMirror */ + zCmd = mprintf("git init %$", zMirror); + gitmirror_message(VERB_NORMAL, "%s\n", zCmd); + rc = fossil_system(zCmd); + if( rc ){ + fossil_fatal("cannot initialize git repository using: %s", zCmd); + } + fossil_free(zCmd); + + /* Must be in the new Git repository directory for subsequent commands */ + rc = file_chdir(zMirror, 0); + if( rc ){ + fossil_fatal("cannot change to directory \"%s\"", zMirror); + } + + if( zMainBr ){ + /* Set the current branch to zMainBr */ + zCmd = mprintf("git symbolic-ref HEAD refs/heads/%s", zMainBr); + gitmirror_message(VERB_NORMAL, "%s\n", zCmd); + rc = fossil_system(zCmd); + if( rc ){ + fossil_fatal("git command failed: %s", zCmd); + } + fossil_free(zCmd); + }else{ + /* If zMainBr is not specified, then check to see what branch + ** name Git chose for itself */ + char *z; + char zLine[1000]; + FILE *xCmd; + int i; + zCmd = "git symbolic-ref --short HEAD"; + gitmirror_message(VERB_NORMAL, "%s\n", zCmd); + xCmd = popen(zCmd, "r"); + if( xCmd==0 ){ + fossil_fatal("git command failed: %s", zCmd); + } + + z = fgets(zLine, sizeof(zLine), xCmd); + pclose(xCmd); + if( z==0 ){ + fossil_fatal("no output from \"%s\"", zCmd); + } + for(i=0; z[i] && !fossil_isspace(z[i]); i++){} + z[i] = 0; + zMainBr = fossil_strdup(z); + } + return zMainBr; +} + +/* +** Implementation of the "fossil git export" command. +*/ +void gitmirror_export_command(void){ + const char *zLimit; /* Text of the --limit flag */ + int nLimit = 0x7fffffff; /* Numeric value of the --limit flag */ + int nTotal = 0; /* Total number of check-ins to export */ + char *zMirror; /* Name of the mirror */ + char *z; /* Generic string */ + char *zCmd; /* git command to run as a subprocess */ + const char *zDebug = 0; /* Value of the --debug flag */ + const char *zAutoPush = 0; /* Value of the --autopush flag */ + char *zMainBr = 0; /* Value of the --mainbranch flag */ + char *zPushUrl; /* URL to sync the mirror to */ + double rEnd; /* time of most recent export */ + int rc; /* Result code */ + int bForce; /* Do the export and sync even if no changes*/ + int bNeedRepack = 0; /* True if we should run repack at the end */ + int fManifest; /* Current "manifest" setting */ + int bIfExists; /* The --if-mirrored flag */ + FILE *xCmd; /* Pipe to the "git fast-import" command */ + FILE *pMarks; /* Git mark files */ + Stmt q; /* Queries */ + char zLine[200]; /* One line of a mark file */ + + zDebug = find_option("debug",0,1); + db_find_and_open_repository(0, 0); + zLimit = find_option("limit", 0, 1); + if( zLimit ){ + nLimit = (unsigned int)atoi(zLimit); + if( nLimit<=0 ) fossil_fatal("--limit must be positive"); + } + zAutoPush = find_option("autopush",0,1); + zMainBr = (char*)find_option("mainbranch",0,1); + bForce = find_option("force","f",0)!=0; + bIfExists = find_option("if-mirrored",0,0)!=0; + gitmirror_verbosity = VERB_NORMAL; + while( find_option("quiet","q",0)!=0 ){ gitmirror_verbosity--; } + while( find_option("verbose","v",0)!=0 ){ gitmirror_verbosity++; } + verify_all_options(); + if( g.argc!=4 && g.argc!=3 ){ usage("export ?MIRROR?"); } + if( g.argc==4 ){ + Blob mirror; + file_canonical_name(g.argv[3], &mirror, 0); + db_set("last-git-export-repo", blob_str(&mirror), 0); + blob_reset(&mirror); + } + zMirror = db_get("last-git-export-repo", 0); + if( zMirror==0 ){ + if( bIfExists ) return; + fossil_fatal("no Git repository specified"); + } + + if( zMainBr ){ + z = fossil_strdup(zMainBr); + gitmirror_sanitize_name(z); + if( strcmp(z, zMainBr) ){ + fossil_fatal("\"%s\" is not a legal branch name for Git", zMainBr); + } + fossil_free(z); + } + + /* Make sure the GIT repository directory exists */ + rc = file_mkdir(zMirror, ExtFILE, 0); + if( rc ) fossil_fatal("cannot create directory \"%s\"", zMirror); + + /* Make sure GIT has been initialized */ + z = mprintf("%s/.git", zMirror); + if( !file_isdir(z, ExtFILE) ){ + zMainBr = gitmirror_init(zMirror, zMainBr); + bNeedRepack = 1; + } + fossil_free(z); + + /* Make sure the .mirror_state subdirectory exists */ + z = mprintf("%s/.mirror_state", zMirror); + rc = file_mkdir(z, ExtFILE, 0); + if( rc ) fossil_fatal("cannot create directory \"%s\"", z); + fossil_free(z); + + /* Attach the .mirror_state/db database */ + db_multi_exec("ATTACH '%q/.mirror_state/db' AS mirror;", zMirror); + db_begin_write(); + db_multi_exec( + "CREATE TABLE IF NOT EXISTS mirror.mconfig(\n" + " key TEXT PRIMARY KEY,\n" + " Value ANY\n" + ") WITHOUT ROWID;\n" + "CREATE TABLE IF NOT EXISTS mirror.mmark(\n" + " id INTEGER PRIMARY KEY,\n" + " uuid TEXT,\n" + " isfile BOOLEAN,\n" + " githash TEXT,\n" + " UNIQUE(uuid,isfile)\n" + ");" + ); + if( !db_table_has_column("mirror","mmark","isfile") ){ + db_multi_exec( + "ALTER TABLE mirror.mmark RENAME TO mmark_old;" + "CREATE TABLE IF NOT EXISTS mirror.mmark(\n" + " id INTEGER PRIMARY KEY,\n" + " uuid TEXT,\n" + " isfile BOOLEAN,\n" + " githash TEXT,\n" + " UNIQUE(uuid,isfile)\n" + ");" + "INSERT OR IGNORE INTO mirror.mmark(id,uuid,githash,isfile)" + " SELECT id,uuid,githash," + " NOT EXISTS(SELECT 1 FROM repository.event, repository.blob" + " WHERE event.objid=blob.rid" + " AND blob.uuid=mmark_old.uuid)" + " FROM mirror.mmark_old;\n" + "DROP TABLE mirror.mmark_old;\n" + ); + } + + /* Change the autopush setting if the --autopush flag is present */ + if( zAutoPush ){ + if( is_false(zAutoPush) ){ + db_multi_exec("DELETE FROM mirror.mconfig WHERE key='autopush'"); + }else{ + db_multi_exec( + "REPLACE INTO mirror.mconfig(key,value)" + "VALUES('autopush',%Q)", + zAutoPush + ); + } + } + + /* Change the mainbranch setting if the --mainbranch flag is present */ + if( zMainBr && zMainBr[0] ){ + db_multi_exec( + "REPLACE INTO mirror.mconfig(key,value)" + "VALUES('mainbranch',%Q)", + zMainBr + ); + gitmirror_mainbranch = fossil_strdup(zMainBr); + }else{ + /* Recover the saved name of the main branch */ + gitmirror_mainbranch = db_text("master", + "SELECT value FROM mconfig WHERE key='mainbranch'"); + } + + + /* See if there is any work to be done. Exit early if not, before starting + ** the "git fast-import" command. */ + if( !bForce + && !db_exists("SELECT 1 FROM event WHERE type IN ('ci','t')" + " AND mtime>coalesce((SELECT value FROM mconfig" + " WHERE key='start'),0.0)") + ){ + gitmirror_message(VERB_NORMAL, "no changes\n"); + db_commit_transaction(); + return; + } + + /* Do we need to include manifest files in the clone? */ + fManifest = db_get_manifest_setting(); + + /* Change to the MIRROR directory so that the Git commands will work */ + rc = file_chdir(zMirror, 0); + if( rc ) fossil_fatal("cannot change the working directory to \"%s\"", + zMirror); + + /* Start up the git fast-import command */ + if( zDebug ){ + if( fossil_strcmp(zDebug,"stdout")==0 ){ + xCmd = stdout; + }else{ + xCmd = fopen(zDebug, "wb"); + if( xCmd==0 ) fossil_fatal("cannot open file \"%s\" for writing", zDebug); + } + }else{ + zCmd = mprintf("git fast-import" + " --export-marks=.mirror_state/marks.txt" + " --quiet --done"); + gitmirror_message(VERB_NORMAL, "%s\n", zCmd); +#ifdef _WIN32 + xCmd = popen(zCmd, "wb"); +#else + xCmd = popen(zCmd, "w"); +#endif + if( xCmd==0 ){ + fossil_fatal("cannot start the \"git fast-import\" command"); + } + fossil_free(zCmd); + } + + /* Run the export */ + rEnd = 0.0; + db_multi_exec( + "CREATE TEMP TABLE tomirror(objid,mtime,uuid);\n" + "INSERT INTO tomirror " + "SELECT objid, mtime, blob.uuid FROM event, blob\n" + " WHERE type='ci'" + " AND mtime>coalesce((SELECT value FROM mconfig WHERE key='start'),0.0)" + " AND blob.rid=event.objid" + " AND blob.uuid NOT IN (SELECT uuid FROM mirror.mmark WHERE NOT isfile);" + ); + nTotal = db_int(0, "SELECT count(*) FROM tomirror"); + if( nLimitnTotal ){ + nLimit = nTotal; + } + db_prepare(&q, + "SELECT objid, mtime, uuid FROM tomirror ORDER BY mtime" + ); + while( nLimit && db_step(&q)==SQLITE_ROW ){ + int rid = db_column_int(&q, 0); + double rMTime = db_column_double(&q, 1); + const char *zUuid = db_column_text(&q, 2); + if( rMTime>rEnd ) rEnd = rMTime; + rc = gitmirror_send_checkin(xCmd, rid, zUuid, &nLimit, fManifest); + if( rc ) break; + gitmirror_message(VERB_NORMAL,"%d/%d \r", nTotal-nLimit, nTotal); + fflush(stdout); + } + db_finalize(&q); + fprintf(xCmd, "done\n"); + if( zDebug ){ + if( xCmd!=stdout ) fclose(xCmd); + }else{ + pclose(xCmd); + } + gitmirror_message(VERB_NORMAL, "%d check-ins added to the %s\n", + nTotal-nLimit, zMirror); + + /* Read the export-marks file. Transfer the new marks over into + ** the import-marks file. + */ + pMarks = fopen(".mirror_state/marks.txt", "rb"); + if( pMarks ){ + db_prepare(&q, "UPDATE mirror.mmark SET githash=:githash WHERE id=:id"); + while( fgets(zLine, sizeof(zLine), pMarks) ){ + int j, k; + if( zLine[0]!=':' ) continue; + db_bind_int(&q, ":id", atoi(zLine+1)); + for(j=1; zLine[j] && zLine[j]!=' '; j++){} + if( zLine[j]!=' ' ) continue; + j++; + if( zLine[j]==0 ) continue; + for(k=j; fossil_isalnum(zLine[k]); k++){} + zLine[k] = 0; + db_bind_text(&q, ":githash", &zLine[j]); + db_step(&q); + db_reset(&q); + } + db_finalize(&q); + fclose(pMarks); + file_delete(".mirror_state/marks.txt"); + }else{ + fossil_fatal("git fast-import didn't generate a marks file!"); + } + db_multi_exec( + "CREATE INDEX IF NOT EXISTS mirror.mmarkx1 ON mmark(githash);" + ); + + /* Do any tags that have been created since the start time */ + db_prepare(&q, + "SELECT substr(tagname,5), githash" + " FROM (SELECT tagxref.tagid AS xtagid, tagname, rid, max(mtime) AS mtime" + " FROM tagxref JOIN tag ON tag.tagid=tagxref.tagid" + " WHERE tag.tagname GLOB 'sym-*'" + " AND tagxref.tagtype=1" + " AND tagxref.mtime > coalesce((SELECT value FROM mconfig" + " WHERE key='start'),0.0)" + " GROUP BY tagxref.tagid) AS tx" + " JOIN blob ON tx.rid=blob.rid" + " JOIN mmark ON mmark.uuid=blob.uuid;" + ); + while( db_step(&q)==SQLITE_ROW ){ + char *zTagname = fossil_strdup(db_column_text(&q,0)); + const char *zObj = db_column_text(&q,1); + char *zTagCmd; + gitmirror_sanitize_name(zTagname); + zTagCmd = mprintf("git tag -f %$ %$", zTagname, zObj); + fossil_free(zTagname); + gitmirror_message(VERB_NORMAL, "%s\n", zTagCmd); + fossil_system(zTagCmd); + fossil_free(zTagCmd); + } + db_finalize(&q); + + /* Update all references that might have changed since the start time */ + db_prepare(&q, + "SELECT" + " tagxref.value AS name," + " max(event.mtime) AS mtime," + " mmark.githash AS gitckin" + " FROM tagxref, tag, event, blob, mmark" + " WHERE tagxref.tagid=tag.tagid" + " AND tagxref.tagtype>0" + " AND tag.tagname='branch'" + " AND event.objid=tagxref.rid" + " AND event.mtime > coalesce((SELECT value FROM mconfig" + " WHERE key='start'),0.0)" + " AND blob.rid=tagxref.rid" + " AND mmark.uuid=blob.uuid" + " GROUP BY 1" + ); + while( db_step(&q)==SQLITE_ROW ){ + char *zBrname = fossil_strdup(db_column_text(&q,0)); + const char *zObj = db_column_text(&q,2); + char *zRefCmd; + if( fossil_strcmp(zBrname,"trunk")==0 ){ + fossil_free(zBrname); + zBrname = fossil_strdup(gitmirror_mainbranch); + }else{ + gitmirror_sanitize_name(zBrname); + } + zRefCmd = mprintf("git update-ref \"refs/heads/%s\" %$", zBrname, zObj); + fossil_free(zBrname); + gitmirror_message(VERB_NORMAL, "%s\n", zRefCmd); + fossil_system(zRefCmd); + fossil_free(zRefCmd); + } + db_finalize(&q); + + /* Update the start time */ + if( rEnd>0.0 ){ + db_prepare(&q, "REPLACE INTO mirror.mconfig(key,value) VALUES('start',:x)"); + db_bind_double(&q, ":x", rEnd); + db_step(&q); + db_finalize(&q); + } + db_commit_transaction(); + + /* Maybe run a git repack */ + if( bNeedRepack ){ + const char *zRepack = "git repack -adf"; + gitmirror_message(VERB_NORMAL, "%s\n", zRepack); + fossil_system(zRepack); + } + + /* Optionally do a "git push" */ + zPushUrl = db_text(0, "SELECT value FROM mconfig WHERE key='autopush'"); + if( zPushUrl ){ + char *zPushCmd; + UrlData url; + if( sqlite3_strglob("http*", zPushUrl)==0 ){ + url_parse_local(zPushUrl, 0, &url); + zPushCmd = mprintf("git push --mirror %s", url.canonical); + }else{ + zPushCmd = mprintf("git push --mirror %s", zPushUrl); + } + gitmirror_message(VERB_NORMAL, "%s\n", zPushCmd); + fossil_free(zPushCmd); + zPushCmd = mprintf("git push --mirror %$", zPushUrl); + rc = fossil_system(zPushCmd); + if( rc ){ + fossil_fatal("cannot push content using: %s", zPushCmd); + } + fossil_free(zPushCmd); + } +} + +/* +** Implementation of the "fossil git status" command. +** +** Show the status of a "git export". +*/ +void gitmirror_status_command(void){ + char *zMirror; + char *z; + int n, k; + db_find_and_open_repository(0, 0); + verify_all_options(); + zMirror = db_get("last-git-export-repo", 0); + if( zMirror==0 ){ + fossil_print("Git mirror: none\n"); + return; + } + fossil_print("Git mirror: %s\n", zMirror); + db_multi_exec("ATTACH '%q/.mirror_state/db' AS mirror;", zMirror); + z = db_text(0, "SELECT datetime(value) FROM mconfig WHERE key='start'"); + if( z ){ + double rAge = db_double(0.0, "SELECT julianday('now') - value" + " FROM mconfig WHERE key='start'"); + if( rAge>1.0/86400.0 ){ + fossil_print("Last export: %s (%z ago)\n", z, human_readable_age(rAge)); + }else{ + fossil_print("Last export: %s (moments ago)\n", z); + } + } + z = db_text(0, "SELECT value FROM mconfig WHERE key='autopush'"); + if( z==0 ){ + fossil_print("Autopush: off\n"); + }else{ + UrlData url; + url_parse_local(z, 0, &url); + fossil_print("Autopush: %s\n", url.canonical); + fossil_free(z); + } + n = db_int(0, + "SELECT count(*) FROM event" + " WHERE type='ci'" + " AND mtime>coalesce((SELECT value FROM mconfig" + " WHERE key='start'),0.0)" + ); + z = db_text("master", "SELECT value FROM mconfig WHERE key='mainbranch'"); + fossil_print("Main-Branch: %s\n",z); + if( n==0 ){ + fossil_print("Status: up-to-date\n"); + }else{ + fossil_print("Status: %d check-in%s awaiting export\n", + n, n==1 ? "" : "s"); + } + n = db_int(0, "SELECT count(*) FROM mmark WHERE isfile"); + k = db_int(0, "SELECT count(*) FROm mmark WHERE NOT isfile"); + fossil_print("Exported: %d check-ins and %d file blobs\n", k, n); +} + +/* +** COMMAND: git* +** +** Usage: %fossil git SUBCOMMAND +** +** Do incremental import or export operations between Fossil and Git. +** Subcommands: +** +** > fossil git export [MIRROR] [OPTIONS] +** +** Write content from the Fossil repository into the Git repository +** in directory MIRROR. The Git repository is created if it does not +** already exist. If the Git repository does already exist, then +** new content added to fossil since the previous export is appended. +** +** Repeat this command whenever new checkins are added to the Fossil +** repository in order to reflect those changes into the mirror. If +** the MIRROR option is omitted, the repository from the previous +** invocation is used. +** +** The MIRROR directory will contain a subdirectory named +** ".mirror_state" that contains information that Fossil needs to +** do incremental exports. Do not attempt to manage or edit the files +** in that directory since doing so can disrupt future incremental +** exports. +** +** Options: +** --autopush URL Automatically do a 'git push' to URL. The +** URL is remembered and used on subsequent exports +** to the same repository. Or if URL is "off" the +** auto-push mechanism is disabled +** --debug FILE Write fast-export text to FILE rather than +** piping it into "git fast-import". +** -f|--force Do the export even if nothing has changed +** --if-mirrored No-op if the mirror does not already exist. +** --limit N Add no more than N new check-ins to MIRROR. +** Useful for debugging +** --mainbranch NAME Use NAME as the name of the main branch in Git. +** The "trunk" branch of the Fossil repository is +** mapped into this name. "master" is used if +** this option is omitted. +** -q|--quiet Reduce output. Repeat for even less output. +** -v|--verbose More output. +** +** > fossil git import MIRROR +** +** TBD... +** +** > fossil git status +** +** Show the status of the current Git mirror, if there is one. +*/ +void gitmirror_command(void){ + char *zCmd; + int nCmd; + if( g.argc<3 ){ + usage("export ARGS..."); + } + zCmd = g.argv[2]; + nCmd = (int)strlen(zCmd); + if( nCmd>2 && strncmp(zCmd,"export",nCmd)==0 ){ + gitmirror_export_command(); + }else + if( nCmd>2 && strncmp(zCmd,"import",nCmd)==0 ){ + fossil_fatal("not yet implemented - check back later"); + }else + if( nCmd>2 && strncmp(zCmd,"status",nCmd)==0 ){ + gitmirror_status_command(); + }else + { + fossil_fatal("unknown subcommand \"%s\": should be one of " + "\"export\", \"import\", \"status\"", + zCmd); + } +} ADDED src/extcgi.c Index: src/extcgi.c ================================================================== --- /dev/null +++ src/extcgi.c @@ -0,0 +1,420 @@ +/* +** Copyright (c) 2019 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@sqlite.org +** http://www.hwaci.com/drh/ +** +******************************************************************************* +** +** This file contains code to invoke CGI-based extensions to the +** Fossil server via the /ext webpage. +** +** The /ext webpage acts like a recursive webserver, relaying the +** HTTP request to some other component - usually another CGI. +** +** Before doing the relay, /ext examines the login cookie to see +** if the HTTP request is coming from a validated user, and if so +** /ext sets some additional environment variables that the extension +** CGI script can use. In this way, the extension CGI scripts use the +** same login system as the main repository, and appear to be +** an integrated part of the repository. +*/ +#include "config.h" +#include "extcgi.h" +#include + +/* +** These are the environment variables that should be set for CGI +** extension programs: +*/ +static const char *azCgiEnv[] = { + "AUTH_TYPE", + "AUTH_CONTENT", + "CONTENT_LENGTH", + "CONTENT_TYPE", + "DOCUMENT_ROOT", + "FOSSIL_CAPABILITIES", + "FOSSIL_NONCE", + "FOSSIL_REPOSITORY", + "FOSSIL_URI", + "FOSSIL_USER", + "GATEWAY_INTERFACE", + "HTTPS", + "HTTP_ACCEPT", + /* "HTTP_ACCEPT_ENCODING", // omitted from sub-cgi */ + "HTTP_COOKIE", + "HTTP_HOST", + "HTTP_IF_MODIFIED_SINCE", + "HTTP_IF_NONE_MATCH", + "HTTP_REFERER", + "HTTP_USER_AGENT", + "PATH_INFO", + "QUERY_STRING", + "REMOTE_ADDR", + "REMOTE_USER", + "REQUEST_METHOD", + "REQUEST_SCHEME", + "REQUEST_URI", + "SCRIPT_DIRECTORY", + "SCRIPT_FILENAME", + "SCRIPT_NAME", + "SERVER_NAME", + "SERVER_PORT", + "SERVER_PROTOCOL", +}; + +/* +** Check a pathname to determine if it is acceptable for use as +** extension CGI. Some pathnames are excluded for security reasons. +** Return NULL on success or a static error string if there is +** a failure. +*/ +const char *ext_pathname_ok(const char *zName){ + int i; + const char *zFailReason = 0; + for(i=0; zName[i]; i++){ + char c = zName[i]; + if( (c=='.' || c=='-') && (i==0 || zName[i-1]=='/') ){ + zFailReason = "path element begins with '.' or '-'"; + break; + } + if( !fossil_isalnum(c) && c!='_' && c!='-' && c!='.' && c!='/' ){ + zFailReason = "illegal character in path"; + break; + } + } + return zFailReason; +} + +/* +** The *pzPath input is a pathname obtained from mprintf(). +** +** If +** +** (1) zPathname is the name of a directory, and +** (2) the name ends with "/", and +** (3) the directory contains a file named index.html, index.wiki, +** or index.md (in that order) +** +** then replace the input with a revised name that includes the index.* +** file and return non-zero (true). If any condition is not met, return +** zero and leave the input pathname unchanged. +*/ +static int isDirWithIndexFile(char **pzPath){ + static const char *azIndexNames[] = { + "index.html", "index.wiki", "index.md" + }; + int i; + if( file_isdir(*pzPath, ExtFILE)!=1 ) return 0; + if( sqlite3_strglob("*/", *pzPath)!=0 ) return 0; + for(i=0; i=nRoot+1 ); + style_set_current_page("ext/%s", &zScript[nRoot+1]); + zMime = mimetype_from_name(zScript); + if( zMime==0 ) zMime = "application/octet-stream"; + if( !file_isexe(zScript, ExtFILE) ){ + /* File is not executable. Must be a regular file. In that case, + ** disallow extra path elements */ + if( zPath[nScript]!=0 ){ + zFailReason = "extra path elements after filename"; + goto ext_not_found; + } + blob_read_from_file(&reply, zScript, ExtFILE); + document_render(&reply, zMime, zName, zName); + return; + } + + /* If we reach this point, that means we are dealing with an executable + ** file name zScript. Run that file as CGI. + */ + cgi_replace_parameter("DOCUMENT_ROOT", g.zExtRoot); + cgi_replace_parameter("SCRIPT_FILENAME", zScript); + cgi_replace_parameter("SCRIPT_NAME", + mprintf("%T/ext/%T",g.zTop,zScript+nRoot+1)); + cgi_replace_parameter("SCRIPT_DIRECTORY", file_dirname(zScript)); + cgi_replace_parameter("PATH_INFO", zName + strlen(zScript+nRoot+1)); + if( g.zLogin ){ + cgi_replace_parameter("REMOTE_USER", g.zLogin); + cgi_set_parameter_nocopy("FOSSIL_USER", g.zLogin, 0); + } + cgi_set_parameter_nocopy("FOSSIL_NONCE", style_nonce(), 0); + cgi_set_parameter_nocopy("FOSSIL_REPOSITORY", g.zRepositoryName, 0); + cgi_set_parameter_nocopy("FOSSIL_URI", g.zTop, 0); + cgi_set_parameter_nocopy("FOSSIL_CAPABILITIES", + db_text("","SELECT fullcap(cap) FROM user WHERE login=%Q", + g.zLogin ? g.zLogin : "nobody"), 0); + cgi_replace_parameter("GATEWAY_INTERFACE","CGI/1.0"); + for(i=0; i0 ){ + size_t nSent, toSend; + unsigned char *data = (unsigned char*)blob_buffer(&g.cgiIn); + toSend = (size_t)blob_size(&g.cgiIn); + do{ + nSent = fwrite(data, 1, toSend, toChild); + if( nSent<=0 ){ + zFailReason = "unable to send all content to the CGI child process"; + goto ext_not_found; + } + toSend -= nSent; + data += nSent; + }while( toSend>0 ); + fflush(toChild); + } + if( g.perm.Debug && P("fossil-ext-debug")!=0 ){ + /* For users with Debug privilege, if the "fossil-ext-debug" query + ** parameter exists, then show raw output from the CGI */ + zMime = "text/plain"; + }else{ + while( fgets(zLine,sizeof(zLine),fromChild) ){ + for(i=0; zLine[i] && zLine[i]!='\r' && zLine[i]!='\n'; i++){} + zLine[i] = 0; + if( i==0 ) break; + if( fossil_strnicmp(zLine,"Location:",9)==0 ){ + fclose(fromChild); + fclose(toChild); + cgi_redirect(&zLine[10]); /* no return */ + }else if( fossil_strnicmp(zLine,"Status:",7)==0 ){ + int j; + for(i=7; fossil_isspace(zLine[i]); i++){} + for(j=i; fossil_isdigit(zLine[j]); j++){} + while( fossil_isspace(zLine[j]) ){ j++; } + cgi_set_status(atoi(&zLine[i]), &zLine[j]); + }else if( fossil_strnicmp(zLine,"Content-Length:",15)==0 ){ + nContent = atoi(&zLine[15]); + }else if( fossil_strnicmp(zLine,"Content-Type:",13)==0 ){ + int j; + for(i=13; fossil_isspace(zLine[i]); i++){} + for(j=i; zLine[j] && zLine[j]!=';'; j++){} + zMime = mprintf("%.*s", j-i, &zLine[i]); + }else{ + cgi_append_header(zLine); + cgi_append_header("\r\n"); + } + } + } + blob_read_from_channel(&reply, fromChild, nContent); + zFailReason = 0; /* Indicate success */ + +ext_not_found: + fossil_free(zPath); + if( fromChild ){ + fclose(fromChild); + }else if( fdFromChild>2 ){ + close(fdFromChild); + } + if( toChild ) fclose(toChild); + if( zFailReason==0 ){ + document_render(&reply, zMime, zName, zName); + }else{ + cgi_set_status(404, "Not Found"); + @

Not Found

+ @

Page not found: %h(zPathInfo)

+ if( g.perm.Debug ){ + @

Reason for failure: %h(zFailReason)

+ } + } + return; +} + +/* +** Create a temporary SFILE table and fill it with one entry for each file +** in the extension document root directory (g.zExtRoot). The SFILE table +** looks like this: +** +** CREATE TEMP TABLE sfile( +** pathname TEXT PRIMARY KEY, +** isexe BOOLEAN +** ) WITHOUT ROWID; +*/ +void ext_files(void){ + Blob base; + db_multi_exec( + "CREATE TEMP TABLE sfile(\n" + " pathname TEXT PRIMARY KEY,\n" + " isexe BOOLEAN\n" + ") WITHOUT ROWID;" + ); + blob_init(&base, g.zExtRoot, -1); + vfile_scan(&base, blob_size(&base), + SCAN_ALL|SCAN_ISEXE, + 0, 0, ExtFILE); + blob_zero(&base); +} + +/* +** WEBPAGE: extfilelist +** +** List all files in the extension CGI document root and its subfolders. +*/ +void ext_filelist_page(void){ + Stmt q; + login_check_credentials(); + if( !g.perm.Admin ){ + login_needed(0); + return; + } + ext_files(); + style_set_current_feature("extcgi"); + style_header("CGI Extension Filelist"); + @ + @ + db_prepare(&q, "SELECT pathname, isexe FROM sfile" + " ORDER BY pathname"); + while( db_step(&q)==SQLITE_ROW ){ + const char *zName = db_column_text(&q,0); + int isExe = db_column_int(&q,1); + @ + if( ext_pathname_ok(zName)!=0 ){ + @ + @ + }else{ + @ + if( isExe ){ + @ + }else{ + @ + } + } + @ + } + db_finalize(&q); + @ + @
%h(zName)data file%h(zName)CGIstatic content
+ style_finish_page(); +} Index: src/file.c ================================================================== --- src/file.c +++ src/file.c @@ -2,11 +2,11 @@ ** Copyright (c) 2006 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: @@ -13,204 +13,1021 @@ ** drh@hwaci.com ** http://www.hwaci.com/drh/ ** ******************************************************************************* ** -** File utilities +** File utilities. */ #include "config.h" #include #include #include +#include +#include +#include +#include #include "file.h" /* -** The file status information from the most recent stat() call. +** On Windows, include the Platform SDK header file. */ -static struct stat fileStat; -static int fileStatValid = 0; +#ifdef _WIN32 +# include +# include +# include +#else +# include +#endif + +#if INTERFACE + +/* Many APIs take a eFType argument which must be one of ExtFILE, RepoFILE, +** or SymFILE. +** +** The difference is in the handling of symbolic links. RepoFILE should be +** used for files that are under management by a Fossil repository. ExtFILE +** should be used for files that are not under management. SymFILE is for +** a few special cases such as the "fossil test-tarball" command when we never +** want to follow symlinks. +** +** ExtFILE Symbolic links always refer to the object to which the +** link points. Symlinks are never recognized as symlinks but +** instead always appear to the the target object. +** +** SymFILE Symbolic links always appear to be files whose name is +** the target pathname of the symbolic link. +** +** RepoFILE Like SymFILE if allow-symlinks is true, or like +** ExtFILE if allow-symlinks is false. In other words, +** symbolic links are only recognized as something different +** from files or directories if allow-symlinks is true. +*/ +#define ExtFILE 0 /* Always follow symlinks */ +#define RepoFILE 1 /* Follow symlinks if and only if allow-symlinks is OFF */ +#define SymFILE 2 /* Never follow symlinks */ + +#include +#if defined(_WIN32) +# define DIR _WDIR +# define dirent _wdirent +# define opendir _wopendir +# define readdir _wreaddir +# define closedir _wclosedir +#endif /* _WIN32 */ + +#if defined(_WIN32) && (defined(__MSVCRT__) || defined(_MSC_VER)) +/* +** File status information for windows systems. +*/ +struct fossilStat { + i64 st_size; + i64 st_mtime; + int st_mode; +}; +#endif + +#if defined(_WIN32) || defined(__CYGWIN__) +# define fossil_isdirsep(a) (((a) == '/') || ((a) == '\\')) +#else +# define fossil_isdirsep(a) ((a) == '/') +#endif + +#endif /* INTERFACE */ + +#if !defined(_WIN32) || !(defined(__MSVCRT__) || defined(_MSC_VER)) +/* +** File status information for unix systems +*/ +# define fossilStat stat +#endif /* -** Fill in the fileStat variable for the file named zFilename. -** If zFilename==0, then use the previous value of fileStat if +** On Windows S_ISLNK always returns FALSE. +*/ +#if !defined(S_ISLNK) +# define S_ISLNK(x) (0) +#endif + +/* +** Local state information for the file status routines +*/ +static struct { + struct fossilStat fileStat; /* File status from last fossil_stat() */ + int fileStatValid; /* True if fileStat is valid */ +} fx; + +/* +** Fill *buf with information about zFilename. +** +** If zFilename refers to a symbolic link: +** +** (A) If allow-symlinks is on and eFType is RepoFILE, then fill +** *buf with information about the symbolic link itself. +** +** (B) If allow-symlinks is off or eFType is ExtFILE, then fill +** *buf with information about the object that the symbolic link +** points to. +*/ +static int fossil_stat( + const char *zFilename, /* name of file or directory to inspect. */ + struct fossilStat *buf, /* pointer to buffer where info should go. */ + int eFType /* Look at symlink itself if RepoFILE and enabled. */ +){ + int rc; + void *zMbcs = fossil_utf8_to_path(zFilename, 0); +#if !defined(_WIN32) + if( (eFType==RepoFILE && db_allow_symlinks()) + || eFType==SymFILE ){ + /* Symlinks look like files whose content is the name of the target */ + rc = lstat(zMbcs, buf); + }else{ + /* Symlinks look like the object to which they point */ + rc = stat(zMbcs, buf); + } +#else + rc = win32_stat(zMbcs, buf, eFType); +#endif + fossil_path_free(zMbcs); + return rc; +} + +/* +** Clears the fx.fileStat variable and its associated validity flag. +*/ +static void resetStat(){ + fx.fileStatValid = 0; + memset(&fx.fileStat, 0, sizeof(struct fossilStat)); +} + +/* +** Fill in the fx.fileStat variable for the file named zFilename. +** If zFilename==0, then use the previous value of fx.fileStat if ** there is a previous value. ** ** Return the number of errors. No error messages are generated. */ -static int getStat(const char *zFilename){ +static int getStat(const char *zFilename, int eFType){ int rc = 0; if( zFilename==0 ){ - if( fileStatValid==0 ) rc = 1; + if( fx.fileStatValid==0 ) rc = 1; }else{ - if( stat(zFilename, &fileStat)!=0 ){ - fileStatValid = 0; + if( fossil_stat(zFilename, &fx.fileStat, eFType)!=0 ){ + fx.fileStatValid = 0; rc = 1; }else{ - fileStatValid = 1; + fx.fileStatValid = 1; rc = 0; } } return rc; } - /* ** Return the size of a file in bytes. Return -1 if the file does not ** exist. If zFilename is NULL, return the size of the most recently ** stat-ed file. */ -i64 file_size(const char *zFilename){ - return getStat(zFilename) ? -1 : fileStat.st_size; +i64 file_size(const char *zFilename, int eFType){ + return getStat(zFilename, eFType) ? -1 : fx.fileStat.st_size; } /* ** Return the modification time for a file. Return -1 if the file ** does not exist. If zFilename is NULL return the size of the most ** recently stat-ed file. */ -i64 file_mtime(const char *zFilename){ - return getStat(zFilename) ? -1 : fileStat.st_mtime; +i64 file_mtime(const char *zFilename, int eFType){ + return getStat(zFilename, eFType) ? -1 : fx.fileStat.st_mtime; +} + +/* +** Return the mode bits for a file. Return -1 if the file does not +** exist. If zFilename is NULL return the size of the most recently +** stat-ed file. +*/ +int file_mode(const char *zFilename, int eFType){ + return getStat(zFilename, eFType) ? -1 : fx.fileStat.st_mode; +} + +/* +** Return TRUE if either of the following are true: +** +** (1) zFilename is an ordinary file +** +** (2) allow_symlinks is on and zFilename is a symbolic link to +** a file, directory, or other object +*/ +int file_isfile_or_link(const char *zFilename){ + if( getStat(zFilename, RepoFILE) ){ + return 0; /* stat() failed. Return false. */ + } + return S_ISREG(fx.fileStat.st_mode) || S_ISLNK(fx.fileStat.st_mode); } /* ** Return TRUE if the named file is an ordinary file. Return false ** for directories, devices, fifos, symlinks, etc. */ -int file_isfile(const char *zFilename){ - return getStat(zFilename) ? 0 : S_ISREG(fileStat.st_mode); +int file_isfile(const char *zFilename, int eFType){ + return getStat(zFilename, eFType) ? 0 : S_ISREG(fx.fileStat.st_mode); +} + +/* +** Create a symbolic link named zLinkFile that points to zTargetFile. +** +** If allow-symlinks is off, create an ordinary file named zLinkFile +** with the name of zTargetFile as its content. +**/ +void symlink_create(const char *zTargetFile, const char *zLinkFile){ +#if !defined(_WIN32) + if( db_allow_symlinks() ){ + int i, nName; + char *zName, zBuf[1000]; + + nName = strlen(zLinkFile); + if( nName>=sizeof(zBuf) ){ + zName = mprintf("%s", zLinkFile); + }else{ + zName = zBuf; + memcpy(zName, zLinkFile, nName+1); + } + nName = file_simplify_name(zName, nName, 0); + for(i=1; i=0 ){ + fossil_free(z); + z = mprintf("%s-%s-%d", zBase, zSuffix, cnt++); + } + if( relFlag ){ + Blob x; + file_relative_name(z, &x, 0); + fossil_free(z); + z = blob_str(&x); + } + return z; } /* ** Return the tail of a file pathname. The tail is the last component ** of the path. For example, the tail of "/a/b/c.d" is "c.d". */ const char *file_tail(const char *z){ const char *zTail = z; + if( !zTail ) return 0; while( z[0] ){ - if( z[0]=='/' ) zTail = &z[1]; + if( fossil_isdirsep(z[0]) ) zTail = &z[1]; z++; } return zTail; } + +/* +** Return the directory of a file path name. The directory is all components +** except the last one. For example, the directory of "/a/b/c.d" is "/a/b". +** If there is no directory, NULL is returned; otherwise, the returned memory +** should be freed via fossil_free(). +*/ +char *file_dirname(const char *z){ + const char *zTail = file_tail(z); + if( zTail && zTail!=z ){ + return mprintf("%.*s", (int)(zTail-z-1), z); + }else{ + return 0; + } +} + +/* SQL Function: file_dirname(NAME) +** +** Return the directory for NAME +*/ +void file_dirname_sql_function( + sqlite3_context *context, + int argc, + sqlite3_value **argv +){ + const char *zName = (const char*)sqlite3_value_text(argv[0]); + char *zDir; + if( zName==0 ) return; + zDir = file_dirname(zName); + if( zDir ){ + sqlite3_result_text(context,zDir,-1,fossil_free); + } +} + + +/* +** Rename a file or directory. +** Returns zero upon success. +*/ +int file_rename( + const char *zFrom, + const char *zTo, + int isFromDir, + int isToDir +){ + int rc; +#if defined(_WIN32) + wchar_t *zMbcsFrom = fossil_utf8_to_path(zFrom, isFromDir); + wchar_t *zMbcsTo = fossil_utf8_to_path(zTo, isToDir); + rc = _wrename(zMbcsFrom, zMbcsTo); +#else + char *zMbcsFrom = fossil_utf8_to_path(zFrom, isFromDir); + char *zMbcsTo = fossil_utf8_to_path(zTo, isToDir); + rc = rename(zMbcsFrom, zMbcsTo); +#endif + fossil_path_free(zMbcsTo); + fossil_path_free(zMbcsFrom); + return rc; +} /* ** Copy the content of a file from one place to another. */ void file_copy(const char *zFrom, const char *zTo){ FILE *in, *out; int got; char zBuf[8192]; - in = fopen(zFrom, "rb"); + in = fossil_fopen(zFrom, "rb"); if( in==0 ) fossil_fatal("cannot open \"%s\" for reading", zFrom); - out = fopen(zTo, "wb"); + file_mkfolder(zTo, ExtFILE, 0, 0); + out = fossil_fopen(zTo, "wb"); if( out==0 ) fossil_fatal("cannot open \"%s\" for writing", zTo); while( (got=fread(zBuf, 1, sizeof(zBuf), in))>0 ){ fwrite(zBuf, 1, got, out); } fclose(in); fclose(out); + if( file_isexe(zFrom, ExtFILE) ) file_setexe(zTo, 1); +} + +/* +** COMMAND: test-file-copy +** +** Usage: %fossil test-file-copy SOURCE DESTINATION +** +** Make a copy of the file at SOURCE into a new name DESTINATION. Any +** directories in the path leading up to DESTINATION that do not already +** exist are created automatically. +*/ +void test_file_copy(void){ + if( g.argc!=4 ){ + fossil_fatal("Usage: %s test-file-copy SOURCE DESTINATION", g.argv[0]); + } + file_copy(g.argv[2], g.argv[3]); } /* -** Set or clear the execute bit on a file. +** Set or clear the execute bit on a file. Return true if a change +** occurred and false if this routine is a no-op. +** +** This routine assumes RepoFILE as the eFType. In other words, if +** zFilename is a symbolic link, it is the object that zFilename points +** to that is modified. */ -void file_setexe(const char *zFilename, int onoff){ -#ifndef __MINGW32__ +int file_setexe(const char *zFilename, int onoff){ + int rc = 0; +#if !defined(_WIN32) struct stat buf; - if( stat(zFilename, &buf)!=0 ) return; + if( fossil_stat(zFilename, &buf, RepoFILE)!=0 + || S_ISLNK(buf.st_mode) + || S_ISDIR(buf.st_mode) + ){ + return 0; + } if( onoff ){ - if( (buf.st_mode & 0111)!=0111 ){ - chmod(zFilename, buf.st_mode | 0111); + int targetMode = (buf.st_mode & 0444)>>2; + if( (buf.st_mode & 0100)==0 ){ + chmod(zFilename, buf.st_mode | targetMode); + rc = 1; } }else{ - if( (buf.st_mode & 0111)!=0 ){ + if( (buf.st_mode & 0100)!=0 ){ chmod(zFilename, buf.st_mode & ~0111); + rc = 1; } } -#endif /* __MINGW32__ */ +#endif /* _WIN32 */ + return rc; +} + +/* +** Set the mtime for a file. +*/ +void file_set_mtime(const char *zFilename, i64 newMTime){ +#if !defined(_WIN32) + char *zMbcs; + struct timeval tv[2]; + memset(tv, 0, sizeof(tv[0])*2); + tv[0].tv_sec = newMTime; + tv[1].tv_sec = newMTime; + zMbcs = fossil_utf8_to_path(zFilename, 0); + utimes(zMbcs, tv); +#else + struct _utimbuf tb; + wchar_t *zMbcs = fossil_utf8_to_path(zFilename, 0); + tb.actime = newMTime; + tb.modtime = newMTime; + _wutime(zMbcs, &tb); +#endif + fossil_path_free(zMbcs); +} + +/* +** COMMAND: test-set-mtime +** +** Usage: %fossil test-set-mtime FILENAME DATE/TIME +** +** Sets the mtime of the named file to the date/time shown. +*/ +void test_set_mtime(void){ + const char *zFile; + char *zDate; + i64 iMTime; + if( g.argc!=4 ){ + usage("FILENAME DATE/TIME"); + } + db_open_or_attach(":memory:", "mem"); + iMTime = db_int64(0, "SELECT strftime('%%s',%Q)", g.argv[3]); + zFile = g.argv[2]; + file_set_mtime(zFile, iMTime); + iMTime = file_mtime(zFile, RepoFILE); + zDate = db_text(0, "SELECT datetime(%lld, 'unixepoch')", iMTime); + fossil_print("Set mtime of \"%s\" to %s (%lld)\n", zFile, zDate, iMTime); +} + +/* +** Delete a file. +** +** If zFilename is a symbolic link, then it is the link itself that is +** removed, not the object that zFilename points to. +** +** Returns zero upon success. +*/ +int file_delete(const char *zFilename){ + int rc; +#ifdef _WIN32 + wchar_t *z = fossil_utf8_to_path(zFilename, 0); + rc = _wunlink(z); +#else + char *z = fossil_utf8_to_path(zFilename, 0); + rc = unlink(zFilename); +#endif + fossil_path_free(z); + return rc; +} + +/* SQL Function: file_delete(NAME) +** +** Remove file NAME. Return zero on success and non-zero if anything goes +** wrong. +*/ +void file_delete_sql_function( + sqlite3_context *context, + int argc, + sqlite3_value **argv +){ + const char *zName = (const char*)sqlite3_value_text(argv[0]); + int rc; + if( zName==0 ){ + rc = 1; + }else{ + rc = file_delete(zName); + } + sqlite3_result_int(context, rc); } /* -** Create the directory named in the argument, if it does not already -** exist. If forceFlag is 1, delete any prior non-directory object +** Create a directory called zName, if it does not already exist. +** If forceFlag is 1, delete any prior non-directory object ** with the same name. ** ** Return the number of errors. */ -int file_mkdir(const char *zName, int forceFlag){ - int rc = file_isdir(zName); +int file_mkdir(const char *zName, int eFType, int forceFlag){ + int rc = file_isdir(zName, eFType); if( rc==2 ){ if( !forceFlag ) return 1; - unlink(zName); + file_delete(zName); } if( rc!=1 ){ -#ifdef __MINGW32__ - return mkdir(zName); +#if defined(_WIN32) + wchar_t *zMbcs = fossil_utf8_to_path(zName, 1); + rc = _wmkdir(zMbcs); +#else + char *zMbcs = fossil_utf8_to_path(zName, 1); + rc = mkdir(zMbcs, 0755); +#endif + fossil_path_free(zMbcs); + return rc; + } + return 0; +} + +/* +** Create the tree of directories in which zFilename belongs, if that sequence +** of directories does not already exist. +** +** On success, return zero. On error, return errorReturn if positive, otherwise +** print an error message and abort. +*/ +int file_mkfolder( + const char *zFilename, /* Pathname showing directories to be created */ + int eFType, /* Follow symlinks if ExtFILE */ + int forceFlag, /* Delete non-directory objects in the way */ + int errorReturn /* What to do when an error is seen */ +){ + int nName, rc = 0; + char *zName; + + nName = strlen(zFilename); + zName = mprintf("%s", zFilename); + nName = file_simplify_name(zName, nName, 0); + while( nName>0 && zName[nName-1]!='/' ){ nName--; } + if( nName>1 ){ + zName[nName-1] = 0; + if( file_isdir(zName, eFType)!=1 ){ + rc = file_mkfolder(zName, eFType, forceFlag, errorReturn); + if( rc==0 ){ + if( file_mkdir(zName, eFType, forceFlag) + && file_isdir(zName, eFType)!=1 + ){ + if( errorReturn <= 0 ){ + fossil_fatal_recursive("unable to create directory %s", zName); + } + rc = errorReturn; + } + } + } + } + free(zName); + return rc; +} + +#if defined(_WIN32) +/* +** 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. +*/ +int 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; +} + +/* +** COMMAND: test-is-normal-dir +** +** Usage: %fossil test-is-normal-dir NAME... +** +** Returns non-zero if the specified names represent real directories, i.e. +** not junctions, symbolic links, etc. +*/ +void test_is_normal_dir(void){ + int i; + for(i=2; i %lx\n", g.argv[i], GetFileAttributesW(zMbcs)); + fossil_print("ISDIR \"%s\" -> %d\n", g.argv[i], file_is_normal_dir(zMbcs)); + fossil_path_free(zMbcs); + } +} +#endif + +/* +** Removes the directory named in the argument, if it exists. The directory +** must be empty and cannot be the current directory or the root directory. +** +** Returns zero upon success. +*/ +int file_rmdir(const char *zName){ + int rc = file_isdir(zName, RepoFILE); + if( rc==2 ) return 1; /* cannot remove normal file */ + if( rc==1 ){ +#if defined(_WIN32) + wchar_t *zMbcs = fossil_utf8_to_path(zName, 1); + if( file_is_normal_dir(zMbcs) ){ + rc = _wrmdir(zMbcs); + }else{ + rc = ENOTDIR; /* junction, symbolic link, etc. */ + } #else - return mkdir(zName, 0755); + char *zMbcs = fossil_utf8_to_path(zName, 1); + rc = rmdir(zName); #endif + fossil_path_free(zMbcs); + return rc; } return 0; } + +/* SQL Function: rmdir(NAME) +** +** Try to remove the directory NAME. Return zero on success and non-zero +** for failure. +*/ +void file_rmdir_sql_function( + sqlite3_context *context, + int argc, + sqlite3_value **argv +){ + const char *zName = (const char*)sqlite3_value_text(argv[0]); + int rc; + if( zName==0 ){ + rc = 1; + }else{ + rc = file_rmdir(zName); + } + sqlite3_result_int(context, rc); +} + +/* +** Check the input argument to see if it looks like it has an prefix that +** indicates a remote file. If so, return the tail of the specification, +** which is the name of the file on the remote system. +** +** If the input argument does not have a prefix that makes it look like +** a remote file reference, then return NULL. +** +** Remote files look like: "HOST:PATH" or "USER@HOST:PATH". Host must +** be a valid hostname, meaning it must follow these rules: +** +** * Only characters [-.a-zA-Z0-9]. No spaces or other punctuation +** * Does not begin or end with - +** +** The USER section, it it exists, must not contain the '@' character. +*/ +const char *file_skip_userhost(const char *zIn){ + const char *zTail; + int n, i; + if( zIn[0]==':' ) return 0; + zTail = strchr(zIn, ':'); + if( zTail==0 ) return 0; + if( zTail - zIn > 10000 ) return 0; + n = (int)(zTail - zIn); + if( zIn[n-1]=='-' || zIn[n-1]=='.' ) return 0; + for(i=n-1; i>0 && zIn[i-1]!='@'; i--){ + if( !fossil_isalnum(zIn[i]) && zIn[i]!='-' && zIn[i]!='.' ) return 0; + } + if( zIn[i]=='-' || zIn[i]=='.' || i==1 ) return 0; + if( i>1 ){ + i -= 2; + while( i>=0 ){ + if( zIn[i]=='@' ) return 0; + i--; + } + } + return zTail+1; +} /* ** 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 any of these characters in the path: "\*[]?" +** * 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 http://en.wikipedia.org/wiki/UTF-8#Invalid_byte_sequences +** and http://en.wikipedia.org/wiki/Unicode (for the noncharacters) +** +** The bStrictUtf8 flag is true for new inputs, but is false when parsing +** legacy manifests, for backwards compatibility. */ -int file_is_simple_pathname(const char *z){ +int file_is_simple_pathname(const char *z, int bStrictUtf8){ int i; - if( *z=='/' || *z==0 ) return 0; - if( *z=='.' ){ + unsigned char c = (unsigned char) z[0]; + char maskNonAscii = bStrictUtf8 ? 0x80 : 0x00; + if( c=='/' || c==0 ) return 0; + if( c=='.' ){ if( z[1]=='/' || z[1]==0 ) return 0; if( z[1]=='.' && (z[2]=='/' || z[2]==0) ) return 0; } - for(i=0; z[i]; i++){ - if( z[i]=='\\' || z[i]=='*' || z[i]=='[' || z[i]==']' || z[i]=='?' ){ + 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( z[i]=='/' ){ + 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; } @@ -217,91 +1034,520 @@ } } if( z[i-1]=='/' ) return 0; return 1; } +int file_is_simple_pathname_nonstrict(const char *z){ + unsigned char c = (unsigned char) z[0]; + if( c=='/' || c==0 ) return 0; + if( c=='.' ){ + if( z[1]=='/' || z[1]==0 ) return 0; + if( z[1]=='.' && (z[2]=='/' || z[2]==0) ) return 0; + } + while( (z = strchr(z+1, '/'))!=0 ){ + if( z[1]=='/' ) return 0; + if( z[1]==0 ) return 0; + if( z[1]=='.' ){ + if( z[2]=='/' || z[2]==0 ) return 0; + if( z[2]=='.' && (z[3]=='/' || z[3]==0) ) 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 int backup_dir(const char *z, int *pJ){ + int j = *pJ; + int i; + if( j<=0 ) 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; +} /* ** Simplify a filename by ** +** * Remove extended path prefix on windows and cygwin +** * Convert 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. */ -int file_simplify_name(char *z, int n){ - int i, j; +int file_simplify_name(char *z, int n, int slash){ + int i = 1, j; + assert( z!=0 ); if( n<0 ) n = strlen(z); -#ifdef __MINGW32__ - for(i=0; i3 && !memcmp(z, "//?/", 4) ){ + if( fossil_strnicmp(z+4,"UNC", 3) ){ + i += 4; + z[0] = z[4]; + }else{ + i += 6; + z[0] = '/'; + } } #endif - while( n>1 && z[n-1]=='/' ){ n--; } - for(i=j=0; i1 && z[n-1]=='/' ){ n--; } + } + + /* Remove duplicate '/' characters. Except, two // at the beginning + ** of a pathname is allowed since this is important on windows. */ + for(j=1; i0 && z[j-1]!='/' ){ j--; } - if( j>0 ){ j--; } + + /* 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=0 ) z[j] = z[i]; + j++; } + if( j==0 ) z[j++] = '/'; z[j] = 0; return j; } + +/* +** COMMAND: test-simplify-name +** +** Usage: %fossil test-simplify-name FILENAME... +** +** Print the simplified versions of each FILENAME. This is used to test +** the file_simplify_name() routine. +** +** If FILENAME is of the form "HOST:PATH" or "USER@HOST:PATH", then remove +** and print the remote host prefix first. This is used to test the +** file_skip_userhost() interface. +*/ +void cmd_test_simplify_name(void){ + int i; + char *z; + const char *zTail; + for(i=2; i ", z); + file_simplify_name(z, -1, 0); + fossil_print("[%s]\n", z); + fossil_free(z); + } +} + +/* +** Get the current working directory. +** +** On windows, the name is converted from unicode to UTF8 and all '\\' +** characters are converted to '/'. No conversions are needed on +** unix. +** +** Store the value of the CWD in zBuf which is nBuf bytes in size. +** or if zBuf==0, allocate space to hold the result using fossil_malloc(). +*/ +char *file_getcwd(char *zBuf, int nBuf){ + char zTemp[2000]; + if( zBuf==0 ){ + zBuf = zTemp; + nBuf = sizeof(zTemp); + } +#ifdef _WIN32 + win32_getcwd(zBuf, nBuf); +#else + if( getcwd(zBuf, nBuf-1)==0 ){ + if( errno==ERANGE ){ + fossil_fatal("pwd too big: max %d", nBuf-1); + }else{ + fossil_fatal("cannot find current working directory; %s", + strerror(errno)); + } + } +#endif + return zBuf==zTemp ? fossil_strdup(zBuf) : zBuf; +} + +/* +** Return true if zPath is an absolute pathname. Return false +** if it is relative. +*/ +int file_is_absolute_path(const char *zPath){ + if( fossil_isdirsep(zPath[0]) +#if defined(_WIN32) || defined(__CYGWIN__) + || (fossil_isalpha(zPath[0]) && zPath[1]==':' + && (fossil_isdirsep(zPath[2]) || zPath[2]=='\0')) +#endif + ){ + return 1; + }else{ + return 0; + } +} /* ** Compute a canonical pathname for a file or directory. ** Make the name absolute if it is relative. ** Remove redundant / characters ** Remove all /./ path elements. ** Convert /A/../ to just / +** If the slash parameter is non-zero, the trailing slash, if any, +** is retained. +** +** See also: file_canonical_name_dup() */ -void file_canonical_name(const char *zOrigName, Blob *pOut){ - if( zOrigName[0]=='/' -#ifdef __MINGW32__ - || zOrigName[0]=='\\' - || (strlen(zOrigName)>3 && zOrigName[1]==':' - && (zOrigName[2]=='\\' || zOrigName[2]=='/')) -#endif - ){ - blob_set(pOut, zOrigName); - blob_materialize(pOut); +void file_canonical_name(const char *zOrigName, Blob *pOut, int slash){ + blob_zero(pOut); + if( file_is_absolute_path(zOrigName) ){ + blob_appendf(pOut, "%/", zOrigName); }else{ char zPwd[2000]; - if( getcwd(zPwd, sizeof(zPwd)-20)==0 ){ - fprintf(stderr, "pwd too big: max %d\n", (int)sizeof(zPwd)-20); - exit(1); - } - blob_zero(pOut); - blob_appendf(pOut, "%//%/", zPwd, zOrigName); - } - blob_resize(pOut, file_simplify_name(blob_buffer(pOut), blob_size(pOut))); + file_getcwd(zPwd, sizeof(zPwd)-strlen(zOrigName)); + if( zPwd[0]=='/' && strlen(zPwd)==1 ){ + /* when on '/', don't add an extra '/' */ + if( zOrigName[0]=='.' && strlen(zOrigName)==1 ){ + /* '.' when on '/' mean '/' */ + blob_appendf(pOut, "%/", zPwd); + }else{ + blob_appendf(pOut, "%/%/", zPwd, zOrigName); + } + }else{ + blob_appendf(pOut, "%//%/", zPwd, zOrigName); + } + } +#if defined(_WIN32) || defined(__CYGWIN__) + { + char *zOut; + /* + ** On Windows/cygwin, normalize the drive letter to upper case. + */ + zOut = blob_str(pOut); + if( fossil_islower(zOut[0]) && zOut[1]==':' && zOut[2]=='/' ){ + zOut[0] = fossil_toupper(zOut[0]); + } + } +#endif + blob_resize(pOut, file_simplify_name(blob_buffer(pOut), + blob_size(pOut), slash)); +} + +/* +** Compute the canonical name of a file. Store that name in +** memory obtained from fossil_malloc() and return a pointer to the +** name. +** +** See also: file_canonical_name() +*/ +char *file_canonical_name_dup(const char *zOrigName){ + Blob x; + if( zOrigName==0 ) return 0; + blob_init(&x, 0, 0); + file_canonical_name(zOrigName, &x, 0); + return blob_str(&x); +} + +/* +** The input is the name of an executable, such as one might +** type on a command-line. This routine resolves that name into +** a full pathname. The result is obtained from fossil_malloc() +** and should be freed by the caller. +** +** This routine only works on unix. On Windows, simply return +** a copy of the input. +*/ +char *file_fullexename(const char *zCmd){ +#ifdef _WIN32 + char *zPath; + char *z = 0; + const char *zExe = ""; + if( sqlite3_strlike("%.exe",zCmd,0)!=0 ) zExe = ".exe"; + if( file_is_absolute_path(zCmd) ){ + return mprintf("%s%s", zCmd, zExe); + } + if( strchr(zCmd,'\\')!=0 && strchr(zCmd,'/')!=0 ){ + int i; + Blob out = BLOB_INITIALIZER; + file_canonical_name(zCmd, &out, 0); + blob_append(&out, zExe, -1); + z = fossil_strdup(blob_str(&out)); + blob_reset(&out); + for(i=0; z[i]; i++){ if( z[i]=='/' ) z[i] = '\\'; } + return z; + } + z = mprintf(".\\%s%s", zCmd, zExe); + if( file_isfile(z, ExtFILE) ){ + int i; + Blob out = BLOB_INITIALIZER; + file_canonical_name(zCmd, &out, 0); + blob_append(&out, zExe, -1); + z = fossil_strdup(blob_str(&out)); + blob_reset(&out); + for(i=0; z[i]; i++){ if( z[i]=='/' ) z[i] = '\\'; } + return z; + } + fossil_free(z); + zPath = fossil_getenv("PATH"); + while( zPath && zPath[0] ){ + int n; + char *zColon; + zColon = strchr(zPath, ';'); + n = zColon ? (int)(zColon-zPath) : (int)strlen(zPath); + while( n>0 && zPath[n-1]=='\\' ){ n--; } + z = mprintf("%.*s\\%s%s", n, zPath, zCmd, zExe); + if( file_isfile(z, ExtFILE) ){ + return z; + } + fossil_free(z); + if( zColon==0 ) break; + zPath = zColon+1; + } + return fossil_strdup(zCmd); +#else + char *zPath; + char *z; + if( zCmd[0]=='/' ){ + return fossil_strdup(zCmd); + } + if( strchr(zCmd,'/')!=0 ){ + Blob out = BLOB_INITIALIZER; + file_canonical_name(zCmd, &out, 0); + z = fossil_strdup(blob_str(&out)); + blob_reset(&out); + return z; + } + zPath = fossil_getenv("PATH"); + while( zPath && zPath[0] ){ + int n; + char *zColon; + zColon = strchr(zPath, ':'); + n = zColon ? (int)(zColon-zPath) : (int)strlen(zPath); + z = mprintf("%.*s/%s", n, zPath, zCmd); + if( file_isexe(z, ExtFILE) ){ + return z; + } + fossil_free(z); + if( zColon==0 ) break; + zPath = zColon+1; + } + return fossil_strdup(zCmd); +#endif +} + +/* +** COMMAND: test-which +** +** Usage: %fossil test-which ARGS... +** +** For each argument, search the PATH for the executable with the name +** and print its full pathname. +*/ +void test_which_cmd(void){ + int i; + for(i=2; i [%s]\n", zPath, zFull); + memset(&testFileStat, 0, sizeof(struct fossilStat)); + rc = fossil_stat(zPath, &testFileStat, 0); + fossil_print(" stat_rc = %d\n", rc); + sqlite3_snprintf(sizeof(zBuf), zBuf, "%lld", testFileStat.st_size); + fossil_print(" stat_size = %s\n", zBuf); + if( g.db==0 ) sqlite3_open(":memory:", &g.db); + z = db_text(0, "SELECT datetime(%lld, 'unixepoch')", testFileStat.st_mtime); + sqlite3_snprintf(sizeof(zBuf), zBuf, "%lld (%s)", testFileStat.st_mtime, z); + fossil_free(z); + fossil_print(" stat_mtime = %s\n", zBuf); + fossil_print(" stat_mode = 0%o\n", testFileStat.st_mode); + memset(&testFileStat, 0, sizeof(struct fossilStat)); + rc = fossil_stat(zPath, &testFileStat, 1); + fossil_print(" l_stat_rc = %d\n", rc); + sqlite3_snprintf(sizeof(zBuf), zBuf, "%lld", testFileStat.st_size); + fossil_print(" l_stat_size = %s\n", zBuf); + z = db_text(0, "SELECT datetime(%lld, 'unixepoch')", testFileStat.st_mtime); + sqlite3_snprintf(sizeof(zBuf), zBuf, "%lld (%s)", testFileStat.st_mtime, z); + fossil_free(z); + fossil_print(" l_stat_mtime = %s\n", zBuf); + fossil_print(" l_stat_mode = 0%o\n", testFileStat.st_mode); + if( reset ) resetStat(); + sqlite3_snprintf(sizeof(zBuf), zBuf, "%lld", file_size(zPath,ExtFILE)); + fossil_print(" file_size(ExtFILE) = %s\n", zBuf); + iMtime = file_mtime(zPath, ExtFILE); + z = db_text(0, "SELECT datetime(%lld, 'unixepoch')", iMtime); + sqlite3_snprintf(sizeof(zBuf), zBuf, "%lld (%s)", iMtime, z); + fossil_free(z); + fossil_print(" file_mtime(ExtFILE) = %s\n", zBuf); + fossil_print(" file_mode(ExtFILE) = 0%o\n", file_mode(zPath,ExtFILE)); + fossil_print(" file_isfile(ExtFILE) = %d\n", file_isfile(zPath,ExtFILE)); + fossil_print(" file_isdir(ExtFILE) = %d\n", file_isdir(zPath,ExtFILE)); + if( reset ) resetStat(); + sqlite3_snprintf(sizeof(zBuf), zBuf, "%lld", file_size(zPath,RepoFILE)); + fossil_print(" file_size(RepoFILE) = %s\n", zBuf); + iMtime = file_mtime(zPath,RepoFILE); + z = db_text(0, "SELECT datetime(%lld, 'unixepoch')", iMtime); + sqlite3_snprintf(sizeof(zBuf), zBuf, "%lld (%s)", iMtime, z); + fossil_free(z); + fossil_print(" file_mtime(RepoFILE) = %s\n", zBuf); + fossil_print(" file_mode(RepoFILE) = 0%o\n", file_mode(zPath,RepoFILE)); + fossil_print(" file_isfile(RepoFILE) = %d\n", file_isfile(zPath,RepoFILE)); + fossil_print(" file_isfile_or_link = %d\n", file_isfile_or_link(zPath)); + fossil_print(" file_islink = %d\n", file_islink(zPath)); + fossil_print(" file_isexe(RepoFILE) = %d\n", file_isexe(zPath,RepoFILE)); + fossil_print(" file_isdir(RepoFILE) = %d\n", file_isdir(zPath,RepoFILE)); + fossil_print(" file_is_repository = %d\n", file_is_repository(zPath)); + fossil_print(" file_is_reserved_name = %d\n", + file_is_reserved_name(zFull,-1)); + fossil_print(" file_in_cwd = %d\n", file_in_cwd(zPath)); + blob_reset(&x); + if( reset ) resetStat(); +} + +/* +** COMMAND: test-file-environment +** +** Usage: %fossil test-file-environment FILENAME... +** +** Display the effective file handling subsystem "settings" and then +** display file system information about the files specified, if any. +** +** Options: +** +** --allow-symlinks BOOLEAN Temporarily turn allow-symlinks on/off +** --open-config Open the configuration database first. +** --reset Reset cached stat() info for each file. +** --root ROOT Use ROOT as the root of the checkout +** --slash Trailing slashes, if any, are retained. +*/ +void cmd_test_file_environment(void){ + int i; + int slashFlag = find_option("slash",0,0)!=0; + int resetFlag = find_option("reset",0,0)!=0; + const char *zRoot = find_option("root",0,1); + const char *zAllow = find_option("allow-symlinks",0,1); + if( find_option("open-config", 0, 0)!=0 ){ + Th_OpenConfig(1); + } + db_find_and_open_repository(OPEN_ANY_SCHEMA|OPEN_OK_NOT_FOUND, 0); + fossil_print("filenames_are_case_sensitive() = %d\n", + filenames_are_case_sensitive()); + if( zAllow ){ + g.allowSymlinks = !is_false(zAllow); + } + if( zRoot==0 ) zRoot = g.zLocalRoot; + fossil_print("db_allow_symlinks() = %d\n", db_allow_symlinks()); + fossil_print("local-root = [%s]\n", zRoot); + for(i=2; i [%s]\n", zName, blob_buffer(&x)); blob_reset(&x); + sqlite3_snprintf(sizeof(zBuf), zBuf, "%lld", file_size(zName,RepoFILE)); + fossil_print(" file_size = %s\n", zBuf); + sqlite3_snprintf(sizeof(zBuf), zBuf, "%lld", file_mtime(zName,RepoFILE)); + fossil_print(" file_mtime = %s\n", zBuf); + fossil_print(" file_isfile = %d\n", file_isfile(zName,RepoFILE)); + fossil_print(" file_isfile_or_link = %d\n", file_isfile_or_link(zName)); + fossil_print(" file_islink = %d\n", file_islink(zName)); + fossil_print(" file_isexe = %d\n", file_isexe(zName,RepoFILE)); + fossil_print(" file_isdir = %d\n", file_isdir(zName,RepoFILE)); } } /* ** Return TRUE if the given filename is canonical. @@ -310,12 +1556,12 @@ ** contain no "/./" or "/../" terms. */ int file_is_canonical(const char *z){ int i; if( z[0]!='/' -#ifdef __MINGW32__ - && (z[0]==0 || z[1]!=':' || z[2]!='/') +#if defined(_WIN32) || defined(__CYGWIN__) + && (!fossil_isupper(z[0]) || z[1]!=':' || z[2]!='/') #endif ) return 0; for(i=0; z[i]; i++){ if( z[i]=='\\' ) return 0; @@ -326,41 +1572,65 @@ } } } return 1; } + +/* +** Return a pointer to the first character in a pathname past the +** drive letter. This routine is a no-op on unix. +*/ +char *file_without_drive_letter(char *zIn){ +#ifdef _WIN32 + if( fossil_isalpha(zIn[0]) && zIn[1]==':' ) zIn += 2; +#endif + return zIn; +} /* ** Compute a pathname for a file or directory that is relative -** to the current directory. +** to the current directory. If the slash parameter is non-zero, +** the trailing slash, if any, is retained. */ -void file_relative_name(const char *zOrigName, Blob *pOut){ +void file_relative_name(const char *zOrigName, Blob *pOut, int slash){ char *zPath; blob_set(pOut, zOrigName); - blob_resize(pOut, file_simplify_name(blob_buffer(pOut), blob_size(pOut))); - zPath = blob_buffer(pOut); + blob_resize(pOut, file_simplify_name(blob_buffer(pOut), + blob_size(pOut), slash)); + zPath = file_without_drive_letter(blob_buffer(pOut)); if( zPath[0]=='/' ){ int i, j; Blob tmp; - char zPwd[2000]; - if( getcwd(zPwd, sizeof(zPwd)-20)==0 ){ - fprintf(stderr, "pwd too big: max %d\n", (int)sizeof(zPwd)-20); - exit(1); - } - for(i=1; zPath[i] && zPwd[i]==zPath[i]; i++){} + char *zPwd; + char zBuf[2000]; + zPwd = zBuf; + file_getcwd(zBuf, sizeof(zBuf)-20); + zPwd = file_without_drive_letter(zBuf); + i = 1; +#if defined(_WIN32) || defined(__CYGWIN__) + while( zPath[i] && fossil_tolower(zPwd[i])==fossil_tolower(zPath[i]) ) i++; +#else + while( zPath[i] && zPwd[i]==zPath[i] ) i++; +#endif if( zPath[i]==0 ){ - blob_reset(pOut); + memcpy(&tmp, pOut, sizeof(tmp)); if( zPwd[i]==0 ){ - blob_append(pOut, ".", 1); + blob_set(pOut, "."); }else{ - blob_append(pOut, "..", 2); + blob_set(pOut, ".."); for(j=i+1; zPwd[j]; j++){ - if( zPwd[j]=='/' ) { + if( zPwd[j]=='/' ){ blob_append(pOut, "/..", 3); } } + while( i>0 && (zPwd[i]!='/')) --i; + blob_append(pOut, zPath+i, j-i); + } + if( slash && i>0 && zPath[strlen(zPath)-1]=='/'){ + blob_append(pOut, "/", 1); } + blob_reset(&tmp); return; } if( zPwd[i]==0 && zPath[i]=='/' ){ memcpy(&tmp, pOut, sizeof(tmp)); blob_set(pOut, "./"); @@ -367,13 +1637,18 @@ blob_append(pOut, &zPath[i+1], -1); blob_reset(&tmp); return; } while( zPath[i-1]!='/' ){ i--; } - blob_set(&tmp, "../"); + if( zPwd[0]=='/' && strlen(zPwd)==1 ){ + /* If on '/', don't go to higher level */ + blob_zero(&tmp); + }else{ + blob_set(&tmp, "../"); + } for(j=i; zPwd[j]; j++){ - if( zPwd[j]=='/' ) { + if( zPwd[j]=='/' ){ blob_append(&tmp, "../", 3); } } blob_append(&tmp, &zPath[i], -1); blob_reset(pOut); @@ -380,67 +1655,179 @@ memcpy(pOut, &tmp, sizeof(tmp)); } } /* -** COMMAND: test-relative-name +** COMMAND: test-relative-name ** ** Test the operation of the relative name generator. */ void cmd_test_relative_name(void){ int i; Blob x; + int slashFlag = find_option("slash",0,0)!=0; blob_zero(&x); for(i=2; i0 && zLocalRoot[nLocalRoot-1]=='/' ); + file_canonical_name(zOrigName, &full, 0); + nFull = blob_size(&full); + zFull = blob_buffer(&full); + if( filenames_are_case_sensitive() ){ + xCmp = fossil_strncmp; + }else{ + xCmp = fossil_strnicmp; + } + + /* Special case. zOrigName refers to g.zLocalRoot directory. */ + if( (nFull==nLocalRoot-1 && xCmp(zLocalRoot, zFull, nFull)==0) + || (nFull==1 && zFull[0]=='/' && nLocalRoot==1 && zLocalRoot[0]=='/') ){ + if( absolute ){ + blob_append(pOut, zLocalRoot, nLocalRoot); + }else{ + blob_append(pOut, ".", 1); + } + blob_reset(&localRoot); + blob_reset(&full); + return 1; + } + + if( nFull<=nLocalRoot || xCmp(zLocalRoot, zFull, nLocalRoot) ){ + blob_reset(&localRoot); blob_reset(&full); if( errFatal ){ fossil_fatal("file outside of checkout tree: %s", zOrigName); } return 0; } - blob_zero(pOut); - blob_append(pOut, blob_buffer(&full)+n, blob_size(&full)-n); + if( absolute ){ + if( !file_is_absolute_path(zOrigName) ){ + blob_append(pOut, zLocalRoot, nLocalRoot); + } + blob_append(pOut, zOrigName, -1); + blob_resize(pOut, file_simplify_name(blob_buffer(pOut), + blob_size(pOut), 0)); + }else{ + blob_append(pOut, &zFull[nLocalRoot], nFull-nLocalRoot); + } + blob_reset(&localRoot); + blob_reset(&full); return 1; } /* -** COMMAND: test-tree-name +** COMMAND: test-tree-name ** ** Test the operation of the tree name generator. +** +** Options: +** --absolute Return an absolute path instead of a relative one. +** --case-sensitive B Enable or disable case-sensitive filenames. B is +** a boolean: "yes", "no", "true", "false", etc. */ void cmd_test_tree_name(void){ int i; Blob x; + int absoluteFlag = find_option("absolute",0,0)!=0; + db_find_and_open_repository(0,0); blob_zero(&x); for(i=2; inCwd+1 + && xCmp(zFull,zCwd,nCwd)==0 + && zFull[nCwd]=='/' + && strchr(zFull+nCwd+1, '/')==0 + ){ + rc = 1; + }else{ + rc = 0; + } + fossil_free(zFull); + fossil_free(zCwd); + return rc; +} /* ** Parse a URI into scheme, host, port, and path. */ void file_parse_uri( @@ -480,48 +1867,879 @@ blob_set(pPath, "/"); } } /* -** Construct a random temporary filename into zBuf[]. +** Construct a random temporary filename into pBuf where the name of +** the temporary file is derived from zBasis. The suffix on the temp +** file is the same as the suffix on zBasis, and the temp file has +** the root of zBasis in its name. +** +** If zTag is not NULL, then try to create the temp-file using zTag +** as a differentiator. If that fails, or if zTag is NULL, then use +** a bunch of random characters as the tag. +** +** Dangerous characters in zBasis are changed. */ -void file_tempname(int nBuf, char *zBuf){ +void file_tempname(Blob *pBuf, const char *zBasis, const char *zTag){ +#if defined(_WIN32) + const char *azDirs[] = { + 0, /* GetTempPath */ + 0, /* TEMP */ + 0, /* TMP */ + ".", + }; +#else static const char *azDirs[] = { + 0, /* TMPDIR */ "/var/tmp", "/usr/tmp", "/tmp", "/temp", ".", }; +#endif static const unsigned char zChars[] = "abcdefghijklmnopqrstuvwxyz" "ABCDEFGHIJKLMNOPQRSTUVWXYZ" "0123456789"; - unsigned int i, j; - struct stat buf; + unsigned int i; const char *zDir = "."; - - for(i=0; i0 && z[i]=='\\' ) z[i] = 0; + } + + azDirs[1] = fossil_getenv("TEMP"); + azDirs[2] = fossil_getenv("TMP"); +#else + azDirs[0] = fossil_getenv("TMPDIR"); +#endif + + for(i=0; i= (size_t)nBuf ){ - fossil_fatal("insufficient space for temporary filename"); + assert( zBasis!=0 ); + zSuffix = 0; + for(i=0; zBasis[i]; i++){ + if( zBasis[i]=='/' || zBasis[i]=='\\' ){ + zBasis += i+1; + i = -1; + }else if( zBasis[i]=='.' ){ + zSuffix = zBasis + i; + } + } + if( zSuffix==0 || zSuffix<=zBasis ){ + zSuffix = ""; + nBasis = i; + }else{ + nBasis = (int)(zSuffix - zBasis); } - + if( nBasis==0 ){ + nBasis = 6; + zBasis = "fossil"; + } do{ - sqlite3_snprintf(nBuf-17, zBuf, "%s/", zDir); - j = (int)strlen(zBuf); - sqlite3_randomness(15, &zBuf[j]); - for(i=0; i<15; i++, j++){ - zBuf[j] = (char)zChars[ ((unsigned char)zBuf[j])%(sizeof(zChars)-1) ]; - } - zBuf[j] = 0; - }while( access(zBuf,0)==0 ); + blob_zero(pBuf); + if( cnt++>20 ) fossil_fatal("cannot generate a temporary filename"); + if( zTag==0 ){ + sqlite3_randomness(15, zRand); + for(i=0; i<15; i++){ + zRand[i] = (char)zChars[ ((unsigned char)zRand[i])%(sizeof(zChars)-1) ]; + } + zRand[15] = 0; + zTag = zRand; + } + blob_appendf(pBuf, "%s/%.*s~%s%s", zDir, nBasis, zBasis, zTag, zSuffix); + zTag = 0; + for(z=blob_str(pBuf); z!=0 && (z=strpbrk(z,"'\"`;|$&"))!=0; z++){ + z[0] = '_'; + } + }while( file_size(blob_str(pBuf), ExtFILE)>=0 ); + +#if defined(_WIN32) + fossil_path_free((char *)azDirs[0]); + fossil_path_free((char *)azDirs[1]); + fossil_path_free((char *)azDirs[2]); + /* Change all \ characters in the windows path into / so that they can + ** be safely passed to a subcommand, such as by gdiff */ + z = blob_buffer(pBuf); + for(i=0; z[i]; i++) if( z[i]=='\\' ) z[i] = '/'; +#else + fossil_path_free((char *)azDirs[0]); +#endif +} + +/* +** Compute a temporary filename in zDir. The filename is based on +** the current time. +*/ +char *file_time_tempname(const char *zDir, const char *zSuffix){ + struct tm *tm; + unsigned int r; + static unsigned int cnt = 0; + time_t t; + t = time(0); + tm = gmtime(&t); + sqlite3_randomness(sizeof(r), &r); + return mprintf("%s/%04d%02d%02d%02d%02d%02d%04d%06d%s", + zDir, tm->tm_year+1900, tm->tm_mon+1, tm->tm_mday, + tm->tm_hour, tm->tm_min, tm->tm_sec, cnt++, r%1000000, zSuffix); +} + + +/* +** COMMAND: test-tempname +** Usage: fossil test-name [--time SUFFIX] [--tag NAME] BASENAME ... +** +** Generate temporary filenames derived from BASENAME. Use the --time +** option to generate temp names based on the time of day. If --tag NAME +** is specified, try to use NAME as the differentiator in the temp file. +*/ +void file_test_tempname(void){ + int i; + const char *zSuffix = find_option("time",0,1); + Blob x = BLOB_INITIALIZER; + char *z; + const char *zTag = find_option("tag",0,1); + verify_all_options(); + for(i=2; i=4 && fossil_isdigit(zPath[3]) + && (zPath[4]=='/' || zPath[4]=='.' || zPath[4]==0)) + || (i<4 && (zPath[3]=='/' || zPath[3]=='.' || zPath[3]==0))) + ){ + sqlite3_snprintf(5,zReturn,"%.*s", i>=4 ? 4 : 3, zPath); + return zReturn; + } + } + while( zPath[0] && zPath[0]!='/' ) zPath++; + while( zPath[0]=='/' ) zPath++; + } + return 0; +} + +/* +** COMMAND: test-valid-for-windows +** Usage: fossil test-valid-for-windows FILENAME .... +** +** Show which filenames are not valid for Windows +*/ +void file_test_valid_for_windows(void){ + int i; + for(i=2; id_name[0]==0 ) continue; + if( omitDotFiles && pEntry->d_name[0]=='.' ) continue; + if( zGlob ){ + char *zUtf8 = fossil_path_to_utf8(pEntry->d_name); + int rc = sqlite3_strglob(zGlob, zUtf8); + fossil_path_free(zUtf8); + if( rc ) continue; + } + n++; + } + closedir(d); + } + fossil_path_free(zNative); + return n; +} + +/* +** COMMAND: test-dir-size +** +** Usage: %fossil test-dir-size NAME [GLOB] [--nodots] +** +** Return the number of objects in the directory NAME. If GLOB is +** provided, then only count objects that match the GLOB pattern. +** if --nodots is specified, omit files that begin with ".". +*/ +void test_dir_size_cmd(void){ + int omitDotFiles = find_option("nodots",0,0)!=0; + const char *zGlob; + const char *zDir; + verify_all_options(); + if( g.argc!=3 && g.argc!=4 ){ + usage("NAME [GLOB] [-nodots]"); + } + zDir = g.argv[2]; + zGlob = g.argc==4 ? g.argv[3] : 0; + fossil_print("%d\n", file_directory_size(zDir, zGlob, omitDotFiles)); +} + +/* +** Internal helper for touch_cmd(). zAbsName must be resolvable as-is +** to an existing file - this function does not expand/normalize +** it. i.e. it "really should" be an absolute path. zTreeName is +** strictly cosmetic: it is used when dryRunFlag, verboseFlag, or +** quietFlag generate output, and is assumed to be a repo-relative or +** or subdir-relative filename. +** +** newMTime is the file's new timestamp (Unix epoch). +** +** Returns 1 if it sets zAbsName's mtime, 0 if it does not (indicating +** that the file already has that timestamp or a warning was emitted +** or was not found). If dryRunFlag is true then it outputs the name +** of the file it would have timestamped but does not stamp the +** file. If verboseFlag is true, it outputs a message if the file's +** timestamp is actually modified. If quietFlag is true then the +** output of non-fatal warning messages is suppressed. +** +** As a special case, if newMTime is 0 then this function emits a +** warning (unless quietFlag is true), does NOT set the timestamp, and +** returns 0. The timestamp is known to be zero when +** mtime_of_manifest_file() is asked to provide the timestamp for a +** file which is currently undergoing an uncommitted merge (though +** this may depend on exactly where that merge is happening the +** history of the project). +*/ +static int touch_cmd_stamp_one_file(char const *zAbsName, + char const *zTreeName, + i64 newMtime, int dryRunFlag, + int verboseFlag, int quietFlag){ + i64 currentMtime; + if(newMtime==0){ + if( quietFlag==0 ){ + fossil_print("SKIPPING timestamp of 0: %s\n", zTreeName); + } + return 0; + } + currentMtime = file_mtime(zAbsName, 0); + if(currentMtime<0){ + fossil_print("SKIPPING: cannot stat file: %s\n", zAbsName); + return 0; + }else if(currentMtime==newMtime){ + return 0; + }else if( dryRunFlag!=0 ){ + fossil_print( "dry-run: %s\n", zTreeName ); + }else{ + file_set_mtime(zAbsName, newMtime); + if( verboseFlag!=0 ){ + fossil_print( "touched %s\n", zTreeName ); + } + } + return 1; +} + +/* +** Internal helper for touch_cmd(). If the given file name is found in +** the given checkout version, which MUST be the checkout version +** currently populating the vfile table, the vfile.mrid value for the +** file is returned, else 0 is returned. zName must be resolvable +** as-is from the vfile table - this function neither expands nor +** normalizes it, though it does compare using the repo's +** filename_collation() preference. +*/ +static int touch_cmd_vfile_mrid( int vid, char const *zName ){ + int mrid = 0; + static Stmt q = empty_Stmt_m; + db_static_prepare(&q, + "SELECT vfile.mrid " + "FROM vfile LEFT JOIN blob ON vfile.mrid=blob.rid " + "WHERE vid=:vid AND pathname=:pathname %s", + filename_collation()); + db_bind_int(&q, ":vid", vid); + db_bind_text(&q, ":pathname", zName); + if(SQLITE_ROW==db_step(&q)){ + mrid = db_column_int(&q, 0); + } + db_reset(&q); + return mrid; +} + +/* +** COMMAND: touch* +** +** Usage: %fossil touch ?OPTIONS? ?FILENAME...? +** +** For each file in the current checkout matching one of the provided +** list of glob patterns and/or file names, the file's mtime is +** updated to a value specified by one of the flags --checkout, +** --checkin, or --now. +** +** If neither glob patterns nor filenames are provided, it operates on +** all files managed by the currently checked-out version. +** +** This command gets its name from the conventional Unix "touch" +** command. +** +** Options: +** --now Stamp each affected file with the current time. +** This is the default behavior. +** -c|--checkin Stamp each affected file with the time of the +** most recent check-in which modified that file. +** -C|--checkout Stamp each affected file with the time of the +** currently-checked-out version. +** -g GLOBLIST Comma-separated list of glob patterns. +** -G GLOBFILE Similar to -g but reads its globs from a +** fossil-conventional glob list file. +** -v|--verbose Outputs extra information about its globs +** and each file it touches. +** -n|--dry-run Outputs which files would require touching, +** but does not touch them. +** -q|--quiet Suppress warnings, e.g. when skipping unmanaged +** or out-of-tree files. +** +** Only one of --now, --checkin, and --checkout may be used. The +** default is --now. +** +** Only one of -g or -G may be used. If neither is provided and no +** additional filenames are provided, the effect is as if a glob of +** '*' were provided, i.e. all files belonging to the +** currently-checked-out version. Note that all glob patterns provided +** via these flags are always evaluated as if they are relative to the +** top of the source tree, not the current working (sub)directory. +** Filenames provided without these flags, on the other hand, are +** treated as relative to the current directory. +** +** As a special case, files currently undergoing an uncommitted merge +** might not get timestamped with --checkin because it may be +** impossible for fossil to choose between multiple potential +** timestamps. A non-fatal warning is emitted for such cases. +** +*/ +void touch_cmd(){ + const char * zGlobList; /* -g List of glob patterns */ + const char * zGlobFile; /* -G File of glob patterns */ + Glob * pGlob = 0; /* List of glob patterns */ + int verboseFlag; + int dryRunFlag; + int vid; /* Checkout version */ + int changeCount = 0; /* Number of files touched */ + int quietFlag = 0; /* -q|--quiet */ + int timeFlag; /* -1==--checkin, 1==--checkout, 0==--now */ + i64 nowTime = 0; /* Timestamp of --now or --checkout */ + Stmt q; + Blob absBuffer = empty_blob; /* Absolute filename buffer */ + + verboseFlag = find_option("verbose","v",0)!=0; + quietFlag = find_option("quiet","q",0)!=0 || g.fQuiet; + dryRunFlag = find_option("dry-run","n",0)!=0 + || find_option("dryrun",0,0)!=0; + zGlobList = find_option("glob", "g",1); + zGlobFile = find_option("globfile", "G",1); + + if(zGlobList && zGlobFile){ + fossil_fatal("Options -g and -G may not be used together."); + } + + { + int const ci = + (find_option("checkin","c",0) || find_option("check-in",0,0)) + ? 1 : 0; + int const co = find_option("checkout","C",0) ? 1 : 0; + int const now = find_option("now",0,0) ? 1 : 0; + if(ci + co + now > 1){ + fossil_fatal("Options --checkin, --checkout, and --now may " + "not be used together."); + }else if(co){ + timeFlag = 1; + if(verboseFlag){ + fossil_print("Timestamp = current checkout version.\n"); + } + }else if(ci){ + timeFlag = -1; + if(verboseFlag){ + fossil_print("Timestamp = checkin in which each file was " + "most recently modified.\n"); + } + }else{ + timeFlag = 0; + if(verboseFlag){ + fossil_print("Timestamp = current system time.\n"); + } + } + } + + verify_all_options(); + + db_must_be_within_tree(); + vid = db_lget_int("checkout", 0); + if(vid==0){ + fossil_fatal("Cannot determine checkout version."); + } + + if(zGlobList){ + pGlob = *zGlobList ? glob_create(zGlobList) : 0; + }else if(zGlobFile){ + Blob globs = empty_blob; + blob_read_from_file(&globs, zGlobFile, ExtFILE); + pGlob = glob_create( globs.aData ); + blob_reset(&globs); + } + if( pGlob && verboseFlag!=0 ){ + int i; + for(i=0; inPattern; ++i){ + fossil_print("glob: %s\n", pGlob->azPattern[i]); + } + } + + db_begin_transaction(); + if(timeFlag==0){/*--now*/ + nowTime = time(0); + }else if(timeFlag>0){/*--checkout: get the checkout + manifest's timestamp*/ + assert(vid>0); + nowTime = db_int64(-1, + "SELECT CAST(strftime('%%s'," + "(SELECT mtime FROM event WHERE objid=%d)" + ") AS INTEGER)", vid); + if(nowTime<0){ + fossil_fatal("Could not determine checkout version's time!"); + } + }else{ /* --checkin */ + assert(0 == nowTime); + } + if((pGlob && pGlob->nPattern>0) || g.argc<3){ + /* + ** We have either (1) globs or (2) no trailing filenames. If there + ** are neither globs nor filenames then we operate on all managed + ** files. + */ + db_prepare(&q, + "SELECT vfile.mrid, pathname " + "FROM vfile LEFT JOIN blob ON vfile.mrid=blob.rid " + "WHERE vid=%d", vid); + while(SQLITE_ROW==db_step(&q)){ + int const fid = db_column_int(&q, 0); + const char * zName = db_column_text(&q, 1); + i64 newMtime = nowTime; + char const * zAbs = 0; /* absolute path */ + absBuffer.nUsed = 0; + assert(timeFlag<0 ? newMtime==0 : newMtime>0); + if(pGlob){ + if(glob_match(pGlob, zName)==0) continue; + } + blob_appendf( &absBuffer, "%s%s", g.zLocalRoot, zName ); + zAbs = blob_str(&absBuffer); + if( newMtime || mtime_of_manifest_file(vid, fid, &newMtime)==0 ){ + changeCount += + touch_cmd_stamp_one_file( zAbs, zName, newMtime, dryRunFlag, + verboseFlag, quietFlag ); + } + } + db_finalize(&q); + } + glob_free(pGlob); + pGlob = 0; + if(g.argc>2){ + /* + ** Trailing filenames on the command line. These require extra + ** care to avoid modifying unmanaged or out-of-tree files and + ** finding an associated --checkin timestamp. + */ + int i; + Blob treeNameBuf = empty_blob; /* Buffer for file_tree_name(). */ + for( i = 2; i < g.argc; ++i, + blob_reset(&treeNameBuf) ){ + char const * zArg = g.argv[i]; + char const * zTreeFile; /* repo-relative filename */ + char const * zAbs; /* absolute filename */ + i64 newMtime = nowTime; + int nameCheck; + int fid; /* vfile.mrid of file */ + nameCheck = file_tree_name( zArg, &treeNameBuf, 0, 0 ); + if(nameCheck==0){ + if(quietFlag==0){ + fossil_print("SKIPPING out-of-tree file: %s\n", zArg); + } + continue; + } + zTreeFile = blob_str(&treeNameBuf); + fid = touch_cmd_vfile_mrid( vid, zTreeFile ); + if(fid==0){ + if(quietFlag==0){ + fossil_print("SKIPPING unmanaged file: %s\n", zArg); + } + continue; + } + absBuffer.nUsed = 0; + blob_appendf(&absBuffer, "%s%s", g.zLocalRoot, zTreeFile); + zAbs = blob_str(&absBuffer); + if(timeFlag<0){/*--checkin*/ + if(mtime_of_manifest_file( vid, fid, &newMtime )!=0){ + fossil_fatal("Could not resolve --checkin mtime of %s", zTreeFile); + } + }else{ + assert(newMtime>0); + } + changeCount += + touch_cmd_stamp_one_file( zAbs, zArg, newMtime, dryRunFlag, + verboseFlag, quietFlag ); + } + } + db_end_transaction(0); + blob_reset(&absBuffer); + if( dryRunFlag!=0 ){ + fossil_print("dry-run: would have touched %d file(s)\n", + changeCount); + }else{ + fossil_print("Touched %d file(s)\n", changeCount); + } +} + +/* +** If zFileName is not NULL and contains a '.', this returns a pointer +** to the position after the final '.', else it returns NULL. As a +** special case, if it ends with a period then a pointer to the +** terminating NUL byte is returned. +*/ +const char * file_extension(const char *zFileName){ + const char * zExt = zFileName ? strrchr(zFileName, '.') : 0; + return zExt ? &zExt[1] : 0; +} + +/* +** Returns non-zero if the specified file name ends with any reserved name, +** e.g.: _FOSSIL_ or .fslckout. Specifically, it returns 1 for exact match +** or 2 for a tail match on a longer file name. +** +** For the sake of efficiency, zFilename must be a canonical name, e.g. an +** absolute path using only forward slash ('/') as a directory separator. +** +** nFilename must be the length of zFilename. When negative, strlen() will +** be used to calculate it. +*/ +int file_is_reserved_name(const char *zFilename, int nFilename){ + const char *zEnd; /* one-after-the-end of zFilename */ + int gotSuffix = 0; /* length of suffix (-wal, -shm, -journal) */ + + assert( zFilename && "API misuse" ); + if( nFilename<0 ) nFilename = (int)strlen(zFilename); + if( nFilename<8 ) return 0; /* 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( fossil_strnicmp("wal", &zEnd[-3], 3) + && fossil_strnicmp("shm", &zEnd[-3], 3) ){ + return 0; + } + gotSuffix = 4; + }else if( nFilename>=16 && zEnd[-8]=='-' ){ /*strlen(_FOSSIL_-journal) */ + if( fossil_strnicmp("journal", &zEnd[-7], 7) ) return 0; + 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( fossil_strnicmp("_FOSSIL_", &zEnd[-8], 8) ) return 0; + if( 8==nFilename ) return 1; + return zEnd[-9]=='/' ? 2 : gotSuffix; + } + case 'T': + case 't':{ + if( nFilename<9 || zEnd[-9]!='.' + || fossil_strnicmp(".fslckout", &zEnd[-9], 9) ){ + return 0; + } + if( 9==nFilename ) return 1; + return zEnd[-10]=='/' ? 2 : gotSuffix; + } + default:{ + return 0; + } + } +} + +/* +** COMMAND: test-is-reserved-name +** +** Usage: %fossil test-is-reserved-name FILENAMES... +** +** Passes each given name to file_is_reserved_name() and outputs one +** line per file: the result value of that function followed by the +** name. +*/ +void test_is_reserved_name_cmd(void){ + int i; + + if(g.argc<3){ + usage("FILENAME_1 [...FILENAME_N]"); + } + for( i = 2; i < g.argc; ++i ){ + const int check = file_is_reserved_name(g.argv[i], -1); + fossil_print("%d %s\n", check, g.argv[i]); + } +} + + +/* +** Returns 1 if the given directory contains a file named .fslckout, 2 +** if it contains a file named _FOSSIL_, else returns 0. +*/ +int dir_has_ckout_db(const char *zDir){ + int rc = 0; + char * zCkoutDb = mprintf("%//.fslckout", zDir); + if(file_isfile(zCkoutDb, ExtFILE)){ + rc = 1; + }else{ + fossil_free(zCkoutDb); + zCkoutDb = mprintf("%//_FOSSIL_", zDir); + if(file_isfile(zCkoutDb, ExtFILE)){ + rc = 2; + } + } + fossil_free(zCkoutDb); + return rc; } ADDED src/fileedit.c Index: src/fileedit.c ================================================================== --- /dev/null +++ src/fileedit.c @@ -0,0 +1,2067 @@ +/* +** Copyright (c) 2020 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 code for the /fileedit page and related bits. +*/ +#include "config.h" +#include "fileedit.h" +#include +#include + +/* +** State for the "mini-checkin" infrastructure, which enables the +** ability to commit changes to a single file without a checkout +** db, e.g. for use via an HTTP request. +** +** Use CheckinMiniInfo_init() to cleanly initialize one to a known +** valid/empty default state. +** +** Memory for all non-const pointer members is owned by the +** CheckinMiniInfo instance, unless explicitly noted otherwise, and is +** freed by CheckinMiniInfo_cleanup(). Similarly, each instance owns +** any memory for its own Blob members, but NOT for its pointers to +** blobs. +*/ +struct CheckinMiniInfo { + Manifest * pParent; /* parent checkin. Memory is owned by this + object. */ + char *zParentUuid; /* Full UUID of pParent */ + char *zFilename; /* Name of single file to commit. Must be + relative to the top of the repo. */ + Blob fileContent; /* Content of file referred to by zFilename. */ + Blob fileHash; /* Hash of this->fileContent, using the repo's + preferred hash method. */ + Blob comment; /* Check-in comment text */ + char *zCommentMimetype; /* Mimetype of comment. May be NULL */ + char *zUser; /* User name */ + char *zDate; /* Optionally force this date string (anything + supported by date_in_standard_format()). + Maybe be NULL. */ + Blob *pMfOut; /* If not NULL, checkin_mini() will write a + copy of the generated manifest here. This + memory is NOT owned by CheckinMiniInfo. */ + int filePerm; /* Permissions (via file_perm()) of the input + file. We need to store this before calling + checkin_mini() because the real input file + name may differ from the repo-centric + this->zFilename, and checkin_mini() requires + the permissions of the original file. For + web commits, set this to PERM_REG or (when + editing executable scripts) PERM_EXE before + calling checkin_mini(). */ + int flags; /* Bitmask of fossil_cimini_flags. */ +}; +typedef struct CheckinMiniInfo CheckinMiniInfo; + +/* +** CheckinMiniInfo::flags values. +*/ +enum fossil_cimini_flags { +/* +** Must have a value of 0. All other flags have unspecified values. +*/ +CIMINI_NONE = 0, +/* +** Tells checkin_mini() to use dry-run mode. +*/ +CIMINI_DRY_RUN = 1, +/* +** Tells checkin_mini() to allow forking from a non-leaf commit. +*/ +CIMINI_ALLOW_FORK = 1<<1, +/* +** Tells checkin_mini() to dump its generated manifest to stdout. +*/ +CIMINI_DUMP_MANIFEST = 1<<2, + +/* +** By default, content containing what appears to be a merge conflict +** marker is not permitted. This flag relaxes that requirement. +*/ +CIMINI_ALLOW_MERGE_MARKER = 1<<3, + +/* +** By default mini-checkins are not allowed to be "older" +** than their parent. i.e. they may not have a timestamp +** which predates their parent. This flag bypasses that +** check. +*/ +CIMINI_ALLOW_OLDER = 1<<4, + +/* +** Indicates that the content of the newly-checked-in file is +** converted, if needed, to use the same EOL style as the previous +** version of that file. Only the in-memory/in-repo copies are +** affected, not the original file (if any). +*/ +CIMINI_CONVERT_EOL_INHERIT = 1<<5, +/* +** Indicates that the input's EOLs should be converted to Unix-style. +*/ +CIMINI_CONVERT_EOL_UNIX = 1<<6, +/* +** Indicates that the input's EOLs should be converted to Windows-style. +*/ +CIMINI_CONVERT_EOL_WINDOWS = 1<<7, +/* +** A hint to checkin_mini() to "prefer" creation of a delta manifest. +** It may decide not to for various reasons. +*/ +CIMINI_PREFER_DELTA = 1<<8, +/* +** A "stronger hint" to checkin_mini() to prefer creation of a delta +** manifest if it at all can. It will decide not to only if creation +** of a delta is not a realistic option or if it's forbitted by the +** forbid-delta-manifests repo config option. For this to work, it +** must be set together with the CIMINI_PREFER_DELTA flag, but the two +** cannot be combined in this enum. +** +** This option is ONLY INTENDED FOR TESTING, used in bypassing +** heuristics which may otherwise disable generation of a delta on the +** grounds of efficiency (e.g. not generating a delta if the parent +** non-delta only has a few F-cards). +*/ +CIMINI_STRONGLY_PREFER_DELTA = 1<<9, +/* +** Tells checkin_mini() to permit the addition of a new file. Normally +** this is disabled because there are hypothetically many cases where +** it could cause the inadvertent addition of a new file when an +** update to an existing was intended, as a side-effect of name-case +** differences. +*/ +CIMINI_ALLOW_NEW_FILE = 1<<10 +}; + +/* +** Initializes p to a known-valid default state. +*/ +static void CheckinMiniInfo_init( CheckinMiniInfo * p ){ + memset(p, 0, sizeof(CheckinMiniInfo)); + p->flags = CIMINI_NONE; + p->filePerm = -1; + p->comment = p->fileContent = p->fileHash = empty_blob; +} + +/* +** Frees all memory owned by p, but does not free p. + */ +static void CheckinMiniInfo_cleanup( CheckinMiniInfo * p ){ + blob_reset(&p->comment); + blob_reset(&p->fileContent); + blob_reset(&p->fileHash); + if(p->pParent){ + manifest_destroy(p->pParent); + } + fossil_free(p->zFilename); + fossil_free(p->zDate); + fossil_free(p->zParentUuid); + fossil_free(p->zCommentMimetype); + fossil_free(p->zUser); + CheckinMiniInfo_init(p); +} + +/* +** Internal helper which returns an F-card perms string suitable for +** writing as-is into a manifest. If it's not empty, it includes a +** leading space to separate it from the F-card's hash field. +*/ +static const char * mfile_permint_mstring(int perm){ + switch(perm){ + case PERM_EXE: return " x"; + case PERM_LNK: return " l"; + default: return ""; + } +} + +/* +** Given a ManifestFile permission string (or NULL), it returns one of +** PERM_REG, PERM_EXE, or PERM_LNK. +*/ +static int mfile_permstr_int(const char *zPerm){ + if(!zPerm || !*zPerm) return PERM_REG; + else if(strstr(zPerm,"x")) return PERM_EXE; + else if(strstr(zPerm,"l")) return PERM_LNK; + else return PERM_REG/*???*/; +} + +/* +** Internal helper for checkin_mini() and friends. Appends an F-card +** for p to pOut. +*/ +static void checkin_mini_append_fcard(Blob *pOut, + const ManifestFile *p){ + if(p->zUuid){ + assert(*p->zUuid); + blob_appendf(pOut, "F %F %s%s", p->zName, + p->zUuid, + mfile_permint_mstring(manifest_file_mperm(p))); + if(p->zPrior){ + assert(*p->zPrior); + blob_appendf(pOut, " %F\n", p->zPrior); + }else{ + blob_append(pOut, "\n", 1); + } + }else{ + /* File was removed from parent delta. */ + blob_appendf(pOut, "F %F\n", p->zName); + } +} + +/* +** Handles the F-card parts for create_manifest_mini(). +** +** If asDelta is true, F-cards will be handled as for a delta +** manifest, and the caller MUST have added a B-card to pOut before +** calling this. +** +** Returns 1 on success, 0 on error, and writes any error message to +** pErr (if it's not NULL). The only non-immediately-fatal/panic error +** is if pCI->filePerm is PERM_LNK or pCI would update a PERM_LNK +** in-repo file. +*/ +static int create_manifest_mini_fcards( Blob * pOut, + CheckinMiniInfo * pCI, + int asDelta, + Blob * pErr){ + int wroteThisCard = 0; + const ManifestFile * pFile; + int (*fncmp)(char const *, char const *) = /* filename comparator */ + filenames_are_case_sensitive() + ? fossil_strcmp + : fossil_stricmp; +#define mf_err(EXPR) if(pErr) blob_appendf EXPR; return 0 +#define write_this_card(NAME) \ + blob_appendf(pOut, "F %F %b%s\n", (NAME), &pCI->fileHash, \ + mfile_permint_mstring(pCI->filePerm)); \ + wroteThisCard = 1 + + assert(pCI->filePerm!=PERM_LNK && "This should have been validated before."); + assert(pCI->filePerm==PERM_REG || pCI->filePerm==PERM_EXE); + if(PERM_LNK==pCI->filePerm){ + goto err_no_symlink; + } + manifest_file_rewind(pCI->pParent); + if(asDelta!=0 && (pCI->pParent->zBaseline==0 + || pCI->pParent->nFile==0)){ + /* Parent is a baseline or a delta with no F-cards, so this is + ** the simplest case: create a delta with a single F-card. + */ + pFile = manifest_file_find(pCI->pParent, pCI->zFilename); + if(pFile!=0 && manifest_file_mperm(pFile)==PERM_LNK){ + goto err_no_symlink; + } + write_this_card(pFile ? pFile->zName : pCI->zFilename); + return 1; + } + while(1){ + int cmp; + if(asDelta==0){ + pFile = manifest_file_next(pCI->pParent, 0); + }else{ + /* Parent is a delta manifest with F-cards. Traversal of delta + ** manifest file entries is normally done via + ** manifest_file_next(), which takes into account the + ** differences between the delta and its parent and returns + ** F-cards from both. Each successive delta from the same + ** baseline includes all F-card changes from the previous + ** deltas, so we instead clone the parent's F-cards except for + ** the one (if any) which matches the new file. + */ + pFile = pCI->pParent->iFile < pCI->pParent->nFile + ? &pCI->pParent->aFile[pCI->pParent->iFile++] + : 0; + } + if(0==pFile) break; + cmp = fncmp(pFile->zName, pCI->zFilename); + if(cmp<0){ + checkin_mini_append_fcard(pOut,pFile); + }else{ + if(cmp==0 || 0==wroteThisCard){ + assert(0==wroteThisCard); + if(PERM_LNK==manifest_file_mperm(pFile)){ + goto err_no_symlink; + } + write_this_card(cmp==0 ? pFile->zName : pCI->zFilename); + } + if(cmp>0){ + assert(wroteThisCard!=0); + checkin_mini_append_fcard(pOut,pFile); + } + } + } + if(wroteThisCard==0){ + write_this_card(pCI->zFilename); + } + return 1; +err_no_symlink: + mf_err((pErr,"Cannot commit or overwrite symlinks " + "via mini-checkin.")); + return 0; +#undef write_this_card +#undef mf_err +} + +/* +** Creates a manifest file, written to pOut, from the state in the +** fully-populated and semantically valid pCI argument. pCI is not +** *semantically* modified by this routine but cannot be const because +** blob_str() may need to NUL-terminate any given blob. +** +** Returns true on success. On error, returns 0 and, if pErr is not +** NULL, writes an error message there. +** +** Intended only to be called via checkin_mini() or routines which +** have already completely vetted pCI for semantic validity. +*/ +static int create_manifest_mini( Blob * pOut, CheckinMiniInfo * pCI, + Blob * pErr){ + Blob zCard = empty_blob; /* Z-card checksum */ + int asDelta = 0; +#define mf_err(EXPR) if(pErr) blob_appendf EXPR; return 0 + + assert(blob_str(&pCI->fileHash)); + assert(pCI->pParent); + assert(pCI->zFilename); + assert(pCI->zUser); + assert(pCI->zDate); + + /* Potential TODOs include... + ** + ** - Maybe add support for tags. Those can be edited via /info page, + ** and feel like YAGNI/feature creep for this purpose. + */ + blob_zero(pOut); + manifest_file_rewind(pCI->pParent) /* force load of baseline */; + /* Determine whether we want to create a delta manifest... */ + if((CIMINI_PREFER_DELTA & pCI->flags) + && ((CIMINI_STRONGLY_PREFER_DELTA & pCI->flags) + || (pCI->pParent->pBaseline + ? pCI->pParent->pBaseline + : pCI->pParent)->nFile > 15 + /* 15 is arbitrary: don't create a delta when there is only a + ** tiny gain for doing so. That heuristic is not *quite* + ** right, in that when we're deriving from another delta, we + ** really should compare the F-card count between it and its + ** baseline, and create a delta if the baseline has (say) + ** twice or more as many F-cards as the previous delta. */) + && !db_get_boolean("forbid-delta-manifests",0) + ){ + asDelta = 1; + blob_appendf(pOut, "B %s\n", + pCI->pParent->zBaseline + ? pCI->pParent->zBaseline + : pCI->zParentUuid); + } + if(blob_size(&pCI->comment)!=0){ + blob_appendf(pOut, "C %F\n", blob_str(&pCI->comment)); + }else{ + blob_append(pOut, "C (no\\scomment)\n", 16); + } + blob_appendf(pOut, "D %s\n", pCI->zDate); + if(create_manifest_mini_fcards(pOut,pCI,asDelta,pErr)==0){ + return 0; + } + if(pCI->zCommentMimetype!=0 && pCI->zCommentMimetype[0]!=0){ + blob_appendf(pOut, "N %F\n", pCI->zCommentMimetype); + } + blob_appendf(pOut, "P %s\n", pCI->zParentUuid); + blob_appendf(pOut, "U %F\n", pCI->zUser); + md5sum_blob(pOut, &zCard); + blob_appendf(pOut, "Z %b\n", &zCard); + blob_reset(&zCard); + return 1; +#undef mf_err +} + +/* +** A so-called "single-file/mini/web checkin" is a slimmed-down form +** of the checkin command which accepts only a single file and is +** intended to accept edits to a file via the web interface or from +** the CLI from outside of a checkout. +** +** Being fully non-interactive is a requirement for this function, +** thus it cannot perform autosync or similar activities (which +** includes checking for repo locks). +** +** This routine uses the state from the given fully-populated pCI +** argument to add pCI->fileContent to the database, and create and +** save a manifest for that change. Ownership of pCI and its contents +** are unchanged. +** +** This function may may modify pCI as follows: +** +** - If one of Manifest pCI->pParent or pCI->zParentUuid are NULL, +** then the other will be assigned based on its counterpart. Both +** may not be NULL. +** +** - pCI->zDate is normalized to/replaced with a valid date/time +** string. If its original value cannot be validated then +** this function fails. If pCI->zDate is NULL, the current time +** is used. +** +** - If the CIMINI_CONVERT_EOL_INHERIT flag is set, +** pCI->fileContent appears to be plain text, and its line-ending +** style differs from its previous version, it is converted to the +** same EOL style as the previous version. If this is done, the +** pCI->fileHash is re-computed. Note that only pCI->fileContent, +** not the original file, is affected by the conversion. +** +** - Else if one of the CIMINI_CONVERT_EOL_WINDOWS or +** CIMINI_CONVERT_EOL_UNIX flags are set, pCI->fileContent is +** converted, if needed, to the corresponding EOL style. +** +** - If EOL conversion takes place, pCI->fileHash is re-calculated. +** +** - If pCI->fileHash is empty, this routine populates it with the +** repository's preferred hash algorithm (after any EOL conversion). +** +** - pCI->comment may be converted to Unix-style newlines. +** +** pCI's ownership is not modified. +** +** This function validates pCI's state and fails if any validation +** fails. +** +** On error, returns false (0) and, if pErr is not NULL, writes a +** diagnostic message there. +** +** Returns true on success. If pRid is not NULL, the RID of the +** resulting manifest is written to *pRid. +** +** The checkin process is largely influenced by pCI->flags, and that +** must be populated before calling this. See the fossil_cimini_flags +** enum for the docs for each flag. +*/ +static int checkin_mini(CheckinMiniInfo * pCI, int *pRid, Blob * pErr){ + Blob mf = empty_blob; /* output manifest */ + int rid = 0, frid = 0; /* various RIDs */ + int isPrivate; /* whether this is private content + or not */ + ManifestFile * zFilePrev; /* file entry from pCI->pParent */ + int prevFRid = 0; /* RID of file's prev. version */ +#define ci_err(EXPR) if(pErr!=0){blob_appendf EXPR;} goto ci_error + + db_begin_transaction(); + if(pCI->pParent==0 && pCI->zParentUuid==0){ + ci_err((pErr, "Cannot determine parent version.")); + } + else if(pCI->pParent==0){ + pCI->pParent = manifest_get_by_name(pCI->zParentUuid, 0); + if(pCI->pParent==0){ + ci_err((pErr,"Cannot load manifest for [%S].", pCI->zParentUuid)); + } + }else if(pCI->zParentUuid==0){ + pCI->zParentUuid = rid_to_uuid(pCI->pParent->rid); + assert(pCI->zParentUuid); + } + assert(pCI->pParent->rid>0); + if(leaf_is_closed(pCI->pParent->rid)){ + ci_err((pErr,"Cannot commit to a closed leaf.")); + /* Remember that in order to override this we'd also need to + ** cancel TAG_CLOSED on pCI->pParent. There would seem to be no + ** reason we can't do that via the generated manifest, but the + ** commit command does not offer that option, so mini-checkin + ** probably shouldn't, either. + */ + } + if( !db_exists("SELECT 1 FROM user WHERE login=%Q", pCI->zUser) ){ + ci_err((pErr,"No such user: %s", pCI->zUser)); + } + if(!(CIMINI_ALLOW_FORK & pCI->flags) + && !is_a_leaf(pCI->pParent->rid)){ + ci_err((pErr,"Parent [%S] is not a leaf and forking is disabled.", + pCI->zParentUuid)); + } + if(!(CIMINI_ALLOW_MERGE_MARKER & pCI->flags) + && contains_merge_marker(&pCI->fileContent)){ + ci_err((pErr,"Content appears to contain a merge conflict marker.")); + } + if(!file_is_simple_pathname(pCI->zFilename, 1)){ + ci_err((pErr,"Invalid filename for use in a repository: %s", + pCI->zFilename)); + } + if(!(CIMINI_ALLOW_OLDER & pCI->flags) + && !checkin_is_younger(pCI->pParent->rid, pCI->zDate)){ + ci_err((pErr,"Checkin time (%s) may not be older " + "than its parent (%z).", + pCI->zDate, + db_text(0, "SELECT strftime('%%Y-%%m-%%dT%%H:%%M:%%f',%lf)", + pCI->pParent->rDate) + )); + } + { + /* + ** Normalize the timestamp. We don't use date_in_standard_format() + ** because that has side-effects we don't want to trigger here. + */ + char * zDVal = db_text( + 0, "SELECT strftime('%%Y-%%m-%%dT%%H:%%M:%%f',%Q)", + pCI->zDate ? pCI->zDate : "now"); + if(zDVal==0 || zDVal[0]==0){ + fossil_free(zDVal); + ci_err((pErr,"Invalid timestamp string: %s", pCI->zDate)); + } + fossil_free(pCI->zDate); + pCI->zDate = zDVal; + } + { /* Confirm that only one EOL policy is in place. */ + int n = 0; + if(CIMINI_CONVERT_EOL_INHERIT & pCI->flags) ++n; + if(CIMINI_CONVERT_EOL_UNIX & pCI->flags) ++n; + if(CIMINI_CONVERT_EOL_WINDOWS & pCI->flags) ++n; + if(n>1){ + ci_err((pErr,"More than 1 EOL conversion policy was specified.")); + } + } + /* Potential TODOs include: + ** + ** - Commit allows an empty checkin only with a flag, but we + ** currently disallow an empty checkin entirely. Conform with + ** commit? + ** + ** Non-TODOs: + ** + ** - Check for a commit lock would require auto-sync, which this + ** code cannot do if it's going to be run via a web page. + */ + + /* + ** Confirm that pCI->zFilename can be found in pCI->pParent. If + ** not, fail unless the CIMINI_ALLOW_NEW_FILE flag is set. This is + ** admittedly an artificial limitation, not strictly necessary. We + ** do it to hopefully reduce the chance of an "oops" where file + ** X/Y/z gets committed as X/Y/Z or X/y/z due to a typo or + ** case-sensitivity mismatch between the user/repo/filesystem, or + ** some such. + */ + manifest_file_rewind(pCI->pParent); + zFilePrev = manifest_file_find(pCI->pParent, pCI->zFilename); + if(!(CIMINI_ALLOW_NEW_FILE & pCI->flags) + && (!zFilePrev + || !zFilePrev->zUuid/*was removed from parent delta manifest*/) + ){ + ci_err((pErr,"File [%s] not found in manifest [%S]. " + "Adding new files is currently not permitted.", + pCI->zFilename, pCI->zParentUuid)); + }else if(zFilePrev + && manifest_file_mperm(zFilePrev)==PERM_LNK){ + ci_err((pErr,"Cannot save a symlink via a mini-checkin.")); + } + if(zFilePrev){ + prevFRid = fast_uuid_to_rid(zFilePrev->zUuid); + } + + if(((CIMINI_CONVERT_EOL_INHERIT & pCI->flags) + || (CIMINI_CONVERT_EOL_UNIX & pCI->flags) + || (CIMINI_CONVERT_EOL_WINDOWS & pCI->flags)) + && blob_size(&pCI->fileContent)>0 + ){ + /* Convert to the requested EOL style. Note that this inherently + ** runs a risk of breaking content, e.g. string literals which + ** contain embedded newlines. Note that HTML5 specifies that + ** form-submitted TEXTAREA content gets normalized to CRLF-style: + ** + ** https://html.spec.whatwg.org/multipage/form-elements.html#the-textarea-element + */ + const int pseudoBinary = LOOK_LONG | LOOK_NUL; + const int lookFlags = LOOK_CRLF | LOOK_LONE_LF | pseudoBinary; + const int lookNew = looks_like_utf8( &pCI->fileContent, lookFlags ); + if(!(pseudoBinary & lookNew)){ + int rehash = 0; + /*fossil_print("lookNew=%08x\n",lookNew);*/ + if(CIMINI_CONVERT_EOL_INHERIT & pCI->flags){ + Blob contentPrev = empty_blob; + int lookOrig, nOrig; + content_get(prevFRid, &contentPrev); + lookOrig = looks_like_utf8(&contentPrev, lookFlags); + nOrig = blob_size(&contentPrev); + blob_reset(&contentPrev); + /*fossil_print("lookOrig=%08x\n",lookOrig);*/ + if(nOrig>0 && lookOrig!=lookNew){ + /* If there is a newline-style mismatch, adjust the new + ** content version to the previous style, then re-hash the + ** content. Note that this means that what we insert is NOT + ** what's in the filesystem. + */ + if(!(lookOrig & LOOK_CRLF) && (lookNew & LOOK_CRLF)){ + /* Old has Unix-style, new has Windows-style. */ + blob_to_lf_only(&pCI->fileContent); + rehash = 1; + }else if((lookOrig & LOOK_CRLF) && !(lookNew & LOOK_CRLF)){ + /* Old has Windows-style, new has Unix-style. */ + blob_add_cr(&pCI->fileContent); + rehash = 1; + } + } + }else{ + const int oldSize = blob_size(&pCI->fileContent); + if(CIMINI_CONVERT_EOL_UNIX & pCI->flags){ + if(LOOK_CRLF & lookNew){ + blob_to_lf_only(&pCI->fileContent); + } + }else{ + assert(CIMINI_CONVERT_EOL_WINDOWS & pCI->flags); + if(!(LOOK_CRLF & lookNew)){ + blob_add_cr(&pCI->fileContent); + } + } + if(blob_size(&pCI->fileContent)!=oldSize){ + rehash = 1; + } + } + if(rehash!=0){ + hname_hash(&pCI->fileContent, 0, &pCI->fileHash); + } + } + }/* end EOL conversion */ + + if(blob_size(&pCI->fileHash)==0){ + /* Hash the content if it's not done already... */ + hname_hash(&pCI->fileContent, 0, &pCI->fileHash); + assert(blob_size(&pCI->fileHash)>0); + } + if(zFilePrev){ + /* Has this file been changed since its previous commit? Note + ** that we have to delay this check until after the potentially + ** expensive EOL conversion. */ + assert(blob_size(&pCI->fileHash)); + if(0==fossil_strcmp(zFilePrev->zUuid, blob_str(&pCI->fileHash)) + && manifest_file_mperm(zFilePrev)==pCI->filePerm){ + ci_err((pErr,"File is unchanged. Not committing.")); + } + } +#if 1 + /* Do we really want to normalize comment EOLs? Web-posting will + ** submit them in CRLF or LF format, depending on how exactly the + ** content is submitted (FORM (CRLF) or textarea-to-POST (LF, at + ** least in theory)). */ + blob_to_lf_only(&pCI->comment); +#endif + /* Create, save, deltify, and crosslink the manifest... */ + if(create_manifest_mini(&mf, pCI, pErr)==0){ + return 0; + } + isPrivate = content_is_private(pCI->pParent->rid); + rid = content_put_ex(&mf, 0, 0, 0, isPrivate); + if(pCI->flags & CIMINI_DUMP_MANIFEST){ + fossil_print("%b", &mf); + } + if(pCI->pMfOut!=0){ + /* Cross-linking clears mf, so we have to copy it, + ** instead of taking over its memory. */ + blob_reset(pCI->pMfOut); + blob_append(pCI->pMfOut, blob_buffer(&mf), blob_size(&mf)); + } + content_deltify(rid, &pCI->pParent->rid, 1, 0); + manifest_crosslink(rid, &mf, 0); + blob_reset(&mf); + /* Save and deltify the file content... */ + frid = content_put_ex(&pCI->fileContent, blob_str(&pCI->fileHash), + 0, 0, isPrivate); + if(zFilePrev!=0){ + assert(prevFRid>0); + content_deltify(frid, &prevFRid, 1, 0); + } + db_end_transaction((CIMINI_DRY_RUN & pCI->flags) ? 1 : 0); + if(pRid!=0){ + *pRid = rid; + } + return 1; +ci_error: + assert(db_transaction_nesting_depth()>0); + db_end_transaction(1); + return 0; +#undef ci_err +} + +/* +** COMMAND: test-ci-mini +** +** This is an on-going experiment, subject to change or removal at +** any time. +** +** Usage: %fossil test-ci-mini ?OPTIONS? FILENAME +** +** where FILENAME is a repo-relative name as it would appear in the +** vfile table. +** +** Options: +** +** -R|--repository REPO The repository file to commit to. +** --as FILENAME The repository-side name of the input +** file, relative to the top of the +** repository. Default is the same as the +** input file name. +** -m|--comment COMMENT Required checkin comment. +** -M|--comment-file FILE Reads checkin comment from the given file. +** -r|--revision VERSION Commit from this version. Default is +** the checkout version (if available) or +** trunk (if used without a checkout). +** --allow-fork Allows the commit to be made against a +** non-leaf parent. Note that no autosync +** is performed beforehand. +** --allow-merge-conflict Allows checkin of a file even if it +** appears to contain a fossil merge conflict +** marker. +** --user-override USER USER to use instead of the current +** default. +** --date-override DATETIME DATE to use instead of 'now'. +** --allow-older Allow a commit to be older than its +** ancestor. +** --convert-eol-inherit Convert EOL style of the checkin to match +** the previous version's content. +** --convert-eol-unix Convert the EOL style to Unix. +** --convert-eol-windows Convert the EOL style to Windows. +** (only one of the --convert-eol-X options may be used and they only +** modified the saved blob, not the input file.) +** --delta Prefer to generate a delta manifest, if +** able. The forbid-delta-manifests repo +** config option trumps this, as do certain +** heuristics. +** --allow-new-file Allow addition of a new file this way. +** Disabled by default to avoid that case- +** sensitivity errors inadvertently lead to +** adding a new file where an update is +** intended. +** -d|--dump-manifest Dumps the generated manifest to stdout +** immediately after it's generated. +** --save-manifest FILE Saves the generated manifest to a file +** after successfully processing it. +** --wet-run Disables the default dry-run mode. +** +** Example: +** +** %fossil test-ci-mini -R REPO -m ... -r foo --as src/myfile.c myfile.c +** +*/ +void test_ci_mini_cmd(void){ + CheckinMiniInfo cimi; /* checkin state */ + int newRid = 0; /* RID of new version */ + const char * zFilename; /* argv[2] */ + const char * zComment; /* -m comment */ + const char * zCommentFile; /* -M FILE */ + const char * zAsFilename; /* --as filename */ + const char * zRevision; /* -r|--revision [=trunk|checkout] */ + const char * zUser; /* --user-override */ + const char * zDate; /* --date-override */ + char const * zManifestFile = 0;/* --save-manifest FILE */ + + /* This function should perform only the minimal "business logic" it + ** needs in order to fully/properly populate the CheckinMiniInfo and + ** then pass it on to checkin_mini() to do most of the validation + ** and work. The point of this is to avoid duplicate code when a web + ** front-end is added for checkin_mini(). + */ + CheckinMiniInfo_init(&cimi); + zComment = find_option("comment","m",1); + zCommentFile = find_option("comment-file","M",1); + zAsFilename = find_option("as",0,1); + zRevision = find_option("revision","r",1); + zUser = find_option("user-override",0,1); + zDate = find_option("date-override",0,1); + zManifestFile = find_option("save-manifest",0,1); + if(find_option("wet-run",0,0)==0){ + cimi.flags |= CIMINI_DRY_RUN; + } + if(find_option("allow-fork",0,0)!=0){ + cimi.flags |= CIMINI_ALLOW_FORK; + } + if(find_option("dump-manifest","d",0)!=0){ + cimi.flags |= CIMINI_DUMP_MANIFEST; + } + if(find_option("allow-merge-conflict",0,0)!=0){ + cimi.flags |= CIMINI_ALLOW_MERGE_MARKER; + } + if(find_option("allow-older",0,0)!=0){ + cimi.flags |= CIMINI_ALLOW_OLDER; + } + if(find_option("convert-eol-inherit",0,0)!=0){ + cimi.flags |= CIMINI_CONVERT_EOL_INHERIT; + }else if(find_option("convert-eol-unix",0,0)!=0){ + cimi.flags |= CIMINI_CONVERT_EOL_UNIX; + }else if(find_option("convert-eol-windows",0,0)!=0){ + cimi.flags |= CIMINI_CONVERT_EOL_WINDOWS; + } + if(find_option("delta",0,0)!=0){ + cimi.flags |= CIMINI_PREFER_DELTA; + } + if(find_option("delta2",0,0)!=0){ + /* Undocumented. For testing only. */ + cimi.flags |= CIMINI_PREFER_DELTA | CIMINI_STRONGLY_PREFER_DELTA; + } + if(find_option("allow-new-file",0,0)!=0){ + cimi.flags |= CIMINI_ALLOW_NEW_FILE; + } + db_find_and_open_repository(0, 0); + verify_all_options(); + user_select(); + if(g.argc!=3){ + usage("INFILE"); + } + if(zComment && zCommentFile){ + fossil_fatal("Only one of -m or -M, not both, may be used."); + }else{ + if(zCommentFile && *zCommentFile){ + blob_read_from_file(&cimi.comment, zCommentFile, ExtFILE); + }else if(zComment && *zComment){ + blob_append(&cimi.comment, zComment, -1); + } + if(!blob_size(&cimi.comment)){ + fossil_fatal("Non-empty checkin comment is required."); + } + } + db_begin_transaction(); + zFilename = g.argv[2]; + cimi.zFilename = mprintf("%/", zAsFilename ? zAsFilename : zFilename); + cimi.filePerm = file_perm(zFilename, ExtFILE); + cimi.zUser = mprintf("%s", zUser ? zUser : login_name()); + if(zDate){ + cimi.zDate = mprintf("%s", zDate); + } + if(zRevision==0 || zRevision[0]==0){ + if(g.localOpen/*checkout*/){ + zRevision = db_lget("checkout-hash", 0)/*leak*/; + }else{ + zRevision = "trunk"; + } + } + name_to_uuid2(zRevision, "ci", &cimi.zParentUuid); + if(cimi.zParentUuid==0){ + fossil_fatal("Cannot determine version to commit to."); + } + blob_read_from_file(&cimi.fileContent, zFilename, ExtFILE); + { + Blob theManifest = empty_blob; /* --save-manifest target */ + Blob errMsg = empty_blob; + int rc; + if(zManifestFile){ + cimi.pMfOut = &theManifest; + } + rc = checkin_mini(&cimi, &newRid, &errMsg); + if(rc){ + assert(blob_size(&errMsg)==0); + }else{ + assert(blob_size(&errMsg)); + fossil_fatal("%b", &errMsg); + } + if(zManifestFile){ + fossil_print("Writing manifest to: %s\n", zManifestFile); + assert(blob_size(&theManifest)>0); + blob_write_to_file(&theManifest, zManifestFile); + blob_reset(&theManifest); + } + } + if(newRid!=0){ + fossil_print("New version%s: %z\n", + (cimi.flags & CIMINI_DRY_RUN) ? " (dry run)" : "", + rid_to_uuid(newRid)); + } + db_end_transaction(0/*checkin_mini() will have triggered it to roll + ** back in dry-run mode, but we need access to + ** the transaction-written db state in this + ** routine.*/); + if(!(cimi.flags & CIMINI_DRY_RUN) && newRid!=0 && g.localOpen!=0){ + fossil_warning("The checkout state is now out of sync " + "with regards to this commit. It needs to be " + "'update'd or 'close'd and re-'open'ed."); + } + CheckinMiniInfo_cleanup(&cimi); +} + +/* +** If the fileedit-glob setting has a value, this returns its Glob +** object (in memory owned by this function), else it returns NULL. +*/ +Glob *fileedit_glob(void){ + static Glob * pGlobs = 0; + static int once = 0; + if(0==pGlobs && once==0){ + char * zGlobs = db_get("fileedit-glob",0); + once = 1; + if(0!=zGlobs && 0!=*zGlobs){ + pGlobs = glob_create(zGlobs); + } + fossil_free(zGlobs); + } + return pGlobs; +} + +/* +** Returns true if the given filename qualifies for online editing by +** the current user, else returns false. +** +** Editing requires that the user have the Write permission and that +** the filename match the glob defined by the fileedit-glob setting. +** A missing or empty value for that glob disables all editing. +*/ +int fileedit_is_editable(const char *zFilename){ + Glob * pGlobs = fileedit_glob(); + if(pGlobs!=0 && zFilename!=0 && *zFilename!=0 && 0!=g.perm.Write){ + return glob_match(pGlobs, zFilename); + }else{ + return 0; + } +} + +/* +** Given a repo-relative filename and a manifest RID, returns the UUID +** of the corresponding file entry. Returns NULL if no match is +** found. If pFilePerm is not NULL, the file's permission flag value +** is written to *pFilePerm. +*/ +static char *fileedit_file_uuid(char const *zFilename, + int vid, int *pFilePerm){ + Stmt stmt = empty_Stmt; + char * zFileUuid = 0; + db_prepare(&stmt, "SELECT uuid, perm FROM files_of_checkin " + "WHERE filename=%Q %s AND checkinID=%d", + zFilename, filename_collation(), vid); + if(SQLITE_ROW==db_step(&stmt)){ + zFileUuid = mprintf("%s",db_column_text(&stmt, 0)); + if(pFilePerm){ + *pFilePerm = mfile_permstr_int(db_column_text(&stmt, 1)); + } + } + db_finalize(&stmt); + return zFileUuid; +} + +/* +** Returns true if the current user is allowed to edit the given +** filename, as determined by fileedit_is_editable(), else false, +** in which case it queues up an error response and the caller +** must return immediately. +*/ +static int fileedit_ajax_check_filename(const char * zFilename){ + if(0==fileedit_is_editable(zFilename)){ + ajax_route_error(403, "File is disallowed by the " + "fileedit-glob setting."); + return 0; + } + return 1; +} + +/* +** Passed the values of the "checkin" and "filename" request +** properties, this function verifies that they are valid and +** populates: +** +** - *zRevUuid = the fully-expanded value of zRev (owned by the +** caller). zRevUuid may be NULL. +** +** - *pVid = the RID of zRevUuid. pVid May be NULL. If the vid +** cannot be resolved or is ambiguous, pVid is not assigned. +** +** - *frid = the RID of zFilename's blob content. May not be NULL +** unless zFilename is also NULL. If BOTH of zFilename and frid are +** NULL then no confirmation is done on the filename argument - only +** zRev is checked. +** +** Returns 0 if the given file is not in the given checkin or if +** fileedit_ajax_check_filename() fails, else returns true. If it +** returns false, it queues up an error response and the caller must +** return immediately. +*/ +static int fileedit_ajax_setup_filerev(const char * zRev, + char ** zRevUuid, + int * pVid, + const char * zFilename, + int * frid){ + char * zFileUuid = 0; /* file content UUID */ + const int checkFile = zFilename!=0 || frid!=0; + int vid = 0; + + if(checkFile && !fileedit_ajax_check_filename(zFilename)){ + return 0; + } + vid = symbolic_name_to_rid(zRev, "ci"); + if(0==vid){ + ajax_route_error(404,"Cannot resolve name as a checkin: %s", + zRev); + return 0; + }else if(vid<0){ + ajax_route_error(400,"Checkin name is ambiguous: %s", + zRev); + return 0; + }else if(pVid!=0){ + *pVid = vid; + } + if(checkFile){ + zFileUuid = fileedit_file_uuid(zFilename, vid, 0); + if(zFileUuid==0){ + ajax_route_error(404, "Checkin does not contain file."); + return 0; + } + } + if(zRevUuid!=0){ + *zRevUuid = rid_to_uuid(vid); + } + if(checkFile){ + assert(zFileUuid!=0); + if(frid!=0){ + *frid = fast_uuid_to_rid(zFileUuid); + } + fossil_free(zFileUuid); + } + return 1; +} + +/* +** AJAX route /fileedit?ajax=content +** +** Query parameters: +** +** filename=FILENAME +** checkin=CHECKIN_NAME +** +** User must have Write access to use this page. +** +** Responds with the raw content of the given page. On error it +** produces a JSON response as documented for ajax_route_error(). +** +** Extra response headers: +** +** x-fileedit-file-perm: empty or "x" or "l", representing PERM_REG, +** PERM_EXE, or PERM_LINK, respectively. +** +** x-fileedit-checkin-branch: branch name for the passed-in checkin. +*/ +static void fileedit_ajax_content(void){ + const char * zFilename = 0; + const char * zRev = 0; + int vid, frid; + Blob content = empty_blob; + const char * zMime; + + ajax_get_fnci_args( &zFilename, &zRev ); + if(!ajax_route_bootstrap(1,0) + || !fileedit_ajax_setup_filerev(zRev, 0, &vid, + zFilename, &frid)){ + return; + } + zMime = mimetype_from_name(zFilename); + content_get(frid, &content); + if(0==zMime){ + if(looks_like_binary(&content)){ + zMime = "application/octet-stream"; + }else{ + zMime = "text/plain"; + } + } + { /* Send the is-exec bit via response header so that the UI can be + ** updated to account for that. */ + int fperm = 0; + char * zFuuid = fileedit_file_uuid(zFilename, vid, &fperm); + const char * zPerm = mfile_permint_mstring(fperm); + assert(zFuuid); + cgi_printf_header("x-fileedit-file-perm:%s\r\n", zPerm); + fossil_free(zFuuid); + } + { /* Send branch name via response header for UI usability reasons */ + char * zBranch = branch_of_rid(vid); + if(zBranch!=0 && zBranch[0]!=0){ + cgi_printf_header("x-fileedit-checkin-branch: %s\r\n", zBranch); + } + fossil_free(zBranch); + } + cgi_set_content_type(zMime); + cgi_set_content(&content); +} + +/* +** AJAX route /fileedit?ajax=diff +** +** Required query parameters: +** +** filename=FILENAME +** content=text +** checkin=checkin version +** +** Optional parameters: +** +** sbs=integer (1=side-by-side or 0=unified, default=0) +** +** ws=integer (0=diff whitespace, 1=ignore EOL ws, 2=ignore all ws) +** +** Reminder to self: search info.c for isPatch to see how a +** patch-style siff can be produced. +** +** User must have Write access to use this page. +** +** Responds with the HTML content of the diff. On error it produces a +** JSON response as documented for ajax_route_error(). +*/ +static void fileedit_ajax_diff(void){ + /* + ** Reminder: we only need the filename to perform valdiation + ** against fileedit_is_editable(), else this route could be + ** abused to get diffs against content disallowed by the + ** whitelist. + */ + const char * zFilename = 0; + const char * zRev = 0; + const char * zContent = P("content"); + char * zRevUuid = 0; + int vid, frid, iFlag; + u64 diffFlags = DIFF_HTML | DIFF_NOTTOOBIG; + Blob content = empty_blob; + + iFlag = atoi(PD("sbs","0")); + if(0==iFlag){ + diffFlags |= DIFF_LINENO; + }else{ + diffFlags |= DIFF_SIDEBYSIDE; + } + iFlag = atoi(PD("ws","2")); + if(2==iFlag){ + diffFlags |= DIFF_IGNORE_ALLWS; + }else if(1==iFlag){ + diffFlags |= DIFF_IGNORE_EOLWS; + } + diffFlags |= DIFF_STRIP_EOLCR; + ajax_get_fnci_args( &zFilename, &zRev ); + if(!ajax_route_bootstrap(1,1) + || !fileedit_ajax_setup_filerev(zRev, &zRevUuid, &vid, + zFilename, &frid)){ + return; + } + if(!zContent){ + zContent = ""; + } + cgi_set_content_type("text/html"); + blob_init(&content, zContent, -1); + { + Blob orig = empty_blob; + content_get(frid, &orig); + ajax_render_diff(&orig, &content, diffFlags); + blob_reset(&orig); + } + fossil_free(zRevUuid); + blob_reset(&content); +} + +/* +** Sets up and validates most, but not all, of p's checkin-related +** state from the CGI environment. Returns 0 on success or a suggested +** HTTP result code on error, in which case a message will have been +** written to pErr. +** +** It always fails if it cannot completely resolve the 'file' and 'r' +** parameters, including verifying that the refer to a real +** file/version combination and editable by the current user. All +** others are optional (at this level, anyway, but upstream code might +** require them). +** +** If the 3rd argument is not NULL and an error is related to a +** missing arg then *bIsMissingArg is set to true. This is +** intended to allow /fileedit to squelch certain initialization +** errors. +** +** Intended to be used only by /filepage and /filepage_commit. +*/ +static int fileedit_setup_cimi_from_p(CheckinMiniInfo * p, Blob * pErr, + int * bIsMissingArg){ + char * zFileUuid = 0; /* UUID of file content */ + const char * zFlag; /* generic flag */ + int rc = 0, vid = 0, frid = 0; /* result code, checkin/file rids */ + +#define fail(EXPR) blob_appendf EXPR; goto end_fail + zFlag = PD("filename",P("fn")); + if(zFlag==0 || !*zFlag){ + rc = 400; + if(bIsMissingArg){ + *bIsMissingArg = 1; + } + fail((pErr,"Missing required 'filename' parameter.")); + } + p->zFilename = mprintf("%s",zFlag); + + if(0==fileedit_is_editable(p->zFilename)){ + rc = 403; + fail((pErr,"Filename [%h] is disallowed " + "by the [fileedit-glob] repository " + "setting.", + p->zFilename)); + } + + zFlag = PD("checkin",P("ci")); + if(!zFlag){ + rc = 400; + if(bIsMissingArg){ + *bIsMissingArg = 1; + } + fail((pErr,"Missing required 'checkin' parameter.")); + } + vid = symbolic_name_to_rid(zFlag, "ci"); + if(0==vid){ + rc = 404; + fail((pErr,"Could not resolve checkin version.")); + }else if(vid<0){ + rc = 400; + fail((pErr,"Checkin name is ambiguous.")); + } + p->zParentUuid = rid_to_uuid(vid)/*fully expand it*/; + + zFileUuid = fileedit_file_uuid(p->zFilename, vid, &p->filePerm); + if(!zFileUuid){ + rc = 404; + fail((pErr,"Checkin [%S] does not contain file: " + "[%h]", p->zParentUuid, p->zFilename)); + }else if(PERM_LNK==p->filePerm){ + rc = 400; + fail((pErr,"Editing symlinks is not permitted.")); + } + + /* Find the repo-side file entry or fail... */ + frid = fast_uuid_to_rid(zFileUuid); + assert(frid); + + /* Read file content from submit request or repo... */ + zFlag = P("content"); + if(zFlag==0){ + content_get(frid, &p->fileContent); + }else{ + blob_init(&p->fileContent,zFlag,-1); + } + if(looks_like_binary(&p->fileContent)){ + rc = 400; + fail((pErr,"File appears to be binary. Cannot edit: " + "[%h]",p->zFilename)); + } + + zFlag = PT("comment"); + if(zFlag!=0 && *zFlag!=0){ + blob_append(&p->comment, zFlag, -1); + } + zFlag = P("comment_mimetype"); + if(zFlag){ + p->zCommentMimetype = mprintf("%s",zFlag); + zFlag = 0; + } +#define p_int(K) atoi(PD(K,"0")) + if(p_int("dry_run")!=0){ + p->flags |= CIMINI_DRY_RUN; + } + if(p_int("allow_fork")!=0){ + p->flags |= CIMINI_ALLOW_FORK; + } + if(p_int("allow_older")!=0){ + p->flags |= CIMINI_ALLOW_OLDER; + } + if(0==p_int("exec_bit")){ + p->filePerm = PERM_REG; + }else{ + p->filePerm = PERM_EXE; + } + if(p_int("allow_merge_conflict")!=0){ + p->flags |= CIMINI_ALLOW_MERGE_MARKER; + } + if(p_int("prefer_delta")!=0){ + p->flags |= CIMINI_PREFER_DELTA; + } + + /* EOL conversion policy... */ + switch(p_int("eol")){ + case 1: p->flags |= CIMINI_CONVERT_EOL_UNIX; break; + case 2: p->flags |= CIMINI_CONVERT_EOL_WINDOWS; break; + default: p->flags |= CIMINI_CONVERT_EOL_INHERIT; break; + } +#undef p_int + /* + ** TODO?: date-override date selection field. Maybe use + ** an input[type=datetime-local]. + */ + p->zUser = mprintf("%s",g.zLogin); + return 0; +end_fail: +#undef fail + fossil_free(zFileUuid); + return rc ? rc : 500; +} + +/* +** Renders a list of all open leaves in JSON form: +** +** [ +** {checkin: UUID, branch: branchName, timestamp: string} +** ] +** +** The entries are ordered newest first. +** +** If zFirstUuid is not NULL then *zFirstUuid is set to a copy of the +** full UUID of the first (most recent) leaf, which must be freed by +** the caller. It is set to 0 if there are no leaves. +*/ +static void fileedit_render_leaves_list(char ** zFirstUuid){ + Blob sql = empty_blob; + Stmt q = empty_Stmt; + int i = 0; + + if(zFirstUuid){ + *zFirstUuid = 0; + } + blob_append(&sql, timeline_query_for_tty(), -1); + blob_append_sql(&sql, " AND blob.rid IN (SElECT rid FROM leaf " + "WHERE NOT EXISTS(" + "SELECT 1 from tagxref WHERE tagid=%d AND " + "tagtype>0 AND rid=leaf.rid" + ")) " + "ORDER BY mtime DESC", TAG_CLOSED); + db_prepare_blob(&q, &sql); + CX("["); + while( SQLITE_ROW==db_step(&q) ){ + const char * zUuid = db_column_text(&q, 1); + if(i++){ + CX(","); + }else if(zFirstUuid){ + *zFirstUuid = fossil_strdup(zUuid); + } + CX("{"); + CX("\"checkin\":%!j,", zUuid); + CX("\"branch\":%!j,", db_column_text(&q, 7)); + CX("\"timestamp\":%!j", db_column_text(&q, 2)); + CX("}"); + } + CX("]"); + db_finalize(&q); +} + +/* +** For the given fully resolved UUID, renders a JSON object containing +** the fileeedit-editable files in that checkin: +** +** { +** checkin: UUID, +** editableFiles: [ filename1, ... filenameN ] +** } +** +** They are sorted by name using filename_collation(). +*/ +static void fileedit_render_checkin_files(const char * zFullUuid){ + Blob sql = empty_blob; + Stmt q = empty_Stmt; + int i = 0; + + CX("{\"checkin\":%!j," + "\"editableFiles\":[", zFullUuid); + blob_append_sql(&sql, "SELECT filename FROM files_of_checkin(%Q) " + "ORDER BY filename %s", + zFullUuid, filename_collation()); + db_prepare_blob(&q, &sql); + while( SQLITE_ROW==db_step(&q) ){ + const char * zFilename = db_column_text(&q, 0); + if(fileedit_is_editable(zFilename)){ + if(i++){ + CX(","); + } + CX("%!j", zFilename); + } + } + db_finalize(&q); + CX("]}"); +} + +/* +** AJAX route /fileedit?ajax=filelist +** +** Fetches a JSON-format list of leaves and/or filenames for use in +** creating a file selection list in /fileedit. It has different modes +** of operation depending on its arguments: +** +** 'leaves': just fetch a list of open leaf versions, in this +** format: +** +** [ +** {checkin: UUID, branch: branchName, timestamp: string} +** ] +** +** The entries are ordered newest first. +** +** 'checkin=CHECKIN_NAME': fetch the current list of is-editable files +** for the current user and given checkin name: +** +** { +** checkin: UUID, +** editableFiles: [ filename1, ... filenameN ] // sorted by name +** } +** +** On error it produces a JSON response as documented for +** ajax_route_error(). +*/ +static void fileedit_ajax_filelist(){ + const char * zCi = PD("checkin",P("ci")); + + if(!ajax_route_bootstrap(1,0)){ + return; + } + cgi_set_content_type("application/json"); + if(zCi!=0){ + char * zCiFull = 0; + if(0==fileedit_ajax_setup_filerev(zCi, &zCiFull, 0, 0, 0)){ + /* Error already reported */ + return; + } + fileedit_render_checkin_files(zCiFull); + fossil_free(zCiFull); + }else if(P("leaves")!=0){ + fileedit_render_leaves_list(0); + }else{ + ajax_route_error(500, "Unhandled URL argument."); + } +} + +/* +** AJAX route /fileedit?ajax=commit +** +** Required query parameters: +** +** filename=FILENAME +** checkin=Parent checkin UUID +** content=text +** comment=non-empty text +** +** Optional query parameters: +** +** comment_mimetype=text (NOT currently honored) +** +** dry_run=int (1 or 0) +** +** include_manifest=int (1 or 0), whether to include +** the generated manifest in the response. +** +** +** User must have Write permissions to use this page. +** +** Responds with JSON (with some state repeated +** from the input in order to avoid certain race conditions +** client-side): +** +** { +** checkin: newUUID, +** filename: theFilename, +** mimetype: string, +** branch: name of the checkin's branch, +** isExe: bool, +** dryRun: bool, +** manifest: text of manifest, +** } +** +** On error it produces a JSON response as documented for +** ajax_route_error(). +*/ +static void fileedit_ajax_commit(void){ + Blob err = empty_blob; /* Error messages */ + Blob manifest = empty_blob; /* raw new manifest */ + CheckinMiniInfo cimi; /* checkin state */ + int rc; /* generic result code */ + int newVid = 0; /* new version's RID */ + char * zNewUuid = 0; /* newVid's UUID */ + char const * zMimetype; + char * zBranch = 0; + + if(!ajax_route_bootstrap(1,1)){ + return; + } + db_begin_transaction(); + CheckinMiniInfo_init(&cimi); + rc = fileedit_setup_cimi_from_p(&cimi, &err, 0); + if(0!=rc){ + ajax_route_error(rc,"%b",&err); + goto end_cleanup; + } + if(blob_size(&cimi.comment)==0){ + ajax_route_error(400,"Empty checkin comment is not permitted."); + goto end_cleanup; + } + if(0!=atoi(PD("include_manifest","0"))){ + cimi.pMfOut = &manifest; + } + checkin_mini(&cimi, &newVid, &err); + if(blob_size(&err)){ + ajax_route_error(500,"%b",&err); + goto end_cleanup; + } + assert(newVid>0); + zNewUuid = rid_to_uuid(newVid); + cgi_set_content_type("application/json"); + CX("{"); + CX("\"checkin\":%!j,", zNewUuid); + CX("\"filename\":%!j,", cimi.zFilename); + CX("\"isExe\": %s,", cimi.filePerm==PERM_EXE ? "true" : "false"); + zMimetype = mimetype_from_name(cimi.zFilename); + if(zMimetype!=0){ + CX("\"mimetype\": %!j,", zMimetype); + } + zBranch = branch_of_rid(newVid); + if(zBranch!=0){ + CX("\"branch\": %!j,", zBranch); + fossil_free(zBranch); + } + CX("\"dryRun\": %s", + (CIMINI_DRY_RUN & cimi.flags) ? "true" : "false"); + if(blob_size(&manifest)>0){ + CX(",\"manifest\": %!j", blob_str(&manifest)); + } + CX("}"); +end_cleanup: + db_end_transaction(0/*noting that dry-run mode will have already + ** set this to rollback mode. */); + fossil_free(zNewUuid); + blob_reset(&err); + blob_reset(&manifest); + CheckinMiniInfo_cleanup(&cimi); +} + +/* +** WEBPAGE: fileedit +** +** Enables the online editing and committing of text files. Requires +** that the user have Write permissions and that a user with setup +** permissions has set the fileedit-glob setting to a list of glob +** patterns matching files which may be edited (e.g. "*.wiki,*.md"). +** Note that fileedit-glob, by design, is a local-only setting. +** It does not sync across repository clones, and must be explicitly +** set on any repositories where this page should be activated. +** +** Optional query parameters: +** +** filename=FILENAME Repo-relative path to the file. +** checkin=VERSION Checkin version, using any unambiguous +** symbolic version name. +** +** If passed a filename but no checkin then it will attempt to +** load that file from the most recent leaf checkin. +** +** Once the page is loaded, files may be selected from any open leaf +** version. The only way to edit files from non-leaf checkins is to +** pass both the filename and checkin as URL parameters to the page. +** Users with the proper permissions will be presented with "Edit" +** links in various file-specific contexts for files which match the +** fileedit-glob, regardless of whether they refer to leaf versions or +** not. +*/ +void fileedit_page(void){ + const char * zFileMime = 0; /* File mime type guess */ + CheckinMiniInfo cimi; /* Checkin state */ + int previewRenderMode = AJAX_RENDER_GUESS; /* preview mode */ + Blob err = empty_blob; /* Error report */ + const char *zAjax = P("name"); /* Name of AJAX route for + sub-dispatching. */ + + /* + ** Internal-use URL parameters: + ** + ** name=string The name of a page-specific AJAX operation. + ** + ** Noting that fossil internally stores all URL path components + ** after the first as the "name" value. Thus /fileedit?name=blah is + ** equivalent to /fileedit/blah. The latter is the preferred + ** form. This means, however, that no fileedit ajax routes may make + ** use of the name parameter. + ** + ** Which additional parameters are used by each distinct ajax route + ** is an internal implementation detail and may change with any + ** given build of this code. An unknown "name" value triggers an + ** error, as documented for ajax_route_error(). + */ + + /* Allow no access to this page without check-in privilege */ + login_check_credentials(); + if( !g.perm.Write ){ + if(zAjax!=0){ + ajax_route_error(403, "Write permissions required."); + }else{ + login_needed(g.anon.Write); + } + return; + } + /* No access to anything on this page if the fileedit-glob is empty */ + if( fileedit_glob()==0 ){ + if(zAjax!=0){ + ajax_route_error(403, "Online editing is disabled for this " + "repository."); + return; + } + style_header("File Editor (disabled)"); + CX("

Online File Editing Is Disabled

\n"); + if( g.perm.Admin ){ + CX("

To enable online editing, the " + "" + "fileedit-glob repository setting\n" + "must be set to a comma- and/or newine-delimited list of glob\n" + "values matching files which may be edited online." + "

\n"); + }else{ + CX("

Online editing is disabled for this repository.

\n"); + } + style_finish_page(); + return; + } + + /* Dispatch AJAX methods based tail of the request URI. + ** The AJAX parts do their own permissions/CSRF check and + ** fail with a JSON-format response if needed. + */ + if( 0!=zAjax ){ + /* preview mode is handled via /ajax/preview-text */ + if(0==strcmp("content",zAjax)){ + fileedit_ajax_content(); + }else if(0==strcmp("filelist",zAjax)){ + fileedit_ajax_filelist(); + }else if(0==strcmp("diff",zAjax)){ + fileedit_ajax_diff(); + }else if(0==strcmp("commit",zAjax)){ + fileedit_ajax_commit(); + }else{ + ajax_route_error(500, "Unhandled ajax route name."); + } + return; + } + + db_begin_transaction(); + CheckinMiniInfo_init(&cimi); + style_header("File Editor"); + style_emit_noscript_for_js_page(); + /* As of this point, don't use return or fossil_fatal(). Write any + ** error in (&err) and goto end_footer instead so that we can be + ** sure to emit the error message, do any cleanup, and end the + ** transaction cleanly. + */ + { + int isMissingArg = 0; + if(fileedit_setup_cimi_from_p(&cimi, &err, &isMissingArg)==0){ + assert(cimi.zFilename); + zFileMime = mimetype_from_name(cimi.zFilename); + }else if(isMissingArg!=0){ + /* Squelch these startup warnings - they're non-fatal now but + ** used to be fatal. */ + blob_reset(&err); + } + } + + /******************************************************************** + ** All errors which "could" have happened up to this point are of a + ** degree which keep us from rendering the rest of the page, and + ** thus have already caused us to skipped to the end of the page to + ** render the errors. Any up-coming errors, barring malloc failure + ** or similar, are not "that" fatal. We can/should continue + ** rendering the page, then output the error message at the end. + ********************************************************************/ + + /* The CSS for this page lives in a common file but much of it we + ** don't want inadvertently being used by other pages. We don't + ** have a common, page-specific container we can filter our CSS + ** selectors, but we do have the BODY, which we can decorate with + ** whatever CSS we wish... + */ + style_script_begin(__FILE__,__LINE__); + CX("document.body.classList.add('fileedit');\n"); + style_script_end(); + + /* Status bar */ + CX("
" + "Status messages will go here.
\n" + /* will be moved into the tab container via JS */); + + CX("
" + "(no file loaded)" + "" + "
"); + + /* Main tab container... */ + CX("
"); + + /* The .hidden class on the following tab elements is to help lessen + the FOUC effect of the tabs before JS re-assembles them. */ + + /***** File/version info tab *****/ + { + CX("
"); + CX("
"); + CX("
"/*#fileedit-tab-fileselect*/); + } + + /******* Content tab *******/ + { + CX("
"); + CX("
"); + CX("
" + "" + "
" + "Reload the file from the server, discarding " + "any local edits. To help avoid accidental loss of " + "edits, it requires confirmation (a second click) within " + "a few seconds or it will not reload." + "
" + "
"); + style_select_list_int("select-font-size", + "editor_font_size", "Editor font size", + NULL/*tooltip*/, + 100, + "100%", 100, "125%", 125, + "150%", 150, "175%", 175, + "200%", 200, NULL); + CX("
"); + CX("
"); + CX(""); + CX("
"/*textarea wrapper*/); + CX("
"/*#tab-file-content*/); + } + + /****** Preview tab ******/ + { + CX("
"); + CX("
"); + CX(""); + /* Toggle auto-update of preview when the Preview tab is selected. */ + CX("
" + "" + "" + "
" + "If on, the preview will automatically " + "refresh (if needed) when this tab is selected." + "
" + "
"); + + /* Default preview rendering mode selection... */ + previewRenderMode = zFileMime + ? ajax_render_mode_for_mimetype(zFileMime) + : AJAX_RENDER_GUESS; + style_select_list_int("select-preview-mode", + "preview_render_mode", + "Preview Mode", + "Preview mode format.", + previewRenderMode, + "Guess", AJAX_RENDER_GUESS, + "Wiki/Markdown", AJAX_RENDER_WIKI, + "HTML (iframe)", AJAX_RENDER_HTML_IFRAME, + "HTML (inline)", AJAX_RENDER_HTML_INLINE, + "Plain Text", AJAX_RENDER_PLAIN_TEXT, + NULL); + /* Allow selection of HTML preview iframe height */ + style_select_list_int("select-preview-html-ems", + "preview_html_ems", + "HTML Preview IFrame Height (EMs)", + "Height (in EMs) of the iframe used for " + "HTML preview", + 40 /*default*/, + "", 20, "", 40, + "", 60, "", 80, + "", 100, NULL); + /* Selection of line numbers for text preview */ + style_labeled_checkbox("cb-line-numbers", + "preview_ln", + "Add line numbers to plain-text previews?", + "1", P("preview_ln")!=0, + "If on, plain-text files (only) will get " + "line numbers added to the preview."); + CX("
"/*.fileedit-options*/); + CX("
"); + CX("
"/*#fileedit-tab-preview*/); + } + + /****** Diff tab ******/ + { + CX("
"); + + CX("
"); + CX("" + ""); + if(0){ + /* For the time being let's just ignore all whitespace + ** changes, as files with Windows-style EOLs always show + ** more diffs than we want then they're submitted to + ** ?ajax=diff because JS normalizes them to Unix EOLs. + ** We can revisit this decision later. */ + style_select_list_int("diff-ws-policy", + "diff_ws", "Whitespace", + "Whitespace handling policy.", + 2, + "Diff all whitespace", 0, + "Ignore EOL whitespace", 1, + "Ignore all whitespace", 2, + NULL); + } + CX("
"); + CX("
" + "Diffs will be shown here." + "
"); + CX("
"/*#fileedit-tab-diff*/); + } + + /****** Commit ******/ + CX("
"); + { + /******* Commit flags/options *******/ + CX("
"); + style_labeled_checkbox("cb-dry-run", + "dry_run", "Dry-run?", "1", + 0, + "In dry-run mode, the Commit button performs" + "all work needed for committing changes but " + "then rolls back the transaction, and thus " + "does not really commit."); + style_labeled_checkbox("cb-allow-fork", + "allow_fork", "Allow fork?", "1", + cimi.flags & CIMINI_ALLOW_FORK, + "Allow committing to create a fork?"); + style_labeled_checkbox("cb-allow-older", + "allow_older", "Allow older?", "1", + cimi.flags & CIMINI_ALLOW_OLDER, + "Allow saving against a parent version " + "which has a newer timestamp?"); + style_labeled_checkbox("cb-exec-bit", + "exec_bit", "Executable?", "1", + PERM_EXE==cimi.filePerm, + "Set the executable bit?"); + style_labeled_checkbox("cb-allow-merge-conflict", + "allow_merge_conflict", + "Allow merge conflict markers?", "1", + cimi.flags & CIMINI_ALLOW_MERGE_MARKER, + "Allow saving even if the content contains " + "what appear to be fossil merge conflict " + "markers?"); + style_labeled_checkbox("cb-prefer-delta", + "prefer_delta", + "Prefer delta manifest?", "1", + db_get_boolean("forbid-delta-manifests",0) + ? 0 + : (db_get_boolean("seen-delta-manifest",0) + || cimi.flags & CIMINI_PREFER_DELTA), + "Will create a delta manifest, instead of " + "baseline, if conditions are favorable to " + "do so. This option is only a suggestion."); + style_labeled_checkbox("cb-include-manifest", + "include_manifest", + "Response manifest?", "1", + 0, + "Include the manifest in the response? " + "It's generally only useful for debug " + "purposes."); + style_select_list_int("select-eol-style", + "eol", "EOL Style", + "EOL conversion policy, noting that " + "webpage-side processing may implicitly change " + "the line endings of the input.", + (cimi.flags & CIMINI_CONVERT_EOL_UNIX) + ? 1 : (cimi.flags & CIMINI_CONVERT_EOL_WINDOWS + ? 2 : 0), + "Inherit", 0, + "Unix", 1, + "Windows", 2, + NULL); + + CX("
"/*checkboxes*/); + } + + { /******* Commit comment, button, and result manifest *******/ + CX("
" + "Message (required)
\n"); + /* We have two comment input fields, defaulting to single-line + ** mode. JS code sets up the ability to toggle between single- + ** and multi-line modes. */ + CX(""); + CX("\n"); + { /* comment options... */ + CX("
"); + CX(" "); + if(0){ + /* Manifests support an N-card (comment mime type) but it has + ** yet to be honored where comments are rendered, so we don't + ** currently offer it as an option here: + ** https://fossil-scm.org/forum/forumpost/662da045a1 + ** + ** If/when it's ever implemented, simply enable this block and + ** adjust the container's layout accordingly (as of this + ** writing, that means changing the CSS class from + ** 'flex-container flex-column' to 'flex-container flex-row'). + */ + style_select_list_str("comment-mimetype", "comment_mimetype", + "Comment style:", + "Specify how fossil will interpret the " + "comment string.", + NULL, + "Fossil", "text/x-fossil-wiki", + "Markdown", "text/x-markdown", + "Plain text", "text/plain", + NULL); + CX("
\n"); + } + CX("
" + "(Warning: switching from multi- to single-line mode will " + "strip out all newlines!)
"); + } + CX("
\n"/*commit comment options*/); + CX("
" + "" + "
\n"); + CX("
\n" + /* Manifest gets rendered here after a commit. */); + } + CX("
"/*#fileedit-tab-commit*/); + + /****** Help/Tips ******/ + CX("
"); + { + CX("

Help & Tips

"); + CX("
    "); + CX("
  • Only files matching the fileedit-glob " + "repository setting can be edited online. That setting " + "must be a comma- or newline-delimited list of glob patterns " + "for files which may be edited online.
  • "); + CX("
  • Committing edits creates a new commit record with a single " + "modified file.
  • "); + CX("
  • \"Delta manifests\" (see the checkbox on the Commit tab) " + "make for smaller commit records, especially in repositories " + "with many files.
  • "); + CX("
  • The file selector allows, for usability's sake, only files " + "in leaf check-ins to be selected, but files may be edited via " + "non-leaf check-ins by passing them as the filename " + "and checkin URL arguments to this page.
  • "); + CX("
  • The editor stores some number of local edits in one of " + "window.fileStorage or " + "window.sessionStorage, if able, but which storage " + "is unspecified and may differ across environments. When " + "committing or force-reloading a file, local edits to that " + "file/check-in combination are discarded.
  • "); + CX("
"); + } + CX("
"/*#fileedit-tab-help*/); + + builtin_fossil_js_bundle_or("fetch", "dom", "tabs", "confirmer", + "storage", "popupwidget", "copybutton", + "pikchr", NULL); + /* + ** Set up a JS-side mapping of the AJAX_RENDER_xyz values. This is + ** used for dynamically toggling certain UI components on and off. + ** Must come after window.fossil has been intialized and before + ** fossil.page.fileedit.js. Potential TODO: move this into the + ** window.fossil bootstrapping so that we don't have to "fulfill" + ** the JS multiple times. + */ + ajax_emit_js_preview_modes(1); + builtin_request_js("sbsdiff.js"); + builtin_request_js("fossil.page.fileedit.js"); + builtin_fulfill_js_requests(); + { + /* Dynamically populate the editor, display any error in the err + ** blob, and/or switch to tab #0, where the file selector + ** lives. The extra C scopes here correspond to JS-level scopes, + ** to improve grokability. */ + style_script_begin(__FILE__,__LINE__); + CX("\n(function(){\n"); + CX("try{\n"); + { + char * zFirstLeafUuid = 0; + CX("fossil.config['fileedit-glob'] = "); + glob_render_json_to_cgi(fileedit_glob()); + CX(";\n"); + if(blob_size(&err)>0){ + CX("fossil.error(%!j);\n", blob_str(&err)); + } + /* Populate the page with the current leaves and, if available, + the selected checkin's file list, to save 1 or 2 XHR requests + at startup. That makes this page uncacheable, but compressed + delivery of this page is currently less than 6k. */ + CX("fossil.page.initialLeaves = "); + fileedit_render_leaves_list(cimi.zParentUuid ? 0 : &zFirstLeafUuid); + CX(";\n"); + if(zFirstLeafUuid){ + assert(!cimi.zParentUuid); + cimi.zParentUuid = zFirstLeafUuid; + zFirstLeafUuid = 0; + } + if(cimi.zParentUuid){ + CX("fossil.page.initialFiles = "); + fileedit_render_checkin_files(cimi.zParentUuid); + CX(";\n"); + } + CX("fossil.onPageLoad(function(){\n"); + { + if(blob_size(&err)>0){ + CX("fossil.error(%!j);\n", + blob_str(&err)); + CX("fossil.page.tabs.switchToTab(0);\n"); + } + if(cimi.zParentUuid && cimi.zFilename){ + CX("fossil.page.loadFile(%!j,%!j);\n", + cimi.zFilename, cimi.zParentUuid) + /* Reminder we cannot embed the JSON-format + content of the file here because if it contains + a SCRIPT tag then it will break the whole page. */; + } + } + CX("});\n")/*fossil.onPageLoad()*/; + } + CX("}catch(e){" + "fossil.error(e); console.error('Exception:',e);" + "}\n"); + CX("})();")/*anonymous function*/; + style_script_end(); + } + blob_reset(&err); + CheckinMiniInfo_cleanup(&cimi); + db_end_transaction(0); + style_finish_page(); +} Index: src/finfo.c ================================================================== --- src/finfo.c +++ src/finfo.c @@ -20,127 +20,572 @@ #include "config.h" #include "finfo.h" /* ** COMMAND: finfo -** -** Usage: %fossil finfo FILENAME +** +** Usage: %fossil finfo ?OPTIONS? FILENAME +** +** Print the complete change history for a single file going backwards +** in time. The default mode is -l. +** +** For the -l|--log mode: If "-b|--brief" is specified one line per revision +** is printed, otherwise the full comment is printed. The "-n|--limit N" +** and "--offset P" options limits the output to the first N changes +** after skipping P changes. +** +** The -i mode will print the artifact ID of FILENAME given the REVISION +** provided by the -r flag (which is required). +** +** In the -s mode prints the status as . This is +** a quick status and does not check for up-to-date-ness of the file. +** +** In the -p mode, there's an optional flag "-r|--revision REVISION". +** The specified version (or the latest checked out version) is printed +** to stdout. The -p mode is another form of the "cat" command. ** -** Print the change history for a single file. +** Options: +** -b|--brief Display a brief (one line / revision) summary +** --case-sensitive B Enable or disable case-sensitive filenames. B is a +** boolean: "yes", "no", "true", "false", etc. +** -i|--id Print the artifact ID (requires -r) +** -l|--log Select log mode (the default) +** -n|--limit N Display the first N changes (default unlimited). +** N less than 0 means no limit. +** --offset P Skip P changes +** -p|--print Select print mode +** -r|--revision R Print the given revision (or ckout, if none is given) +** to stdout (only in print mode) +** -s|--status Select status mode (print a status indicator for FILE) +** -W|--width N Width of lines (default is to auto-detect). Must be +** more than 22 or else 0 to indicate no limit. ** -** The "--limit N" and "--offset P" options limits the output to the first -** N changes after skipping P changes. +** See also: [[artifact]], [[cat]], [[descendants]], [[info]], [[leaves]] */ void finfo_cmd(void){ - Stmt q; - int vid; - Blob dest; - const char *zFilename; - const char *zLimit; - const char *zOffset; - int iLimit, iOffset; - - db_must_be_within_tree(); - vid = db_lget_int("checkout", 0); - if( vid==0 ){ - fossil_panic("no checkout to finfo files in"); - } - zLimit = find_option("limit",0,1); - iLimit = zLimit ? atoi(zLimit) : -1; - zOffset = find_option("offset",0,1); - iOffset = zOffset ? atoi(zOffset) : 0; - if (g.argc<3) { - usage("FILENAME"); - } - file_tree_name(g.argv[2], &dest, 1); - zFilename = blob_str(&dest); - db_prepare(&q, - "SELECT " - " (SELECT uuid FROM blob WHERE rid=mlink.fid)," /* New file */ - " (SELECT uuid FROM blob WHERE rid=mlink.mid)," /* The check-in */ - " date(event.mtime,'localtime')," - " coalesce(event.ecomment, event.comment)," - " coalesce(event.euser, event.user)" - " FROM mlink, event" - " WHERE mlink.fnid=(SELECT fnid FROM filename WHERE name=%Q)" - " AND event.objid=mlink.mid" - " ORDER BY event.mtime DESC LIMIT %d OFFSET %d /*sort*/", - zFilename, iLimit, iOffset - ); - - printf("History of %s\n", zFilename); - while( db_step(&q)==SQLITE_ROW ){ - const char *zFileUuid = db_column_text(&q, 0); - const char *zCiUuid = db_column_text(&q, 1); - const char *zDate = db_column_text(&q, 2); - const char *zCom = db_column_text(&q, 3); - const char *zUser = db_column_text(&q, 4); - char *zOut; - printf("%s ", zDate); - if( zFileUuid==0 ){ - zOut = sqlite3_mprintf("[%.10s] DELETED %s (user: %s)", - zCiUuid, zCom, zUser); - }else{ - zOut = sqlite3_mprintf("[%.10s] %s (user: %s, artifact: [%.10s])", - zCiUuid, zCom, zUser, zFileUuid); - } - comment_print(zOut, 11, 79); - sqlite3_free(zOut); - } - db_finalize(&q); - blob_reset(&dest); -} - + db_must_be_within_tree(); + if( find_option("status","s",0) ){ + Stmt q; + Blob line; + Blob fname; + int vid; + + /* We should be done with options.. */ + verify_all_options(); + + if( g.argc!=3 ) usage("-s|--status FILENAME"); + vid = db_lget_int("checkout", 0); + if( vid==0 ){ + fossil_fatal("no checkout to finfo files in"); + } + vfile_check_signature(vid, CKSIG_ENOTFILE); + file_tree_name(g.argv[2], &fname, 0, 1); + db_prepare(&q, + "SELECT pathname, deleted, rid, chnged, coalesce(origname!=pathname,0)" + " FROM vfile WHERE vfile.pathname=%B %s", + &fname, filename_collation()); + blob_zero(&line); + if( db_step(&q)==SQLITE_ROW ) { + Blob uuid; + int isDeleted = db_column_int(&q, 1); + int isNew = db_column_int(&q,2) == 0; + int chnged = db_column_int(&q,3); + int renamed = db_column_int(&q,4); + + blob_zero(&uuid); + db_blob(&uuid, + "SELECT uuid FROM blob, mlink, vfile WHERE " + "blob.rid = mlink.mid AND mlink.fid = vfile.rid AND " + "vfile.pathname=%B %s", + &fname, filename_collation() + ); + if( isNew ){ + blob_appendf(&line, "new"); + }else if( isDeleted ){ + blob_appendf(&line, "deleted"); + }else if( renamed ){ + blob_appendf(&line, "renamed"); + }else if( chnged ){ + blob_appendf(&line, "edited"); + }else{ + blob_appendf(&line, "unchanged"); + } + blob_appendf(&line, " "); + blob_appendf(&line, " %10.10s", blob_str(&uuid)); + blob_reset(&uuid); + }else{ + blob_appendf(&line, "unknown 0000000000"); + } + db_finalize(&q); + fossil_print("%s\n", blob_str(&line)); + blob_reset(&fname); + blob_reset(&line); + }else if( find_option("print","p",0) ){ + Blob record; + Blob fname; + const char *zRevision = find_option("revision", "r", 1); + + /* We should be done with options.. */ + verify_all_options(); + + file_tree_name(g.argv[2], &fname, 0, 1); + if( zRevision ){ + historical_blob(zRevision, blob_str(&fname), &record, 1); + }else{ + int rid = db_int(0, "SELECT rid FROM vfile WHERE pathname=%B %s", + &fname, filename_collation()); + if( rid==0 ){ + fossil_fatal("no history for file: %b", &fname); + } + content_get(rid, &record); + } + blob_write_to_file(&record, "-"); + blob_reset(&record); + blob_reset(&fname); + }else if( find_option("id","i",0) ){ + Blob fname; + int rid; + const char *zRevision = find_option("revision", "r", 1); + + verify_all_options(); + + if( zRevision==0 ) usage("-i|--id also requires -r|--revision"); + if( g.argc!=3 ) usage("-r|--revision REVISION FILENAME"); + file_tree_name(g.argv[2], &fname, 0, 1); + rid = db_int(0, "SELECT rid FROM blob WHERE uuid =" + " (SELECT uuid FROM files_of_checkin(%Q)" + " WHERE filename=%B %s)", + zRevision, &fname, filename_collation()); + if( rid==0 ) { + fossil_fatal("file not found for revision %s: %s", + zRevision, blob_str(&fname)); + } + whatis_rid(rid,0); + blob_reset(&fname); + }else{ + Blob line; + Stmt q; + Blob fname; + int rid; + const char *zFilename; + const char *zLimit; + const char *zWidth; + const char *zOffset; + int iLimit, iOffset, iBrief, iWidth; + + if( find_option("log","l",0) ){ + /* this is the default, no-op */ + } + zLimit = find_option("limit","n",1); + zWidth = find_option("width","W",1); + iLimit = zLimit ? atoi(zLimit) : -1; + zOffset = find_option("offset",0,1); + iOffset = zOffset ? atoi(zOffset) : 0; + iBrief = (find_option("brief","b",0) == 0); + if( iLimit==0 ){ + iLimit = -1; + } + if( zWidth ){ + iWidth = atoi(zWidth); + if( (iWidth!=0) && (iWidth<=22) ){ + fossil_fatal("-W|--width value must be >22 or 0"); + } + }else{ + iWidth = -1; + } + + /* We should be done with options.. */ + verify_all_options(); + + if( g.argc!=3 ){ + usage("?-l|--log? ?-b|--brief? FILENAME"); + } + file_tree_name(g.argv[2], &fname, 0, 1); + rid = db_int(0, "SELECT rid FROM vfile WHERE pathname=%B %s", + &fname, filename_collation()); + if( rid==0 ){ + fossil_fatal("no history for file: %b", &fname); + } + zFilename = blob_str(&fname); + db_prepare(&q, + "SELECT DISTINCT b.uuid, ci.uuid, date(event.mtime,toLocal())," + " coalesce(event.ecomment, event.comment)," + " coalesce(event.euser, event.user)," + " (SELECT value FROM tagxref WHERE tagid=%d AND tagtype>0" + " AND tagxref.rid=mlink.mid)" /* Tags */ + " FROM mlink, blob b, event, blob ci, filename" + " WHERE filename.name=%Q %s" + " AND mlink.fnid=filename.fnid" + " AND b.rid=mlink.fid" + " AND event.objid=mlink.mid" + " AND event.objid=ci.rid" + " ORDER BY event.mtime DESC LIMIT %d OFFSET %d", + TAG_BRANCH, zFilename, filename_collation(), + iLimit, iOffset + ); + blob_zero(&line); + if( iBrief ){ + fossil_print("History for %s\n", blob_str(&fname)); + } + while( db_step(&q)==SQLITE_ROW ){ + const char *zFileUuid = db_column_text(&q, 0); + const char *zCiUuid = db_column_text(&q,1); + const char *zDate = db_column_text(&q, 2); + const char *zCom = db_column_text(&q, 3); + const char *zUser = db_column_text(&q, 4); + const char *zBr = db_column_text(&q, 5); + char *zOut; + if( zBr==0 ) zBr = "trunk"; + if( iBrief ){ + fossil_print("%s ", zDate); + zOut = mprintf( + "[%S] %s (user: %s, artifact: [%S], branch: %s)", + zCiUuid, zCom, zUser, zFileUuid, zBr); + comment_print(zOut, zCom, 11, iWidth, get_comment_format()); + fossil_free(zOut); + }else{ + blob_reset(&line); + blob_appendf(&line, "%S ", zCiUuid); + blob_appendf(&line, "%.10s ", zDate); + blob_appendf(&line, "%8.8s ", zUser); + blob_appendf(&line, "%8.8s ", zBr); + blob_appendf(&line,"%-39.39s", zCom ); + comment_print(blob_str(&line), zCom, 0, iWidth, get_comment_format()); + } + } + db_finalize(&q); + blob_reset(&fname); + } +} + +/* +** COMMAND: cat +** +** Usage: %fossil cat FILENAME ... ?OPTIONS? +** +** Print on standard output the content of one or more files as they exist +** in the repository. The version currently checked out is shown by default. +** Other versions may be specified using the -r option. +** +** Options: +** -R|--repository REPO Extract artifacts from repository REPO +** -r VERSION The specific check-in containing the file +** +** See also: [[finfo]] +*/ +void cat_cmd(void){ + int i; + Blob content, fname; + const char *zRev; + db_find_and_open_repository(0, 0); + zRev = find_option("r","r",1); + + /* We should be done with options.. */ + verify_all_options(); + + for(i=2; i0" - " AND tagxref.rid=mlink.mid)" /* Tags */ - " FROM mlink, event" - " WHERE mlink.fnid=(SELECT fnid FROM filename WHERE name=%Q)" - " AND event.objid=mlink.mid" - " ORDER BY event.mtime DESC /*sort*/", - TAG_BRANCH, - zFilename + if( fnid==0 ){ + @ No such file: %h(zFilename) + style_finish_page(); + return; + } + if( g.perm.Admin ){ + style_submenu_element("MLink Table", "%R/mlink?name=%t", zFilename); + } + if( ridFrom ){ + if( P("to")!=0 ){ + ridTo = name_to_typed_rid(P("to"),"ci"); + path_shortest_stored_in_ancestor_table(ridFrom,ridTo); + }else{ + compute_direct_ancestors(ridFrom); + } + } + url_add_parameter(&url, "name", zFilename); + blob_zero(&sql); + if( ridCi ){ + /* If we will be tracking changes across renames, some extra temp + ** tables (implemented as CTEs) are required */ + blob_append_sql(&sql, + /* The clade(fid,fnid) table is the set of all (fid,fnid) pairs + ** that should participate in the output. Clade is computed by + ** walking the graph of mlink edges. + */ + "WITH RECURSIVE clade(fid,fnid) AS (\n" + " SELECT blob.rid, %d FROM blob\n" /* %d is fnid */ + " WHERE blob.uuid=(SELECT uuid FROM files_of_checkin(%Q)" + " WHERE filename=%Q)\n" /* %Q is the filename */ + " UNION\n" + " SELECT mlink.fid, mlink.fnid\n" + " FROM clade, mlink\n" + " WHERE clade.fid=mlink.pid\n" + " AND ((mlink.pfnid=0 AND mlink.fnid=clade.fnid)\n" + " OR mlink.pfnid=clade.fnid)\n" + " AND (mlink.fid>0 OR NOT EXISTS(SELECT 1 FROM mlink AS mx" + " WHERE mx.mid=mlink.mid AND mx.pid=mlink.pid" + " AND mx.fid>0 AND mx.pfnid=mlink.fnid))\n" + " UNION\n" + " SELECT mlink.pid," + " CASE WHEN mlink.pfnid>0 THEN mlink.pfnid ELSE mlink.fnid END\n" + " FROM clade, mlink\n" + " WHERE mlink.pid>0\n" + " AND mlink.fid=clade.fid\n" + " AND mlink.fnid=clade.fnid\n" + ")\n", + fnid, zCI, zFilename + ); + }else{ + /* This is the case for all files with a given name. We will still + ** create a "clade(fid,fnid)" table that identifies all participates + ** in the output graph, so that subsequent queries can all be the same, + ** but in the case the clade table is much simplier, being just a + ** single direct query against the mlink table. + */ + blob_append_sql(&sql, + "WITH clade(fid,fnid) AS (\n" + " SELECT DISTINCT fid, %d\n" + " FROM mlink\n" + " WHERE fnid=%d)", + fnid, fnid + ); + } + blob_append_sql(&sql, + "SELECT\n" + " datetime(min(event.mtime),toLocal()),\n" /* Date of change */ + " coalesce(event.ecomment, event.comment),\n" /* Check-in comment */ + " coalesce(event.euser, event.user),\n" /* User who made chng */ + " mlink.pid,\n" /* Parent file rid */ + " mlink.fid,\n" /* File rid */ + " (SELECT uuid FROM blob WHERE rid=mlink.pid),\n" /* Parent file hash */ + " blob.uuid,\n" /* Current file hash */ + " (SELECT uuid FROM blob WHERE rid=mlink.mid),\n" /* Check-in hash */ + " event.bgcolor,\n" /* Background color */ + " (SELECT value FROM tagxref WHERE tagid=%d AND tagtype>0" + " AND tagxref.rid=mlink.mid),\n" /* Branchname */ + " mlink.mid,\n" /* check-in ID */ + " mlink.pfnid,\n" /* Previous filename */ + " blob.size,\n" /* File size */ + " mlink.fnid,\n" /* Current filename */ + " filename.name\n" /* Current filename */ + "FROM clade CROSS JOIN mlink, event" + " LEFT JOIN blob ON blob.rid=clade.fid" + " LEFT JOIN filename ON filename.fnid=clade.fnid\n" + "WHERE mlink.fnid=clade.fnid AND mlink.fid=clade.fid\n" + " AND event.objid=mlink.mid\n", + TAG_BRANCH ); + if( (zA = P("a"))!=0 ){ + blob_append_sql(&sql, " AND event.mtime>=%.16g\n", + symbolic_name_to_mtime(zA,0)); + url_add_parameter(&url, "a", zA); + } + if( (zB = P("b"))!=0 ){ + blob_append_sql(&sql, " AND event.mtime<=%.16g\n", + symbolic_name_to_mtime(zB,0)); + url_add_parameter(&url, "b", zB); + } + if( ridFrom ){ + blob_append_sql(&sql, + " AND mlink.mid IN (SELECT rid FROM ancestor)\n" + "GROUP BY mlink.fid\n" + ); + }else{ + /* We only want each version of a file to appear on the graph once, + ** at its earliest appearance. All the other times that it gets merged + ** into this or that branch can be ignored. An exception is for when + ** files are deleted (when they have mlink.fid==0). If the same file + ** is deleted in multiple places, we want to show each deletion, so + ** use a "fake fid" which is derived from the parent-fid for grouping. + ** The same fake-fid must be used on the graph. + */ + blob_append_sql(&sql, + "GROUP BY" + " CASE WHEN mlink.fid>0 THEN mlink.fid ELSE mlink.pid+1000000000 END," + " mlink.fnid\n" + ); + } + blob_append_sql(&sql, "ORDER BY event.mtime DESC"); + if( (n = atoi(PD("n","0")))>0 ){ + blob_append_sql(&sql, " LIMIT %d", n); + url_add_parameter(&url, "n", P("n")); + } + blob_append_sql(&sql, " /*sort*/\n"); + db_prepare(&q, "%s", blob_sql_text(&sql)); + if( P("showsql")!=0 ){ + @

SQL:

%h(blob_str(&sql))
+ } + zMark = P("m"); + if( zMark ){ + selRid = symbolic_name_to_rid(zMark, "*"); + } + blob_reset(&sql); blob_zero(&title); - blob_appendf(&title, "History of "); - hyperlinked_path(zFilename, &title); + if( ridFrom ){ + char *zUuid = db_text(0, "SELECT uuid FROM blob WHERE rid=%d", ridFrom); + char *zLink = href("%R/info/%!S", zUuid); + if( ridTo ){ + blob_appendf(&title, "Changes to file "); + }else if( n>0 ){ + blob_appendf(&title, "First %d ancestors of file ", n); + }else{ + blob_appendf(&title, "Ancestors of file "); + } + blob_appendf(&title,"%z%h", + href("%R/file?name=%T&ci=%!S", zFilename, zUuid), + zFilename); + if( fShowId ) blob_appendf(&title, " (%d)", fnid); + blob_append(&title, ridTo ? " between " : " from ", -1); + blob_appendf(&title, "check-in %z%S", zLink, zUuid); + if( fShowId ) blob_appendf(&title, " (%d)", ridFrom); + fossil_free(zUuid); + if( ridTo ){ + zUuid = db_text(0, "SELECT uuid FROM blob WHERE rid=%d", ridTo); + zLink = href("%R/info/%!S", zUuid); + blob_appendf(&title, " and check-in %z%S", zLink, zUuid); + fossil_free(zUuid); + } + }else if( ridCi ){ + blob_appendf(&title, "History of the file that is called "); + hyperlinked_path(zFilename, &title, 0, "tree", "", LINKPATH_FILE); + if( fShowId ) blob_appendf(&title, " (%d)", fnid); + blob_appendf(&title, " at checkin %z%h", + href("%R/info?name=%t",zCI), zCI); + }else{ + blob_appendf(&title, "History for "); + hyperlinked_path(zFilename, &title, 0, "tree", "", LINKPATH_FILE); + if( fShowId ) blob_appendf(&title, " (%d)", fnid); + } + if( uBg ){ + blob_append(&title, " (color-coded by user)", -1); + } @

%b(&title)

blob_reset(&title); pGraph = graph_init(); - @
- @ + @
+ mxfnid = db_int(0, "SELECT max(fnid) FROM filename"); + if( ridFrom ){ + db_prepare(&qparent, + "SELECT DISTINCT pid*%d+CASE WHEN pfnid>0 THEN pfnid ELSE fnid END" + " FROM mlink" + " WHERE fid=:fid AND mid=:mid AND pid>0 AND fnid=:fnid" + " AND pmid IN (SELECT rid FROM ancestor)" + " ORDER BY isaux /*sort*/", mxfnid+1 + ); + }else{ + db_prepare(&qparent, + "SELECT DISTINCT pid*%d+CASE WHEN pfnid>0 THEN pfnid ELSE fnid END" + " FROM mlink" + " WHERE fid=:fid AND mid=:mid AND pid>0 AND fnid=:fnid" + " ORDER BY isaux /*sort*/", mxfnid+1 + ); + } while( db_step(&q)==SQLITE_ROW ){ const char *zDate = db_column_text(&q, 0); const char *zCom = db_column_text(&q, 1); const char *zUser = db_column_text(&q, 2); int fpid = db_column_int(&q, 3); @@ -148,66 +593,371 @@ const char *zPUuid = db_column_text(&q, 5); const char *zUuid = db_column_text(&q, 6); const char *zCkin = db_column_text(&q,7); const char *zBgClr = db_column_text(&q, 8); const char *zBr = db_column_text(&q, 9); + int fmid = db_column_int(&q, 10); + int pfnid = db_column_int(&q, 11); + int szFile = db_column_int(&q, 12); + int fnid = db_column_int(&q, 13); + const char *zFName = db_column_text(&q,14); int gidx; char zTime[10]; - char zShort[20]; - char zShortCkin[20]; + int nParent = 0; + GraphRowId aParent[GR_MAX_RAIL]; + + db_bind_int(&qparent, ":fid", frid); + db_bind_int(&qparent, ":mid", fmid); + db_bind_int(&qparent, ":fnid", fnid); + while( db_step(&qparent)==SQLITE_ROW && nParent0 ? 1 : 0, &fpid, zBr, zBgClr); - if( memcmp(zDate, zPrevDate, 10) ){ - sprintf(zPrevDate, "%.10s", zDate); + if( uBg ){ + zBgClr = user_color(zUser); + }else if( brBg || zBgClr==0 || zBgClr[0]==0 ){ + zBgClr = strcmp(zBr,"trunk")==0 ? "" : hash_color(zBr); + } + gidx = graph_add_row(pGraph, + frid>0 ? (GraphRowId)frid*(mxfnid+1)+fnid : fpid+1000000000, + nParent, 0, aParent, zBr, zBgClr, + zUuid, 0); + if( strncmp(zDate, zPrevDate, 10) ){ + sqlite3_snprintf(sizeof(zPrevDate), zPrevDate, "%.10s", zDate); @ + @
%s(zPrevDate)
+ @ } memcpy(zTime, &zDate[11], 5); zTime[5] = 0; - @ - @ + if( frid==selRid ){ + @ + }else{ + @ + } + @ + @ if( zBgClr && zBgClr[0] ){ - @ + @ } db_finalize(&q); + db_finalize(&qparent); if( pGraph ){ - graph_finish(pGraph, 1); + graph_finish(pGraph, 0, TIMELINE_DISJOINT); if( pGraph->nErr ){ graph_free(pGraph); pGraph = 0; }else{ - @ \ + @ } } @
- @
%s(zPrevDate)
- @
- @ %s(zTime)
\ + @ %z(href("%R/file?name=%T&ci=%!S",zFName,zCkin))%s(zTime)
+ @
- }else{ - @ - } - sqlite3_snprintf(sizeof(zShort), zShort, "%.10s", zUuid); - sqlite3_snprintf(sizeof(zShortCkin), zShortCkin, "%.10s", zCkin); - if( zUuid ){ - if( g.okHistory ){ - @ [%S(zUuid)] - }else{ - @ [%S(zUuid)] - } - @ part of check-in - }else{ - @ Deleted by check-in - } - hyperlink_to_uuid(zShortCkin); - @ %h(zCom) (user: - hyperlink_to_user(zUser, zDate, ""); - @ branch: %h(zBr)) - if( g.okHistory && zUuid ){ - if( fpid ){ - @ [diff] - } - @ + @ + }else{ + @ + } + if( tmFlags & TIMELINE_COMPACT ){ + @ + }else{ + @ + if( pfnid ){ + char *zPrevName = db_text(0,"SELECT name FROM filename WHERE fnid=%d", + pfnid); + @ Renamed %h(zPrevName) → %h(zFName). + fossil_free(zPrevName); + } + if( zUuid && ridTo==0 && nParent==0 ){ + @ Added: + } + if( zUuid==0 ){ + char *zNewName; + zNewName = db_text(0, + "SELECT name FROM filename WHERE fnid = " + " (SELECT fnid FROM mlink" + " WHERE mid=%d" + " AND pfnid IN (SELECT fnid FROM filename WHERE name=%Q))", + fmid, zFName); + if( zNewName ){ + @ Renamed to + @ %z(href("%R/finfo?name=%t",zNewName))%h(zNewName). + fossil_free(zNewName); + }else{ + @ Deleted: + } + } + if( (tmFlags & TIMELINE_VERBOSE)!=0 && zUuid ){ + hyperlink_to_version(zUuid); + @ part of check-in \ + hyperlink_to_version(zCkin); + } + } + @ %W(zCom) + if( (tmFlags & TIMELINE_COMPACT)!=0 ){ + @ ... + } + if( tmFlags & TIMELINE_COLUMNAR ){ + if( zBgClr && zBgClr[0] ){ + @ + }else{ + @ + } + } + if( tmFlags & TIMELINE_COMPACT ){ + cgi_printf("",frid); + } + cgi_printf("", zStyle); + if( tmFlags & (TIMELINE_COMPACT|TIMELINE_VERBOSE) ) cgi_printf("("); + if( zUuid && (tmFlags & TIMELINE_VERBOSE)==0 ){ + @ file: %z(href("%R/file?name=%T&ci=%!S",zFName,zCkin))\ + @ [%S(zUuid)] + if( fShowId ){ + int srcId = delta_source_rid(frid); + if( srcId>0 ){ + @ id: %d(frid)←%d(srcId) + }else{ + @ id: %d(frid) + } + } + } + @ check-in: \ + hyperlink_to_version(zCkin); + if( fShowId ){ + @ (%d(fmid)) + } + @ user: \ + hyperlink_to_user(zUser, zDate, ","); + @ branch: %z(href("%R/timeline?t=%T",zBr))%h(zBr), + if( tmFlags & (TIMELINE_COMPACT|TIMELINE_VERBOSE) ){ + @ size: %d(szFile)) + }else{ + @ size: %d(szFile) + } + if( g.perm.Hyperlink && zUuid ){ + const char *z = zFName; + @ + @ %z(href("%R/annotate?filename=%h&checkin=%s",z,zCkin)) @ [annotate] + @ %z(href("%R/blame?filename=%h&checkin=%s",z,zCkin)) + @ [blame] + @ %z(href("%R/timeline?uf=%!S",zUuid))[check-ins using] + if( fpid>0 ){ + @ %z(href("%R/fdiff?v1=%!S&v2=%!S",zPUuid,zUuid))[diff] + } + if( fileedit_is_editable(zFName) ){ + @ %z(href("%R/fileedit?filename=%T&checkin=%!S",zFName,zCkin))\ + @ [edit] + } + @ + } + if( fDebug & FINFO_DEBUG_MLINK ){ + int ii; + char *zAncLink; + @
fid=%d(frid) \ + @ graph-id=%lld(frid>0?(GraphRowId)frid*(mxfnid+1)+fnid:fpid+1000000000) \ + @ pid=%d(fpid) mid=%d(fmid) fnid=%d(fnid) \ + @ pfnid=%d(pfnid) mxfnid=%d(mxfnid) + if( nParent>0 ){ + @ parents=%lld(aParent[0]) + for(ii=1; ii + } + tag_private_status(frid); + /* End timelineDetail */ + if( tmFlags & TIMELINE_COMPACT ){ + @
+ }else{ + @ } - @
+ @
- timeline_output_graph_javascript(pGraph); - style_footer(); + timeline_output_graph_javascript(pGraph, TIMELINE_FILEDIFF, iTableId); + style_finish_page(); +} + +/* +** WEBPAGE: mlink +** URL: /mlink?name=FILENAME +** URL: /mlink?ci=NAME +** +** Show all MLINK table entries for a particular file, or for +** a particular check-in. +** +** This screen is intended for use by Fossil developers to help +** in debugging Fossil itself. Ordinary Fossil users are not +** expected to know what the MLINK table is or why it is important. +** +** To avoid confusing ordinary users, this page is only available +** to administrators. +*/ +void mlink_page(void){ + const char *zFName = P("name"); + const char *zCI = P("ci"); + Stmt q; + + login_check_credentials(); + if( !g.perm.Admin ){ login_needed(g.anon.Admin); return; } + style_set_current_feature("finfo"); + style_header("MLINK Table"); + if( zFName==0 && zCI==0 ){ + @ + @ Requires either a name= or ci= query parameter + @ + }else if( zFName ){ + int fnid = db_int(0,"SELECT fnid FROM filename WHERE name=%Q",zFName); + if( fnid<=0 ) fossil_fatal("no such file: \"%s\"", zFName); + db_prepare(&q, + "SELECT" + /* 0 */ " datetime(event.mtime,toLocal())," + /* 1 */ " (SELECT uuid FROM blob WHERE rid=mlink.mid)," + /* 2 */ " (SELECT uuid FROM blob WHERE rid=mlink.pmid)," + /* 3 */ " isaux," + /* 4 */ " (SELECT uuid FROM blob WHERE rid=mlink.fid)," + /* 5 */ " (SELECT uuid FROM blob WHERE rid=mlink.pid)," + /* 6 */ " mlink.pid," + /* 7 */ " mperm," + /* 8 */ " (SELECT name FROM filename WHERE fnid=mlink.pfnid)" + " FROM mlink, event" + " WHERE mlink.fnid=%d" + " AND event.objid=mlink.mid" + " ORDER BY 1 DESC", + fnid + ); + style_table_sorter(); + @

MLINK table for file + @ %h(zFName)

+ @
+ @ + @ + @ + @ + @ + @ + @ + @ + @ + @ + @ + @ + while( db_step(&q)==SQLITE_ROW ){ + const char *zDate = db_column_text(&q,0); + const char *zCkin = db_column_text(&q,1); + const char *zParent = db_column_text(&q,2); + int isMerge = db_column_int(&q,3); + const char *zFid = db_column_text(&q,4); + const char *zPid = db_column_text(&q,5); + int isExe = db_column_int(&q,7); + const char *zPrior = db_column_text(&q,8); + @ + @ + @ + if( zParent ){ + @ + }else{ + @ + } + @ + if( zFid ){ + @ + }else{ + @ + } + if( zPid ){ + @ + }else{ + @ + } + @ + if( zPrior ){ + @ + }else{ + @ + } + @ + } + db_finalize(&q); + @ + @
DateCheck-inParent
Check-in
Merge?NewOldExe
Bit?
Prior
Name
%s(zDate)%S(zCkin)%S(zParent)(New)%s(isMerge?"✓":"")%S(zFid)(Deleted)%S(zPid) + }else if( db_column_int(&q,6)<0 ){ + @ (Added by merge)(New)%s(isExe?"✓":"")%h(zPrior)
+ @
+ }else{ + int mid = name_to_rid_www("ci"); + db_prepare(&q, + "SELECT" + /* 0 */ " (SELECT name FROM filename WHERE fnid=mlink.fnid)," + /* 1 */ " (SELECT uuid FROM blob WHERE rid=mlink.fid)," + /* 2 */ " pid," + /* 3 */ " (SELECT uuid FROM blob WHERE rid=mlink.pid)," + /* 4 */ " (SELECT name FROM filename WHERE fnid=mlink.pfnid)," + /* 5 */ " (SELECT uuid FROM blob WHERE rid=mlink.pmid)," + /* 6 */ " mperm," + /* 7 */ " isaux" + " FROM mlink WHERE mid=%d ORDER BY 1", + mid + ); + @

MLINK table for check-in %h(zCI)

+ render_checkin_context(mid, 0, 1); + style_table_sorter(); + @
+ @
+ @ + @ + @ + @ + @ + @ + @ + @ + @ + @ + @ + while( db_step(&q)==SQLITE_ROW ){ + const char *zName = db_column_text(&q,0); + const char *zFid = db_column_text(&q,1); + const char *zPid = db_column_text(&q,3); + const char *zPrior = db_column_text(&q,4); + const char *zParent = db_column_text(&q,5); + int isExec = db_column_int(&q,6); + int isAux = db_column_int(&q,7); + @ + @ + if( zParent ){ + @ + }else{ + @ + } + @ + if( zFid ){ + @ + }else{ + @ + } + if( zPid ){ + @ + }else{ + @ + } + @ + if( zPrior ){ + @ + }else{ + @ + } + @ + } + db_finalize(&q); + @ + @
FileParent
Check-in
Merge?NewOldExe
Bit?
Prior
Name
%h(zName)%S(zParent)(New)%s(isAux?"✓":"")%S(zFid)(Deleted)%S(zPid) + }else if( db_column_int(&q,2)<0 ){ + @ (Added by merge)(New)%s(isExec?"✓":"")%h(zPrior)
+ @
+ } + style_finish_page(); } ADDED src/foci.c Index: src/foci.c ================================================================== --- /dev/null +++ src/foci.c @@ -0,0 +1,276 @@ +/* +** 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 routine implements eponymous virtual table for SQLite that gives +** all of the files associated with a single check-in. The table works +** as a table-valued function. +** +** The source code filename "foci" is short for "Files of Check-in". +** +** Usage example: +** +** SELECT * FROM files_of_checkin('trunk'); +** +** The "schema" for the temp.foci table is: +** +** CREATE TABLE files_of_checkin( +** checkinID INTEGER, -- RID for the check-in manifest +** filename TEXT, -- Name of a file +** uuid TEXT, -- hash of the file +** previousName TEXT, -- Name of the file in previous check-in +** perm TEXT, -- Permissions on the file +** symname TEXT HIDDEN -- Symbolic name of the check-in. +** ); +** +** The hidden symname column is (optionally) used as a query parameter to +** identify the particular check-in to parse. The checkinID parameter +** (such is a unique numeric RID rather than symbolic name) can also be used +** to identify the check-in. Example: +** +** SELECT * FROM files_of_checkin +** WHERE checkinID=symbolic_name_to_rid('trunk'); +** +*/ +#include "config.h" +#include "foci.h" +#include + +/* +** The schema for the virtual table: +*/ +static const char zFociSchema[] = +@ CREATE TABLE files_of_checkin( +@ checkinID INTEGER, -- RID for the check-in manifest +@ filename TEXT, -- Name of a file +@ uuid TEXT, -- hash of the file +@ previousName TEXT, -- Name of the file in previous check-in +@ perm TEXT, -- Permissions on the file +@ symname TEXT HIDDEN -- Symbolic name of the check-in +@ ); +; + +#define FOCI_CHECKINID 0 +#define FOCI_FILENAME 1 +#define FOCI_UUID 2 +#define FOCI_PREVNAME 3 +#define FOCI_PERM 4 +#define FOCI_SYMNAME 5 + +#if INTERFACE +/* +** The subclasses of sqlite3_vtab and sqlite3_vtab_cursor tables +** that implement the files_of_checkin virtual table. +*/ +struct FociTable { + sqlite3_vtab base; /* Base class - must be first */ +}; +struct FociCursor { + sqlite3_vtab_cursor base; /* Base class - must be first */ + Manifest *pMan; /* Current manifest */ + ManifestFile *pFile; /* Current file */ + int iFile; /* File index */ +}; +#endif /* INTERFACE */ + + +/* +** Connect to or create a foci virtual table. +*/ +static int fociConnect( + sqlite3 *db, + void *pAux, + int argc, const char *const*argv, + sqlite3_vtab **ppVtab, + char **pzErr +){ + FociTable *pTab; + + pTab = (FociTable *)sqlite3_malloc(sizeof(FociTable)); + memset(pTab, 0, sizeof(FociTable)); + sqlite3_declare_vtab(db, zFociSchema); + *ppVtab = &pTab->base; + return SQLITE_OK; +} + +/* +** Disconnect from or destroy a focivfs virtual table. +*/ +static int fociDisconnect(sqlite3_vtab *pVtab){ + sqlite3_free(pVtab); + return SQLITE_OK; +} + +/* +** Available scan methods: +** +** (0) A full scan. Visit every manifest in the repo. (Slow) +** (1) checkinID=?. visit only the single manifest specified. +** (2) symName=? visit only the single manifest specified. +*/ +static int fociBestIndex(sqlite3_vtab *tab, sqlite3_index_info *pIdxInfo){ + int i; + pIdxInfo->estimatedCost = 1000000000.0; + for(i=0; inConstraint; i++){ + if( !pIdxInfo->aConstraint[i].usable ) continue; + if( pIdxInfo->aConstraint[i].op==SQLITE_INDEX_CONSTRAINT_EQ + && (pIdxInfo->aConstraint[i].iColumn==FOCI_CHECKINID + || pIdxInfo->aConstraint[i].iColumn==FOCI_SYMNAME) + ){ + if( pIdxInfo->aConstraint[i].iColumn==FOCI_CHECKINID ){ + pIdxInfo->idxNum = 1; + }else{ + pIdxInfo->idxNum = 2; + } + pIdxInfo->estimatedCost = 1.0; + pIdxInfo->aConstraintUsage[i].argvIndex = 1; + pIdxInfo->aConstraintUsage[i].omit = 1; + break; + } + } + return SQLITE_OK; +} + +/* +** Open a new focivfs cursor. +*/ +static int fociOpen(sqlite3_vtab *pVTab, sqlite3_vtab_cursor **ppCursor){ + FociCursor *pCsr; + pCsr = (FociCursor *)sqlite3_malloc(sizeof(FociCursor)); + memset(pCsr, 0, sizeof(FociCursor)); + pCsr->base.pVtab = pVTab; + *ppCursor = (sqlite3_vtab_cursor *)pCsr; + return SQLITE_OK; +} + +/* +** Close a focivfs cursor. +*/ +static int fociClose(sqlite3_vtab_cursor *pCursor){ + FociCursor *pCsr = (FociCursor *)pCursor; + manifest_destroy(pCsr->pMan); + sqlite3_free(pCsr); + return SQLITE_OK; +} + +/* +** Move a focivfs cursor to the next entry in the file. +*/ +static int fociNext(sqlite3_vtab_cursor *pCursor){ + FociCursor *pCsr = (FociCursor *)pCursor; + pCsr->pFile = manifest_file_next(pCsr->pMan, 0); + pCsr->iFile++; + return SQLITE_OK; +} + +static int fociEof(sqlite3_vtab_cursor *pCursor){ + FociCursor *pCsr = (FociCursor *)pCursor; + return pCsr->pFile==0; +} + +static int fociFilter( + sqlite3_vtab_cursor *pCursor, + int idxNum, const char *idxStr, + int argc, sqlite3_value **argv +){ + FociCursor *pCur = (FociCursor *)pCursor; + manifest_destroy(pCur->pMan); + if( idxNum ){ + int rid; + if( idxNum==1 ){ + rid = sqlite3_value_int(argv[0]); + }else{ + rid = symbolic_name_to_rid((const char*)sqlite3_value_text(argv[0]),"ci"); + } + pCur->pMan = manifest_get(rid, CFTYPE_MANIFEST, 0); + if( pCur->pMan ){ + manifest_file_rewind(pCur->pMan); + pCur->pFile = manifest_file_next(pCur->pMan, 0); + } + }else{ + pCur->pMan = 0; + } + pCur->iFile = 0; + return SQLITE_OK; +} + +static int fociColumn( + sqlite3_vtab_cursor *pCursor, + sqlite3_context *ctx, + int i +){ + FociCursor *pCsr = (FociCursor *)pCursor; + switch( i ){ + case FOCI_CHECKINID: + sqlite3_result_int(ctx, pCsr->pMan->rid); + break; + case FOCI_FILENAME: + sqlite3_result_text(ctx, pCsr->pFile->zName, -1, + SQLITE_TRANSIENT); + break; + case FOCI_UUID: + sqlite3_result_text(ctx, pCsr->pFile->zUuid, -1, + SQLITE_TRANSIENT); + break; + case FOCI_PREVNAME: + sqlite3_result_text(ctx, pCsr->pFile->zPrior, -1, + SQLITE_TRANSIENT); + break; + case FOCI_PERM: + sqlite3_result_text(ctx, pCsr->pFile->zPerm, -1, + SQLITE_TRANSIENT); + break; + case FOCI_SYMNAME: + break; + } + return SQLITE_OK; +} + +static int fociRowid(sqlite3_vtab_cursor *pCursor, sqlite_int64 *pRowid){ + FociCursor *pCsr = (FociCursor *)pCursor; + *pRowid = pCsr->iFile; + return SQLITE_OK; +} + +int foci_register(sqlite3 *db){ + static sqlite3_module foci_module = { + 0, /* iVersion */ + fociConnect, /* xCreate */ + fociConnect, /* xConnect */ + fociBestIndex, /* xBestIndex */ + fociDisconnect, /* xDisconnect */ + fociDisconnect, /* xDestroy */ + fociOpen, /* xOpen - open a cursor */ + fociClose, /* xClose - close a cursor */ + fociFilter, /* xFilter - configure scan constraints */ + fociNext, /* xNext - advance a cursor */ + fociEof, /* xEof - check for end of scan */ + fociColumn, /* xColumn - read data */ + fociRowid, /* xRowid - read data */ + 0, /* xUpdate */ + 0, /* xBegin */ + 0, /* xSync */ + 0, /* xCommit */ + 0, /* xRollback */ + 0, /* xFindMethod */ + 0, /* xRename */ + 0, /* xSavepoint */ + 0, /* xRelease */ + 0 /* xRollbackTo */ + }; + sqlite3_create_module(db, "files_of_checkin", &foci_module, 0); + return SQLITE_OK; +} ADDED src/forum.c Index: src/forum.c ================================================================== --- /dev/null +++ src/forum.c @@ -0,0 +1,1480 @@ +/* +** Copyright (c) 2018 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 code used to generate the user forum. +*/ +#include "config.h" +#include +#include "forum.h" + +/* +** Default to using Markdown markup +*/ +#define DEFAULT_FORUM_MIMETYPE "text/x-markdown" + +#if INTERFACE +/* +** Each instance of the following object represents a single message - +** either the initial post, an edit to a post, a reply, or an edit to +** a reply. +*/ +struct ForumPost { + int fpid; /* rid for this post */ + int sid; /* Serial ID number */ + int rev; /* Revision number */ + char *zUuid; /* Artifact hash */ + char *zDisplayName; /* Name of user who wrote this post */ + double rDate; /* Date for this post */ + ForumPost *pIrt; /* This post replies to pIrt */ + ForumPost *pEditHead; /* Original, unedited post */ + ForumPost *pEditTail; /* Most recent edit for this post */ + ForumPost *pEditNext; /* This post is edited by pEditNext */ + ForumPost *pEditPrev; /* This post is an edit of pEditPrev */ + ForumPost *pNext; /* Next in chronological order */ + ForumPost *pPrev; /* Previous in chronological order */ + ForumPost *pDisplay; /* Next in display order */ + int nEdit; /* Number of edits to this post */ + int nIndent; /* Number of levels of indentation for this post */ +}; + +/* +** A single instance of the following tracks all entries for a thread. +*/ +struct ForumThread { + ForumPost *pFirst; /* First post in chronological order */ + ForumPost *pLast; /* Last post in chronological order */ + ForumPost *pDisplay; /* Entries in display order */ + ForumPost *pTail; /* Last on the display list */ + int mxIndent; /* Maximum indentation level */ +}; +#endif /* INTERFACE */ + +/* +** Return true if the forum post with the given rid has been +** subsequently edited. +*/ +int forum_rid_has_been_edited(int rid){ + static Stmt q; + int res; + db_static_prepare(&q, + "SELECT 1 FROM forumpost A, forumpost B" + " WHERE A.fpid=$rid AND B.froot=A.froot AND B.fprev=$rid" + ); + db_bind_int(&q, "$rid", rid); + res = db_step(&q)==SQLITE_ROW; + db_reset(&q); + return res; +} + +/* +** Delete a complete ForumThread and all its entries. +*/ +static void forumthread_delete(ForumThread *pThread){ + ForumPost *pPost, *pNext; + for(pPost=pThread->pFirst; pPost; pPost = pNext){ + pNext = pPost->pNext; + fossil_free(pPost->zUuid); + fossil_free(pPost->zDisplayName); + fossil_free(pPost); + } + fossil_free(pThread); +} + +/* +** Search a ForumPost list forwards looking for the post with fpid +*/ +static ForumPost *forumpost_forward(ForumPost *p, int fpid){ + while( p && p->fpid!=fpid ) p = p->pNext; + return p; +} + +/* +** Search backwards for a ForumPost +*/ +static ForumPost *forumpost_backward(ForumPost *p, int fpid){ + while( p && p->fpid!=fpid ) p = p->pPrev; + return p; +} + +/* +** Add a post to the display list +*/ +static void forumpost_add_to_display(ForumThread *pThread, ForumPost *p){ + if( pThread->pDisplay==0 ){ + pThread->pDisplay = p; + }else{ + pThread->pTail->pDisplay = p; + } + pThread->pTail = p; +} + +/* +** Extend the display list for pThread by adding all entries that +** reference fpid. The first such post will be no earlier then +** post "p". +*/ +static void forumthread_display_order( + ForumThread *pThread, /* The complete thread */ + ForumPost *pBase /* Add replies to this post */ +){ + ForumPost *p; + ForumPost *pPrev = 0; + ForumPost *pBaseIrt; + for(p=pBase->pNext; p; p=p->pNext){ + if( !p->pEditPrev && p->pIrt ){ + pBaseIrt = p->pIrt->pEditHead ? p->pIrt->pEditHead : p->pIrt; + if( pBaseIrt==pBase ){ + if( pPrev ){ + pPrev->nIndent = pBase->nIndent + 1; + forumpost_add_to_display(pThread, pPrev); + forumthread_display_order(pThread, pPrev); + } + pPrev = p; + } + } + } + if( pPrev ){ + pPrev->nIndent = pBase->nIndent + 1; + if( pPrev->nIndent>pThread->mxIndent ) pThread->mxIndent = pPrev->nIndent; + forumpost_add_to_display(pThread, pPrev); + forumthread_display_order(pThread, pPrev); + } +} + +/* +** Construct a ForumThread object given the root record id. +*/ +static ForumThread *forumthread_create(int froot, int computeHierarchy){ + ForumThread *pThread; + ForumPost *pPost; + ForumPost *p; + Stmt q; + int sid = 1; + int firt, fprev; + pThread = fossil_malloc( sizeof(*pThread) ); + memset(pThread, 0, sizeof(*pThread)); + db_prepare(&q, + "SELECT fpid, firt, fprev, (SELECT uuid FROM blob WHERE rid=fpid), fmtime" + " FROM forumpost" + " WHERE froot=%d ORDER BY fmtime", + froot + ); + while( db_step(&q)==SQLITE_ROW ){ + pPost = fossil_malloc( sizeof(*pPost) ); + memset(pPost, 0, sizeof(*pPost)); + pPost->fpid = db_column_int(&q, 0); + firt = db_column_int(&q, 1); + fprev = db_column_int(&q, 2); + pPost->zUuid = fossil_strdup(db_column_text(&q,3)); + pPost->rDate = db_column_double(&q,4); + if( !fprev ) pPost->sid = sid++; + pPost->pPrev = pThread->pLast; + pPost->pNext = 0; + if( pThread->pLast==0 ){ + pThread->pFirst = pPost; + }else{ + pThread->pLast->pNext = pPost; + } + pThread->pLast = pPost; + + /* Find the in-reply-to post. Default to the topic post if the replied-to + ** post cannot be found. */ + if( firt ){ + pPost->pIrt = pThread->pFirst; + for(p=pThread->pFirst; p; p=p->pNext){ + if( p->fpid==firt ){ + pPost->pIrt = p; + break; + } + } + } + + /* Maintain the linked list of post edits. */ + if( fprev ){ + p = forumpost_backward(pPost->pPrev, fprev); + p->pEditNext = pPost; + pPost->sid = p->sid; + pPost->rev = p->rev+1; + pPost->nEdit = p->nEdit+1; + pPost->pEditPrev = p; + pPost->pEditHead = p->pEditHead ? p->pEditHead : p; + for(; p; p=p->pEditPrev ){ + p->nEdit = pPost->nEdit; + p->pEditTail = pPost; + } + } + } + db_finalize(&q); + + if( computeHierarchy ){ + /* Compute the hierarchical display order */ + pPost = pThread->pFirst; + pPost->nIndent = 1; + pThread->mxIndent = 1; + forumpost_add_to_display(pThread, pPost); + forumthread_display_order(pThread, pPost); + } + + /* Return the result */ + return pThread; +} + +/* +** List all forum threads to standard output. +*/ +static void forum_thread_list(void){ + Stmt q; + db_prepare(&q, + " SELECT" + " datetime(max(fmtime))," + " sum(fprev IS NULL)," + " froot" + " FROM forumpost" + " GROUP BY froot" + " ORDER BY 1;" + ); + fossil_print(" id cnt most recent post\n"); + fossil_print("------ ---- -------------------\n"); + while( db_step(&q)==SQLITE_ROW ){ + fossil_print("%6d %4d %s\n", + db_column_int(&q, 2), + db_column_int(&q, 1), + db_column_text(&q, 0) + ); + } + db_finalize(&q); +} + +/* +** COMMAND: test-forumthread +** +** Usage: %fossil test-forumthread [THREADID] +** +** Display a summary of all messages on a thread THREADID. If the +** THREADID argument is omitted, then show a list of all threads. +** +** This command is intended for testing an analysis only. +*/ +void forumthread_cmd(void){ + int fpid; + int froot; + const char *zName; + ForumThread *pThread; + ForumPost *p; + + db_find_and_open_repository(0,0); + verify_all_options(); + if( g.argc==2 ){ + forum_thread_list(); + return; + } + if( g.argc!=3 ) usage("THREADID"); + zName = g.argv[2]; + fpid = symbolic_name_to_rid(zName, "f"); + if( fpid<=0 ){ + fpid = db_int(0, "SELECT rid FROM blob WHERE rid=%d", atoi(zName)); + } + if( fpid<=0 ){ + fossil_fatal("unknown or ambiguous forum id: \"%s\"", zName); + } + froot = db_int(0, "SELECT froot FROM forumpost WHERE fpid=%d", fpid); + if( froot==0 ){ + fossil_fatal("Not a forum post: \"%s\"", zName); + } + fossil_print("fpid = %d\n", fpid); + fossil_print("froot = %d\n", froot); + pThread = forumthread_create(froot, 1); + fossil_print("Chronological:\n"); + fossil_print( +/* 0 1 2 3 4 5 6 7 */ +/* 123456789 123456789 123456789 123456789 123456789 123456789 123456789 123 */ + " sid rev fpid pIrt pEditPrev pEditTail hash\n"); + for(p=pThread->pFirst; p; p=p->pNext){ + fossil_print("%4d %4d %9d %9d %9d %9d %8.8s\n", p->sid, p->rev, + p->fpid, p->pIrt ? p->pIrt->fpid : 0, + p->pEditPrev ? p->pEditPrev->fpid : 0, + p->pEditTail ? p->pEditTail->fpid : 0, p->zUuid); + } + fossil_print("\nDisplay\n"); + for(p=pThread->pDisplay; p; p=p->pDisplay){ + fossil_print("%*s", (p->nIndent-1)*3, ""); + if( p->pEditTail ){ + fossil_print("%d->%d\n", p->fpid, p->pEditTail->fpid); + }else{ + fossil_print("%d\n", p->fpid); + } + } + forumthread_delete(pThread); +} + +/* +** Render a forum post for display +*/ +void forum_render( + const char *zTitle, /* The title. Might be NULL for no title */ + const char *zMimetype, /* Mimetype of the message */ + const char *zContent, /* Content of the message */ + const char *zClass, /* Put in a
if not NULL */ + int bScroll /* Large message content scrolls if true */ +){ + if( zClass ){ + @
+ } + if( zTitle ){ + if( zTitle[0] ){ + @

%h(zTitle)

+ }else{ + @

Deleted

+ } + } + if( zContent && zContent[0] ){ + Blob x; + const int isFossilWiki = zMimetype==0 + || fossil_strcmp(zMimetype, "text/x-fossil-wiki")==0; + if( bScroll ){ + @
+ }else{ + @
+ } + blob_init(&x, 0, 0); + blob_append(&x, zContent, -1); + safe_html_context(DOCSRC_FORUM); + if( isFossilWiki ){ + /* Markdown and plain-text rendering add a wrapper DIV resp. PRE + ** element around the post, and some CSS relies on its existence + ** in order to handle expansion/collapse of the post. Fossil + ** Wiki rendering does not do so, so we must wrap those manually + ** here. */ + @
+ } + wiki_render_by_mimetype(&x, zMimetype); + if( isFossilWiki ){ + @
+ } + blob_reset(&x); + @
+ }else{ + @ Deleted + } + if( zClass ){ + @
+ } +} + +/* +** Compute a display name from a login name. +** +** If the input login is found in the USER table, then check the USER.INFO +** field to see if it has display-name followed by an email address. +** If it does, that becomes the new display name. If not, let the display +** name just be the login. +** +** Space to hold the returned name is obtained from fossil_strdup() or +** mprintf() and should be freed by the caller. +** +** HTML markup within the reply has been property escaped. Hyperlinks +** may have been added. The result is safe for use with %s. +*/ +static char *display_name_from_login(const char *zLogin){ + static Stmt q; + char *zResult; + db_static_prepare(&q, + "SELECT display_name(info) FROM user WHERE login=$login" + ); + db_bind_text(&q, "$login", zLogin); + if( db_step(&q)==SQLITE_ROW && db_column_type(&q,0)==SQLITE_TEXT ){ + const char *zDisplay = db_column_text(&q,0); + if( fossil_strcmp(zDisplay,zLogin)==0 ){ + zResult = mprintf("%z%h", + href("%R/timeline?ss=v&y=f&vfx&u=%t",zLogin),zLogin); + }else{ + zResult = mprintf("%s (%z%h)", zDisplay, + href("%R/timeline?ss=v&y=f&vfx&u=%t",zLogin),zLogin); + } + }else{ + zResult = mprintf("%z%h", + href("%R/timeline?ss=v&y=f&vfx&u=%t",zLogin),zLogin); + } + db_reset(&q); + return zResult; +} + +/* +** Compute and return the display name for a ForumPost. If +** pManifest is not NULL, then it is a Manifest object for the post. +** if pManifest is NULL, this routine has to fetch and parse the +** Manifest object for itself. +** +** Memory to hold the display name is attached to p->zDisplayName +** and will be freed together with the ForumPost object p when it +** is freed. +** +** The returned text has had all HTML markup escaped and is safe for +** use within %s. +*/ +static char *forum_post_display_name(ForumPost *p, Manifest *pManifest){ + Manifest *pToFree = 0; + if( p->zDisplayName ) return p->zDisplayName; + if( pManifest==0 ){ + pManifest = pToFree = manifest_get(p->fpid, CFTYPE_FORUM, 0); + if( pManifest==0 ) return "(unknown)"; + } + p->zDisplayName = display_name_from_login(pManifest->zUser); + if( pToFree ) manifest_destroy(pToFree); + if( p->zDisplayName==0 ) return "(unknown)"; + return p->zDisplayName; +} + + +/* +** Display a single post in a forum thread. +*/ +static void forum_display_post( + ForumPost *p, /* Forum post to display */ + int iIndentScale, /* Indent scale factor */ + int bRaw, /* True to omit the border */ + int bUnf, /* True to leave the post unformatted */ + int bHist, /* True if showing edit history */ + int bSelect, /* True if this is the selected post */ + char *zQuery /* Common query string */ +){ + char *zPosterName; /* Name of user who originally made this post */ + char *zEditorName; /* Name of user who provided the current edit */ + char *zDate; /* The time/date string */ + char *zHist; /* History query string */ + Manifest *pManifest; /* Manifest comprising the current post */ + int bPrivate; /* True for posts awaiting moderation */ + int bSameUser; /* True if author is also the reader */ + int iIndent; /* Indent level */ + const char *zMimetype;/* Formatting MIME type */ + + /* Get the manifest for the post. Abort if not found (e.g. shunned). */ + pManifest = manifest_get(p->fpid, CFTYPE_FORUM, 0); + if( !pManifest ) return; + + /* When not in raw mode, create the border around the post. */ + if( !bRaw ){ + /* Open the
enclosing the post. Set the class string to mark the post + ** as selected and/or obsolete. */ + iIndent = (p->pEditHead ? p->pEditHead->nIndent : p->nIndent)-1; + @
+ }else{ + @ > + } + + /* If this is the first post (or an edit thereof), emit the thread title. */ + if( pManifest->zThreadTitle ){ + @

%h(pManifest->zThreadTitle)

+ } + + /* Begin emitting the header line. The forum of the title + ** varies depending on whether: + ** * The post is unedited + ** * The post was last edited by the original author + ** * The post was last edited by a different person + */ + if( p->pEditHead ){ + zDate = db_text(0, "SELECT datetime(%.17g,toLocal())", + p->pEditHead->rDate); + }else{ + zPosterName = forum_post_display_name(p, pManifest); + zEditorName = zPosterName; + } + zDate = db_text(0, "SELECT datetime(%.17g,toLocal())", p->rDate); + if( p->pEditPrev ){ + zPosterName = forum_post_display_name(p->pEditHead, 0); + zEditorName = forum_post_display_name(p, pManifest); + zHist = bHist ? "" : "&hist"; + @

(%d(p->sid)\ + @ .%0*d(fossil_num_digits(p->nEdit))(p->rev)) \ + if( fossil_strcmp(zPosterName, zEditorName)==0 ){ + @ By %s(zPosterName) on %h(zDate) edited from \ + @ %z(href("%R/forumpost/%S?%s%s",p->pEditPrev->zUuid,zQuery,zHist))\ + @ %d(p->sid).%0*d(fossil_num_digits(p->nEdit))(p->pEditPrev->rev) + }else{ + @ Originally by %s(zPosterName) \ + @ with edits by %s(zEditorName) on %h(zDate) from \ + @ %z(href("%R/forumpost/%S?%s%s",p->pEditPrev->zUuid,zQuery,zHist))\ + @ %d(p->sid).%0*d(fossil_num_digits(p->nEdit))(p->pEditPrev->rev) + } + }else{ + zPosterName = forum_post_display_name(p, pManifest); + @

(%d(p->sid)) \ + @ By %s(zPosterName) on %h(zDate) + } + fossil_free(zDate); + + + /* If debugging is enabled, link to the artifact page. */ + if( g.perm.Debug ){ + @ \ + @ (artifact-%d(p->fpid)) + } + + /* If this is a reply, refer back to the parent post. */ + if( p->pIrt ){ + @ in reply to %z(href("%R/forumpost/%S?%s",p->pIrt->zUuid,zQuery))\ + @ %d(p->pIrt->sid)\ + if( p->pIrt->nEdit ){ + @ .%0*d(fossil_num_digits(p->pIrt->nEdit))(p->pIrt->rev)\ + } + @ + } + + /* If this post was later edited, refer forward to the next edit. */ + if( p->pEditNext ){ + @ updated by %z(href("%R/forumpost/%S?%s",p->pEditNext->zUuid,zQuery))\ + @ %d(p->pEditNext->sid)\ + @ .%0*d(fossil_num_digits(p->nEdit))(p->pEditNext->rev) + } + + /* Provide a link to select the individual post. */ + if( !bSelect ){ + @ %z(href("%R/forumpost/%!S?%s",p->zUuid,zQuery))[link] + } + + /* Provide a link to the raw source code. */ + if( !bUnf ){ + @ %z(href("%R/forumpost/%!S?raw",p->zUuid))[source] + } + @

+ } + + /* Check if this post is approved, also if it's by the current user. */ + bPrivate = content_is_private(p->fpid); + bSameUser = login_is_individual() + && fossil_strcmp(pManifest->zUser, g.zLogin)==0; + + /* Render the post if the user is able to see it. */ + if( bPrivate && !g.perm.ModForum && !bSameUser ){ + @

Awaiting Moderator Approval

+ }else{ + if( bRaw || bUnf || p->pEditTail ){ + zMimetype = "text/plain"; + }else{ + zMimetype = pManifest->zMimetype; + } + forum_render(0, zMimetype, pManifest->zWiki, 0, !bRaw); + } + + /* When not in raw mode, finish creating the border around the post. */ + if( !bRaw ){ + /* If the user is able to write to the forum and if this post has not been + ** edited, create a form with various interaction buttons. */ + if( g.perm.WrForum && !p->pEditTail ){ + @
+ @ + if( !bPrivate ){ + /* Reply and Edit are only available if the post has been approved. */ + @ + if( g.perm.Admin || bSameUser ){ + @ + @ + } + }else if( g.perm.ModForum ){ + /* Allow moderators to approve or reject pending posts. Also allow + ** forum supervisors to mark non-special users as trusted and therefore + ** able to post unmoderated. */ + @ + @ + if( g.perm.AdminForum && !login_is_special(pManifest->zUser) ){ + @
+ @ + } + }else if( bSameUser ){ + /* Allow users to delete (reject) their own pending posts. */ + @ + } + @
+ } + @
+ } + + /* Clean up. */ + manifest_destroy(pManifest); +} + +/* +** Possible display modes for forum_display_thread(). +*/ +enum { + FD_RAW, /* Like FD_SINGLE, but additionally omit the border, force + ** unformatted mode, and inhibit history mode */ + FD_SINGLE, /* Render a single post and (optionally) its edit history */ + FD_CHRONO, /* Render all posts in chronological order */ + FD_HIER, /* Render all posts in an indented hierarchy */ +}; + +/* +** Display a forum thread. If mode is FD_RAW or FD_SINGLE, display only a +** single post from the thread and (optionally) its edit history. +*/ +static void forum_display_thread( + int froot, /* Forum thread root post ID */ + int fpid, /* Selected forum post ID, or 0 if none selected */ + int mode, /* Forum display mode, one of the FD_* enumerations */ + int bUnf, /* True if rendering unformatted */ + int bHist /* True if showing edit history, ignored for FD_RAW */ +){ + ForumThread *pThread; /* Thread structure */ + ForumPost *pSelect; /* Currently selected post, or NULL if none */ + ForumPost *p; /* Post iterator pointer */ + char *zQuery; /* Common query string */ + int iIndentScale = 4; /* Indent scale factor, measured in "ex" units */ + int sid; /* Comparison serial ID */ + + /* In raw mode, force unformatted display and disable history. */ + if( mode == FD_RAW ){ + bUnf = 1; + bHist = 0; + } + + /* Thread together the posts and (optionally) compute the hierarchy. */ + pThread = forumthread_create(froot, mode==FD_HIER); + + /* Compute the appropriate indent scaling. */ + if( mode==FD_HIER ){ + iIndentScale = 4; + while( iIndentScale>1 && iIndentScale*pThread->mxIndent>25 ){ + iIndentScale--; + } + }else{ + iIndentScale = 0; + } + + /* Find the selected post, or (depending on parameters) its latest edit. */ + pSelect = fpid ? forumpost_forward(pThread->pFirst, fpid) : 0; + if( !bHist && mode!=FD_RAW && pSelect && pSelect->pEditTail ){ + pSelect = pSelect->pEditTail; + } + + /* When displaying only a single post, abort if no post was selected or the + ** selected forum post does not exist in the thread. Otherwise proceed to + ** display the entire thread without marking any posts as selected. */ + if( !pSelect && (mode==FD_RAW || mode==FD_SINGLE) ){ + return; + } + + /* Create the common query string to append to nearly all post links. */ + zQuery = mode==FD_RAW ? 0 : mprintf("t=%c%s%s", + mode==FD_SINGLE ? 's' : mode==FD_CHRONO ? 'c' : 'h', + bUnf ? "&unf" : "", bHist ? "&hist" : ""); + + /* Identify which post to display first. If history is shown, start with the + ** original, unedited post. Otherwise advance to the post's latest edit. */ + if( mode==FD_RAW || mode==FD_SINGLE ){ + p = pSelect; + if( bHist && p->pEditHead ) p = p->pEditHead; + }else{ + p = mode==FD_CHRONO ? pThread->pFirst : pThread->pDisplay; + if( !bHist && p->pEditTail ) p = p->pEditTail; + } + + /* Display the appropriate subset of posts in sequence. */ + while( p ){ + /* Display the post. */ + forum_display_post(p, iIndentScale, mode==FD_RAW, + bUnf, bHist, p==pSelect, zQuery); + + /* Advance to the next post in the thread. */ + if( mode==FD_CHRONO ){ + /* Chronological mode: display posts (optionally including edits) in their + ** original commit order. */ + if( bHist ){ + p = p->pNext; + }else{ + sid = p->sid; + if( p->pEditHead ) p = p->pEditHead; + do p = p->pNext; while( p && p->sid<=sid ); + if( p && p->pEditTail ) p = p->pEditTail; + } + }else if( bHist && p->pEditNext ){ + /* Hierarchical and single mode: display each post's edits in sequence. */ + p = p->pEditNext; + }else if( mode==FD_HIER ){ + /* Hierarchical mode: after displaying with each post (optionally + ** including edits), go to the next post in computed display order. */ + p = p->pEditHead ? p->pEditHead->pDisplay : p->pDisplay; + if( !bHist && p && p->pEditTail ) p = p->pEditTail; + }else{ + /* Single and raw mode: terminate after displaying the selected post and + ** (optionally) its edits. */ + break; + } + } + + /* Undocumented "threadtable" query parameter causes thread table to be + ** displayed for debugging purposes. */ + if( PB("threadtable") ){ + @
+ @ + @ + } + @
sidrevfpidpIrtpEditHeadpEditTail\ + @ pEditNextpEditPrevpDisplayhash + for(p=pThread->pFirst; p; p=p->pNext){ + @
%d(p->sid)%d(p->rev)%d(p->fpid)\ + @ %d(p->pIrt ? p->pIrt->fpid : 0)\ + @ %d(p->pEditHead ? p->pEditHead->fpid : 0)\ + @ %d(p->pEditTail ? p->pEditTail->fpid : 0)\ + @ %d(p->pEditNext ? p->pEditNext->fpid : 0)\ + @ %d(p->pEditPrev ? p->pEditPrev->fpid : 0)\ + @ %d(p->pDisplay ? p->pDisplay->fpid : 0)\ + @ %S(p->zUuid)
+ } + + /* Clean up. */ + forumthread_delete(pThread); + fossil_free(zQuery); +} + +/* +** Emit Forum Javascript which applies (or optionally can apply) +** to all forum-related pages. It does not include page-specific +** code (e.g. "forum.js"). +*/ +static void forum_emit_js(void){ + builtin_fossil_js_bundle_or("copybutton", "pikchr", NULL); + builtin_request_js("fossil.page.forumpost.js"); +} + +/* +** WEBPAGE: forumpost +** +** Show a single forum posting. The posting is shown in context with +** its entire thread. The selected posting is enclosed within +**
...
. Javascript is used to move the +** selected posting into view after the page loads. +** +** Query parameters: +** +** name=X REQUIRED. The hash of the post to display. +** t=a Automatic display mode, i.e. hierarchical for +** desktop and chronological for mobile. This is the +** default if the "t" query parameter is omitted. +** t=c Show posts in the order they were written. +** t=h Show posts using hierarchical indenting. +** t=s Show only the post specified by "name=X". +** t=r Alias for "t=c&unf&hist". +** t=y Alias for "t=s&unf&hist". +** raw Alias for "t=s&unf". Additionally, omit the border +** around the post, and ignore "t" and "hist". +** unf Show the original, unformatted source text. +** hist Show edit history in addition to current posts. +*/ +void forumpost_page(void){ + forumthread_page(); +} + +/* +** WEBPAGE: forumthread +** +** Show all forum messages associated with a particular message thread. +** The result is basically the same as /forumpost except that none of +** the postings in the thread are selected. +** +** Query parameters: +** +** name=X REQUIRED. The hash of any post of the thread. +** t=a Automatic display mode, i.e. hierarchical for +** desktop and chronological for mobile. This is the +** default if the "t" query parameter is omitted. +** t=c Show posts in the order they were written. +** t=h Show posts using hierarchical indenting. +** unf Show the original, unformatted source text. +** hist Show edit history in addition to current posts. +*/ +void forumthread_page(void){ + int fpid; + int froot; + char *zThreadTitle; + const char *zName = P("name"); + const char *zMode = PD("t","a"); + int bRaw = PB("raw"); + int bUnf = PB("unf"); + int bHist = PB("hist"); + int mode = 0; + login_check_credentials(); + if( !g.perm.RdForum ){ + login_needed(g.anon.RdForum); + return; + } + if( zName==0 ){ + webpage_error("Missing \"name=\" query parameter"); + } + fpid = symbolic_name_to_rid(zName, "f"); + if( fpid<=0 ){ + if( fpid==0 ){ + webpage_notfound_error("Unknown forum id: \"%s\"", zName); + }else{ + ambiguous_page(); + } + return; + } + froot = db_int(0, "SELECT froot FROM forumpost WHERE fpid=%d", fpid); + if( froot==0 ){ + webpage_notfound_error("Not a forum post: \"%s\"", zName); + } + if( fossil_strcmp(g.zPath,"forumthread")==0 ) fpid = 0; + + /* Decode the mode parameters. */ + if( bRaw ){ + mode = FD_RAW; + bUnf = 1; + bHist = 0; + cgi_replace_query_parameter("unf", "on"); + cgi_delete_query_parameter("hist"); + cgi_delete_query_parameter("raw"); + }else{ + switch( *zMode ){ + case 'a': mode = cgi_from_mobile() ? FD_CHRONO : FD_HIER; break; + case 'c': mode = FD_CHRONO; break; + case 'h': mode = FD_HIER; break; + case 's': mode = FD_SINGLE; break; + case 'r': mode = FD_CHRONO; break; + case 'y': mode = FD_SINGLE; break; + default: webpage_error("Invalid thread mode: \"%s\"", zMode); + } + if( *zMode=='r' || *zMode=='y') { + bUnf = 1; + bHist = 1; + cgi_replace_query_parameter("t", mode==FD_CHRONO ? "c" : "s"); + cgi_replace_query_parameter("unf", "on"); + cgi_replace_query_parameter("hist", "on"); + } + } + + /* Define the page header. */ + zThreadTitle = db_text("", + "SELECT" + " substr(event.comment,instr(event.comment,':')+2)" + " FROM forumpost, event" + " WHERE event.objid=forumpost.fpid" + " AND forumpost.fpid=%d;", + fpid + ); + style_set_current_feature("forum"); + style_header("%s%s", zThreadTitle, *zThreadTitle ? "" : "Forum"); + fossil_free(zThreadTitle); + if( mode!=FD_CHRONO ){ + style_submenu_element("Chronological", "%R/%s/%s?t=c%s%s", g.zPath, zName, + bUnf ? "&unf" : "", bHist ? "&hist" : ""); + } + if( mode!=FD_HIER ){ + style_submenu_element("Hierarchical", "%R/%s/%s?t=h%s%s", g.zPath, zName, + bUnf ? "&unf" : "", bHist ? "&hist" : ""); + } + style_submenu_checkbox("unf", "Unformatted", 0, 0); + style_submenu_checkbox("hist", "History", 0, 0); + + /* Display the thread. */ + forum_display_thread(froot, fpid, mode, bUnf, bHist); + + /* Emit Forum Javascript. */ + builtin_request_js("forum.js"); + forum_emit_js(); + + /* Emit the page style. */ + style_finish_page(); +} + +/* +** Return true if a forum post should be moderated. +*/ +static int forum_need_moderation(void){ + if( P("domod") ) return 1; + if( g.perm.WrTForum ) return 0; + if( g.perm.ModForum ) return 0; + return 1; +} + +/* +** Return true if the string is white-space only. +*/ +static int whitespace_only(const char *z){ + if( z==0 ) return 1; + while( z[0] && fossil_isspace(z[0]) ){ z++; } + return z[0]==0; +} + +/* +** Add a new Forum Post artifact to the repository. +** +** Return true if a redirect occurs. +*/ +static int forum_post( + const char *zTitle, /* Title. NULL for replies */ + int iInReplyTo, /* Post replying to. 0 for new threads */ + int iEdit, /* Post being edited, or zero for a new post */ + const char *zUser, /* Username. NULL means use login name */ + const char *zMimetype, /* Mimetype of content. */ + const char *zContent /* Content */ +){ + char *zDate; + char *zI; + char *zG; + int iBasis; + Blob x, cksum, formatCheck, errMsg; + Manifest *pPost; + int nContent = zContent ? (int)strlen(zContent) : 0; + + schema_forum(); + if( iEdit==0 && whitespace_only(zContent) ){ + return 0; + } + if( iInReplyTo==0 && iEdit>0 ){ + iBasis = iEdit; + iInReplyTo = db_int(0, "SELECT firt FROM forumpost WHERE fpid=%d", iEdit); + }else{ + iBasis = iInReplyTo; + } + webpage_assert( (zTitle==0)+(iInReplyTo==0)==1 ); + blob_init(&x, 0, 0); + zDate = date_in_standard_format("now"); + blob_appendf(&x, "D %s\n", zDate); + fossil_free(zDate); + zG = db_text(0, + "SELECT uuid FROM blob, forumpost" + " WHERE blob.rid==forumpost.froot" + " AND forumpost.fpid=%d", iBasis); + if( zG ){ + blob_appendf(&x, "G %s\n", zG); + fossil_free(zG); + } + if( zTitle ){ + blob_appendf(&x, "H %F\n", zTitle); + } + zI = db_text(0, "SELECT uuid FROM blob WHERE rid=%d", iInReplyTo); + if( zI ){ + blob_appendf(&x, "I %s\n", zI); + fossil_free(zI); + } + if( fossil_strcmp(zMimetype,"text/x-fossil-wiki")!=0 ){ + blob_appendf(&x, "N %s\n", zMimetype); + } + if( iEdit>0 ){ + char *zP = db_text(0, "SELECT uuid FROM blob WHERE rid=%d", iEdit); + if( zP==0 ) webpage_error("missing edit artifact %d", iEdit); + blob_appendf(&x, "P %s\n", zP); + fossil_free(zP); + } + if( zUser==0 ){ + if( login_is_nobody() ){ + zUser = "anonymous"; + }else{ + zUser = login_name(); + } + } + blob_appendf(&x, "U %F\n", zUser); + blob_appendf(&x, "W %d\n%s\n", nContent, zContent); + md5sum_blob(&x, &cksum); + blob_appendf(&x, "Z %b\n", &cksum); + blob_reset(&cksum); + + /* Verify that the artifact we are creating is well-formed */ + blob_init(&formatCheck, 0, 0); + blob_init(&errMsg, 0, 0); + blob_copy(&formatCheck, &x); + pPost = manifest_parse(&formatCheck, 0, &errMsg); + if( pPost==0 ){ + webpage_error("malformed forum post artifact - %s", blob_str(&errMsg)); + } + webpage_assert( pPost->type==CFTYPE_FORUM ); + manifest_destroy(pPost); + + if( P("dryrun") ){ + @
+ @ This is the artifact that would have been generated: + @
%h(blob_str(&x))
+ @
+ blob_reset(&x); + return 0; + }else{ + int nrid = wiki_put(&x, iEdit>0 ? iEdit : 0, + forum_need_moderation()); + blob_reset(&x); + cgi_redirectf("%R/forumpost/%S", rid_to_uuid(nrid)); + return 1; + } +} + +/* +** Paint the form elements for entering a Forum post +*/ +static void forum_post_widget( + const char *zTitle, + const char *zMimetype, + const char *zContent +){ + if( zTitle ){ + @ Title:
+ } + @ %z(href("%R/markup_help"))Markup style: + mimetype_option_menu(zMimetype); + @

+} + +/* +** WEBPAGE: forumnew +** WEBPAGE: forumedit +** +** Start a new thread on the forum or reply to an existing thread. +** But first prompt to see if the user would like to log in. +*/ +void forum_page_init(void){ + int isEdit; + char *zGoto; + login_check_credentials(); + if( !g.perm.WrForum ){ + login_needed(g.anon.WrForum); + return; + } + if( sqlite3_strglob("*edit*", g.zPath)==0 ){ + zGoto = mprintf("%R/forume2?fpid=%S",PD("fpid","")); + isEdit = 1; + }else{ + zGoto = mprintf("%R/forume1"); + isEdit = 0; + } + if( login_is_individual() ){ + if( isEdit ){ + forumedit_page(); + }else{ + forumnew_page(); + } + return; + } + style_set_current_feature("forum"); + style_header("%h As Anonymous?", isEdit ? "Reply" : "Post"); + @

You are not logged in. + @

+ @
+ @
+ @ + @
+ @
Post to the forum anonymously + if( login_self_register_available(0) ){ + @
+ @
+ @ + @ + @
+ @
Create a new account and post using that new account + } + @
+ @
+ @ + @ + @ + @
+ @
Log into an existing account + @
+ forum_emit_js(); + style_finish_page(); + fossil_free(zGoto); +} + +/* +** Write the "From: USER" line on the webpage. +*/ +static void forum_from_line(void){ + if( login_is_nobody() ){ + @ From: anonymous
+ }else{ + @ From: %h(login_name())
+ } +} + +/* +** WEBPAGE: forume1 +** +** Start a new forum thread. +*/ +void forumnew_page(void){ + const char *zTitle = PDT("title",""); + const char *zMimetype = PD("mimetype",DEFAULT_FORUM_MIMETYPE); + const char *zContent = PDT("content",""); + login_check_credentials(); + if( !g.perm.WrForum ){ + login_needed(g.anon.WrForum); + return; + } + if( P("submit") && cgi_csrf_safe(1) ){ + if( forum_post(zTitle, 0, 0, 0, zMimetype, zContent) ) return; + } + if( P("preview") && !whitespace_only(zContent) ){ + @

Preview:

+ forum_render(zTitle, zMimetype, zContent, "forumEdit", 1); + } + style_set_current_feature("forum"); + style_header("New Forum Thread"); + @
+ @

New Thread:

+ forum_from_line(); + forum_post_widget(zTitle, zMimetype, zContent); + @ + if( P("preview") && !whitespace_only(zContent) ){ + @ + }else{ + @ + } + if( g.perm.Debug ){ + /* Give extra control over the post to users with the special + * Debug capability, which includes Admin and Setup users */ + @
+ @ + @
+ @
+ @
+ } + @
+ forum_emit_js(); + style_finish_page(); +} + +/* +** WEBPAGE: forume2 +** +** Edit an existing forum message. +** Query parameters: +** +** fpid=X Hash of the post to be edited. REQUIRED +*/ +void forumedit_page(void){ + int fpid; + int froot; + Manifest *pPost = 0; + Manifest *pRootPost = 0; + const char *zMimetype = 0; + const char *zContent = 0; + const char *zTitle = 0; + char *zDate = 0; + const char *zFpid = PD("fpid",""); + int isCsrfSafe; + int isDelete = 0; + + login_check_credentials(); + if( !g.perm.WrForum ){ + login_needed(g.anon.WrForum); + return; + } + fpid = symbolic_name_to_rid(zFpid, "f"); + if( fpid<=0 || (pPost = manifest_get(fpid, CFTYPE_FORUM, 0))==0 ){ + webpage_error("Missing or invalid fpid query parameter"); + } + froot = db_int(0, "SELECT froot FROM forumpost WHERE fpid=%d", fpid); + if( froot==0 || (pRootPost = manifest_get(froot, CFTYPE_FORUM, 0))==0 ){ + webpage_error("fpid does not appear to be a forum post: \"%d\"", fpid); + } + if( P("cancel") ){ + cgi_redirectf("%R/forumpost/%S",P("fpid")); + return; + } + isCsrfSafe = cgi_csrf_safe(1); + if( g.perm.ModForum && isCsrfSafe ){ + if( P("approve") ){ + const char *zUserToTrust; + moderation_approve('f', fpid); + if( g.perm.AdminForum + && PB("trust") + && (zUserToTrust = P("trustuser"))!=0 + ){ + db_unprotect(PROTECT_USER); + db_multi_exec("UPDATE user SET cap=cap||'4' " + "WHERE login=%Q AND cap NOT GLOB '*4*'", + zUserToTrust); + db_protect_pop(); + } + cgi_redirectf("%R/forumpost/%S",P("fpid")); + return; + } + if( P("reject") ){ + char *zParent = + db_text(0, + "SELECT uuid FROM forumpost, blob" + " WHERE forumpost.fpid=%d AND blob.rid=forumpost.firt", + fpid + ); + moderation_disapprove(fpid); + if( zParent ){ + cgi_redirectf("%R/forumpost/%S",zParent); + }else{ + cgi_redirectf("%R/forum"); + } + return; + } + } + style_set_current_feature("forum"); + isDelete = P("nullout")!=0; + if( P("submit") + && isCsrfSafe + && (zContent = PDT("content",""))!=0 + && (!whitespace_only(zContent) || isDelete) + ){ + int done = 1; + const char *zMimetype = PD("mimetype",DEFAULT_FORUM_MIMETYPE); + if( P("reply") ){ + done = forum_post(0, fpid, 0, 0, zMimetype, zContent); + }else if( P("edit") || isDelete ){ + done = forum_post(P("title"), 0, fpid, 0, zMimetype, zContent); + }else{ + webpage_error("Missing 'reply' query parameter"); + } + if( done ) return; + } + if( isDelete ){ + zMimetype = "text/x-fossil-wiki"; + zContent = ""; + if( pPost->zThreadTitle ) zTitle = ""; + style_header("Delete %s", zTitle ? "Post" : "Reply"); + @

Original Post:

+ forum_render(pPost->zThreadTitle, pPost->zMimetype, pPost->zWiki, + "forumEdit", 1); + @

Change Into:

+ forum_render(zTitle, zMimetype, zContent,"forumEdit", 1); + @
+ @ + @ + @ + @ + if( zTitle ){ + @ + } + }else if( P("edit") ){ + /* Provide an edit to the fpid post */ + zMimetype = P("mimetype"); + zContent = PT("content"); + zTitle = P("title"); + if( zContent==0 ) zContent = fossil_strdup(pPost->zWiki); + if( zMimetype==0 ) zMimetype = fossil_strdup(pPost->zMimetype); + if( zTitle==0 && pPost->zThreadTitle!=0 ){ + zTitle = fossil_strdup(pPost->zThreadTitle); + } + style_header("Edit %s", zTitle ? "Post" : "Reply"); + @

Original Post:

+ forum_render(pPost->zThreadTitle, pPost->zMimetype, pPost->zWiki, + "forumEdit", 1); + if( P("preview") ){ + @

Preview of Edited Post:

+ forum_render(zTitle, zMimetype, zContent,"forumEdit", 1); + } + @

Revised Message:

+ @ + @ + @ + forum_from_line(); + forum_post_widget(zTitle, zMimetype, zContent); + }else{ + /* Reply */ + char *zDisplayName; + zMimetype = PD("mimetype",DEFAULT_FORUM_MIMETYPE); + zContent = PDT("content",""); + style_header("Reply"); + if( pRootPost->zThreadTitle ){ + @

Thread: %h(pRootPost->zThreadTitle)

+ } + @

Replying To: + @ %S(zFpid) + @ [source] + @

+ zDate = db_text(0, "SELECT datetime(%.17g,toLocal())", pPost->rDate); + zDisplayName = display_name_from_login(pPost->zUser); + @

By %s(zDisplayName) on %h(zDate)

+ fossil_free(zDisplayName); + fossil_free(zDate); + forum_render(0, pPost->zMimetype, pPost->zWiki, "forumEdit", 1); + if( P("preview") && !whitespace_only(zContent) ){ + @

Preview:

+ forum_render(0, zMimetype,zContent, "forumEdit", 1); + } + @

Enter Reply:

+ @ + @ + @ + forum_from_line(); + forum_post_widget(0, zMimetype, zContent); + } + if( !isDelete ){ + @ + } + @ + if( (P("preview") && !whitespace_only(zContent)) || isDelete ){ + @ + } + if( g.perm.Debug ){ + /* For the test-forumnew page add these extra debugging controls */ + @
+ @ + @
+ @
+ @
+ } + @
+ forum_emit_js(); + style_finish_page(); +} + +/* +** WEBPAGE: forummain +** WEBPAGE: forum +** +** The main page for the forum feature. Show a list of recent forum +** threads. Also show a search box at the top if search is enabled, +** and a button for creating a new thread, if enabled. +** +** Query parameters: +** +** n=N The number of threads to show on each page +** x=X Skip the first X threads +*/ +void forum_main_page(void){ + Stmt q; + int iLimit, iOfst, iCnt; + int srchFlags; + login_check_credentials(); + srchFlags = search_restrict(SRCH_FORUM); + if( !g.perm.RdForum ){ + login_needed(g.anon.RdForum); + return; + } + style_set_current_feature("forum"); + style_header("Forum"); + if( g.perm.WrForum ){ + style_submenu_element("New Thread","%R/forumnew"); + }else{ + /* Can't combine this with previous case using the ternary operator + * because that causes an error yelling about "non-constant format" + * with some compilers. I can't see it, since both expressions have + * the same format, but I'm no C spec lawyer. */ + style_submenu_element("New Thread","%R/login"); + } + if( g.perm.ModForum && moderation_needed() ){ + style_submenu_element("Moderation Requests", "%R/modreq"); + } + if( (srchFlags & SRCH_FORUM)!=0 ){ + if( search_screen(SRCH_FORUM, 0) ){ + style_submenu_element("Recent Threads","%R/forum"); + style_finish_page(); + return; + } + } + iLimit = atoi(PD("n","25")); + iOfst = atoi(PD("x","0")); + iCnt = 0; + if( db_table_exists("repository","forumpost") ){ + db_prepare(&q, + "WITH thread(age,duration,cnt,root,last) AS (" + " SELECT" + " julianday('now') - max(fmtime)," + " max(fmtime) - min(fmtime)," + " sum(fprev IS NULL)," + " froot," + " (SELECT fpid FROM forumpost AS y" + " WHERE y.froot=x.froot %s" + " ORDER BY y.fmtime DESC LIMIT 1)" + " FROM forumpost AS x" + " WHERE %s" + " GROUP BY froot" + " ORDER BY 1 LIMIT %d OFFSET %d" + ")" + "SELECT" + " thread.age," /* 0 */ + " thread.duration," /* 1 */ + " thread.cnt," /* 2 */ + " blob.uuid," /* 3 */ + " substr(event.comment,instr(event.comment,':')+1)," /* 4 */ + " thread.last" /* 5 */ + " FROM thread, blob, event" + " WHERE blob.rid=thread.last" + " AND event.objid=thread.last" + " ORDER BY 1;", + g.perm.ModForum ? "" : "AND y.fpid NOT IN private" /*safe-for-%s*/, + g.perm.ModForum ? "true" : "fpid NOT IN private" /*safe-for-%s*/, + iLimit+1, iOfst + ); + while( db_step(&q)==SQLITE_ROW ){ + char *zAge = human_readable_age(db_column_double(&q,0)); + int nMsg = db_column_int(&q, 2); + const char *zUuid = db_column_text(&q, 3); + const char *zTitle = db_column_text(&q, 4); + if( iCnt==0 ){ + if( iOfst>0 ){ + @

Threads at least %s(zAge) old

+ }else{ + @

Most recent threads

+ } + @
+ if( iOfst>0 ){ + if( iOfst>iLimit ){ + @ + }else{ + @ + } + } + } + iCnt++; + if( iCnt>iLimit ){ + @ + fossil_free(zAge); + break; + } + @ + @ + @ + }else{ + char *zDuration = human_readable_age(db_column_double(&q,1)); + @ %d(nMsg) posts spanning %h(zDuration) + fossil_free(zDuration); + } + @ + fossil_free(zAge); + } + db_finalize(&q); + } + if( iCnt>0 ){ + @
\ + @ %z(href("%R/forum?x=%d&n=%d",iOfst-iLimit,iLimit))\ + @ ↑ Newer...
%z(href("%R/forum?n=%d",iLimit))\ + @ ↑ Newer...
\ + @ %z(href("%R/forum?x=%d&n=%d",iOfst+iLimit,iLimit))\ + @ ↓ Older...
%h(zAge) ago%z(href("%R/forumpost/%S",zUuid))%h(zTitle)\ + if( g.perm.ModForum && moderation_pending(db_column_int(&q,5)) ){ + @ \ + @ Awaiting Moderator Approval
+ } + if( nMsg<2 ){ + @ no replies
+ }else{ + @

No forum posts found

+ } + style_finish_page(); +} ADDED src/forum.js Index: src/forum.js ================================================================== --- /dev/null +++ src/forum.js @@ -0,0 +1,19 @@ +(function(){ + function absoluteY(obj){ + var top = 0; + if( obj.offsetParent ){ + do{ + top += obj.offsetTop; + }while( obj = obj.offsetParent ); + } + return top; + } + var x = document.getElementsByClassName('forumSel'); + if(x[0]){ + var w = window.innerHeight; + var h = x[0].scrollHeight; + var y = absoluteY(x[0]); + if( w>h ) y = y + (h-w)/2; + if( y>0 ) window.scrollTo(0, y); + } +})(); ADDED src/fossil.bootstrap.js Index: src/fossil.bootstrap.js ================================================================== --- /dev/null +++ src/fossil.bootstrap.js @@ -0,0 +1,294 @@ +"use strict"; +(function () { + /* CustomEvent polyfill, courtesy of Mozilla: + https://developer.mozilla.org/en-US/docs/Web/API/CustomEvent/CustomEvent + */ + if(typeof window.CustomEvent === "function") return false; + window.CustomEvent = function(event, params) { + if(!params) params = {bubbles: false, cancelable: false, detail: null}; + const evt = document.createEvent('CustomEvent'); + evt.initCustomEvent( event, !!params.bubbles, !!params.cancelable, params.detail ); + return evt; + }; +})(); +(function(global){ + /* Bootstrapping bits for the global.fossil object. Must be loaded + after style.c:builtin_emit_script_fossil_bootstrap() has + initialized that object. + */ + + const F = global.fossil; + + /** + Returns the current time in something approximating + ISO-8601 format. + */ + const timestring = function f(){ + if(!f.rx1){ + f.rx1 = /\.\d+Z$/; + } + const d = new Date(); + return d.toISOString().replace(f.rx1,'').split('T').join(' '); + }; + + /** Returns the local time string of Date object d, defaulting + to the current time. */ + const localTimeString = function ff(d){ + if(!ff.pad){ + ff.pad = (x)=>(''+x).length>1 ? x : '0'+x; + } + d || (d = new Date()); + return [ + d.getFullYear(),'-',ff.pad(d.getMonth()+1/*sigh*/), + '-',ff.pad(d.getDate()), + ' ',ff.pad(d.getHours()),':',ff.pad(d.getMinutes()), + ':',ff.pad(d.getSeconds()) + ].join(''); + }; + + /* + ** By default fossil.message() sends its arguments console.debug(). If + ** fossil.message.targetElement is set, it is assumed to be a DOM + ** element, its innerText gets assigned to the concatenation of all + ** arguments (with a space between each), and the CSS 'error' class is + ** removed from the object. Pass it a falsy value to clear the target + ** element. + ** + ** Returns this object. + */ + F.message = function f(msg){ + const args = Array.prototype.slice.call(arguments,0); + const tgt = f.targetElement; + if(args.length) args.unshift( + localTimeString()+':' + //timestring(),'UTC:' + ); + if(tgt){ + tgt.classList.remove('error'); + tgt.innerText = args.join(' '); + } + else{ + if(args.length){ + args.unshift('Fossil status:'); + console.debug.apply(console,args); + } + } + return this; + }; + /* + ** Set default message.targetElement to #fossil-status-bar, if found. + */ + F.message.targetElement = + document.querySelector('#fossil-status-bar'); + if(F.message.targetElement){ + F.message.targetElement.addEventListener( + 'dblclick', ()=>F.message(), false + ); + } + /* + ** By default fossil.error() sends its first argument to + ** console.error(). If fossil.message.targetElement (yes, + ** fossil.message) is set, it adds the 'error' CSS class to + ** that element and sets its content as defined for message(). + ** + ** Returns this object. + */ + F.error = function f(msg){ + const args = Array.prototype.slice.call(arguments,0); + const tgt = F.message.targetElement; + args.unshift(timestring(),'UTC:'); + if(tgt){ + tgt.classList.add('error'); + tgt.innerText = args.join(' '); + } + else{ + args.unshift('Fossil error:'); + console.error.apply(console,args); + } + return this; + }; + + /** + For each property in the given object, its key/value are encoded + for use as URL parameters and the combined string is + returned. e.g. {a:1,b:2} encodes to "a=1&b=2". + + If the 2nd argument is an array, each encoded element is appended + to that array and tgtArray is returned. The above object would be + appended as ['a','=','1','&','b','=','2']. This form is used for + building up parameter lists before join('')ing the array to create + the result string. + + If passed a truthy 3rd argument, it does not really encode each + component - it simply concatenates them together. + */ + F.encodeUrlArgs = function(obj,tgtArray,fakeEncode){ + if(!obj) return ''; + const a = (tgtArray instanceof Array) ? tgtArray : [], + enc = fakeEncode ? (x)=>x : encodeURIComponent; + let k, i = 0; + for( k in obj ){ + if(i++) a.push('&'); + a.push(enc(k),'=',enc(obj[k])); + } + return a===tgtArray ? a : a.join(''); + }; + /** + repoUrl( repoRelativePath [,urlParams] ) + + Creates a URL by prepending this.rootPath to the given path + (which must be relative from the top of the site, without a + leading slash). If urlParams is a string, it must be + paramters encoded in the form "key=val&key2=val2..." WITHOUT + a leading '?'. If it's an object, all of its properties get + appended to the URL in that form. + */ + F.repoUrl = function(path,urlParams){ + if(!urlParams) return this.rootPath+path; + const url=[this.rootPath,path]; + url.push('?'); + if('string'===typeof urlParams) url.push(urlParams); + else if(urlParams && 'object'===typeof urlParams){ + this.encodeUrlArgs(urlParams, url); + } + return url.join(''); + }; + + /** + Returns true if v appears to be a plain object. + */ + F.isObject = function(v){ + return v && + (v instanceof Object) && + ('[object Object]' === Object.prototype.toString.apply(v) ); + }; + + /** + For each object argument, this function combines their properties, + using a last-one-wins policy, and returns a new object with the + combined properties. If passed a single object, it effectively + shallowly clones that object. + */ + F.mergeLastWins = function(){ + var k, o, i; + const n = arguments.length, rc={}; + for(i = 0; i < n; ++i){ + if(!F.isObject(o = arguments[i])) continue; + for( k in o ){ + if(o.hasOwnProperty(k)) rc[k] = o[k]; + } + } + return rc; + }; + + /** + Expects to be passed as hash code as its first argument. It + returns a "shortened" form of hash, with a length which depends + on the 2nd argument: truthy = fossil.config.hashDigitsUrl, falsy + = fossil.config.hashDigits, number == that many digits. The + fossil.config values are derived from the 'hash-digits' + repo-level config setting or the + FOSSIL_HASH_DIGITS_URL/FOSSIL_HASH_DIGITS compile-time options. + + If its first arugment is a non-string, that value is returned + as-is. + */ + F.hashDigits = function(hash,forUrl){ + const n = ('number'===typeof forUrl) + ? forUrl : F.config[forUrl ? 'hashDigitsUrl' : 'hashDigits']; + return ('string'==typeof hash ? hash.substr( + 0, n + ) : hash); + }; + + /** + Convenience wrapper which adds an onload event listener to the + window object. Returns this. + */ + F.onPageLoad = function(callback){ + window.addEventListener('load', callback, false); + return this; + }; + + /** + Convenience wrapper which adds a DOMContentLoadedevent listener + to the window object. Returns this. + */ + F.onDOMContentLoaded = function(callback){ + window.addEventListener('DOMContentLoaded', callback, false); + return this; + }; + + /** + Assuming name is a repo-style filename, this function returns + a shortened form of that name: + + .../LastDirectoryPart/FilenamePart + + If the name has 0-1 directory parts, it is returned as-is. + + Design note: in practice it is generally not helpful to elide the + *last* directory part because embedded docs (in particular) often + include x/y/index.md and x/z/index.md, both of which would be + shortened to something like x/.../index.md. + */ + F.shortenFilename = function(name){ + const a = name.split('/'); + if(a.length<=2) return name; + while(a.length>2) a.shift(); + return '.../'+a.join('/'); + }; + + /** + Adds a listener for fossil-level custom events. Events are + delivered to their callbacks as CustomEvent objects with a + 'detail' property holding the event's app-level data. + + The exact events fired differ by page, and not all pages trigger + events. + + Pedantic sidebar: the custom event's 'target' property is an + unspecified DOM element. Clients must not rely on its value being + anything specific or useful. + + Returns this object. + */ + F.page.addEventListener = function f(eventName, callback){ + if(!f.proxy){ + f.proxy = document.createElement('span'); + } + f.proxy.addEventListener(eventName, callback, false); + return this; + }; + + /** + Internal. Dispatches a new CustomEvent to all listeners + registered for the given eventName via + fossil.page.addEventListener(), passing on a new CustomEvent with + a 'detail' property equal to the 2nd argument. Returns this + object. + */ + F.page.dispatchEvent = function(eventName, eventDetail){ + if(this.addEventListener.proxy){ + try{ + this.addEventListener.proxy.dispatchEvent( + new CustomEvent(eventName,{detail: eventDetail}) + ); + }catch(e){ + console.error(eventName,"event listener threw:",e); + } + } + return this; + }; + + /** + Sets the innerText of the page's TITLE tag to + the given text and returns this object. + */ + F.page.setPageTitle = function(title){ + const t = document.querySelector('title'); + if(t) t.innerText = title; + return this; + }; + +})(window); ADDED src/fossil.confirmer.js Index: src/fossil.confirmer.js ================================================================== --- /dev/null +++ src/fossil.confirmer.js @@ -0,0 +1,331 @@ +"use strict"; +/************************************************************** +Confirmer is a utility which provides an alternative to confirmation +dialog boxes and "check this checkbox to confirm action" widgets. It +acts by modifying a button to require two clicks within a certain +time, with the second click acting as a confirmation of the first. If +the second click does not come within a specified timeout then the +action is not confirmed. + +Usage: + +fossil.confirmer(domElement, options); + +Usually: + +fossil.confirmer(element, { + onconfirm: function(){ + // this === the element. + // Do whatever the element would normally do when + // clicked. + } +}); + +Options: + + .initialText = initial text of the element. Defaults to the result + of the element's .value (for INPUT tags) or innerHTML (for + everything else). After the timeout/tick count expires, or if the + user confirms the operation, the element's text is re-set to this + value. + + .confirmText = text to show when in "confirm mode". + Default=("Confirm: "+initialText), or something similar. + + .timeout = Number of milliseconds to wait for confirmation. + Default=3000. Alternately, use a combination of .ticks and + .ticktime. + + .onconfirm = function to call when clicked in confirm mode. Default + = undefined. The function's "this" is the DOM element to which + the countdown applies. + + .ontimeout = function to call when confirm is not issued. Default = + undefined. The function's "this" is the DOM element to which the + countdown applies. + + .onactivate = function to call when item is clicked, but only if the + item is not currently in countdown mode. This is called (and must + return) before the countdown starts. The function's "this" is the + DOM element to which the countdown applies. This can be used, e.g., + to change the element's text or CSS classes. + + .classInitial = optional CSS class string (default='') which is + added to the element during its "initial" state (the state it is in + when it is not waiting on a timeout). When the target is activated + (waiting on a timeout) this class is removed. In the case of a + timeout, this class is added *before* the .ontimeout handler is + called. + + .classWaiting = optional CSS class string (default='') which is + added to the target when it is waiting on a timeout. When the target + leaves timeout-wait mode, this class is removed. When timeout-wait + mode is entered, this class is added *before* the .onactivate + handler is called. + + .ticktime = a number of ms to wait per tick (see the next item). + Default = 1000. + + .ticks = a number of "ticks" to wait, as an alternative to .timeout. + When this mode is active, the ontick callback will be triggered + immediately before each tick, including the first one. If both + .ticks and .timeout are set, only one will be used, but which one is + unspecified. If passed a ticks value with a truncated integer value + of 0 or less, it will throw an exception (e.g. that also applies if + it's passed 0.5). + + .ontick = when using .ticks, this callback is passed the current + tick number before each tick, and its "this" is the target + element. On each subsequent call, the tick count will be reduced by + 1, and it is passed 0 after the final tick expires or when the + action has been confirmed, immediately before the onconfirm or + ontimeout callback. The intention of the callback is to update the + label of the target element. If .ticks is set but .ontick is not + then a default implementation is used which updates the element with + the .confirmText, prepending a countdown to it. + + .pinSize = if true AND confirmText is set, calculate the larger of + the element's original and confirmed size and pin it to the larger + of those sizes to avoid layout reflows when confirmation is + running. The pinning is implemented by setting its minWidth and + maxWidth style properties to the same value. This does not work if + the element text is updated dynamically via ontick(). This ONLY + works if the element is in the DOM and is not hidden (e.g. via + display:none) at the time this routine is called, otherwise we + cannot calculate its size. If the element needs to be hidden, hide + it after initializing the confirmer. + + .debug = boolean. If truthy, it sends some debug output to the dev + console to track what it's doing. + +Various notes: + +- To change the default option values, modify the + fossil.confirmer.defaultOpts object. + +- Exceptions triggered via the callbacks are caught and emitted to the + dev console if the debug option is enabled, but are otherwise + ignored. + +- Due to the nature of multi-threaded code, it is potentially possible + that confirmation and timeout actions BOTH happen if the user + triggers the associated action at "just the right millisecond" + before the timeout is triggered. + +TODO: + +- Add an invert option which activates if the timeout is reached and +"times out" if the element is clicked again. e.g. a button which says +"Saving..." and cancels the op if it's clicked again, else it saves +after X time/ticks. + +- Internally we save/restore the initial text of non-INPUT elements +using a relatively expensive bit of DOMParser hoop-jumping. We +"should" instead move their child nodes aside (into an internal +out-of-DOM element) and restore them as needed. + +Terse Change history: + +- 20200811 + - Added pinSize option. + +- 20200507: + - Add a tick-based countdown in order to more easily support + updating the target element with the countdown. + +- 20200506: + - Ported from jQuery to plain JS. + +- 20181112: + - extended to support certain INPUT elements. + - made default opts configurable. + +- 20070717: initial jQuery-based impl. +*/ +(function(F/*the fossil object*/){ + F.confirmer = function f(elem,opt){ + const dbg = opt.debug + ? function(){console.debug.apply(console,arguments)} + : function(){}; + dbg("confirmer opt =",opt); + if(!f.Holder){ + f.isInput = (e)=>/^(input|textarea)$/i.test(e.nodeName); + f.Holder = function(target,opt){ + const self = this; + this.target = target; + this.opt = opt; + this.timerID = undefined; + this.state = this.states.initial; + const isInput = f.isInput(target); + const updateText = function(msg){ + if(isInput) target.value = msg; + else{ + /* Jump through some hoops to avoid assigning to innerHTML... */ + const newNode = new DOMParser().parseFromString(msg, 'text/html'); + let childs = newNode.documentElement.querySelector('body'); + childs = childs ? Array.prototype.slice.call(childs.childNodes, 0) : []; + target.innerText = ''; + childs.forEach((e)=>target.appendChild(e)); + } + } + const formatCountdown = (txt, number) => txt + " ["+number+"]"; + if(opt.pinSize && opt.confirmText){ + /* Try to pin the element's width the the greater of its + current width or its waiting-on-confirmation width + to avoid layout reflow when it's activated. */ + const digits = (''+(opt.timeout/1000 || opt.ticks)).length; + const lblLong = formatCountdown(opt.confirmText, "00000000".substr(0,digits+1)); + const w1 = parseInt(target.getBoundingClientRect().width); + updateText(lblLong); + const w2 = parseInt(target.getBoundingClientRect().width); + if(w1 || w2){ + /* If target is not in visible part of the DOM, those values may be 0. */ + target.style.minWidth = target.style.maxWidth = (w1>w2 ? w1 : w2)+"px"; + } + } + updateText(this.opt.initialText); + if(this.opt.ticks && !this.opt.ontick){ + this.opt.ontick = function(tick){ + updateText(formatCountdown(self.opt.confirmText,tick)); + }; + } + this.setClasses(false); + this.doTimeout = function() { + if(this.timerID){ + clearTimeout( this.timerID ); + delete this.timerID; + } + if( this.state != this.states.waiting ) { + // it was already confirmed + return; + } + this.setClasses( false ); + this.state = this.states.initial; + dbg("Timeout triggered."); + if( this.opt.ontick ){ + try{this.opt.ontick.call(this.target, 0)} + catch(e){dbg("ontick EXCEPTION:",e)} + } + if( this.opt.ontimeout ) { + try{this.opt.ontimeout.call(this.target)} + catch(e){dbg("ontimeout EXCEPTION:",e)} + } + updateText(this.opt.initialText); + }; + target.addEventListener( + 'click', function(){ + switch( self.state ) { + case( self.states.waiting ): + /* Cancel the wait on confirmation */ + if( undefined !== self.timerID ){ + clearTimeout( self.timerID ); + delete self.timerID; + } + self.state = self.states.initial; + self.setClasses( false ); + dbg("Confirmed"); + if( self.opt.ontick ){ + try{self.opt.ontick.call(self.target,0)} + catch(e){dbg("ontick EXCEPTION:",e)} + } + if( self.opt.onconfirm ){ + try{self.opt.onconfirm.call(self.target)} + catch(e){dbg("onconfirm EXCEPTION:",e)} + } + updateText(self.opt.initialText); + break; + case( self.states.initial ): + /* Enter the waiting-on-confirmation state... */ + if(self.opt.ticks) self.opt.currentTick = self.opt.ticks; + self.setClasses( true ); + self.state = self.states.waiting; + updateText( self.opt.confirmText ); + if( self.opt.onactivate ) self.opt.onactivate.call( self.target ); + if( self.opt.ontick ) self.opt.ontick.call(self.target, self.opt.currentTick); + if(self.opt.timeout){ + dbg("Waiting "+self.opt.timeout+"ms on confirmation..."); + self.timerID = + setTimeout(()=>self.doTimeout(),self.opt.timeout ); + }else if(self.opt.ticks){ + dbg("Waiting on confirmation for "+self.opt.ticks + +" ticks of "+self.opt.ticktime+"ms each..."); + self.timerID = + setInterval(function(){ + if(0===--self.opt.currentTick) self.doTimeout(); + else{ + try{self.opt.ontick.call(self.target, + self.opt.currentTick)} + catch(e){dbg("ontick EXCEPTION:",e)} + } + },self.opt.ticktime); + } + break; + default: // can't happen. + break; + } + }, false + ); + }; + f.Holder.prototype = { + states:{initial: 0, waiting: 1}, + setClasses: function(activated) { + if(activated) { + if( this.opt.classWaiting ) { + this.target.classList.add( this.opt.classWaiting ); + } + if( this.opt.classInitial ) { + this.target.classList.remove( this.opt.classInitial ); + } + }else{ + if( this.opt.classInitial ) { + this.target.classList.add( this.opt.classInitial ); + } + if( this.opt.classWaiting ) { + this.target.classList.remove( this.opt.classWaiting ); + } + } + } + }; + }/*static init*/ + opt = F.mergeLastWins(f.defaultOpts,{ + initialText: ( + f.isInput(elem) ? elem.value : elem.innerHTML + ) || "PLEASE SET .initialText" + },opt); + if(!opt.confirmText){ + opt.confirmText = "Confirm: "+opt.initialText; + } + if(opt.ticks){ + delete opt.timeout; + opt.ticks = 0 | opt.ticks /* ensure it's an integer */; + if(opt.ticks<=0){ + throw new Error("ticks must be >0"); + } + if(opt.ticktime <= 0) opt.ticktime = 1000; + }else{ + delete opt.ontick; + delete opt.ticks; + } + new f.Holder(elem,opt); + return this; + }; + /** + The default options for initConfirmer(). Tweak them to set the + defaults. A couple of them (initialText and confirmText) are + dynamically-generated, and can't reasonably be set in the + defaults. Some, like ticks, cannot be set here because that would + end up indirectly replacing non-tick timeouts with ticks. + */ + F.confirmer.defaultOpts = { + timeout:undefined, + ticks: 3, + ticktime: 998/*not *quite* 1000*/, + onconfirm: undefined, + ontimeout: undefined, + onactivate: undefined, + classInitial: '', + classWaiting: '', + debug: false + }; + +})(window.fossil); ADDED src/fossil.copybutton.js Index: src/fossil.copybutton.js ================================================================== --- /dev/null +++ src/fossil.copybutton.js @@ -0,0 +1,128 @@ +(function(F/*fossil object*/){ + /** + A basic API for creating and managing a copy-to-clipboard button. + + Requires: fossil.bootstrap, fossil.dom + */ + const D = F.dom; + + /** + Initializes element e as a copy button using the given options + object. + + The first argument may be a DOM element or a string (CSS selector + suitable for use with document.querySelector()). + + Options: + + .copyFromElement: DOM element + + .copyFromId: DOM element ID + + One of copyFromElement or copyFromId must be provided, but copyFromId + may optionally be provided via e.dataset.copyFromId. + + .extractText: optional callback which is triggered when the copy + button is clicked. I tmust return the text to copy to the + clipboard. The default is to extract it from the copy-from + element, using its [value] member, if it has one, else its + [innerText]. A client-provided callback may use any data source + it likes, so long as it's synchronous. If this function returns a + falsy value then the clipboard is not modified. This function is + called with the fully expanded/resolved options object as its + "this" (that's a different instance than the one passed to this + function!). + + .cssClass: optional CSS class, or list of classes, to apply to e. + + .style: optional object of properties to copy directly into + e.style. + + .oncopy: an optional callback function which is added as an event + listener for the 'text-copied' event (see below). There is + functionally no difference from setting this option or adding a + 'text-copied' event listener to the element, and this option is + considered to be a convenience form of that. For the sake of + framework-level consistency, the default value is a callback + which passes the copy button to fossil.dom.flashOnce(). + + Note that this function's own defaultOptions object holds default + values for some options. Any changes made to that object affect + any future calls to this function. + + Be aware that clipboard functionality might or might not be + available in any given environment. If this button appears to + have no effect, that may be because it is not enabled/available + in the current platform. + + The copy button emits custom event 'text-copied' after it has + successfully copied text to the clipboard. The event's "detail" + member is an object with a "text" property holding the copied + text. Other properties may be added in the future. The event is + not fired if copying to the clipboard fails (e.g. is not + available in the current environment). + + As a special case, the copy button's click handler is suppressed + (becomes a no-op) for as long as the element has the CSS class + "disabled". This allows elements which cannot be disabled via + HTML attributes, e.g. a SPAN, to act as a copy button while still + providing a way to disable them. + + Returns the copy-initialized element. + + Example: + + const button = fossil.copyButton('#my-copy-button', { + copyFromId: 'some-other-element-id' + }); + button.addEventListener('text-copied',function(ev){ + fossil.dom.flashOnce(ev.target); + console.debug("Copied text:",ev.detail.text); + }); + */ + F.copyButton = function f(e, opt){ + if('string'===typeof e){ + e = document.querySelector(e); + } + opt = F.mergeLastWins(f.defaultOptions, opt); + if(opt.cssClass){ + D.addClass(e, opt.cssClass); + } + var srcId, srcElem; + if(opt.copyFromElement){ + srcElem = opt.copyFromElement; + }else if((srcId = opt.copyFromId || e.dataset.copyFromId)){ + srcElem = document.querySelector('#'+srcId); + } + const extract = opt.extractText || ( + undefined===srcElem.value ? ()=>srcElem.innerText : ()=>srcElem.value + ); + D.copyStyle(e, opt.style); + e.addEventListener( + 'click', + function(ev){ + ev.preventDefault(); + ev.stopPropagation(); + if(e.classList.contains('disabled')) return; + const txt = extract.call(opt); + if(txt && D.copyTextToClipboard(txt)){ + e.dispatchEvent(new CustomEvent('text-copied',{ + detail: {text: txt} + })); + } + }, + false + ); + if('function' === typeof opt.oncopy){ + e.addEventListener('text-copied', opt.oncopy, false); + } + return e; + }; + + F.copyButton.defaultOptions = { + cssClass: 'copy-button', + oncopy: D.flashOnce.eventHandler, + style: {/*properties copied as-is into element.style*/} + }; + +})(window.fossil); ADDED src/fossil.dom.js Index: src/fossil.dom.js ================================================================== --- /dev/null +++ src/fossil.dom.js @@ -0,0 +1,944 @@ +"use strict"; +(function(F/*fossil object*/){ + /** + A collection of HTML DOM utilities to simplify, a bit, using the + DOM API. It is focused on manipulation of the DOM, but one of its + core mantras is "No innerHTML." Using innerHTML in this code, in + particular assigning to it, is absolutely verboten. + */ + const argsToArray = (a)=>Array.prototype.slice.call(a,0); + const isArray = (v)=>v instanceof Array; + + const dom = { + create: function(elemType){ + return document.createElement(elemType); + }, + createElemFactory: function(eType){ + return function(){ + return document.createElement(eType); + }; + }, + remove: function(e){ + if(e.forEach){ + e.forEach( + (x)=>x.parentNode.removeChild(x) + ); + }else{ + e.parentNode.removeChild(e); + } + return e; + }, + /** + Removes all child DOM elements from the given element + and returns that element. + + If e has a forEach method (is an array or DOM element + collection), this function instead clears each element in the + collection. May be passed any number of arguments, each of + which must be a DOM element or a container of DOM elements with + a forEach() method. Returns its first argument. + */ + clearElement: function f(e){ + if(!f.each){ + f.each = function(e){ + if(e.forEach){ + e.forEach((x)=>f(x)); + return e; + } + while(e.firstChild) e.removeChild(e.firstChild); + }; + } + argsToArray(arguments).forEach(f.each); + return arguments[0]; + }, + }/* dom object */; + + /** + Returns the result of splitting the given str on + a run of spaces of (\s*,\s*). + */ + dom.splitClassList = function f(str){ + if(!f.rx){ + f.rx = /(\s+|\s*,\s*)/; + } + return str ? str.split(f.rx) : [str]; + }; + + dom.div = dom.createElemFactory('div'); + dom.p = dom.createElemFactory('p'); + dom.code = dom.createElemFactory('code'); + dom.pre = dom.createElemFactory('pre'); + dom.header = dom.createElemFactory('header'); + dom.footer = dom.createElemFactory('footer'); + dom.section = dom.createElemFactory('section'); + dom.span = dom.createElemFactory('span'); + dom.strong = dom.createElemFactory('strong'); + dom.em = dom.createElemFactory('em'); + /** + Returns a LABEL element. If passed an argument, + it must be an id or an HTMLElement with an id, + and that id is set as the 'for' attribute of the + label. If passed 2 arguments, the 2nd is text or + a DOM element to append to the label. + */ + dom.label = function(forElem, text){ + const rc = document.createElement('label'); + if(forElem){ + if(forElem instanceof HTMLElement){ + forElem = this.attr(forElem, 'id'); + } + dom.attr(rc, 'for', forElem); + } + if(text) this.append(rc, text); + return rc; + }; + /** + Returns an IMG element with an optional src + attribute value. + */ + dom.img = function(src){ + const e = this.create('img'); + if(src) e.setAttribute('src',src); + return e; + }; + /** + Creates and returns a new anchor element with the given + optional href and label. If label===true then href is used + as the label. + */ + dom.a = function(href,label){ + const e = this.create('a'); + if(href) e.setAttribute('href',href); + if(label) e.appendChild(dom.text(true===label ? href : label)); + return e; + }; + dom.hr = dom.createElemFactory('hr'); + dom.br = dom.createElemFactory('br'); + /** Returns a new TEXT node which contains the text of all of the + arguments appended together. */ + dom.text = function(/*...*/){ + return document.createTextNode(argsToArray(arguments).join('')); + }; + dom.button = function(label){ + const b = this.create('button'); + if(label) b.appendChild(this.text(label)); + return b; + }; + /** + Returns a TEXTAREA element. + + Usages: + + ([boolean readonly = false]) + (non-boolean rows[,cols[,readonly=false]]) + + Each of the rows/cols/readonly attributes is only set if it is + truthy. + */ + dom.textarea = function(){ + const rc = this.create('textarea'); + let rows, cols, readonly; + if(1===arguments.length){ + if('boolean'===typeof arguments[0]){ + readonly = !!arguments[0]; + }else{ + rows = arguments[0]; + } + }else if(arguments.length){ + rows = arguments[0]; + cols = arguments[1]; + readonly = arguments[2]; + } + if(rows) rc.setAttribute('rows',rows); + if(cols) rc.setAttribute('cols', cols); + if(readonly) rc.setAttribute('readonly', true); + return rc; + }; + + /** + Returns a new SELECT element. + */ + dom.select = dom.createElemFactory('select'); + + /** + Returns an OPTION element with the given value and label text + (which defaults to the value). + + Usage: + + (value[, label]) + (selectElement [,value [,label = value]]) + + Any forms taking a SELECT element append the new element to the + given SELECT element. + + If any label is falsy and the value is not then the value is used + as the label. A non-falsy label value may have any type suitable + for passing as the 2nd argument to dom.append(). + + If the value has the undefined value then it is NOT assigned as + the option element's value and no label is set unless it has a + non-undefined value. + */ + dom.option = function(value,label){ + const a = arguments; + var sel; + if(1==a.length){ + if(a[0] instanceof HTMLElement){ + sel = a[0]; + }else{ + value = a[0]; + } + }else if(2==a.length){ + if(a[0] instanceof HTMLElement){ + sel = a[0]; + value = a[1]; + }else{ + value = a[0]; + label = a[1]; + } + } + else if(3===a.length){ + sel = a[0]; + value = a[1]; + label = a[2]; + } + const o = this.create('option'); + if(undefined !== value){ + o.value = value; + this.append(o, this.text(label || value)); + }else if(undefined !== label){ + this.append(o, label); + } + if(sel) this.append(sel, o); + return o; + }; + dom.h = function(level){ + return this.create('h'+level); + }; + dom.ul = dom.createElemFactory('ul'); + /** + Creates and returns a new LI element, appending it to the + given parent argument if it is provided. + */ + dom.li = function(parent){ + const li = this.create('li'); + if(parent) parent.appendChild(li); + return li; + }; + + /** + Returns a function which creates a new DOM element of the + given type and accepts an optional parent DOM element + argument. If the function's argument is truthy, the new + child element is appended to the given parent element. + Returns the new child element. + */ + dom.createElemFactoryWithOptionalParent = function(childType){ + return function(parent){ + const e = this.create(childType); + if(parent) parent.appendChild(e); + return e; + }; + }; + + dom.table = dom.createElemFactory('table'); + dom.thead = dom.createElemFactoryWithOptionalParent('thead'); + dom.tbody = dom.createElemFactoryWithOptionalParent('tbody'); + dom.tfoot = dom.createElemFactoryWithOptionalParent('tfoot'); + dom.tr = dom.createElemFactoryWithOptionalParent('tr'); + dom.td = dom.createElemFactoryWithOptionalParent('td'); + dom.th = dom.createElemFactoryWithOptionalParent('th'); + + /** + Creates and returns a FIELDSET element, optionaly with a LEGEND + element added to it. If legendText is an HTMLElement then is is + assumed to be a LEGEND and is appended as-is, else it is assumed + (if truthy) to be a value suitable for passing to + dom.append(aLegendElement,...). + */ + dom.fieldset = function(legendText){ + const fs = this.create('fieldset'); + if(legendText){ + this.append( + fs, + (legendText instanceof HTMLElement) + ? legendText + : this.append(this.legend(legendText)) + ); + } + return fs; + }; + /** + Returns a new LEGEND legend element. The given argument, if + not falsy, is append()ed to the element (so it may be a string + or DOM element. + */ + dom.legend = function(legendText){ + const rc = this.create('legend'); + if(legendText) this.append(rc, legendText); + return rc; + }; + + /** + Appends each argument after the first to the first argument + (a DOM node) and returns the first argument. + + - If an argument is a string or number, it is transformed + into a text node. + + - If an argument is an array or has a forEach member, this + function appends each element in that list to the target + by calling its forEach() method to pass it (recursively) + to this function. + + - Else the argument assumed to be of a type legal + to pass to parent.appendChild(). + */ + dom.append = function f(parent/*,...*/){ + const a = argsToArray(arguments); + a.shift(); + for(let i in a) { + var e = a[i]; + if(isArray(e) || e.forEach){ + e.forEach((x)=>f.call(this, parent,x)); + continue; + } + if('string'===typeof e + || 'number'===typeof e + || 'boolean'===typeof e + || e instanceof Error) e = this.text(e); + parent.appendChild(e); + } + return parent; + }; + + dom.input = function(type){ + return this.attr(this.create('input'), 'type', type); + }; + /** + Returns a new CHECKBOX input element. + + Usages: + + ([boolean checked = false]) + (non-boolean value [,boolean checked]) + */ + dom.checkbox = function(value, checked){ + const rc = this.input('checkbox'); + if(1===arguments.length && 'boolean'===typeof value){ + checked = !!value; + value = undefined; + } + if(undefined !== value) rc.value = value; + if(!!checked) rc.checked = true; + return rc; + }; + /** + Returns a new RADIO input element. + + ([boolean checked = false]) + (string name [,boolean checked]) + (string name, non-boolean value [,boolean checked]) + */ + dom.radio = function(){ + const rc = this.input('radio'); + let name, value, checked; + if(1===arguments.length && 'boolean'===typeof name){ + checked = arguments[0]; + name = value = undefined; + }else if(2===arguments.length){ + name = arguments[0]; + if('boolean'===typeof arguments[1]){ + checked = arguments[1]; + }else{ + value = arguments[1]; + checked = undefined; + } + }else if(arguments.length){ + name = arguments[0]; + value = arguments[1]; + checked = arguments[2]; + } + if(name) this.attr(rc, 'name', name); + if(undefined!==value) rc.value = value; + if(!!checked) rc.checked = true; + return rc; + }; + + /** + Internal impl for addClass(), removeClass(). + */ + const domAddRemoveClass = function f(action,e){ + if(!f.rxSPlus){ + f.rxSPlus = /\s+/; + f.applyAction = function(e,a,v){ + if(!e || !v + /*silently skip empty strings/flasy + values, for user convenience*/) return; + else if(e.forEach){ + e.forEach((E)=>E.classList[a](v)); + }else{ + e.classList[a](v); + } + }; + } + var i = 2, n = arguments.length; + for( ; i < n; ++i ){ + let c = arguments[i]; + if(!c) continue; + else if(isArray(c) || + ('string'===typeof c + && c.indexOf(' ')>=0 + && (c = c.split(f.rxSPlus))) + || c.forEach + ){ + c.forEach((k)=>k ? f.applyAction(e, action, k) : false); + // ^^^ we could arguably call f(action,e,k) to recursively + // apply constructs like ['foo bar'] or [['foo'],['bar baz']]. + }else if(c){ + f.applyAction(e, action, c); + } + } + return e; + }; + + /** + Adds one or more CSS classes to one or more DOM elements. + + The first argument is a target DOM element or a list type of such elements + which has a forEach() method. Each argument + after the first may be a string or array of strings. Each + string may contain spaces, in which case it is treated as a + list of CSS classes. + + Returns e. + */ + dom.addClass = function(e,c){ + const a = argsToArray(arguments); + a.unshift('add'); + return domAddRemoveClass.apply(this, a); + }; + /** + The 'remove' counterpart of the addClass() method, taking + the same arguments and returning the same thing. + */ + dom.removeClass = function(e,c){ + const a = argsToArray(arguments); + a.unshift('remove'); + return domAddRemoveClass.apply(this, a); + }; + + /** + Toggles CSS class c on e (a single element for forEach-capable + collection of elements. Returns its first argument. + */ + dom.toggleClass = function f(e,c){ + if(e.forEach){ + e.forEach((x)=>x.classList.toggle(c)); + }else{ + e.classList.toggle(c); + } + return e; + }; + + /** + Returns true if DOM element e contains CSS class c, else + false. + */ + dom.hasClass = function(e,c){ + return (e && e.classList) ? e.classList.contains(c) : false; + }; + + /** + Each argument after the first may be a single DOM element or a + container of them with a forEach() method. All such elements are + appended, in the given order, to the dest element using + dom.append(dest,theElement). Thus the 2nd and susequent arguments + may be any type supported as the 2nd argument to that function. + + Returns dest. + */ + dom.moveTo = function(dest,e){ + const n = arguments.length; + var i = 1; + const self = this; + for( ; i < n; ++i ){ + e = arguments[i]; + this.append(dest, e); + } + return dest; + }; + /** + Each argument after the first may be a single DOM element + or a container of them with a forEach() method. For each + DOM element argument, all children of that DOM element + are moved to dest (via appendChild()). For each list argument, + each entry in the list is assumed to be a DOM element and is + appended to dest. + + dest may be an Array, in which case each child is pushed + into the array and removed from its current parent element. + + All children are appended in the given order. + + Returns dest. + */ + dom.moveChildrenTo = function f(dest,e){ + if(!f.mv){ + f.mv = function(d,v){ + if(d instanceof Array){ + d.push(v); + if(v.parentNode) v.parentNode.removeChild(v); + } + else d.appendChild(v); + }; + } + const n = arguments.length; + var i = 1; + for( ; i < n; ++i ){ + e = arguments[i]; + if(!e){ + console.warn("Achtung: dom.moveChildrenTo() passed a falsy value at argument",i,"of", + arguments,arguments[i]); + continue; + } + if(e.forEach){ + e.forEach((x)=>f.mv(dest, x)); + }else{ + while(e.firstChild){ + f.mv(dest, e.firstChild); + } + } + } + return dest; + }; + + /** + Adds each argument (DOM Elements) after the first to the + DOM immediately before the first argument (in the order + provided), then removes the first argument from the DOM. + Returns void. + + If any argument beyond the first has a forEach method, that + method is used to recursively insert the collection's + contents before removing the first argument from the DOM. + */ + dom.replaceNode = function f(old,nu){ + var i = 1, n = arguments.length; + ++f.counter; + try { + for( ; i < n; ++i ){ + const e = arguments[i]; + if(e.forEach){ + e.forEach((x)=>f.call(this,old,e)); + continue; + } + old.parentNode.insertBefore(e, old); + } + } + finally{ + --f.counter; + } + if(!f.counter){ + old.parentNode.removeChild(old); + } + }; + dom.replaceNode.counter = 0; + /** + Two args == getter: (e,key), returns value + + Three or more == setter: (e,key,val[...,keyN,valN]), returns + e. If val===null or val===undefined then the attribute is + removed. If (e) has a forEach method then this routine is applied + to each element of that collection via that method. Each pair of + keys/values is applied to all elements designated by the first + argument. + */ + dom.attr = function f(e){ + if(2===arguments.length) return e.getAttribute(arguments[1]); + const a = argsToArray(arguments); + if(e.forEach){ /* Apply to all elements in the collection */ + e.forEach(function(x){ + a[0] = x; + f.apply(f,a); + }); + return e; + } + a.shift(/*element(s)*/); + while(a.length){ + const key = a.shift(), val = a.shift(); + if(null===val || undefined===val){ + e.removeAttribute(key); + }else{ + e.setAttribute(key,val); + } + } + return e; + }; + + const enableDisable = function f(enable){ + var i = 1, n = arguments.length; + for( ; i < n; ++i ){ + let e = arguments[i]; + if(e.forEach){ + e.forEach((x)=>f(enable,x)); + }else{ + e.disabled = !enable; + } + } + return arguments[1]; + }; + + /** + Enables (by removing the "disabled" attribute) each element + (HTML DOM element or a collection with a forEach method) + and returns the first argument. + */ + dom.enable = function(e){ + const args = argsToArray(arguments); + args.unshift(true); + return enableDisable.apply(this,args); + }; + /** + Disables (by setting the "disabled" attribute) each element + (HTML DOM element or a collection with a forEach method) + and returns the first argument. + */ + dom.disable = function(e){ + const args = argsToArray(arguments); + args.unshift(false); + return enableDisable.apply(this,args); + }; + + /** + A proxy for document.querySelector() which throws if + selection x is not found. It may optionally be passed an + "origin" object as its 2nd argument, which restricts the + search to that branch of the tree. + */ + dom.selectOne = function(x,origin){ + var src = origin || document, + e = src.querySelector(x); + if(!e){ + e = new Error("Cannot find DOM element: "+x); + console.error(e, src); + throw e; + } + return e; + }; + + /** + "Blinks" the given element a single time for the given number of + milliseconds, defaulting (if the 2nd argument is falsy or not a + number) to flashOnce.defaultTimeMs. If a 3rd argument is passed + in, it must be a function, and it gets called at the end of the + asynchronous flashing processes. + + This will only activate once per element during that timeframe - + further calls will become no-ops until the blink is + completed. This routine adds a dataset member to the element for + the duration of the blink, to allow it to block multiple blinks. + + If passed 2 arguments and the 2nd is a function, it behaves as if + it were called as (arg1, undefined, arg2). + + Returns e, noting that the flash itself is asynchronous and may + still be running, or not yet started, when this function returns. + */ + dom.flashOnce = function f(e,howLongMs,afterFlashCallback){ + if(e.dataset.isBlinking){ + return; + } + if(2===arguments.length && 'function' ===typeof howLongMs){ + afterFlashCallback = howLongMs; + howLongMs = f.defaultTimeMs; + } + if(!howLongMs || 'number'!==typeof howLongMs){ + howLongMs = f.defaultTimeMs; + } + e.dataset.isBlinking = true; + const transition = e.style.transition; + e.style.transition = "opacity "+howLongMs+"ms ease-in-out"; + const opacity = e.style.opacity; + e.style.opacity = 0; + setTimeout(function(){ + e.style.transition = transition; + e.style.opacity = opacity; + delete e.dataset.isBlinking; + if(afterFlashCallback) afterFlashCallback(); + }, howLongMs); + return e; + }; + dom.flashOnce.defaultTimeMs = 400; + /** + A DOM event handler which simply passes event.target + to dom.flashOnce(). + */ + dom.flashOnce.eventHandler = (event)=>dom.flashOnce(event.target) + + /** + Attempts to copy the given text to the system clipboard. Returns + true if it succeeds, else false. + */ + dom.copyTextToClipboard = function(text){ + if( window.clipboardData && window.clipboardData.setData ){ + window.clipboardData.setData('Text',text); + return true; + }else{ + const x = document.createElement("textarea"); + x.style.position = 'fixed'; + x.value = text; + document.body.appendChild(x); + x.select(); + var rc; + try{ + document.execCommand('copy'); + rc = true; + }catch(err){ + rc = false; + }finally{ + document.body.removeChild(x); + } + return rc; + } + }; + + /** + Copies all properties from the 2nd argument (a plain object) into + the style member of the first argument (DOM element or a + forEach-capable list of elements). If the 2nd argument is falsy + or empty, this is a no-op. + + Returns its first argument. + */ + dom.copyStyle = function f(e, style){ + if(e.forEach){ + e.forEach((x)=>f(x, style)); + return e; + } + if(style){ + let k; + for(k in style){ + if(style.hasOwnProperty(k)) e.style[k] = style[k]; + } + } + return e; + }; + + /** + Given a DOM element, this routine measures its "effective + height", which is the bounding top/bottom range of this element + and all of its children, recursively. For some DOM structure + cases, a parent may have a reported height of 0 even though + children have non-0 sizes. + + Returns 0 if !e or if the element really has no height. + */ + dom.effectiveHeight = function f(e){ + if(!e) return 0; + if(!f.measure){ + f.measure = function callee(e, depth){ + if(!e) return; + const m = e.getBoundingClientRect(); + if(0===depth){ + callee.top = m.top; + callee.bottom = m.bottom; + }else{ + callee.top = m.top ? Math.min(callee.top, m.top) : callee.top; + callee.bottom = Math.max(callee.bottom, m.bottom); + } + Array.prototype.forEach.call(e.children,(e)=>callee(e,depth+1)); + if(0===depth){ + //console.debug("measure() height:",e.className, callee.top, callee.bottom, (callee.bottom - callee.top)); + f.extra += callee.bottom - callee.top; + } + return f.extra; + }; + } + f.extra = 0; + f.measure(e,0); + return f.extra; + }; + + /** + Parses a string as HTML. + + Usages: + + Array (htmlString) + DOMElement (DOMElement target, htmlString) + + The first form parses the string as HTML and returns an Array of + all elements parsed from it. If string is falsy then it returns + an empty array. + + The second form parses the HTML string and appends all elements + to the given target element using dom.append(), then returns the + first argument. + + Caveats: + + - It expects a partial HTML document as input, not a full HTML + document with a HEAD and BODY tags. Because of how DOMParser + works, only children of the parsed document's (virtual) body are + acknowledged by this routine. + */ + dom.parseHtml = function(){ + let childs, string, tgt; + if(1===arguments.length){ + string = arguments[0]; + }else if(2==arguments.length){ + tgt = arguments[0]; + string = arguments[1]; + } + if(string){ + const newNode = new DOMParser().parseFromString(string, 'text/html'); + childs = newNode.documentElement.querySelector('body'); + childs = childs ? Array.prototype.slice.call(childs.childNodes, 0) : []; + /* ^^^ we need a clone of the list because reparenting them + modifies a NodeList they're in. */ + }else{ + childs = []; + } + return tgt ? this.moveTo(tgt, childs) : childs; + }; + + /** + Sets up pseudo-automatic content preview handling between a + source element (typically a TEXTAREA) and a target rendering + element (typically a DIV). The selector argument must be one of: + + - A single DOM element + - A collection of DOM elements with a forEach method. + - A CSS selector + + Each element in the collection must have the following data + attributes: + + - data-f-preview-from: is either a DOM element id, WITH a leading + '#' prefix, or the name of a method (see below). If it's an ID, + the DOM element must support .value to get the content. + + - data-f-preview-to: the DOM element id of the target "previewer" + element, WITH a leading '#', or the name of a method (see below). + + - data-f-preview-via: the name of a method (see below). + + - OPTIONAL data-f-preview-as-text: a numeric value. Explained below. + + Each element gets a click handler added to it which does the + following: + + 1) Reads the content from its data-f-preview-from element or, if + that property refers to a method, calls the method without + arguments and uses its result as the content. + + 2) Passes the content to + methodNamespace[f-data-post-via](content,callback). f-data-post-via + is responsible for submitting the preview HTTP request, including + any parameters the request might require. When the response + arrives, it must pass the content of the response to its 2nd + argument, an auto-generated callback installed by this mechanism + which... + + 3) Assigns the response text to the data-f-preview-to element or + passes it to the function + methodNamespace[f-data-preview-to](content), as appropriate. If + data-f-preview-to is a DOM element and data-f-preview-as-text is + '0' (the default) then the target elements contents are replaced + with the given content as HTML, else the content is assigned to + the target's textContent property. (Note that this routine uses + DOMParser, rather than assignment to innerHTML, to apply + HTML-format content.) + + The methodNamespace (2nd argument) defaults to fossil.page, and + any method-name data properties, e.g. data-f-preview-via and + potentially data-f-preview-from/to, must be a single method name, + not a property-access-style string. e.g. "myPreview" is legal but + "foo.myPreview" is not (unless, of course, the method is actually + named "foo.myPreview" (which is legal but would be + unconventional)). All such methods are called with + methodNamespace as their "this". + + An example... + + First an input button: + + + + And a sample data-f-preview-via method: + + fossil.page.myPreview = function(content,callback){ + const fd = new FormData(); + fd.append('foo', ...); + fossil.fetch('preview_forumpost',{ + payload: fd, + onload: callback, + onerror: (e)=>{ // only if app-specific handling is needed + fossil.fetch.onerror(e); // default impl + ... any app-specific error reporting ... + } + }); + }; + + Then connect the parts with: + + fossil.connectPagePreviewers('#test-preview-connector'); + + Note that the data-f-preview-from, data-f-preview-via, and + data-f-preview-to selector are not resolved until the button is + actually clicked, so they need not exist in the DOM at the + instant when the connection is set up, so long as they can be + resolved when the preview-refreshing element is clicked. + + Maintenance reminder: this method is not strictly part of + fossil.dom, but is in its file because it needs access to + dom.parseHtml() to avoid an innerHTML assignment and all code + which uses this routine also needs fossil.dom. + */ + F.connectPagePreviewers = function f(selector,methodNamespace){ + if('string'===typeof selector){ + selector = document.querySelectorAll(selector); + }else if(!selector.forEach){ + selector = [selector]; + } + if(!methodNamespace){ + methodNamespace = F.page; + } + selector.forEach(function(e){ + e.addEventListener( + 'click', function(r){ + const eTo = '#'===e.dataset.fPreviewTo[0] + ? document.querySelector(e.dataset.fPreviewTo) + : methodNamespace[e.dataset.fPreviewTo], + eFrom = '#'===e.dataset.fPreviewFrom[0] + ? document.querySelector(e.dataset.fPreviewFrom) + : methodNamespace[e.dataset.fPreviewFrom], + asText = +(e.dataset.fPreviewAsText || 0); + eTo.textContent = "Fetching preview..."; + methodNamespace[e.dataset.fPreviewVia]( + (eFrom instanceof Function ? eFrom.call(methodNamespace) : eFrom.value), + function(r){ + if(eTo instanceof Function) eTo.call(methodNamespace, r||''); + else if(!r){ + dom.clearElement(eTo); + }else if(asText){ + eTo.textContent = r; + }else{ + dom.parseHtml(dom.clearElement(eTo), r); + } + } + ); + }, false + ); + }); + return this; + }/*F.connectPagePreviewers()*/; + + return F.dom = dom; +})(window.fossil); ADDED src/fossil.fetch.js Index: src/fossil.fetch.js ================================================================== --- /dev/null +++ src/fossil.fetch.js @@ -0,0 +1,239 @@ +"use strict"; +/** + Requires that window.fossil has already been set up. +*/ +(function(namespace){ +const fossil = namespace; + /** + fetch() is an HTTP request/response mini-framework + similar (but not identical) to the not-quite-ubiquitous + window.fetch(). + + JS usages: + + fossil.fetch( URI [, onLoadCallback] ); + + fossil.fetch( URI [, optionsObject = {}] ); + + Noting that URI must be relative to the top of the repository and + should not start with a slash (if it does, it is stripped). It gets + the equivalent of "%R/" prepended to it. + + The optionsObject may be an onload callback or an object with any + of these properties: + + - onload: callback(responseData) (default = output response to the + console). In the context of the callback, the options object is + "this", noting that this call may have amended the options object + with state other than what the caller provided. + + - onerror: callback(Error object) (default = output error message + to console.error() and fossil.error()). Triggered if the request + generates any response other than HTTP 200 or suffers a connection + error or timeout while awaiting a response. In the context of the + callback, the options object is "this". + + - method: 'POST' | 'GET' (default = 'GET'). CASE SENSITIVE! + + - payload: anything acceptable by XHR2.send(ARG) (DOMString, + Document, FormData, Blob, File, ArrayBuffer), or a plain object or + array, either of which gets JSON.stringify()'d. If payload is set + then the method is automatically set to 'POST'. By default XHR2 + will set the content type based on the payload type. If an + object/array is converted to JSON, the contentType option is + automatically set to 'application/json', and if JSON.stringify() of + that value fails then the exception is propagated to this + function's caller. + + - contentType: Optional request content type when POSTing. Ignored + if the method is not 'POST'. + + - responseType: optional string. One of ("text", "arraybuffer", + "blob", or "document") (as specified by XHR2). Default = "text". + As an extension, it supports "json", which tells it that the + response is expected to be text and that it should be JSON.parse()d + before passing it on to the onload() callback. If parsing of such + an object fails, the onload callback is not called, and the + onerror() callback is passed the exception from the parsing error. + + - urlParams: string|object. If a string, it is assumed to be a + URI-encoded list of params in the form "key1=val1&key2=val2...", + with NO leading '?'. If it is an object, all of its properties get + converted to that form. Either way, the parameters get appended to + the URL before submitting the request. + + - responseHeaders: If true, the onload() callback is passed an + additional argument: a map of all of the response headers. If it's + a string value, the 2nd argument passed to onload() is instead the + value of that single header. If it's an array, it's treated as a + list of headers to return, and the 2nd argument is a map of those + header values. When a map is passed on, all of its keys are + lower-cased. When a given header is requested and that header is + set multiple times, their values are (per the XHR docs) + concatenated together with ", " between them. + + - beforesend/aftersend: optional callbacks which are called + without arguments immediately before the request is submitted + and immediately after it is received, regardless of success or + error. In the context of the callback, the options object is + the "this". These can be used to, e.g., keep track of in-flight + requests and update the UI accordingly, e.g. disabling/enabling + DOM elements. Any exceptions thrown in an beforesend are passed + to the onerror() handler and cause the fetch() to prematurely + abort. Exceptions thrown in aftersend are currently silently + ignored (feature or bug?). + + - timeout: integer in milliseconds specifying the XHR timeout + duration. Default = fossil.fetch.timeout. + + When an options object does not provide + onload/onerror/beforesend/aftersend handlers of its own, this + function falls back to defaults which are member properties of this + function with the same name, e.g. fossil.fetch.onload(). The + default onload/onerror implementations route the data through the + dev console and (for onerror()) through fossil.error(). The default + beforesend/aftersend are no-ops. Individual pages may overwrite + those members to provide default implementations suitable for the + page's use, e.g. keeping track of how many in-flight ajax requests + are pending. + + Note that this routine may add properties to the 2nd argument, so + that instance should not be kept around for later use. + + Returns this object, noting that the XHR request is asynchronous, + and still in transit (or has yet to be sent) when that happens. +*/ +fossil.fetch = function f(uri,opt){ + const F = fossil; + if(!f.onload){ + f.onload = (r)=>console.debug('fossil.fetch() XHR response:',r); + } + if(!f.onerror){ + f.onerror = function(e/*exception*/){ + console.error("fossil.fetch() XHR error:",e); + if(e instanceof Error) F.error('Exception:',e); + else F.error("Unknown error in handling of XHR request."); + }; + } + if(!f.parseResponseHeaders){ + f.parseResponseHeaders = function(h){ + const rc = {}; + if(!h) return rc; + const ar = h.trim().split(/[\r\n]+/); + ar.forEach(function(line) { + const parts = line.split(': '); + const header = parts.shift(); + const value = parts.join(': '); + rc[header.toLowerCase()] = value; + }); + return rc; + }; + } + if('/'===uri[0]) uri = uri.substr(1); + if(!opt) opt = {}; + else if('function'===typeof opt) opt={onload:opt}; + if(!opt.onload) opt.onload = f.onload; + if(!opt.onerror) opt.onerror = f.onerror; + if(!opt.beforesend) opt.beforesend = f.beforesend; + if(!opt.aftersend) opt.aftersend = f.aftersend; + let payload = opt.payload, jsonResponse = false; + if(undefined!==payload){ + opt.method = 'POST'; + if(!(payload instanceof FormData) + && !(payload instanceof Document) + && !(payload instanceof Blob) + && !(payload instanceof File) + && !(payload instanceof ArrayBuffer) + && ('object'===typeof payload + || payload instanceof Array)){ + payload = JSON.stringify(payload); + opt.contentType = 'application/json'; + } + } + const url=[f.urlTransform(uri,opt.urlParams)], + x=new XMLHttpRequest(); + if('json'===opt.responseType){ + /* 'json' is an extension to the supported XHR.responseType + list. We use it as a flag to tell us to JSON.parse() + the response. */ + jsonResponse = true; + x.responseType = 'text'; + }else{ + x.responseType = opt.responseType||'text'; + } + x.ontimeout = function(){ + try{opt.aftersend()}catch(e){/*ignore*/} + opt.onerror(new Error("XHR timeout of "+x.timeout+"ms expired.")); + }; + x.onreadystatechange = function(){ + if(XMLHttpRequest.DONE !== x.readyState) return; + try{opt.aftersend()}catch(e){/*ignore*/} + if(false && 0===x.status){ + /* For reasons unknown, we _sometimes_ trigger x.status==0 in FF + when the /chat page starts up, but not in Chrome nor in other + apps. Insofar as has been determined, this happens before a + request is actually sent and it appears to have no + side-effects on the app other than to generate an error + (i.e. no requests/responses are missing). This is a silly + workaround which may or may not bite us later. If so, it can + be removed at the cost of an unsightly console error message + in FF. */ + return; + } + if(200!==x.status){ + let err; + try{ + const j = JSON.parse(x.response); + if(j.error) err = new Error(j.error); + }catch(ex){/*ignore*/} + opt.onerror(err || new Error("HTTP response status "+x.status+".")); + return; + } + const orh = opt.responseHeaders; + let head; + if(true===orh){ + head = f.parseResponseHeaders(x.getAllResponseHeaders()); + }else if('string'===typeof orh){ + head = x.getResponseHeader(orh); + }else if(orh instanceof Array){ + head = {}; + orh.forEach((s)=>{ + if('string' === typeof s) head[s.toLowerCase()] = x.getResponseHeader(s); + }); + } + try{ + const args = [(jsonResponse && x.response) + ? JSON.parse(x.response) : x.response]; + if(head) args.push(head); + opt.onload.apply(opt, args); + }catch(e){ + opt.onerror(e); + } + }; + try{opt.beforesend()} + catch(e){ + opt.onerror(e); + return; + } + x.open(opt.method||'GET', url.join(''), true); + if('POST'===opt.method && 'string'===typeof opt.contentType){ + x.setRequestHeader('Content-Type',opt.contentType); + } + x.timeout = +opt.timeout || f.timeout; + if(undefined!==payload) x.send(payload); + else x.send(); + return this; +}; + +/** + urlTransform() must refer to a function which accepts a relative path + to the same site as fetch() is served from and an optional set of + URL parameters to pass with it (in the form a of a string + ("a=b&c=d...") or an object of key/value pairs (which it converts + to such a string), and returns the resulting URL or URI as a string. +*/ +fossil.fetch.urlTransform = (u,p)=>fossil.repoUrl(u,p); +fossil.fetch.beforesend = function(){}; +fossil.fetch.aftersend = function(){}; +fossil.fetch.timeout = 15000/* Default timeout, in ms. */; +})(window.fossil); ADDED src/fossil.info-diff.js Index: src/fossil.info-diff.js ================================================================== --- /dev/null +++ src/fossil.info-diff.js @@ -0,0 +1,14 @@ +"use strict"; +window.fossil.onPageLoad(function(){ + const F = window.fossil, D = F.dom; + const addToggle = function(diffElem){ + const sib = diffElem.previousElementSibling, + btn = sib ? D.addClass(D.checkbox(true), 'diff-toggle') : 0; + if(!sib) return; + D.append(sib,btn); + btn.addEventListener('click', function(){ + diffElem.classList.toggle('hidden'); + }, false); + }; + document.querySelectorAll('pre.udiff, table.sbsdiffcols').forEach(addToggle); +}); ADDED src/fossil.numbered-lines.js Index: src/fossil.numbered-lines.js ================================================================== --- /dev/null +++ src/fossil.numbered-lines.js @@ -0,0 +1,127 @@ +(function callee(arg){ + /* + JS counterpart of info.c:output_text_with_line_numbers() + which ties an event handler to the line numbers to allow + selection of individual lines or ranges. + + Requires: fossil.bootstrap, fossil.dom, fossil.popupwidget, + fossil.copybutton + */ + var tbl = arg || document.querySelectorAll('table.numbered-lines'); + if(tbl && !arg){ + if(tbl.length>1){ /* multiple query results: recurse */ + tbl.forEach( (t)=>callee(t) ); + return; + }else{/* single query result */ + tbl = tbl[0]; + } + } + if(!tbl) return /* no matching elements */; + const F = window.fossil, D = F.dom; + const tdLn = tbl.querySelector('td.line-numbers'); + const urlArgsRaw = (window.location.search||'?') + .replace(/&?\budc=[^&]*/,'') /* "update display prefs cookie" */ + .replace(/&?\bln=[^&]*/,'') /* inbound line number/range */ + .replace('?&','?'); + var urlArgsDecoded = urlArgsRaw; + try{urlArgsDecoded = decodeURIComponent(urlArgsRaw);} + catch{} + const lineState = { urlArgs: urlArgsDecoded, start: 0, end: 0 }; + const lineTip = new F.PopupWidget({ + style: { + cursor: 'pointer' + }, + refresh: function(){ + const link = this.state.link; + D.clearElement(link); + if(lineState.start){ + const ls = [lineState.start]; + if(lineState.end) ls.push(lineState.end); + link.dataset.url = ( + window.location.toString().split('?')[0] + + lineState.urlArgs + '&ln='+ls.join('-') + ); + D.append( + D.clearElement(link), + ' Copy link to '+( + ls.length===1 ? 'line ' : 'lines ' + )+ls.join('-') + ); + }else{ + D.append(link, "No lines selected."); + } + }, + init: function(){ + const e = this.e; + const btnCopy = D.span(), + link = D.span(); + this.state = {link}; + F.copyButton(btnCopy,{ + copyFromElement: link, + extractText: ()=>link.dataset.url, + oncopy: (ev)=>{ + D.flashOnce(ev.target, undefined, ()=>lineTip.hide()); + // arguably too snazzy: F.toast.message("Copied link to clipboard."); + } + }); + this.e.addEventListener('click', ()=>btnCopy.click(), false); + D.append(this.e, btnCopy, link) + } + }); + + tbl.addEventListener('click', ()=>lineTip.hide(), true); + + tdLn.addEventListener('click', function f(ev){ + if('SPAN'!==ev.target.tagName) return; + else if('number' !== typeof f.mode){ + f.mode = 0 /*0=none selected, 1=1 selected, 2=2 selected*/; + f.spans = tdLn.querySelectorAll('span'); + f.selected = tdLn.querySelectorAll('span.selected-line'); + f.unselect = (e)=>D.removeClass(e, 'selected-line','start','end'); + } + ev.stopPropagation(); + const ln = +ev.target.innerText; + if(2===f.mode){/*Reset selection*/ + f.mode = 0; + } + if(0===f.mode){/*Select single line*/ + lineState.end = 0; + lineState.start = ln; + f.mode = 1; + }else if(1===f.mode){ + if(ln === lineState.start){/*Unselect line*/ + lineState.start = 0; + f.mode = 0; + }else{/*Select range*/ + if(ln=lineState.start){ + let i = lineState.start, end = lineState.end || lineState.start, span = f.spans[i-1]; + for( ; i<=end && span; span = f.spans[i++] ){ + span.classList.add('selected-line'); + f.selected.push(span); + if(i===lineState.start) span.classList.add('start'); + if(i===end) span.classList.add('end'); + } + } + lineTip.refresh().show(rect.right+3, rect.top-4); + } + }, false); + +})(); ADDED src/fossil.page.brlist.js Index: src/fossil.page.brlist.js ================================================================== --- /dev/null +++ src/fossil.page.brlist.js @@ -0,0 +1,74 @@ +/* + * This script adds multiselect facility for the list of branches. + * + * Some info on 'const': + * https://caniuse.com/const + * https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Statements/const#browser_compatibility + * + * According to MDN 'const' requires Android's WebView 37, + * which may not be available. + * For the time being, continueing without 'const' and 'indexOf' + * (but that may be reconsidered later). +*/ +window.addEventListener( 'load', function() { + +var submenu = document.querySelector("div.submenu"); +var anchor = document.createElement("A"); +var brlistDataObj = document.getElementById("brlist-data"); +var brlistDataTxt = brlistDataObj.textContent || brlistDataObj.innerText; +var brlistData = JSON.parse(brlistDataTxt); +anchor.classList.add("label"); +anchor.classList.add("timeline-link"); +anchor.href = brlistData.timelineUrl; +var prefix = anchor.href.toString() + "?ms=brlist&t="; +submenu.insertBefore(anchor,submenu.childNodes[0]); + +var amendAnchor = function( selected ){ + if( selected.length == 0 ){ + anchor.classList.remove('selected'); + anchor.href = prefix; + return; + } + re = selected.join(","); + try{re = encodeURIComponent(re);} + catch{console.log("encodeURIComponent() failed for ",re);} + anchor.href = prefix + re; + anchor.innerHTML = "View " + selected.length + + ( selected.length > 1 ? " branches" : " branch" ); + anchor.classList.add('selected'); +} + +var onChange = function( event ){ + var cbx = event.target; + var tr = cbx.parentElement.parentElement; + var tag = cbx.parentElement.children[0].innerText; + var re = anchor.href.substr(prefix.length); + try{re = decodeURIComponent(re);} + catch{console.log("decodeURIComponent() failed for ",re);} + var selected = ( re != "" ? re.split(",") : [] ); + if( cbx.checked ){ + selected.push(tag); + tr.classList.add('selected'); + } + else { + tr.classList.remove('selected'); + for( var i = selected.length; --i >= 0 ;) + if( selected[i] == tag ) + selected.splice(i,1); + } + amendAnchor( selected ); +} + +var stags = []; /* initially selected tags, not used above */ +document.querySelectorAll("div.brlist > table td:first-child > input") + .forEach( function( cbx ){ + cbx.onchange = onChange; + cbx.disabled = false; + if( cbx.checked ){ + stags.push(cbx.parentElement.children[0].innerText); + cbx.parentElement.parentElement.classList.add('selected'); + } + }); +amendAnchor( stags ); + +}); // window.addEventListener( 'load' ... ADDED src/fossil.page.fileedit.js Index: src/fossil.page.fileedit.js ================================================================== --- /dev/null +++ src/fossil.page.fileedit.js @@ -0,0 +1,1416 @@ +(function(F/*the fossil object*/){ + "use strict"; + /** + Client-side implementation of the /filepage app. Requires that + the fossil JS bootstrapping is complete and that several fossil + JS APIs have been installed: fossil.fetch, fossil.dom, + fossil.tabs, fossil.storage, fossil.confirmer. + + Custom events which can be listened for via + fossil.page.addEventListener(): + + - Event 'fileedit-file-loaded': passes on information when it + loads a file (whether from the network or its internal local-edit + cache), in the form of an "finfo" object: + + { + filename: string, + checkin: UUID string, + branch: branch name of UUID, + isExe: bool, true only for executable files + mimetype: mimetype string, as determined by the fossil server. + } + + The internal docs and code frequently use the term "finfo", and such + references refer to an object with that form. + + The fossil.page.fileContent() method gets or sets the current file + content for the page. + + - Event 'fileedit-committed': is fired when a commit completes, + passing on the same info as fileedit-file-loaded. + + - Event 'fileedit-content-replaced': when the editor's content is + replaced, as opposed to it being edited via user + interaction. This normally happens via selecting a file to + load. The event detail is the fossil.page object, not the current + file content. + + - Event 'fileedit-preview-updated': when the preview is refreshed + from the server, this event passes on information about the preview + change in the form of an object: + + { + element: the DOM element which contains the content preview. + + mimetype: the fossil-reported content mimetype. + + previewMode: a string describing the preview mode: see + the fossil.page.previewModes map for the values. This can + be used to determine whether, e.g., the content is suitable + for applying a 3rd-party code highlighting API to. + } + + Here's an example which can be used with the highlightjs code + highlighter to update the highlighting when the preview is + refreshed in "wiki" mode (which includes fossil-native wiki and + markdown): + + fossil.page.addEventListener( + 'fileedit-preview-updated', + (ev)=>{ + if(ev.detail.previewMode==='wiki'){ + ev.detail.element.querySelectorAll( + 'code[class^=language-]' + ).forEach((e)=>hljs.highlightBlock(e)); + } + } + ); + */ + const E = (s)=>document.querySelector(s), + D = F.dom, + P = F.page; + + P.config = { + defaultMaxStashSize: 7 + }; + + /** + $stash is an internal-use-only object for managing "stashed" + local edits, to help avoid that users accidentally lose content + by switching tabs or following links or some such. The basic + theory of operation is... + + All "stashed" state is stored using fossil.storage. + + - When the current file content is modified by the user, the + current stathe of the current P.finfo and its the content + is stashed. For the built-in editor widget, "changes" is + notified via a 'change' event. For a client-side custom + widget, the client needs to call P.stashContentChange() when + their widget triggers the equivalent of a 'change' event. + + - For certain non-content updates (as of this writing, only the + is-executable checkbox), only the P.finfo stash entry is + updated, not the content (unless the content has not yet been + stashed, in which case it is also stashed so that the stash + always has matching pairs of finfo/content). + + - When saving, the stashed entry for the previous version is removed + from the stash. + + - When "loading", we use any stashed state for the given + checkin/file combination. When forcing a re-load of content, + any stashed entry for that combination is removed from the + stash. + + - Every time P.stashContentChange() updates the stash, it is + pruned to $stash.prune.defaultMaxCount most-recently-updated + entries. + + - This API often refers to "finfo objects." Those are objects + with a minimum of {checkin,filename} properties (which must be + valid), and a combination of those two properties is used as + basis for the stash keys for any given checkin/filename + combination. + + The structure of the stash is a bit convoluted for efficiency's + sake: we store a map of file info (finfo) objects separately from + those files' contents because otherwise we would be required to + JSONize/de-JSONize the file content when stashing/restoring it, + and that would be horribly inefficient (meaning "battery-consuming" + on mobile devices). + */ + const $stash = { + keys: { + index: F.page.name+'.index' + }, + /** + index: { + "CHECKIN_HASH:FILENAME": {file info w/o content} + ... + } + + In F.storage we... + + - Store this.index under the key this.keys.index. + + - Store each file's content under the key + (P.name+'/CHECKIN_HASH:FILENAME'). These are stored separately + from the index entries to avoid having to JSONize/de-JSONize + the content. The assumption/hope is that the browser can store + those records "directly," without any intermediary + encoding/decoding going on. + */ + indexKey: function(finfo){return finfo.checkin+':'+finfo.filename}, + /** Returns the key for storing content for the given key suffix, + by prepending P.name to suffix. */ + contentKey: function(suffix){return P.name+'/'+suffix}, + /** Returns the index object, fetching it from the stash or creating + it anew on the first call. */ + getIndex: function(){ + if(!this.index){ + this.index = F.storage.getJSON( + this.keys.index, {} + ); + } + return this.index; + }, + _fireStashEvent: function(){ + if(this._disableNextEvent) delete this._disableNextEvent; + else F.page.dispatchEvent('fileedit-stash-updated', this); + }, + /** + Returns the stashed version, if any, for the given finfo object. + */ + getFinfo: function(finfo){ + const ndx = this.getIndex(); + return ndx[this.indexKey(finfo)]; + }, + /** Serializes this object's index to F.storage. Returns this. */ + storeIndex: function(){ + if(this.index) F.storage.setJSON(this.keys.index,this.index); + return this; + }, + /** Updates the stash record for the given finfo + and (optionally) content. If passed 1 arg, only + the finfo stash is updated, else both the finfo + and its contents are (re-)stashed. Returns this. + */ + updateFile: function(finfo,content){ + const ndx = this.getIndex(), + key = this.indexKey(finfo), + old = ndx[key]; + const record = old || (ndx[key]={ + checkin: finfo.checkin, + filename: finfo.filename, + mimetype: finfo.mimetype + }); + record.isExe = !!finfo.isExe; + record.stashTime = new Date().getTime(); + if(!record.branch) record.branch=finfo.branch; + this.storeIndex(); + if(arguments.length>1){ + F.storage.set(this.contentKey(key), content); + } + this._fireStashEvent(); + return this; + }, + /** + Returns the stashed content, if any, for the given finfo + object. + */ + stashedContent: function(finfo){ + return F.storage.get(this.contentKey(this.indexKey(finfo))); + }, + /** Returns true if we have stashed content for the given finfo + record. */ + hasStashedContent: function(finfo){ + return F.storage.contains(this.contentKey(this.indexKey(finfo))); + }, + /** Unstashes the given finfo record and its content. + Returns this. */ + unstash: function(finfo){ + const ndx = this.getIndex(), + key = this.indexKey(finfo); + delete finfo.stashTime; + delete ndx[key]; + F.storage.remove(this.contentKey(key)); + this.storeIndex(); + this._fireStashEvent(); + return this; + }, + /** + Clears all $stash entries from F.storage. Returns this. + */ + clear: function(){ + const ndx = this.getIndex(), + self = this; + let count = 0; + Object.keys(ndx).forEach(function(k){ + ++count; + const e = ndx[k]; + delete ndx[k]; + F.storage.remove(self.contentKey(k)); + }); + F.storage.remove(this.keys.index); + delete this.index; + if(count) this._fireStashEvent(); + return this; + }, + /** + Removes all but the maxCount most-recently-updated stash + entries, where maxCount defaults to this.prune.defaultMaxCount. + */ + prune: function f(maxCount){ + const ndx = this.getIndex(); + const li = []; + if(!maxCount || maxCount<0) maxCount = f.defaultMaxCount; + Object.keys(ndx).forEach((k)=>li.push(ndx[k])); + li.sort((l,r)=>l.stashTime - r.stashTime); + let n = 0; + while(li.length>maxCount){ + ++n; + const e = li.shift(); + this._disableNextEvent = true; + this.unstash(e); + console.warn("Pruned oldest local file edit entry:",e); + } + if(n) this._fireStashEvent(); + } + }; + $stash.prune.defaultMaxCount = P.config.defaultMaxStashSize; + + /** + Widget for the checkin/file selection list. + */ + P.fileSelectWidget = { + e:{ + container: E('#fileedit-file-selector') + }, + finfo: {}, + cache: { + checkins: undefined, + files:{}, + branchKey: 'fileedit/uuid-branches', + branchNames: {} + }, + /** + Fetches the list of leaf checkins from the server and updates + the UI with that list. + */ + loadLeaves: function(){ + D.append(D.clearElement( + this.e.ciListLabel, + this.e.selectCi, + this.e.selectFiles + ),"Loading leaves..."); + D.disable(this.e.btnLoadFile, this.e.selectFiles, this.e.selectCi); + const self = this; + const onload = function(list){ + D.append(D.clearElement(self.e.ciListLabel), + "Open leaves (newest first):"); + self.cache.checkins = list; + D.clearElement(D.enable(self.e.selectCi)); + let loadThisOne = P.initialFiles/*possibly injected at page-load time*/; + if(loadThisOne){ + self.cache.files[loadThisOne.checkin] = loadThisOne; + delete P.initialFiles; + } + list.forEach(function(o,n){ + if(!n && !loadThisOne) loadThisOne = o; + self.cache.branchNames[F.hashDigits(o.checkin,true)] = o.branch; + D.option(self.e.selectCi, o.checkin, + o.timestamp+' ['+o.branch+']: ' + +F.hashDigits(o.checkin)); + }); + F.storage.setJSON(self.cache.branchKey, self.cache.branchNames); + if(loadThisOne){ + self.e.selectCi.value = loadThisOne.checkin; + } + self.loadFiles(loadThisOne ? loadThisOne.checkin : false); + }; + if(P.initialLeaves/*injected at page-load time.*/){ + const lv = P.initialLeaves; + delete P.initialLeaves; + onload(lv); + }else{ + F.fetch('fileedit/filelist',{ + urlParams:'leaves', + responseType: 'json', + onload: onload + }); + } + }, + /** + Loads the file list for the given checkin UUID. It uses a + cached copy on subsequent calls for the same UUID. If passed a + falsy value, it instead clears and disables the file selection + list. + */ + loadFiles: function(ciUuid){ + delete this.finfo.filename; + this.finfo.checkin = ciUuid; + const selFiles = this.e.selectFiles; + if(!ciUuid){ + D.clearElement(D.disable(selFiles, this.e.btnLoadFile)); + return this; + } + const onload = (response)=>{ + D.clearElement(selFiles); + D.append( + D.clearElement(this.e.fileListLabel), + "Editable files for ", + D.append( + D.code(), "[", + D.a(F.repoUrl('timeline',{ + c: ciUuid + }), F.hashDigits(ciUuid)),"]" + ), ":" + ); + this.cache.files[response.checkin] = response; + response.editableFiles.forEach(function(fn,n){ + D.option(selFiles, fn); + }); + if(selFiles.options.length){ + D.enable(selFiles, this.e.btnLoadFile); + } + }; + const got = this.cache.files[ciUuid]; + if(got){ + onload(got); + return this; + } + D.disable(selFiles,this.e.btnLoadFile); + D.clearElement(selFiles); + D.append(D.clearElement(this.e.fileListLabel), + "Loading files for "+F.hashDigits(ciUuid)+"..."); + F.fetch('fileedit/filelist',{ + urlParams:{checkin: ciUuid}, + responseType: 'json', + onload + }); + return this; + }, + + /** + If this object has ever loaded the given checkin version via + loadLeaves(), this returns the branch name associated with that + version, else returns undefined; + */ + checkinBranchName: function(uuid){ + return this.cache.branchNames[F.hashDigits(uuid,true)]; + }, + + /** + Initializes the checkin/file selector widget. Must only be + called once. + */ + init: function(){ + this.cache.branchNames = F.storage.getJSON(this.cache.branchKey, {}); + const selCi = this.e.selectCi = D.addClass(D.select(), 'flex-grow'), + selFiles = this.e.selectFiles + = D.addClass(D.select(), 'file-list'), + btnLoad = this.e.btnLoadFile = + D.addClass(D.button("Load file"), "flex-shrink"), + filesLabel = this.e.fileListLabel = + D.addClass(D.div(),'flex-shrink','file-list-label'), + ciLabelWrapper = D.addClass( + D.div(), 'flex-container','flex-row', 'flex-shrink', + 'stretch', 'child-gap-small' + ), + btnReload = D.addClass( + D.button('Reload'), 'flex-shrink' + ), + ciLabel = this.e.ciListLabel = + D.addClass(D.span(),'flex-shrink','checkin-list-label') + ; + D.attr(selCi, 'title',"The list of opened leaves."); + D.attr(selFiles, 'title', + "The list of editable files for the selected checkin."); + D.attr(btnLoad, 'title', + "Load the selected file into the editor."); + D.disable(selCi, selFiles, btnLoad); + D.attr(selFiles, 'size', 12); + D.append( + this.e.container, + ciLabel, + D.append(ciLabelWrapper, + selCi, + btnReload), + filesLabel, + selFiles, + /* Use a wrapper for btnLoad so that the button itself does not + stretch to fill the parent width: */ + D.append(D.addClass(D.div(), 'flex-shrink'), btnLoad) + ); + if(F.config['fileedit-glob']){ + D.append( + this.e.container, + D.append( + D.span(), + D.append(D.code(),"fileedit-glob"), + " config setting = ", + D.append(D.code(), JSON.stringify(F.config['fileedit-glob'])) + ) + ); + } + + this.loadLeaves(); + selCi.addEventListener( + 'change', (e)=>this.loadFiles(e.target.value), false + ); + const doLoad = (e)=>{ + this.finfo.filename = selFiles.value; + if(this.finfo.filename){ + P.loadFile(this.finfo.filename, this.finfo.checkin); + } + }; + btnLoad.addEventListener('click', doLoad, false); + selFiles.addEventListener('dblclick', doLoad, false); + btnReload.addEventListener( + 'click', (e)=>this.loadLeaves(), false + ); + delete this.init; + } + }/*P.fileSelectWidget*/; + + /** + Widget for listing and selecting $stash entries. + */ + P.stashWidget = { + e:{/*DOM element(s)*/}, + init: function(domInsertPoint/*insert widget BEFORE this element*/){ + const wrapper = D.addClass( + D.attr(D.div(),'id','fileedit-stash-selector'), + 'input-with-label' + ); + const sel = this.e.select = D.select(); + const btnClear = this.e.btnClear = D.button("Discard Edits"), + btnHelp = D.append( + D.addClass(D.div(), "help-buttonlet"), + 'Locally-edited files. Timestamps are the last local edit time. ', + 'Only the ',P.config.defaultMaxStashSize,' most recent files ', + 'are retained. Saving or reloading a file removes it from this list. ', + D.append(D.code(),F.storage.storageImplName()), + ' = ',F.storage.storageHelpDescription() + ); + + D.append(wrapper, "Local edits (", + D.append(D.code(), + F.storage.storageImplName()), + "):", + btnHelp, sel, btnClear); + F.helpButtonlets.setup(btnHelp); + D.option(D.disable(sel), undefined, "(empty)"); + F.page.addEventListener('fileedit-stash-updated',(e)=>this.updateList(e.detail)); + F.page.addEventListener('fileedit-file-loaded',(e)=>this.updateList($stash, e.detail)); + sel.addEventListener('change',function(e){ + const opt = this.selectedOptions[0]; + if(opt && opt._finfo) P.loadFile(opt._finfo); + }); + if(F.storage.isTransient()){/*Warn if our storage is particularly transient...*/ + D.append(wrapper, D.append( + D.addClass(D.span(),'warning'), + "Warning: persistent storage is not available, "+ + "so uncomitted edits will not survive a page reload." + )); + } + domInsertPoint.parentNode.insertBefore(wrapper, domInsertPoint); + P.tabs.switchToTab(1/*DOM visibility workaround*/); + F.confirmer(btnClear, { + /* must come after insertion into the DOM for the pinSize option to work. */ + pinSize: true, + confirmText: "DISCARD all local edits?", + onconfirm: function(e){ + if(P.finfo){ + const stashed = P.getStashedFinfo(P.finfo); + P.clearStash(); + if(stashed) P.loadFile(/*reload after discarding edits*/); + }else{ + P.clearStash(); + } + }, + ticks: F.config.confirmerButtonTicks + }); + D.addClass(this.e.btnClear,'hidden' /* must not be set until after confirmer is set up!*/); + $stash._fireStashEvent(/*read the page-load-time stash*/); + P.tabs.switchToTab(0/*DOM visibility workaround*/); + delete this.init; + }, + /** + Regenerates the edit selection list. + */ + updateList: function f(stasher,theFinfo){ + if(!f.compare){ + const cmpBase = (l,r)=>l(''+x).length>1 ? x : '0'+x; + f.timestring = function ff(d){ + return [ + d.getFullYear(),'-',pad(d.getMonth()+1/*sigh*/),'-',pad(d.getDate()), + '@',pad(d.getHours()),':',pad(d.getMinutes()) + ].join(''); + }; + } + const index = stasher.getIndex(), ilist = []; + Object.keys(index).forEach((finfo)=>{ + ilist.push(index[finfo]); + }); + const self = this; + D.clearElement(this.e.select); + if(0===ilist.length){ + D.addClass(this.e.btnClear, 'hidden'); + D.option(D.disable(this.e.select),undefined,"No local edits"); + return; + } + D.enable(this.e.select); + D.removeClass(this.e.btnClear, 'hidden'); + D.disable(D.option(this.e.select,0,"Select a local edit...")); + const currentFinfo = theFinfo || P.finfo || {filename:''}; + ilist.sort(f.compare).forEach(function(finfo,n){ + const key = stasher.indexKey(finfo), + branch = finfo.branch + || P.fileSelectWidget.checkinBranchName(finfo.checkin)||''; + /* Remember that we don't know the branch name for non-leaf versions + which P.fileSelectWidget() has never seen/cached. */ + const opt = D.option( + self.e.select, n+1/*value is (almost) irrelevant*/, + [F.hashDigits(finfo.checkin), ' [',branch||'?branch?','] ', + f.timestring(new Date(finfo.stashTime)),' ', + false ? finfo.filename : F.shortenFilename(finfo.filename) + ].join('') + ); + opt._finfo = finfo; + if(0===f.compare(currentFinfo, finfo)){ + D.attr(opt, 'selected', true); + } + }); + } + }/*P.stashWidget*/; + + /** + Internal workaround to select the current preview mode + and fire a change event if the value actually changes + or if forceEvent is truthy. + */ + P.selectPreviewMode = function(modeValue, forceEvent){ + const s = this.e.selectPreviewMode; + if(!modeValue) modeValue = s.value; + else if(s.value != modeValue){ + s.value = modeValue; + forceEvent = true; + } + if(forceEvent){ + // Force UI update + s.dispatchEvent(new Event('change',{target:s})); + } + }; + + /** + Keep track of how many in-flight AJAX requests there are so we + can disable input elements while any are pending. For + simplicity's sake we simply disable ALL OF IT while any AJAX is + pending, rather than disabling operation-specific UI elements, + which would be a huge maintenance hassle. + + Noting, however, that this global on/off is not *quite* + pedantically correct. Pedantically speaking. If an element is + disabled before an XHR starts, this code "should" notice that and + not include it in the to-re-enable list. That would be annoying + to do, and becomes impossible to do properly once multiple XHRs + are in transit and an element is disabled seprately between two + of those in-transit requests (that would be an unlikely, but + possible, corner case). As of this writing, the only elements + which are ever normally programmatically toggled between + enabled/disabled... + + 1) Belong to the file selection list and remain disabled until + the list of leaves and files are loaded. i.e. they would be + disabled *anyway* during their own XHR requests. + + 2) The stashWidget's SELECT list when no local edits are + stashed. Curiously, the all-or-nothing re-enabling implemented + here does not re-enable that particular selection list. That's + because of timing, though: that widget is "manually" disabled + when the list is empty, and that list is normally emptied in + conjunction with an XHR request. + */ + const ajaxState = { + count: 0 /* in-flight F.fetch() requests */, + toDisable: undefined /* elements to disable during ajax activity */ + }; + F.fetch.beforesend = function f(){ + if(!ajaxState.toDisable){ + ajaxState.toDisable = document.querySelectorAll( + 'button, input, select, textarea' + ); + } + if(1===++ajaxState.count){ + D.addClass(document.body, 'waiting'); + D.disable(ajaxState.toDisable); + } + }; + F.fetch.aftersend = function(){ + if(0===--ajaxState.count){ + D.removeClass(document.body, 'waiting'); + D.enable(ajaxState.toDisable); + } + }; + + F.onPageLoad(function() { + P.base = {tag: E('base')}; + P.base.originalHref = P.base.tag.href; + P.tabs = new F.TabManager('#fileedit-tabs'); + P.e = { /* various DOM elements we work with... */ + taEditor: E('#fileedit-content-editor'), + taCommentSmall: E('#fileedit-comment'), + taCommentBig: E('#fileedit-comment-big'), + taComment: undefined/*gets set to one of taComment{Big,Small}*/, + ajaxContentTarget: E('#ajax-target'), + btnCommit: E("#fileedit-btn-commit"), + btnReload: E("#fileedit-tab-content button.fileedit-content-reload"), + selectPreviewMode: E('#select-preview-mode select'), + selectHtmlEmsWrap: E('#select-preview-html-ems'), + selectEolWrap: E('#select-eol-style'), + selectEol: E('#select-eol-style select[name=eol]'), + selectFontSizeWrap: E('#select-font-size'), + selectDiffWS: E('select[name=diff_ws]'), + cbLineNumbersWrap: E('#cb-line-numbers'), + cbAutoPreview: E('#cb-preview-autorefresh'), + previewTarget: E('#fileedit-tab-preview-wrapper'), + manifestTarget: E('#fileedit-manifest'), + diffTarget: E('#fileedit-tab-diff-wrapper'), + cbIsExe: E('input[type=checkbox][name=exec_bit]'), + cbManifest: E('input[type=checkbox][name=include_manifest]'), + editStatus: E('#fileedit-edit-status'), + tabs:{ + content: E('#fileedit-tab-content'), + preview: E('#fileedit-tab-preview'), + diff: E('#fileedit-tab-diff'), + commit: E('#fileedit-tab-commit'), + fileSelect: E('#fileedit-tab-fileselect') + } + }; + /* Figure out which comment editor to show by default and + hide the other one. By default we take the one which does + not have the 'hidden' CSS class. If neither do, we default + to single-line mode. */ + if(D.hasClass(P.e.taCommentSmall, 'hidden')){ + P.e.taComment = P.e.taCommentBig; + }else if(D.hasClass(P.e.taCommentBig,'hidden')){ + P.e.taComment = P.e.taCommentSmall; + }else{ + P.e.taComment = P.e.taCommentSmall; + D.addClass(P.e.taCommentBig, 'hidden'); + } + D.removeClass(P.e.taComment, 'hidden'); + P.tabs.addCustomWidget( E('#fossil-status-bar') ).addCustomWidget(P.e.editStatus); + let currentTab/*used for ctrl-enter switch between editor and preview*/; + P.tabs.addEventListener( + /* Set up auto-refresh of the preview tab... */ + 'before-switch-to', function(ev){ + currentTab = ev.detail; + if(ev.detail===P.e.tabs.preview){ + P.baseHrefForFile(); + if(P.previewNeedsUpdate && P.e.cbAutoPreview.checked) P.preview(); + }else if(ev.detail===P.e.tabs.diff){ + /* Work around a weird bug where the page gets wider than + the window when the diff tab is NOT in view and the + current SBS diff widget is wider than the window. When + the diff IS in view then CSS overflow magically reduces + the page size again. Weird. Maybe FF-specific. Note that + this weirdness happens even though P.e.diffTarget's parent + is hidden (and therefore P.e.diffTarget is also hidden). + */ + D.removeClass(P.e.diffTarget, 'hidden'); + } + } + ); + P.tabs.addEventListener( + /* Set up auto-refresh of the preview tab... */ + 'before-switch-from', function(ev){ + if(ev.detail===P.e.tabs.preview){ + P.baseHrefRestore(); + }else if(ev.detail===P.e.tabs.diff){ + /* See notes in the before-switch-to handler. */ + D.addClass(P.e.diffTarget, 'hidden'); + } + } + ); + //////////////////////////////////////////////////////////// + // Trigger preview on Ctrl-Enter. This only works on the built-in + // editor widget, not a client-provided one. + P.e.taEditor.addEventListener('keydown',function(ev){ + if(ev.ctrlKey && 13 === ev.keyCode){ + ev.preventDefault(); + ev.stopPropagation(); + P.e.taEditor.blur(/*force change event, if needed*/); + P.tabs.switchToTab(P.e.tabs.preview); + if(!P.e.cbAutoPreview.checked){/* If NOT in auto-preview mode, trigger an update. */ + P.preview(); + } + } + }, false); + // If we're in the preview tab, have ctrl-enter switch back to the editor. + document.body.addEventListener('keydown',function(ev){ + if(ev.ctrlKey && 13 === ev.keyCode){ + if(currentTab === P.e.tabs.preview){ + //ev.preventDefault(); + //ev.stopPropagation(); + P.tabs.switchToTab(P.e.tabs.content); + P.e.taEditor.focus(/*doesn't work for client-supplied editor widget! + And it's slow as molasses for long docs, as focus() + forces a document reflow.*/); + } + } + }, true); + + F.connectPagePreviewers( + P.e.tabs.preview.querySelector( + '#btn-preview-refresh' + ) + ); + + const diffButtons = E('#fileedit-tab-diff-buttons'); + diffButtons.querySelector('button.sbs').addEventListener( + "click",(e)=>P.diff(true), false + ); + diffButtons.querySelector('button.unified').addEventListener( + "click",(e)=>P.diff(false), false + ); + P.e.btnCommit.addEventListener( + "click",(e)=>P.commit(), false + ); + P.tabs.switchToTab(1/*DOM visibility workaround*/); + F.confirmer(P.e.btnReload, { + pinSize: true, + confirmText: "Really reload, losing edits?", + onconfirm: (e)=>P.unstashContent().loadFile(), + ticks: F.config.confirmerButtonTicks + }); + E('#comment-toggle').addEventListener( + "click",(e)=>P.toggleCommentMode(), false + ); + + P.e.taEditor.addEventListener('change', ()=>P.notifyOfChange(), false); + P.e.cbIsExe.addEventListener( + 'change', ()=>P.stashContentChange(true), false + ); + + /** + Cosmetic: jump through some hoops to enable/disable + certain preview options depending on the current + preview mode... + */ + P.e.selectPreviewMode.addEventListener( + "change", function(e){ + const mode = e.target.value, + name = P.previewModes[mode], + hide = [], unhide = []; + P.previewModes.current = name; + if('guess'===name){ + unhide.push(P.e.cbLineNumbersWrap, + P.e.selectHtmlEmsWrap); + }else{ + if('text'===name) unhide.push(P.e.cbLineNumbersWrap); + else hide.push(P.e.cbLineNumbersWrap); + if('htmlIframe'===name) unhide.push(P.e.selectHtmlEmsWrap); + else hide.push(P.e.selectHtmlEmsWrap); + } + hide.forEach((e)=>e.classList.add('hidden')); + unhide.forEach((e)=>e.classList.remove('hidden')); + }, false + ); + P.selectPreviewMode(false, true); + const selectFontSize = E('select[name=editor_font_size]'); + if(selectFontSize){ + selectFontSize.addEventListener( + "change",function(e){ + const ed = P.e.taEditor; + ed.className = ed.className.replace( + /\bfont-size-\d+/g, '' ); + ed.classList.add('font-size-'+e.target.value); + }, false + ); + selectFontSize.dispatchEvent( + // Force UI update + new Event('change',{target:selectFontSize}) + ); + } + + P.addEventListener( + // Clear certain views when new content is loaded/set + 'fileedit-content-replaced', + ()=>{ + P.previewNeedsUpdate = true; + D.clearElement(P.e.diffTarget, P.e.previewTarget, P.e.manifestTarget); + } + ); + P.addEventListener( + // Clear certain views after a non-dry-run commit + 'fileedit-committed', + (e)=>{ + if(!e.detail.dryRun){ + D.clearElement(P.e.diffTarget, P.e.previewTarget); + } + } + ); + + P.fileSelectWidget.init(); + P.stashWidget.init( + P.e.tabs.content.lastElementChild + ); + }/*F.onPageLoad()*/); + + /** + Getter (if called with no args) or setter (if passed an arg) for + the current file content. + + The setter form sets the content, dispatches a + 'fileedit-content-replaced' event, and returns this object. + */ + P.fileContent = function f(){ + if(0===arguments.length){ + return f.get(); + }else{ + f.set(arguments[0] || ''); + this.dispatchEvent('fileedit-content-replaced', this); + return this; + } + }; + /* Default get/set impls for file content */ + P.fileContent.get = function(){return P.e.taEditor.value}; + P.fileContent.set = function(content){P.e.taEditor.value = content}; + + /** + For use when installing a custom editor widget. Pass it the + getter and setter callbacks to fetch resp. set the content of the + custom widget. They will be triggered via + P.fileContent(). Returns this object. + */ + P.setContentMethods = function(getter, setter){ + this.fileContent.get = getter; + this.fileContent.set = setter; + return this; + }; + + /** + Alerts the editor app that a "change" has happened in the editor. + When connecting 3rd-party editor widgets to this app, it is (or + may be) necessary to call this for any "change" events the widget + emits. Whether or not "change" means that there were "really" + edits is irrelevant. + + This function may perform an arbitrary amount of work, so it + should not be called for every keypress within the editor + widget. Calling it for "blur" events is generally sufficient, and + calling it for each Enter keypress is generally reasonable but + also computationally costly. + */ + P.notifyOfChange = function(){ + P.stashContentChange(); + }; + + /** + Removes the default editor widget (and any dependent elements) + from the DOM, adds the given element in its place, removes this + method from this object, and returns this object. + */ + P.replaceEditorElement = function(newEditor){ + P.e.taEditor.parentNode.insertBefore(newEditor, P.e.taEditor); + P.e.taEditor.remove(); + P.e.selectFontSizeWrap.remove(); + delete this.replaceEditorElement; + return P; + }; + + /** + If either of... + + - P.previewModes.current==='wiki' + + - P.previewModes.current==='guess' AND the currently-loaded file + has a mimetype of "text/x-fossil-wiki" or "text/x-markdown". + + ... then this function updates the document's base.href to a + repo-relative /doc/{{this.finfo.checkin}}/{{directory part of + this.finfo.filename}}/ + + If neither of those conditions applies, this is a no-op. + */ + P.baseHrefForFile = function f(){ + const fn = this.finfo ? this.finfo.filename : undefined; + if(!fn) return this; + if(!f.wikiMimeTypes){ + f.wikiMimeTypes = ["text/x-fossil-wiki", "text/x-markdown"]; + } + if('wiki'===P.previewModes.current + || ('guess'===P.previewModes.current + && f.wikiMimeTypes.indexOf(this.finfo.mimetype)>=0)){ + const a = fn.split('/'); + a.pop(); + this.base.tag.href = F.repoUrl( + 'doc/'+F.hashDigits(this.finfo.checkin) + +'/'+(a.length ? a.join('/')+'/' : '') + ); + } + return this; + }; + + /** + Sets the document's base.href value to its page-load-time + setting. + */ + P.baseHrefRestore = function(){ + P.base.tag.href = P.base.originalHref; + }; + + /** + Toggles between single- and multi-line comment + mode. + */ + P.toggleCommentMode = function(){ + var s, h, c = this.e.taComment.value; + if(this.e.taComment === this.e.taCommentSmall){ + s = this.e.taCommentBig; + h = this.e.taCommentSmall; + }else{ + s = this.e.taCommentSmall; + h = this.e.taCommentBig; + /* + Doing (input[type=text].value = textarea.value) unfortunately + strips all newlines. To compensate we'll replace each EOL with + a space. Not ideal. If we were to instead escape them as \n, + and do the reverse when toggling again, then they would get + committed as escaped newlines if the user did not first switch + back to multi-line mode. We cannot blindly unescape the + newlines, in the off chance that the user actually enters \n + in the comment. + */ + c = c.replace(/\r?\n/g,' '); + } + s.value = c; + this.e.taComment = s; + D.addClass(h, 'hidden'); + D.removeClass(s, 'hidden'); + }; + + /** + Returns true if fossil.page.finfo is set, indicating that a file + has been loaded, else it reports an error and returns false. + + If passed a truthy value any error message about not having + a file loaded is suppressed. + */ + const affirmHasFile = function(quiet){ + if(!P.finfo){ + if(!quiet) F.error("No file is loaded."); + } + return !!P.finfo; + }; + + /** + updateVersion() updates the filename and version in various UI + elements... + + Returns this object. + */ + P.updateVersion = function f(file,rev){ + if(!f.eLinks){ + f.eName = P.e.editStatus.querySelector('span.name'); + f.eLinks = P.e.editStatus.querySelector('span.links'); + } + if(1===arguments.length){/*assume object*/ + this.finfo = arguments[0]; + file = this.finfo.filename; + rev = this.finfo.checkin; + }else if(0===arguments.length){ + if(affirmHasFile()){ + file = this.finfo.filename; + rev = this.finfo.checkin; + } + }else{ + this.finfo = {filename:file,checkin:rev}; + } + const fi = this.finfo; + D.clearElement(f.eName, f.eLinks); + if(!fi){ + D.append(f.eName, '(no file loaded)'); + return this; + } + const rHuman = F.hashDigits(rev), + rUrl = F.hashDigits(rev,true); + + //TODO? port over is-edited marker from /wikiedit + //var marker = getEditMarker(wi, false); + D.append(f.eName/*,marker*/,D.a(F.repoUrl('finfo',{name:file, m:rUrl}), file)); + + D.append( + f.eLinks, + D.append(D.span(), fi.mimetype||'?mimetype?'), + D.a(F.repoUrl('info/'+rUrl), rHuman), + D.a(F.repoUrl('timeline',{m:rUrl}), "timeline"), + D.a(F.repoUrl('annotate',{filename:file, checkin:rUrl}),'annotate'), + D.a(F.repoUrl('blame',{filename:file, checkin:rUrl}),'blame') + ); + const purlArgs = F.encodeUrlArgs({ + filename: this.finfo.filename, + checkin: rUrl + },false,true); + const purl = F.repoUrl('fileedit',purlArgs); + D.append( f.eLinks, D.a(purl,"editor permalink") ); + this.setPageTitle("Edit: "+fi.filename); + return this; + }; + + /** + loadFile() loads (file,checkinVersion) and updates the relevant + UI elements to reflect the loaded state. If passed no arguments + then it re-uses the values from the currently-loaded file, reloading + it (emitting an error message if no file is loaded). + + Returns this object, noting that the load is async. After loading + it triggers a 'fileedit-file-loaded' event, passing it + this.finfo. + + If a locally-edited copy of the given file/rev is found, that + copy is used instead of one fetched from the server, but it is + still treated as a load event. + + Alternate call forms: + + - no arguments: re-loads from this.finfo. + + - 1 argument: assumed to be an finfo-style object. Must have at + least {filename, checkin} properties, but need not have other + finfo state. + */ + P.loadFile = function(file,rev){ + if(0===arguments.length){ + /* Reload from this.finfo */ + if(!affirmHasFile()) return this; + file = this.finfo.filename; + rev = this.finfo.checkin; + }else if(1===arguments.length){ + /* Assume finfo-like object */ + const arg = arguments[0]; + file = arg.filename; + rev = arg.checkin; + } + const self = this; + const onload = (r,headers)=>{ + delete self.finfo; + self.updateVersion({ + filename: file, + checkin: rev, + branch: headers['x-fileedit-checkin-branch'], + isExe: ('x'===headers['x-fileedit-file-perm']), + mimetype: headers['content-type'].split(';').shift() + }); + self.tabs.switchToTab(self.e.tabs.content); + self.e.cbIsExe.checked = self.finfo.isExe; + self.fileContent(r); + P.previewNeedsUpdate = true; + self.dispatchEvent('fileedit-file-loaded', self.finfo); + }; + const semiFinfo = {filename: file, checkin: rev}; + const stashFinfo = this.getStashedFinfo(semiFinfo); + if(stashFinfo){ // fake a response from the stash... + this.finfo = stashFinfo; + this.e.cbIsExe.checked = !!stashFinfo.isExe; + onload(this.contentFromStash()||'',{ + 'x-fileedit-file-perm': stashFinfo.isExe ? 'x' : undefined, + 'content-type': stashFinfo.mimetype, + 'x-fileedit-checkin-branch': stashFinfo.branch + }); + F.message("Fetched from the local-edit storage:", + F.hashDigits(stashFinfo.checkin), + stashFinfo.filename); + return this; + } + F.message( + "Loading content..." + ).fetch('fileedit/content',{ + urlParams: { + filename:file, + checkin:rev + }, + responseHeaders: [ + 'x-fileedit-file-perm', + 'x-fileedit-checkin-branch', + 'content-type'], + onload:(r,headers)=>{ + onload(r,headers); + F.message('Loaded content for', + F.hashDigits(self.finfo.checkin), + self.finfo.filename); + } + }); + return this; + }; + + /** + Fetches the page preview based on the contents and settings of + this page's input fields, and updates the UI with with the + preview. + + Returns this object, noting that the operation is async. + */ + P.preview = function f(switchToTab){ + if(!affirmHasFile()) return this; + return this._postPreview(this.fileContent(), function(c){ + P._previewTo(c); + if(switchToTab) self.tabs.switchToTab(self.e.tabs.preview); + }); + }; + + /** + Callback for use with F.connectPagePreviewers(). Gets passed + the preview content. + */ + P._previewTo = function(c){ + const target = this.e.previewTarget; + D.clearElement(target); + if('string'===typeof c) D.parseHtml(target,c); + if(F.pikchr){ + F.pikchr.addSrcView(target.querySelectorAll('svg.pikchr')); + } + }; + + /** + Callback for use with F.connectPagePreviewers() + */ + P._postPreview = function(content,callback){ + if(!affirmHasFile()) return this; + if(!content){ + callback(content); + return this; + } + const fd = new FormData(); + fd.append('render_mode',this.e.selectPreviewMode.value); + fd.append('filename',this.finfo.filename); + fd.append('ln',E('[name=preview_ln]').checked ? 1 : 0); + fd.append('iframe_height', E('[name=preview_html_ems]').value); + fd.append('content',content || ''); + F.message( + "Fetching preview..." + ).fetch('ajax/preview-text',{ + payload: fd, + responseHeaders: 'x-ajax-render-mode', + onload: (r,header)=>{ + P.selectPreviewMode(P.previewModes[header]); + if('wiki'===header) P.baseHrefForFile(); + else P.baseHrefRestore(); + callback(r); + F.message('Updated preview.'); + P.previewNeedsUpdate = false; + P.dispatchEvent('fileedit-preview-updated',{ + previewMode: P.previewModes.current, + mimetype: P.finfo.mimetype, + element: P.e.previewTarget + }); + }, + onerror: (e)=>{ + F.fetch.onerror(e); + callback("Error fetching preview: "+e); + } + }); + return this; + }; + + /** + Undo some of the SBS diff-rendering bits which hurt us more than + they help... + */ + P.tweakSbsDiffs2 = function(){ + if(1){ + const dt = this.e.diffTarget; + dt.querySelectorAll('.sbsdiffcols .difftxtcol').forEach( + (dtc)=>{ + const pre = dtc.querySelector('pre'); + pre.style.width = 'initial'; + //pre.removeAttribute('style'); + //console.debug("pre width =",pre.style.width); + } + ); + } + this.tweakSbsDiffs(); + }; + + /** + Fetches the content diff based on the contents and settings of + this page's input fields, and updates the UI with the diff view. + + Returns this object, noting that the operation is async. + */ + P.diff = function f(sbs){ + if(!affirmHasFile()) return this; + const content = this.fileContent(), + self = this, + target = this.e.diffTarget; + const fd = new FormData(); + fd.append('filename',this.finfo.filename); + fd.append('checkin', this.finfo.checkin); + fd.append('sbs', sbs ? 1 : 0); + fd.append('content',content); + if(this.e.selectDiffWS) fd.append('ws',this.e.selectDiffWS.value); + F.message( + "Fetching diff..." + ).fetch('fileedit/diff',{ + payload: fd, + onload: function(c){ + D.parseHtml(D.clearElement(target),[ + "
Diff [", + self.finfo.checkin, + "] → Local Edits
", + c||'No changes.' + ].join('')); + if(sbs) P.tweakSbsDiffs2(); + F.message('Updated diff.'); + self.tabs.switchToTab(self.e.tabs.diff); + } + }); + return this; + }; + + /** + Performs an async commit based on the form contents and updates + the UI. + + Returns this object. + */ + P.commit = function f(){ + if(!affirmHasFile()) return this; + const self = this; + const content = this.fileContent(), + target = D.clearElement(P.e.manifestTarget), + cbDryRun = E('[name=dry_run]'), + isDryRun = cbDryRun.checked, + filename = this.finfo.filename; + if(!f.onload){ + f.onload = function(c){ + const oldFinfo = JSON.parse(JSON.stringify(self.finfo)) + if(c.manifest){ + D.parseHtml(D.clearElement(target), [ + "

Manifest", + (c.dryRun?" (dry run)":""), + ": ", F.hashDigits(c.checkin),"

", + "
",
+            c.manifest.replace(/ */
+            "
" + ].join('')); + delete c.manifest/*so we don't stash this with finfo*/; + } + const msg = [ + 'Committed', + c.dryRun ? '(dry run)' : '', + '[', F.hashDigits(c.checkin) ,'].' + ]; + if(!c.dryRun){ + self.unstashContent(oldFinfo); + self.finfo = c; + self.e.taComment.value = ''; + self.updateVersion(); + self.fileSelectWidget.loadLeaves(); + } + self.dispatchEvent('fileedit-committed', c); + F.message.apply(F, msg); + self.tabs.switchToTab(self.e.tabs.commit); + }; + } + const fd = new FormData(); + fd.append('filename',filename); + fd.append('checkin', this.finfo.checkin); + fd.append('content',content); + fd.append('dry_run',isDryRun ? 1 : 0); + fd.append('eol', this.e.selectEol.value || 0); + /* Text fields or select lists... */ + fd.append('comment', this.e.taComment.value); + if(0){ + // Comment mimetype is currently not supported by the UI... + ['comment_mimetype' + ].forEach(function(name){ + var e = E('[name='+name+']'); + if(e) fd.append(name,e.value); + }); + } + /* Checkboxes: */ + ['allow_fork', + 'allow_older', + 'exec_bit', + 'allow_merge_conflict', + 'include_manifest', + 'prefer_delta' + ].forEach(function(name){ + var e = E('[name='+name+']'); + if(e){ + fd.append(name, e.checked ? 1 : 0); + }else{ + console.error("Missing checkbox? name =",name); + } + }); + F.message( + "Checking in..." + ).fetch('fileedit/commit',{ + payload: fd, + responseType: 'json', + onload: f.onload + }); + return this; + }; + + /** + Updates P.finfo for certain state and stashes P.finfo, with the + current content fetched via P.fileContent(). + + If passed truthy AND the stash already has stashed content for + the current file, only the stashed finfo record is updated, else + both the finfo and content are updated. + */ + P.stashContentChange = function(onlyFinfo){ + if(affirmHasFile(true)){ + const fi = this.finfo; + fi.isExe = this.e.cbIsExe.checked; + if(!fi.branch) fi.branch = this.fileSelectWidget.checkinBranchName(fi.checkin); + if(onlyFinfo && $stash.hasStashedContent(fi)){ + $stash.updateFile(fi); + }else{ + $stash.updateFile(fi, P.fileContent()); + } + F.message("Stashed change to",F.hashDigits(fi.checkin),fi.filename); + $stash.prune(); + this.previewNeedsUpdate = true; + } + return this; + }; + + /** + Removes any stashed state for the current P.finfo (if set) from + F.storage. Returns this. + */ + P.unstashContent = function(){ + const finfo = arguments[0] || this.finfo; + if(finfo){ + this.previewNeedsUpdate = true; + $stash.unstash(finfo); + //console.debug("Unstashed",finfo); + F.message("Unstashed",F.hashDigits(finfo.checkin),finfo.filename); + } + return this; + }; + + /** + Clears all stashed file state from F.storage. Returns this. + */ + P.clearStash = function(){ + $stash.clear(); + return this; + }; + + /** + If stashed content for P.finfo exists, it is returned, else + undefined is returned. + */ + P.contentFromStash = function(){ + return affirmHasFile(true) ? $stash.stashedContent(this.finfo) : undefined; + }; + + /** + If a stashed version of the given finfo object exists (same + filename/checkin values), return it, else return undefined. + */ + P.getStashedFinfo = function(finfo){ + return $stash.getFinfo(finfo); + }; + + P.$stash = $stash /*only for testing/debugging - not part of the API.*/; + +})(window.fossil); ADDED src/fossil.page.forumpost.js Index: src/fossil.page.forumpost.js ================================================================== --- /dev/null +++ src/fossil.page.forumpost.js @@ -0,0 +1,106 @@ +(function(F/*the fossil object*/){ + "use strict"; + /* JS code for /forumpage and friends. Requires fossil.dom + and can optionally use fossil.pikchr. */ + const P = F.page, D = F.dom; + + /** + When the page is loaded, this handler does the following: + + - Installs expand/collapse UI elements on "long" posts and collapses + them. + + - Any pikchr-generated SVGs get a source-toggle button added to them + which activates when the mouse is over the image or it is tapped. + + This is a harmless no-op if the current page has neither forum + post constructs for (1) nor any pikchr images for (2), nor will + NOT running this code cause any breakage for clients with no JS + support: this is all "nice-to-have", not required functionality. + */ + F.onPageLoad(function(){ + const scrollbarIsVisible = (e)=>e.scrollHeight > e.clientHeight; + /* Returns an event handler which implements the post expand/collapse toggle + on contentElem when the given widget is activated. */ + const getWidgetHandler = function(widget, contentElem){ + return function(ev){ + if(ev) ev.preventDefault(); + const wasExpanded = widget.classList.contains('expanded'); + widget.classList.toggle('expanded'); + contentElem.classList.toggle('expanded'); + if(wasExpanded){ + contentElem.classList.add('shrunken'); + contentElem.parentElement.scrollIntoView({ + /* This is non-standard, but !(MSIE, Safari) supposedly support it: + https://developer.mozilla.org/en-US/docs/Web/API/Element/scrollIntoView#Browser_compatibility + */ behavior: 'smooth' + }); + }else{ + contentElem.classList.remove('shrunken'); + } + return false; + }; + }; + + /* Adds an Expand/Collapse toggle to all div.forumPostBody + elements which are deemed "too large" (those for which + scrolling is currently activated because they are taller than + their max-height). */ + document.querySelectorAll( + 'div.forumTime, div.forumEdit' + ).forEach(function f(forumPostWrapper){ + const content = forumPostWrapper.querySelector('div.forumPostBody'); + if(!content || !scrollbarIsVisible(content)) return; + const parent = content.parentElement, + widget = D.addClass( + D.div(), + 'forum-post-collapser','bottom' + ), + rightTapZone = D.addClass( + D.div(), + 'forum-post-collapser','right' + ); + /* Repopulates the rightTapZone with arrow indicators. Because + of the wildly varying height of these elements, This has to + be done dynamically at init time and upon collapse/expand. Will not + work until the rightTapZone has been added to the DOM. */ + const refillTapZone = function f(){ + if(!f.baseTapIndicatorHeight){ + /* To figure out how often to place an arrow in the rightTapZone, + we simply grab the first header element from the page and use + its hight as our basis for calculation. */ + const h1 = document.querySelector('h1, h2'); + f.baseTapIndicatorHeight = h1.getBoundingClientRect().height; + } + D.clearElement(rightTapZone); + var rtzHeight = parseInt(window.getComputedStyle(rightTapZone).height); + do { + D.append(rightTapZone, D.span()); + rtzHeight -= f.baseTapIndicatorHeight * 8; + }while(rtzHeight>0); + }; + const handlerStep1 = getWidgetHandler(widget, content); + const widgetEventHandler = ()=>{ handlerStep1(); refillTapZone(); }; + content.classList.add('with-expander'); + widget.addEventListener('click', widgetEventHandler, false); + /** Append 3 children, which CSS will evenly space across the + widget. This improves visibility over having the label + in only the left, right, or center. */ + var i = 0; + for( ; i < 3; ++i ) D.append(widget, D.span()); + if(content.nextSibling){ + forumPostWrapper.insertBefore(widget, content.nextSibling); + }else{ + forumPostWrapper.appendChild(widget); + } + content.appendChild(rightTapZone); + rightTapZone.addEventListener('click', widgetEventHandler, false); + refillTapZone(); + })/*F.onPageLoad()*/; + + if(F.pikchr){ + F.pikchr.addSrcView(); + } + })/*onload callback*/; + +})(window.fossil); ADDED src/fossil.page.pikchrshow.js Index: src/fossil.page.pikchrshow.js ================================================================== --- /dev/null +++ src/fossil.page.pikchrshow.js @@ -0,0 +1,610 @@ +(function(F/*the fossil object*/){ + "use strict"; + /** + Client-side implementation of the /pikchrshow app. Requires that + the fossil JS bootstrapping is complete and that these fossil JS + APIs have been installed: fossil.fetch, fossil.dom, + fossil.copybutton, fossil.popupwidget, fossil.storage + */ + const E = (s)=>document.querySelector(s), + D = F.dom, + P = F.page; + + P.previewMode = 0 /*0==rendered SVG, 1==pikchr text markdown, + 2==pikchr text fossil, 3==raw SVG. */ + P.response = {/*stashed state for the server's preview response*/ + isError: false, + inputText: undefined /* value of the editor field at render-time */, + raw: undefined /* raw response text/HTML from server */, + rawSvg: undefined /* plain-text SVG part of responses. Required + because the browser will convert \u00a0 to +   if we extract the SVG from the DOM, + resulting in illegal SVG. */ + }; + + /** + If string r contains an SVG element, this returns that section + of the string, else it returns falsy. + */ + const getResponseSvg = function(r){ + const i0 = r.indexOf("=0){ + const i1 = r.indexOf(" legend'), + previewCopyButton: D.attr( + D.addClass(D.span(),'copy-button'), + 'id','preview-copy-button' + ), + previewModeLabel: D.label('preview-copy-button'), + btnSubmit: E('#pikchr-submit-preview'), + btnStash: E('#pikchr-stash'), + btnUnstash: E('#pikchr-unstash'), + btnClearStash: E('#pikchr-clear-stash'), + cbDarkMode: E('#flipcolors-wrapper > input[type=checkbox]'), + taContent: E('#content'), + taPreviewText: D.textarea(20,0,true), + uiControls: E('#pikchrshow-controls'), + previewModeToggle: D.button("Preview mode"), + markupAlignDefault: D.attr(D.radio('markup-align','',true), + 'id','markup-align-default'), + markupAlignCenter: D.attr(D.radio('markup-align','center'), + 'id','markup-align-center'), + markupAlignIndent: D.attr(D.radio('markup-align','indent'), + 'id','markup-align-indent'), + markupAlignWrapper: D.addClass(D.span(), 'input-with-label') + }; + + //////////////////////////////////////////////////////////// + // Setup markup alignment selection... + const alignEvent = function(ev){ + /* Update markdown/fossil wiki preview if it's active */ + if(P.previewMode==1 || P.previewMode==2){ + P.renderPreview(); + } + }; + P.e.markupAlignRadios = [ + P.e.markupAlignDefault, + P.e.markupAlignCenter, + P.e.markupAlignIndent + ]; + D.append(P.e.markupAlignWrapper, + D.addClass(D.append(D.span(),"align:"), + 'v-align-middle')); + P.e.markupAlignRadios.forEach( + function(e){ + e.addEventListener('change', alignEvent, false); + D.append(P.e.markupAlignWrapper, + D.addClass([ + e, + D.label(e, e.value || "left") + ], 'v-align-middle')); + } + ); + + //////////////////////////////////////////////////////////// + // Setup the preview fieldset's LEGEND element... + D.append( P.e.previewLegend, + P.e.previewModeToggle, + '\u00a0', + P.e.previewCopyButton, + P.e.previewModeLabel, + P.e.markupAlignWrapper ); + + //////////////////////////////////////////////////////////// + // Trigger preview on Ctrl-Enter. + P.e.taContent.addEventListener('keydown',function(ev){ + if(ev.ctrlKey && 13 === ev.keyCode) P.preview(); + }, false); + + //////////////////////////////////////////////////////////// + // Setup clipboard-copy of markup/SVG... + F.copyButton(P.e.previewCopyButton, {copyFromElement: P.e.taPreviewText}); + P.e.previewModeLabel.addEventListener('click', ()=>P.e.previewCopyButton.click(), false); + + //////////////////////////////////////////////////////////// + // Set up dark mode simulator... + P.e.cbDarkMode.addEventListener('change', function(ev){ + if(ev.target.checked) D.addClass(P.e.previewTarget, 'dark-mode'); + else D.removeClass(P.e.previewTarget, 'dark-mode'); + }, false); + if(P.e.cbDarkMode.checked) D.addClass(P.e.previewTarget, 'dark-mode'); + + //////////////////////////////////////////////////////////// + // Set up preview update and preview mode toggle... + P.e.btnSubmit.addEventListener('click', ()=>P.preview(), false); + P.e.previewModeToggle.addEventListener('click', function(){ + /* Rotate through the 4 available preview modes */ + P.previewMode = ++P.previewMode % 4; + P.renderPreview(); + }, false); + + //////////////////////////////////////////////////////////// + // Set up selection list of predefined scripts... + if(true){ + const selectScript = P.e.selectScript = D.select(), + cbAutoPreview = P.e.cbAutoPreview = + D.attr(D.checkbox(true),'id', 'cb-auto-preview'), + cbWrap = D.addClass(D.div(),'input-with-label') + ; + D.append( + cbWrap, + selectScript, + cbAutoPreview, + D.label(cbAutoPreview,"Auto-preview?"), + F.helpButtonlets.create( + D.append(D.div(), + 'Auto-preview automatically previews selected ', + 'built-in pikchr scripts by sending them to ', + 'the server for rendering. Not recommended on a ', + 'slow connection/server.', + D.br(),D.br(), + 'Pikchr scripts may also be dragged/dropped from ', + 'the local filesystem into the text area, if the ', + 'environment supports it, but the auto-preview ', + 'option does not apply to them.' + ) + ) + )/*.childNodes.forEach(function(ch){ + ch.style.margin = "0 0.25em"; + })*/; + D.append(P.e.uiControls, cbWrap); + P.predefinedPiks.forEach(function(script,ndx){ + const opt = D.option(script.code ? script.code.trim() :'', script.name); + D.append(selectScript, opt); + opt.$_sampleScript = script /* for response caching purposes */; + if(!ndx) selectScript.selectedIndex = 0 /*timing/ordering workaround*/; + if(!script.code) D.disable(opt); + }); + delete P.predefinedPiks; + selectScript.addEventListener('change', function(ev){ + const val = ev.target.value; + if(!val) return; + const opt = ev.target.selectedOptions[0]; + P.e.taContent.value = val; + if(cbAutoPreview.checked){ + P.preview.$_sampleScript = opt.$_sampleScript; + P.preview(); + } + }, false); + } + + //////////////////////////////////////////////////////////// + // Move dark mode checkbox to the end and add a help buttonlet + D.append( + P.e.uiControls, + D.append( + P.e.cbDarkMode.parentNode/*the .input-with-label element*/, + F.helpButtonlets.create( + D.div(), + 'Dark mode changes the colors of rendered SVG to ', + 'make them more visible in dark-themed skins. ', + 'This only changes (using CSS) how they are rendered, ', + 'not any actual colors written in the script.', + D.br(), D.br(), + 'In some color combinations, certain browsers might ', + 'cause the SVG image to blur considerably with this ', + 'setting enabled!' + ) + ) + ); + + //////////////////////////////////////////////////////////// + // File drag/drop pikchr scripts into P.e.taContent. + // Adapted from: https://stackoverflow.com/a/58677161 + const dropHighlight = P.e.taContent; + const dropEvents = { + drop: function(ev){ + //ev.stopPropagation(); + ev.preventDefault(); + D.removeClass(dropHighlight, 'dragover'); + const file = ev.dataTransfer.files[0]; + if(file) { + const reader = new FileReader(); + reader.addEventListener( + 'load', function(e) {P.e.taContent.value = e.target.result}, false + ); + reader.readAsText(file, "UTF-8"); + } + }, + dragenter: function(ev){ + //ev.stopPropagation(); + ev.preventDefault(); + ev.dataTransfer.dropEffect = "copy"; + D.addClass(dropHighlight, 'dragover'); + //console.debug("dragenter"); + }, + dragover: function(ev){ + //ev.stopPropagation(); + ev.preventDefault(); + //console.debug("dragover"); + }, + dragend: function(ev){ + //ev.stopPropagation(); + ev.preventDefault(); + //console.debug("dragend"); + }, + dragleave: function(ev){ + //ev.stopPropagation(); + ev.preventDefault(); + D.removeClass(dropHighlight, 'dragover'); + //console.debug("dragleave"); + } + }; + /* + The idea here is to accept drops at multiple points or, ideally, + document.body, and apply them to P.e.taContent, but the precise + combination of event handling needed to pull this off is eluding + me. + */ + [P.e.taContent + //P.e.previewTarget,// works only until we drag over the SVG element! + //document.body + /* ideally we'd link only to document.body, but the events seem to + get out of whack, with dropleave being triggered + at unexpected points. */ + ].forEach(function(e){ + Object.keys(dropEvents).forEach( + (k)=>e.addEventListener(k, dropEvents[k], true) + ); + }); + + //////////////////////////////////////////////////////////// + // Setup stash/unstash + const stashKey = 'pikchrshow-stash'; + P.e.btnStash.addEventListener('click', function(){ + const val = P.e.taContent.value; + if(val){ + F.storage.set(stashKey, val); + D.enable(P.e.btnUnstash); + F.toast.message("Stashed pikchr."); + } + }, false); + P.e.btnUnstash.addEventListener('click', function(){ + const val = F.storage.get(stashKey); + P.e.taContent.value = val || ''; + }, false); + P.e.btnClearStash.addEventListener('click', function(){ + F.storage.remove(stashKey); + D.disable(P.e.btnUnstash); + F.toast.message("Cleared pikchr stash."); + }, false); + F.helpButtonlets.create(P.e.btnClearStash.nextElementSibling); + // If we have stashed contents, enable Unstash, else disable it: + if(F.storage.contains(stashKey)) D.enable(P.e.btnUnstash); + else D.disable(P.e.btnUnstash); + + //////////////////////////////////////////////////////////// + // If we start with content, get it in sync with the state + // generated by P.preview(). Normally the server pre-populates it + // with an example. + let needsPreview; + if(!P.e.taContent.value){ + P.e.taContent.value = F.storage.get(stashKey,''); + needsPreview = true; + } + if(P.e.taContent.value){ + /* Fill our "response" state so that renderPreview() can work */ + P.response.inputText = P.e.taContent.value; + P.response.raw = P.e.previewTarget.innerHTML; + P.response.rawSvg = getResponseSvg( + P.response.raw /*note that this is already in the DOM, + which means that the browser has already mangled + \u00a0 to  , so...*/.split(' ').join('\u00a0')); + if(needsPreview) P.preview(); + else{ + /*If it's from the server, it's already rendered, but this + gets all labels/headers in sync.*/ + P.renderPreview(); + } + } + }/*F.onPageLoad()*/); + + /** + Updates the preview view based on the current preview mode and + error state. + */ + P.renderPreview = function f(){ + if(!f.hasOwnProperty('rxNonce')){ + f.rxNonce = /\r?\n?/g /*pikchr nonce comments*/; + f.showMarkupAlignment = function(showIt){ + P.e.markupAlignWrapper.classList[showIt ? 'remove' : 'add']('hidden'); + }; + f.getMarkupAlignmentClass = function(){ + if(P.e.markupAlignCenter.checked) return ' center'; + else if(P.e.markupAlignIndent.checked) return ' indent'; + return ''; + }; + f.getSvgNode = function(txt){ + const childs = D.parseHtml(txt); + const wrapper = childs.filter((e)=>'DIV'===e.tagName)[0]; + return wrapper ? wrapper.querySelector('svg.pikchr') : undefined; + }; + } + const preTgt = this.e.previewTarget; + if(this.response.isError){ + D.append(D.clearElement(preTgt), D.parseHtml(P.response.raw)); + D.addClass(preTgt, 'error'); + this.e.previewModeLabel.innerText = "Error"; + return; + } + D.removeClass(preTgt, 'error'); + D.removeClass(this.e.previewCopyButton, 'disabled'); + D.removeClass(this.e.markupAlignWrapper, 'hidden'); + D.enable(this.e.previewModeToggle, this.e.markupAlignRadios); + let label, svg; + switch(this.previewMode){ + case 0: + label = "SVG"; + f.showMarkupAlignment(false); + D.parseHtml(D.clearElement(preTgt), P.response.raw); + svg = preTgt.querySelector('svg.pikchr'); + if(svg && P.response.rawSvg){ /*for copy button*/ + this.e.taPreviewText.value = P.response.rawSvg; + F.pikchr.addSrcView(svg); + } + break; + case 1: + label = "Markdown"; + f.showMarkupAlignment(true); + this.e.taPreviewText.value = [ + '```pikchr'+f.getMarkupAlignmentClass(), + this.response.inputText.trim(), '```' + ].join('\n'); + D.append(D.clearElement(preTgt), this.e.taPreviewText); + break; + case 2: + label = "Fossil wiki"; + f.showMarkupAlignment(true); + this.e.taPreviewText.value = [ + '', this.response.inputText.trim(), '' + ].join(''); + D.append(D.clearElement(preTgt), this.e.taPreviewText); + break; + case 3: + label = "Raw SVG"; + f.showMarkupAlignment(false); + svg = f.getSvgNode(this.response.raw); + if(svg){ + this.e.taPreviewText.value = + P.response.rawSvg || "Error extracting SVG element."; + }else{ + this.e.taPreviewText.value = "ERROR parsing response HTML:\n"+ + this.response.raw; + console.error("svg parsed HTML nodes:",childs); + } + D.append(D.clearElement(preTgt), this.e.taPreviewText); + break; + } + this.e.previewModeLabel.innerText = label; + }; + + /** + Fetches the preview from the server and updates the preview to + the rendered SVG content or error report. + */ + P.preview = function fp(){ + if(!fp.hasOwnProperty('toDisable')){ + fp.toDisable = [ + /* input elements to disable during ajax operations */ + this.e.btnSubmit, this.e.taContent, + this.e.cbAutoPreview, this.e.selectScript, + this.e.btnStash, this.e.btnClearStash + /* handled separately: previewModeToggle, previewCopyButton, + markupAlignRadios */ + ]; + fp.target = this.e.previewTarget; + fp.updateView = function(c,isError){ + P.previewMode = 0; + P.response.raw = c; + P.response.rawSvg = getResponseSvg(c); + P.response.isError = isError; + D.enable(fp.toDisable); + P.renderPreview(); + }; + } + D.disable(fp.toDisable, this.e.previewModeToggle, this.e.markupAlignRadios); + D.addClass(this.e.markupAlignWrapper, 'hidden'); + D.addClass(this.e.previewCopyButton, 'disabled'); + const content = this.e.taContent.value.trim(); + this.response.raw = this.response.rawSvg = undefined; + this.response.inputText = content; + const sampleScript = fp.$_sampleScript; + delete fp.$_sampleScript; + if(sampleScript && sampleScript.cached){ + fp.updateView(sampleScript.cached, false); + return this; + } + if(!content){ + fp.updateView("No pikchr content!",true); + return this; + } + const self = this; + const fd = new FormData(); + fd.append('ajax', true); + fd.append('content',content); + F.fetch('pikchrshow',{ + payload: fd, + responseHeaders: 'x-pikchrshow-is-error', + onload: (r,isErrHeader)=>{ + const isErr = +isErrHeader ? true : false; + if(!isErr && sampleScript){ + sampleScript.cached = r; + } + fp.updateView(r,isErr); + }, + onerror: (e)=>{ + F.fetch.onerror(e); + fp.updateView("Error fetching preview: "+e, true); + } + }); + return this; + }/*preview()*/; + + /** + Predefined scripts. Each entry is an object: + + { + name: required string, + + code: optional code string. An entry with no code + is treated like a separator in the resulting + SELECT element (a disabled OPTION). + + } + */ + P.predefinedPiks = [ + {name: "-- Example Scripts --"}, +/* + The following were imported from the pikchr test scripts: + + https://fossil-scm.org/pikchr/dir/examples +*/ +{name:"Cardinal headings",code:` linerad = 5px +C: circle "Center" rad 150% + circle "N" at 1.0 n of C; arrow from C to last chop -> + circle "NE" at 1.0 ne of C; arrow from C to last chop <- + circle "E" at 1.0 e of C; arrow from C to last chop <-> + circle "SE" at 1.0 se of C; arrow from C to last chop -> + circle "S" at 1.0 s of C; arrow from C to last chop <- + circle "SW" at 1.0 sw of C; arrow from C to last chop <-> + circle "W" at 1.0 w of C; arrow from C to last chop -> + circle "NW" at 1.0 nw of C; arrow from C to last chop <- + arrow from 2nd circle to 3rd circle chop + arrow from 4th circle to 3rd circle chop + arrow from SW to S chop <-> + circle "ESE" at 2.0 heading 112.5 from Center \ + thickness 150% fill lightblue radius 75% + arrow from Center to ESE thickness 150% <-> chop + arrow from ESE up 1.35 then to NE chop + line dashed <- from E.e to (ESE.x,E.y) + line dotted <-> thickness 50% from N to NW chop +`},{name:"Core object types",code:`AllObjects: [ + +# First row of objects +box "box" +box rad 10px "box (with" "rounded" "corners)" at 1in right of previous +circle "circle" at 1in right of previous +ellipse "ellipse" at 1in right of previous + +# second row of objects +OVAL1: oval "oval" at 1in below first box +oval "(tall &" "thin)" "oval" width OVAL1.height height OVAL1.width \ + at 1in right of previous +cylinder "cylinder" at 1in right of previous +file "file" at 1in right of previous + +# third row shows line-type objects +dot "dot" above at 1in below first oval +line right from 1.8cm right of previous "lines" above +arrow right from 1.8cm right of previous "arrows" above +spline from 1.8cm right of previous \ + go right .15 then .3 heading 30 then .5 heading 160 then .4 heading 20 \ + then right .15 +"splines" at 3rd vertex of previous + +# The third vertex of the spline is not actually on the drawn +# curve. The third vertex is a control point. To see its actual +# position, uncomment the following line: +#dot color red at 3rd vertex of previous spline + +# Draw various lines below the first line +line dashed right from 0.3cm below start of previous line +line dotted right from 0.3cm below start of previous +line thin right from 0.3cm below start of previous +line thick right from 0.3cm below start of previous + + +# Draw arrows with different arrowhead configurations below +# the first arrow +arrow <- right from 0.4cm below start of previous arrow +arrow <-> right from 0.4cm below start of previous + +# Draw splines with different arrowhead configurations below +# the first spline +spline same from .4cm below start of first spline -> +spline same from .4cm below start of previous <- +spline same from .4cm below start of previous <-> + +] # end of AllObjects + +# Label the whole diagram +text "Examples Of Pikchr Objects" big bold at .8cm above north of AllObjects +`},{name:"Swimlanes",code:` $laneh = 0.75 + + # Draw the lanes + down + box width 3.5in height $laneh fill 0xacc9e3 + box same fill 0xc5d8ef + box same as first box + box same as 2nd box + line from 1st box.sw+(0.2,0) up until even with 1st box.n \ + "Alan" above aligned + line from 2nd box.sw+(0.2,0) up until even with 2nd box.n \ + "Betty" above aligned + line from 3rd box.sw+(0.2,0) up until even with 3rd box.n \ + "Charlie" above aligned + line from 4th box.sw+(0.2,0) up until even with 4th box.n \ + "Darlene" above aligned + + # fill in content for the Alice lane + right +A1: circle rad 0.1in at end of first line + (0.2,-0.2) \ + fill white thickness 1.5px "1" + arrow right 50% + circle same "2" + arrow right until even with first box.e - (0.65,0.0) + ellipse "future" fit fill white height 0.2 width 0.5 thickness 1.5px +A3: circle same at A1+(0.8,-0.3) "3" fill 0xc0c0c0 + arrow from A1 to last circle chop "fork!" below aligned + + # content for the Betty lane +B1: circle same as A1 at A1-(0,$laneh) "1" + arrow right 50% + circle same "2" + arrow right until even with first ellipse.w + ellipse same "future" +B3: circle same at A3-(0,$laneh) "3" + arrow right 50% + circle same as A3 "4" + arrow from B1 to 2nd last circle chop + + # content for the Charlie lane +C1: circle same as A1 at B1-(0,$laneh) "1" + arrow 50% + circle same "2" + arrow right 0.8in "goes" "offline" +C5: circle same as A3 "5" + arrow right until even with first ellipse.w \ + "back online" above "pushes 5" below "pulls 3 & 4" below + ellipse same "future" + + # content for the Darlene lane +D1: circle same as A1 at C1-(0,$laneh) "1" + arrow 50% + circle same "2" + arrow right until even with C5.w + circle same "5" + arrow 50% + circle same as A3 "6" + arrow right until even with first ellipse.w + ellipse same "future" +D3: circle same as B3 at B3-(0,2*$laneh) "3" + arrow 50% + circle same "4" + arrow from D1 to D3 chop +`} + + ]; + +})(window.fossil); ADDED src/fossil.page.whistory.js Index: src/fossil.page.whistory.js ================================================================== --- /dev/null +++ src/fossil.page.whistory.js @@ -0,0 +1,161 @@ +/* This script adds interactivity for wiki-history webpages. + * + * The main code is within the 'on-click' handler of the "diff" links. + * Instead of standard redirection it fills-in two hidden inputs with + * the appropriate values and submits the corresponding form. + * A special care should be taken if some intermediate edits are hidden. + * + * For the sake of compatibility with ascetic browsers the code tries + * to avoid modern API and ECMAScript constructs. This makes it less + * readable and may be reconsidered in the future. +*/ +window.addEventListener( 'load', function() { + +document.getElementById("wh-form").method = "GET"; + +var wh_id = document.getElementById("wh-id" ); +var wh_pid = document.getElementById("wh-pid"); +var wh_cleaner = document.getElementById("wh-cleaner"); +var wh_collapser = document.getElementById("wh-collapser"); + +var wh_radios = []; // user-visible controls for baseline selection +var wh_hidden = 0; // current number of hidden (collapsed) rows +var wh_selected = -1; // index of the currently selected radio-button + +var wh_onRadio = function( event ){ + + var indx = event.target.indx; + if( wh_selected == indx ){ + + wh_selected = -1; + event.target.checked = false; + } + else wh_selected = indx; +} +var wh_onDifflink = function( event ){ + + event.preventDefault(); + var indx = event.target.indx; + wh_id.value = wh_radios[indx].value; + + if( wh_hidden > 0 ){ + + var p = indx + 1; + if( wh_selected >= 0 ){ + + var tr = wh_radios[wh_selected].parentElement.parentElement; + if( ! tr.hidden ) + p = wh_selected; + } + while( p < wh_radios.length ){ + + if( ! wh_radios[p].parentElement.parentElement.hidden ) + break; + p++; + } + if( p < wh_radios.length ){ + + wh_pid.value = wh_radios[p].value; + wh_pid.checked = true; + } + else { // just render the wiki for the case of the first major edit + + var path = document.location.pathname.split("/"); + path.pop(); + var newpath = path.join("/") + "/info/" + wh_radios[indx].value; + document.location = document.location.origin + newpath; + return; + } + } + else if( wh_selected >= 0 ) { + + wh_pid.value = wh_radios[wh_selected].value; + wh_pid.checked = true; + } + else wh_pid.checked = false; + + document.getElementById("wh-form").submit(); +} +var wh_onCleaner = function() { + + if( wh_selected >= 0 ) { + + wh_radios[wh_selected].checked = false; + wh_selected = -1; + } +} +var wh_onCollapser = function( event ){ + + var collapsing = ( wh_hidden == 0 ); + for( var k = 0; k < wh_radios.length; k++ ){ + + var radio = wh_radios[k]; + var tr = radio.parentElement.parentElement; + if( tr.className == "wh-intermediate" ){ + + if( tr.hidden = ! tr.hidden ) + wh_hidden++; + else + wh_hidden--; + + } else if( radio.iterspan ) + radio.iterspan.hidden = ! collapsing; + } + if( wh_hidden > 0 ) { + + wh_collapser.title="Show intermediate edits"; + wh_collapser.innerHTML = " ♻ " + wh_hidden; + } + else { + + wh_collapser.title="Hide intermediate edits"; + wh_collapser.innerHTML = " ♲" + } +} + +var inputs = document.getElementsByTagName("input"); +for( var k = 0, indx = 0; k < inputs.length; k++ ) { + + var r = inputs[k]; + if( r.type == "radio" && r.name == "baseline" ) { + + wh_radios.push( r ); + r.indx = indx++; + r.addEventListener( "click", wh_onRadio ); + r.disabled = false; + var td = r.parentElement.nextElementSibling; + r.iterspan = td.getElementsByTagName("span")[0]; + } +} +for( var edits = 0, k = wh_radios.length - 1; k >= 0; k-- ) { + + var td = wh_radios[k].parentElement.nextElementSibling; + if( td.parentElement.className == "wh-intermediate" ) + + edits++; + + else if( edits > 0 ){ + + var span = td.getElementsByTagName("span")[0]; + span.innerHTML = " ♲" + edits; + wh_radios[k].iterspan = span; + edits = 0; + // also: ∪ (union) Σ (sigma) × (times) + } +} +var links = document.getElementsByTagName("a"); +for( var i = 0, indx = 0; i < links.length; i++ ) { + + var l = links[i]; + if( l.className == "wh-difflink" ){ + + l.indx = indx++; + l.addEventListener( "click", wh_onDifflink ); + } +} +wh_cleaner.addEventListener( "click", wh_onCleaner ); +wh_collapser.addEventListener( "click", wh_onCollapser ); +wh_collapser.title="Hide intermediate edits"; +wh_collapser.hidden = false; + +}); // window.addEventListener( 'load' ... ADDED src/fossil.page.wikiedit.js Index: src/fossil.page.wikiedit.js ================================================================== --- /dev/null +++ src/fossil.page.wikiedit.js @@ -0,0 +1,1557 @@ +(function(F/*the fossil object*/){ + "use strict"; + /** + Client-side implementation of the /wikiedit app. Requires that + the fossil JS bootstrapping is complete and that several fossil + JS APIs have been installed: fossil.fetch, fossil.dom, + fossil.tabs, fossil.storage, fossil.confirmer, fossil.popupwidget. + + Custom events which can be listened for via + fossil.page.addEventListener(): + + - Event 'wiki-page-loaded': passes on information when it + loads a wiki (whether from the network or its internal local-edit + cache), in the form of an "winfo" object: + + { + name: string, + mimetype: mimetype string, + type: "normal" | "tag" | "checkin" | "branch" | "sandbox", + version: UUID string or null for a sandbox page or new page, + parent: parent UUID string or null if no parent, + isEmpty: true if page has no content (is "deleted"). + content: string, optional in most contexts + } + + The internal docs and code frequently use the term "winfo", and such + references refer to an object with that form. + + The fossil.page.wikiContent() method gets or sets the current + file content for the page. + + - Event 'wiki-saved': is fired when a commit completes, + passing on the same info as wiki-page-loaded. + + - Event 'wiki-content-replaced': when the editor's content is + replaced, as opposed to it being edited via user + interaction. This normally happens via selecting a file to + load. The event detail is the fossil.page object, not the current + file content. + + - Event 'wiki-preview-updated': when the preview is refreshed + from the server, this event passes on information about the preview + change in the form of an object: + + { + element: the DOM element which contains the content preview. + mimetype: the page's mimetype. + } + + Here's an example which can be used with the highlightjs code + highlighter to update the highlighting when the preview is + refreshed in "wiki" mode (which includes fossil-native wiki and + markdown): + + fossil.page.addEventListener( + 'wiki-preview-updated', + (ev)=>{ + if(ev.detail.mimetype!=='text/plain'){ + ev.detail.element.querySelectorAll( + 'code[class^=language-]' + ).forEach((e)=>hljs.highlightBlock(e)); + } + } + ); + */ + const E = (s)=>document.querySelector(s), + D = F.dom, + P = F.page; + P.config = { + /* Max number of locally-edited pages to stash, after which we + drop the least-recently used. */ + defaultMaxStashSize: 10, + useConfirmerButtons:{ + /* If true during fossil.page setup, certain buttons will use a + "confirmer" step, else they will not. The confirmer topic has + been the source of much contention in the forum. */ + save: false, + reload: true, + discardStash: true + } + }; + + /** + $stash is an internal-use-only object for managing "stashed" + local edits, to help avoid that users accidentally lose content + by switching tabs or following links or some such. The basic + theory of operation is... + + All "stashed" state is stored using fossil.storage. + + - When the current wiki content is modified by the user, the + current state of the page is stashed. + + - When saving, the stashed entry for the previous version is + removed from the stash. + + - When "loading", we use any stashed state for the given + checkin/file combination. When forcing a re-load of content, + any stashed entry for that combination is removed from the + stash. + + - Every time P.stashContentChange() updates the stash, it is + pruned to $stash.prune.defaultMaxCount most-recently-updated + entries. + + - This API often refers to "winfo objects." Those are objects + with a minimum of {page,mimetype} properties (which must be + valid), and the page name is used as basis for the stash keys + for any given page. + + The structure of the stash is a bit convoluted for efficiency's + sake: we store a map of file info (winfo) objects separately from + those files' contents because otherwise we would be required to + JSONize/de-JSONize the file content when stashing/restoring it, + and that would be horribly inefficient (meaning "battery-consuming" + on mobile devices). + */ + const $stash = { + keys: { + index: F.page.name+'.index' + }, + /** + index: { + "PAGE_NAME": {wiki page info w/o content} + ... + } + + In F.storage we... + + - Store this.index under the key this.keys.index. + + - Store each page's content under the key + (P.name+'/PAGE_NAME'). These are stored separately from the + index entries to avoid having to JSONize/de-JSONize the + content. The assumption/hope is that the browser can store + those records "directly," without any intermediary + encoding/decoding going on. + */ + indexKey: function(winfo){return winfo.name}, + /** Returns the key for storing content for the given key suffix, + by prepending P.name to suffix. */ + contentKey: function(suffix){return P.name+'/'+suffix}, + /** Returns the index object, fetching it from the stash or creating + it anew on the first call. */ + getIndex: function(){ + if(!this.index){ + this.index = F.storage.getJSON( + this.keys.index, {} + ); + } + return this.index; + }, + _fireStashEvent: function(){ + if(this._disableNextEvent) delete this._disableNextEvent; + else F.page.dispatchEvent('wiki-stash-updated', this); + }, + /** + Returns the stashed version, if any, for the given winfo object. + */ + getWinfo: function(winfo){ + const ndx = this.getIndex(); + return ndx[this.indexKey(winfo)]; + }, + /** Serializes this object's index to F.storage. Returns this. */ + storeIndex: function(){ + if(this.index) F.storage.setJSON(this.keys.index,this.index); + return this; + }, + /** Updates the stash record for the given winfo + and (optionally) content. If passed 1 arg, only + the winfo stash is updated, else both the winfo + and its contents are (re-)stashed. Returns this. + */ + updateWinfo: function(winfo,content){ + const ndx = this.getIndex(), + key = this.indexKey(winfo), + old = ndx[key]; + const record = old || (ndx[key]={ + name: winfo.name + }); + record.mimetype = winfo.mimetype; + record.type = winfo.type; + record.parent = winfo.parent; + record.version = winfo.version; + record.stashTime = new Date().getTime(); + record.isEmpty = !!winfo.isEmpty; + this.storeIndex(); + if(arguments.length>1){ + if(content) delete record.isEmpty; + F.storage.set(this.contentKey(key), content); + } + this._fireStashEvent(); + return this; + }, + /** + Returns the stashed content, if any, for the given winfo + object. + */ + stashedContent: function(winfo){ + return F.storage.get(this.contentKey(this.indexKey(winfo))); + }, + /** Returns true if we have stashed content for the given winfo + record or page name. */ + hasStashedContent: function(winfo){ + if('string'===typeof winfo) winfo = {name: winfo}; + return F.storage.contains(this.contentKey(this.indexKey(winfo))); + }, + /** Unstashes the given winfo record and its content. + Returns this. */ + unstash: function(winfo){ + const ndx = this.getIndex(), + key = this.indexKey(winfo); + delete winfo.stashTime; + delete ndx[key]; + F.storage.remove(this.contentKey(key)); + this.storeIndex(); + this._fireStashEvent(); + return this; + }, + /** + Clears all $stash entries from F.storage. Returns this. + */ + clear: function(){ + const ndx = this.getIndex(), + self = this; + let count = 0; + Object.keys(ndx).forEach(function(k){ + ++count; + const e = ndx[k]; + delete ndx[k]; + F.storage.remove(self.contentKey(k)); + }); + F.storage.remove(this.keys.index); + delete this.index; + if(count) this._fireStashEvent(); + return this; + }, + /** + Removes all but the maxCount most-recently-updated stash + entries, where maxCount defaults to this.prune.defaultMaxCount. + */ + prune: function f(maxCount){ + const ndx = this.getIndex(); + const li = []; + if(!maxCount || maxCount<0) maxCount = f.defaultMaxCount; + Object.keys(ndx).forEach((k)=>li.push(ndx[k])); + li.sort((l,r)=>l.stashTime - r.stashTime); + let n = 0; + while(li.length>maxCount){ + ++n; + const e = li.shift(); + this._disableNextEvent = true; + this.unstash(e); + console.warn("Pruned oldest local file edit entry:",e); + } + if(n) this._fireStashEvent(); + } + }; + $stash.prune.defaultMaxCount = P.config.defaultMaxStashSize || 10; + P.$stash = $stash /* we have to expose this for the new-page case :/ */; + + /** + Internal workaround to select the current preview mode + and fire a change event if the value actually changes + or if forceEvent is truthy. + */ + P.selectMimetype = function(modeValue, forceEvent){ + const s = this.e.selectMimetype; + if(!modeValue) modeValue = s.value; + else if(s.value != modeValue){ + s.value = modeValue; + forceEvent = true; + } + if(forceEvent){ + // Force UI update + s.dispatchEvent(new Event('change',{target:s})); + } + }; + + /** + Internal helper to get an edit status indicator for the given + winfo object. Pass it a winfo object or one of the "constants" + which are assigned as member properties of this function (see + below its definition). + */ + const getEditMarker = function f(winfo, textOnly){ + const esm = F.config.editStateMarkers; + if(f.NEW===winfo){ /* force is-new */ + return textOnly ? esm.isNew : + D.addClass(D.append(D.span(),esm.isNew), 'is-new'); + }else if(f.MODIFIED===winfo){ /* force is-modified */ + return textOnly ? esm.isModified : + D.addClass(D.append(D.span(),esm.isModified), 'is-modified'); + }else if(f.DELETED===winfo){/* force is-deleted */ + return textOnly ? esm.isDeleted : + D.addClass(D.append(D.span(),esm.isDeleted), 'is-deleted'); + }else if(winfo && winfo.version){ /* is existing page modified? */ + if($stash.getWinfo(winfo)){ + return textOnly ? esm.isModified : + D.addClass(D.append(D.span(),esm.isModified), 'is-modified'); + } + /*fall through*/ + } + else if(winfo){ /* is new non-sandbox or is modified sandbox? */ + if('sandbox'!==winfo.type){ + return textOnly ? esm.isNew : + D.addClass(D.append(D.span(),esm.isNew), 'is-new'); + }else if($stash.getWinfo(winfo)){ + return textOnly ? esm.isModified : + D.addClass(D.append(D.span(),esm.isModified), 'is-modified'); + } + } + return textOnly ? '' : D.span(); + }; + getEditMarker.NEW = 1; + getEditMarker.MODIFIED = 2; + getEditMarker.DELETED = 3; + + /** + Returns undefined if winfo is falsy, true if the given winfo + object appears to be "new", else returns false. + */ + const winfoIsNew = function(winfo){ + if(!winfo) return undefined; + else if('sandbox' === winfo.type) return false; + else return !winfo.version; + }; + + /** + Sets up and maintains the widgets for the list of wiki pages. + */ + const WikiList = { + e: { + filterCheckboxes: { + /*map of wiki page type to checkbox for list filtering purposes, + except for "sandbox" type, which is assumed to be covered by + the "normal" type filter. */}, + }, + cache: { + pageList: [], + optByName:{/*map of page names to OPTION object, to speed up + certain operations.*/}, + names: { + /* Map of page names to "something." We don't map to their + winfo bits because those regularly get swapped out via + de/serialization. We need this map to support the add-new-page + feature, to give us a way to check for dupes without asking + the server or walking through the whole selection list. + */} + }, + /** + Updates OPTION elements to reflect whether the page has local + changes or is new/unsaved. This implementation is horribly + inefficient, in that we have to walk and validate the whole + list for each stash-level change. + + If passed an argument, it is assumed to be an OPTION element + and only that element is updated, else all OPTION elements + in this.e.select are updated. + + Reminder to self: in order to mark is-edited/is-new state we + have to update the OPTION element's inner text to reflect the + is-modified/is-new flags, rather than use CSS classes to tag + them, because mobile Chrome can neither restyle OPTION elements + no render ::before content on them. We *also* use CSS tags, but + they aren't sufficient for the mobile browsers. + */ + _refreshStashMarks: function callee(option){ + if(!callee.eachOpt){ + const self = this; + callee.eachOpt = function(keyOrOpt){ + const opt = 'string'===typeof keyOrOpt ? self.e.select.options[keyOrOpt] : keyOrOpt; + const stashed = $stash.getWinfo({name:opt.value}); + var prefix = ''; + D.removeClass(opt, 'stashed', 'stashed-new', 'deleted'); + if(stashed){ + const isNew = winfoIsNew(stashed); + prefix = getEditMarker(isNew ? getEditMarker.NEW : getEditMarker.MODIFIED, true); + D.addClass(opt, isNew ? 'stashed-new' : 'stashed'); + D.removeClass(opt, 'deleted'); + }else if(opt.dataset.isDeleted){ + prefix = getEditMarker(getEditMarker.DELETED,true); + D.addClass(opt, 'deleted'); + } + opt.innerText = prefix + opt.value; + self.cache.names[opt.value] = true; + }; + } + if(arguments.length){ + callee.eachOpt(option); + }else{ + this.cache.names = {/*must reset it to acount for local page removals*/}; + Object.keys(this.e.select.options).forEach(callee.eachOpt); + } + }, + /** Removes the given wiki page entry from the page selection + list, if it's in the list. */ + removeEntry: function(name){ + const sel = this.e.select; + var ndx = sel.selectedIndex; + sel.value = name; + if(sel.selectedIndex>-1){ + if(ndx === sel.selectedIndex) ndx = -1; + sel.options.remove(sel.selectedIndex); + } + sel.selectedIndex = ndx; + delete this.cache.names[name]; + delete this.cache.optByName[name]; + this.cache.pageList = this.cache.pageList.filter((wi)=>name !== wi.name); + }, + + /** + Rebuilds the selection list. Necessary when it's loaded from + the server, we locally create a new page, or we remove a + locally-created new page. + */ + _rebuildList: function callee(){ + /* Jump through some hoops to integrate new/unsaved + pages into the list of existing pages... We use a map + as an intermediary in order to filter out any local-stash + dupes from server-side copies. */ + const list = this.cache.pageList; + if(!list) return; + if(!callee.sorticase){ + callee.sorticase = function(l,r){ + if(l===r) return 0; + l = l.toLowerCase(); + r = r.toLowerCase(); + return l<=r ? -1 : 1; + }; + } + const map = {}, ndx = $stash.getIndex(), sel = this.e.select; + D.clearElement(sel); + list.forEach((winfo)=>map[winfo.name] = winfo); + Object.keys(ndx).forEach(function(key){ + const winfo = ndx[key]; + if(!winfo.version/*new page*/) map[winfo.name] = winfo; + }); + const self = this; + Object.keys(map) + .sort(callee.sorticase) + .forEach(function(name){ + const winfo = map[name]; + const opt = D.option(sel, winfo.name); + const wtype = opt.dataset.wtype = + winfo.type==='sandbox' ? 'normal' : (winfo.type||'normal'); + const cb = self.e.filterCheckboxes[wtype]; + self.cache.optByName[winfo.name] = opt; + if(cb && !cb.checked) D.addClass(opt, 'hidden'); + if(winfo.isEmpty){ + opt.dataset.isDeleted = true; + } + self._refreshStashMarks(opt); + }); + D.enable(sel); + if(P.winfo) sel.value = P.winfo.name; + }, + + /** Loads the page list and populates the selection list. */ + loadList: function callee(){ + if(!callee.onload){ + const self = this; + callee.onload = function(list){ + self.cache.pageList = list; + self._rebuildList(); + F.message("Loaded page list."); + }; + } + if(P.initialPageList){ + /* ^^^ injected at page-creation time. */ + const list = P.initialPageList; + delete P.initialPageList; + callee.onload(list); + }else{ + F.fetch('wikiajax/list',{ + urlParams:{verbose:true}, + responseType: 'json', + onload: callee.onload + }); + } + return this; + }, + + /** + Returns true if the given name appears to be a valid + wiki page name, noting that the final arbitrator is the + server. On validation error it emits a message via fossil.error() + and returns false. + */ + validatePageName: function(name){ + var err; + if(!name){ + err = "may not be empty"; + }else if(this.cache.names.hasOwnProperty(name)){ + err = "page already exists: "+name; + }else if(name.length>100){ + err = "too long (limit is 100)"; + }else if(/\s{2,}/.test(name)){ + err = "multiple consecutive spaces"; + }else if(/[\t\r\n]/.test(name)){ + err = "contains control character(s)"; + }else{ + let i = 0, n = name.length, c; + for( ; i < n; ++i ){ + if(name.charCodeAt(i)<0x20){ + err = "contains control character(s)"; + break; + } + } + } + if(err){ + F.error("Invalid name:",err); + } + return !err; + }, + + /** + If the given name is valid, a new page with that (trimmed) name + is added to the local stash. + */ + addNewPage: function(name){ + name = name.trim(); + if(!this.validatePageName(name)) return false; + var wtype = 'normal'; + if(0===name.indexOf('checkin/')) wtype = 'checkin'; + else if(0===name.indexOf('branch/')) wtype = 'branch'; + else if(0===name.indexOf('tag/')) wtype = 'tag'; + /* ^^^ note that we're not validating that, e.g., checkin/XYZ + has a full artifact ID after "checkin/". */ + const winfo = { + name: name, type: wtype, mimetype: 'text/x-fossil-wiki', + version: null, parent: null + }; + this.cache.pageList.push( + winfo/*keeps entry from getting lost from the list on save*/ + ); + $stash.updateWinfo(winfo, ''); + this._rebuildList(); + P.loadPage(winfo.name); + return true; + }, + + /** + Installs a wiki page selection list into the given parent DOM + element and loads the page list from the server. + */ + init: function(parentElem){ + const sel = D.select(), btn = D.addClass(D.button("Reload page list"), 'save'); + this.e.select = sel; + D.addClass(parentElem, 'WikiList'); + D.clearElement(parentElem); + D.append( + parentElem, + D.append(D.fieldset("Select a page to edit"), + sel) + ); + D.attr(sel, 'size', 12); + D.option(D.disable(D.clearElement(sel)), undefined, "Loading..."); + + /** Set up filter checkboxes for the various types + of wiki pages... */ + const fsFilter = D.addClass(D.fieldset("Page types"),"page-types-list"), + fsFilterBody = D.div(), + filters = ['normal', 'branch/...', 'tag/...', 'checkin/...'] + ; + D.append(fsFilter, fsFilterBody); + D.addClass(fsFilterBody, 'flex-container', 'flex-column', 'stretch'); + + // Add filters by page type... + const self = this; + const filterByType = function(wtype, show){ + sel.querySelectorAll('option[data-wtype='+wtype+']').forEach(function(opt){ + if(show) opt.classList.remove('hidden'); + else opt.classList.add('hidden'); + }); + }; + filters.forEach(function(label){ + const wtype = label.split('/')[0]; + const cbId = 'wtype-filter-'+wtype, + lbl = D.attr(D.append(D.label(),label), + 'for', cbId), + cb = D.attr(D.input('checkbox'), 'id', cbId); + D.append(fsFilterBody, D.append(D.span(), cb, lbl)); + self.e.filterCheckboxes[wtype] = cb; + cb.checked = true; + filterByType(wtype, cb.checked); + cb.addEventListener( + 'change', + function(ev){filterByType(wtype, ev.target.checked)}, + false + ); + }); + { /* add filter for "deleted" pages */ + const cbId = 'wtype-filter-deleted', + lbl = D.attr(D.append(D.label(), + getEditMarker(getEditMarker.DELETED,false), + 'deleted'), + 'for', cbId), + cb = D.attr(D.input('checkbox'), 'id', cbId); + cb.checked = false; + D.addClass(parentElem,'hide-deleted'); + D.attr(lbl); + const deletedTip = F.helpButtonlets.create( + D.span(), + 'Fossil considers empty pages to be "deleted" in some contexts.' + ); + D.append(fsFilterBody, D.append( + D.span(), cb, lbl, deletedTip + )); + cb.addEventListener( + 'change', + function(ev){ + if(ev.target.checked) D.removeClass(parentElem,'hide-deleted'); + else D.addClass(parentElem,'hide-deleted'); + }, + false); + } + /* A legend of the meanings of the symbols we use in + the OPTION elements to denote certain state. */ + const fsLegend = D.fieldset("Edit status"), + fsLegendBody = D.div(); + D.append(fsLegend, fsLegendBody); + D.addClass(fsLegendBody, 'flex-container', 'flex-column', 'stretch'); + D.append( + fsLegendBody, + D.append(D.span(), getEditMarker(getEditMarker.NEW,false)," = new/unsaved"), + D.append(D.span(), getEditMarker(getEditMarker.MODIFIED,false)," = has local edits"), + D.append(D.span(), getEditMarker(getEditMarker.DELETED,false)," = is empty (deleted)") + ); + + const fsNewPage = D.fieldset("Create new page"), + fsNewPageBody = D.div(), + newPageName = D.input('text'), + newPageBtn = D.button("Add page locally") + ; + D.append(parentElem, fsNewPage); + D.append(fsNewPage, fsNewPageBody); + D.addClass(fsNewPageBody, 'flex-container', 'flex-column', 'new-page'); + D.append( + fsNewPageBody, newPageName, newPageBtn, + D.append(D.addClass(D.span(), 'mini-tip'), + "New pages exist only in this browser until they are saved.") + ); + newPageBtn.addEventListener('click', function(){ + if(self.addNewPage(newPageName.value)){ + newPageName.value = ''; + } + }, false); + + D.append( + parentElem, + D.append(D.addClass(D.div(), 'fieldset-wrapper'), + fsFilter, fsNewPage, fsLegend) + ); + + D.append(parentElem, btn); + btn.addEventListener('click', ()=>this.loadList(), false); + this.loadList(); + const onSelect = (e)=>P.loadPage(e.target.value); + sel.addEventListener('change', onSelect, false); + sel.addEventListener('dblclick', onSelect, false); + F.page.addEventListener('wiki-stash-updated', ()=>{ + if(P.winfo) this._refreshStashMarks(); + else this._rebuildList(); + }); + F.page.addEventListener('wiki-page-loaded', function(ev){ + /* Needed to handle the saved-an-empty-page case. */ + const page = ev.detail, + opt = self.cache.optByName[page.name]; + if(opt){ + if(page.isEmpty) opt.dataset.isDeleted = true; + else delete opt.dataset.isDeleted; + self._refreshStashMarks(opt); + }else if('sandbox'!==page.type){ + F.error("BUG: internal mis-handling of page object: missing OPTION for page "+page.name); + } + }); + delete this.init; + } + }; + + /** + Widget for listing and selecting $stash entries. + */ + P.stashWidget = { + e:{/*DOM element(s)*/}, + init: function(domInsertPoint/*insert widget BEFORE this element*/){ + const wrapper = D.addClass( + D.attr(D.div(),'id','wikiedit-stash-selector'), + 'input-with-label' + ); + const sel = this.e.select = D.select(), + btnClear = this.e.btnClear = D.button("Discard Edits"), + btnHelp = D.append( + D.addClass(D.div(), "help-buttonlet"), + 'Locally-edited wiki pages. Timestamps are the last local edit time. ', + 'Only the ',P.config.defaultMaxStashSize,' most recent pages ', + 'are retained. Saving or reloading a file removes it from this list. ', + D.append(D.code(),F.storage.storageImplName()), + ' = ',F.storage.storageHelpDescription() + ); + D.append(wrapper, "Local edits (", + D.append(D.code(), + F.storage.storageImplName()), + "):", + btnHelp, sel, btnClear); + F.helpButtonlets.setup(btnHelp); + D.option(D.disable(sel), undefined, "(empty)"); + P.addEventListener('wiki-stash-updated',(e)=>this.updateList(e.detail)); + P.addEventListener('wiki-page-loaded',(e)=>this.updateList($stash, e.detail)); + sel.addEventListener('change',function(e){ + const opt = this.selectedOptions[0]; + if(opt && opt._winfo) P.loadPage(opt._winfo); + }); + if(F.storage.isTransient()){/*Warn if our storage is particularly transient...*/ + D.append(wrapper, D.append( + D.addClass(D.span(),'warning'), + "Warning: persistent storage is not available, "+ + "so uncomitted edits will not survive a page reload." + )); + } + domInsertPoint.parentNode.insertBefore(wrapper, domInsertPoint); + if(P.config.useConfirmerButtons.discardStash){ + /* Must come after btnClear is in the DOM AND the button must + not be hidden, else pinned sizing won't work. */ + F.confirmer(btnClear, { + pinSize: true, + confirmText: "DISCARD all local edits?", + onconfirm: ()=>P.clearStash(), + ticks: F.config.confirmerButtonTicks + }); + }else{ + btnClear.addEventListener('click', ()=>P.clearStash(), false); + } + D.addClass(btnClear,'hidden'); + $stash._fireStashEvent(/*read the page-load-time stash*/); + delete this.init; + }, + /** + Regenerates the edit selection list. + */ + updateList: function f(stasher,theWinfo){ + if(!f.compare){ + const cmpBase = (l,r)=>lcmpBase(l.name.toLowerCase(), r.name.toLowerCase()); + f.rxZ = /\.\d+Z$/ /* ms and 'Z' part of date string */; + const pad=(x)=>(''+x).length>1 ? x : '0'+x; + f.timestring = function(d){ + return [ + d.getFullYear(),'-',pad(d.getMonth()+1/*sigh*/),'-',pad(d.getDate()), + '@',pad(d.getHours()),':',pad(d.getMinutes()) + ].join(''); + }; + } + const index = stasher.getIndex(), ilist = []; + Object.keys(index).forEach((winfo)=>{ + ilist.push(index[winfo]); + }); + const self = this; + D.clearElement(this.e.select); + if(0===ilist.length){ + D.addClass(this.e.btnClear, 'hidden'); + D.option(D.disable(this.e.select),undefined,"No local edits"); + return; + } + D.enable(this.e.select); + if(true){ + /* The problem with this Clear button is that it allows the + user to nuke a non-empty newly-added page without the + failsafe confirmation we have if they use + P.e.btnReload. Not yet sure how best to resolve that. */ + D.removeClass(this.e.btnClear, 'hidden'); + } + D.disable(D.option(this.e.select,undefined,"Select a local edit...")); + const currentWinfo = theWinfo || P.winfo || {name:''}; + ilist.sort(f.compare).forEach(function(winfo,n){ + const key = stasher.indexKey(winfo), + rev = winfo.version || ''; + const opt = D.option( + self.e.select, n+1/*value is (almost) irrelevant*/, + [winfo.name, + ' [', + rev ? F.hashDigits(rev) : ( + winfo.type==='sandbox' ? 'sandbox' : 'new/local' + ),'] ', + f.timestring(new Date(winfo.stashTime)) + ].join('') + ); + opt._winfo = winfo; + if(0===f.compare(currentWinfo, winfo)){ + D.attr(opt, 'selected', true); + } + }); + } + }/*P.stashWidget*/; + + /** + Keep track of how many in-flight AJAX requests there are so we + can disable input elements while any are pending. For + simplicity's sake we simply disable ALL OF IT while any AJAX is + pending, rather than disabling operation-specific UI elements, + which would be a huge maintenance hassle. + + Noting, however, that this global on/off is not *quite* + pedantically correct. Pedantically speaking. If an element is + disabled before an XHR starts, this code "should" notice that and + not include it in the to-re-enable list. That would be annoying + to do, and becomes impossible to do properly once multiple XHRs + are in transit and an element is disabled seprately between two + of those in-transit requests (that would be an unlikely, but + possible, corner case). + */ + const ajaxState = { + count: 0 /* in-flight F.fetch() requests */, + toDisable: undefined /* elements to disable during ajax activity */ + }; + F.fetch.beforesend = function f(){ + if(!ajaxState.toDisable){ + ajaxState.toDisable = document.querySelectorAll( + ['button:not([disabled])', + 'input:not([disabled])', + 'select:not([disabled])', + 'textarea:not([disabled])' + ].join(',') + ); + } + if(1===++ajaxState.count){ + D.addClass(document.body, 'waiting'); + D.disable(ajaxState.toDisable); + } + }; + F.fetch.aftersend = function(){ + if(0===--ajaxState.count){ + D.removeClass(document.body, 'waiting'); + D.enable(ajaxState.toDisable); + delete ajaxState.toDisable /* required to avoid enable/disable + race condition with the save button */; + } + }; + + F.onPageLoad(function() { + document.body.classList.add('wikiedit'); + P.base = {tag: E('base'), wikiUrl: F.repoUrl('wiki')}; + P.base.originalHref = P.base.tag.href; + P.e = { /* various DOM elements we work with... */ + taEditor: E('#wikiedit-content-editor'), + btnReload: E("#wikiedit-tab-content button.wikiedit-content-reload"), + btnSave: E("button.wikiedit-save"), + btnSaveClose: E("button.wikiedit-save-close"), + selectMimetype: E('select[name=mimetype]'), + selectFontSizeWrap: E('#select-font-size'), +// selectDiffWS: E('select[name=diff_ws]'), + cbAutoPreview: E('#cb-preview-autorefresh'), + previewTarget: E('#wikiedit-tab-preview-wrapper'), + diffTarget: E('#wikiedit-tab-diff-wrapper'), + editStatus: E('#wikiedit-edit-status'), + tabContainer: E('#wikiedit-tabs'), + tabs:{ + pageList: E('#wikiedit-tab-pages'), + content: E('#wikiedit-tab-content'), + preview: E('#wikiedit-tab-preview'), + diff: E('#wikiedit-tab-diff'), + misc: E('#wikiedit-tab-misc') + //commit: E('#wikiedit-tab-commit') + } + }; + P.tabs = new F.TabManager(D.clearElement(P.e.tabContainer)); + /* Move the status bar between the tab buttons and + tab panels. Seems to be the best fit in terms of + functionality and visibility. */ + P.tabs.addCustomWidget( E('#fossil-status-bar') ).addCustomWidget(P.e.editStatus); + let currentTab/*used for ctrl-enter switch between editor and preview*/; + P.tabs.addEventListener( + /* Set up some before-switch-to tab event tasks... */ + 'before-switch-to', function(ev){ + const theTab = currentTab = ev.detail, btnSlot = theTab.querySelector('.save-button-slot'); + if(btnSlot){ + /* Several places make sense for a save button, so we'll + move that button around to those tabs where it makes sense. */ + btnSlot.parentNode.insertBefore( P.e.btnSave.parentNode, btnSlot ); + btnSlot.parentNode.insertBefore( P.e.btnSaveClose.parentNode, btnSlot ); + P.updateSaveButton(); + } + if(theTab===P.e.tabs.preview){ + P.baseHrefForWiki(); + if(P.previewNeedsUpdate && P.e.cbAutoPreview.checked) P.preview(); + }else if(theTab===P.e.tabs.diff){ + /* Work around a weird bug where the page gets wider than + the window when the diff tab is NOT in view and the + current SBS diff widget is wider than the window. When + the diff IS in view then CSS overflow magically reduces + the page size again. Weird. Maybe FF-specific. Note that + this weirdness happens even though P.e.diffTarget's parent + is hidden (and therefore P.e.diffTarget is also hidden). + */ + D.removeClass(P.e.diffTarget, 'hidden'); + } + } + ); + P.tabs.addEventListener( + /* Set up auto-refresh of the preview tab... */ + 'before-switch-from', function(ev){ + const theTab = ev.detail; + if(theTab===P.e.tabs.preview){ + P.baseHrefRestore(); + }else if(theTab===P.e.tabs.diff){ + /* See notes in the before-switch-to handler. */ + D.addClass(P.e.diffTarget, 'hidden'); + } + } + ); + //////////////////////////////////////////////////////////// + // Trigger preview on Ctrl-Enter. This only works on the built-in + // editor widget, not a client-provided one. + P.e.taEditor.addEventListener('keydown',function(ev){ + if(ev.ctrlKey && 13 === ev.keyCode){ + ev.preventDefault(); + ev.stopPropagation(); + P.e.taEditor.blur(/*force change event, if needed*/); + P.tabs.switchToTab(P.e.tabs.preview); + if(!P.e.cbAutoPreview.checked){/* If NOT in auto-preview mode, trigger an update. */ + P.preview(); + } + } + }, false); + // If we're in the preview tab, have ctrl-enter switch back to the editor. + document.body.addEventListener('keydown',function(ev){ + if(ev.ctrlKey && 13 === ev.keyCode){ + if(currentTab === P.e.tabs.preview){ + //ev.preventDefault(); + //ev.stopPropagation(); + P.tabs.switchToTab(P.e.tabs.content); + P.e.taEditor.focus(/*doesn't work for client-supplied editor widget! + And it's slow as molasses for long docs, as focus() + forces a document reflow. */); + //console.debug("BODY ctrl-enter"); + } + } + }, true); + + F.connectPagePreviewers( + P.e.tabs.preview.querySelector( + '#btn-preview-refresh' + ) + ); + + const diffButtons = E('#wikiedit-tab-diff-buttons'); + diffButtons.querySelector('button.sbs').addEventListener( + "click",(e)=>P.diff(true), false + ); + diffButtons.querySelector('button.unified').addEventListener( + "click",(e)=>P.diff(false), false + ); + if(0) P.e.btnCommit.addEventListener( + "click",(e)=>P.commit(), false + ); + const doSave = function(alsoClose){ + const w = P.winfo; + if(!w){ + F.error("No page loaded."); + return; + } + if(alsoClose){ + P.save(()=>window.location.href=F.repoUrl('wiki',{name: w.name})); + }else{ + P.save(); + } + }; + const doReload = function(e){ + const w = P.winfo; + if(!w){ + F.error("No page loaded."); + return; + } + if(!w.version/* new/unsaved page */ + && w.type!=='sandbox' + && P.wikiContent()){ + F.error("This new/unsaved page has content.", + "To really discard this page,", + "first clear its content", + "then use the Discard button."); + return; + } + P.unstashContent(); + if(w.version || w.type==='sandbox'){ + P.loadPage(w); + }else{ + WikiList.removeEntry(w.name); + delete P.winfo; + P.updatePageTitle(); + F.message("Discarded new page ["+w.name+"]."); + } + }; + + if(P.config.useConfirmerButtons.reload){ + P.tabs.switchToTab(1/*DOM visibility workaround*/); + F.confirmer(P.e.btnReload, { + pinSize: true, + confirmText: "Really reload, losing edits?", + onconfirm: doReload, + ticks: F.config.confirmerButtonTicks + }); + }else{ + P.e.btnReload.addEventListener('click', doReload, false); + } + if(P.config.useConfirmerButtons.save){ + P.tabs.switchToTab(1/*DOM visibility workaround*/); + F.confirmer(P.e.btnSave, { + pinSize: true, + confirmText: "Really save changes?", + onconfirm: ()=>doSave(), + ticks: F.config.confirmerButtonTicks + }); + F.confirmer(P.e.btnSaveClose, { + pinSize: true, + confirmText: "Really save changes?", + onconfirm: ()=>doSave(true), + ticks: F.config.confirmerButtonTicks + }); + }else{ + P.e.btnSave.addEventListener('click', ()=>doSave(), false); + P.e.btnSaveClose.addEventListener('click', ()=>doSave(true), false); + } + + P.e.taEditor.addEventListener('change', ()=>P.notifyOfChange(), false); + + P.selectMimetype(false, true); + P.e.selectMimetype.addEventListener( + 'change', + function(e){ + if(P.winfo && P.winfo.mimetype !== e.target.value){ + P.winfo.mimetype = e.target.value; + P._isDirty = true; + P.stashContentChange(true); + } + }, + false + ); + + const selectFontSize = E('select[name=editor_font_size]'); + if(selectFontSize){ + selectFontSize.addEventListener( + "change",function(e){ + const ed = P.e.taEditor; + ed.className = ed.className.replace( + /\bfont-size-\d+/g, '' ); + ed.classList.add('font-size-'+e.target.value); + }, false + ); + selectFontSize.dispatchEvent( + // Force UI update + new Event('change',{target:selectFontSize}) + ); + } + + P.addEventListener( + // Clear certain views when new content is loaded/set + 'wiki-content-replaced', + ()=>{ + P.previewNeedsUpdate = true; + D.clearElement(P.e.diffTarget, P.e.previewTarget); + } + ); + P.addEventListener( + // Clear certain views after a save + 'wiki-saved', + (e)=>{ + D.clearElement(P.e.diffTarget, P.e.previewTarget); + // TODO: replace preview with new content + } + ); + P.addEventListener('wiki-stash-updated',function(){ + /* MUST come before WikiList.init() and P.stashWidget.init() so + that interwoven event handlers get called in the right + order. */ + if(P.winfo && !P.winfo.version && !$stash.getWinfo(P.winfo)){ + // New local page was removed. + delete P.winfo; + P.wikiContent(''); + P.updatePageTitle(); + } + P.updateSaveButton(); + }).updatePageTitle().updateSaveButton(); + + P.addEventListener( + // Update various state on wiki page load + 'wiki-page-loaded', + function(ev){ + delete P._isDirty; + const winfo = ev.detail; + P.winfo = winfo; + P.previewNeedsUpdate = true; + P.e.selectMimetype.value = winfo.mimetype; + P.tabs.switchToTab(P.e.tabs.content); + P.wikiContent(winfo.content || ''); + WikiList.e.select.value = winfo.name; + if(!winfo.version && winfo.type!=='sandbox'){ + F.message('You are editing a new, unsaved page:',winfo.name); + } + P.updatePageTitle().updateSaveButton(/* b/c save() routes through here */); + }, + false + ); + /* These init()s need to come after P's event handlers are registered. + The tab-switching is a workaround for the pinSize option of the confirmer widgets: + it does not work if the confirmer button being initialized is in a hidden + part of the DOM :/. */ + P.tabs.switchToTab(0); + WikiList.init( P.e.tabs.pageList.firstElementChild ); + P.tabs.switchToTab(1); + P.stashWidget.init(P.e.tabs.content.lastElementChild); + P.tabs.switchToTab(0); + //P.$wikiList = WikiList/*only for testing/debugging*/; + }/*F.onPageLoad()*/); + + /** + Returns true if fossil.page.winfo is set, indicating that a page + has been loaded, else it reports an error and returns false. + + If passed a truthy value any error message about not having + a wiki page loaded is suppressed. + */ + const affirmPageLoaded = function(quiet){ + if(!P.winfo && !quiet) F.error("No wiki page is loaded."); + return !!P.winfo; + }; + + /** Updates the in-tab title/edit status information */ + P.updateEditStatus = function f(){ + if(!f.eLinks){ + f.eName = P.e.editStatus.querySelector('span.name'); + f.eLinks = P.e.editStatus.querySelector('span.links'); + } + const wi = this.winfo; + D.clearElement(f.eName, f.eLinks); + if(!wi){ + D.append(f.eName, '(no page loaded)'); + return; + } + var marker = getEditMarker(wi, false); + D.append(f.eName,marker,wi.name); + if(wi.version){ + D.append( + f.eLinks, + D.a(F.repoUrl('wiki',{name:wi.name}),"viewer"), + D.a(F.repoUrl('whistory',{name:wi.name}),'history'), + D.a(F.repoUrl('attachlist',{page:wi.name}),"attachments"), + D.a(F.repoUrl('attachadd',{page:wi.name,from: F.repoUrl('wikiedit',{name: wi.name})}), "attach"), + D.a(F.repoUrl('wikiedit',{name:wi.name}),"editor permalink") + ); + } + }; + + /** + Update the page title and header based on the state of + this.winfo. A no-op if this.winfo is not set. Returns this. + */ + P.updatePageTitle = function f(){ + if(!f.titleElement){ + f.titleElement = document.head.querySelector('title'); + } + const wi = P.winfo, marker = getEditMarker(wi, true), + title = wi ? wi.name : 'no page loaded'; + f.titleElement.innerText = 'Wiki Editor: ' + marker + title; + this.updateEditStatus(); + return this; + }; + + /** + Change the save button depending on whether we have stuff to save + or not. + */ + P.updateSaveButton = function(){ + /** + // Currently disabled, per forum feedback and platform-level + // event-handling compatibility, but might be revisited. We now + // use an is-dirty flag instead to prevent saving when no change + // event has fired for the current doc. + if(!this.winfo || !this.getStashedWinfo(this.winfo)){ + D.disable(this.e.btnSave).innerText = + "No changes to save"; + D.disable(this.e.btnSaveClose); + }else{ + D.enable(this.e.btnSave).innerText = "Save"; + D.enable(this.e.btnSaveClose); + }*/ + return this; + }; + + /** + Getter (if called with no args) or setter (if passed an arg) for + the current file content. + + The setter form sets the content, dispatches a + 'wiki-content-replaced' event, and returns this object. + */ + P.wikiContent = function f(){ + if(0===arguments.length){ + return f.get(); + }else{ + f.set(arguments[0] || ''); + this.dispatchEvent('wiki-content-replaced', this); + return this; + } + }; + /* Default get/set impls for file content */ + P.wikiContent.get = function(){return P.e.taEditor.value}; + P.wikiContent.set = function(content){P.e.taEditor.value = content}; + + /** + For use when installing a custom editor widget. Pass it the + getter and setter callbacks to fetch resp. set the content of the + custom widget. They will be triggered via + P.wikiContent(). Returns this object. + */ + P.setContentMethods = function(getter, setter){ + this.wikiContent.get = getter; + this.wikiContent.set = setter; + return this; + }; + + /** + Alerts the editor app that a "change" has happened in the editor. + When connecting 3rd-party editor widgets to this app, it is + necessary to call this for any "change" events the widget emits. + Whether or not "change" means that there were "really" edits is + irrelevant, but this app will not allow saving unless it believes + at least one "change" has been made (by being signaled through + this method). + + This function may perform an arbitrary amount of work, so it + should not be called for every keypress within the editor + widget. Calling it for "blur" events is generally sufficient, and + calling it for each Enter keypress is generally reasonable but + also computationally costly. + */ + P.notifyOfChange = function(){ + P._isDirty = true; + P.stashContentChange(); + }; + + /** + Removes the default editor widget (and any dependent elements) + from the DOM, adds the given element in its place, removes this + method from this object, and returns this object. This is not + needed if the 3rd-party widget replaces or hides this app's + editor widget (e.g. TinyMCE). + */ + P.replaceEditorElement = function(newEditor){ + P.e.taEditor.parentNode.insertBefore(newEditor, P.e.taEditor); + P.e.taEditor.remove(); + P.e.selectFontSizeWrap.remove(); + delete this.replaceEditorElement; + return P; + }; + + /** + Sets the current page's base.href to {g.zTop}/wiki. + */ + P.baseHrefForWiki = function f(){ + this.base.tag.href = this.base.wikiUrl; + return this; + }; + + /** + Sets the document's base.href value to its page-load-time + setting. + */ + P.baseHrefRestore = function(){ + this.base.tag.href = this.base.originalHref; + }; + + + /** + loadPage() loads the given wiki page and updates the relevant + UI elements to reflect the loaded state. If passed no arguments + then it re-uses the values from the currently-loaded page, reloading + it (emitting an error message if no file is loaded). + + Returns this object, noting that the load is async. After loading + it triggers a 'wiki-page-loaded' event, passing it this.winfo. + + If a locally-edited copy of the given file/rev is found, that + copy is used instead of one fetched from the server, but it is + still treated as a load event. + + Alternate call forms: + + - no arguments: re-loads from this.winfo. + + - 1 non-string argument: assumed to be an winfo-style + object. Must have at least the {name} property, but need not have + other winfo state. + */ + P.loadPage = function(name){ + if(0===arguments.length){ + /* Reload from this.winfo */ + if(!affirmPageLoaded()) return this; + name = this.winfo.name; + }else if(1===arguments.length && 'string' !== typeof name){ + /* Assume winfo-like object */ + const arg = arguments[0]; + name = arg.name; + } + const onload = (r)=>{ + this.dispatchEvent('wiki-page-loaded', r); + }; + const stashWinfo = this.getStashedWinfo({name: name}); + if(stashWinfo){ // fake a response from the stash... + F.message("Fetched from the local-edit storage:", stashWinfo.name); + onload({ + name: stashWinfo.name, + mimetype: stashWinfo.mimetype, + type: stashWinfo.type, + version: stashWinfo.version, + parent: stashWinfo.parent, + isEmpty: !!stashWinfo.isEmpty, + content: $stash.stashedContent(stashWinfo) + }); + this._isDirty = true/*b/c loading normally clears that flag*/; + return this; + } + F.message( + "Loading content..." + ).fetch('wikiajax/fetch',{ + urlParams: { + page: name + }, + responseType: 'json', + onload:(r)=>{ + F.message('Loaded page ['+r.name+'].'); + onload(r); + } + }); + return this; + }; + + /** + Fetches the page preview based on the contents and settings of + this page's input fields, and updates the UI with with the + preview. + + Returns this object, noting that the operation is async. + */ + P.preview = function f(switchToTab){ + if(!affirmPageLoaded()) return this; + return this._postPreview(this.wikiContent(), function(c){ + P._previewTo(c); + if(switchToTab) self.tabs.switchToTab(self.e.tabs.preview); + }); + }; + + /** + Callback for use with F.connectPagePreviewers(). Gets passed + the preview content. + */ + P._previewTo = function(c){ + const target = this.e.previewTarget; + D.clearElement(target); + if('string'===typeof c) D.parseHtml(target,c); + if(F.pikchr){ + F.pikchr.addSrcView(target.querySelectorAll('svg.pikchr')); + } + }; + + /** + Callback for use with F.connectPagePreviewers() + */ + P._postPreview = function(content,callback){ + if(!affirmPageLoaded()) return this; + if(!content){ + callback(content); + return this; + } + const fd = new FormData(); + const mimetype = this.e.selectMimetype.value; + fd.append('page', this.winfo.name); + fd.append('mimetype',mimetype); + fd.append('content',content || ''); + F.message( + "Fetching preview..." + ).fetch('wikiajax/preview',{ + payload: fd, + onload: (r,header)=>{ + callback(r); + F.message('Updated preview.'); + P.previewNeedsUpdate = false; + P.dispatchEvent('wiki-preview-updated',{ + mimetype: mimetype, + element: P.e.previewTarget + }); + }, + onerror: (e)=>{ + F.fetch.onerror(e); + callback("Error fetching preview: "+e); + } + }); + return this; + }; + + /** + Undo some of the SBS diff-rendering bits which hurt us more than + they help... + */ + P.tweakSbsDiffs2 = function(){ + if(1){ + const dt = this.e.diffTarget; + dt.querySelectorAll('.sbsdiffcols .difftxtcol').forEach( + (dtc)=>{ + const pre = dtc.querySelector('pre'); + pre.style.width = 'initial'; + //pre.removeAttribute('style'); + //console.debug("pre width =",pre.style.width); + } + ); + } + this.tweakSbsDiffs(); + }; + + /** + Fetches the content diff based on the contents and settings of + this page's input fields, and updates the UI with the diff view. + + Returns this object, noting that the operation is async. + */ + P.diff = function f(sbs){ + if(!affirmPageLoaded()) return this; + const content = this.wikiContent(), + self = this, + target = this.e.diffTarget; + const fd = new FormData(); + fd.append('page',this.winfo.name); + fd.append('sbs', sbs ? 1 : 0); + fd.append('content',content); + if(this.e.selectDiffWS) fd.append('ws',this.e.selectDiffWS.value); + F.message( + "Fetching diff..." + ).fetch('wikiajax/diff',{ + payload: fd, + onload: function(c){ + D.parseHtml(D.clearElement(target), [ + "
Diff [", + self.winfo.name, + "] → Local Edits
", + c||'No changes.' + ].join('')); + if(sbs) P.tweakSbsDiffs2(); + F.message('Updated diff.'); + self.tabs.switchToTab(self.e.tabs.diff); + } + }); + return this; + }; + + /** + Saves the current wiki page and re-populates the editor + with the saved state. If passed an argument, it is + expected to be a function, which is called only if + saving succeeds, after all other post-save processing. + */ + P.save = function callee(onSuccessCallback){ + if(!affirmPageLoaded()) return this; + else if(!this._isDirty){ + F.error("There are no changes to save."); + return this; + } + const content = this.wikiContent(); + const self = this; + callee.onload = function(w){ + const oldWinfo = self.winfo; + self.unstashContent(oldWinfo); + self.dispatchEvent('wiki-page-loaded', w)/* will reset save buttons */; + F.message("Saved page: ["+w.name+"]."); + if('function'===typeof onSuccessCallback){ + onSuccessCallback(); + } + }; + const fd = new FormData(), w = P.winfo; + fd.append('page',w.name); + fd.append('mimetype', w.mimetype); + fd.append('isnew', w.version ? 0 : 1); + fd.append('content', P.wikiContent()); + F.message( + "Saving page..." + ).fetch('wikiajax/save',{ + payload: fd, + responseType: 'json', + onload: callee.onload + }); + return this; + }; + + /** + Updates P.winfo for certain state and stashes P.winfo, with the + current content fetched via P.wikiContent(). + + If passed truthy AND the stash already has stashed content for + the current page, only the stashed winfo record is updated, else + both the winfo and content are updated. + */ + P.stashContentChange = function(onlyWinfo){ + if(affirmPageLoaded(true)){ + const wi = this.winfo; + wi.mimetype = P.e.selectMimetype.value; + if(onlyWinfo && $stash.hasStashedContent(wi)){ + $stash.updateWinfo(wi); + }else{ + $stash.updateWinfo(wi, P.wikiContent()); + } + F.message("Stashed changes to page ["+wi.name+"]."); + P.updatePageTitle(); + $stash.prune(); + this.previewNeedsUpdate = true; + } + return this; + }; + + /** + Removes any stashed state for the current P.winfo (if set) from + F.storage. Returns this. + */ + P.unstashContent = function(){ + const winfo = arguments[0] || this.winfo; + if(winfo){ + this.previewNeedsUpdate = true; + $stash.unstash(winfo); + //console.debug("Unstashed",winfo); + F.message("Unstashed page ["+winfo.name+"]."); + } + return this; + }; + + /** + Clears all stashed file state from F.storage. Returns this. + */ + P.clearStash = function(){ + $stash.clear(); + return this; + }; + + /** + If stashed content for P.winfo exists, it is returned, else + undefined is returned. + */ + P.contentFromStash = function(){ + return affirmPageLoaded(true) ? $stash.stashedContent(this.winfo) : undefined; + }; + + /** + If a stashed version of the given winfo object exists (same + filename/checkin values), return it, else return undefined. + */ + P.getStashedWinfo = function(winfo){ + return $stash.getWinfo(winfo); + }; + +})(window.fossil); ADDED src/fossil.pikchr.js Index: src/fossil.pikchr.js ================================================================== --- /dev/null +++ src/fossil.pikchr.js @@ -0,0 +1,85 @@ +(function(F/*window.fossil object*/){ + "use strict"; + const D = F.dom, P = F.pikchr = {}; + + /** + Initializes pikchr-rendered elements with the ability to + toggle between their SVG and source code. + + The first argument may be any of: + + - A single SVG.pikchr element. + + - A collection (with a forEach method) of such elements. + + - A CSS selector string for one or more such elements. + + - An array of such strings. + + Passing no value is equivalent to passing 'svg.pikchr'. + + For each SVG in the resulting set, this function sets up event + handlers which allow the user to toggle the SVG between image and + source code modes. The image will switch modes in response to + cltr-click and, if its *parent* element has the "toggle" CSS + class, it will also switch modes in response to single-click. + + If the parent element has the "source" CSS class, the image + starts off with its source code visible and the image hidden, + instead of the default of the other way around. + + Returns this object. + + Each element will only be processed once by this routine, even if + it is passed to this function multiple times. Each processed + element gets a "data" attribute set to it to indicate that it was + already dealt with. + + This code expects the following structure around the SVGs, and + will not process any which don't match this: + + +
+ +
+ */ + P.addSrcView = function f(svg){ + if(!f.hasOwnProperty('parentClick')){ + f.parentClick = function(ev){ + if(ev.altKey || ev.metaKey || ev.ctrlKey + /* Every combination of special key (alt, shift, ctrl, + meta) is handled differently everywhere. Shift is used + by the browser, Ctrl doesn't work on an iMac, and Alt is + intercepted by most Linux window managers to control + window movement! So... we just listen for *any* of them + (except Shift) and the user will need to find one which + works on on their environment. */ + || this.classList.contains('toggle')){ + this.classList.toggle('source'); + ev.stopPropagation(); + ev.preventDefault(); + } + }; + }; + if(!svg) svg = 'svg.pikchr'; + if('string' === typeof svg){ + document.querySelectorAll(svg).forEach((e)=>f.call(this, e)); + return this; + }else if(svg.forEach){ + svg.forEach((e)=>f.call(this, e)); + return this; + } + if(svg.dataset.pikchrProcessed){ + return this; + } + svg.dataset.pikchrProcessed = 1; + const parent = svg.parentNode.parentNode /* outermost div.pikchr-wrapper */; + const srcView = parent ? svg.parentNode.nextElementSibling : undefined; + if(!srcView || !srcView.classList.contains('pikchr-src')){ + /* Without this element, there's nothing for us to do here. */ + return this; + } + parent.addEventListener('click', f.parentClick, false); + return this; + }; +})(window.fossil); ADDED src/fossil.popupwidget.js Index: src/fossil.popupwidget.js ================================================================== --- /dev/null +++ src/fossil.popupwidget.js @@ -0,0 +1,469 @@ +(function(F/*fossil object*/){ + /** + A very basic tooltip-like widget. It's intended to be popped up + to display basic information or basic user interaction + components, e.g. a copy-to-clipboard button. + + Requires: fossil.bootstrap, fossil.dom + */ + const D = F.dom; + + /** + Creates a new tooltip-like widget using the given options object. + + Options: + + .refresh: callback which is called just before the tooltip is + revealed. It must refresh the contents of the tooltip, if needed, + by applying the content to/within this.e, which is the base DOM + element for the tooltip (and is a child of document.body). If the + contents are static and set up via the .init option then this + callback is not needed. When moving an already-shown tooltip, + this is *not* called. It arguably should be, but the fact is that + we often have to show() a popup twice in a row without hiding it + between those calls: once to get its computed size and another to + move it by some amount relative to that size. If the state of the + popup depends on its position and a "double-show()" is needed + then the client must hide() the popup between the two calls to + show() in order to force a call to refresh() on the second + show(). + + .adjustX: an optional callback which is called when the tooltip + is to be displayed at a given position and passed the X + viewport-relative coordinate. This routine must either return its + argument as-is or return an adjusted value. The intent is to + allow a given tooltip may be positioned more appropriately for a + given context, if needed (noting that the desired position can, + and probably should, be passed to the show() method + instead). This class's API assumes that clients give it + viewport-relative coordinates, and it will take care to translate + those to page-relative, so this callback should not do so. + + .adjustY: the Y counterpart of adjustX. + + .init: optional callback called one time to initialize the state + of the tooltip. This is called after the this.e has been created + and added (initially hidden) to the DOM. If this is called, it is + removed from the object immediately after it is called. + + All callback options are called with the PopupWidget object as + their "this". + + + .cssClass: optional CSS class, or list of classes, to apply to + the new element. In addition to any supplied here (or inherited + from the default), the class "fossil-PopupWidget" is always set + in order to allow certain app-internal CSS to account for popup + windows in special cases. + + .style: optional object of properties to copy directly into + the element's style object. + + The options passed to this constructor get normalized into a + separate object which includes any default values for options not + provided by the caller. That object is available this the + resulting PopupWidget's options property. Default values for any + options not provided by the caller are pulled from + PopupWidget.defaultOptions, and modifying those affects all + future calls to this method but has no effect on existing + instances. + + + Example: + + const tip = new fossil.PopupWidget({ + init: function(){ + // optionally populate DOM element this.e with the widget's + // content. + }, + refresh: function(){ + // (re)populate/refresh the contents of the main + // wrapper element, this.e. + } + }); + + tip.show(50, 100); + // ^^^ viewport-relative coordinates. See show() for other options. + + */ + F.PopupWidget = function f(opt){ + opt = F.mergeLastWins(f.defaultOptions,opt); + this.options = opt; + const e = this.e = D.addClass(D.div(), opt.cssClass, + "fossil-PopupWidget"); + this.show(false); + if(opt.style){ + let k; + for(k in opt.style){ + if(opt.style.hasOwnProperty(k)) e.style[k] = opt.style[k]; + } + } + D.append(document.body, e/*must be in the DOM for size calc. to work*/); + D.copyStyle(e, opt.style); + if(opt.init){ + opt.init.call(this); + delete opt.init; + } + }; + + /** + Default options for the PopupWidget constructor. These values are + used for any options not provided by the caller. Any changes made + to this instace affect future calls to PopupWidget() but have no + effect on existing instances. + */ + F.PopupWidget.defaultOptions = { + cssClass: 'fossil-tooltip', + style: undefined /*{optional properties copied as-is into element.style}*/, + adjustX: (x)=>x, + adjustY: (y)=>y, + refresh: function(){}, + init: undefined /* optional initialization function */ + }; + + F.PopupWidget.prototype = { + + /** Returns true if the widget is currently being shown, else false. */ + isShown: function(){return !this.e.classList.contains('hidden')}, + + /** Calls the refresh() method of the options object and returns + this object. */ + refresh: function(){ + if(this.options.refresh){ + this.options.refresh.call(this); + } + return this; + }, + + /** + Shows or hides the tooltip. + + Usages: + + (bool showIt) => hide it or reveal it at its last position. + + (x, y) => reveal/move it at/to the given + relative-to-the-viewport position, which will be adjusted to make + it page-relative. + + (DOM element) => reveal/move it at/to a position based on the + the given element (adjusted slightly). + + For the latter two, this.options.adjustX() and adjustY() will + be called to adjust it further. + + Returns this object. + + If this call will reveal the element then it calls + this.refresh() to update the UI state. If the element was + already revealed, the call to refresh() is skipped. + + Sidebar: showing/hiding the widget is, as is conventional for + this framework, done by removing/adding the 'hidden' CSS class + to it, so that class must be defined appropriately. + */ + show: function(){ + var x = undefined, y = undefined, showIt, + wasShown = !this.e.classList.contains('hidden'); + if(2===arguments.length){ + x = arguments[0]; + y = arguments[1]; + showIt = true; + }else if(1===arguments.length){ + if(arguments[0] instanceof HTMLElement){ + const p = arguments[0]; + const r = p.getBoundingClientRect(); + x = r.x + r.x/5; + y = r.y - r.height/2; + showIt = true; + }else{ + showIt = !!arguments[0]; + } + } + if(showIt){ + if(!wasShown) this.refresh(); + x = this.options.adjustX.call(this,x); + y = this.options.adjustY.call(this,y); + x += window.pageXOffset; + y += window.pageYOffset; + } + if(showIt){ + if('number'===typeof x && 'number'===typeof y){ + this.e.style.left = x+"px"; + this.e.style.top = y+"px"; + } + D.removeClass(this.e, 'hidden'); + }else{ + D.addClass(this.e, 'hidden'); + this.e.style.removeProperty('left'); + this.e.style.removeProperty('top'); + } + return this; + }, + + /** + Equivalent to show(false), but may be overridden by instances, + so long as they also call this.show(false) to perform the + actual hiding. Overriding can be used to clean up any state so + that the next call to refresh() (before the popup is show()n + again) can recognize whether it needs to do something, noting + that it's legal, and sometimes necessary, to call show() + multiple times without needing/wanting to completely refresh + the popup between each call (e.g. when moving the popup after + it's been show()n). + */ + hide: function(){return this.show(false)}, + + /** + A convenience method which adds click handlers to this popup's + main element and document.body to hide (via hide()) the popup + when either element is clicked or the ESC key is pressed. Only + call this once per instance, if at all. Returns this; + + The first argument specifies whether a click handler on this + object is installed. The second specifies whether a click + outside of this object should close it. The third specifies + whether an ESC handler is installed. + + Passing no arguments is equivalent to passing (true,true,true), + and passing fewer arguments defaults the unpassed parameters to + true. + */ + installHideHandlers: function f(onClickSelf, onClickOther, onEsc){ + if(!arguments.length) onClickSelf = onClickOther = onEsc = true; + else if(1===arguments.length) onClickOther = onEsc = true; + else if(2===arguments.length) onEsc = true; + if(onClickSelf) this.e.addEventListener('click', ()=>this.hide(), false); + if(onClickOther) document.body.addEventListener('click', ()=>this.hide(), true); + if(onEsc){ + const self = this; + document.body.addEventListener('keydown', function(ev){ + if(self.isShown() && 27===ev.which) self.hide(); + }, true); + } + return this; + } + }/*F.PopupWidget.prototype*/; + + /** + Internal impl for F.toast() and friends. + + args: + + 1) CSS class to assign to the outer element, along with + fossil-toast-message. Must be falsy for the non-warning/non-error + case. + + 2) Multiplier of F.toast.config.displayTimeMs. Should be + 1 for default case and progressively higher for warning/error + cases. + + 3) The 'arguments' object from the function which is calling + this. + + Returns F.toast. + */ + const toastImpl = function f(cssClass, durationMult, argsObject){ + if(!f.toaster){ + f.toaster = new F.PopupWidget({ + cssClass: 'fossil-toast-message' + }); + D.attr(f.toaster.e, 'role', 'alert'); + } + const T = f.toaster; + if(f._timer) clearTimeout(f._timer); + D.clearElement(T.e); + if(f._prevCssClass) T.e.classList.remove(f._prevCssClass); + if(cssClass) T.e.classList.add(cssClass); + f._prevCssClass = cssClass; + D.append(T.e, Array.prototype.slice.call(argsObject,0)); + T.show(F.toast.config.position.x, F.toast.config.position.y); + f._timer = setTimeout( + ()=>T.hide(), + F.toast.config.displayTimeMs * durationMult + ); + return F.toast; + }; + + F.toast = { + config: { + position: { x: 5, y: 5 /*viewport-relative, pixels*/ }, + displayTimeMs: 3000 + }, + /** + Convenience wrapper around a PopupWidget which pops up a shared + PopupWidget instance to show toast-style messages (commonly + seen on Android). Its arguments may be anything suitable for + passing to fossil.dom.append(), and each argument is first + append()ed to the toast widget, then the widget is shown for + F.toast.config.displayTimeMs milliseconds. If this is called + while a toast is currently being displayed, the first will be + overwritten and the time until the message is hidden will be + reset. + + The toast is always shown at the viewport-relative coordinates + defined by the F.toast.config.position. + + The toaster's DOM element has the CSS class fossil-tooltip + and fossil-toast-message, so can be style via those. + + The 3 main message types (message, warning, error) each get a + CSS class with that same name added to them. Thus CSS can + select on .fossil-toast-message.error to style error toasts. + */ + message: function(/*...*/){ + return toastImpl(false,1, arguments); + }, + /** + Displays a toast with the 'warning' CSS class assigned to it. It + displays for 1.5 times as long as a normal toast. + */ + warning: function(/*...*/){ + return toastImpl('warning',1.5,arguments); + }, + /** + Displays a toast with the 'error' CSS class assigned to it. It + displays for twice as long as a normal toast. + */ + error: function(/*...*/){ + return toastImpl('error',2,arguments); + } + }/*F.toast*/; + + + F.helpButtonlets = { + /** + Initializes one or more "help buttonlets". It may be passed any of: + + - A string: CSS selector (multiple matches are legal) + + - A single DOM element. + + - A forEach-compatible container of DOM elements. + + - No arguments, which is equivalent to passing the string + ".help-buttonlet:not(.processed)". + + Passing the same element(s) more than once is a no-op: during + initialization, each elements get the class'processed' added to + it, and any elements with that class are skipped. + + All child nodes of a help buttonlet are removed from the button + during initialization and stashed away for use in a PopupWidget + when the botton is clicked. + + */ + setup: function f(){ + if(!f.hasOwnProperty('clickHandler')){ + f.clickHandler = function fch(ev){ + ev.preventDefault(); + if(!fch.popup){ + fch.popup = new F.PopupWidget({ + cssClass: ['fossil-tooltip', 'help-buttonlet-content'], + refresh: function(){ + } + }); + fch.popup.e.style.maxWidth = '80%'/*of body*/; + fch.popup.installHideHandlers(); + } + D.append(D.clearElement(fch.popup.e), ev.target.$helpContent); + /* Shift the help around a bit to "better" fit the + screen. However, fch.popup.e.getClientRects() is empty + until the popup is shown, so we have to show it, + calculate the resulting size, then move and/or resize it. + + This algorithm/these heuristics can certainly be improved + upon. + */ + var popupRect, rectElem = ev.target; + while(rectElem){ + popupRect = rectElem.getClientRects()[0]/*undefined if off-screen!*/; + if(popupRect) break; + rectElem = rectElem.parentNode; + } + if(!popupRect) popupRect = {x:0, y:0, left:0, right:0}; + var x = popupRect.left, y = popupRect.top; + if(x<0) x = 0; + if(y<0) y = 0; + if(rectElem){ + /* Try to ensure that the popup's z-level is higher than this element's */ + const rz = window.getComputedStyle(rectElem).zIndex; + var myZ; + if(rz && !isNaN(+rz)){ + myZ = +rz + 1; + }else{ + myZ = 10000/*guess!*/; + } + fch.popup.e.style.zIndex = myZ; + } + fch.popup.show(x, y); + x = popupRect.left, y = popupRect.top; + popupRect = fch.popup.e.getBoundingClientRect(); + const rectBody = document.body.getClientRects()[0]; + if(popupRect.right > rectBody.right){ + x -= (popupRect.right - rectBody.right); + } + if(x + popupRect.width > rectBody.right){ + x = rectBody.x + (rectBody.width*0.1); + fch.popup.e.style.minWidth = '70%'; + }else{ + fch.popup.e.style.removeProperty('min-width'); + x -= popupRect.width/2; + } + if(x<0) x = 0; + //console.debug("dimensions",x,y, popupRect, rectBody); + fch.popup.show(x, y); + }; + f.foreachElement = function(e){ + if(e.classList.contains('processed')) return; + e.classList.add('processed'); + e.$helpContent = []; + /* We have to move all child nodes out of the way because we + cannot hide TEXT nodes via CSS (which cannot select TEXT + nodes). We have to do it in two steps to avoid invaliding + the list during traversal. */ + e.childNodes.forEach((ch)=>e.$helpContent.push(ch)); + e.$helpContent.forEach((ch)=>ch.remove()); + e.addEventListener('click', f.clickHandler, false); + }; + }/*static init*/ + var elems; + if(!arguments.length){ + arguments[0] = '.help-buttonlet:not(.processed)'; + arguments.length = 1; + } + if(arguments.length){ + if('string'===typeof arguments[0]){ + elems = document.querySelectorAll(arguments[0]); + }else if(arguments[0] instanceof HTMLElement){ + elems = [arguments[0]]; + }else if(arguments[0].forEach){/* assume DOM element list or array */ + elems = arguments[0]; + } + } + if(elems) elems.forEach(f.foreachElement); + }, + + /** + Sets up the given element as a "help buttonlet", adding the CSS + class help-buttonlet to it. Any (optional) arguments after the + first are appended to the element using fossil.dom.append(), so + that they become the content for the buttonlet's popup help. + + The element is then passed to this.setup() before it + is returned from this function. + */ + create: function(elem/*...body*/){ + D.addClass(elem, 'help-buttonlet'); + if(arguments.length>1){ + const args = Array.prototype.slice.call(arguments,1); + D.append(elem, args); + } + this.setup(elem); + return elem; + } + }/*helpButtonlets*/; + + F.onDOMContentLoaded( ()=>F.helpButtonlets.setup() ); + +})(window.fossil); ADDED src/fossil.storage.js Index: src/fossil.storage.js ================================================================== --- /dev/null +++ src/fossil.storage.js @@ -0,0 +1,165 @@ +(function(F){ + /** + fossil.store is a basic wrapper around localStorage + or sessionStorage or a dummy proxy object if neither + of those are available. + */ + const tryStorage = function f(obj){ + if(!f.key) f.key = 'fossil.access.check'; + try{ + obj.setItem(f.key, 'f'); + const x = obj.getItem(f.key); + obj.removeItem(f.key); + if(x!=='f') throw new Error(f.key+" failed") + return obj; + }catch(e){ + return undefined; + } + }; + + /** Internal storage impl for fossil.storage. */ + const $storage = + tryStorage(window.localStorage) + || tryStorage(window.sessionStorage) + || tryStorage({ + // A basic dummy xyzStorage stand-in + $$$:{}, + setItem: function(k,v){this.$$$[k]=v}, + getItem: function(k){ + return this.$$$.hasOwnProperty(k) ? this.$$$[k] : undefined; + }, + removeItem: function(k){delete this.$$$[k]}, + clear: function(){this.$$$={}} + }); + + /** + For the dummy storage we need to differentiate between + $storage and its real property storage for hasOwnProperty() + to work properly... + */ + const $storageHolder = $storage.hasOwnProperty('$$$') ? $storage.$$$ : $storage; + + /** + A prefix which gets internally applied to all fossil.storage + property keys so that localStorage and sessionStorage across the + same browser profile instance do not "leak" across multiple repos + being hosted by the same origin server. Such polination is still + there but, with this key prefix applied, it won't be immediately + visible via the storage API. + + With this in place we can justify using localStorage instead of + sessionStorage again. + + One implication, it was discovered after the release of 2.12, of + using localStorage and sessionStorage, is that their scope (the + same "origin" and client application/profile) allows multiple + repos on the same origin to use the same storage. Thus a user + editing a wiki in /repoA/wikiedit could then see those edits in + /repoB/wikiedit. The data do not cross user- or browser + boundaries, though, so it "might" arguably be called a bug. Even + so, it was never intended for that to happen. Rather than lose + localStorage access altogether, storageKeyPrefix was added so + that we can sandbox that state for the various repos. + + See: https://fossil-scm.org/forum/forumpost/4afc4d34de + + Sidebar: it might seem odd to provide a key prefix and stick all + properties in the topmost level of the storage object. We do that + because adding a layer of object to sandbox each repo would mean + (de)serializing that whole tree on every storage property change + (and we update storage often during editing sessions). + e.g. instead of storageObject.projectName.foo we have + storageObject[storageKeyPrefix+'foo']. That's soley for + efficiency's sake (in terms of battery life and + environment-internal storage-level effort). Even so, it might (or + might not) be useful to do that someday. + */ + const storageKeyPrefix = ( + $storageHolder===$storage/*localStorage or sessionStorage*/ + ? ( + F.config.projectCode || F.config.projectName + || F.config.shortProjectName || window.location.pathname + )+'::' : ( + '' /* transient storage */ + ) + ); + + /** + A proxy for localStorage or sessionStorage or a + page-instance-local proxy, if neither one is availble. + + Which exact storage implementation is uses is unspecified, and + apps must not rely on it. + */ + F.storage = { + storageKeyPrefix: storageKeyPrefix, + /** Sets the storage key k to value v, implicitly converting + it to a string. */ + set: (k,v)=>$storage.setItem(storageKeyPrefix+k,v), + /** Sets storage key k to JSON.stringify(v). */ + setJSON: (k,v)=>$storage.setItem(storageKeyPrefix+k,JSON.stringify(v)), + /** Returns the value for the given storage key, or + dflt if the key is not found in the storage. */ + get: (k,dflt)=>$storageHolder.hasOwnProperty( + storageKeyPrefix+k + ) ? $storage.getItem(storageKeyPrefix+k) : dflt, + /** Returns true if the given key has a value of "true". If the + key is not found, it returns true if the boolean value of dflt + is "true". (Remember that JS persistent storage values are all + strings.) */ + getBool: function(k,dflt){ + return 'true'===this.get(k,''+(!!dflt)); + }, + /** Returns the JSON.parse()'d value of the given + storage key's value, or dflt is the key is not + found or JSON.parse() fails. */ + getJSON: function f(k,dflt){ + try { + const x = this.get(k,f); + return x===f ? dflt : JSON.parse(x); + } + catch(e){return dflt} + }, + /** Returns true if the storage contains the given key, + else false. */ + contains: (k)=>$storageHolder.hasOwnProperty(storageKeyPrefix+k), + /** Removes the given key from the storage. Returns this. */ + remove: function(k){ + $storage.removeItem(storageKeyPrefix+k); + return this; + }, + /** Clears ALL keys from the storage. Returns this. */ + clear: function(){ + this.keys().forEach((k)=>$storage.removeItem(/*w/o prefix*/k)); + return this; + }, + /** Returns an array of all keys currently in the storage. */ + keys: ()=>Object.keys($storageHolder).filter((v)=>(v||'').startsWith(storageKeyPrefix)), + /** Returns true if this storage is transient (only available + until the page is reloaded), indicating that fileStorage + and sessionStorage are unavailable. */ + isTransient: ()=>$storageHolder!==$storage, + /** Returns a symbolic name for the current storage mechanism. */ + storageImplName: function(){ + if($storage===window.localStorage) return 'localStorage'; + else if($storage===window.sessionStorage) return 'sessionStorage'; + else return 'transient'; + }, + + /** + Returns a brief help text string for the currently-selected + storage type. + */ + storageHelpDescription: function(){ + return { + localStorage: "Browser-local persistent storage with an "+ + "unspecified long-term lifetime (survives closing the browser, "+ + "but maybe not a browser upgrade).", + sessionStorage: "Storage local to this browser tab, "+ + "lost if this tab is closed.", + "transient": "Transient storage local to this invocation of this page." + }[this.storageImplName()]; + } + }; + +})(window.fossil); ADDED src/fossil.tabs.js Index: src/fossil.tabs.js ================================================================== --- /dev/null +++ src/fossil.tabs.js @@ -0,0 +1,259 @@ +"use strict"; +(function(F/*fossil object*/){ + const E = (s)=>document.querySelector(s), + EA = (s)=>document.querySelectorAll(s), + D = F.dom; + + /** + Creates a TabManager. If passed a truthy first argument, it is + passed to init(). If passed a truthy second argument, it must be + an Object holding configuration options: + + { + tabAccessKeys: boolean (=true) + If true, tab buttons are assigned "accesskey" values + equal to their 1-based tab number. + } + */ + const TabManager = function(domElem, options){ + this.e = {}; + this.options = F.mergeLastWins(TabManager.defaultOptions , options); + if(domElem) this.init(domElem); + }; + + /** + Default values for the options object passed to the TabManager + constructor. Changing these affects the defaults of all + TabManager instances instantiated after that point. + */ + TabManager.defaultOptions = { + tabAccessKeys: true + }; + + /** + Internal helper to normalize a method argument to a tab + element. arg may be a tab DOM element, a selector string, or an + index into tabMgr.e.tabs.childNodes. Returns the corresponding + tab element. + */ + const tabArg = function(arg,tabMgr){ + if('string'===typeof arg) arg = E(arg); + else if(tabMgr && 'number'===typeof arg && arg>=0){ + arg = tabMgr.e.tabs.childNodes[arg]; + } + return arg; + }; + + /** + Sets sets the visibility of tab element e to on or off. e MUST be + a TabManager tab element. + */ + const setVisible = function(e,yes){ + D[yes ? 'removeClass' : 'addClass'](e, 'hidden'); + }; + + TabManager.prototype = { + /** + Initializes the tabs associated with the given tab container + (DOM element or selector for a single element). This must be + called once before using any other member functions of a given + instance, noting that the constructor will call this if it is + passed an argument. + + The tab container must have an 'id' attribute. This function + looks through the DOM for all elements which have + data-tab-parent=thatId. For each one it creates a button to + switch to that tab and moves the element into this.e.tabs, + *possibly* injecting an intermediary element between + this.e.tabs and the element. + + The label for each tab is set by the data-tab-label attribute + of each element, defaulting to something not terribly useful. + + When it's done, it auto-selects the first tab unless a tab has + a truthy numeric value in its data-tab-select attribute, in + which case the last tab to have such a property is selected. + + This method must only be called once per instance. TabManagers + may be nested but must not share any tabs instances. + + Returns this object. + + DOM elements of potential interest to users: + + this.e.container = the outermost container element. + + this.e.tabBar = the button bar. Each "button" (whether it's a + buttor not is unspecified) has a class of .tab-button. + + this.e.tabs = the parent for all of the tab elements. + + It is legal, within reason, to manipulate these a bit, in + particular this.e.container, e.g. by adding more children to + it. Do not remove elements from the tabs or tabBar, however, or + the tab state may get sorely out of sync. + + CSS classes: the container element has whatever class(es) the + client sets on. this.e.tabBar gets the 'tab-bar' class and + this.e.tabs gets the 'tabs' class. It's hypothetically possible + to move the tabs to either side or the bottom using only CSS, + but it's never been tested. + */ + init: function(container){ + container = tabArg(container); + const cID = container.getAttribute('id'); + if(!cID){ + throw new Error("Tab container element is missing 'id' attribute."); + } + const c = this.e.container = container; + this.e.tabBar = D.addClass(D.div(),'tab-bar'); + this.e.tabs = D.addClass(D.div(),'tabs'); + D.append(c, this.e.tabBar, this.e.tabs); + let selectIndex = 0; + EA('[data-tab-parent='+cID+']').forEach((c,n)=>{ + if(+c.dataset.tabSelect) selectIndex=n; + this.addTab(c); + }); + return this.switchToTab(selectIndex); + }, + + /** + For the given tab element, unique selector string, or integer + (0-based tab number), returns the button associated with that + tab, or undefined if the argument does not match any current + tab. + */ + getButtonForTab: function(tab){ + tab = tabArg(tab,this); + var i = -1; + this.e.tabs.childNodes.forEach(function(e,n){ + if(e===tab) i = n; + }); + return i>=0 ? this.e.tabBar.childNodes[i] : undefined; + }, + /** + Adds the given DOM element or unique selector as the next + tab in the tab container, adding a button to switch to + the tab. Returns this object. + + If this object's options include a truthy tabAccessKeys then + each tab button gets assigned an accesskey attribute equal to + its 1-based index in the tab list. e.g. key 1 is the first tab + and key 5 is the 5th. Whether/how that accesskey is accessed is + dependent on the browser and its OS: + + https://developer.mozilla.org/en-US/docs/Web/HTML/Global_attributes/accesskey + */ + addTab: function f(tab){ + if(!f.click){ + f.click = function(e){ + e.target.$manager.switchToTab(e.target.$tab); + }; + } + tab = tabArg(tab); + tab.remove(); + D.append(this.e.tabs, D.addClass(tab,'tab-panel')); + const tabCount = this.e.tabBar.childNodes.length+1; + const lbl = tab.dataset.tabLabel || 'Tab #'+tabCount; + const btn = D.addClass(D.append(D.span(), lbl), 'tab-button'); + D.append(this.e.tabBar,btn); + btn.$manager = this; + btn.$tab = tab; + if(this.options.tabAccessKeys){ + D.attr(btn, 'accesskey', tabCount); + } + btn.addEventListener('click', f.click, false); + return this; + }, + + /** + Internal. Fires a new CustomEvent to all listeners which have + registered via this.addEventListener(). + */ + _dispatchEvent: function(name, detail){ + try{ + this.e.container.dispatchEvent( + new CustomEvent(name, {detail: detail}) + ); + }catch(e){ + /* ignore */ + } + return this; + }, + + /** + Registers an event listener for this object's custom events. + The callback gets a CustomEvent object with a 'detail' + propertly holding any tab-related state for the event. The events + are: + + - 'before-switch-from' is emitted immediately before a new tab + is switched away from. detail = the tab element being switched + away from. + + - 'before-switch-to' is emitted immediately before a new tab is + switched to. detail = the tab element. + + - 'after-switch-to' is emitted immediately after a new tab is + switched to. detail = the tab element. + + Any exceptions thrown by listeners are caught and ignored, to + avoid that they knock the tab state out of sync. + + Returns this object. + */ + addEventListener: function(eventName, callback){ + this.e.container.addEventListener(eventName, callback, false); + return this; + }, + + /** + Inserts the given DOM element immediately after the tab bar. + Intended for a status bar or similar always-visible component. + Returns this object. + */ + addCustomWidget: function(e){ + this.e.container.insertBefore(e, this.e.tabs); + return this; + }, + + /** + If the given DOM element, unique selector, or integer (0-based + tab number) is one of this object's tabs, the UI makes that tab + the currently-visible one, firing any relevant events. Returns + this object. If the argument is the current tab, this is a + no-op, and no events are fired. + */ + switchToTab: function(tab){ + tab = tabArg(tab,this); + const self = this; + if(tab===this._currentTab) return this; + else if(this._currentTab){ + this._dispatchEvent('before-switch-from', this._currentTab); + } + delete this._currentTab; + this.e.tabs.childNodes.forEach((e,ndx)=>{ + const btn = this.e.tabBar.childNodes[ndx]; + if(e===tab){ + if(D.hasClass(e,'selected')){ + return; + } + self._dispatchEvent('before-switch-to',tab); + setVisible(e, true); + this._currentTab = e; + D.addClass(btn,'selected'); + self._dispatchEvent('after-switch-to',tab); + }else{ + if(D.hasClass(e,'selected')){ + return; + } + setVisible(e, false); + D.removeClass(btn,'selected'); + } + }); + return this; + } + }; + + F.TabManager = TabManager; +})(window.fossil); ADDED src/fossil.wikiedit-wysiwyg.js Index: src/fossil.wikiedit-wysiwyg.js ================================================================== --- /dev/null +++ src/fossil.wikiedit-wysiwyg.js @@ -0,0 +1,490 @@ +/** + A slight adaptation of fossil's legacy wysiwyg wiki editor which + makes it usable with the newer editor's edit widget replacement + API. + + Requires: window.fossil, fossil.dom, and that the current page is + /wikiedit. If called from another page it returns without effect. + + Caveat: this is an all-or-nothing solution. That is, once plugged + in to /wikiedit, it cannot be removed without reloading the page. + That is a limitation of the current editor-widget-swapping API. +*/ +(function(F/*fossil object*/){ + 'use strict'; + if(!F || !F.page || F.page.name!=='wikiedit') return; + + const D = F.dom; + + //////////////////////////////////////////////////////////////////////// + // Install an app-specific stylesheet... + (function(){ + const head = document.head || document.querySelector('head'), + styleTag = document.createElement('style'), + styleCSS = ` +.intLink { cursor: pointer; } +img.intLink { border: 0; } +#wysiwyg-container { + display: flex; + flex-direction: column; + max-width: 100% /* w/o this, toolbars don't wrap properly! */ +} +#wysiwygBox { + border: 1px solid rgba(127,127,127,0.3); + border-radius: 0.25em; + padding: 0.25em 1em; + margin: 0; + overflow: auto; + min-height: 20em; + resize: vertical; +} +#wysiwygEditMode { /* wrapper for radio buttons */ + border: 1px solid rgba(127,127,127,0.3); + border-radius: 0.25em; + padding: 0 0.35em 0 0.35em +} +#wysiwygEditMode > * { + vertical-align: text-top; +} +#wysiwygEditMode label { cursor: pointer; } +#wysiwyg-toolbars { + margin: 0 0 0.25em 0; + display: flex; + flex-wrap: wrap; + flex-direction: column; + align-items: flex-start; +} +#wysiwyg-toolbars > * { + margin: 0 0.5em 0.25em 0; +} +#wysiwyg-toolBar1, #wysiwyg-toolBar2 { + margin: 0 0.2em 0.2em 0; + display: flex; + flex-flow: row wrap; +} +#wysiwyg-toolBar1 > * { /* formatting buttons */ + vertical-align: middle; + margin: 0 0.25em 0.25em 0; +} +#wysiwyg-toolBar2 > * { /* icons */ + border: 1px solid rgba(127,127,127,0.3); + vertical-align: baseline; + margin: 0.1em; +} +`; + head.appendChild(styleTag); + styleTag.type = 'text/css'; + D.append(styleTag, styleCSS); + })(); + + const outerContainer = D.attr(D.div(), 'id', 'wysiwyg-container'), + toolbars = D.attr(D.div(), 'id', 'wysiwyg-toolbars'), + toolbar1 = D.attr(D.div(), 'id', 'wysiwyg-toolBar1'), + // ^^^ formatting options + toolbar2 = D.attr(D.div(), 'id', 'wysiwyg-toolBar2') + // ^^^^ action icon buttons + ; + D.append(outerContainer, D.append(toolbars, toolbar1, toolbar2)); + + /** Returns a function which simplifies adding a list of options + to the given select element. See below for example usage. */ + const addOptions = function(select){ + return function ff(value, label){ + D.option(select, value, label || value); + return ff; + }; + }; + + //////////////////////////////////////////////////////////////////////// + // Edit mode selection (radio buttons). + const radio0 = + D.attr( + D.input('radio'), + 'name','wysiwyg-mode', + 'id', 'wysiwyg-mode-0', + 'value',0, + 'checked',true), + radio1 = D.attr( + D.input('radio'), + 'id','wysiwyg-mode-1', + 'name','wysiwyg-mode', + 'value',1), + radios = D.append( + D.attr(D.span(), 'id', 'wysiwygEditMode'), + radio0, D.append( + D.attr(D.label(), 'for', 'wysiwyg-mode-0'), + "WYSIWYG" + ), + radio1, D.append( + D.attr(D.label(), 'for', 'wysiwyg-mode-1'), + "Raw HTML" + ) + ); + D.append(toolbar1, radios); + const radioHandler = function(){setDocMode(+this.value)}; + radio0.addEventListener('change',radioHandler, false); + radio1.addEventListener('change',radioHandler, false); + + + //////////////////////////////////////////////////////////////////////// + // Text formatting options... + var select; + select = D.addClass(D.select(), 'format'); + select.dataset.format = "formatblock"; + D.append(toolbar1, select); + addOptions(select)( + '', '- formatting -')( + "h1", "Title 1

")( + "h2", "Title 2

")( + "h3", "Title 3

")( + "h4", "Title 4

")( + "h5", "Title 5

")( + "h6", "Subtitle
")( + "p", "Paragraph

")( + "pre", "Preformatted

");
+
+  select = D.addClass(D.select(), 'format');
+  select.dataset.format = "fontname";
+  D.append(toolbar1, select);
+  D.addClass(
+    D.option(select, '', '- font -'),
+    "heading"
+  );
+  addOptions(select)(
+    'Arial')(
+    'Arial Black')(
+    'Courier New')(
+    'Times New Roman');
+
+  select = D.addClass(D.select(), 'format');
+  D.append(toolbar1, select);
+  select.dataset.format = "fontsize";
+  D.addClass(
+    D.option(select, '', '- size -'),
+    "heading"
+  );
+  addOptions(select)(
+    "1", "Very small")(
+    "2", "A bit small")(
+    "3", "Normal")(
+    "4", "Medium-large")(
+    "5", "Big")(
+    "6", "Very big")(
+    "7", "Maximum");
+
+  select = D.addClass(D.select(), 'format');
+  D.append(toolbar1, select);
+  select.dataset.format = 'forecolor';
+  D.addClass(
+    D.option(select, '', '- color -'),
+    "heading"
+  );
+  addOptions(select)(
+    "red", "Red")(
+    "blue", "Blue")(
+    "green", "Green")(
+    "black", "Black")(
+    "grey", "Grey")(
+    "yellow", "Yellow")(
+    "cyan", "Cyan")(
+    "magenta", "Magenta");
+
+
+  ////////////////////////////////////////////////////////////////////////
+  // Icon-based toolbar...
+  /**
+     Inject the icons...
+
+     mkbuiltins strips anything which looks like a C++-style comment,
+     even if it's in a string literal, and thus the runs of "/"
+     characters in the DOM element data attributes have been mangled
+     to work around that: we simply use \x2f for every 2nd slash.
+  */
+  (function f(title,format,src){
+    const img = D.img();
+    D.append(toolbar2, img);
+    D.addClass(img, 'intLink');
+    D.attr(img, 'title', title);
+    img.dataset.format = format;
+    D.attr(img, 'src', 'string'===typeof src ? src : src.join(''));
+    return f;
+  })(
+    'Undo', 'undo',
+    ["data:image/gif;base64,R0lGODlhFgAWAOMKADljwliE33mOrpGjuYKl8aezxqPD+7",
+     "/I19DV3NHa7P/\x2f/\x2f/\x2f/\x2f/\x2f/\x2f/\x2f/\x2f",
+     "/\x2f/\x2f/\x2f/yH5BAEKAA8ALAAAAAAWABYAAARR8MlJq704680",
+     "7TkaYeJJBnES4EeUJvIGapWYAC0CsocQ7SDlWJkAkCA6ToMYWIARGQF3mRQVIEjkkSVLIbSfE",
+     "whdRIH4fh/DZMICe3/C4nBQBADs="]
+  )(
+    'Redo','redo',
+    ["data:image/gif;base64,R0lGODlhFgAWAMIHAB1ChDljwl9vj1iE34Kl8aPD+7/I1/",
+     "/\x2f/yH5BAEKAAcALAAAAAAWABYAAANKeLrc/jDKSesyphi7SiEgsVXZEATDICqBVJjpqWZt9Na",
+     "EDNbQK1wCQsxlYnxMAImhyDoFAElJasRRvAZVRqqQXUy7Cgx4TC6bswkAOw=="]
+  )(
+    "Remove formatting",
+    "removeFormat",
+    ["data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAABYAAAAWCAYAAADEtGw7AA",
+     "AABGdBTUEAALGPC/xhBQAAAAZiS0dEAP8A/wD/oL2nkwAAAAlwSFlzAAAOxAAADsQBlSsOGwA",
+     "AAAd0SU1FB9oECQMCKPI8CIIAAAAIdEVYdENvbW1lbnQA9syWvwAAAuhJREFUOMtjYBgFxAB5",
+     "01ZWBvVaL2nHnlmk6mXCJbF69zU+Hz/9fB5O1lx+bg45qhl8/fYr5it3XrP/YWTUvvvk3VeqG",
+     "Xz70TvbJy8+Wv39+2/Hz19/mGwjZzuTYjALuoBv9jImaXHeyD3H7kU8fPj2ICML8z92dlbtMz",
+     "deiG3fco7J08foH1kurkm3E9iw54YvKwuTuom+LPt/BgbWf3/\x2fsf37/1/c02cCG1lB8f/\x2ff95",
+     "DZx74MTMzshhoSm6szrQ/a6Ir/Z2RkfEjBxuLYFpDiDi6Af/\x2f/2ckaHBp7+7wmavP5n76+P2C",
+     "lrLIYl8H9W36auJCbCxM4szMTJac7Kza/\x2f/\x2fR3H1w2cfWAgafPbqs5g7D95++/P1B4+ECK8tA",
+     "wMDw/1H7159+/7r7ZcvPz4fOHbzEwMDwx8GBgaGnNatfHZx8zqrJ+4VJBh5CQEGOySEua/v3n",
+     "7hXmqI8WUGBgYGL3vVG7fuPK3i5GD9/fja7ZsMDAzMG/Ze52mZeSj4yu1XEq/ff7W5dvfVAS1",
+     "lsXc4Db7z8C3r8p7Qjf/\x2f/2dnZGxlqJuyr3rPqQd/Hhyu7oSpYWScylDQsd3kzvnH738wMDzj",
+     "5GBN1VIWW4c3KDon7VOvm7S3paB9u5qsU5/x5KUnlY+eexQbkLNsErK61+++VnAJcfkyMTIwf",
+     "fj0QwZbJDKjcETs1Y8evyd48toz8y/ffzv/\x2fvPP4veffxpX77z6l5JewHPu8MqTDAwMDLzyrj",
+     "b/mZm0JcT5Lj+89+Ybm6zz95oMh7s4XbygN3Sluq4Mj5K8iKMgP4f0/\x2f/\x2ffv77/\x2f8nLy+7MCc",
+     "XmyYDAwODS9jM9tcvPypd35pne3ljdjvj26+H2dhYpuENikgfvQeXNmSl3tqepxXsqhXPyc66",
+     "6s+fv1fMdKR3TK72zpix8nTc7bdfhfkEeVbC9KhbK/9iYWHiErbu6MWbY/7/\x2f8/4/\x2f9/pgOnH",
+     "6jGVazvFDRtq2VgiBIZrUTIBgCk+ivHvuEKwAAAAABJRU5ErkJggg=="]
+  )(
+    "Bold",
+    "bold",
+    ["data:image/gif;base64,R0lGODlhFgAWAID/AMDAwAAAACH5BAEAAAAALAAAAAAWAB",
+     "YAQAInhI+pa+H9mJy0LhdgtrxzDG5WGFVk6aXqyk6Y9kXvKKNuLbb6zgMFADs="]
+  )(
+    "Italic",
+    "italic",
+    ["data:image/gif;base64,R0lGODlhFgAWAKEDAAAAAF9vj5WIbf/\x2f/yH5BAEAAAMALA",
+     "AAAAAWABYAAAIjnI+py+0Po5x0gXvruEKHrF2BB1YiCWgbMFIYpsbyTNd2UwAAOw=="]
+  )(
+    "Underline",
+    "underline",
+    ["data:image/gif;base64,R0lGODlhFgAWAKECAAAAAF9vj/\x2f/\x2f/\x2f/\x2fyH5BAEAAAIALA",
+     "AAAAAWABYAAAIrlI+py+0Po5zUgAsEzvEeL4Ea15EiJJ5PSqJmuwKBEKgxVuXWtun+DwxCCgA",
+     "7"]
+  )(
+    "Left align",
+    "justifyleft",
+    ["data:image/gif;base64,R0lGODlhFgAWAID/AMDAwAAAACH5BAEAAAAALAAAAAAWAB",
+     "YAQAIghI+py+0Po5y02ouz3jL4D4JMGELkGYxo+qzl4nKyXAAAOw=="]
+  )(
+    "Center align",
+    "justifycenter",
+    ["data:image/gif;base64,R0lGODlhFgAWAID/AMDAwAAAACH5BAEAAAAALAAAAAAWAB",
+     "YAQAIfhI+py+0Po5y02ouz3jL4D4JOGI7kaZ5Bqn4sycVbAQA7"]
+  )(
+    "Right align",
+    "justifyright",
+    ["data:image/gif;base64,R0lGODlhFgAWAID/AMDAwAAAACH5BAEAAAAALAAAAAAWAB",
+     "YAQAIghI+py+0Po5y02ouz3jL4D4JQGDLkGYxouqzl43JyVgAAOw=="]
+  )(
+    "Numbered list",
+    "insertorderedlist",
+    ["data:image/gif;base64,R0lGODlhFgAWAMIGAAAAADljwliE35GjuaezxtHa7P/\x2f/\x2f",
+     "/\x2f/yH5BAEAAAcALAAAAAAWABYAAAM2eLrc/jDKSespwjoRFvggCBUBoTFBeq6QIAysQnRHaEO",
+     "zyaZ07Lu9lUBnC0UGQU1K52s6n5oEADs="]
+  )(
+    "Dotted list",
+    "insertunorderedlist",
+    ["data:image/gif;base64,R0lGODlhFgAWAMIGAAAAAB1ChF9vj1iE33mOrqezxv/\x2f/\x2f",
+     "/\x2f/yH5BAEAAAcALAAAAAAWABYAAAMyeLrc/jDKSesppNhGRlBAKIZRERBbqm6YtnbfMY7lud6",
+     "4UwiuKnigGQliQuWOyKQykgAAOw=="]
+  )(
+    "Quote",
+    "formatblock",
+    ["data:image/gif;base64,R0lGODlhFgAWAIQXAC1NqjFRjkBgmT9nqUJnsk9xrFJ7u2",
+     "R9qmKBt1iGzHmOrm6Sz4OXw3Odz4Cl2ZSnw6KxyqO306K63bG70bTB0rDI3bvI4P",
+     "/\x2f/\x2f/\x2f/\x2f/",
+     "/\x2f/\x2f/\x2f/\x2f/\x2f/\x2f/\x2f/\x2f/\x2f/\x2f",
+     "/\x2f/\x2f/\x2fyH5BAEKAB8ALAAAAAAWABYAAAVP4CeOZGmeaKqubEs2Cekk",
+     "ErvEI1zZuOgYFlakECEZFi0GgTGKEBATFmJAVXweVOoKEQgABB9IQDCmrLpjETrQQlhHjINrT",
+     "q/b7/i8fp8PAQA7"]
+  )(
+    "Delete indentation",
+    "outdent",
+    ["data:image/gif;base64,R0lGODlhFgAWAMIHAAAAADljwliE35GjuaezxtDV3NHa7P",
+     "/\x2f/yH5BAEAAAcALAAAAAAWABYAAAM2eLrc/jDKCQG9F2i7u8agQgyK1z2EIBil+TWqEMxhMcz",
+     "sYVJ3e4ahk+sFnAgtxSQDqWw6n5cEADs="]
+  )(
+    "Add indentation",
+    "indent",
+    ["data:image/gif;base64,R0lGODlhFgAWAOMIAAAAADljwl9vj1iE35GjuaezxtDV3N",
+     "Ha7P/\x2f/\x2f/\x2f/\x2f/\x2f/\x2f/\x2f/\x2f/\x2f/\x2f/\x2f/\x2f/\x2f",
+     "/\x2f/\x2f/yH5BAEAAAgALAAAAAAWABYAAAQ7EMlJq704650",
+     "B/x8gemMpgugwHJNZXodKsO5oqUOgo5KhBwWESyMQsCRDHu9VOyk5TM9zSpFSr9gsJwIAOw=="
+    ]
+  )(
+    "Hyperlink",
+    "createlink",
+    ["data:image/gif;base64,R0lGODlhFgAWAOMKAB1ChDRLY19vj3mOrpGjuaezxrCztb",
+     "/I19Ha7Pv8/f/\x2f/\x2f/\x2f/\x2f/\x2f/\x2f/\x2f/\x2f/\x2f/\x2f/\x2f",
+     "/yH5BAEKAA8ALAAAAAAWABYAAARY8MlJq704682",
+     "7/2BYIQVhHg9pEgVGIklyDEUBy/RlE4FQF4dCj2AQXAiJQDCWQCAEBwIioEMQBgSAFhDAGghG",
+     "i9XgHAhMNoSZgJkJei33UESv2+/4vD4TAQA7"]
+  )(
+    "Cut",
+    "cut",
+    ["data:image/gif;base64,R0lGODlhFgAWAIQSAB1ChBFNsRJTySJYwjljwkxwl19vj1",
+     "dusYODhl6MnHmOrpqbmpGjuaezxrCztcDCxL/I18rL1P/\x2f/\x2f/\x2f/\x2f/\x2f/\x2f",
+     "/\x2f/\x2f/\x2f/\x2f/\x2f/\x2f/\x2f/\x2f/",
+     "/\x2f/\x2f/\x2f/\x2f/\x2f/\x2f/\x2f/\x2f/\x2f/\x2f/\x2f/\x2f/\x2f",
+     "yH5BAEAAB8ALAAAAAAWABYAAAVu4CeOZGmeaKqubDs6TNnE",
+     "bGNApNG0kbGMi5trwcA9GArXh+FAfBAw5UexUDAQESkRsfhJPwaH4YsEGAAJGisRGAQY7UCC9",
+     "ZAXBB+74LGCRxIEHwAHdWooDgGJcwpxDisQBQRjIgkDCVlfmZqbmiEAOw=="]
+  )(
+    "Copy",
+    "copy",
+    ["data:image/gif;base64,R0lGODlhFgAWAIQcAB1ChBFNsTRLYyJYwjljwl9vj1iE31",
+     "iGzF6MnHWX9HOdz5GjuYCl2YKl8ZOt4qezxqK63aK/9KPD+7DI3b/I17LM/MrL1MLY9NHa7OP",
+     "s++bx/Pv8/f/\x2f/\x2f/\x2f/\x2f/\x2f/\x2f/\x2f",
+     "/yH5BAEAAB8ALAAAAAAWABYAAAWG4CeOZGmeaKqubOum1SQ/",
+     "kPVOW749BeVSus2CgrCxHptLBbOQxCSNCCaF1GUqwQbBd0JGJAyGJJiobE+LnCaDcXAaEoxhQ",
+     "ACgNw0FQx9kP+wmaRgYFBQNeAoGihCAJQsCkJAKOhgXEw8BLQYciooHf5o7EA+kC40qBKkAAA",
+     "Grpy+wsbKzIiEAOw=="]
+  )(
+    /* Paste, when activated via JS, has no effect in some (maybe all)
+       environments. Activated externally, e.g. keyboard, it works. */
+    "Paste (does not work in all environments)",
+    "paste",
+    ["data:image/gif;base64,R0lGODlhFgAWAIQUAD04KTRLY2tXQF9vj414WZWIbXmOrp",
+     "qbmpGjudClFaezxsa0cb/I1+3YitHa7PrkIPHvbuPs+/fvrvv8/f/\x2f/\x2f/\x2f",
+     "/\x2f/\x2f/\x2f/\x2f/\x2f/\x2f/\x2f/",
+     "/\x2f/\x2f/\x2f/\x2f/\x2f/\x2f/\x2f/\x2f/\x2f/\x2f/\x2f/\x2f/\x2f",
+     "yH5BAEAAB8ALAAAAAAWABYAAAWN4CeOZGmeaKqubGsusPvB",
+     "SyFJjVDs6nJLB0khR4AkBCmfsCGBQAoCwjF5gwquVykSFbwZE+AwIBV0GhFog2EwIDchjwRiQ",
+     "o9E2Fx4XD5R+B0DDAEnBXBhBhN2DgwDAQFjJYVhCQYRfgoIDGiQJAWTCQMRiwwMfgicnVcAAA",
+     "MOaK+bLAOrtLUyt7i5uiUhADs="]
+  );
+
+  ////////////////////////////////////////////////////////////////////////
+  // The main editor area...
+  const oDoc = D.attr(D.div(), 'id', "wysiwygBox");
+  D.attr(oDoc, 'contenteditable', 'true');
+  D.append(outerContainer, oDoc);
+  
+  /* Initialize the document editor */
+  function initDoc() {
+    initEventHandlers();
+    if (!isWysiwyg()) { setDocMode(true); }
+  }
+
+  function initEventHandlers() {
+    //console.debug("initEventHandlers()");
+    const handleDropDown = function() {
+      formatDoc(this.dataset.format,this[this.selectedIndex].value);
+      this.selectedIndex = 0;
+    };
+    
+    const handleFormatButton = function() {
+      var extra;
+      switch (this.dataset.format) {
+      case 'createlink':
+        const sLnk = prompt('Target URL:','');
+        if(sLnk) extra = sLnk;
+        break;
+      case 'formatblock':
+        extra = 'blockquote';
+        break;
+      }
+      formatDoc(this.dataset.format, extra);
+    };
+
+    var i, controls = outerContainer.querySelectorAll('select.format');
+    for(i = 0; i < controls.length; i++) {
+      controls[i].addEventListener('change', handleDropDown, false);;
+    }
+    controls = outerContainer.querySelectorAll('.intLink');
+    for(i = 0; i < controls.length; i++) {
+      controls[i].addEventListener('click', handleFormatButton, false);
+    }
+  }
+
+  /* Return true if the document editor is in WYSIWYG mode.  Return
+  ** false if it is in Markup mode */
+  function isWysiwyg() {
+    return radio0.checked;
+  }
+
+  /* Run the editing command if in WYSIWYG mode */
+  function formatDoc(sCmd, sValue) {
+    if (isWysiwyg()){
+      try {
+        // First, try the W3C draft standard way, which has
+        // been working on all non-IE browsers for a while.
+        // It is also supported by IE11 and higher.
+        document.execCommand("styleWithCSS", false, false);
+      } catch (e) {
+        try {
+          // For IE9 or IE10, this should work.
+          document.execCommand("useCSS", 0, true);
+        } catch (e) {
+          // OK, that apparently did not work, do nothing.
+        }
+      }
+      document.execCommand(sCmd, false, sValue);
+      oDoc.focus();
+    }
+  }
+
+  /* Change the editing mode.  Convert to markup if the argument
+  ** is true and wysiwyg if the argument is false. */
+  function setDocMode(bToMarkup, content) {
+    if(undefined===content){
+      content = bToMarkup ? oDoc.innerHTML : oDoc.innerText;
+    }
+    if(!setDocMode.linebreak){
+      setDocMode.linebreak = new RegExp("

","ig"); + } + if(!setDocMode.toHide){ + setDocMode.toHide = toolbars.querySelectorAll( + '#wysiwyg-toolBar1 > *:not(#wysiwygEditMode), ' + +'#wysiwyg-toolBar2'); + } + if (bToMarkup) { + /* WYSIWYG -> Markup */ + // Legacy did this: content=content.replace(setDocMode.linebreak,"

\n\n

") + D.append(D.clearElement(oDoc), content) + oDoc.style.whiteSpace = "pre-wrap"; + D.addClass(setDocMode.toHide, 'hidden'); + } else { + /* Markup -> WYSIWYG */ + D.parseHtml(D.clearElement(oDoc), content); + oDoc.style.whiteSpace = "normal"; + D.removeClass(setDocMode.toHide, 'hidden'); + } + oDoc.focus(); + } + + //////////////////////////////////////////////////////////////////////// + // A hook which can be activated via a site skin to plug this editor + // in to the wikiedit page. + F.page.wysiwyg = { + // only for debugging: oDoc: oDoc, + /* + Replaces wikiedit's default editor widget with this wysiwyg + editor. + + Must either be called via an onPageLoad handler via the site + skin's footer or else it can be called manually from the dev + tools console. Calling it too early (e.g. in the page footer + outside of an an onPageLoad handler) will crash because wikiedit + has not been initialized. + */ + init: function(){ + initDoc(); + const content = F.page.wikiContent() || ''; + var isDirty = false /* keep from stashing too often */; + F.page.setContentMethods( + function(){ + const rc = isWysiwyg() ? oDoc.innerHTML : oDoc.innerText; + return rc; + }, + function(content){ + isDirty = false; + setDocMode(radio0.checked ? 0 : 1, content); + } + ); + oDoc.addEventListener('blur', function(){ + if(isDirty) F.page.notifyOfChange(); + }, false); + oDoc.addEventListener('input', function(){isDirty = true}, false); + F.page.wikiContent(content)/*feed it back in to our widget*/; + F.page.replaceEditorElement(outerContainer); + F.message("Replaced wiki editor widget with legacy wysiwyg editor."); + } + }; +})(window.fossil); ADDED src/fshell.c Index: src/fshell.c ================================================================== --- /dev/null +++ src/fshell.c @@ -0,0 +1,130 @@ +/* +** Copyright (c) 2016 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 module contains the code that implements the "fossil shell" command. +** +** The fossil shell prompts for lines of user input, then parses each line +** after the fashion of a standard Bourne shell and forks a child process +** to run the corresponding Fossil command. This only works on Unix. +** +** The "fossil shell" command is intended for use with SEE-enabled fossil. +** It allows multiple commands to be issued without having to reenter the +** crypto phasephrase for each command. +*/ +#include "config.h" +#include "fshell.h" +#include + +#ifndef _WIN32 +# include "linenoise.h" +# include +# include +#endif + + +/* +** COMMAND: shell* +** +** Usage: %fossil shell +** +** Prompt for lines of input from stdin. Parse each line and evaluate +** it as a separate fossil command, in a child process. The initial +** "fossil" is omitted from each line. +** +** This command only works on unix-like platforms that support fork(). +** It is non-functional on Windows. +*/ +void shell_cmd(void){ +#ifdef _WIN32 + fossil_fatal("the 'shell' command is not supported on windows"); +#else + int nArg; + int mxArg = 0; + int n, i; + char **azArg = 0; + int fDebug; + pid_t childPid; + char *zLine = 0; + char *zPrompt = 0; + fDebug = find_option("debug", 0, 0)!=0; + db_find_and_open_repository(OPEN_ANY_SCHEMA|OPEN_OK_NOT_FOUND, 0); + if(g.zRepositoryName!=0){ + zPrompt = mprintf("fossil (%z)> ", db_get("project-name","unnamed")); + }else{ + zPrompt = mprintf("fossil (no repo)> "); + } + db_close(0); + sqlite3_shutdown(); + linenoiseSetMultiLine(1); + while( (free(zLine), zLine = linenoise(zPrompt)) ){ + /* Remember shell history within the current session */ + linenoiseHistoryAdd(zLine); + + /* Parse the line of input */ + n = (int)strlen(zLine); + for(i=0, nArg=1; i=n ) break; + if( nArg>=mxArg ){ + mxArg = nArg+10; + azArg = fossil_realloc(azArg, sizeof(char*)*mxArg); + if( nArg==1 ) azArg[0] = g.argv[0]; + } + if( zLine[i]=='"' || zLine[i]=='\'' ){ + char cQuote = zLine[i]; + i++; + azArg[nArg++] = &zLine[i]; + for(i++; i +#include +#include +#include +#include +#include +#include +#include "fusefs.h" + +#define FUSE_USE_VERSION 26 +#include + +/* +** Global state information about the archive +*/ +static struct sGlobal { + /* A cache of a single check-in manifest */ + int rid; /* rid for the cached manifest */ + char *zSymName; /* Symbolic name corresponding to rid */ + Manifest *pMan; /* The cached manifest */ + /* A cache of a single file within a single check-in */ + int iFileRid; /* Check-in ID for the cached file */ + ManifestFile *pFile; /* Name of a cached file */ + Blob content; /* Content of the cached file */ + /* Parsed path */ + char *az[3]; /* 0=type, 1=id, 2=path */ +} fusefs; + +/* +** Clear the fusefs.az[] array. +*/ +static void fusefs_clear_path(void){ + int i; + for(i=0; i0 && strcmp(zSymName, fusefs.zSymName)==0 ){ + return fusefs.rid; + }else{ + return symbolic_name_to_rid(zSymName, "ci"); + } +} + + +/* +** Implementation of stat() +*/ +static int fusefs_getattr(const char *zPath, struct stat *stbuf){ + int n, rid; + ManifestFile *pFile; + char *zDir; + stbuf->st_uid = getuid(); + stbuf->st_gid = getgid(); + n = fusefs_parse_path(zPath); + if( n==0 ){ + stbuf->st_mode = S_IFDIR | 0555; + stbuf->st_nlink = 2; + return 0; + } + if( strcmp(fusefs.az[0],"checkins")!=0 ) return -ENOENT; + if( n==1 ){ + stbuf->st_mode = S_IFDIR | 0111; + stbuf->st_nlink = 2; + return 0; + } + rid = fusefs_name_to_rid(fusefs.az[1]); + if( rid<=0 ) return -ENOENT; + if( n==2 ){ + stbuf->st_mode = S_IFDIR | 0555; + stbuf->st_nlink = 2; + return 0; + } + fusefs_load_rid(rid, fusefs.az[1]); + if( fusefs.pMan==0 ) return -ENOENT; + stbuf->st_mtime = (fusefs.pMan->rDate - 2440587.5)*86400.0; + pFile = manifest_file_seek(fusefs.pMan, fusefs.az[2], 0); + if( pFile ){ + static Stmt q; + stbuf->st_mode = S_IFREG | + (manifest_file_mperm(pFile)==PERM_EXE ? 0555 : 0444); + stbuf->st_nlink = 1; + db_static_prepare(&q, "SELECT size FROM blob WHERE uuid=$uuid"); + db_bind_text(&q, "$uuid", pFile->zUuid); + if( db_step(&q)==SQLITE_ROW ){ + stbuf->st_size = db_column_int(&q, 0); + } + db_reset(&q); + return 0; + } + zDir = mprintf("%s/", fusefs.az[2]); + pFile = manifest_file_seek(fusefs.pMan, zDir, 1); + fossil_free(zDir); + if( pFile==0 ) return -ENOENT; + n = (int)strlen(fusefs.az[2]); + if( strncmp(fusefs.az[2], pFile->zName, n)!=0 ) return -ENOENT; + if( pFile->zName[n]!='/' ) return -ENOENT; + stbuf->st_mode = S_IFDIR | 0555; + stbuf->st_nlink = 2; + return 0; +} + +/* +** Implementation of readdir() +*/ +static int fusefs_readdir( + const char *zPath, + void *buf, + fuse_fill_dir_t filler, + off_t offset, + struct fuse_file_info *fi +){ + int n, rid; + ManifestFile *pFile; + const char *zPrev = ""; + int nPrev = 0; + char *z; + int cnt = 0; + n = fusefs_parse_path(zPath); + if( n==0 ){ + filler(buf, ".", NULL, 0); + filler(buf, "..", NULL, 0); + filler(buf, "checkins", NULL, 0); + return 0; + } + if( strcmp(fusefs.az[0],"checkins")!=0 ) return -ENOENT; + if( n==1 ) return -ENOENT; + rid = fusefs_name_to_rid(fusefs.az[1]); + if( rid<=0 ) return -ENOENT; + fusefs_load_rid(rid, fusefs.az[1]); + if( fusefs.pMan==0 ) return -ENOENT; + filler(buf, ".", NULL, 0); + filler(buf, "..", NULL, 0); + manifest_file_rewind(fusefs.pMan); + if( n==2 ){ + while( (pFile = manifest_file_next(fusefs.pMan, 0))!=0 ){ + if( nPrev>0 && strncmp(pFile->zName, zPrev, nPrev)==0 + && pFile->zName[nPrev]=='/' ) continue; + zPrev = pFile->zName; + for(nPrev=0; zPrev[nPrev] && zPrev[nPrev]!='/'; nPrev++){} + z = mprintf("%.*s", nPrev, zPrev); + filler(buf, z, NULL, 0); + fossil_free(z); + cnt++; + } + }else{ + char *zBase = mprintf("%s/", fusefs.az[2]); + int nBase = (int)strlen(zBase); + while( (pFile = manifest_file_next(fusefs.pMan, 0))!=0 ){ + if( strcmp(pFile->zName, zBase)>=0 ) break; + } + while( pFile && strncmp(zBase, pFile->zName, nBase)==0 ){ + if( nPrev==0 || strncmp(pFile->zName+nBase, zPrev, nPrev)!=0 ){ + zPrev = pFile->zName+nBase; + for(nPrev=0; zPrev[nPrev] && zPrev[nPrev]!='/'; nPrev++){} + if( zPrev[nPrev]=='/' ){ + z = mprintf("%.*s", nPrev, zPrev); + filler(buf, z, NULL, 0); + fossil_free(z); + }else{ + filler(buf, zPrev, NULL, 0); + nPrev = 0; + } + cnt++; + } + pFile = manifest_file_next(fusefs.pMan, 0); + } + fossil_free(zBase); + } + return cnt>0 ? 0 : -ENOENT; +} + + +/* +** Implementation of read() +*/ +static int fusefs_read( + const char *zPath, + char *buf, + size_t size, + off_t offset, + struct fuse_file_info *fi +){ + int n, rid; + n = fusefs_parse_path(zPath); + if( n<3 ) return -ENOENT; + if( strcmp(fusefs.az[0], "checkins")!=0 ) return -ENOENT; + rid = fusefs_name_to_rid(fusefs.az[1]); + if( rid<=0 ) return -ENOENT; + fusefs_load_rid(rid, fusefs.az[1]); + if( fusefs.pFile!=0 && strcmp(fusefs.az[2], fusefs.pFile->zName)!=0 ){ + fusefs.pFile = 0; + blob_reset(&fusefs.content); + } + fusefs.pFile = manifest_file_seek(fusefs.pMan, fusefs.az[2], 0); + if( fusefs.pFile==0 ) return -ENOENT; + rid = uuid_to_rid(fusefs.pFile->zUuid, 0); + blob_reset(&fusefs.content); + content_get(rid, &fusefs.content); + if( offset>blob_size(&fusefs.content) ) return 0; + if( offset+size>blob_size(&fusefs.content) ){ + size = blob_size(&fusefs.content) - offset; + } + memcpy(buf, blob_buffer(&fusefs.content)+offset, size); + return size; +} + +static struct fuse_operations fusefs_methods = { + .getattr = fusefs_getattr, + .readdir = fusefs_readdir, + .read = fusefs_read, +}; + +/* +** COMMAND: fusefs* +** +** Usage: %fossil fusefs [--debug] DIRECTORY +** +** This command uses the Fuse Filesystem (FuseFS) to mount a directory +** at DIRECTORY that contains the content of all check-ins in the +** repository. The names of files are DIRECTORY/checkins/VERSION/PATH +** where DIRECTORY is the root of the mount, VERSION is any valid +** check-in name (examples: "trunk" or "tip" or a tag or any unique +** prefix of an artifact hash, etc) and PATH is the pathname of the file in +** the check-in. If DIRECTORY does not exist, then an attempt is made +** to create it. +** +** The DIRECTORY/checkins directory is not searchable so one cannot +** do "ls DIRECTORY/checkins" to get a listing of all possible check-in +** names. There are countless variations on check-in names and it is +** impractical to list them all. But all other directories are searchable +** and so the "ls" command will work everywhere else in the fusefs +** file hierarchy. +** +** The FuseFS typically only works on Linux, and then only on Linux +** systems that have the right kernel drivers and have installed the +** appropriate support libraries. +** +** After stopping the "fossil fusefs" command, it might also be necessary +** to run "fusermount -u DIRECTORY" to reset the FuseFS before using it +** again. +*/ +void fusefs_cmd(void){ + char *zMountPoint; + char *azNewArgv[5]; + int doDebug = find_option("debug","d",0)!=0; + + db_find_and_open_repository(0,0); + verify_all_options(); + blob_init(&fusefs.content, 0, 0); + if( g.argc!=3 ) usage("DIRECTORY"); + zMountPoint = g.argv[2]; + if( file_mkdir(zMountPoint, ExtFILE, 0) ){ + fossil_fatal("cannot make directory [%s]", zMountPoint); + } + azNewArgv[0] = g.argv[0]; + azNewArgv[1] = doDebug ? "-d" : "-f"; + azNewArgv[2] = "-s"; + azNewArgv[3] = zMountPoint; + azNewArgv[4] = 0; + g.localOpen = 0; /* Prevent tags like "current" and "prev" */ + fuse_main(4, azNewArgv, &fusefs_methods, NULL); + fusefs_reset(); + fusefs_clear_path(); +} +#endif /* FOSSIL_HAVE_FUSEFS */ + +/* +** Return version numbers for the FUSE header that was used at compile-time +** and/or the FUSE library that was loaded at runtime. +*/ +const char *fusefs_lib_version(void){ +#if defined(FOSSIL_HAVE_FUSEFS) && FUSE_MAJOR_VERSION>=3 + return fuse_pkgversion(); +#else + return "unknown"; +#endif +} + +const char *fusefs_inc_version(void){ +#ifdef FOSSIL_HAVE_FUSEFS + return COMPILER_STRINGIFY(FUSE_MAJOR_VERSION) "." + COMPILER_STRINGIFY(FUSE_MINOR_VERSION); +#else + return "unknown"; +#endif +} ADDED src/fuzz.c Index: src/fuzz.c ================================================================== --- /dev/null +++ src/fuzz.c @@ -0,0 +1,141 @@ +/* +** Copyright (c) 2019 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 code to connect Fossil to libFuzzer. Do a web search +** for "libfuzzer" for details about that fuzzing platform. +** +** To build on linux (the only platform for which this works at +** present) first do +** +** ./configure +** +** Then edit the Makefile as follows: +** +** (1) Change CC to be "clang-6.0" or some other compiler that +** supports libFuzzer +** +** (2) Change APPNAME to "fossil-fuzz" +** +** (3) Add "-fsanitize=fuzzer" and "-DFOSSIL_FUZZ" to TCCFLAGS. Perhaps +** make the first change "-fsanitize=fuzzer,undefined,address" for +** extra, but slower, testing. +** +** Then build the fuzzer using: +** +** make clean fossil-fuzz +** +** To run the fuzzer, create a working directory ("cases"): +** +** mkdir cases +** +** Then seed the working directory with example input files. For example, +** if fuzzing the wiki formatter, perhaps copy *.wiki into cases. Then +** run the fuzzer thusly: +** +** fossil-fuzz cases +** +** The default is to fuzz the Fossil-wiki translator. Use the --fuzztype TYPE +** option to fuzz different aspects of the system. +*/ +#include "config.h" +#include "fuzz.h" + +#if LOCAL_INTERFACE +/* +** Type of fuzzing: +*/ +#define FUZZ_WIKI 0 /* The Fossil-Wiki formatter */ +#define FUZZ_MARKDOWN 1 /* The Markdown formatter */ +#define FUZZ_ARTIFACT 2 /* Fuzz the artifact parser */ +#endif + +/* The type of fuzzing to do */ +static int eFuzzType = FUZZ_WIKI; + +/* The fuzzer invokes this routine once for each fuzzer input +*/ +int LLVMFuzzerTestOneInput(const uint8_t *aData, size_t nByte){ + Blob in, out; + blob_init(&in, 0, 0); + blob_append(&in, (char*)aData, (int)nByte); + blob_zero(&out); + switch( eFuzzType ){ + case FUZZ_WIKI: { + Blob title = BLOB_INITIALIZER; + wiki_convert(&in, &out, 0); + blob_reset(&out); + markdown_to_html(&in, &title, &out); + blob_reset(&title); + break; + } + } + blob_reset(&in); + blob_reset(&out); + return 0; +} + +/* +** Check fuzzer command-line options. +*/ +static void fuzzer_options(void){ + const char *zType; + db_find_and_open_repository(OPEN_OK_NOT_FOUND|OPEN_SUBSTITUTE,0); + db_multi_exec("PRAGMA query_only=1;"); + zType = find_option("fuzztype",0,1); + if( zType==0 || fossil_strcmp(zType,"wiki")==0 ){ + eFuzzType = FUZZ_WIKI; + }else if( fossil_strcmp(zType,"markdown")==0 ){ + eFuzzType = FUZZ_MARKDOWN; + }else{ + fossil_fatal("unknown fuzz type: \"%s\"", zType); + } +} + +/* Libfuzzer invokes this routine once prior to start-up to +** process command-line options. +*/ +int LLVMFuzzerInitialize(int *pArgc, char ***pArgv){ + expand_args_option(*pArgc, *pArgv); + fuzzer_options(); + *pArgc = g.argc; + *pArgv = g.argv; + return 0; +} + +/* +** COMMAND: test-fuzz +** +** Usage: %fossil test-fuzz [-type TYPE] INPUTFILE... +** +** Run a fuzz test using INPUTFILE as the test data. TYPE can be one of: +** +** wiki Fuzz the Fossil-wiki translator +** markdown Fuzz the markdown translator +** artifact Fuzz the artifact parser +*/ +void fuzz_command(void){ + Blob in; + int i; + fuzzer_options(); + verify_all_options(); + for(i=2; i + +/* +** Construct and return a string which is an SQL expression that will +** be TRUE if value zVal matches any of the GLOB expressions in the list +** zGlobList. For example: +** +** zVal: "x" +** zGlobList: "*.o,*.obj" +** +** Result: "(x GLOB '*.o' OR x GLOB '*.obj')" +** +** Commas and whitespace are considered to be element delimters. Each +** element of the GLOB list may optionally be enclosed in either '...' or +** "...". This allows commas and/or whitespace to be used in the elements +** themselves. +** +** This routine makes no effort to free the memory space it uses, which +** currently consists of a blob object and its contents. +*/ +char *glob_expr(const char *zVal, const char *zGlobList){ + Blob expr; + const char *zSep = "("; + int nTerm = 0; + int i; + int cTerm; + + if( zGlobList==0 || zGlobList[0]==0 ) return fossil_strdup("0"); + blob_zero(&expr); + while( zGlobList[0] ){ + while( fossil_isspace(zGlobList[0]) || zGlobList[0]==',' ){ + zGlobList++; /* Skip leading commas, spaces, and newlines */ + } + if( zGlobList[0]==0 ) break; + if( zGlobList[0]=='\'' || zGlobList[0]=='"' ){ + cTerm = zGlobList[0]; + zGlobList++; + }else{ + cTerm = ','; + } + /* Find the next delimter (or the end of the string). */ + for(i=0; zGlobList[i] && zGlobList[i]!=cTerm; i++){ + if( cTerm!=',' ) continue; /* If quoted, keep going. */ + if( fossil_isspace(zGlobList[i]) ) break; /* If space, stop. */ + } + blob_appendf(&expr, "%s%s GLOB '%#q'", zSep, zVal, i, zGlobList); + zSep = " OR "; + if( cTerm!=',' && zGlobList[i] ) i++; + zGlobList += i; + if( zGlobList[0] ) zGlobList++; + nTerm++; + } + if( nTerm ){ + blob_appendf(&expr, ")"); + return blob_str(&expr); + }else{ + return fossil_strdup("0"); + } +} + +#if INTERFACE +/* +** A Glob object holds a set of patterns read to be matched against +** a string. +*/ +struct Glob { + int nPattern; /* Number of patterns */ + char **azPattern; /* Array of pointers to patterns */ +}; +#endif /* INTERFACE */ + +/* +** zPatternList is a comma-separated list of glob patterns. Parse up +** that list and use it to create a new Glob object. +** +** Elements of the glob list may be optionally enclosed in single our +** double-quotes. This allows a comma to be part of a glob pattern. +** +** Leading and trailing spaces on unquoted glob patterns are ignored. +** +** An empty or null pattern list results in a null glob, which will +** match nothing. +*/ +Glob *glob_create(const char *zPatternList){ + int nList; /* Size of zPatternList in bytes */ + int i; /* Loop counters */ + Glob *p; /* The glob being created */ + char *z; /* Copy of the pattern list */ + char delimiter; /* '\'' or '\"' or 0 */ + + if( zPatternList==0 || zPatternList[0]==0 ) return 0; + nList = strlen(zPatternList); + p = fossil_malloc( sizeof(*p) + nList+1 ); + memset(p, 0, sizeof(*p)); + z = (char*)&p[1]; + memcpy(z, zPatternList, nList+1); + while( z[0] ){ + while( fossil_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 = ','; + } + p->azPattern = fossil_realloc(p->azPattern, (p->nPattern+1)*sizeof(char*) ); + p->azPattern[p->nPattern++] = z; + /* 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( fossil_isspace(z[i]) ) break; /* If space, stop. */ + } + if( z[i]==0 ) break; + z[i] = 0; + z += i+1; + } + return p; +} + +/* +** Return true (non-zero) if zString matches any of the patterns in +** the Glob. The value returned is actually a 1-based index of the pattern +** that matched. Return 0 if none of the patterns match zString. +** +** A NULL glob matches nothing. +*/ +int glob_match(Glob *pGlob, const char *zString){ + int i; + if( pGlob==0 ) return 0; + for(i=0; inPattern; i++){ + if( sqlite3_strglob(pGlob->azPattern[i], zString)==0 ) return i+1; + } + return 0; +} + +/* +** Free all memory associated with the given Glob object +*/ +void glob_free(Glob *pGlob){ + if( pGlob ){ + fossil_free(pGlob->azPattern); + fossil_free(pGlob); + } +} + +/* +** Appends the given glob to the given buffer in the form of a +** JS/JSON-compatible array. It requires that pDest have been +** initialized. If pGlob is NULL or empty it emits [] (an empty +** array). +*/ +void glob_render_json_to_blob(Glob *pGlob, Blob *pDest){ + int i = 0; + blob_append(pDest, "[", 1); + for( ; pGlob && i < pGlob->nPattern; ++i ){ + if(i){ + blob_append(pDest, ",", 1); + } + blob_appendf(pDest, "%!j", pGlob->azPattern[i]); + } + blob_append(pDest, "]", 1); +} +/* +** Functionally equivalent to glob_render_json_to_blob() +** but outputs via cgi_print(). +*/ +void glob_render_json_to_cgi(Glob *pGlob){ + int i = 0; + CX("["); + for( ; pGlob && i < pGlob->nPattern; ++i ){ + if(i){ + CX(","); + } + CX("%!j", pGlob->azPattern[i]); + } + CX("]"); +} + +/* +** COMMAND: test-glob +** +** Usage: %fossil test-glob PATTERN STRING... +** +** PATTERN is a comma- and whitespace-separated list of optionally +** quoted glob patterns. Show which of the STRINGs that follow match +** the PATTERN. +** +** If PATTERN begins with "@" the rest of the pattern is understood +** to be a setting name (such as binary-glob, crln-glob, or encoding-glob) +** and the value of that setting is used as the actually glob pattern. +*/ +void glob_test_cmd(void){ + Glob *pGlob; + int i; + char *zPattern; + if( g.argc<4 ) usage("PATTERN STRING ..."); + zPattern = g.argv[2]; + if( zPattern[0]=='@' ){ + db_find_and_open_repository(OPEN_ANY_SCHEMA,0); + zPattern = db_get(zPattern+1, 0); + if( zPattern==0 ) fossil_fatal("no such setting: %s", g.argv[2]+1); + fossil_print("GLOB pattern: %s\n", zPattern); + } + fossil_print("SQL expression: %s\n", glob_expr("x", zPattern)); + pGlob = glob_create(zPattern); + for(i=0; inPattern; i++){ + fossil_print("pattern[%d] = [%s]\n", i, pGlob->azPattern[i]); + } + for(i=3; i + +/* Notes: +** +** The graph is laid out in 1 or more "rails". A "rail" is a vertical +** band in the graph in which one can place nodes or arrows connecting +** nodes. There can be between 1 and GR_MAX_RAIL rails. If the graph +** is to complex to be displayed in GR_MAX_RAIL rails, it is omitted. +** +** A "riser" is the thick line that comes out of the top of a node and +** goes up to the next node on the branch, or to the top of the screen. +** A "descender" is a thick line that comes out of the bottom of a node +** and proceeds down to the bottom of the page. +** +** Invoke graph_init() to create a new GraphContext object. Then +** call graph_add_row() to add nodes, one by one, to the graph. +** Nodes must be added in display order, from top to bottom. +** Then invoke graph_render() to run the layout algorithm. The +** layout algorithm computes which rails all of the nodes sit on, and +** the rails used for merge arrows. +*/ #if INTERFACE -#define GR_MAX_PARENT 10 -#define GR_MAX_RAIL 32 +/* +** The type of integer identifiers for rows of the graph. +** +** For a normal /timeline graph, the identifiers are never that big +** an an ordinary 32-bit int will work fine. But for the /finfo page, +** the identifier is a combination of the BLOB.RID and the FILENAME.FNID +** values, and so it can become quite large for repos that have both many +** check-ins and many files. For this reason, we make the identifier +** a 64-bit integer, to dramatically reduce the risk of an overflow. +*/ +typedef sqlite3_int64 GraphRowId; + +#define GR_MAX_RAIL 40 /* Max number of "rails" to display */ /* The graph appears vertically beside a timeline. Each row in the -** timeline corresponds to a row in the graph. +** timeline corresponds to a row in the graph. GraphRow.idx is 0 for +** the top-most row and increases moving down. Hence (in the absence of +** time skew) parents have a larger index than their children. +** +** The nParent field is -1 for entires that do not participate in the graph +** but which are included just so that we can capture their background color. */ struct GraphRow { - int rid; /* The rid for the check-in */ - int nParent; /* Number of parents */ - int aParent[GR_MAX_PARENT]; /* Array of parents. 0 element is primary .*/ + GraphRowId rid; /* The rid for the check-in */ + i8 nParent; /* Number of parents. */ + i8 nCherrypick; /* Subset of aParent that are cherrypicks */ + i8 nNonCherrypick; /* Number of non-cherrypick parents */ + u8 nMergeChild; /* Number of merge children */ + GraphRowId *aParent; /* Array of parents. 0 element is primary .*/ char *zBranch; /* Branch name */ char *zBgClr; /* Background Color */ + char zUuid[HNAME_MAX+1]; /* Check-in for file ID */ GraphRow *pNext; /* Next row down in the list of all rows */ GraphRow *pPrev; /* Previous row */ - - int idx; /* Row index. First is 1. 0 used for "none" */ - u8 isLeaf; /* True if no direct child nodes */ + + int idx; /* Row index. Top row is smallest. */ + int idxTop; /* Direct descendent highest up on the graph */ + GraphRow *pChild; /* Child immediately above this node */ u8 isDup; /* True if this is duplicate of a prior entry */ - int iRail; /* Which rail this check-in appears on. 0-based.*/ - int aiRaiser[GR_MAX_RAIL]; /* Raisers from this node to a higher row. */ - int bDescender; /* Raiser from bottom of graph to here. */ - u32 mergeIn; /* Merge in from other rails */ - int mergeOut; /* Merge out to this rail */ - int mergeUpto; /* Draw the merge rail up to this level */ - - u32 railInUse; /* Mask of occupied rails */ + u8 isLeaf; /* True if this is a leaf node */ + u8 isStepParent; /* pChild is actually a step-child */ + u8 hasNormalOutMerge; /* Is parent of at laest 1 non-cherrypick merge */ + u8 timeWarp; /* Child is earlier in time */ + u8 bDescender; /* True if riser from bottom of graph to here. */ + u8 selfUp; /* Space above this node but belonging */ + i8 iRail; /* Which rail this check-in appears on. 0-based.*/ + i8 mergeOut; /* Merge out to this rail. -1 if no merge-out */ + u8 mergeIn[GR_MAX_RAIL]; /* Merge in from non-zero rails */ + int aiRiser[GR_MAX_RAIL]; /* Risers from this node to a higher row. */ + int mergeUpto; /* Draw the mergeOut rail up to this level */ + int cherrypickUpto; /* Continue the mergeOut rail up to here */ + u64 mergeDown; /* Draw merge lines up from bottom of graph */ + u64 cherrypickDown; /* Draw cherrypick lines up from bottom */ + u64 railInUse; /* Mask of occupied rails at this row */ }; /* Context while building a graph */ struct GraphContext { - int nErr; /* Number of errors encountered */ - int mxRail; /* Number of rails required to render the graph */ - GraphRow *pFirst; /* First row in the list */ - GraphRow *pLast; /* Last row in the list */ - int nBranch; /* Number of distinct branches */ - char **azBranch; /* Names of the branches */ + int nErr; /* Number of errors encountered */ + int mxRail; /* Number of rails required to render the graph */ + GraphRow *pFirst; /* First row in the list. Top row of graph. */ + GraphRow *pLast; /* Last row in the list. Bottom row of graph. */ + int nBranch; /* Number of distinct branches */ + char **azBranch; /* Names of the branches */ int nRow; /* Number of rows */ - int railMap[GR_MAX_RAIL]; /* Rail order mapping */ int nHash; /* Number of slots in apHash[] */ - GraphRow **apHash; /* Hash table of rows */ + GraphRow **apHash; /* Hash table of GraphRow objects. Key: rid */ + u8 aiRailMap[GR_MAX_RAIL]; /* Mapping of rails to actually columns */ }; #endif + +/* The N-th bit */ +#define BIT(N) (((u64)1)<<(N)) + +/* +** Number of rows before and answer a node with a riser or descender +** that goes off-screen before we can reuse that rail. +*/ +#define RISER_MARGIN 4 + /* ** Malloc for zeroed space. Panic if unable to provide the ** requested space. */ void *safeMalloc(int nByte){ - void *p = malloc(nByte); - if( p==0 ) fossil_panic("out of memory"); + void *p = fossil_malloc(nByte); memset(p, 0, nByte); return p; } /* @@ -86,13 +143,13 @@ GraphContext *graph_init(void){ return (GraphContext*)safeMalloc( sizeof(GraphContext) ); } /* -** Destroy a GraphContext; +** Clear all content from a graph */ -void graph_free(GraphContext *p){ +static void graph_clear(GraphContext *p){ int i; GraphRow *pRow; while( p->pFirst ){ pRow = p->pFirst; p->pFirst = pRow->pNext; @@ -99,17 +156,26 @@ free(pRow); } for(i=0; inBranch; i++) free(p->azBranch[i]); free(p->azBranch); free(p->apHash); + memset(p, 0, sizeof(*p)); + p->nErr = 1; +} + +/* +** Destroy a GraphContext; +*/ +void graph_free(GraphContext *p){ + graph_clear(p); free(p); } /* -** Insert a row into the hash table. If there is already another -** row with the same rid, overwrite the prior entry if the overwrite -** flag is set. +** Insert a row into the hash table. pRow->rid is the key. Keys must +** be unique. If there is already another row with the same rid, +** overwrite the prior entry if and only if the overwrite flag is set. */ static void hashInsert(GraphContext *p, GraphRow *pRow, int overwrite){ int h; h = pRow->rid % p->nHash; while( p->apHash[h] && p->apHash[h]->rid!=pRow->rid ){ @@ -122,11 +188,11 @@ } /* ** Look up the row with rid. */ -static GraphRow *hashFind(GraphContext *p, int rid){ +static GraphRow *hashFind(GraphContext *p, GraphRowId rid){ int h = rid % p->nHash; while( p->apHash[h] && p->apHash[h]->rid!=rid ){ h++; if( h>=p->nHash ) h = 0; } @@ -135,82 +201,102 @@ /* ** Return the canonical pointer for a given branch name. ** Multiple calls to this routine with equivalent strings ** will return the same pointer. +** +** The returned value is a pointer to a (readonly) string that +** has the useful property that strings can be checked for +** equality by comparing pointers. ** ** Note: also used for background color names. */ static char *persistBranchName(GraphContext *p, const char *zBranch){ int i; for(i=0; inBranch; i++){ - if( strcmp(zBranch, p->azBranch[i])==0 ) return p->azBranch[i]; + if( fossil_strcmp(zBranch, p->azBranch[i])==0 ) return p->azBranch[i]; } p->nBranch++; - p->azBranch = realloc(p->azBranch, sizeof(char*)*p->nBranch); - if( p->azBranch==0 ) fossil_panic("out of memory"); + p->azBranch = fossil_realloc(p->azBranch, sizeof(char*)*p->nBranch); p->azBranch[p->nBranch-1] = mprintf("%s", zBranch); return p->azBranch[p->nBranch-1]; } /* -** Add a new row t the graph context. Rows are added from top to bottom. +** Add a new row to the graph context. Rows are added from top to bottom. */ int graph_add_row( GraphContext *p, /* The context to which the row is added */ - int rid, /* RID for the check-in */ + GraphRowId rid, /* RID for the check-in */ int nParent, /* Number of parents */ - int *aParent, /* Array of parents */ + int nCherrypick, /* How many of aParent[] are actually cherrypicks */ + GraphRowId *aParent, /* Array of parents */ const char *zBranch, /* Branch for this check-in */ - const char *zBgClr /* Background color. NULL or "" for white. */ + const char *zBgClr, /* Background color. NULL or "" for white. */ + const char *zUuid, /* hash name of the object being graphed */ + int isLeaf /* True if this row is a leaf */ ){ GraphRow *pRow; + int nByte; + static int nRow = 0; if( p->nErr ) return 0; - if( nParent>GR_MAX_PARENT ){ p->nErr++; return 0; } - pRow = (GraphRow*)safeMalloc( sizeof(GraphRow) ); + nByte = sizeof(GraphRow); + if( nParent>0 ) nByte += sizeof(pRow->aParent[0])*nParent; + pRow = (GraphRow*)safeMalloc( nByte ); + pRow->aParent = nParent>0 ? (GraphRowId*)&pRow[1] : 0; pRow->rid = rid; + if( nCherrypick>=nParent ){ + nCherrypick = nParent-1; /* Safety. Should never happen. */ + } pRow->nParent = nParent; + pRow->nCherrypick = nCherrypick; + pRow->nNonCherrypick = nParent - nCherrypick; pRow->zBranch = persistBranchName(p, zBranch); - if( zBgClr==0 || zBgClr[0]==0 ) zBgClr = "white"; + if( zUuid==0 ) zUuid = ""; + sqlite3_snprintf(sizeof(pRow->zUuid), pRow->zUuid, "%s", zUuid); + pRow->isLeaf = isLeaf; + memset(pRow->aiRiser, -1, sizeof(pRow->aiRiser)); + if( zBgClr==0 ) zBgClr = ""; pRow->zBgClr = persistBranchName(p, zBgClr); - memcpy(pRow->aParent, aParent, sizeof(aParent[0])*nParent); + if( nParent>0 ) memcpy(pRow->aParent, aParent, sizeof(aParent[0])*nParent); if( p->pFirst==0 ){ p->pFirst = pRow; }else{ p->pLast->pNext = pRow; } p->pLast = pRow; p->nRow++; - pRow->idx = p->nRow; + pRow->idx = pRow->idxTop = ++nRow; return pRow->idx; } /* ** Return the index of a rail currently not in use for any row between -** top and bottom, inclusive. +** top and bottom, inclusive. */ static int findFreeRail( GraphContext *p, /* The graph context */ int top, int btm, /* Span of rows for which the rail is needed */ - u32 inUseMask, /* Mask or rails already in use */ int iNearto /* Find rail nearest to this rail */ ){ GraphRow *pRow; int i; int iBest = 0; int iBestDist = 9999; + u64 inUseMask = 0; for(pRow=p->pFirst; pRow && pRow->idxpNext){} while( pRow && pRow->idx<=btm ){ inUseMask |= pRow->railInUse; pRow = pRow->pNext; } - for(i=0; i<32; i++){ - if( (inUseMask & (1<1000 ) p->nErr++; + if( iBest>p->mxRail ) p->mxRail = iBest; return iBest; } + +/* +** Assign all children of node pBottom to the same rail as pBottom. +*/ +static void assignChildrenToRail(GraphRow *pBottom, u32 tmFlags){ + int iRail = pBottom->iRail; + GraphRow *pCurrent; + GraphRow *pPrior; + u64 mask = ((u64)1)<railInUse |= mask; + pPrior = pBottom; + for(pCurrent=pBottom->pChild; pCurrent; pCurrent=pCurrent->pChild){ + assert( pPrior->idx > pCurrent->idx ); + assert( pCurrent->iRail<0 ); + if( pPrior->timeWarp ) break; + pCurrent->iRail = iRail; + pCurrent->railInUse |= mask; + pPrior->aiRiser[iRail] = pCurrent->idx; + while( pPrior->idx > pCurrent->idx ){ + pPrior->railInUse |= mask; + pPrior = pPrior->pPrev; + assert( pPrior!=0 ); + } + } + /* Mask of additional rows for the riser to infinity */ + if( !pPrior->isLeaf && (tmFlags & TIMELINE_DISJOINT)==0 ){ + int n = RISER_MARGIN; + GraphRow *p; + pPrior->selfUp = 0; + for(p=pPrior; p && (n--)>0; p=p->pPrev){ + pPrior->selfUp++; + p->railInUse |= mask; + } + } +} + +/* +** Create a merge-arrow riser going from pParent up to pChild. +*/ +static void createMergeRiser( + GraphContext *p, + GraphRow *pParent, + GraphRow *pChild, + int isCherrypick +){ + int u; + u64 mask; + GraphRow *pLoop; + + if( pParent->mergeOut<0 ){ + u = pParent->aiRiser[pParent->iRail]; + if( u>0 && uidx ){ + /* The thick arrow up to the next primary child of pDesc goes + ** further up than the thin merge arrow riser, so draw them both + ** on the same rail. */ + pParent->mergeOut = pParent->iRail; + }else if( pParent->idx - pChild->idx < pParent->selfUp ){ + pParent->mergeOut = pParent->iRail; + }else{ + /* The thin merge arrow riser is taller than the thick primary + ** child riser, so use separate rails. */ + int iTarget = pParent->iRail; + pParent->mergeOut = findFreeRail(p, pChild->idx, pParent->idx-1, iTarget); + mask = BIT(pParent->mergeOut); + for(pLoop=pChild->pNext; pLoop && pLoop->rid!=pParent->rid; + pLoop=pLoop->pNext){ + pLoop->railInUse |= mask; + } + } + } + if( isCherrypick ){ + if( pParent->cherrypickUpto==0 || pParent->cherrypickUpto > pChild->idx ){ + pParent->cherrypickUpto = pChild->idx; + } + }else{ + pParent->hasNormalOutMerge = 1; + if( pParent->mergeUpto==0 || pParent->mergeUpto > pChild->idx ){ + pParent->mergeUpto = pChild->idx; + } + } + pChild->mergeIn[pParent->mergeOut] = isCherrypick ? 2 : 1; +} + +/* +** Compute the maximum rail number. +*/ +static void find_max_rail(GraphContext *p){ + GraphRow *pRow; + p->mxRail = 0; + for(pRow=p->pFirst; pRow; pRow=pRow->pNext){ + if( pRow->iRail>p->mxRail ) p->mxRail = pRow->iRail; + if( pRow->mergeOut>p->mxRail ) p->mxRail = pRow->mergeOut; + while( p->mxRailmergeDown|pRow->cherrypickDown)>(BIT(p->mxRail+1)-1) + ){ + p->mxRail++; + } + } +} + +/* +** Draw a riser from pRow upward to indicate that it is going +** to a node that is off the graph to the top. +*/ +static void riser_to_top(GraphRow *pRow){ + u64 mask = BIT(pRow->iRail); + int n = RISER_MARGIN; + pRow->aiRiser[pRow->iRail] = 0; + while( pRow && (n--)>0 ){ + pRow->railInUse |= mask; + pRow = pRow->pPrev; + } +} + /* ** Compute the complete graph +** +** When primary or merge parents are off-screen, normally a line is drawn +** from the node down to the bottom of the graph. This line is called a +** "descender". But if the omitDescenders flag is true, then lines down +** to the bottom of the screen are omitted. +** +** The tmFlags parameter is zero or more of the TIMELINE_* constants. +** Only the following are honored: +** +** TIMELINE_DISJOINT: Omit descenders +** TIMELINE_FILLGAPS: Use step-children +** TIMELINE_XMERGE: Omit off-graph merge lines */ -void graph_finish(GraphContext *p, int omitDescenders){ - GraphRow *pRow, *pDesc, *pDup, *pLoop; - int i; - u32 mask; - u32 inUse; - int hasDup = 0; /* True if one or more isDup entries */ +void graph_finish(GraphContext *p, const char *zLeftBranch, u32 tmFlags){ + GraphRow *pRow, *pDesc, *pDup, *pLoop, *pParent; + int i, j; + u64 mask; + int hasDup = 0; /* True if one or more isDup entries */ const char *zTrunk; + u8 *aMap; /* Copy of p->aiRailMap */ + int omitDescenders = (tmFlags & TIMELINE_DISJOINT)!=0; + int nTimewarp = 0; + int riserMargin = (tmFlags & TIMELINE_DISJOINT) ? 0 : RISER_MARGIN; + + /* If mergeRiserFrom[X]==Y that means rail X holds a merge riser + ** coming up from the bottom of the graph from off-screen check-in Y + ** where Y is the RID. There is no riser on rail X if mergeRiserFrom[X]==0. + */ + GraphRowId mergeRiserFrom[GR_MAX_RAIL]; if( p==0 || p->pFirst==0 || p->nErr ) return; + p->nErr = 1; /* Assume an error until proven otherwise */ /* Initialize all rows */ p->nHash = p->nRow*2 + 1; p->apHash = safeMalloc( sizeof(p->apHash[0])*p->nHash ); for(pRow=p->pFirst; pRow; pRow=pRow->pNext){ @@ -247,160 +471,396 @@ pDup->isDup = 1; } hashInsert(p, pRow, 1); } p->mxRail = -1; + memset(mergeRiserFrom, 0, sizeof(mergeRiserFrom)); + + /* Purge merge-parents that are out-of-graph if descenders are not + ** drawn. + ** + ** Each node has one primary parent and zero or more "merge" parents. + ** A merge parent is a prior check-in from which changes were merged into + ** the current check-in. If a merge parent is not in the visible section + ** of this graph, then no arrows will be drawn for it, so remove it from + ** the aParent[] array. + */ + if( (tmFlags & (TIMELINE_DISJOINT|TIMELINE_XMERGE))!=0 ){ + for(pRow=p->pFirst; pRow; pRow=pRow->pNext){ + for(i=1; inParent; i++){ + GraphRow *pParent = hashFind(p, pRow->aParent[i]); + if( pParent==0 ){ + memmove(pRow->aParent+i, pRow->aParent+i+1, + sizeof(pRow->aParent[0])*(pRow->nParent-i-1)); + pRow->nParent--; + if( inNonCherrypick ){ + pRow->nNonCherrypick--; + }else{ + pRow->nCherrypick--; + } + i--; + } + } + } + } + + /* Put the deepest (earliest) merge parent first in the list. + ** An off-screen merge parent is considered deepest. + */ + for(pRow=p->pFirst; pRow; pRow=pRow->pNext ){ + if( pRow->nParent<=1 ) continue; + for(i=1; inParent; i++){ + GraphRow *pParent = hashFind(p, pRow->aParent[i]); + if( pParent ) pParent->nMergeChild++; + } + if( pRow->nCherrypick>1 ){ + int iBest = -1; + int iDeepest = -1; + for(i=pRow->nNonCherrypick; inParent; i++){ + GraphRow *pParent = hashFind(p, pRow->aParent[i]); + if( pParent==0 ){ + iBest = i; + break; + } + if( pParent->idx>iDeepest ){ + iDeepest = pParent->idx; + iBest = i; + } + } + i = pRow->nNonCherrypick; + if( iBest>i ){ + GraphRowId x = pRow->aParent[i]; + pRow->aParent[i] = pRow->aParent[iBest]; + pRow->aParent[iBest] = x; + } + } + if( pRow->nNonCherrypick>2 ){ + int iBest = -1; + int iDeepest = -1; + for(i=1; inNonCherrypick; i++){ + GraphRow *pParent = hashFind(p, pRow->aParent[i]); + if( pParent==0 ){ + iBest = i; + break; + } + if( pParent->idx>iDeepest ){ + iDeepest = pParent->idx; + iBest = i; + } + } + if( iBest>1 ){ + GraphRowId x = pRow->aParent[1]; + pRow->aParent[1] = pRow->aParent[iBest]; + pRow->aParent[iBest] = x; + } + } + } + + /* If the primary parent is in a different branch, but there are + ** other parents in the same branch, reorder the parents to make + ** the parent from the same branch the primary parent. + */ + for(pRow=p->pFirst; pRow; pRow=pRow->pNext){ + if( pRow->isDup ) continue; + if( pRow->nNonCherrypick<2 ) continue; /* Not a fork */ + pParent = hashFind(p, pRow->aParent[0]); + if( pParent==0 ) continue; /* Parent off-screen */ + if( pParent->zBranch==pRow->zBranch ) continue; /* Same branch */ + for(i=1; inNonCherrypick; i++){ + pParent = hashFind(p, pRow->aParent[i]); + if( pParent && pParent->zBranch==pRow->zBranch ){ + GraphRowId t = pRow->aParent[0]; + pRow->aParent[0] = pRow->aParent[i]; + pRow->aParent[i] = t; + break; + } + } + } + + + /* Find the pChild pointer for each node. + ** + ** The pChild points to the node directly above on the same rail. + ** The pChild must be in the same branch. Leaf nodes have a NULL + ** pChild. + ** + ** In the case of a fork, choose the pChild that results in the + ** longest rail. + */ + for(pRow=p->pFirst; pRow; pRow=pRow->pNext){ + if( pRow->isDup ) continue; + if( pRow->nParent<=0 ) continue; /* Root node */ + pParent = hashFind(p, pRow->aParent[0]); + if( pParent==0 ) continue; /* Parent off-screen */ + if( pParent->zBranch!=pRow->zBranch ) continue; /* Different branch */ + if( pParent->idx <= pRow->idx ){ + pParent->timeWarp = 1; + nTimewarp++; + }else if( pRow->idxTop < pParent->idxTop ){ + pParent->pChild = pRow; + pParent->idxTop = pRow->idxTop; + } + } + + if( tmFlags & TIMELINE_FILLGAPS ){ + /* If a node has no pChild in the graph + ** and there is a node higher up in the graph + ** that is in the same branch and has no in-graph parent, then + ** make the lower node a step-child of the upper node. This will + ** be represented on the graph by a thick dotted line without an arrowhead. + */ + for(pRow=p->pFirst; pRow; pRow=pRow->pNext){ + if( pRow->pChild ) continue; + if( pRow->isLeaf ) continue; + for(pLoop=pRow->pPrev; pLoop; pLoop=pLoop->pPrev){ + if( pLoop->nParent>0 + && pLoop->zBranch==pRow->zBranch + && hashFind(p,pLoop->aParent[0])==0 + ){ + pRow->pChild = pLoop; + pRow->isStepParent = 1; + pLoop->aParent[0] = pRow->rid; + break; + } + } + } + } - /* Purge merge-parents that are out-of-graph + /* Set the idxTop values for all entries. The idxTop value is the + ** "idx" value for the top entry in its stack of children. */ for(pRow=p->pFirst; pRow; pRow=pRow->pNext){ - for(i=1; inParent; i++){ - if( hashFind(p, pRow->aParent[i])==0 ){ - pRow->aParent[i] = pRow->aParent[--pRow->nParent]; - i--; - } - } - } - - /* Figure out which nodes have no direct children (children on - ** the same rail). Mark such nodes as isLeaf. - */ - memset(p->apHash, 0, sizeof(p->apHash[0])*p->nHash); - for(pRow=p->pLast; pRow; pRow=pRow->pPrev) pRow->isLeaf = 1; - for(pRow=p->pLast; pRow; pRow=pRow->pPrev){ - GraphRow *pParent; - hashInsert(p, pRow, 0); - if( !pRow->isDup - && pRow->nParent>0 - && (pParent = hashFind(p, pRow->aParent[0]))!=0 - && pRow->zBranch==pParent->zBranch - ){ - pParent->isLeaf = 0; + GraphRow *pChild = pRow->pChild; + if( pChild && pRow->idxTop>pChild->idxTop ){ + pRow->idxTop = pChild->idxTop; } } /* Identify rows where the primary parent is off screen. Assign - ** each to a rail and draw descenders to the bottom of the screen. + ** each to a rail and draw descenders downward. + ** + ** Strive to put the "trunk" branch on far left. */ zTrunk = persistBranchName(p, "trunk"); for(i=0; i<2; i++){ - for(pRow=p->pFirst; pRow; pRow=pRow->pNext){ - if( i==0 ){ - if( pRow->zBranch!=zTrunk ) continue; - }else { - if( pRow->iRail>=0 ) continue; - } - if( pRow->nParent==0 || hashFind(p,pRow->aParent[0])==0 ){ - if( omitDescenders ){ - pRow->iRail = findFreeRail(p, pRow->idx, pRow->idx, 0, 0); - }else{ - pRow->iRail = ++p->mxRail; - } - mask = 1<<(pRow->iRail); - if( omitDescenders ){ - pRow->railInUse |= mask; - if( pRow->pNext ) pRow->pNext->railInUse |= mask; - }else{ - pRow->bDescender = pRow->nParent>0; - for(pDesc=pRow; pDesc; pDesc=pDesc->pNext){ - pDesc->railInUse |= mask; - } - } + for(pRow=p->pLast; pRow; pRow=pRow->pPrev){ + if( i==0 && pRow->zBranch!=zTrunk ) continue; + if( pRow->iRail>=0 ) continue; + if( pRow->isDup ) continue; + if( pRow->nParent<0 ) continue; + if( pRow->nParent==0 || hashFind(p,pRow->aParent[0])==0 ){ + pRow->iRail = findFreeRail(p, pRow->idxTop, pRow->idx+riserMargin, 0); + if( p->mxRail>=GR_MAX_RAIL ) return; + mask = BIT(pRow->iRail); + if( !omitDescenders ){ + int n = RISER_MARGIN; + pRow->bDescender = pRow->nParent>0; + for(pLoop=pRow; pLoop && (n--)>0; pLoop=pLoop->pNext){ + pLoop->railInUse |= mask; + } + } + assignChildrenToRail(pRow, tmFlags); } } } /* Assign rails to all rows that are still unassigned. - ** The first primary child of a row goes on the same rail as - ** that row. */ - inUse = (1<<(p->mxRail+1))-1; for(pRow=p->pLast; pRow; pRow=pRow->pPrev){ - int parentRid; - if( pRow->iRail>=0 ) continue; - if( pRow->isDup ){ - pRow->iRail = findFreeRail(p, pRow->idx, pRow->idx, inUse, 0); - pDesc = pRow; + GraphRowId parentRid; + + if( pRow->iRail>=0 ){ + if( pRow->pChild==0 && !pRow->timeWarp ){ + if( !omitDescenders && count_nonbranch_children(pRow->rid)!=0 ){ + riser_to_top(pRow); + } + } + continue; + } + if( pRow->isDup || pRow->nParent<0 ){ + continue; }else{ assert( pRow->nParent>0 ); parentRid = pRow->aParent[0]; - pDesc = hashFind(p, parentRid); - if( pDesc==0 ){ - /* Time skew */ + pParent = hashFind(p, parentRid); + if( pParent==0 ){ pRow->iRail = ++p->mxRail; - pRow->railInUse = 1<iRail; + if( p->mxRail>=GR_MAX_RAIL ) return; + pRow->railInUse = BIT(pRow->iRail); continue; } - if( pDesc->aiRaiser[pDesc->iRail]==0 && pDesc->zBranch==pRow->zBranch ){ - pRow->iRail = pDesc->iRail; - }else{ - pRow->iRail = findFreeRail(p, 0, pDesc->idx, inUse, pDesc->iRail); - } - pDesc->aiRaiser[pRow->iRail] = pRow->idx; - } - mask = 1<iRail; - if( pRow->isLeaf ){ - inUse &= ~mask; - }else{ - inUse |= mask; - } - for(pLoop=pRow; pLoop && pLoop!=pDesc; pLoop=pLoop->pNext){ - pLoop->railInUse |= mask; - } - pDesc->railInUse |= mask; + if( pParent->idx>pRow->idx ){ + /* Common case: Child occurs after parent and is above the + ** parent in the timeline */ + pRow->iRail = findFreeRail(p, pRow->idxTop, pParent->idx, + pParent->iRail); + if( p->mxRail>=GR_MAX_RAIL ) return; + pParent->aiRiser[pRow->iRail] = pRow->idx; + }else{ + /* Timewarp case: Child occurs earlier in time than parent and + ** appears below the parent in the timeline. */ + int iDownRail = ++p->mxRail; + if( iDownRail<1 ) iDownRail = ++p->mxRail; + pRow->iRail = ++p->mxRail; + if( p->mxRail>=GR_MAX_RAIL ) return; + pRow->railInUse = BIT(pRow->iRail); + pParent->aiRiser[iDownRail] = pRow->idx; + mask = BIT(iDownRail); + for(pLoop=p->pFirst; pLoop; pLoop=pLoop->pNext){ + pLoop->railInUse |= mask; + } + } + } + mask = BIT(pRow->iRail); + pRow->railInUse |= mask; + if( pRow->pChild ){ + assignChildrenToRail(pRow, tmFlags); + }else if( !omitDescenders && count_nonbranch_children(pRow->rid)!=0 ){ + if( !pRow->timeWarp ) riser_to_top(pRow); + } + if( pParent ){ + if( pParent->idx>pRow->idx ){ + /* Common case: Parent is below current row in the graph */ + for(pLoop=pParent->pPrev; pLoop && pLoop!=pRow; pLoop=pLoop->pPrev){ + pLoop->railInUse |= mask; + } + }else{ + /* Timewarp case: Parent is above current row in the graph */ + for(pLoop=pParent->pNext; pLoop && pLoop!=pRow; pLoop=pLoop->pNext){ + pLoop->railInUse |= mask; + } + } + } } /* ** Insert merge rails and merge arrows */ for(pRow=p->pFirst; pRow; pRow=pRow->pNext){ - for(i=1; inParent; i++){ - int parentRid = pRow->aParent[i]; - pDesc = hashFind(p, parentRid); - if( pDesc==0 ) continue; - if( pDesc->mergeOut<0 ){ - int iTarget = (pRow->iRail + pDesc->iRail)/2; - pDesc->mergeOut = findFreeRail(p, pRow->idx, pDesc->idx, 0, iTarget); - pDesc->mergeUpto = pRow->idx; - mask = 1<mergeOut; - pDesc->railInUse |= mask; - for(pDesc=pRow->pNext; pDesc && pDesc->rid!=parentRid; - pDesc=pDesc->pNext){ - pDesc->railInUse |= mask; - } - } - pRow->mergeIn |= 1<mergeOut; + int iReuseIdx = -1; + int iReuseRail = -1; + int isCherrypick = 0; + for(i=1; inParent; i++){ + GraphRowId parentRid = pRow->aParent[i]; + if( i==pRow->nNonCherrypick ){ + /* Because full merges are laid out before cherrypicks, + ** it is ok to use a full-merge raise for a cherrypick. + ** See the graph on check-in 8ac66ef33b464d28 for example + ** iReuseIdx = -1; + ** iReuseRail = -1; */ + isCherrypick = 1; + } + pDesc = hashFind(p, parentRid); + if( pDesc==0 ){ + int iMrail = -1; + /* Merge from a node that is off-screen */ + if( iReuseIdx>=p->nRow+1 ){ + continue; /* Suppress multiple off-screen merges */ + } + for(j=0; jidx, p->pLast->idx, 0); + if( p->mxRail>=GR_MAX_RAIL ) return; + mergeRiserFrom[iMrail] = parentRid; + } + iReuseIdx = p->nRow+1; + iReuseRail = iMrail; + mask = BIT(iMrail); + if( i>=pRow->nNonCherrypick ){ + pRow->mergeIn[iMrail] = 2; + pRow->cherrypickDown |= mask; + }else{ + pRow->mergeIn[iMrail] = 1; + pRow->mergeDown |= mask; + } + for(pLoop=pRow->pNext; pLoop; pLoop=pLoop->pNext){ + pLoop->railInUse |= mask; + } + }else{ + /* The merge parent node does exist on this graph */ + if( iReuseIdx>pDesc->idx + && pDesc->nMergeChild==1 + ){ + /* Reuse an existing merge riser */ + pDesc->mergeOut = iReuseRail; + if( isCherrypick ){ + pDesc->cherrypickUpto = pDesc->idx; + }else{ + pDesc->hasNormalOutMerge = 1; + pDesc->mergeUpto = pDesc->idx; + } + }else{ + /* Create a new merge for an on-screen node */ + createMergeRiser(p, pDesc, pRow, isCherrypick); + if( p->mxRail>=GR_MAX_RAIL ) return; + if( iReuseIdx<0 + && pDesc->nMergeChild==1 + && (pDesc->iRail!=pDesc->mergeOut || pDesc->isLeaf) + ){ + iReuseIdx = pDesc->idx; + iReuseRail = pDesc->mergeOut; + } + } + } } } /* - ** Insert merge rails from primaries to duplicates. + ** Insert merge rails from primaries to duplicates. */ if( hasDup ){ + int dupRail; + int mxRail; + find_max_rail(p); + mxRail = p->mxRail; + dupRail = mxRail+1; + if( p->mxRail>=GR_MAX_RAIL ) return; for(pRow=p->pFirst; pRow; pRow=pRow->pNext){ if( !pRow->isDup ) continue; + pRow->iRail = dupRail; pDesc = hashFind(p, pRow->rid); assert( pDesc!=0 && pDesc!=pRow ); - if( pDesc->mergeOut<0 ){ - int iTarget = (pRow->iRail + pDesc->iRail)/2; - pDesc->mergeOut = findFreeRail(p, pRow->idx, pDesc->idx, 0, iTarget); - pDesc->mergeUpto = pRow->idx; - mask = 1<mergeOut; - pDesc->railInUse |= mask; - for(pLoop=pRow->pNext; pLoop && pLoop!=pDesc; pLoop=pLoop->pNext){ - pLoop->railInUse |= mask; - } - } - pRow->mergeIn |= 1<mergeOut; - } + createMergeRiser(p, pDesc, pRow, 0); + if( pDesc->mergeOut>mxRail ) mxRail = pDesc->mergeOut; + } + if( dupRail<=mxRail ){ + dupRail = mxRail+1; + for(pRow=p->pFirst; pRow; pRow=pRow->pNext){ + if( pRow->isDup ) pRow->iRail = dupRail; + } + } + if( mxRail>=GR_MAX_RAIL ) return; } /* ** Find the maximum rail number. */ - for(i=0; irailMap[i] = i; - p->mxRail = 0; - for(pRow=p->pFirst; pRow; pRow=pRow->pNext){ - if( pRow->iRail>p->mxRail ) p->mxRail = pRow->iRail; - if( pRow->mergeOut>p->mxRail ) p->mxRail = pRow->mergeOut; + find_max_rail(p); + + /* + ** Compute the rail mapping. + */ + aMap = p->aiRailMap; + for(i=0; i<=p->mxRail; i++) aMap[i] = i; + if( zLeftBranch && nTimewarp==0 ){ + char *zLeft = persistBranchName(p, zLeftBranch); + j = 0; + for(pRow=p->pFirst; pRow; pRow=pRow->pNext){ + if( pRow->zBranch==zLeft && aMap[pRow->iRail]>=j ){ + for(i=0; i<=p->mxRail; i++){ + if( aMap[i]>=j && aMap[i]<=pRow->iRail ) aMap[i]++; + } + aMap[pRow->iRail] = j++; + } + } + cgi_printf("\n"); } + + p->nErr = 0; } ADDED src/graph.js Index: src/graph.js ================================================================== --- /dev/null +++ src/graph.js @@ -0,0 +1,799 @@ +/* This module contains javascript needed to render timeline graphs in Fossil. +** +** There can be multiple graphs on a single webpage, but this script is only +** loaded once. +** +** Prior to sourcing this script, there should be a separate +** + }else if( renderAsSvg ){ + @ + }else{ + const char *zContentMime; + style_submenu_element("Hex", "%R/hexdump?name=%s", zUuid); + if( zLn==0 || atoi(zLn)==0 ){ + style_submenu_checkbox("ln", "Line Numbers", 0, 0); + } + blob_to_utf8_no_bom(&content, 0); + zContentMime = mimetype_from_content(&content); + if( zMime==0 ) zMime = zContentMime; + @

+ if( zContentMime==0 ){ + const char *z, *zFileName, *zExt; + z = blob_str(&content); + zFileName = db_text(0, + "SELECT name FROM mlink, filename" + " WHERE filename.fnid=mlink.fnid" + " AND mlink.fid=%d", + rid); + zExt = zFileName ? file_extension(zFileName) : 0; + if( zLn ){ + output_text_with_line_numbers(z, blob_size(&content), + zFileName, zLn, 1); + }else if( zExt && zExt[1] ){ + @
+          @ %h(z)
+          @ 
+ }else{ + @
+          @ %h(z)
+          @ 
+ } + }else if( strncmp(zMime, "image/", 6)==0 ){ + @

(file is %d(blob_size(&content)) bytes of image data)

+ @

+ style_submenu_element("Image", "%R/raw/%s?m=%s", zUuid, zMime); + }else if( strncmp(zMime, "audio/", 6)==0 ){ + @

(file is %d(blob_size(&content)) bytes of sound data)

+ @ + }else{ + @ (file is %d(blob_size(&content)) bytes of binary data) + } + @
+ } + } + style_finish_page(); +} /* ** WEBPAGE: tinfo ** URL: /tinfo?name=ARTIFACTID ** ** Show the details of a ticket change control artifact. */ void tinfo_page(void){ int rid; - Blob content; char *zDate; const char *zUuid; - char zTktName[20]; - Manifest m; - + char zTktName[HNAME_MAX+1]; + Manifest *pTktChng; + int modPending; + const char *zModAction; + char *zTktTitle; login_check_credentials(); - if( !g.okRdTkt ){ login_needed(); return; } + if( !g.perm.RdTkt ){ login_needed(g.anon.RdTkt); return; } rid = name_to_rid_www("name"); if( rid==0 ){ fossil_redirect_home(); } zUuid = db_text("", "SELECT uuid FROM blob WHERE rid=%d", rid); - if( g.okAdmin ){ - if( db_exists("SELECT 1 FROM shun WHERE uuid='%s'", zUuid) ){ - style_submenu_element("Unshun","Unshun", "%s/shun?uuid=%s&sub=1", - g.zTop, zUuid); + if( g.perm.Admin ){ + if( db_exists("SELECT 1 FROM shun WHERE uuid=%Q", zUuid) ){ + style_submenu_element("Unshun", "%R/shun?accept=%s&sub=1#accshun", zUuid); }else{ - style_submenu_element("Shun","Shun", "%s/shun?shun=%s#addshun", - g.zTop, zUuid); + style_submenu_element("Shun", "%R/shun?shun=%s#addshun", zUuid); + } + } + pTktChng = manifest_get(rid, CFTYPE_TICKET, 0); + if( pTktChng==0 ) fossil_redirect_home(); + zDate = db_text(0, "SELECT datetime(%.12f)", pTktChng->rDate); + sqlite3_snprintf(sizeof(zTktName), zTktName, "%s", pTktChng->zTicketUuid); + if( g.perm.ModTkt && (zModAction = P("modaction"))!=0 ){ + if( strcmp(zModAction,"delete")==0 ){ + moderation_disapprove(rid); + /* + ** Next, check if the ticket still exists; if not, we cannot + ** redirect to it. + */ + if( db_exists("SELECT 1 FROM ticket WHERE tkt_uuid GLOB '%q*'", + zTktName) ){ + cgi_redirectf("%R/tktview/%s", zTktName); + /*NOTREACHED*/ + }else{ + cgi_redirectf("%R/modreq"); + /*NOTREACHED*/ + } + } + if( strcmp(zModAction,"approve")==0 ){ + moderation_approve('t', rid); } } - content_get(rid, &content); - if( manifest_parse(&m, &content)==0 ){ - fossil_redirect_home(); - } - if( m.type!=CFTYPE_TICKET ){ - fossil_redirect_home(); - } + zTktTitle = db_table_has_column("repository", "ticket", "title" ) + ? db_text("(No title)", + "SELECT title FROM ticket WHERE tkt_uuid=%Q", zTktName) + : 0; + style_set_current_feature("tinfo"); style_header("Ticket Change Details"); - zDate = db_text(0, "SELECT datetime(%.12f)", m.rDate); - memcpy(zTktName, m.zTicketUuid, 10); - zTktName[10] = 0; - if( g.okHistory ){ - @

Changes to ticket %s(zTktName)

- @ - @

By %h(m.zUser) on %s(zDate). See also: - @ artifact content, and - @ ticket history - @

+ style_submenu_element("Raw", "%R/artifact/%s", zUuid); + style_submenu_element("History", "%R/tkthistory/%s", zTktName); + style_submenu_element("Page", "%R/tktview/%t", zTktName); + style_submenu_element("Timeline", "%R/tkttimeline/%t", zTktName); + if( P("plaintext") ){ + style_submenu_element("Formatted", "%R/info/%s", zUuid); }else{ - @

Changes to ticket %s(zTktName)

- @ - @

By %h(m.zUser) on %s(zDate). - @

+ style_submenu_element("Plaintext", "%R/info/%s?plaintext", zUuid); + } + + @
Overview
+ @

+ @ + @ + @ + @ "); + @
Artifact ID:%z(href("%R/artifact/%!S",zUuid))%s(zUuid) + if( g.perm.Setup ){ + @ (%d(rid)) + } + modPending = moderation_pending_www(rid); + @
Ticket:%z(href("%R/tktview/%s",zTktName))%s(zTktName) + if( zTktTitle ){ + @
%h(zTktTitle) } - @ - @
    + @
User & Date: + hyperlink_to_user(pTktChng->zUser, zDate, " on "); + hyperlink_to_date(zDate, "
free(zDate); - ticket_output_change_artifact(&m); - manifest_clear(&m); - style_footer(); + free(zTktTitle); + + if( g.perm.ModTkt && modPending ){ + @

Moderation
+ @
+ @
+ @
+ @
+ @ + @
+ @
+ } + + @
Changes
+ @

+ ticket_output_change_artifact(pTktChng, 0, 1); + manifest_destroy(pTktChng); + style_finish_page(); } /* ** WEBPAGE: info -** URL: info/ARTIFACTID +** URL: info/NAME ** -** The argument is a artifact ID which might be a baseline or a file or -** a ticket changes or a wiki edit or something else. +** The NAME argument is any valid artifact name: an artifact hash, +** a timestamp, a tag name, etc. ** -** Figure out what the artifact ID is and jump to it. +** Because NAME can match so many different things (commit artifacts, +** wiki pages, ticket comments, forum posts...) the format of the output +** page depends on the type of artifact that NAME matches. */ void info_page(void){ const char *zName; Blob uuid; int rid; - + int rc; + int nLen; + zName = P("name"); if( zName==0 ) fossil_redirect_home(); - if( validate16(zName, strlen(zName)) - && db_exists("SELECT 1 FROM ticket WHERE tkt_uuid GLOB '%q*'", zName) ){ - tktview_page(); - return; - } + nLen = strlen(zName); blob_set(&uuid, zName); - if( name_to_uuid(&uuid, 1) ){ - fossil_redirect_home(); + if( name_collisions(zName) ){ + cgi_set_parameter("src","info"); + ambiguous_page(); + return; + } + rc = name_to_uuid(&uuid, -1, "*"); + if( rc==1 ){ + if( validate16(zName, nLen) ){ + if( db_exists("SELECT 1 FROM ticket WHERE tkt_uuid GLOB '%q*'", zName) ){ + cgi_set_parameter_nocopy("tl","1",0); + tktview_page(); + return; + } + if( db_exists("SELECT 1 FROM tag" + " WHERE tagname GLOB 'event-%q*'", zName) ){ + event_page(); + return; + } + } + style_header("No Such Object"); + @

No such object: %h(zName)

+ if( nLen<4 ){ + @

Object name should be no less than 4 characters. Ten or more + @ characters are recommended.

+ } + style_finish_page(); + return; + }else if( rc==2 ){ + cgi_set_parameter("src","info"); + ambiguous_page(); + return; } zName = blob_str(&uuid); - rid = db_int(0, "SELECT rid FROM blob WHERE uuid='%s'", zName); + rid = db_int(0, "SELECT rid FROM blob WHERE uuid=%Q", zName); if( rid==0 ){ style_header("Broken Link"); @

No such object: %h(zName)

- style_footer(); + style_finish_page(); return; } if( db_exists("SELECT 1 FROM mlink WHERE mid=%d", rid) ){ ci_page(); }else @@ -1161,193 +2795,331 @@ winfo_page(); }else if( db_exists("SELECT 1 FROM tagxref JOIN tag USING(tagid)" " WHERE rid=%d AND tagname LIKE 'tkt-%%'", rid) ){ tinfo_page(); + }else + if( db_table_exists("repository","forumpost") + && db_exists("SELECT 1 FROM forumpost WHERE fpid=%d", rid) + ){ + forumthread_page(); }else if( db_exists("SELECT 1 FROM plink WHERE cid=%d", rid) ){ ci_page(); }else if( db_exists("SELECT 1 FROM plink WHERE pid=%d", rid) ){ ci_page(); + }else + if( db_exists("SELECT 1 FROM attachment WHERE attachid=%d", rid) ){ + ainfo_page(); }else { artifact_page(); } } + +/* +** Do a comment comparison. +** +** + Leading and trailing whitespace are ignored. +** + \r\n characters compare equal to \n +** +** Return true if equal and false if not equal. +*/ +static int comment_compare(const char *zA, const char *zB){ + if( zA==0 ) zA = ""; + if( zB==0 ) zB = ""; + while( fossil_isspace(zA[0]) ) zA++; + while( fossil_isspace(zB[0]) ) zB++; + while( zA[0] && zB[0] ){ + if( zA[0]==zB[0] ){ zA++; zB++; continue; } + if( zA[0]=='\r' && zA[1]=='\n' && zB[0]=='\n' ){ + zA += 2; + zB++; + continue; + } + if( zB[0]=='\r' && zB[1]=='\n' && zA[0]=='\n' ){ + zB += 2; + zA++; + continue; + } + return 0; + } + while( fossil_isspace(zB[0]) ) zB++; + while( fossil_isspace(zA[0]) ) zA++; + return zA[0]==0 && zB[0]==0; +} + +/* +** The following methods operate on the newtags temporary table +** that is used to collect various changes to be added to a control +** artifact for a check-in edit. +*/ +static void init_newtags(void){ + db_multi_exec("CREATE TEMP TABLE newtags(tag UNIQUE, prefix, value)"); +} + +static void change_special( + const char *zName, /* Name of the special tag */ + const char *zOp, /* Operation prefix (e.g. +,-,*) */ + const char *zValue /* Value of the tag */ +){ + db_multi_exec("REPLACE INTO newtags VALUES(%Q,'%q',%Q)", zName, zOp, zValue); +} + +static void change_sym_tag(const char *zTag, const char *zOp){ + db_multi_exec("REPLACE INTO newtags VALUES('sym-%q',%Q,NULL)", zTag, zOp); +} + +static void cancel_special(const char *zTag){ + change_special(zTag,"-",0); +} + +static void add_color(const char *zNewColor, int fPropagateColor){ + change_special("bgcolor",fPropagateColor ? "*" : "+", zNewColor); +} + +static void cancel_color(void){ + change_special("bgcolor","-",0); +} + +static void add_comment(const char *zNewComment){ + change_special("comment","+",zNewComment); +} + +static void add_date(const char *zNewDate){ + change_special("date","+",zNewDate); +} + +static void add_user(const char *zNewUser){ + change_special("user","+",zNewUser); +} + +static void add_tag(const char *zNewTag){ + change_sym_tag(zNewTag,"+"); +} + +static void cancel_tag(int rid, const char *zCancelTag){ + if( db_exists("SELECT 1 FROM tagxref, tag" + " WHERE tagxref.rid=%d AND tagtype>0" + " AND tagxref.tagid=tag.tagid AND tagname='sym-%q'", + rid, zCancelTag) + ) change_sym_tag(zCancelTag,"-"); +} + +static void hide_branch(void){ + change_special("hidden","*",0); +} + +static void close_leaf(int rid){ + change_special("closed",is_a_leaf(rid)?"+":"*",0); +} + +static void change_branch(int rid, const char *zNewBranch){ + db_multi_exec( + "REPLACE INTO newtags " + " SELECT tagname, '-', NULL FROM tagxref, tag" + " WHERE tagxref.rid=%d AND tagtype==2" + " AND tagname GLOB 'sym-*'" + " AND tag.tagid=tagxref.tagid", + rid + ); + change_special("branch","*",zNewBranch); + change_sym_tag(zNewBranch,"*"); +} + +/* +** The apply_newtags method is called after all newtags have been added +** and the control artifact is completed and then written to the DB. +*/ +static void apply_newtags( + Blob *ctrl, + int rid, + const char *zUuid, + const char *zUserOvrd, /* The user name on the control artifact */ + int fDryRun /* Print control artifact, but make no changes */ +){ + Stmt q; + int nChng = 0; + + db_prepare(&q, "SELECT tag, prefix, value FROM newtags" + " ORDER BY prefix || tag"); + while( db_step(&q)==SQLITE_ROW ){ + const char *zTag = db_column_text(&q, 0); + const char *zPrefix = db_column_text(&q, 1); + const char *zValue = db_column_text(&q, 2); + nChng++; + if( zValue ){ + blob_appendf(ctrl, "T %s%F %s %F\n", zPrefix, zTag, zUuid, zValue); + }else{ + blob_appendf(ctrl, "T %s%F %s\n", zPrefix, zTag, zUuid); + } + } + db_finalize(&q); + if( nChng>0 ){ + int nrid; + Blob cksum; + if( zUserOvrd && zUserOvrd[0] ){ + blob_appendf(ctrl, "U %F\n", zUserOvrd); + }else{ + blob_appendf(ctrl, "U %F\n", login_name()); + } + md5sum_blob(ctrl, &cksum); + blob_appendf(ctrl, "Z %b\n", &cksum); + if( fDryRun ){ + assert( g.isHTTP==0 ); /* Only print control artifact in console mode. */ + fossil_print("%s", blob_str(ctrl)); + blob_reset(ctrl); + }else{ + db_begin_transaction(); + g.markPrivate = content_is_private(rid); + nrid = content_put(ctrl); + manifest_crosslink(nrid, ctrl, MC_PERMIT_HOOKS); + db_end_transaction(0); + } + assert( blob_is_reset(ctrl) ); + } +} + +/* +** This method checks that the date can be parsed. +** Returns 1 if datetime() can validate, 0 otherwise. +*/ +int is_datetime(const char* zDate){ + return db_int(0, "SELECT datetime(%Q) NOT NULL", zDate); +} /* ** WEBPAGE: ci_edit -** URL: ci_edit?r=RID&c=NEWCOMMENT&u=NEWUSER +** +** Edit a check-in. (Check-ins are immutable and do not really change. +** This page really creates supplemental tags that affect the display +** of the check-in.) +** +** Query parameters: +** +** rid=INTEGER Record ID of the check-in to edit (REQUIRED) +** +** POST parameters after pressing "Preview", "Cancel", or "Apply": ** -** Present a dialog for updating properties of a baseline: +** c=TEXT New check-in comment +** u=TEXT New user name +** newclr Apply a background color +** clr=TEXT New background color (only if newclr) +** pclr Propagate new background color (only if newclr) +** dt=TEXT New check-in date/time (ISO8610 format) +** newtag Add a new tag to the check-in +** tagname=TEXT Name of the new tag to be added (only if newtag) +** newbr Put the check-in on a new branch +** brname=TEXT Name of the new branch (only if newbr) +** close Close this check-in +** hide Hide this check-in +** cNNN Cancel tag with tagid=NNN ** -** * The check-in user -** * The check-in comment -** * The background color. +** cancel Cancel the edit. Return to the check-in view +** preview Show a preview of the edited check-in comment +** apply Apply changes */ void ci_edit_page(void){ int rid; const char *zComment; /* Current comment on the check-in */ const char *zNewComment; /* Revised check-in comment */ const char *zUser; /* Current user for the check-in */ const char *zNewUser; /* Revised user */ const char *zDate; /* Current date of the check-in */ const char *zNewDate; /* Revised check-in date */ - const char *zColor; - const char *zNewColor; - const char *zNewTagFlag; - const char *zNewTag; - const char *zNewBrFlag; - const char *zNewBranch; - const char *zCloseFlag; - int fPropagateColor; + const char *zNewColorFlag; /* "checked" if "Change color" is checked */ + const char *zColor; /* Current background color */ + const char *zNewColor; /* Revised background color */ + const char *zNewTagFlag; /* "checked" if "Add tag" is checked */ + const char *zNewTag; /* Name of the new tag */ + const char *zNewBrFlag; /* "checked" if "New branch" is checked */ + const char *zNewBranch; /* Name of the new branch */ + const char *zCloseFlag; /* "checked" if "Close" is checked */ + const char *zHideFlag; /* "checked" if "Hide" is checked */ + int fPropagateColor; /* True if color propagates before edit */ + int fNewPropagateColor; /* True if color propagates after edit */ + int fHasHidden = 0; /* True if hidden tag already set */ + int fHasClosed = 0; /* True if closed tag already set */ + const char *zChngTime = 0; /* Value of chngtime= query param, if any */ char *zUuid; Blob comment; + char *zBranchName = 0; Stmt q; - static const struct SampleColors { - const char *zCName; - const char *zColor; - } aColor[] = { - { "(none)", "" }, - { "#f2dcdc", "#f2dcdc" }, - { "#f0ffc0", "#f0ffc0" }, - { "#bde5d6", "#bde5d6" }, - { "#c0ffc0", "#c0ffc0" }, - { "#c0fff0", "#c0fff0" }, - { "#c0f0ff", "#c0f0ff" }, - { "#d0c0ff", "#d0c0ff" }, - { "#ffc0ff", "#ffc0ff" }, - { "#ffc0d0", "#ffc0d0" }, - { "#fff0c0", "#fff0c0" }, - { "#c0c0c0", "#c0c0c0" }, - }; - int nColor = sizeof(aColor)/sizeof(aColor[0]); - int i; - + login_check_credentials(); - if( !g.okWrite ){ login_needed(); return; } - rid = name_to_rid(P("r")); + if( !g.perm.Write ){ login_needed(g.anon.Write); return; } + rid = name_to_typed_rid(P("r"), "ci"); zUuid = db_text(0, "SELECT uuid FROM blob WHERE rid=%d", rid); zComment = db_text(0, "SELECT coalesce(ecomment,comment)" " FROM event WHERE objid=%d", rid); if( zComment==0 ) fossil_redirect_home(); if( P("cancel") ){ - cgi_redirectf("ci?name=%s", zUuid); + cgi_redirectf("%R/ci/%S", zUuid); } + if( g.perm.Setup ) zChngTime = P("chngtime"); zNewComment = PD("c",zComment); zUser = db_text(0, "SELECT coalesce(euser,user)" " FROM event WHERE objid=%d", rid); if( zUser==0 ) fossil_redirect_home(); - zNewUser = PD("u",zUser); + zNewUser = PDT("u",zUser); zDate = db_text(0, "SELECT datetime(mtime)" " FROM event WHERE objid=%d", rid); if( zDate==0 ) fossil_redirect_home(); - zNewDate = PD("dt",zDate); + zNewDate = PDT("dt",zDate); zColor = db_text("", "SELECT bgcolor" " FROM event WHERE objid=%d", rid); - zNewColor = PD("clr",zColor); - fPropagateColor = P("pclr")!=0; + zNewColor = PDT("clr",zColor); + fPropagateColor = db_int(0, "SELECT tagtype FROM tagxref" + " WHERE rid=%d AND tagid=%d", + rid, TAG_BGCOLOR)==2; + fNewPropagateColor = P("clr")!=0 ? P("pclr")!=0 : fPropagateColor; + zNewColorFlag = P("newclr") ? " checked" : ""; zNewTagFlag = P("newtag") ? " checked" : ""; - zNewTag = PD("tagname",""); + zNewTag = PDT("tagname",""); zNewBrFlag = P("newbr") ? " checked" : ""; - zNewBranch = PD("brname",""); + zNewBranch = PDT("brname",""); zCloseFlag = P("close") ? " checked" : ""; - if( P("apply") ){ + zHideFlag = P("hide") ? " checked" : ""; + if( P("apply") && cgi_csrf_safe(1) ){ Blob ctrl; - char *zDate; - int nChng = 0; + char *zNow; login_verify_csrf_secret(); blob_zero(&ctrl); - zDate = db_text(0, "SELECT datetime('now')"); - zDate[10] = 'T'; - blob_appendf(&ctrl, "D %s\n", zDate); - db_multi_exec("CREATE TEMP TABLE newtags(tag UNIQUE, prefix, value)"); - if( zNewColor[0] && strcmp(zColor,zNewColor)!=0 ){ - char *zPrefix = "+"; - if( fPropagateColor ){ - zPrefix = "*"; - } - db_multi_exec("REPLACE INTO newtags VALUES('bgcolor',%Q,%Q)", - zPrefix, zNewColor); - } - if( zNewColor[0]==0 && zColor[0]!=0 ){ - db_multi_exec("REPLACE INTO newtags VALUES('bgcolor','-',NULL)"); - } - if( strcmp(zComment,zNewComment)!=0 ){ - db_multi_exec("REPLACE INTO newtags VALUES('comment','+',%Q)", - zNewComment); - } - if( strcmp(zDate,zNewDate)!=0 ){ - db_multi_exec("REPLACE INTO newtags VALUES('date','+',%Q)", - zNewDate); - } - if( strcmp(zUser,zNewUser)!=0 ){ - db_multi_exec("REPLACE INTO newtags VALUES('user','+',%Q)", zNewUser); - } + zNow = date_in_standard_format(zChngTime ? zChngTime : "now"); + blob_appendf(&ctrl, "D %s\n", zNow); + init_newtags(); + if( zNewColorFlag[0] + && zNewColor[0] + && (fPropagateColor!=fNewPropagateColor + || fossil_strcmp(zColor,zNewColor)!=0) + ){ + add_color(zNewColor,fNewPropagateColor); + } + if( comment_compare(zComment,zNewComment)==0 ) add_comment(zNewComment); + if( fossil_strcmp(zDate,zNewDate)!=0 ) add_date(zNewDate); + if( fossil_strcmp(zUser,zNewUser)!=0 ) add_user(zNewUser); db_prepare(&q, "SELECT tag.tagid, tagname FROM tagxref, tag" " WHERE tagxref.rid=%d AND tagtype>0 AND tagxref.tagid=tag.tagid", rid ); while( db_step(&q)==SQLITE_ROW ){ int tagid = db_column_int(&q, 0); const char *zTag = db_column_text(&q, 1); char zLabel[30]; - sprintf(zLabel, "c%d", tagid); - if( P(zLabel) ){ - db_multi_exec("REPLACE INTO newtags VALUES(%Q,'-',NULL)", zTag); - } - } - db_finalize(&q); - if( zCloseFlag[0] ){ - db_multi_exec("REPLACE INTO newtags VALUES('closed','+',NULL)"); - } - if( zNewTagFlag[0] ){ - db_multi_exec("REPLACE INTO newtags VALUES('sym-%q','+',NULL)", zNewTag); - } - if( zNewBrFlag[0] ){ - db_multi_exec( - "REPLACE INTO newtags " - " SELECT tagname, '-', NULL FROM tagxref, tag" - " WHERE tagxref.rid=%d AND tagtype==2" - " AND tagname GLOB 'sym-*'" - " AND tag.tagid=tagxref.tagid", - rid - ); - db_multi_exec("REPLACE INTO newtags VALUES('branch','*',%Q)", zNewBranch); - db_multi_exec("REPLACE INTO newtags VALUES('sym-%q','*',NULL)", - zNewBranch); - } - db_prepare(&q, "SELECT tag, prefix, value FROM newtags" - " ORDER BY prefix || tag"); - while( db_step(&q)==SQLITE_ROW ){ - const char *zTag = db_column_text(&q, 0); - const char *zPrefix = db_column_text(&q, 1); - const char *zValue = db_column_text(&q, 2); - nChng++; - if( zValue ){ - blob_appendf(&ctrl, "T %s%F %s %F\n", zPrefix, zTag, zUuid, zValue); - }else{ - blob_appendf(&ctrl, "T %s%F %s\n", zPrefix, zTag, zUuid); - } - } - db_finalize(&q); - if( nChng>0 ){ - int nrid; - Blob cksum; - blob_appendf(&ctrl, "U %F\n", g.zLogin); - md5sum_blob(&ctrl, &cksum); - blob_appendf(&ctrl, "Z %b\n", &cksum); - db_begin_transaction(); - g.markPrivate = content_is_private(rid); - nrid = content_put(&ctrl, 0, 0); - manifest_crosslink(nrid, &ctrl); - db_end_transaction(0); - } - cgi_redirectf("ci?name=%s", zUuid); + sqlite3_snprintf(sizeof(zLabel), zLabel, "c%d", tagid); + if( P(zLabel) ) cancel_special(zTag); + } + db_finalize(&q); + if( zHideFlag[0] ) hide_branch(); + if( zCloseFlag[0] ) close_leaf(rid); + if( zNewTagFlag[0] && zNewTag[0] ) add_tag(zNewTag); + if( zNewBrFlag[0] && zNewBranch[0] ) change_branch(rid,zNewBranch); + apply_newtags(&ctrl, rid, zUuid, 0, 0); + cgi_redirectf("%R/ci/%S", zUuid); } blob_zero(&comment); blob_append(&comment, zNewComment, -1); zUuid[10] = 0; style_header("Edit Check-in [%s]", zUuid); @@ -1355,16 +3127,18 @@ Blob suffix; int nTag = 0; @ Preview: @
@ - if( zNewColor && zNewColor[0] ){ - @
+ if( zNewColorFlag[0] && zNewColor && zNewColor[0] ){ + @
+ }else if( zColor[0] ){ + @
}else{ @
} - wiki_convert(&comment, 0, WIKI_INLINE); + @ %!W(blob_str(&comment)) blob_zero(&suffix); blob_appendf(&suffix, "(user: %h", zNewUser); db_prepare(&q, "SELECT substr(tagname,5) FROM tagxref, tag" " WHERE tagname GLOB 'sym-*' AND tagxref.rid=%d" " AND tagtype>1 AND tag.tagid=tagxref.tagid", @@ -1380,123 +3154,422 @@ } db_finalize(&q); blob_appendf(&suffix, ")"); @ %s(blob_str(&suffix)) @
+ if( zChngTime ){ + @

The timestamp on the tag used to make the changes above + @ will be overridden as: %s(date_in_standard_format(zChngTime))

+ } @
- @
+ @
blob_reset(&suffix); } @

Make changes to attributes of check-in - @ [%s(zUuid)]:

- @
+ @ [%z(href("%R/ci/%!S",zUuid))%s(zUuid)]:

+ form_begin(0, "%R/ci_edit"); login_insert_csrf_secret(); - @ + @
@ - @ + @ @ - @ + @ @ - @ - @ - - @ - @ - - @ - @ + @ + + if( zChngTime ){ + @ + @ + } + + @ + @ + + @ + @ - @ - @ - - if( is_a_leaf(rid) - && !db_exists("SELECT 1 FROM tagxref " - " WHERE tagid=%d AND rid=%d AND tagtype>0", - TAG_CLOSED, rid) - ){ - @ - @ - } + if( !zBranchName ){ + zBranchName = db_get("main-branch", 0); + } + if( !zNewBranch || !zNewBranch[0]){ + zNewBranch = zBranchName; + } + @ + @ + if( !fHasHidden ){ + @ + @ + } + if( !fHasClosed ){ + if( is_a_leaf(rid) ){ + @ + @ + }else if( zBranchName ){ + @ + @ + } + } + if( zBranchName ) fossil_free(zBranchName); @ @
User:
User: - @ + @ @
Comment:
Comment: @ @
Check-in Time: - @ - @
Background Color: - @ - @ - @ - for(i=0; i - }else{ - @ - if( (i%6)==5 && i+1 - } - } - @ - @
- if( fPropagateColor ){ - @ - }else{ - @ - } - @ Propagate color to descendants
- } - if( strcmp(zNewColor, aColor[i].zColor)==0 ){ - @ - }else{ - @ - } - @ %h(aColor[i].zCName)
- @
Tags: - @ - @ Add the following new tag name to this check-in: - @ - db_prepare(&q, - "SELECT tag.tagid, tagname FROM tagxref, tag" - " WHERE tagxref.rid=%d AND tagtype>0 AND tagxref.tagid=tag.tagid" - " ORDER BY CASE WHEN tagname GLOB 'sym-*' THEN substr(tagname,5)" - " ELSE tagname END", + @
Check-in Time: + @ + @
Timestamp of this change: + @ + @
Background Color: + @
+ @
+ @
Tags: + @ + @ + zBranchName = db_text(0, "SELECT value FROM tagxref, tag" + " WHERE tagxref.rid=%d AND tagtype>0 AND tagxref.tagid=tag.tagid" + " AND tagxref.tagid=%d", rid, TAG_BRANCH); + db_prepare(&q, + "SELECT tag.tagid, tagname, tagxref.value FROM tagxref, tag" + " WHERE tagxref.rid=%d AND tagtype>0 AND tagxref.tagid=tag.tagid" + " ORDER BY CASE WHEN tagname GLOB 'sym-*' THEN substr(tagname,5)" + " ELSE tagname END /*sort*/", rid ); while( db_step(&q)==SQLITE_ROW ){ int tagid = db_column_int(&q, 0); const char *zTagName = db_column_text(&q, 1); + int isSpecialTag = fossil_strncmp(zTagName, "sym-", 4)!=0; char zLabel[30]; - sprintf(zLabel, "c%d", tagid); - if( P(zLabel) ){ - @
- }else{ - @
- } - if( strncmp(zTagName, "sym-", 4)==0 ){ - @ Cancel tag %h(&zTagName[4]) - }else{ - @ Cancel special tag %h(zTagName) + + if( tagid == TAG_CLOSED ){ + fHasClosed = 1; + }else if( (tagid == TAG_COMMENT) || (tagid == TAG_BRANCH) ){ + continue; + }else if( tagid==TAG_HIDDEN ){ + fHasHidden = 1; + }else if( !isSpecialTag && zTagName && + fossil_strcmp(&zTagName[4], zBranchName)==0){ + continue; + } + sqlite3_snprintf(sizeof(zLabel), zLabel, "c%d", tagid); + @
+ }else{ + @ Cancel tag %h(&zTagName[4]) } } db_finalize(&q); @
Branching: - @ - @ Make this check-in the start of a new branch named: - @ - @
Leaf Closure: - @ - @ Mark this leaf as "closed" so that it no longer appears on the - @ "leaves" page and is no longer labeled as a "Leaf". - @
Branching: + @ + @
Branch Hiding: + @ + @
Leaf Closure: + @ + @
Branch Closure: + @ + @
- @ - @ - @ + @ + @ + if( P("preview") ){ + @ + } @
- @ - style_footer(); + @
+ builtin_request_js("ci_edit.js"); + style_finish_page(); +} + +/* +** Prepare an ammended commit comment. Let the user modify it using the +** editor specified in the global_config table or either +** the VISUAL or EDITOR environment variable. +** +** Store the final commit comment in pComment. pComment is assumed +** to be uninitialized - any prior content is overwritten. +** +** Use zInit to initialize the check-in comment so that the user does +** not have to retype. +*/ +static void prepare_amend_comment( + Blob *pComment, + const char *zInit, + const char *zUuid +){ + Blob prompt; +#if defined(_WIN32) || defined(__CYGWIN__) + int bomSize; + const unsigned char *bom = get_utf8_bom(&bomSize); + blob_init(&prompt, (const char *) bom, bomSize); + if( zInit && zInit[0]){ + blob_append(&prompt, zInit, -1); + } +#else + blob_init(&prompt, zInit, -1); +#endif + blob_append(&prompt, "\n# Enter a new comment for check-in ", -1); + if( zUuid && zUuid[0] ){ + blob_append(&prompt, zUuid, -1); + } + blob_append(&prompt, ".\n# Lines beginning with a # are ignored.\n", -1); + prompt_for_user_comment(pComment, &prompt); + blob_reset(&prompt); +} + +#define AMEND_USAGE_STMT "HASH OPTION ?OPTION ...?" +/* +** COMMAND: amend +** +** Usage: %fossil amend HASH OPTION ?OPTION ...? +** +** Amend the tags on check-in HASH to change how it displays in the timeline. +** +** Options: +** +** --author USER Make USER the author for check-in +** -m|--comment COMMENT Make COMMENT the check-in comment +** -M|--message-file FILE Read the amended comment from FILE +** -e|--edit-comment Launch editor to revise comment +** --date DATETIME Make DATETIME the check-in time +** --bgcolor COLOR Apply COLOR to this check-in +** --branchcolor COLOR Apply and propagate COLOR to the branch +** --tag TAG Add new TAG to this check-in +** --cancel TAG Cancel TAG from this check-in +** --branch NAME Make this check-in the start of branch NAME +** --hide Hide branch starting from this check-in +** --close Mark this "leaf" as closed +** -n|--dry-run Print control artifact, but make no changes +** --date-override DATETIME Set the change time on the control artifact +** --user-override USER Set the user name on the control artifact +** +** DATETIME may be "now" or "YYYY-MM-DDTHH:MM:SS.SSS". If in +** year-month-day form, it may be truncated, the "T" may be replaced by +** a space, and it may also name a timezone offset from UTC as "-HH:MM" +** (westward) or "+HH:MM" (eastward). Either no timezone suffix or "Z" +** means UTC. +*/ +void ci_amend_cmd(void){ + int rid; + const char *zComment; /* Current comment on the check-in */ + const char *zNewComment; /* Revised check-in comment */ + const char *zComFile; /* Filename from which to read comment */ + const char *zUser; /* Current user for the check-in */ + const char *zNewUser; /* Revised user */ + const char *zDate; /* Current date of the check-in */ + const char *zNewDate; /* Revised check-in date */ + const char *zColor; + const char *zNewColor; + const char *zNewBrColor; + const char *zNewBranch; + const char **pzNewTags = 0; + const char **pzCancelTags = 0; + int fClose; /* True if leaf should be closed */ + int fHide; /* True if branch should be hidden */ + int fPropagateColor; /* True if color propagates before amend */ + int fNewPropagateColor = 0; /* True if color propagates after amend */ + int fHasHidden = 0; /* True if hidden tag already set */ + int fHasClosed = 0; /* True if closed tag already set */ + int fEditComment; /* True if editor to be used for comment */ + int fDryRun; /* Print control artifact, make no changes */ + const char *zChngTime; /* The change time on the control artifact */ + const char *zUserOvrd; /* The user name on the control artifact */ + const char *zUuid; + Blob ctrl; + Blob comment; + char *zNow; + int nTags, nCancels; + int i; + Stmt q; + + if( g.argc==3 ) usage(AMEND_USAGE_STMT); + fEditComment = find_option("edit-comment","e",0)!=0; + zNewComment = find_option("comment","m",1); + zComFile = find_option("message-file","M",1); + zNewBranch = find_option("branch",0,1); + zNewColor = find_option("bgcolor",0,1); + zNewBrColor = find_option("branchcolor",0,1); + if( zNewBrColor ){ + zNewColor = zNewBrColor; + fNewPropagateColor = 1; + } + zNewDate = find_option("date",0,1); + zNewUser = find_option("author",0,1); + pzNewTags = find_repeatable_option("tag",0,&nTags); + pzCancelTags = find_repeatable_option("cancel",0,&nCancels); + fClose = find_option("close",0,0)!=0; + fHide = find_option("hide",0,0)!=0; + fDryRun = find_option("dry-run","n",0)!=0; + if( fDryRun==0 ) fDryRun = find_option("dryrun","n",0)!=0; + zChngTime = find_option("date-override",0,1); + if( zChngTime==0 ) zChngTime = find_option("chngtime",0,1); + zUserOvrd = find_option("user-override",0,1); + db_find_and_open_repository(0,0); + user_select(); + verify_all_options(); + if( g.argc<3 || g.argc>=4 ) usage(AMEND_USAGE_STMT); + rid = name_to_typed_rid(g.argv[2], "ci"); + if( rid==0 && !is_a_version(rid) ) fossil_fatal("no such check-in"); + zUuid = db_text(0, "SELECT uuid FROM blob WHERE rid=%d", rid); + if( zUuid==0 ) fossil_fatal("Unable to find artifact hash"); + zComment = db_text(0, "SELECT coalesce(ecomment,comment)" + " FROM event WHERE objid=%d", rid); + zUser = db_text(0, "SELECT coalesce(euser,user)" + " FROM event WHERE objid=%d", rid); + zDate = db_text(0, "SELECT datetime(mtime)" + " FROM event WHERE objid=%d", rid); + zColor = db_text("", "SELECT bgcolor" + " FROM event WHERE objid=%d", rid); + fPropagateColor = db_int(0, "SELECT tagtype FROM tagxref" + " WHERE rid=%d AND tagid=%d", + rid, TAG_BGCOLOR)==2; + fNewPropagateColor = zNewColor && zNewColor[0] + ? fNewPropagateColor : fPropagateColor; + db_prepare(&q, + "SELECT tag.tagid FROM tagxref, tag" + " WHERE tagxref.rid=%d AND tagtype>0 AND tagxref.tagid=tag.tagid", + rid + ); + while( db_step(&q)==SQLITE_ROW ){ + int tagid = db_column_int(&q, 0); + + if( tagid == TAG_CLOSED ){ + fHasClosed = 1; + }else if( tagid==TAG_HIDDEN ){ + fHasHidden = 1; + }else{ + continue; + } + } + db_finalize(&q); + blob_zero(&ctrl); + zNow = date_in_standard_format(zChngTime && zChngTime[0] ? zChngTime : "now"); + blob_appendf(&ctrl, "D %s\n", zNow); + init_newtags(); + if( zNewColor && zNewColor[0] + && (fPropagateColor!=fNewPropagateColor + || fossil_strcmp(zColor,zNewColor)!=0) + ){ + add_color( + mprintf("%s%s", (zNewColor[0]!='#' && + validate16(zNewColor,strlen(zNewColor)) && + (strlen(zNewColor)==6 || strlen(zNewColor)==3)) ? "#" : "", + zNewColor + ), + fNewPropagateColor + ); + } + if( (zNewColor!=0 && zNewColor[0]==0) && (zColor && zColor[0] ) ){ + cancel_color(); + } + if( fEditComment ){ + prepare_amend_comment(&comment, zComment, zUuid); + zNewComment = blob_str(&comment); + }else if( zComFile ){ + blob_zero(&comment); + blob_read_from_file(&comment, zComFile, ExtFILE); + blob_to_utf8_no_bom(&comment, 1); + zNewComment = blob_str(&comment); + } + if( zNewComment && zNewComment[0] + && comment_compare(zComment,zNewComment)==0 ) add_comment(zNewComment); + if( zNewDate && zNewDate[0] && fossil_strcmp(zDate,zNewDate)!=0 ){ + if( is_datetime(zNewDate) ){ + add_date(zNewDate); + }else{ + fossil_fatal("Unsupported date format, use YYYY-MM-DD HH:MM:SS"); + } + } + if( zNewUser && zNewUser[0] && fossil_strcmp(zUser,zNewUser)!=0 ){ + add_user(zNewUser); + } + if( pzNewTags!=0 ){ + for(i=0; i %s\n", + db_column_text(&q,0), + db_column_text(&q,1), + db_column_text(&q,2), + db_column_text(&q,3)); + } + db_finalize(&q); } ADDED src/interwiki.c Index: src/interwiki.c ================================================================== --- /dev/null +++ src/interwiki.c @@ -0,0 +1,428 @@ +/* +** Copyright (c) 2020 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 subroutines used for recognizing, configuring, and +** handling interwiki hyperlinks. +*/ +#include "config.h" +#include "interwiki.h" + + +/* +** If zTarget is an interwiki link, return a pointer to a URL for that +** link target in memory obtained from fossil_malloc(). If zTarget is +** not a valid interwiki link, return NULL. +** +** An interwiki link target is of the form: +** +** Code:PageName +** +** "Code" is a brief code that describes the intended target wiki. +** The code must be ASCII alpha-numeric. No symbols or non-ascii +** characters are allows. Case is ignored for the code. +** Codes are assigned by "intermap:*" entries in the CONFIG table. +** The link is only valid if there exists an entry in the CONFIG table +** that matches "intermap:Code". +** +** Each value of each intermap:Code entry in the CONFIG table is a JSON +** object with the following fields: +** +** { +** "base": Base URL for the remote site. +** "hash": Append this to "base" for Hash targets. +** "wiki": Append this to "base" for Wiki targets. +** } +** +** If the remote wiki is Fossil, then the correct value for "hash" +** is "/info/" and the correct value for "wiki" is "/wiki?name=". +** If (for example) Wikipedia is the remote, then "hash" should be +** omitted and the correct value for "wiki" is "/wiki/". +** +** PageName is link name of the target wiki. Several different forms +** of PageName are recognized. +** +** Path If PageName is empty or begins with a "/" character, then +** it is a pathname that is appended to "base". +** +** Hash If PageName is a hexadecimal string of 4 or more +** characters, then PageName is appended to "hash" which +** is then appended to "base". +** +** Wiki If PageName does not start with "/" and it is +** not a hexadecimal string of 4 or more characters, then +** PageName is appended to "wiki" and that combination is +** appended to "base". +** +** See https://en.wikipedia.org/wiki/Interwiki_links for further information +** on interwiki links. +*/ +char *interwiki_url(const char *zTarget){ + int nCode; + int i; + const char *zPage; + int nPage; + char *zUrl = 0; + char *zName; + static Stmt q; + for(i=0; fossil_isalnum(zTarget[i]); i++){} + if( zTarget[i]!=':' ) return 0; + nCode = i; + if( nCode==4 && strncmp(zTarget,"wiki",4)==0 ) return 0; + zPage = zTarget + nCode + 1; + nPage = (int)strlen(zPage); + db_static_prepare(&q, + "SELECT json_extract(value,'$.base')," + " json_extract(value,'$.hash')," + " json_extract(value,'$.wiki')" + " FROM config WHERE name=lower($name)" + ); + zName = mprintf("interwiki:%.*s", nCode, zTarget); + db_bind_text(&q, "$name", zName); + while( db_step(&q)==SQLITE_ROW ){ + const char *zBase = db_column_text(&q,0); + if( zBase==0 || zBase[0]==0 ) break; + if( nPage==0 || zPage[0]=='/' ){ + /* Path */ + zUrl = mprintf("%s%s", zBase, zPage); + }else if( nPage>=4 && validate16(zPage,nPage) ){ + /* Hash */ + const char *zHash = db_column_text(&q,1); + if( zHash && zHash[0] ){ + zUrl = mprintf("%s%s%s", zBase, zHash, zPage); + } + }else{ + /* Wiki */ + const char *zWiki = db_column_text(&q,2); + if( zWiki && zWiki[0] ){ + zUrl = mprintf("%s%s%s", zBase, zWiki, zPage); + } + } + break; + } + db_reset(&q); + free(zName); + return zUrl; +} + +/* +** If hyperlink target zTarget begins with an interwiki tag that ought +** to be excluded from display, then return the number of characters in +** that tag. +** +** Path interwiki targets always return zero. In other words, links +** of the form: +** +** remote:/path/to/file.txt +** +** Do not have the interwiki tag removed. But Hash and Wiki links are +** transformed: +** +** src:39cb0a323f2f3fb6 -> 39cb0a323f2f3fb6 +** fossil:To Do List -> To Do List +*/ +int interwiki_removable_prefix(const char *zTarget){ + int i; + for(i=0; fossil_isalnum(zTarget[i]); i++){} + if( zTarget[i]!=':' ) return 0; + i++; + if( zTarget[i]==0 || zTarget[i]=='/' ) return 0; + return i; +} + +/* +** Verify that a name is a valid interwiki "Code". Rules: +** +** * ascii +** * alphanumeric +*/ +static int interwiki_valid_name(const char *zName){ + int i; + for(i=0; zName[i]; i++){ + if( !fossil_isalnum(zName[i]) ) return 0; + } + return 1; +} + +/* +** COMMAND: interwiki* +** +** Usage: %fossil interwiki COMMAND ... +** +** Manage the "intermap" that defines the mapping from interwiki tags +** to complete URLs for interwiki links. +** +** > fossil interwiki delete TAG ... +** +** Delete one or more interwiki maps. +** +** > fossil interwiki edit TAG --base URL --hash PATH --wiki PATH +** +** Create a interwiki referenced call TAG. The base URL is +** the --base option, which is required. The --hash and --wiki +** paths are optional. The TAG must be lower-case alphanumeric +** and must be unique. A new entry is created if it does not +** already exit. +** +** > fossil interwiki list +** +** Show all interwiki mappings. +*/ +void interwiki_cmd(void){ + const char *zCmd; + int nCmd; + db_find_and_open_repository(0, 0); + if( g.argc<3 ){ + usage("SUBCOMMAND ..."); + } + zCmd = g.argv[2]; + nCmd = (int)strlen(zCmd); + if( strncmp(zCmd,"edit",nCmd)==0 ){ + const char *zName; + const char *zBase = find_option("base",0,1); + const char *zHash = find_option("hash",0,1); + const char *zWiki = find_option("wiki",0,1); + verify_all_options(); + if( g.argc!=4 ) usage("add TAG ?OPTIONS?"); + zName = g.argv[3]; + if( zBase==0 ){ + fossil_fatal("the --base option is required"); + } + if( !interwiki_valid_name(zName) ){ + fossil_fatal("not a valid interwiki tag: \"%s\"", zName); + } + db_begin_write(); + db_unprotect(PROTECT_CONFIG); + db_multi_exec( + "REPLACE INTO config(name,value,mtime)" + " VALUES('interwiki:'||lower(%Q)," + " json_object('base',%Q,'hash',%Q,'wiki',%Q)," + " now());", + zName, zBase, zHash, zWiki + ); + setup_incr_cfgcnt(); + db_protect_pop(); + db_commit_transaction(); + }else + if( strncmp(zCmd, "delete", nCmd)==0 ){ + int i; + verify_all_options(); + if( g.argc<4 ) usage("delete ID ..."); + db_begin_write(); + db_unprotect(PROTECT_CONFIG); + for(i=3; i\n"); + } + blob_appendf(out,"", + db_column_text(&q,0)); + blob_appendf(out,"\n", + db_column_text(&q,1)); + n++; + } + db_finalize(&q); + if( n>0 ){ + blob_appendf(out,"
%h → %h
\n"); + }else{ + blob_appendf(out,"None\n"); + } +} + +/* +** WEBPAGE: /intermap +** +** View and modify the interwiki tag map or "intermap". +** This page is visible to administrators only. +*/ +void interwiki_page(void){ + Stmt q; + int n = 0; + const char *z; + const char *zTag = ""; + const char *zBase = ""; + const char *zHash = ""; + const char *zWiki = ""; + char *zErr = 0; + + login_check_credentials(); + if( !g.perm.Read && !g.perm.RdWiki && ~g.perm.RdTkt ){ + login_needed(0); + return; + } + if( g.perm.Setup && P("submit")!=0 && cgi_csrf_safe(1) ){ + zTag = PT("tag"); + zBase = PT("base"); + zHash = PT("hash"); + zWiki = PT("wiki"); + if( zTag==0 || zTag[0]==0 || !interwiki_valid_name(zTag) ){ + zErr = mprintf("Not a valid interwiki tag name: \"%s\"", zTag?zTag : ""); + }else if( zBase==0 || zBase[0]==0 ){ + db_unprotect(PROTECT_CONFIG); + db_multi_exec("DELETE FROM config WHERE name='interwiki:%q';", zTag); + db_protect_pop(); + }else{ + if( zHash && zHash[0]==0 ) zHash = 0; + if( zWiki && zWiki[0]==0 ) zWiki = 0; + db_unprotect(PROTECT_CONFIG); + db_multi_exec( + "REPLACE INTO config(name,value,mtime)" + "VALUES('interwiki:'||lower(%Q)," + " json_object('base',%Q,'hash',%Q,'wiki',%Q)," + " now());", + zTag, zBase, zHash, zWiki); + db_protect_pop(); + } + } + + style_set_current_feature("interwiki"); + style_header("Interwiki Map Configuration"); + @

Interwiki links are hyperlink targets of the form + @

Tag:PageName
+ @

Such links resolve to links to PageName on a separate server + @ identified by Tag. The Interwiki Map or "intermap" is a mapping + @ from Tags to complete Server URLs. + db_prepare(&q, + "SELECT substr(name,11)," + " json_extract(value,'$.base')," + " json_extract(value,'$.hash')," + " json_extract(value,'$.wiki')" + " FROM config WHERE name glob 'interwiki:*'" + ); + while( db_step(&q)==SQLITE_ROW ){ + if( n==0 ){ + @ The current mapping is as follows: + @

    + } + @
  1. %h(db_column_text(&q,0)) + @

      + @
    • Base-URL: %h(db_column_text(&q,1)) + z = db_column_text(&q,2); + if( z==0 ){ + @
    • Hash-path: NULL + }else{ + @
    • Hash-path: %h(z) + } + z = db_column_text(&q,3); + if( z==0 ){ + @
    • Wiki-path: NULL + }else{ + @
    • Wiki-path: %h(z) + } + @
    + n++; + } + db_finalize(&q); + if( n ){ + @
+ }else{ + @ No mappings are currently defined. + } + + if( !g.perm.Setup ){ + /* Do not show intermap editing fields to non-setup users */ + style_finish_page(); + return; + } + + @

To add a new mapping, fill out the form below providing a unique name + @ for the tag. To edit an exist mapping, fill out the form and use the + @ existing name as the tag. To delete an existing mapping, fill in the + @ tag field but leave the "Base URL" field blank.

+ if( zErr ){ + @

%h(zErr)

+ } + @
+ login_insert_csrf_secret(); + @ + @ + @ + @ + @ + @ + @ + @ + @ + @ + @ + @
Tag:
Base URL:
Hash-path: + @ (use "/info/" when the target is Fossil)
Wiki-path: + @ (use "/wiki?name=" when the target is Fossil)
+ @
+ + style_finish_page(); +} ADDED src/json.c Index: src/json.c ================================================================== --- /dev/null +++ src/json.c @@ -0,0 +1,2426 @@ +#ifdef FOSSIL_ENABLE_JSON +/* +** Copyright (c) 2011 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/ +** +******************************************************************************* +** +** Code for the JSON API. +** +** The JSON API's public interface is documented at: +** +** https://fossil-scm.org/fossil/doc/trunk/www/json-api/index.md +** +** Notes for hackers... +** +** Here's how command/page dispatching works: json_page_top() (in HTTP mode) or +** json_cmd_top() (in CLI mode) catch the "json" path/command. Those functions then +** dispatch to a JSON-mode-specific command/page handler with the type fossil_json_f(). +** See the API docs for that typedef (below) for the semantics of the callbacks. +** +** +*/ +#include "VERSION.h" +#include "config.h" +#include "json.h" +#include +#include + +#if INTERFACE +#include "json_detail.h" /* workaround for apparent enum limitation in makeheaders */ +#endif + +const FossilJsonKeys_ FossilJsonKeys = { + "anonymousSeed" /*anonymousSeed*/, + "authToken" /*authToken*/, + "COMMAND_PATH" /*commandPath*/, + "mtime" /*mtime*/, + "payload" /* payload */, + "requestId" /*requestId*/, + "resultCode" /*resultCode*/, + "resultText" /*resultText*/, + "timestamp" /*timestamp*/ +}; + +/* +** Given the current request path string, this function returns true +** if it refers to a JSON API path. i.e. if (1) it starts with /json +** or (2) g.zCmdName is "server" or "cgi" and the path starts with +** /somereponame/json. Specifically, it returns 1 in the former case +** and 2 for the latter. +*/ +int json_request_is_json_api(const char * zPathInfo){ + int rc = 0; + if(zPathInfo==0){ + rc = 0; + }else if(0==strncmp("/json",zPathInfo,5) + && (zPathInfo[5]==0 || zPathInfo[5]=='/')){ + rc = 1; + }else if(g.zCmdName!=0 && (0==strcmp("server",g.zCmdName) + || 0==strcmp("ui",g.zCmdName) + || 0==strcmp("cgi",g.zCmdName) + || 0==strcmp("http",g.zCmdName)) ){ + /* When running in server/cgi "directory" mode, zPathInfo is + ** prefixed with the repository's name, so in order to determine + ** whether or not we're really running in json mode we have to try + ** a bit harder. Problem reported here: + ** https://fossil-scm.org/forum/forumpost/e4953666d6 + */ + ReCompiled * pReg = 0; + const char * zErr = re_compile(&pReg, "^/[^/]+/json(/.*)?", 0); + assert(zErr==0 && "Regex compilation failed?"); + if(zErr==0 && + re_match(pReg, (const unsigned char *)zPathInfo, -1)){ + rc = 2; + } + re_free(pReg); + } + return rc; +} + +/* +** Returns true (non-0) if fossil appears to be running in JSON mode. +** and either has JSON POSTed input awaiting consumption or fossil is +** running in HTTP mode (in which case certain JSON data *might* be +** available via GET parameters). +*/ +int fossil_has_json(){ + return g.json.isJsonMode && (g.isHTTP || g.json.post.o); +} + +/* +** Placeholder /json/XXX page impl for NYI (Not Yet Implemented) +** (but planned) pages/commands. +*/ +cson_value * json_page_nyi(){ + g.json.resultCode = FSL_JSON_E_NYI; + return NULL; +} + +/* +** Given a FossilJsonCodes value, it returns a string suitable for use +** as a resultCode string. Returns some unspecified non-empty string +** if errCode is not one of the FossilJsonCodes values. +*/ +static char const * json_err_cstr( int errCode ){ + switch( errCode ){ + case 0: return "Success"; +#define C(X,V) case FSL_JSON_E_ ## X: return V + + C(GENERIC,"Generic error"); + C(INVALID_REQUEST,"Invalid request"); + C(UNKNOWN_COMMAND,"Unknown command or subcommand"); + C(UNKNOWN,"Unknown error"); + C(TIMEOUT,"Timeout reached"); + C(ASSERT,"Assertion failed"); + C(ALLOC,"Resource allocation failed"); + C(NYI,"Not yet implemented"); + C(PANIC,"x"); + C(MANIFEST_READ_FAILED,"Reading artifact manifest failed"); + C(FILE_OPEN_FAILED,"Opening file failed"); + + C(AUTH,"Authentication error"); + C(MISSING_AUTH,"Authentication info missing from request"); + C(DENIED,"Access denied"); + C(WRONG_MODE,"Request not allowed (wrong operation mode)"); + C(LOGIN_FAILED,"Login failed"); + C(LOGIN_FAILED_NOSEED,"Anonymous login attempt was missing password seed"); + C(LOGIN_FAILED_NONAME,"Login failed - name not supplied"); + C(LOGIN_FAILED_NOPW,"Login failed - password not supplied"); + C(LOGIN_FAILED_NOTFOUND,"Login failed - no match found"); + + C(USAGE,"Usage error"); + C(INVALID_ARGS,"Invalid argument(s)"); + C(MISSING_ARGS,"Missing argument(s)"); + C(AMBIGUOUS_UUID,"Resource identifier is ambiguous"); + C(UNRESOLVED_UUID,"Provided uuid/tag/branch could not be resolved"); + C(RESOURCE_ALREADY_EXISTS,"Resource already exists"); + C(RESOURCE_NOT_FOUND,"Resource not found"); + + C(DB,"Database error"); + C(STMT_PREP,"Statement preparation failed"); + C(STMT_BIND,"Statement parameter binding failed"); + C(STMT_EXEC,"Statement execution/stepping failed"); + C(DB_LOCKED,"Database is locked"); + C(DB_NEEDS_REBUILD,"Fossil repository needs to be rebuilt"); + C(DB_NOT_FOUND,"Fossil repository db file could not be found."); + C(DB_NOT_VALID, "Fossil repository db file is not valid."); + C(DB_NEEDS_CHECKOUT, "Command requires a local checkout."); +#undef C + default: + return "Unknown Error"; + } +} + +/* +** Implements the cson_data_dest_f() interface and outputs the data to +** a fossil Blob object. pState must be-a initialized (Blob*), to +** which n bytes of src will be appended. +**/ +int cson_data_dest_Blob(void * pState, void const * src, unsigned int n){ + Blob * b = (Blob*)pState; + blob_append( b, (char const *)src, (int)n ) /* will die on OOM */; + return 0; +} + +/* +** Implements the cson_data_source_f() interface and reads input from +** a fossil Blob object. pState must be-a (Blob*) populated with JSON +** data. +*/ +int cson_data_src_Blob(void * pState, void * dest, unsigned int * n){ + Blob * b = (Blob*)pState; + *n = blob_read( b, dest, *n ); + return 0; +} + +/* +** Convenience wrapper around cson_output() which appends the output +** to pDest. pOpt may be NULL, in which case g.json.outOpt will be used. +*/ +int cson_output_Blob( cson_value const * pVal, Blob * pDest, cson_output_opt const * pOpt ){ + return cson_output( pVal, cson_data_dest_Blob, + pDest, pOpt ? pOpt : &g.json.outOpt ); +} + +/* +** Convenience wrapper around cson_parse() which reads its input +** from pSrc. pSrc is rewound before parsing. +** +** pInfo may be NULL. If it is not NULL then it will contain details +** about the parse state when this function returns. +** +** On success a new JSON Object or Array is returned (owned by the +** caller). On error NULL is returned. +*/ +cson_value * cson_parse_Blob( Blob * pSrc, cson_parse_info * pInfo ){ + cson_value * root = NULL; + blob_rewind( pSrc ); + cson_parse( &root, cson_data_src_Blob, pSrc, NULL, pInfo ); + return root; +} + +/* +** Implements the cson_data_dest_f() interface and outputs the data to +** cgi_append_content(). pState is ignored. +**/ +int cson_data_dest_cgi(void * pState, void const * src, unsigned int n){ + cgi_append_content( (char const *)src, (int)n ); + return 0; +} + +/* +** Returns a string in the form FOSSIL-XXXX, where XXXX is a +** left-zero-padded value of code. The returned buffer is static, and +** must be copied if needed for later. The returned value will always +** be 11 bytes long (not including the trailing NUL byte). +** +** In practice we will only ever call this one time per app execution +** when constructing the JSON response envelope, so the static buffer +** "shouldn't" be a problem. +** +*/ +char const * json_rc_cstr( int code ){ + enum { BufSize = 12 }; + static char buf[BufSize] = {'F','O','S','S','I','L','-',0}; + assert((code >= 1000) && (code <= 9999) && "Invalid Fossil/JSON code."); + sprintf(buf+7,"%04d", code); + return buf; +} + +/* +** Adds v to the API-internal cleanup mechanism. key is ignored +** (legacy) but might be re-introduced and "should" be a unique +** (app-wide) value. Failure to insert an item may be caused by any +** of the following: +** +** - Allocation error. +** - g.json.gc.a is NULL +** - key is NULL or empty. +** +** Returns 0 on success. +** +** Ownership of v is transfered to (or shared with) g.json.gc, and v +** will be valid until that object is cleaned up or some internal code +** incorrectly removes it from the gc (which we never do). If this +** function fails, it is fatal to the app (as it indicates an +** allocation error (more likely than not) or a serious internal error +** such as numeric overflow). +*/ +void json_gc_add( char const * key, cson_value * v ){ + int const rc = cson_array_append( g.json.gc.a, v ); + + assert( NULL != g.json.gc.a ); + if( 0 != rc ){ + cson_value_free( v ); + } + assert( (0==rc) && "Adding item to GC failed." ); + if(0!=rc){ + fprintf(stderr,"%s: FATAL: alloc error.\n", g.argv[0]) + /* reminder: allocation error is the only reasonable cause of + error here, provided g.json.gc.a and v are not NULL. + */ + ; + fossil_exit(1)/*not fossil_panic() b/c it might land us somewhere + where this function is called again. + */; + } +} + + +/* +** Returns the value of json_rc_cstr(code) as a new JSON +** string, which is owned by the caller and must eventually +** be cson_value_free()d or transfered to a JSON container. +*/ +cson_value * json_rc_string( int code ){ + return cson_value_new_string( json_rc_cstr(code), 11 ); +} + +cson_value * json_new_string( char const * str ){ + return str + ? cson_value_new_string(str,strlen(str)) + : NULL; +} + +cson_value * json_new_string_f( char const * fmt, ... ){ + cson_value * v; + char * zStr; + va_list vargs; + va_start(vargs,fmt); + zStr = vmprintf(fmt,vargs); + va_end(vargs); + v = cson_value_new_string(zStr, strlen(zStr)); + free(zStr); + return v; +} + +cson_value * json_new_int( i64 v ){ + return cson_value_new_integer((cson_int_t)v); +} + +/* +** Gets a POST/POST.payload/GET/COOKIE/ENV value. The returned memory +** is owned by the g.json object (one of its sub-objects). Returns +** NULL if no match is found. +** +** ENV means the system environment (getenv()). +** +** Precedence: POST.payload, GET/COOKIE/non-JSON POST, JSON POST, ENV. +** +** FIXME: the precedence SHOULD be: GET, POST.payload, POST, COOKIE, +** ENV, but the amalgamation of the GET/POST vars makes it effectively +** impossible to do that. Since fossil only uses one cookie, cookie +** precedence isn't a real/high-priority problem. +*/ +cson_value * json_getenv( char const * zKey ){ + cson_value * rc; + rc = g.json.reqPayload.o + ? cson_object_get( g.json.reqPayload.o, zKey ) + : NULL; + if(rc){ + return rc; + } + rc = cson_object_get( g.json.param.o, zKey ); + if( rc ){ + return rc; + } + rc = cson_object_get( g.json.post.o, zKey ); + if(rc){ + return rc; + }else{ + char const * cv = PD(zKey,NULL); + if(!cv && !g.isHTTP){ + /* reminder to self: in CLI mode i'd like to try + find_option(zKey,NULL,XYZ) here, but we don't have a sane + default for the XYZ param here. + */ + cv = fossil_getenv(zKey); + } + if(cv){/*transform it to JSON for later use.*/ + /* use sscanf() to figure out if it's an int, + and transform it to JSON int if it is. + + FIXME: use strtol(), since it has more accurate + error handling. + */ + int intVal = -1; + char endOfIntCheck; + int const scanRc = sscanf(cv,"%d%c",&intVal, &endOfIntCheck) + /* The %c bit there is to make sure that we don't accept 123x + as a number. sscanf() returns the number of tokens + successfully parsed, so an RC of 1 will be correct for "123" + but "123x" will have RC==2. + + But it appears to not be working that way :/ + */ + ; + if(1==scanRc){ + json_setenv( zKey, cson_value_new_integer(intVal) ); + }else{ + rc = cson_value_new_string(cv,strlen(cv)); + json_setenv( zKey, rc ); + } + return rc; + }else{ + return NULL; + } + } +} + +/* +** Wrapper around json_getenv() which... +** +** If it finds a value and that value is-a JSON number or is a string +** which looks like an integer or is-a JSON bool/null then it is +** converted to an int. If none of those apply then dflt is returned. +*/ +int json_getenv_int(char const * pKey, int dflt ){ + cson_value const * v = json_getenv(pKey); + const cson_type_id type = v ? cson_value_type_id(v) : CSON_TYPE_UNDEF; + switch(type){ + case CSON_TYPE_INTEGER: + case CSON_TYPE_DOUBLE: + return (int)cson_value_get_integer(v); + case CSON_TYPE_STRING: { + char const * sv = cson_string_cstr(cson_value_get_string(v)); + assert( (NULL!=sv) && "This is quite unexpected." ); + return sv ? atoi(sv) : dflt; + } + case CSON_TYPE_BOOL: + return cson_value_get_bool(v) ? 1 : 0; + case CSON_TYPE_NULL: + return 0; + default: + return dflt; + } +} + + +/* +** Wrapper around json_getenv() which tries to evaluate a payload/env +** value as a boolean. Uses mostly the same logic as +** json_getenv_int(), with the addition that string values which +** either start with a digit 1..9 or the letters [tTyY] are considered +** to be true. If this function cannot find a matching key/value then +** dflt is returned. e.g. if it finds the key but the value is-a +** Object then dftl is returned. +** +** If an entry is found, this function guarantees that it will return +** either 0 or 1, as opposed to "0 or non-zero", so that clients can +** pass a different value as dflt. Thus they can use, e.g. -1 to know +** whether or not this function found a match (it will return -1 in +** that case). +*/ +int json_getenv_bool(char const * pKey, int dflt ){ + cson_value const * v = json_getenv(pKey); + const cson_type_id type = v ? cson_value_type_id(v) : CSON_TYPE_UNDEF; + switch(type){ + case CSON_TYPE_INTEGER: + case CSON_TYPE_DOUBLE: + return cson_value_get_integer(v) ? 1 : 0; + case CSON_TYPE_STRING: { + char const * sv = cson_string_cstr(cson_value_get_string(v)); + assert( (NULL!=sv) && "This is quite unexpected." ); + if(!*sv || ('0'==*sv)){ + return 0; + }else{ + return ((('1'<=*sv) && ('9'>=*sv)) + || ('t'==*sv) || ('T'==*sv) + || ('y'==*sv) || ('Y'==*sv) + ) + ? 1 : 0; + } + } + case CSON_TYPE_BOOL: + return cson_value_get_bool(v) ? 1 : 0; + case CSON_TYPE_NULL: + return 0; + default: + return dflt; + } +} + +/* +** Returns the string form of a json_getenv() value, but ONLY If that +** value is-a String. Non-strings are not converted to strings for +** this purpose. Returned memory is owned by g.json or fossil and is +** valid until end-of-app or the given key is replaced in fossil's +** internals via cgi_replace_parameter() and friends or json_setenv(). +*/ +char const * json_getenv_cstr( char const * zKey ){ + return cson_value_get_cstr( json_getenv(zKey) ); +} + +/* +** An extended form of find_option() which tries to look up a combo +** GET/POST/CLI argument. +** +** zKey must be the GET/POST parameter key. zCLILong must be the "long +** form" CLI flag (NULL means to use zKey). zCLIShort may be NULL or +** the "short form" CLI flag (if NULL, no short form is used). +** +** If argPos is >=0 and no other match is found, +** json_command_arg(argPos) is also checked. +** +** On error (no match found) NULL is returned. +** +** This ONLY works for String JSON/GET/CLI values, not JSON +** booleans and whatnot. +*/ +char const * json_find_option_cstr2(char const * zKey, + char const * zCLILong, + char const * zCLIShort, + int argPos){ + char const * rc = NULL; + assert(NULL != zKey); + if(!g.isHTTP){ + rc = find_option(zCLILong ? zCLILong : zKey, + zCLIShort, 1); + } + if(!rc && fossil_has_json()){ + rc = json_getenv_cstr(zKey); + if(!rc && zCLIShort){ + rc = cson_value_get_cstr( cson_object_get( g.json.param.o, zCLIShort) ); + } + } + if(!rc && (argPos>=0)){ + rc = json_command_arg((unsigned char)argPos); + } + return rc; +} + +/* +** Short-hand form of json_find_option_cstr2(zKey,zCLILong,zCLIShort,-1). +*/ +char const * json_find_option_cstr(char const * zKey, + char const * zCLILong, + char const * zCLIShort){ + return json_find_option_cstr2(zKey, zCLILong, zCLIShort, -1); +} + +/* +** The boolean equivalent of json_find_option_cstr(). +** If the option is not found, dftl is returned. +*/ +int json_find_option_bool(char const * zKey, + char const * zCLILong, + char const * zCLIShort, + int dflt ){ + int rc = -1; + if(!g.isHTTP){ + if(NULL != find_option(zCLILong ? zCLILong : zKey, + zCLIShort, 0)){ + rc = 1; + } + } + if((-1==rc) && fossil_has_json()){ + rc = json_getenv_bool(zKey,-1); + } + return (-1==rc) ? dflt : rc; +} + +/* +** The integer equivalent of json_find_option_cstr2(). +** If the option is not found, dftl is returned. +*/ +int json_find_option_int(char const * zKey, + char const * zCLILong, + char const * zCLIShort, + int dflt ){ + enum { Magic = -1947854832 }; + int rc = Magic; + if(!g.isHTTP){ + /* FIXME: use strtol() for better error/dflt handling. */ + char const * opt = find_option(zCLILong ? zCLILong : zKey, + zCLIShort, 1); + if(NULL!=opt){ + rc = atoi(opt); + } + } + if(Magic==rc){ + rc = json_getenv_int(zKey,Magic); + } + return (Magic==rc) ? dflt : rc; +} + + +/* +** Adds v to g.json.param.o using the given key. May cause any prior +** item with that key to be destroyed (depends on current reference +** count for that value). On success, transfers (or shares) ownership +** of v to (or with) g.json.param.o. On error ownership of v is not +** modified. +*/ +int json_setenv( char const * zKey, cson_value * v ){ + return cson_object_set( g.json.param.o, zKey, v ); +} + +/* +** Guesses a RESPONSE Content-Type value based (primarily) on the +** HTTP_ACCEPT header. +** +** It will try to figure out if the client can support +** application/json or application/javascript, and will fall back to +** text/plain if it cannot figure out anything more specific. +** +** Returned memory is static and immutable, but if the environment +** changes after calling this then subsequent calls to this function +** might return different (also static/immutable) values. +*/ +char const * json_guess_content_type(){ + char const * cset; + char doUtf8; + cset = PD("HTTP_ACCEPT_CHARSET",NULL); + doUtf8 = ((NULL == cset) || (NULL!=strstr("utf-8",cset))) + ? 1 : 0; + if( g.json.jsonp ){ + return doUtf8 + ? "application/javascript; charset=utf-8" + : "application/javascript"; + }else{ + /* + Content-type + + If the browser does not sent an ACCEPT for application/json + then we fall back to text/plain. + */ + char const * cstr; + cstr = PD("HTTP_ACCEPT",NULL); + if( NULL == cstr ){ + return doUtf8 + ? "application/json; charset=utf-8" + : "application/json"; + }else{ + if( strstr( cstr, "application/json" ) + || strstr( cstr, "*/*" ) ){ + return doUtf8 + ? "application/json; charset=utf-8" + : "application/json"; + }else{ + return "text/plain"; + } + } + } +} + +/* + ** Given a request CONTENT_TYPE value, this function returns true + ** if it is of a type which the JSON API can ostensibly read. + ** + ** It accepts any of application/json, text/plain, or + ** application/javascript. The former is preferred, but was not + ** widespread when this API was initially built, so the latter forms + ** are permitted as fallbacks. + */ +int json_can_consume_content_type(const char * zType){ + return fossil_strcmp(zType, "application/json")==0 + || fossil_strcmp(zType,"text/plain")==0/*assume this MIGHT be JSON*/ + || fossil_strcmp(zType,"application/javascript")==0; +} + +/* +** Sends pResponse to the output stream as the response object. This +** function does no validation of pResponse except to assert() that it +** is not NULL. The caller is responsible for ensuring that it meets +** API response envelope conventions. +** +** In CLI mode pResponse is sent to stdout immediately. In HTTP +** mode pResponse replaces any current CGI content but cgi_reply() +** is not called to flush the output. +** +** If g.json.jsonp is not NULL then the content type is set to +** application/javascript and the output is wrapped in a jsonp +** wrapper. +*/ +void json_send_response( cson_value const * pResponse ){ + assert( NULL != pResponse ); + if( g.isHTTP ){ + cgi_reset_content(); + if( g.json.jsonp ){ + cgi_printf("%s(",g.json.jsonp); + } + cson_output( pResponse, cson_data_dest_cgi, NULL, &g.json.outOpt ); + if( g.json.jsonp ){ + cgi_append_content(")",1); + } + }else{/*CLI mode*/ + if( g.json.jsonp ){ + fprintf(stdout,"%s(",g.json.jsonp); + } + cson_output_FILE( pResponse, stdout, &g.json.outOpt ); + if( g.json.jsonp ){ + fwrite(")\n", 2, 1, stdout); + } + } +} + +/* +** Returns the current request's JSON authentication token, or NULL if +** none is found. The token's memory is owned by (or shared with) +** g.json. +** +** If an auth token is found in the GET/POST request data then fossil +** is given that data for use in authentication for this +** session. i.e. the GET/POST data overrides fossil's authentication +** cookie value (if any) and also works with clients which do not +** support cookies. +** +** Must be called once before login_check_credentials() is called or +** we will not be able to replace fossil's internal idea of the auth +** info in time (and future changes to that state may cause unexpected +** results). +** +** The result of this call are cached for future calls. +** +** Special case: if g.useLocalauth is true (i.e. the --localauth flag +** was used to start the fossil server instance) and the current +** connection is "local", any authToken provided by the user is +** ignored and no new token is created: localauth mode trumps the +** authToken. +*/ +cson_value * json_auth_token(){ + assert(g.json.gc.a && "json_bootstrap_early() was not called!"); + if( g.json.authToken==0 && g.noPswd==0 + /* g.noPswd!=0 means the user was logged in via a local + connection using --localauth. */ + ){ + /* Try to get an authorization token from GET parameter, POSTed + JSON, or fossil cookie (in that order). */ + g.json.authToken = json_getenv(FossilJsonKeys.authToken); + if(g.json.authToken + && cson_value_is_string(g.json.authToken) + && !PD(login_cookie_name(),NULL)){ + /* tell fossil to use this login info. + + FIXME?: because the JSON bits don't carry around + login_cookie_name(), there is(?) a potential(?) login hijacking + window here. We may need to change the JSON auth token to be in + the form: login_cookie_name()=... + + Then again, the hardened cookie value helps ensure that + only a proper key/value match is valid. + */ + cgi_replace_parameter( login_cookie_name(), cson_value_get_cstr(g.json.authToken) ); + }else if( g.isHTTP ){ + /* try fossil's conventional cookie. */ + /* Reminder: chicken/egg scenario regarding db access in CLI + mode because login_cookie_name() needs the db. CLI + mode does not use any authentication, so we don't need + to support it here. + */ + char const * zCookie = P(login_cookie_name()); + if( zCookie && *zCookie ){ + /* Transfer fossil's cookie to JSON for downstream convenience... */ + cson_value * v = cson_value_new_string(zCookie, strlen(zCookie)); + json_gc_add( FossilJsonKeys.authToken, v ); + g.json.authToken = v; + } + } + } + return g.json.authToken; +} + +/* +** If g.json.reqPayload.o is NULL then NULL is returned, else the +** given property is searched for in the request payload. If found it +** is returned. The returned value is owned by (or shares ownership +** with) g.json, and must NOT be cson_value_free()'d by the +** caller. +*/ +cson_value * json_req_payload_get(char const *pKey){ + return g.json.reqPayload.o + ? cson_object_get(g.json.reqPayload.o,pKey) + : NULL; +} + + +/* +** Returns non-zero if the json_bootstrap_early() function has already +** been called. In general, this function should be used sparingly, +** e.g. from low-level support functions like fossil_warning() where +** there is genuine uncertainty about whether (or not) the JSON setup +** has already been called. +*/ +int json_is_bootstrapped_early(){ + return ((g.json.gc.v != NULL) && (g.json.gc.a != NULL)); +} + +/* +** Initializes some JSON bits which need to be initialized relatively +** early on. It should be called by any routine which might need to +** call into JSON relatively early on in the init process. +** Specifically, early on in cgi_init() and json_cmd_top(), but also +** from any error reporting routines which might be triggered (early +** on in those functions). +** +** Initializes g.json.gc and g.json.param. This code does not (and +** must not) rely on any of the fossil environment having been set +** up. e.g. it must not use cgi_parameter() and friends because this +** must be called before those data are initialized. +** +** If called multiple times, calls after the first are a no-op. +*/ +void json_bootstrap_early(){ + cson_value * v; + + if(g.json.gc.v!=NULL){ + /* Avoid multiple bootstrappings. */ + return; + } + g.json.timerId = fossil_timer_start(); + /* g.json.gc is our "garbage collector" - where we put JSON values + which need a long lifetime but don't have a logical parent to put + them in. */ + v = cson_value_new_array(); + g.json.gc.v = v; + assert(0 != g.json.gc.v); + g.json.gc.a = cson_value_get_array(v); + assert(0 != g.json.gc.a); + cson_value_add_reference(v) + /* Needed to allow us to include this value in other JSON + containers without transferring ownership to those containers. + All other persistent g.json.XXX.v values get appended to + g.json.gc.a, and therefore already have a live reference + for this purpose. */ + ; + + /* + g.json.param holds the JSONized counterpart of fossil's + cgi_parameter_xxx() family of data. We store them as JSON, as + opposed to using fossil's data directly, because we can retain + full type information for data this way (as opposed to it always + being of type string). + */ + v = cson_value_new_object(); + g.json.param.v = v; + g.json.param.o = cson_value_get_object(v); + json_gc_add("$PARAMS", v); +} + +/* +** Appends a warning object to the (pending) JSON response. +** +** Code must be a FSL_JSON_W_xxx value from the FossilJsonCodes enum. +** +** A Warning object has this JSON structure: +** +** { "code":integer, "text":"string" } +** +** But the text part is optional. +** +** If msg is non-NULL and not empty then it is used as the "text" +** property's value. It is copied, and need not refer to static +** memory. +** +** CURRENTLY this code only allows a given warning code to be +** added one time, and elides subsequent warnings. The intention +** is to remove that burden from loops which produce warnings. +** +** FIXME: if msg is NULL then use a standard string for +** the given code. If !*msg then elide the "text" property, +** for consistency with how json_err() works. +*/ +void json_warn( int code, char const * fmt, ... ){ + cson_object * obj = NULL; + assert( (code>FSL_JSON_W_START) + && (code=rc ){ + cson_free_array(a); + a = NULL; + } + return a ? cson_array_value(a) : NULL; +} + + +/* +** Performs some common initialization of JSON-related state. Must be +** called by the json_page_top() and json_cmd_top() dispatching +** functions to set up the JSON stat used by the dispatched functions. +** +** Implicitly sets up the login information state in CGI mode, but +** does not perform any permissions checking. It _might_ (haven't +** tested this) die with an error if an auth cookie is malformed. +** +** This must be called by the top-level JSON command dispatching code +** before they do any work. +** +** This must only be called once, or an assertion may be triggered. +*/ +void json_bootstrap_late(){ + static char once = 0 /* guard against multiple runs */; + char const * zPath = P("PATH_INFO"); + assert(g.json.gc.a && "json_bootstrap_early() was not called!"); + assert( (0==once) && "json_bootstrap_late() called too many times!"); + if( once ){ + return; + }else{ + once = 1; + } + assert(g.json.isJsonMode + && "g.json.isJsonMode should have been set up by now."); + g.json.resultCode = 0; + g.json.cmd.offset = -1; + g.json.jsonp = PD("jsonp",NULL) + /* FIXME: do some sanity checking on g.json.jsonp and ignore it + if it is not halfway reasonable. + */ + ; + if( !g.isHTTP && g.fullHttpReply ){ + /* workaround for server mode, so we see it as CGI mode. */ + g.isHTTP = 1; + } + + if(g.isHTTP){ + cgi_set_content_type(json_guess_content_type()) + /* reminder: must be done after g.json.jsonp is initialized */ + ; +#if 0 + /* Calling this seems to trigger an SQLITE_MISUSE warning??? + Maybe it's not legal to set the logger more than once? + */ + sqlite3_config(SQLITE_CONFIG_LOG, NULL, 0) + /* avoids debug messages on stderr in JSON mode */ + ; +#endif + } + + g.json.cmd.v = cson_value_new_array(); + g.json.cmd.a = cson_value_get_array(g.json.cmd.v); + json_gc_add( FossilJsonKeys.commandPath, g.json.cmd.v ); + /* + The following if/else block translates the PATH_INFO path (in + CLI/server modes) or g.argv (CLI mode) into an internal list so + that we can simplify command dispatching later on. + + Note that translating g.argv this way is overkill but allows us to + avoid CLI-only special-case handling in other code, e.g. + json_command_arg(). + */ + if( zPath ){/* Either CGI or server mode... */ + /* Translate PATH_INFO into JSON array for later convenience. */ + json_string_split(zPath, '/', 1, g.json.cmd.a); + }else{/* assume CLI mode */ + int i; + char const * arg; + cson_value * part; + for(i = 1/*skip argv[0]*/; i < g.argc; ++i ){ + arg = g.argv[i]; + if( !arg || !*arg ){ + continue; + } + if('-' == *arg){ + /* workaround to skip CLI args so that + json_command_arg() does not see them. + This assumes that all arguments come LAST + on the command line. + */ + break; + } + part = cson_value_new_string(arg,strlen(arg)); + cson_array_append(g.json.cmd.a, part); + } + } + + while(!g.isHTTP){ + /* Simulate JSON POST data via input file. Pedantic reminder: + error handling does not honor user-supplied g.json.outOpt + because outOpt cannot (generically) be configured until after + POST-reading is finished. + */ + FILE * inFile = NULL; + char const * jfile = find_option("json-input",NULL,1); + if(!jfile || !*jfile){ + break; + } + inFile = (0==strcmp("-",jfile)) + ? stdin + : fossil_fopen(jfile,"rb"); + if(!inFile){ + g.json.resultCode = FSL_JSON_E_FILE_OPEN_FAILED; + fossil_fatal("Could not open JSON file [%s].",jfile) + /* Does not return. */ + ; + } + cgi_parse_POST_JSON(inFile, 0); + fossil_fclose(inFile); + break; + } + + /* g.json.reqPayload exists only to simplify some of our access to + the request payload. We currently only use this in the context of + Object payloads, not Arrays, strings, etc. + */ + g.json.reqPayload.v = cson_object_get( g.json.post.o, FossilJsonKeys.payload ); + if( g.json.reqPayload.v ){ + g.json.reqPayload.o = cson_value_get_object( g.json.reqPayload.v ) + /* g.json.reqPayload.o may legally be NULL, which means only that + g.json.reqPayload.v is-not-a Object. + */; + } + + /* Anything which needs json_getenv() and friends should go after + this point. + */ + + if(1 == cson_array_length_get(g.json.cmd.a)){ + /* special case: if we're at the top path, look for + a "command" request arg which specifies which command + to run. + */ + char const * cmd = json_getenv_cstr("command"); + if(cmd){ + json_string_split(cmd, '/', 0, g.json.cmd.a); + g.json.cmd.commandStr = cmd; + } + } + + + if(!g.json.jsonp){ + g.json.jsonp = json_find_option_cstr("jsonp",NULL,NULL); + } + if(!g.isHTTP){ + g.json.errorDetailParanoia = 0 /*disable error code dumb-down for CLI mode*/; + } + + {/* set up JSON output formatting options. */ + int indent = -1; + indent = json_find_option_int("indent",NULL,"I",-1); + g.json.outOpt.indentation = (0>indent) + ? (g.isHTTP ? 0 : 1) + : (unsigned char)indent; + g.json.outOpt.addNewline = g.isHTTP + ? 0 + : (g.json.jsonp ? 0 : 1); + } + + if( g.isHTTP ){ + json_auth_token()/* will copy our auth token, if any, to fossil's + core, which we need before we call + login_check_credentials(). */; + login_check_credentials()/* populates g.perm */; + } + else{ + /* FIXME: we need an option which allows us to skip this. At least + one known command (/json/version) does not need an opened + repo. The problem here is we cannot know which functions need + it from here (because command dispatching hasn't yet happened) + and all other commands rely on the repo being opened before + they are called. A textbook example of lack of foresight :/. + */ + db_find_and_open_repository(OPEN_ANY_SCHEMA,0); + } +} + +/* +** Returns the ndx'th item in the "command path", where index 0 is the +** position of the "json" part of the path. Returns NULL if ndx is out +** of bounds or there is no "json" path element. +** +** In CLI mode the "path" is the list of arguments (skipping argv[0]). +** In server/CGI modes the path is taken from PATH_INFO. +** +** The returned bytes are owned by g.json.cmd.v and _may_ be +** invalidated if that object is modified (depending on how it is +** modified). +** +** Note that CLI options are not included in the command path. Use +** find_option() to get those. +** +*/ +char const * json_command_arg(unsigned short ndx){ + cson_array * ar = g.json.cmd.a; + assert((NULL!=ar) && "Internal error. Was json_bootstrap_late() called?"); + assert((g.argc>1) && "Internal error - we never should have gotten this far."); + if( g.json.cmd.offset < 0 ){ + /* first-time setup. */ + short i = 0; +#define NEXT cson_string_cstr( \ + cson_value_get_string( \ + cson_array_get(ar,i) \ + )) + char const * tok = NEXT; + while( tok ){ + if( g.isHTTP/*workaround for "abbreviated name" in CLI mode*/ + ? (0==strncmp("json",tok,4)) + : (0==strcmp(g.argv[1],tok)) + ){ + g.json.cmd.offset = i; + break; + } + ++i; + tok = NEXT; + } + } +#undef NEXT + if(g.json.cmd.offset < 0){ + return NULL; + }else{ + ndx = g.json.cmd.offset + ndx; + return cson_string_cstr(cson_value_get_string(cson_array_get( ar, g.json.cmd.offset + ndx ))); + } +} + +/* Returns the C-string form of json_auth_token(), or NULL +** if json_auth_token() returns NULL. +*/ +char const * json_auth_token_cstr(){ + return cson_value_get_cstr( json_auth_token() ); +} + +/* +** Returns the JsonPageDef with the given name, or NULL if no match is +** found. +** +** head must be a pointer to an array of JsonPageDefs in which the +** last entry has a NULL name. +*/ +JsonPageDef const * json_handler_for_name( char const * name, JsonPageDef const * head ){ + JsonPageDef const * pageDef = head; + assert( head != NULL ); + if(name && *name) for( ; pageDef->name; ++pageDef ){ + if( 0 == strcmp(name, pageDef->name) ){ + return pageDef; + } + } + return NULL; +} + +/* +** Given a Fossil/JSON result code, this function "dumbs it down" +** according to the current value of g.json.errorDetailParanoia. The +** dumbed-down value is returned. +** +** This function assert()s that code is in the inclusive range 0 to +** 9999. +** +** Note that WARNING codes (1..999) are never dumbed down. +** +*/ +static int json_dumbdown_rc( int code ){ + if(!g.json.errorDetailParanoia + || !code + || ((code>=FSL_JSON_W_START) && (code= 1000) && (code <= 9999) && "Invalid Fossil/JSON code."); + switch( g.json.errorDetailParanoia ){ + case 1: modulo = 10; break; + case 2: modulo = 100; break; + case 3: modulo = 1000; break; + default: break; + } + if( modulo ) code = code - (code % modulo); + return code; + } +} + +/* +** Convenience routine which converts a Julian time value into a Unix +** Epoch timestamp. Requires the db, so this cannot be used before the +** repo is opened (will trigger a fatal error in db_xxx()). The returned +** value is owned by the caller. +*/ +cson_value * json_julian_to_timestamp(double j){ + return cson_value_new_integer((cson_int_t) + db_int64(0,"SELECT cast(strftime('%%s',%lf) as int)",j) + ); +} + +/* +** Returns a timestamp value. +*/ +cson_int_t json_timestamp(){ + return (cson_int_t)time(0); +} + +/* +** Returns a new JSON value (owned by the caller) representing +** a timestamp. If timeVal is < 0 then time(0) is used to fetch +** the time, else timeVal is used as-is. The returned value is +** owned by the caller. +*/ +cson_value * json_new_timestamp(cson_int_t timeVal){ + return cson_value_new_integer((timeVal<0) ? (cson_int_t)time(0) : timeVal); +} + +/* +** Internal helper for json_create_response(). Appends the first +** g.json.dispatchDepth elements of g.json.cmd.a, skipping the first +** one (the "json" part), to a string and returns that string value +** (which is owned by the caller). +*/ +static cson_value * json_response_command_path(){ + if(!g.json.cmd.a){ + return NULL; + }else{ + cson_value * rc = NULL; + Blob path = empty_blob; + unsigned int aLen = g.json.dispatchDepth+1; /*cson_array_length_get(g.json.cmd.a);*/ + unsigned int i = 1; + for( ; i < aLen; ++i ){ + char const * part = cson_string_cstr(cson_value_get_string(cson_array_get(g.json.cmd.a, i))); + if(!part){ +#if 1 + fossil_warning("Iterating further than expected in %s.", + __FILE__); +#endif + break; + } + blob_appendf(&path,"%s%s", (i>1 ? "/": ""), part); + } + rc = json_new_string((blob_size(&path)>0) + ? blob_buffer(&path) + : "") + /* reminder; we need an empty string instead of NULL + in this case, to avoid what outwardly looks like + (but is not) an allocation error in + json_create_response(). + */ + ; + blob_reset(&path); + return rc; + } +} + +/* +** Returns a JSON Object representation of the global g object. +** Returned value is owned by the caller. +*/ +cson_value * json_g_to_json(){ + cson_object * o = NULL; + cson_object * pay = NULL; + pay = o = cson_new_object(); + +#define INT(OBJ,K) cson_object_set(o, #K, json_new_int(OBJ.K)) +#define CSTR(OBJ,K) cson_object_set(o, #K, OBJ.K ? json_new_string(OBJ.K) : cson_value_null()) +#define VAL(K,V) cson_object_set(o, #K, (V) ? (V) : cson_value_null()) + VAL(capabilities, json_cap_value()); + INT(g, argc); + INT(g, isConst); + CSTR(g, zConfigDbName); + INT(g, repositoryOpen); + INT(g, localOpen); + INT(g, minPrefix); + INT(g, fSqlTrace); + INT(g, fSqlStats); + INT(g, fSqlPrint); + INT(g, fQuiet); + INT(g, fHttpTrace); + INT(g, fSystemTrace); + INT(g, fNoSync); + INT(g, iErrPriority); + INT(g, sslNotAvailable); + INT(g, cgiOutput); + INT(g, xferPanic); + INT(g, fullHttpReply); + INT(g, xlinkClusterOnly); + INT(g, fTimeFormat); + INT(g, markPrivate); + INT(g, clockSkewSeen); + INT(g, isHTTP); + INT(g.url, isFile); + INT(g.url, isHttps); + INT(g.url, isSsh); + INT(g.url, port); + INT(g.url, dfltPort); + INT(g, useLocalauth); + INT(g, noPswd); + INT(g, userUid); + INT(g, rcvid); + INT(g, okCsrf); + INT(g, thTrace); + INT(g, isHome); + INT(g, nAux); + INT(g, allowSymlinks); + + CSTR(g, zOpenRevision); + CSTR(g, zLocalRoot); + CSTR(g, zPath); + CSTR(g, zExtra); + CSTR(g, zBaseURL); + CSTR(g, zTop); + CSTR(g, zContentType); + CSTR(g, zErrMsg); + CSTR(g.url, name); + CSTR(g.url, hostname); + CSTR(g.url, protocol); + CSTR(g.url, path); + CSTR(g.url, user); + CSTR(g.url, passwd); + CSTR(g.url, canonical); + CSTR(g.url, proxyAuth); + CSTR(g.url, fossil); + CSTR(g, zLogin); + CSTR(g, zSSLIdentity); + CSTR(g, zIpAddr); + CSTR(g, zNonce); + CSTR(g, zCsrfToken); + + o = cson_new_object(); + cson_object_set(pay, "json", cson_object_value(o) ); + INT(g.json, isJsonMode); + INT(g.json, resultCode); + INT(g.json, errorDetailParanoia); + INT(g.json, dispatchDepth); + VAL(authToken, g.json.authToken); + CSTR(g.json, jsonp); + VAL(gc, g.json.gc.v); + VAL(cmd, g.json.cmd.v); + VAL(param, g.json.param.v); + VAL(POST, g.json.post.v); + VAL(warnings, cson_array_value(g.json.warnings)); + /*cson_output_opt outOpt;*/ + + +#undef INT +#undef CSTR +#undef VAL + return cson_object_value(pay); +} + + +/* +** Creates a new Fossil/JSON response envelope skeleton. It is owned +** by the caller, who must eventually free it using cson_value_free(), +** or add it to a cson container to transfer ownership. Returns NULL +** on error. +** +** If payload is not NULL and resultCode is 0 then it is set as the +** "payload" property of the returned object. If resultCode is 0 then +** it defaults to g.json.resultCode. If resultCode is (or defaults to) +** non-zero and payload is not NULL then this function calls +** cson_value_free(payload) and does not insert the payload into the +** response. In either case, ownership of payload is transfered to (or +** shared with, if the caller holds a reference) this function. +** +** pMsg is an optional message string property (resultText) of the +** response. If resultCode is non-0 and pMsg is NULL then +** json_err_cstr() is used to get the error string. The caller may +** provide his own or may use an empty string to suppress the +** resultText property. +** +*/ +static cson_value * json_create_response( int resultCode, + char const * pMsg, + cson_value * payload){ + cson_value * v = NULL; + cson_value * tmp = NULL; + cson_object * o = NULL; + int rc; + resultCode = json_dumbdown_rc(resultCode ? resultCode : g.json.resultCode); + o = cson_new_object(); + v = cson_object_value(o); + if( ! o ) return NULL; +#define SET(K) if(!tmp) goto cleanup; \ + cson_value_add_reference(tmp); \ + rc = cson_object_set( o, K, tmp ); \ + cson_value_free(tmp); \ + if(rc) do{ \ + tmp = NULL; \ + goto cleanup; \ + }while(0) + + tmp = json_new_string(MANIFEST_UUID); + SET("fossil"); + + tmp = json_new_timestamp(-1); + SET(FossilJsonKeys.timestamp); + + if( 0 != resultCode ){ + if( ! pMsg ){ + pMsg = g.zErrMsg; + if(!pMsg){ + pMsg = json_err_cstr(resultCode); + } + } + tmp = json_new_string(json_rc_cstr(resultCode)); + SET(FossilJsonKeys.resultCode); + } + + if( pMsg && *pMsg ){ + tmp = json_new_string(pMsg); + SET(FossilJsonKeys.resultText); + } + + if(g.json.cmd.commandStr){ + tmp = json_new_string(g.json.cmd.commandStr); + }else{ + tmp = json_response_command_path(); + } + if(!tmp){ + tmp = json_new_string("???"); + } + SET("command"); + + tmp = json_getenv(FossilJsonKeys.requestId); + if( tmp ) cson_object_set( o, FossilJsonKeys.requestId, tmp ); + + if(0){/* these are only intended for my own testing...*/ + if(g.json.cmd.v){ + tmp = g.json.cmd.v; + SET("$commandPath"); + } + if(g.json.param.v){ + tmp = g.json.param.v; + SET("$params"); + } + if(0){/*Only for debugging, add some info to the response.*/ + tmp = cson_value_new_integer( g.json.cmd.offset ); + SET("cmd.offset"); + tmp = cson_value_new_bool( g.isHTTP ); + SET("isCGI"); + } + } + + if(fossil_timer_is_active(g.json.timerId)){ + /* This is, philosophically speaking, not quite the right place + for ending the timer, but this is the one function which all of + the JSON exit paths use (and they call it after processing, + just before they end). + */ + sqlite3_uint64 span = fossil_timer_stop(g.json.timerId); + /* I'm actually seeing sub-uSec runtimes in some tests, but a time of + 0 is "just kinda wrong". + */ + cson_object_set(o,"procTimeUs", cson_value_new_integer((cson_int_t)span)); + span /= 1000/*for milliseconds */; + cson_object_set(o,"procTimeMs", cson_value_new_integer((cson_int_t)span)); + assert(!fossil_timer_is_active(g.json.timerId)); + g.json.timerId = -1; + } + if(g.json.warnings){ + tmp = cson_array_value(g.json.warnings); + SET("warnings"); + } + + /* Only add the payload to SUCCESS responses. Else delete it. */ + if( NULL != payload ){ + if( resultCode ){ + cson_value_free(payload); + payload = NULL; + }else{ + tmp = payload; + SET(FossilJsonKeys.payload); + } + } + + if((g.perm.Admin||g.perm.Setup) + && json_find_option_bool("debugFossilG","json-debug-g",NULL,0) + ){ + tmp = json_g_to_json(); + SET("g"); + } + +#undef SET + goto ok; + cleanup: + cson_value_free(v); + v = NULL; + ok: + return v; +} + +/* +** Outputs a JSON error response to either the cgi_xxx() family of +** buffers (in CGI/server mode) or stdout (in CLI mode). If rc is 0 +** then g.json.resultCode is used. If that is also 0 then the "Unknown +** Error" code is used. +** +** If g.isHTTP then the generated JSON error response object replaces +** any currently buffered page output. Because the output goes via +** the cgi_xxx() family of functions, this function inherits any +** compression which fossil does for its output. +** +** If alsoOutput is true AND g.isHTTP then cgi_reply() is called to +** flush the output (and headers). Generally only do this if you are +** about to call exit(). +** +** If !g.isHTTP then alsoOutput is ignored and all output is sent to +** stdout immediately. +** +** For generating the resultText property: if msg is not NULL then it +** is used as-is. If it is NULL then g.zErrMsg is checked, and if that +** is NULL then json_err_cstr(code) is used. +*/ +void json_err( int code, char const * msg, int alsoOutput ){ + int rc = code ? code : (g.json.resultCode + ? g.json.resultCode + : FSL_JSON_E_UNKNOWN); + cson_value * resp = NULL; + if(g.json.isJsonMode==0) return; + rc = json_dumbdown_rc(rc); + if( rc && !msg ){ + msg = g.zErrMsg; + if(!msg){ + msg = json_err_cstr(rc); + } + } + resp = json_create_response(rc, msg, NULL); + if(!resp){ + /* about the only error case here is out-of-memory. DO NOT + call fossil_panic() or fossil_fatal() here because those + allocate. + */ + fprintf(stderr, "%s: Fatal error: could not allocate " + "response object.\n", g.argv[0]); + fossil_exit(1); + } + if( g.isHTTP ){ + if(alsoOutput){ + json_send_response(resp); + }else{ + /* almost a duplicate of json_send_response() :( */ + cgi_reset_content(); + if( g.json.jsonp ){ + cgi_printf("%s(",g.json.jsonp); + } + cson_output( resp, cson_data_dest_cgi, NULL, &g.json.outOpt ); + if( g.json.jsonp ){ + cgi_append_content(")",1); + } + } + }else{ + json_send_response(resp); + } + cson_value_free(resp); +} + +/* +** Sets g.json.resultCode and g.zErrMsg, but does not report the error +** via json_err(). Returns the code passed to it. +** +** code must be in the inclusive range 1000..9999. +*/ +int json_set_err( int code, char const * fmt, ... ){ + assert( (code>=1000) && (code<=9999) ); + free(g.zErrMsg); + g.json.resultCode = code; + if(!fmt || !*fmt){ + g.zErrMsg = mprintf("%s", json_err_cstr(code)); + }else{ + va_list vargs; + char * msg; + va_start(vargs,fmt); + msg = vmprintf(fmt, vargs); + va_end(vargs); + g.zErrMsg = msg; + } + return code; +} + +/* +** Iterates through a prepared SELECT statement and converts each row +** to a JSON object. If pTgt is not NULL then this function will +** append the results to pTgt and return cson_array_value(pTgt). If +** pTgt is NULL then a new Array object is created and returned (owned +** by the caller). Each row of pStmt is converted to an Object and +** appended to the array. If the result set has no rows AND pTgt is +** NULL then NULL (not an empty array) is returned. +*/ +cson_value * json_stmt_to_array_of_obj(Stmt *pStmt, + cson_array * pTgt){ + cson_array * a = pTgt; + char const * warnMsg = NULL; + cson_value * colNamesV = NULL; + cson_array * colNames = NULL; + while( (SQLITE_ROW==db_step(pStmt)) ){ + cson_value * row = NULL; + if(!a){ + a = cson_new_array(); + assert(NULL!=a); + } + if(!colNames){ + colNamesV = cson_sqlite3_column_names(pStmt->pStmt); + assert(NULL != colNamesV); + /*Why? cson_value_add_reference(colNamesV) avoids an ownership problem*/; + colNames = cson_value_get_array(colNamesV); + assert(NULL != colNames); + } + row = cson_sqlite3_row_to_object2(pStmt->pStmt, colNames); + if(!row && !warnMsg){ + warnMsg = "Could not convert at least one result row to JSON."; + continue; + } + if( 0 != cson_array_append(a, row) ){ + cson_value_free(row); + if(pTgt != a) { + cson_free_array(a); + } + assert( 0 && "Alloc error."); + return NULL; + } + } + cson_value_free(colNamesV); + if(warnMsg){ + json_warn( FSL_JSON_W_ROW_TO_JSON_FAILED, "%s", warnMsg ); + } + return cson_array_value(a); +} + +/* +** Works just like json_stmt_to_array_of_obj(), but each row in the +** result set is represented as an Array of values instead of an +** Object (key/value pairs). If pTgt is NULL and the statement +** has no results then NULL is returned, not an empty array. +*/ +cson_value * json_stmt_to_array_of_array(Stmt *pStmt, + cson_array * pTgt){ + cson_array * a = pTgt; + while( (SQLITE_ROW==db_step(pStmt)) ){ + cson_value * row = NULL; + if(!a){ + a = cson_new_array(); + assert(NULL!=a); + } + row = cson_sqlite3_row_to_array(pStmt->pStmt); + cson_array_append(a, row); + } + return cson_array_value(a); +} + +cson_value * json_stmt_to_array_of_values(Stmt *pStmt, + int resultColumn, + cson_array * pTgt){ + cson_array * a = pTgt; + while( (SQLITE_ROW==db_step(pStmt)) ){ + cson_value * row = cson_sqlite3_column_to_value(pStmt->pStmt, + resultColumn); + if(row){ + if(!a){ + a = cson_new_array(); + assert(NULL!=a); + } + cson_array_append(a, row); + } + } + return cson_array_value(a); +} + +/* +** Executes the given SQL and runs it through +** json_stmt_to_array_of_obj(), returning the result of that +** function. If resetBlob is true then blob_reset(pSql) is called +** after preparing the query. +** +** pTgt has the same semantics as described for +** json_stmt_to_array_of_obj(). +** +** FIXME: change this to take a (char const *) instead of a blob, +** to simplify the trivial use-cases (which don't need a Blob). +*/ +cson_value * json_sql_to_array_of_obj(Blob * pSql, cson_array * pTgt, + int resetBlob){ + Stmt q = empty_Stmt; + cson_value * pay = NULL; + assert( blob_size(pSql) > 0 ); + db_prepare(&q, "%s", blob_str(pSql) /*safe-for-%s*/); + if(resetBlob){ + blob_reset(pSql); + } + pay = json_stmt_to_array_of_obj(&q, pTgt); + db_finalize(&q); + return pay; + +} + +/* +** If the given COMMIT rid has any tags associated with it, this +** function returns a JSON Array containing the tag names (owned by +** the caller), else it returns NULL. +** +** See info_tags_of_checkin() for more details (this is simply a JSON +** wrapper for that function). +** +** If there are no tags then this function returns NULL, not an empty +** Array. +*/ +cson_value * json_tags_for_checkin_rid(int rid, int propagatingOnly){ + cson_value * v = NULL; + char * tags = info_tags_of_checkin(rid, propagatingOnly); + if(tags){ + if(*tags){ + v = json_string_split2(tags,',',0); + } + free(tags); + } + return v; +} + +/* + ** Returns a "new" value representing the boolean value of zVal + ** (false if zVal is NULL). Note that cson does not really allocate + ** any memory for boolean values, but they "should" (for reasons of + ** style and philosophy) be cleaned up like any other values (but + ** it's a no-op for bools). + */ +cson_value * json_value_to_bool(cson_value const * zVal){ + return cson_value_get_bool(zVal) + ? cson_value_true() + : cson_value_false(); +} + +/* +** Impl of /json/resultCodes +** +*/ +cson_value * json_page_resultCodes(){ + cson_array * list = cson_new_array(); + cson_object * obj = NULL; + cson_string * kRC; + cson_string * kSymbol; + cson_string * kNumber; + cson_string * kDesc; + cson_array_reserve( list, 35 ); + kRC = cson_new_string("resultCode",10); + kSymbol = cson_new_string("cSymbol",7); + kNumber = cson_new_string("number",6); + kDesc = cson_new_string("description",11); +#define C(K) obj = cson_new_object(); \ + cson_object_set_s(obj, kRC, json_new_string(json_rc_cstr(FSL_JSON_E_##K)) ); \ + cson_object_set_s(obj, kSymbol, json_new_string("FSL_JSON_E_"#K) ); \ + cson_object_set_s(obj, kNumber, cson_value_new_integer(FSL_JSON_E_##K) ); \ + cson_object_set_s(obj, kDesc, json_new_string(json_err_cstr(FSL_JSON_E_##K))); \ + cson_array_append( list, cson_object_value(obj) ); obj = NULL; + + C(GENERIC); + C(INVALID_REQUEST); + C(UNKNOWN_COMMAND); + C(UNKNOWN); + C(TIMEOUT); + C(ASSERT); + C(ALLOC); + C(NYI); + C(PANIC); + C(MANIFEST_READ_FAILED); + C(FILE_OPEN_FAILED); + + C(AUTH); + C(MISSING_AUTH); + C(DENIED); + C(WRONG_MODE); + C(LOGIN_FAILED); + C(LOGIN_FAILED_NOSEED); + C(LOGIN_FAILED_NONAME); + C(LOGIN_FAILED_NOPW); + C(LOGIN_FAILED_NOTFOUND); + + C(USAGE); + C(INVALID_ARGS); + C(MISSING_ARGS); + C(AMBIGUOUS_UUID); + C(UNRESOLVED_UUID); + C(RESOURCE_ALREADY_EXISTS); + C(RESOURCE_NOT_FOUND); + + C(DB); + C(STMT_PREP); + C(STMT_BIND); + C(STMT_EXEC); + C(DB_LOCKED); + C(DB_NEEDS_REBUILD); + C(DB_NOT_FOUND); + C(DB_NOT_VALID); +#undef C + return cson_array_value(list); +} + + +/* +** /json/version implementation. +** +** Returns the payload object (owned by the caller). +*/ +cson_value * json_page_version(){ + cson_value * jval = NULL; + cson_object * jobj = NULL; + jval = cson_value_new_object(); + jobj = cson_value_get_object(jval); +#define FSET(X,K) cson_object_set( jobj, K, cson_value_new_string(X,strlen(X))) + FSET(MANIFEST_UUID,"manifestUuid"); + FSET(MANIFEST_VERSION,"manifestVersion"); + FSET(MANIFEST_DATE,"manifestDate"); + FSET(MANIFEST_YEAR,"manifestYear"); + FSET(RELEASE_VERSION,"releaseVersion"); + cson_object_set( jobj, "releaseVersionNumber", + cson_value_new_integer(RELEASE_VERSION_NUMBER) ); + cson_object_set( jobj, "resultCodeParanoiaLevel", + cson_value_new_integer(g.json.errorDetailParanoia) ); + FSET(FOSSIL_JSON_API_VERSION, "jsonApiVersion" ); +#undef FSET + return jval; +} + + +/* +** Returns the current user's capabilities string as a String value. +** Returned value is owned by the caller, and will only be NULL if +** g.userUid is invalid or an out of memory error. Or, it turns out, +** in CLI mode (where there is no logged-in user). +*/ +cson_value * json_cap_value(){ + if(g.userUid<=0){ + return NULL; + }else{ + Stmt q = empty_Stmt; + cson_value * val = NULL; + db_prepare(&q, "SELECT cap FROM user WHERE uid=%d", g.userUid); + if( db_step(&q)==SQLITE_ROW ){ + char const * str = (char const *)sqlite3_column_text(q.pStmt,0); + if( str ){ + val = json_new_string(str); + } + } + db_finalize(&q); + return val; + } +} + +/* +** Implementation for /json/cap +** +** Returned object contains details about the "capabilities" of the +** current user (what he may/may not do). +** +** This is primarily intended for debuggering, but may have +** a use in client code. (?) +*/ +cson_value * json_page_cap(){ + cson_value * payload = cson_value_new_object(); + cson_value * sub = cson_value_new_object(); + Stmt q; + cson_object * obj = cson_value_get_object(payload); + db_prepare(&q, "SELECT login, cap FROM user WHERE uid=%d", g.userUid); + if( db_step(&q)==SQLITE_ROW ){ + /* reminder: we don't use g.zLogin because it's 0 for the guest + user and the HTML UI appears to currently allow the name to be + changed (but doing so would break other code). */ + char const * str = (char const *)sqlite3_column_text(q.pStmt,0); + if( str ){ + cson_object_set( obj, "name", + cson_value_new_string(str,strlen(str)) ); + } + str = (char const *)sqlite3_column_text(q.pStmt,1); + if( str ){ + cson_object_set( obj, "capabilities", + cson_value_new_string(str,strlen(str)) ); + } + } + db_finalize(&q); + cson_object_set( obj, "permissionFlags", sub ); + obj = cson_value_get_object(sub); + +#define ADD(X,K) cson_object_set(obj, K, cson_value_new_bool(g.perm.X)) + ADD(Setup,"setup"); + ADD(Admin,"admin"); + ADD(Password,"password"); + ADD(Query,"query"); /* don't think this one is actually used */ + ADD(Write,"checkin"); + ADD(Read,"checkout"); + ADD(Hyperlink,"history"); + ADD(Clone,"clone"); + ADD(RdWiki,"readWiki"); + ADD(NewWiki,"createWiki"); + ADD(ApndWiki,"appendWiki"); + ADD(WrWiki,"editWiki"); + ADD(ModWiki,"moderateWiki"); + ADD(RdTkt,"readTicket"); + ADD(NewTkt,"createTicket"); + ADD(ApndTkt,"appendTicket"); + ADD(WrTkt,"editTicket"); + ADD(ModTkt,"moderateTicket"); + ADD(Attach,"attachFile"); + ADD(TktFmt,"createTicketReport"); + ADD(RdAddr,"readPrivate"); + ADD(Zip,"zip"); + ADD(Private,"xferPrivate"); + ADD(WrUnver,"writeUnversioned"); + ADD(RdForum,"readForum"); + ADD(WrForum,"writeForum"); + ADD(WrTForum,"writeTrustedForum"); + ADD(ModForum,"moderateForum"); + ADD(AdminForum,"adminForum"); + ADD(EmailAlert,"emailAlert"); + ADD(Announce,"announce"); + ADD(Debug,"debug"); + ADD(Chat,"chat"); +#undef ADD + return payload; +} + +/* +** Implementation of the /json/stat page/command. +** +*/ +cson_value * json_page_stat(){ + i64 t, fsize; + int n, m; + int full; + enum { BufLen = 1000 }; + char zBuf[BufLen]; + cson_value * jv = NULL; + cson_object * jo = NULL; + cson_value * jv2 = NULL; + cson_object * jo2 = NULL; + char * zTmp = NULL; + if( !g.perm.Read ){ + json_set_err(FSL_JSON_E_DENIED, + "Requires 'o' permissions."); + return NULL; + } + full = json_find_option_bool("full",NULL,"f", + json_find_option_bool("verbose",NULL,"v",0)); +#define SETBUF(O,K) cson_object_set(O, K, cson_value_new_string(zBuf, strlen(zBuf))); + + jv = cson_value_new_object(); + jo = cson_value_get_object(jv); + + zTmp = db_get("project-name",NULL); + cson_object_set(jo, "projectName", json_new_string(zTmp)); + free(zTmp); + zTmp = db_get("project-description",NULL); + cson_object_set(jo, "projectDescription", json_new_string(zTmp)); + free(zTmp); + zTmp = NULL; + fsize = file_size(g.zRepositoryName, ExtFILE); + cson_object_set(jo, "repositorySize", + cson_value_new_integer((cson_int_t)fsize)); + + if(full){ + n = db_int(0, "SELECT count(*) FROM blob"); + m = db_int(0, "SELECT count(*) FROM delta"); + cson_object_set(jo, "blobCount", cson_value_new_integer((cson_int_t)n)); + cson_object_set(jo, "deltaCount", cson_value_new_integer((cson_int_t)m)); + if( n>0 ){ + int a, b; + Stmt q; + db_prepare(&q, "SELECT total(size), avg(size), max(size)" + " FROM blob WHERE size>0"); + db_step(&q); + t = db_column_int64(&q, 0); + cson_object_set(jo, "uncompressedArtifactSize", + cson_value_new_integer((cson_int_t)t)); + cson_object_set(jo, "averageArtifactSize", + cson_value_new_integer((cson_int_t)db_column_int(&q, 1))); + cson_object_set(jo, "maxArtifactSize", + cson_value_new_integer((cson_int_t)db_column_int(&q, 2))); + db_finalize(&q); + if( t/fsize < 5 ){ + b = 10; + fsize /= 10; + }else{ + b = 1; + } + a = t/fsize; + sqlite3_snprintf(BufLen,zBuf, "%d:%d", a, b); + SETBUF(jo, "compressionRatio"); + } + n = db_int(0, "SELECT count(distinct mid) FROM mlink /*scan*/"); + cson_object_set(jo, "checkinCount", cson_value_new_integer((cson_int_t)n)); + n = db_int(0, "SELECT count(*) FROM filename /*scan*/"); + cson_object_set(jo, "fileCount", cson_value_new_integer((cson_int_t)n)); + n = db_int(0, "SELECT count(*) FROM tag /*scan*/" + " WHERE +tagname GLOB 'wiki-*'"); + cson_object_set(jo, "wikiPageCount", cson_value_new_integer((cson_int_t)n)); + n = db_int(0, "SELECT count(*) FROM tag /*scan*/" + " WHERE +tagname GLOB 'tkt-*'"); + cson_object_set(jo, "ticketCount", cson_value_new_integer((cson_int_t)n)); + }/*full*/ + n = db_int(0, "SELECT julianday('now') - (SELECT min(mtime) FROM event)" + " + 0.99"); + cson_object_set(jo, "ageDays", cson_value_new_integer((cson_int_t)n)); + cson_object_set(jo, "ageYears", cson_value_new_double(n/365.2425)); + sqlite3_snprintf(BufLen, zBuf, db_get("project-code","")); + SETBUF(jo, "projectCode"); + cson_object_set(jo, "compiler", cson_value_new_string(COMPILER_NAME, strlen(COMPILER_NAME))); + + jv2 = cson_value_new_object(); + jo2 = cson_value_get_object(jv2); + cson_object_set(jo, "sqlite", jv2); + sqlite3_snprintf(BufLen, zBuf, "%.19s [%.10s] (%s)", + sqlite3_sourceid(), &sqlite3_sourceid()[20], sqlite3_libversion()); + SETBUF(jo2, "version"); + cson_object_set(jo2, "pageCount", cson_value_new_integer((cson_int_t)db_int(0, "PRAGMA repository.page_count"))); + cson_object_set(jo2, "pageSize", cson_value_new_integer((cson_int_t)db_int(0, "PRAGMA repository.page_size"))); + cson_object_set(jo2, "freeList", cson_value_new_integer((cson_int_t)db_int(0, "PRAGMA repository.freelist_count"))); + sqlite3_snprintf(BufLen, zBuf, "%s", db_text(0, "PRAGMA repository.encoding")); + SETBUF(jo2, "encoding"); + sqlite3_snprintf(BufLen, zBuf, "%s", db_text(0, "PRAGMA repository.journal_mode")); + cson_object_set(jo2, "journalMode", *zBuf ? cson_value_new_string(zBuf, strlen(zBuf)) : cson_value_null()); + return jv; +#undef SETBUF +} + + + + +/* +** Creates a comma-separated list of command names +** taken from zPages. zPages must be an array of objects +** whose final entry MUST have a NULL name value or results +** are undefined. +** +** The list is appended to pOut. The number of items (not bytes) +** appended are returned. If filterByMode is non-0 then the result +** list will contain only commands which are able to run in the +** current run mode (CLI vs. HTTP). +*/ +static int json_pagedefs_to_string(JsonPageDef const * zPages, + Blob * pOut, int filterByMode){ + int i = 0; + for( ; zPages->name; ++zPages, ++i ){ + if(filterByMode){ + if(g.isHTTP && zPages->runMode < 0) continue; + else if(zPages->runMode > 0) continue; + } + blob_append(pOut, zPages->name, -1); + if((zPages+1)->name){ + blob_append(pOut, ", ",2); + } + } + return i; +} + +/* +** Creates an error message using zErrPrefix and the given array of +** JSON command definitions, and sets the g.json error state to +** reflect FSL_JSON_E_MISSING_ARGS. If zErrPrefix is NULL then +** some default is used (e.g. "Try one of: "). If it is "" then +** no prefix is used. +** +** The intention is to provide the user (via the response.resultText) +** a list of available commands/subcommands. +** +*/ +void json_dispatch_missing_args_err( JsonPageDef const * pCommands, + char const * zErrPrefix ){ + Blob cmdNames = empty_blob; + blob_init(&cmdNames,NULL,0); + if( !zErrPrefix ) { + zErrPrefix = "Try one of: "; + } + blob_append( &cmdNames, zErrPrefix, strlen(zErrPrefix) ); + json_pagedefs_to_string(pCommands, &cmdNames, 1); + json_set_err(FSL_JSON_E_MISSING_ARGS, "%s", + blob_str(&cmdNames)); + blob_reset(&cmdNames); +} + +cson_value * json_page_dispatch_helper(JsonPageDef const * pages){ + JsonPageDef const * def; + char const * cmd = json_command_arg(1+g.json.dispatchDepth); + assert( NULL != pages ); + if( ! cmd ){ + json_dispatch_missing_args_err(pages, + "No subcommand specified. " + "Try one of: "); + return NULL; + } + def = json_handler_for_name( cmd, pages ); + if(!def){ + json_set_err(FSL_JSON_E_UNKNOWN_COMMAND, + "Unknown subcommand: %s", cmd); + return NULL; + } + else{ + ++g.json.dispatchDepth; + return (*def->func)(); + } +} + + +/* +** Impl of /json/rebuild. Requires admin privileges. +*/ +static cson_value * json_page_rebuild(){ + if( !g.perm.Admin ){ + json_set_err(FSL_JSON_E_DENIED,"Requires 'a' privileges."); + return NULL; + }else{ + /* Reminder: the db_xxx() ops "should" fail via the fossil core + error handlers, which will cause a JSON error and exit(). i.e. we + don't handle the errors here. TODO: confirm that all these db + routine fail gracefully in JSON mode. + + On large repos (e.g. fossil's) this operation is likely to take + longer than the client timeout, which will cause it to fail (but + it's sqlite3, so it'll fail gracefully). + */ + db_close(1); + db_open_repository(g.zRepositoryName); + db_begin_transaction(); + rebuild_db(0, 0, 0); + db_end_transaction(0); + return NULL; + } +} + +/* +** Impl of /json/g. Requires admin/setup rights. +*/ +static cson_value * json_page_g(){ + if(!g.perm.Admin || !g.perm.Setup){ + json_set_err(FSL_JSON_E_DENIED, + "Requires 'a' or 's' privileges."); + return NULL; + } + return json_g_to_json(); +} + +/* Impl in json_login.c. */ +cson_value * json_page_anon_password(); +/* Impl in json_artifact.c. */ +cson_value * json_page_artifact(); +/* Impl in json_branch.c. */ +cson_value * json_page_branch(); +/* Impl in json_diff.c. */ +cson_value * json_page_diff(); +/* Impl in json_dir.c. */ +cson_value * json_page_dir(); +/* Impl in json_login.c. */ +cson_value * json_page_login(); +/* Impl in json_login.c. */ +cson_value * json_page_logout(); +/* Impl in json_query.c. */ +cson_value * json_page_query(); +/* Impl in json_report.c. */ +cson_value * json_page_report(); +/* Impl in json_tag.c. */ +cson_value * json_page_tag(); +/* Impl in json_user.c. */ +cson_value * json_page_user(); +/* Impl in json_config.c. */ +cson_value * json_page_config(); +/* Impl in json_finfo.c. */ +cson_value * json_page_finfo(); +/* Impl in json_status.c. */ +cson_value * json_page_status(); + +/* +** Mapping of names to JSON pages/commands. Each name is a subpath of +** /json (in CGI mode) or a subcommand of the json command in CLI mode +*/ +static const JsonPageDef JsonPageDefs[] = { +/* please keep alphabetically sorted (case-insensitive) for maintenance reasons. */ +{"anonymousPassword", json_page_anon_password, 0}, +{"artifact", json_page_artifact, 0}, +{"branch", json_page_branch,0}, +{"cap", json_page_cap, 0}, +{"config", json_page_config, 0 }, +{"diff", json_page_diff, 0}, +{"dir", json_page_dir, 0}, +{"finfo", json_page_finfo, 0}, +{"g", json_page_g, 0}, +{"HAI",json_page_version,0}, +{"login",json_page_login,0}, +{"logout",json_page_logout,0}, +{"query",json_page_query,0}, +{"rebuild",json_page_rebuild,0}, +{"report", json_page_report, 0}, +{"resultCodes", json_page_resultCodes,0}, +{"stat",json_page_stat,0}, +{"status", json_page_status, 0}, +{"tag", json_page_tag,0}, +/*{"ticket", json_page_nyi,0},*/ +{"timeline", json_page_timeline,0}, +{"user",json_page_user,0}, +{"version",json_page_version,0}, +{"whoami",json_page_whoami,0}, +{"wiki",json_page_wiki,0}, +/* Last entry MUST have a NULL name. */ +{NULL,NULL,0} +}; + +/* +** Internal helper for json_cmd_top() and json_page_top(). +** +** Searches JsonPageDefs for a command with the given name. If found, +** it is used to generate and output a JSON response. If not found, it +** generates a JSON-style error response. Returns 0 on success, non-0 +** on error. On error it will set g.json's error state. +*/ +static int json_dispatch_root_command( char const * zCommand ){ + int rc = 0; + cson_value * payload = NULL; + JsonPageDef const * pageDef = NULL; + pageDef = json_handler_for_name(zCommand,&JsonPageDefs[0]); + if( ! pageDef ){ + rc = FSL_JSON_E_UNKNOWN_COMMAND; + json_set_err( rc, "Unknown command: %s", zCommand ); + }else if( pageDef->runMode < 0 /*CLI only*/){ + rc = FSL_JSON_E_WRONG_MODE; + }else if( (g.isHTTP && (pageDef->runMode < 0 /*CLI only*/)) + || + (!g.isHTTP && (pageDef->runMode > 0 /*HTTP only*/)) + ){ + rc = FSL_JSON_E_WRONG_MODE; + } + else{ + rc = 0; + g.json.dispatchDepth = 1; + payload = (*pageDef->func)(); + } + payload = json_create_response(rc, NULL, payload); + json_send_response(payload); + cson_value_free(payload); + return rc; +} + +#ifdef FOSSIL_ENABLE_JSON +/* dupe ifdef needed for mkindex */ +/* +** WEBPAGE: json +** +** Pages under /json/... must be entered into JsonPageDefs. +** This function dispatches them, and is the HTTP equivalent of +** json_cmd_top(). +*/ +void json_page_top(void){ + char const * zCommand; + assert(g.json.gc.a && "json_bootstrap_early() was not called!"); + assert(g.json.cmd.a && "json_bootstrap_late() was not called!"); + zCommand = json_command_arg(1); + if(!zCommand || !*zCommand){ + json_dispatch_missing_args_err( JsonPageDefs, + "No command (sub-path) specified." + " Try one of: "); + return; + } + json_dispatch_root_command( zCommand ); +} +#endif /* FOSSIL_ENABLE_JSON for mkindex */ + +#ifdef FOSSIL_ENABLE_JSON +/* dupe ifdef needed for mkindex */ +/* +** This function dispatches json commands and is the CLI equivalent of +** json_page_top(). +** +** COMMAND: json +** +** Usage: %fossil json SUBCOMMAND ?OPTIONS? +** +** In CLI mode, the -R REPO common option is supported. Due to limitations +** in the argument dispatching code, any -FLAGS must come after the final +** sub- (or subsub-) command. +** +** The -json-input FILE option can be used to read JSON data and process +** it like the HTTP interface would. For example: +** +** %fossil json -json-input my.json +** +** The commands include: +** +** anonymousPassword +** artifact +** branch +** cap +** config +** diff +** dir +** g +** login +** logout +** query +** rebuild +** report +** resultCodes +** stat +** tag +** timeline +** user +** version (alias: HAI) +** whoami +** wiki +** +** Run '%fossil json' without any subcommand to see the full list (but be +** aware that some listed might not yet be fully implemented). +** +*/ +void json_cmd_top(void){ + char const * cmd = NULL; + int rc = 0; + memset( &g.perm, 0xff, sizeof(g.perm) ) + /* In CLI mode fossil does not use permissions + and they all default to false. We enable them + here because (A) fossil doesn't use them in local + mode but (B) having them set gives us one less + difference in the CLI/CGI/Server-mode JSON + handling. + */ + ; + json_bootstrap_early(); + json_bootstrap_late(); + if( 2 > cson_array_length_get(g.json.cmd.a) ){ + goto usage; + } +#if 0 + json_warn(FSL_JSON_W_ROW_TO_JSON_FAILED, "Just testing."); + json_warn(FSL_JSON_W_ROW_TO_JSON_FAILED, "Just testing again."); +#endif + cmd = json_command_arg(1); + if( !cmd || !*cmd ){ + goto usage; + } + rc = json_dispatch_root_command( cmd ); + if(0 != rc){ + /* FIXME: we need a way of passing this error back + up to the routine which called this callback. + e.g. add g.errCode. + */ + fossil_exit(1); + } + return; + usage: + { + cson_value * payload; + json_dispatch_missing_args_err( JsonPageDefs, + "No subcommand specified." + " Try one of: "); + payload = json_create_response(0, NULL, NULL); + json_send_response(payload); + cson_value_free(payload); + fossil_exit(1); + } +} +#endif /* FOSSIL_ENABLE_JSON for mkindex */ + +#endif /* FOSSIL_ENABLE_JSON */ ADDED src/json_artifact.c Index: src/json_artifact.c ================================================================== --- /dev/null +++ src/json_artifact.c @@ -0,0 +1,511 @@ +#ifdef FOSSIL_ENABLE_JSON +/* +** Copyright (c) 2011 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/ +** +*/ +#include "VERSION.h" +#include "config.h" +#include "json_artifact.h" + +#if INTERFACE +#include "json_detail.h" +#endif + +/* +** Internal callback for /json/artifact handlers. rid refers to +** the rid of a given type of artifact, and each callback is +** specialized to return a JSON form of one type of artifact. +** +** Implementations may assert() that rid refers to requested artifact +** type, since mismatches in the artifact types come from +** json_page_artifact() as opposed to client data. +** +** The pParent parameter points to the response payload object. It +** _may_ be used to populate "top-level" information in the response +** payload, but normally this is neither necessary nor desired. +*/ +typedef cson_value * (*artifact_f)( cson_object * pParent, int rid ); + +/* +** Internal per-artifact-type dispatching helper. +*/ +typedef struct ArtifactDispatchEntry { + /** + Artifact type name, e.g. "checkin", "ticket", "wiki". + */ + char const * name; + + /** + JSON construction callback. Creates the contents for the + payload.artifact property of /json/artifact responses. + */ + artifact_f func; +} ArtifactDispatchEntry; + + +/* +** Generates a JSON Array reference holding the parent UUIDs (as strings). +** If it finds no matches then it returns NULL (OOM is a fatal error). +** +** Returned value is NULL or an Array owned by the caller. +*/ +cson_value * json_parent_uuids_for_ci( int rid ){ + Stmt q = empty_Stmt; + cson_array * pParents = NULL; + db_prepare( &q, + "SELECT uuid FROM plink, blob" + " WHERE plink.cid=%d AND blob.rid=plink.pid" + " ORDER BY plink.isprim DESC", + rid ); + while( SQLITE_ROW==db_step(&q) ){ + if(!pParents) { + pParents = cson_new_array(); + } + cson_array_append( pParents, cson_sqlite3_column_to_value( q.pStmt, 0 ) ); + } + db_finalize(&q); + return cson_array_value(pParents); +} + +/* +** Generates an artifact Object for the given rid, +** which must refer to a Check-in. +** +** Returned value is NULL or an Object owned by the caller. +*/ +cson_value * json_artifact_for_ci( int rid, char showFiles ){ + cson_value * v = NULL; + Stmt q = empty_Stmt; + static cson_value * eventTypeLabel = NULL; + if(!eventTypeLabel){ + eventTypeLabel = json_new_string("checkin"); + json_gc_add("$EVENT_TYPE_LABEL(commit)", eventTypeLabel); + } + + db_prepare(&q, + "SELECT b.uuid, " + " cast(strftime('%%s',e.mtime) as int), " + " strftime('%%s',e.omtime)," + " e.user, " + " e.comment" + " FROM blob b, event e" + " WHERE b.rid=%d" + " AND e.objid=%d", + rid, rid + ); + if( db_step(&q)==SQLITE_ROW ){ + cson_object * o; + cson_value * tmpV = NULL; + const char *zUuid = db_column_text(&q, 0); + const char *zUser; + const char *zComment; + char * zEUser, * zEComment; + i64 mtime, omtime; + v = cson_value_new_object(); + o = cson_value_get_object(v); +#define SET(K,V) cson_object_set(o,(K), (V)) + SET("type", eventTypeLabel ); + SET("uuid",json_new_string(zUuid)); + SET("isLeaf", cson_value_new_bool(is_a_leaf(rid))); + + mtime = db_column_int64(&q,1); + SET("timestamp",json_new_int(mtime)); + omtime = db_column_int64(&q,2); + if(omtime && (omtime!=mtime)){ + SET("originTime",json_new_int(omtime)); + } + + zUser = db_column_text(&q,3); + zEUser = db_text(0, + "SELECT value FROM tagxref WHERE tagid=%d AND rid=%d", + TAG_USER, rid); + if(zEUser){ + SET("user", json_new_string(zEUser)); + if(0!=fossil_strcmp(zEUser,zUser)){ + SET("originUser",json_new_string(zUser)); + } + free(zEUser); + }else{ + SET("user",json_new_string(zUser)); + } + + zComment = db_column_text(&q,4); + zEComment = db_text(0, + "SELECT value FROM tagxref WHERE tagid=%d AND rid=%d", + TAG_COMMENT, rid); + if(zEComment){ + SET("comment",json_new_string(zEComment)); + if(0 != fossil_strcmp(zEComment,zComment)){ + SET("originComment", json_new_string(zComment)); + } + free(zEComment); + }else{ + SET("comment",json_new_string(zComment)); + } + + tmpV = json_parent_uuids_for_ci(rid); + if(tmpV){ + SET("parents", tmpV); + } + + tmpV = json_tags_for_checkin_rid(rid,0); + if(tmpV){ + SET("tags",tmpV); + } + + if( showFiles ){ + tmpV = json_get_changed_files(rid, 1); + if(tmpV){ + SET("files",tmpV); + } + } + +#undef SET + } + db_finalize(&q); + return v; +} + +/* +** Very incomplete/incorrect impl of /json/artifact/TICKET_ID. +*/ +cson_value * json_artifact_ticket( cson_object * zParent, int rid ){ + cson_object * pay = NULL; + Manifest *pTktChng = NULL; + static cson_value * eventTypeLabel = NULL; + if(! g.perm.RdTkt ){ + g.json.resultCode = FSL_JSON_E_DENIED; + return NULL; + } + if(!eventTypeLabel){ + eventTypeLabel = json_new_string("ticket"); + json_gc_add("$EVENT_TYPE_LABEL(ticket)", eventTypeLabel); + } + + pTktChng = manifest_get(rid, CFTYPE_TICKET, 0); + if( pTktChng==0 ){ + g.json.resultCode = FSL_JSON_E_MANIFEST_READ_FAILED; + return NULL; + } + pay = cson_new_object(); + cson_object_set(pay, "eventType", eventTypeLabel ); + cson_object_set(pay, "uuid", json_new_string(pTktChng->zTicketUuid)); + cson_object_set(pay, "user", json_new_string(pTktChng->zUser)); + cson_object_set(pay, "timestamp", json_julian_to_timestamp(pTktChng->rDate)); + manifest_destroy(pTktChng); + return cson_object_value(pay); +} + +/* +** Sub-impl of /json/artifact for check-ins. +*/ +static cson_value * json_artifact_ci( cson_object * zParent, int rid ){ + if(!g.perm.Read){ + json_set_err( FSL_JSON_E_DENIED, "Viewing check-ins requires 'o' privileges." ); + return NULL; + }else{ + cson_value * artV = json_artifact_for_ci(rid, 1); + cson_object * art = cson_value_get_object(artV); + if(art){ + cson_object_merge( zParent, art, CSON_MERGE_REPLACE ); + cson_free_object(art); + } + return cson_object_value(zParent); + } +} + +/* +** Internal mapping of /json/artifact/FOO commands/callbacks. +*/ +static ArtifactDispatchEntry ArtifactDispatchList[] = { +{"checkin", json_artifact_ci}, +{"file", json_artifact_file}, +/*{"tag", NULL}, //impl missing */ +/*{"technote", NULL}, //impl missing */ +{"ticket", json_artifact_ticket}, +{"wiki", json_artifact_wiki}, +/* Final entry MUST have a NULL name. */ +{NULL,NULL} +}; + +/* +** Internal helper which returns: +** +** If the "format" (CLI: -f) flag is set function returns the same as +** json_wiki_get_content_format_flag(), else it returns true (non-0) +** if either the includeContent (HTTP) or -content|-c boolean flags +** (CLI) are set. +*/ +static int json_artifact_get_content_format_flag(){ + enum { MagicValue = -9 }; + int contentFormat = json_wiki_get_content_format_flag(MagicValue); + if(MagicValue == contentFormat){ + contentFormat = json_find_option_bool("includeContent","content","c",0) /* deprecated */ ? -1 : 0; + } + return contentFormat; +} + +extern int json_wiki_get_content_format_flag( int defaultValue ) /* json_wiki.c */; + +cson_value * json_artifact_wiki(cson_object * zParent, int rid){ + if( ! g.perm.RdWiki ){ + json_set_err(FSL_JSON_E_DENIED, + "Requires 'j' privileges."); + return NULL; + }else{ + enum { MagicValue = -9 }; + int const contentFormat = json_artifact_get_content_format_flag(); + return json_get_wiki_page_by_rid(rid, contentFormat); + } +} + +/* +** Internal helper for routines which add a "status" flag to file +** artifact data. isNew and isDel should be the "is this object new?" +** and "is this object removed?" flags of the underlying query. This +** function returns a static string from the set (added, removed, +** modified), depending on the combination of the two args. +** +** Reminder to self: (mlink.pid==0) AS isNew, (mlink.fid==0) AS isDel +*/ +char const * json_artifact_status_to_string( char isNew, char isDel ){ + return isNew + ? "added" + : (isDel + ? "removed" + : "modified"); +} + +cson_value * json_artifact_file(cson_object * zParent, int rid){ + cson_object * pay = NULL; + Stmt q = empty_Stmt; + cson_array * checkin_arr = NULL; + int contentFormat; + i64 contentSize = -1; + char * parentUuid; + if( ! g.perm.Read ){ + json_set_err(FSL_JSON_E_DENIED, + "Requires 'o' privileges."); + return NULL; + } + + pay = zParent; + + contentFormat = json_artifact_get_content_format_flag(); + if( 0 != contentFormat ){ + Blob content = empty_blob; + const char *zMime; + char const * zFormat = (contentFormat<1) ? "raw" : "html"; + content_get(rid, &content); + zMime = mimetype_from_content(&content); + cson_object_set(zParent, "contentType", + json_new_string(zMime ? zMime : "text/plain")); + if(!zMime){/* text/plain */ + if(0 < blob_size(&content)){ + if( 0 < contentFormat ){/*HTML-size it*/ + Blob html = empty_blob; + wiki_convert(&content, &html, 0); + assert( blob_size(&content) < blob_size(&html) ); + blob_swap( &html, &content ); + assert( blob_size(&content) > blob_size(&html) ); + blob_reset( &html ); + }/*else as-is*/ + } + cson_object_set(zParent, "content", + cson_value_new_string(blob_str(&content), + (unsigned int)blob_size(&content))); + }/*else binary: ignore*/ + contentSize = blob_size(&content); + cson_object_set(zParent, "contentSize", json_new_int(contentSize) ); + cson_object_set(zParent, "contentFormat", json_new_string(zFormat) ); + blob_reset(&content); + } + contentSize = db_int64(-1, "SELECT size FROM blob WHERE rid=%d", rid); + assert( -1 < contentSize ); + cson_object_set(zParent, "size", json_new_int(contentSize) ); + + parentUuid = db_text(NULL, + "SELECT DISTINCT p.uuid " + "FROM blob p, blob f, mlink m " + "WHERE m.pid=p.rid " + "AND m.fid=f.rid " + "AND f.rid=%d", + rid + ); + if(parentUuid){ + cson_object_set( zParent, "parent", json_new_string(parentUuid) ); + fossil_free(parentUuid); + } + + /* Find check-ins associated with this file... */ + db_prepare(&q, + "SELECT filename.name AS name, " + " (mlink.pid==0) AS isNew," + " (mlink.fid==0) AS isDel," + " cast(strftime('%%s',event.mtime) as int) AS timestamp," + " coalesce(event.ecomment,event.comment) as comment," + " coalesce(event.euser,event.user) as user," +#if 0 + " a.size AS size," /* same for all check-ins. */ +#endif + " b.uuid as checkin, " +#if 0 + " mlink.mperm as mperm," +#endif + " coalesce((SELECT value FROM tagxref" + " WHERE tagid=%d AND tagtype>0 AND " + " rid=mlink.mid),'trunk') as branch" + " FROM mlink, filename, event, blob a, blob b" + " WHERE filename.fnid=mlink.fnid" + " AND event.objid=mlink.mid" + " AND a.rid=mlink.fid" + " AND b.rid=mlink.mid" + " AND mlink.fid=%d" + " ORDER BY filename.name, event.mtime", + TAG_BRANCH, rid + ); + /* TODO: add a "state" flag for the file in each check-in, + e.g. "modified", "new", "deleted". + */ + checkin_arr = cson_new_array(); + cson_object_set(pay, "checkins", cson_array_value(checkin_arr)); + while( (SQLITE_ROW==db_step(&q) ) ){ + cson_object * row = cson_value_get_object(cson_sqlite3_row_to_object(q.pStmt)); + /* FIXME: move this isNew/isDel stuff into an SQL CASE statement. */ + char const isNew = cson_value_get_bool(cson_object_get(row,"isNew")); + char const isDel = cson_value_get_bool(cson_object_get(row,"isDel")); + cson_object_set(row, "isNew", NULL); + cson_object_set(row, "isDel", NULL); + cson_object_set(row, "state", + json_new_string(json_artifact_status_to_string(isNew, isDel))); + cson_array_append( checkin_arr, cson_object_value(row) ); + } + db_finalize(&q); + return cson_object_value(pay); +} + +/* +** Impl of /json/artifact. This basically just determines the type of +** an artifact and forwards the real work to another function. +*/ +cson_value * json_page_artifact(){ + cson_object * pay = NULL; + char const * zName = NULL; + char const * zType = NULL; + char const * zUuid = NULL; + cson_value * entry = NULL; + Blob uuid = empty_blob; + int rc; + int rid = 0; + ArtifactDispatchEntry const * dispatcher = &ArtifactDispatchList[0]; + zName = json_find_option_cstr2("name", NULL, NULL, g.json.dispatchDepth+1); + if(!zName || !*zName) { + json_set_err(FSL_JSON_E_MISSING_ARGS, + "Missing 'name' argument."); + return NULL; + } + + if( validate16(zName, strlen(zName)) ){ + if( db_exists("SELECT 1 FROM ticket WHERE tkt_uuid GLOB '%q*'", zName) ){ + zType = "ticket"; + goto handle_entry; + } + if( db_exists("SELECT 1 FROM tag WHERE tagname GLOB 'event-%q*'", zName) ){ + zType = "tag"; + goto handle_entry; + } + } + blob_set(&uuid,zName); + rc = name_to_uuid(&uuid,-1,"*"); + /* FIXME: check for a filename if all else fails. */ + if(1==rc){ + g.json.resultCode = FSL_JSON_E_RESOURCE_NOT_FOUND; + goto error; + }else if(2==rc){ + g.json.resultCode = FSL_JSON_E_AMBIGUOUS_UUID; + goto error; + } + zUuid = blob_str(&uuid); + rid = db_int(0, "SELECT rid FROM blob WHERE uuid=%Q", zUuid); + if(0==rid){ + g.json.resultCode = FSL_JSON_E_RESOURCE_NOT_FOUND; + goto error; + } + + if( db_exists("SELECT 1 FROM mlink WHERE mid=%d", rid) + || db_exists("SELECT 1 FROM plink WHERE cid=%d", rid) + || db_exists("SELECT 1 FROM plink WHERE pid=%d", rid)){ + zType = "checkin"; + goto handle_entry; + }else if( db_exists("SELECT 1 FROM tagxref JOIN tag USING(tagid)" + " WHERE rid=%d AND tagname LIKE 'wiki-%%'", rid) ){ + zType = "wiki"; + goto handle_entry; + }else if( db_exists("SELECT 1 FROM tagxref JOIN tag USING(tagid)" + " WHERE rid=%d AND tagname LIKE 'tkt-%%'", rid) ){ + zType = "ticket"; + goto handle_entry; + }else if ( db_exists("SELECT 1 FROM mlink WHERE fid = %d", rid) ){ + zType = "file"; + goto handle_entry; + }else{ + g.json.resultCode = FSL_JSON_E_RESOURCE_NOT_FOUND; + goto error; + } + + error: + assert( 0 != g.json.resultCode ); + goto veryend; + + handle_entry: + pay = cson_new_object(); + assert( (NULL != zType) && "Internal dispatching error." ); + for( ; dispatcher->name; ++dispatcher ){ + if(0!=fossil_strcmp(dispatcher->name, zType)){ + continue; + }else{ + entry = (*dispatcher->func)(pay, rid); + break; + } + } + if(entry==0){ + g.json.resultCode = FSL_JSON_E_RESOURCE_NOT_FOUND + /* This is not quite right. We need a new result code + for this case. */; + g.zErrMsg = mprintf("Missing implementation for " + "artifacts of this type."); + goto error; + } + if(!g.json.resultCode){ + assert( NULL != entry ); + assert( NULL != zType ); + cson_object_set( pay, "type", json_new_string(zType) ); + cson_object_set( pay, "uuid", json_new_string(zUuid) ); + /*cson_object_set( pay, "name", json_new_string(zName ? zName : zUuid) );*/ + /*cson_object_set( pay, "rid", cson_value_new_integer(rid) );*/ + if(cson_value_is_object(entry) && (cson_value_get_object(entry) != pay)){ + cson_object_set(pay, "artifact", entry); + } + } + veryend: + blob_reset(&uuid); + if(g.json.resultCode && pay){ + cson_free_object(pay); + pay = NULL; + } + return cson_object_value(pay); +} + +#endif /* FOSSIL_ENABLE_JSON */ ADDED src/json_branch.c Index: src/json_branch.c ================================================================== --- /dev/null +++ src/json_branch.c @@ -0,0 +1,389 @@ +#ifdef FOSSIL_ENABLE_JSON +/* +** Copyright (c) 2011 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/ +** +*/ +#include "VERSION.h" +#include "config.h" +#include "json_branch.h" + +#if INTERFACE +#include "json_detail.h" +#endif + + +static cson_value * json_branch_list(); +static cson_value * json_branch_create(); +/* +** Mapping of /json/branch/XXX commands/paths to callbacks. +*/ +static const JsonPageDef JsonPageDefs_Branch[] = { +{"create", json_branch_create, 0}, +{"list", json_branch_list, 0}, +{"new", json_branch_create, -1/* for compat with non-JSON branch command.*/}, +/* Last entry MUST have a NULL name. */ +{NULL,NULL,0} +}; + +/* +** Implements the /json/branch family of pages/commands. Far from +** complete. +** +*/ +cson_value * json_page_branch(){ + return json_page_dispatch_helper(&JsonPageDefs_Branch[0]); +} + +/* +** Impl for /json/branch/list +** +** +** CLI mode options: +** +** -r|--range X, where X is one of (open,closed,all) +** (only the first letter is significant, default=open) +** -a (same as --range a) +** -c (same as --range c) +** +** HTTP mode options: +** +** "range" GET/POST.payload parameter. FIXME: currently we also use +** POST, but really want to restrict this to POST.payload. +*/ +static cson_value * json_branch_list(){ + cson_value * payV; + cson_object * pay; + cson_value * listV; + cson_array * list; + char const * range = NULL; + int branchListFlags = BRL_OPEN_ONLY; + char * sawConversionError = NULL; + Stmt q = empty_Stmt; + if( !g.perm.Read ){ + json_set_err(FSL_JSON_E_DENIED, + "Requires 'o' permissions."); + return NULL; + } + payV = cson_value_new_object(); + pay = cson_value_get_object(payV); + listV = cson_value_new_array(); + list = cson_value_get_array(listV); + if(fossil_has_json()){ + range = json_getenv_cstr("range"); + } + + range = json_find_option_cstr("range",NULL,"r"); + if((!range||!*range) && !g.isHTTP){ + range = find_option("all","a",0); + if(range && *range){ + range = "a"; + }else{ + range = find_option("closed","c",0); + if(range&&*range){ + range = "c"; + } + } + } + + if(!range || !*range){ + range = "o"; + } + /* Normalize range values... */ + switch(*range){ + case 'c': + range = "closed"; + branchListFlags = BRL_CLOSED_ONLY; + break; + case 'a': + range = "all"; + branchListFlags = BRL_BOTH; + break; + default: + range = "open"; + branchListFlags = BRL_OPEN_ONLY; + break; + }; + cson_object_set(pay,"range",json_new_string(range)); + + if( g.localOpen ){ /* add "current" property (branch name). */ + int vid = db_lget_int("checkout", 0); + char const * zCurrent = vid + ? db_text(0, "SELECT value FROM tagxref" + " WHERE rid=%d AND tagid=%d", + vid, TAG_BRANCH) + : 0; + if(zCurrent){ + cson_object_set(pay,"current",json_new_string(zCurrent)); + } + } + + + branch_prepare_list_query(&q, branchListFlags, 0); + cson_object_set(pay,"branches",listV); + while((SQLITE_ROW==db_step(&q))){ + cson_value * v = cson_sqlite3_column_to_value(q.pStmt,0); + if(v){ + cson_array_append(list,v); + }else if(!sawConversionError){ + sawConversionError = mprintf("Column-to-json failed @ %s:%d", + __FILE__,__LINE__); + } + } + if( sawConversionError ){ + json_warn(FSL_JSON_W_COL_TO_JSON_FAILED,"%s",sawConversionError); + free(sawConversionError); + } + db_finalize(&q); + return payV; +} + +/* +** Parameters for the create-branch operation. +*/ +typedef struct BranchCreateOptions{ + char const * zName; + char const * zBasis; + char const * zColor; + int isPrivate; + /** + Might be set to an error string by + json_branch_new(). + */ + char const * rcErrMsg; +} BranchCreateOptions; + +/* +** Tries to create a new branch based on the options set in zOpt. If +** an error is encountered, zOpt->rcErrMsg _might_ be set to a +** descriptive string and one of the FossilJsonCodes values will be +** returned. Or fossil_fatal() (or similar) might be called, exiting +** the app. +** +** On success 0 is returned and if zNewRid is not NULL then the rid of +** the new branch is assigned to it. +** +** If zOpt->isPrivate is 0 but the parent branch is private, +** zOpt->isPrivate will be set to a non-zero value and the new branch +** will be private. +*/ +static int json_branch_new(BranchCreateOptions * zOpt, + int *zNewRid){ + /* Mostly copied from branch.c:branch_new(), but refactored a small + bit to not produce output or interact with the user. The + down-side to that is that we dropped the gpg-signing. It was + either that or abort the creation if we couldn't sign. We can't + sign over HTTP mode, anyway. + */ + char const * zBranch = zOpt->zName; + char const * zBasis = zOpt->zBasis; + char const * zColor = zOpt->zColor; + int rootid; /* RID of the root check-in - what we branch off of */ + int brid; /* RID of the branch check-in */ + int i; /* Loop counter */ + char *zUuid; /* Artifact ID of origin */ + Stmt q; /* Generic query */ + char *zDate; /* Date that branch was created */ + char *zComment; /* Check-in comment for the new branch */ + Blob branch; /* manifest for the new branch */ + Manifest *pParent; /* Parsed parent manifest */ + Blob mcksum; /* Self-checksum on the manifest */ + + /* fossil branch new name */ + if( zBranch==0 || zBranch[0]==0 ){ + zOpt->rcErrMsg = "Branch name may not be null/empty."; + return FSL_JSON_E_INVALID_ARGS; + } + if( db_exists( + "SELECT 1 FROM tagxref" + " WHERE tagtype>0" + " AND tagid=(SELECT tagid FROM tag WHERE tagname='sym-%q')", + zBranch)!=0 ){ + zOpt->rcErrMsg = "Branch already exists."; + return FSL_JSON_E_RESOURCE_ALREADY_EXISTS; + } + + db_begin_transaction(); + rootid = name_to_typed_rid(zBasis, "ci"); + if( rootid==0 ){ + zOpt->rcErrMsg = "Basis branch not found."; + return FSL_JSON_E_RESOURCE_NOT_FOUND; + } + + pParent = manifest_get(rootid, CFTYPE_MANIFEST, 0); + if( pParent==0 ){ + zOpt->rcErrMsg = "Could not read parent manifest."; + return FSL_JSON_E_UNKNOWN; + } + + /* Create a manifest for the new branch */ + blob_zero(&branch); + if( pParent->zBaseline ){ + blob_appendf(&branch, "B %s\n", pParent->zBaseline); + } + zComment = mprintf("Create new branch named \"%s\" " + "from \"%s\".", zBranch, zBasis); + blob_appendf(&branch, "C %F\n", zComment); + free(zComment); + zDate = date_in_standard_format("now"); + blob_appendf(&branch, "D %s\n", zDate); + free(zDate); + + /* Copy all of the content from the parent into the branch */ + for(i=0; inFile; ++i){ + blob_appendf(&branch, "F %F", pParent->aFile[i].zName); + if( pParent->aFile[i].zUuid ){ + blob_appendf(&branch, " %s", pParent->aFile[i].zUuid); + if( pParent->aFile[i].zPerm && pParent->aFile[i].zPerm[0] ){ + blob_appendf(&branch, " %s", pParent->aFile[i].zPerm); + } + } + blob_append(&branch, "\n", 1); + } + zUuid = db_text(0, "SELECT uuid FROM blob WHERE rid=%d", rootid); + blob_appendf(&branch, "P %s\n", zUuid); + free(zUuid); + if( pParent->zRepoCksum ){ + blob_appendf(&branch, "R %s\n", pParent->zRepoCksum); + } + manifest_destroy(pParent); + + /* Add the symbolic branch name and the "branch" tag to identify + ** this as a new branch */ + if( content_is_private(rootid) ) zOpt->isPrivate = 1; + if( zOpt->isPrivate && zColor==0 ) zColor = "#fec084"; + if( zColor!=0 ){ + blob_appendf(&branch, "T *bgcolor * %F\n", zColor); + } + blob_appendf(&branch, "T *branch * %F\n", zBranch); + blob_appendf(&branch, "T *sym-%F *\n", zBranch); + + /* Cancel all other symbolic tags */ + db_prepare(&q, + "SELECT tagname FROM tagxref, tag" + " WHERE tagxref.rid=%d AND tagxref.tagid=tag.tagid" + " AND tagtype>0 AND tagname GLOB 'sym-*'" + " ORDER BY tagname", + rootid); + while( db_step(&q)==SQLITE_ROW ){ + const char *zTag = db_column_text(&q, 0); + blob_appendf(&branch, "T -%F *\n", zTag); + } + db_finalize(&q); + + blob_appendf(&branch, "U %F\n", g.zLogin); + md5sum_blob(&branch, &mcksum); + blob_appendf(&branch, "Z %b\n", &mcksum); + + brid = content_put_ex(&branch, 0, 0, 0, zOpt->isPrivate); + if( brid==0 ){ + fossil_panic("Problem committing manifest: %s", g.zErrMsg); + } + db_multi_exec("INSERT OR IGNORE INTO unsent VALUES(%d)", brid); + if( manifest_crosslink(brid, &branch, MC_PERMIT_HOOKS)==0 ){ + fossil_panic("%s", g.zErrMsg); + } + assert( blob_is_reset(&branch) ); + content_deltify(rootid, &brid, 1, 0); + if( zNewRid ){ + *zNewRid = brid; + } + + /* Commit */ + db_end_transaction(0); + +#if 0 /* Do an autosync push, if requested */ + /* arugable for JSON mode? */ + if( !g.isHTTP && !isPrivate ) autosync(SYNC_PUSH); +#endif + return 0; +} + + +/* +** Impl of /json/branch/create. +*/ +static cson_value * json_branch_create(){ + cson_value * payV = NULL; + cson_object * pay = NULL; + int rc = 0; + BranchCreateOptions opt; + char * zUuid = NULL; + int rid = 0; + if( !g.perm.Write ){ + json_set_err(FSL_JSON_E_DENIED, + "Requires 'i' permissions."); + return NULL; + } + memset(&opt,0,sizeof(BranchCreateOptions)); + if(fossil_has_json()){ + opt.zName = json_getenv_cstr("name"); + } + + if(!opt.zName){ + opt.zName = json_command_arg(g.json.dispatchDepth+1); + } + + if(!opt.zName){ + json_set_err(FSL_JSON_E_MISSING_ARGS, "'name' parameter was not specified." ); + return NULL; + } + + opt.zColor = json_find_option_cstr("bgColor","bgcolor",NULL); + opt.zBasis = json_find_option_cstr("basis",NULL,NULL); + if(!opt.zBasis && !g.isHTTP){ + opt.zBasis = json_command_arg(g.json.dispatchDepth+2); + } + if(!opt.zBasis){ + opt.zBasis = "trunk"; + } + opt.isPrivate = json_find_option_bool("private",NULL,NULL,-1); + if(-1==opt.isPrivate){ + if(!g.isHTTP){ + opt.isPrivate = (NULL != find_option("private","",0)); + }else{ + opt.isPrivate = 0; + } + } + + rc = json_branch_new( &opt, &rid ); + if(rc){ + json_set_err(rc, "%s", opt.rcErrMsg); + goto error; + } + assert(0 != rid); + payV = cson_value_new_object(); + pay = cson_value_get_object(payV); + + cson_object_set(pay,"name",json_new_string(opt.zName)); + cson_object_set(pay,"basis",json_new_string(opt.zBasis)); + cson_object_set(pay,"rid",json_new_int(rid)); + zUuid = db_text(0, "SELECT uuid FROM blob WHERE rid=%d", rid); + cson_object_set(pay,"uuid", json_new_string(zUuid)); + cson_object_set(pay, "isPrivate", cson_value_new_bool(opt.isPrivate)); + free(zUuid); + if(opt.zColor){ + cson_object_set(pay,"bgColor",json_new_string(opt.zColor)); + } + + goto ok; + error: + assert( 0 != g.json.resultCode ); + cson_value_free(payV); + payV = NULL; + ok: + return payV; +} + +#endif /* FOSSIL_ENABLE_JSON */ ADDED src/json_config.c Index: src/json_config.c ================================================================== --- /dev/null +++ src/json_config.c @@ -0,0 +1,190 @@ +#ifdef FOSSIL_ENABLE_JSON +/* +** Copyright (c) 2011 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/ +** +*/ +#include "VERSION.h" +#include "config.h" +#include "json_config.h" + +#if INTERFACE +#include "json_detail.h" +#endif + +static cson_value * json_config_get(); +static cson_value * json_config_save(); + +/* +** Mapping of /json/config/XXX commands/paths to callbacks. +*/ +static const JsonPageDef JsonPageDefs_Config[] = { +{"get", json_config_get, 0}, +{"save", json_config_save, 0}, +/* Last entry MUST have a NULL name. */ +{NULL,NULL,0} +}; + + +/* +** Implements the /json/config family of pages/commands. +** +*/ +cson_value * json_page_config(){ + return json_page_dispatch_helper(&JsonPageDefs_Config[0]); +} + + +/* +** JSON-internal mapping of config options to config groups. This is +** mostly a copy of the config options in configure.c, but that data +** is private and cannot be re-used directly here. +*/ +static const struct JsonConfigProperty { + char const * name; + int groupMask; +} JsonConfigProperties[] = { +{ "css", CONFIGSET_CSS }, +{ "header", CONFIGSET_SKIN }, +{ "footer", CONFIGSET_SKIN }, +{ "details", CONFIGSET_SKIN }, +{ "logo-mimetype", CONFIGSET_SKIN }, +{ "logo-image", CONFIGSET_SKIN }, +{ "background-mimetype", CONFIGSET_SKIN }, +{ "background-image", CONFIGSET_SKIN }, +{ "icon-mimetype", CONFIGSET_SKIN }, +{ "icon-image", CONFIGSET_SKIN }, +{ "timeline-block-markup", CONFIGSET_SKIN }, +{ "timeline-max-comment", CONFIGSET_SKIN }, +{ "timeline-plaintext", CONFIGSET_SKIN }, +{ "adunit", CONFIGSET_SKIN }, +{ "adunit-omit-if-admin", CONFIGSET_SKIN }, +{ "adunit-omit-if-user", CONFIGSET_SKIN }, + +{ "project-name", CONFIGSET_PROJ }, +{ "short-project-name", CONFIGSET_PROJ }, +{ "project-description", CONFIGSET_PROJ }, +{ "index-page", CONFIGSET_PROJ }, +{ "manifest", CONFIGSET_PROJ }, +{ "binary-glob", CONFIGSET_PROJ }, +{ "clean-glob", CONFIGSET_PROJ }, +{ "ignore-glob", CONFIGSET_PROJ }, +{ "keep-glob", CONFIGSET_PROJ }, +{ "crlf-glob", CONFIGSET_PROJ }, +{ "crnl-glob", CONFIGSET_PROJ }, +{ "encoding-glob", CONFIGSET_PROJ }, +{ "empty-dirs", CONFIGSET_PROJ }, +{ "dotfiles", CONFIGSET_PROJ }, + +{ "ticket-table", CONFIGSET_TKT }, +{ "ticket-common", CONFIGSET_TKT }, +{ "ticket-change", CONFIGSET_TKT }, +{ "ticket-newpage", CONFIGSET_TKT }, +{ "ticket-viewpage", CONFIGSET_TKT }, +{ "ticket-editpage", CONFIGSET_TKT }, +{ "ticket-reportlist", CONFIGSET_TKT }, +{ "ticket-report-template", CONFIGSET_TKT }, +{ "ticket-key-template", CONFIGSET_TKT }, +{ "ticket-title-expr", CONFIGSET_TKT }, +{ "ticket-closed-expr", CONFIGSET_TKT }, + +{NULL, 0} +}; + + +/* +** Impl of /json/config/get. Requires setup rights. +** +*/ +static cson_value * json_config_get(){ + cson_object * pay = NULL; + Stmt q = empty_Stmt; + Blob sql = empty_blob; + char const * zName = NULL; + int confMask = 0; + char optSkinBackups = 0; + unsigned int i; + if(!g.perm.Setup){ + json_set_err(FSL_JSON_E_DENIED, "Requires 's' permissions."); + return NULL; + } + + i = g.json.dispatchDepth + 1; + zName = json_command_arg(i); + for( ; zName; zName = json_command_arg(++i) ){ + if(0==(strcmp("all", zName))){ + confMask = CONFIGSET_ALL; + }else if(0==(strcmp("project", zName))){ + confMask |= CONFIGSET_PROJ; + }else if(0==(strcmp("skin", zName))){ + confMask |= (CONFIGSET_CSS|CONFIGSET_SKIN); + }else if(0==(strcmp("ticket", zName))){ + confMask |= CONFIGSET_TKT; + }else if(0==(strcmp("skin-backup", zName))){ + optSkinBackups = 1; + }else{ + json_set_err( FSL_JSON_E_INVALID_ARGS, + "Unknown config area: %s", zName); + return NULL; + } + } + + if(!confMask && !optSkinBackups){ + json_set_err(FSL_JSON_E_MISSING_ARGS, "No configuration area(s) selected."); + } + blob_append(&sql, + "SELECT name, value" + " FROM config " + " WHERE 0 ", -1); + { + const struct JsonConfigProperty * prop = &JsonConfigProperties[0]; + blob_append(&sql," OR name IN (",-1); + for( i = 0; prop->name; ++prop ){ + if(prop->groupMask & confMask){ + if( i++ ){ + blob_append(&sql,",",1); + } + blob_append_sql(&sql, "%Q", prop->name); + } + } + blob_append(&sql,") ", -1); + } + + + if( optSkinBackups ){ + blob_append(&sql, " OR name GLOB 'skin:*'", -1); + } + blob_append(&sql," ORDER BY name", -1); + db_prepare(&q, "%s", blob_sql_text(&sql)); + blob_reset(&sql); + pay = cson_new_object(); + while( (SQLITE_ROW==db_step(&q)) ){ + cson_object_set(pay, + db_column_text(&q,0), + json_new_string(db_column_text(&q,1))); + } + db_finalize(&q); + return cson_object_value(pay); +} + +/* +** Impl of /json/config/save. +** +** TODOs: +*/ +static cson_value * json_config_save(){ + json_set_err(FSL_JSON_E_NYI, NULL); + return NULL; +} +#endif /* FOSSIL_ENABLE_JSON */ ADDED src/json_detail.h Index: src/json_detail.h ================================================================== --- /dev/null +++ src/json_detail.h @@ -0,0 +1,273 @@ +#ifdef FOSSIL_ENABLE_JSON +#if !defined(FOSSIL_JSON_DETAIL_H_INCLUDED) +#define FOSSIL_JSON_DETAIL_H_INCLUDED +/* +** Copyright (c) 2011 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/ +** +*/ + +#if !defined(_RC_COMPILE_) +#include "cson_amalgamation.h" +#endif /* !defined(_RC_COMPILE_) */ + +/** + FOSSIL_JSON_API_VERSION holds the date (YYYYMMDD) of the latest + "significant" change to the JSON API (a change in an interface or + new functionality). It is sent as part of the /json/version + request. We could arguably add it to each response or even add a + version number to each response type, allowing very fine (too + fine?) granularity in compatibility change notification. The + version number could be included in part of the command dispatching + framework, allowing the top-level dispatching code to deal with it + (for the most part). +*/ +#define FOSSIL_JSON_API_VERSION "20120713" + +/* +** Impl details for the JSON API which need to be shared +** across multiple C files. +*/ + +/* +** The "official" list of Fossil/JSON error codes. Their values might +** very well change during initial development but after their first +** public release they must stay stable. +** +** Values must be in the range 1000..9999 for error codes and 1..999 +** for warning codes. +** +** Numbers evenly dividable by 100 are "categories", and error codes +** for a given category have their high bits set to the category +** value. +** +** Maintenance reminder: when entries are added to this list, update +** the code in json_page_resultCodes() and json_err_cstr() (both in +** json.c)! +** +*/ +#if !defined(_RC_COMPILE_) +enum FossilJsonCodes { +FSL_JSON_W_START = 0, +FSL_JSON_W_UNKNOWN /*+1*/, +FSL_JSON_W_ROW_TO_JSON_FAILED /*+2*/, +FSL_JSON_W_COL_TO_JSON_FAILED /*+3*/, +FSL_JSON_W_STRING_TO_ARRAY_FAILED /*+4*/, +FSL_JSON_W_TAG_NOT_FOUND /*+5*/, + +FSL_JSON_W_END = 1000, +FSL_JSON_E_GENERIC = 1000, +FSL_JSON_E_GENERIC_SUB1 = FSL_JSON_E_GENERIC + 100, +FSL_JSON_E_INVALID_REQUEST /*+1*/, +FSL_JSON_E_UNKNOWN_COMMAND /*+2*/, +FSL_JSON_E_UNKNOWN /*+3*/, +/*REUSE: +4*/ +FSL_JSON_E_TIMEOUT /*+5*/, +FSL_JSON_E_ASSERT /*+6*/, +FSL_JSON_E_ALLOC /*+7*/, +FSL_JSON_E_NYI /*+8*/, +FSL_JSON_E_PANIC /*+9*/, +FSL_JSON_E_MANIFEST_READ_FAILED /*+10*/, +FSL_JSON_E_FILE_OPEN_FAILED /*+11*/, + +FSL_JSON_E_AUTH = 2000, +FSL_JSON_E_MISSING_AUTH /*+1*/, +FSL_JSON_E_DENIED /*+2*/, +FSL_JSON_E_WRONG_MODE /*+3*/, + +FSL_JSON_E_LOGIN_FAILED = FSL_JSON_E_AUTH +100, +FSL_JSON_E_LOGIN_FAILED_NOSEED /*+1*/, +FSL_JSON_E_LOGIN_FAILED_NONAME /*+2*/, +FSL_JSON_E_LOGIN_FAILED_NOPW /*+3*/, +FSL_JSON_E_LOGIN_FAILED_NOTFOUND /*+4*/, + +FSL_JSON_E_USAGE = 3000, +FSL_JSON_E_INVALID_ARGS /*+1*/, +FSL_JSON_E_MISSING_ARGS /*+2*/, +FSL_JSON_E_AMBIGUOUS_UUID /*+3*/, +FSL_JSON_E_UNRESOLVED_UUID /*+4*/, +FSL_JSON_E_RESOURCE_ALREADY_EXISTS /*+5*/, +FSL_JSON_E_RESOURCE_NOT_FOUND /*+6*/, + +FSL_JSON_E_DB = 4000, +FSL_JSON_E_STMT_PREP /*+1*/, +FSL_JSON_E_STMT_BIND /*+2*/, +FSL_JSON_E_STMT_EXEC /*+3*/, +FSL_JSON_E_DB_LOCKED /*+4*/, + +FSL_JSON_E_DB_NEEDS_REBUILD = FSL_JSON_E_DB + 101, +FSL_JSON_E_DB_NOT_FOUND = FSL_JSON_E_DB + 102, +FSL_JSON_E_DB_NOT_VALID = FSL_JSON_E_DB + 103, +/* +** Maintenance reminder: FSL_JSON_E_DB_NOT_FOUND gets triggered in the +** bootstrapping process before we know whether we need to check for +** FSL_JSON_E_DB_NEEDS_CHECKOUT. Thus the former error trumps the +** latter. +*/ +FSL_JSON_E_DB_NEEDS_CHECKOUT = FSL_JSON_E_DB + 104 +}; + + +/* +** Signature for JSON page/command callbacks. Each callback is +** responsible for handling one JSON request/command and/or +** dispatching to sub-commands. +** +** By the time the callback is called, json_page_top() (HTTP mode) or +** json_cmd_top() (CLI mode) will have set up the JSON-related +** environment. Implementations may generate a "result payload" of any +** JSON type by returning its value from this function (ownership is +** transferred to the caller). On error they should set +** g.json.resultCode to one of the FossilJsonCodes values and return +** either their payload object or NULL. Note that NULL is a legal +** success value - it simply means the response will contain no +** payload. If g.json.resultCode is non-zero when this function +** returns then the top-level dispatcher will destroy any payload +** returned by this function and will output a JSON error response +** instead. +** +** All of the setup/response code is handled by the top dispatcher +** functions and the callbacks concern themselves only with: +** +** a) Permissions checking (inspecting g.perm). +** b) generating a response payload (if applicable) +** c) Setting g.json's error state (if applicable). See json_set_err(). +** +** It is imperative that NO callback functions EVER output ANYTHING to +** stdout, as that will effectively corrupt any JSON output, and +** almost certainly will corrupt any HTTP response headers. Output +** sent to stderr ends up in my apache log, so that might be useful +** for debugging in some cases, but no such code should be left +** enabled for non-debugging builds. +*/ +typedef cson_value * (*fossil_json_f)(); + +/* +** Holds name-to-function mappings for JSON page/command dispatching. +** +** Internally we model page dispatching lists as arrays of these +** objects, where the final entry in the array has a NULL name value +** to act as the end-of-list sentinel. +** +*/ +typedef struct JsonPageDef{ + /* + ** The commmand/page's name (path, not including leading /json/). + ** + ** Reminder to self: we cannot use sub-paths with commands this way + ** without additional string-splitting downstream. e.g. foo/bar. + ** Alternately, we can create different JsonPageDef arrays for each + ** subset. + */ + char const * name; + /* + ** Returns a payload object for the response. If it returns a + ** non-NULL value, the caller owns it. To trigger an error this + ** function should set g.json.resultCode to a value from the + ** FossilJsonCodes enum. If it sets an error value and returns + ** a payload, the payload will be destroyed (not sent with the + ** response). + */ + fossil_json_f func; + /* + ** Which mode(s) of execution does func() support: + ** + ** <0 = CLI only, >0 = HTTP only, 0==both + ** + ** Now that we can simulate POST in CLI mode, the distinction + ** between them has disappeared in most (or all) cases, so 0 is + ** the standard value. + */ + int runMode; +} JsonPageDef; + +/* +** Holds common keys used for various JSON API properties. +*/ +typedef struct FossilJsonKeys_{ + /** maintainers: please keep alpha sorted (case-insensitive) */ + char const * anonymousSeed; + char const * authToken; + char const * commandPath; + char const * mtime; + char const * payload; + char const * requestId; + char const * resultCode; + char const * resultText; + char const * timestamp; +} FossilJsonKeys_; +extern const FossilJsonKeys_ FossilJsonKeys; + +/* +** A page/command dispatch helper for fossil_json_f() implementations. +** pages must be an array of JsonPageDef commands which we can +** dispatch. The final item in the array MUST have a NULL name +** element. +** +** This function takes the command specified in +** json_command_arg(1+g.json.dispatchDepth) and searches pages for a +** matching name. If found then that page's func() is called to fetch +** the payload, which is returned to the caller. +** +** On error, g.json.resultCode is set to one of the FossilJsonCodes +** values and NULL is returned. If non-NULL is returned, ownership is +** transfered to the caller (but the g.json error state might still be +** set in that case, so the caller must check that or pass it on up +** the dispatch chain). +*/ +cson_value * json_page_dispatch_helper(JsonPageDef const * pages); + +/* +** Convenience wrapper around cson_value_new_string(). +** Returns NULL if str is NULL or on allocation error. +*/ +cson_value * json_new_string( char const * str ); + +/* +** Similar to json_new_string(), but takes a printf()-style format +** specifiers. Supports the printf extensions supported by fossil's +** mprintf(). Returns NULL if str is NULL or on allocation error. +** +** Maintenance note: json_new_string() is NOT variadic because by the +** time the variadic form was introduced we already had use cases +** which segfaulted via json_new_string() because they contain printf +** markup (e.g. wiki content). Been there, debugged that. +*/ +cson_value * json_new_string_f( char const * fmt, ... ); + +/* +** Returns true if fossil is running in JSON mode and we are either +** running in HTTP mode OR g.json.post.o is not NULL (meaning POST +** data was fed in from CLI mode). +** +** Specifically, it will return false when any of these apply: +** +** a) Not running in JSON mode (via json command or /json path). +** +** b) We are running in JSON CLI mode, but no POST data has been fed +** in. +** +** Whether or not we need to take args from CLI or POST data makes a +** difference in argument/parameter handling in many JSON routines, +** and thus this distinction. +*/ +int fossil_has_json(); + +enum json_get_changed_files_flags { + json_get_changed_files_ELIDE_PARENT = 1 << 0 +}; + +#endif /* !defined(_RC_COMPILE_) */ +#endif/*FOSSIL_JSON_DETAIL_H_INCLUDED*/ +#endif /* FOSSIL_ENABLE_JSON */ ADDED src/json_diff.c Index: src/json_diff.c ================================================================== --- /dev/null +++ src/json_diff.c @@ -0,0 +1,138 @@ +#ifdef FOSSIL_ENABLE_JSON +/* +** Copyright (c) 2011 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/ +** +*/ + +#include "config.h" +#include "json_diff.h" + +#if INTERFACE +#include "json_detail.h" +#endif + + + +/* +** Generates a diff between two versions (zFrom and zTo), using nContext +** content lines in the output. On success, returns a new JSON String +** object. On error it sets g.json's error state and returns NULL. +** +** If fSbs is true (non-0) them side-by-side diffs are used. +** +** If fHtml is true then HTML markup is added to the diff. +*/ +cson_value * json_generate_diff(const char *zFrom, const char *zTo, + int nContext, char fSbs, + char fHtml){ + int fromid; + int toid; + int outLen; + Blob from = empty_blob, to = empty_blob, out = empty_blob; + cson_value * rc = NULL; + int flags = (DIFF_CONTEXT_MASK & nContext) + | (fSbs ? DIFF_SIDEBYSIDE : 0) + | (fHtml ? DIFF_HTML : 0); + fromid = name_to_typed_rid(zFrom, "*"); + if(fromid<=0){ + json_set_err(FSL_JSON_E_UNRESOLVED_UUID, + "Could not resolve 'from' ID."); + return NULL; + } + toid = name_to_typed_rid(zTo, "*"); + if(toid<=0){ + json_set_err(FSL_JSON_E_UNRESOLVED_UUID, + "Could not resolve 'to' ID."); + return NULL; + } + content_get(fromid, &from); + content_get(toid, &to); + blob_zero(&out); + text_diff(&from, &to, &out, 0, flags); + blob_reset(&from); + blob_reset(&to); + outLen = blob_size(&out); + if(outLen>=0){ + rc = cson_value_new_string(blob_buffer(&out), + (unsigned int)blob_size(&out)); + } + blob_reset(&out); + return rc; +} + +/* +** Implementation of the /json/diff page. +** +** Arguments: +** +** v1=1st version to diff +** v2=2nd version to diff +** +** Can come from GET, POST.payload, CLI -v1/-v2 or as positional +** parameters following the command name (in HTTP and CLI modes). +** +*/ +cson_value * json_page_diff(){ + cson_object * pay = NULL; + cson_value * v = NULL; + char const * zFrom; + char const * zTo; + int nContext = 0; + char doSBS; + char doHtml; + if(!g.perm.Read){ + json_set_err(FSL_JSON_E_DENIED, + "Requires 'o' permissions."); + return NULL; + } + zFrom = json_find_option_cstr("v1",NULL,NULL); + if(!zFrom){ + zFrom = json_command_arg(2); + } + if(!zFrom){ + json_set_err(FSL_JSON_E_MISSING_ARGS, + "Required 'v1' parameter is missing."); + return NULL; + } + zTo = json_find_option_cstr("v2",NULL,NULL); + if(!zTo){ + zTo = json_command_arg(3); + } + if(!zTo){ + json_set_err(FSL_JSON_E_MISSING_ARGS, + "Required 'v2' parameter is missing."); + return NULL; + } + nContext = json_find_option_int("context",NULL,"c",5); + doSBS = json_find_option_bool("sbs",NULL,"y",0); + doHtml = json_find_option_bool("html",NULL,"h",0); + v = json_generate_diff(zFrom, zTo, nContext, doSBS, doHtml); + if(!v){ + if(!g.json.resultCode){ + json_set_err(FSL_JSON_E_UNKNOWN, + "Generating diff failed for unknown reason."); + } + return NULL; + } + pay = cson_new_object(); + cson_object_set(pay, "from", json_new_string(zFrom)); + cson_object_set(pay, "to", json_new_string(zTo)); + cson_object_set(pay, "diff", v); + v = 0; + + return pay ? cson_object_value(pay) : NULL; +} + +#endif /* FOSSIL_ENABLE_JSON */ ADDED src/json_dir.c Index: src/json_dir.c ================================================================== --- /dev/null +++ src/json_dir.c @@ -0,0 +1,294 @@ +#ifdef FOSSIL_ENABLE_JSON +/* +** Copyright (c) 2011 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/ +** +*/ +#include "VERSION.h" +#include "config.h" +#include "json_dir.h" + +#if INTERFACE +#include "json_detail.h" +#endif + +static cson_value * json_page_dir_list(); +/* +** Mapping of /json/wiki/XXX commands/paths to callbacks. +*/ +#if 0 /* TODO: Not used? */ +static const JsonPageDef JsonPageDefs_Dir[] = { +/* Last entry MUST have a NULL name. */ +{NULL,NULL,0} +}; +#endif + +#if 0 /* TODO: Not used? */ +static char const * json_dir_path_extra(){ + static char const * zP = NULL; + if( !zP ){ + zP = g.zExtra; + while(zP && *zP && ('/'==*zP)){ + ++zP; + } + } + return zP; +} +#endif + +/* +** Impl of /json/dir. 98% of it was taken directly +** from browse.c::page_dir() +*/ +static cson_value * json_page_dir_list(){ + cson_object * zPayload = NULL; /* return value */ + cson_array * zEntries = NULL; /* accumulated list of entries. */ + cson_object * zEntry = NULL; /* a single dir/file entry. */ + cson_array * keyStore = NULL; /* garbage collector for shared strings. */ + cson_string * zKeyName = NULL; + cson_string * zKeySize = NULL; + cson_string * zKeyIsDir = NULL; + cson_string * zKeyUuid = NULL; + cson_string * zKeyTime = NULL; + cson_string * zKeyRaw = NULL; + char * zD = NULL; + char const * zDX = NULL; + int nD; + char * zUuid = NULL; + char const * zCI = NULL; + Manifest * pM = NULL; + Stmt q = empty_Stmt; + int rid = 0; + if( !g.perm.Read ){ + json_set_err(FSL_JSON_E_DENIED, "Requires 'o' permissions."); + return NULL; + } + zCI = json_find_option_cstr("checkin",NULL,"ci" ); + + /* If a specific check-in is requested, fetch and parse it. If the + ** specific check-in does not exist, clear zCI. zCI==0 will cause all + ** files from all check-ins to be displayed. + */ + if( zCI && *zCI ){ + pM = manifest_get_by_name(zCI, &rid); + if( pM ){ + zUuid = db_text(0, "SELECT uuid FROM blob WHERE rid=%d", rid); + }else{ + json_set_err(FSL_JSON_E_UNRESOLVED_UUID, + "Check-in name [%s] is unresolved.", + zCI); + return NULL; + } + } + + /* Jump through some hoops to find the directory name... */ + zDX = json_find_option_cstr("name",NULL,NULL); + if(!zDX && !g.isHTTP){ + zDX = json_command_arg(g.json.dispatchDepth+1); + } + if(zDX && (!*zDX || (0==strcmp(zDX,"/")))){ + zDX = NULL; + } + zD = zDX ? fossil_strdup(zDX) : NULL; + nD = zD ? strlen(zD)+1 : 0; + while( nD>1 && zD[nD-2]=='/' ){ zD[(--nD)-1] = 0; } + + sqlite3_create_function(g.db, "pathelement", 2, SQLITE_UTF8, 0, + pathelementFunc, 0, 0); + + /* Compute the temporary table "localfiles" containing the names + ** of all files and subdirectories in the zD[] directory. + ** + ** Subdirectory names begin with "/". This causes them to sort + ** first and it also gives us an easy way to distinguish files + ** from directories in the loop that follows. + */ + + if( zCI ){ + Stmt ins; + ManifestFile *pFile; + ManifestFile *pPrev = 0; + int nPrev = 0; + int c; + + db_multi_exec( + "CREATE TEMP TABLE json_dir_files(" + " n UNIQUE NOT NULL," /* file name */ + " fn UNIQUE NOT NULL," /* full file name */ + " u DEFAULT NULL," /* file uuid */ + " sz DEFAULT -1," /* file size */ + " mtime DEFAULT NULL" /* file mtime in unix epoch format */ + ");" + ); + + db_prepare(&ins, + "INSERT OR IGNORE INTO json_dir_files (n,fn,u,sz,mtime) " + "SELECT" + " pathelement(:path,0)," + " CASE WHEN %Q IS NULL THEN '' ELSE %Q||'/' END ||:abspath," + " a.uuid," + " a.size," + " CAST(strftime('%%s',e.mtime) AS INTEGER) " + "FROM" + " mlink m, " + " event e," + " blob a," + " blob b " + "WHERE" + " e.objid=m.mid" + " AND a.rid=m.fid"/*FILE artifact*/ + " AND b.rid=m.mid"/*CHECKIN artifact*/ + " AND a.uuid=:uuid", + zD, zD + ); + manifest_file_rewind(pM); + while( (pFile = manifest_file_next(pM,0))!=0 ){ + if( nD>0 + && ((pFile->zName[nD-1]!='/') || (0!=memcmp(pFile->zName, zD, nD-1))) + ){ + continue; + } + /*printf("zD=%s, nD=%d, pFile->zName=%s\n", zD, nD, pFile->zName);*/ + if( pPrev + && memcmp(&pFile->zName[nD],&pPrev->zName[nD],nPrev)==0 + && (pFile->zName[nD+nPrev]==0 || pFile->zName[nD+nPrev]=='/') + ){ + continue; + } + db_bind_text( &ins, ":path", &pFile->zName[nD] ); + db_bind_text( &ins, ":abspath", &pFile->zName[nD] ); + db_bind_text( &ins, ":uuid", pFile->zUuid ); + db_step(&ins); + db_reset(&ins); + pPrev = pFile; + for(nPrev=0; (c=pPrev->zName[nD+nPrev]) && c!='/'; nPrev++){} + if( c=='/' ) nPrev++; + } + db_finalize(&ins); + }else if( zD && *zD ){ + db_multi_exec( + "CREATE TEMP VIEW json_dir_files AS" + " SELECT DISTINCT(pathelement(name,%d)) AS n," + " %Q||'/'||name AS fn," + " NULL AS u, NULL AS sz, NULL AS mtime" + " FROM filename" + " WHERE name GLOB '%q/*'" + " GROUP BY n", + nD, zD, zD + ); + }else{ + db_multi_exec( + "CREATE TEMP VIEW json_dir_files" + " AS SELECT DISTINCT(pathelement(name,0)) AS n, NULL AS fn" + " FROM filename" + ); + } + + if(zCI){ + db_prepare( &q, "SELECT" + " n as name," + " fn as fullname," + " u as uuid," + " sz as size," + " mtime as mtime " + "FROM json_dir_files ORDER BY n"); + }else{/* UUIDs are all NULL. */ + db_prepare( &q, "SELECT n, fn FROM json_dir_files ORDER BY n"); + } + + zKeyName = cson_new_string("name",4); + zKeyUuid = cson_new_string("uuid",4); + zKeyIsDir = cson_new_string("isDir",5); + keyStore = cson_new_array(); + cson_array_append( keyStore, cson_string_value(zKeyName) ); + cson_array_append( keyStore, cson_string_value(zKeyUuid) ); + cson_array_append( keyStore, cson_string_value(zKeyIsDir) ); + + if( zCI ){ + zKeySize = cson_new_string("size",4); + cson_array_append( keyStore, cson_string_value(zKeySize) ); + zKeyTime = cson_new_string("timestamp",9); + cson_array_append( keyStore, cson_string_value(zKeyTime) ); + zKeyRaw = cson_new_string("downloadPath",12); + cson_array_append( keyStore, cson_string_value(zKeyRaw) ); + } + zPayload = cson_new_object(); + cson_object_set_s( zPayload, zKeyName, + json_new_string((zD&&*zD) ? zD : "/") ); + if( zUuid ){ + cson_object_set( zPayload, "checkin", json_new_string(zUuid) ); + } + + while( (SQLITE_ROW==db_step(&q)) ){ + cson_value * name = NULL; + char const * n = db_column_text(&q,0); + char const isDir = ('/'==*n); + zEntry = cson_new_object(); + if(!zEntries){ + zEntries = cson_new_array(); + cson_object_set( zPayload, "entries", cson_array_value(zEntries) ); + } + cson_array_append(zEntries, cson_object_value(zEntry) ); + if(isDir){ + name = json_new_string( n+1 ); + cson_object_set_s(zEntry, zKeyIsDir, cson_value_true() ); + } else{ + name = json_new_string( n ); + } + cson_object_set_s(zEntry, zKeyName, name ); + if( zCI && !isDir){ + /* Don't add the uuid/size for dir entries - that data refers to + one of the files in that directory :/. Entries with no + --checkin may refer to N versions, and therefore we cannot + associate a single size and uuid with them (and fetching all + would be overkill for most use cases). + */ + char const * fullName = db_column_text(&q,1); + char const * u = db_column_text(&q,2); + sqlite_int64 const sz = db_column_int64(&q,3); + sqlite_int64 const ts = db_column_int64(&q,4); + cson_object_set_s(zEntry, zKeyUuid, json_new_string( u ) ); + cson_object_set_s(zEntry, zKeySize, + cson_value_new_integer( (cson_int_t)sz )); + cson_object_set_s(zEntry, zKeyTime, + cson_value_new_integer( (cson_int_t)ts )); + cson_object_set_s(zEntry, zKeyRaw, + json_new_string_f("/raw/%T?name=%t", + fullName, u)); + } + } + db_finalize(&q); + if(pM){ + manifest_destroy(pM); + } + cson_free_array( keyStore ); + + free( zUuid ); + free( zD ); + return cson_object_value(zPayload); +} + +/* +** Implements the /json/dir family of pages/commands. +** +*/ +cson_value * json_page_dir(){ +#if 1 + return json_page_dir_list(); +#else + return json_page_dispatch_helper(&JsonPageDefs_Dir[0]); +#endif +} + +#endif /* FOSSIL_ENABLE_JSON */ ADDED src/json_finfo.c Index: src/json_finfo.c ================================================================== --- /dev/null +++ src/json_finfo.c @@ -0,0 +1,147 @@ +#ifdef FOSSIL_ENABLE_JSON +/* +** Copyright (c) 2011 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/ +** +*/ +#include "VERSION.h" +#include "config.h" +#include "json_finfo.h" + +#if INTERFACE +#include "json_detail.h" +#endif + +/* +** Implements the /json/finfo page/command. +** +*/ +cson_value * json_page_finfo(){ + cson_object * pay = NULL; + cson_array * checkins = NULL; + char const * zFilename = NULL; + Blob sql = empty_blob; + Stmt q = empty_Stmt; + char const * zAfter = NULL; + char const * zBefore = NULL; + int limit = -1; + int currentRow = 0; + char const * zCheckin = NULL; + char sort = -1; + if(!g.perm.Read){ + json_set_err(FSL_JSON_E_DENIED,"Requires 'o' privileges."); + return NULL; + } + json_warn( FSL_JSON_W_UNKNOWN, "Achtung: the output of the finfo command is up for change."); + + /* For the "name" argument we have to jump through some hoops to make sure that we don't + get the fossil-internally-assigned "name" option. + */ + zFilename = json_find_option_cstr2("name",NULL,NULL, g.json.dispatchDepth+1); + if(!zFilename || !*zFilename){ + json_set_err(FSL_JSON_E_MISSING_ARGS, "Missing 'name' parameter."); + return NULL; + } + + if(0==db_int(0,"SELECT 1 FROM filename WHERE name=%Q",zFilename)){ + json_set_err(FSL_JSON_E_RESOURCE_NOT_FOUND, "File entry not found."); + return NULL; + } + + zBefore = json_find_option_cstr("before",NULL,"b"); + zAfter = json_find_option_cstr("after",NULL,"a"); + limit = json_find_option_int("limit",NULL,"n", -1); + zCheckin = json_find_option_cstr("checkin",NULL,"ci"); + + blob_append_sql(&sql, +/*0*/ "SELECT b.uuid," +/*1*/ " ci.uuid," +/*2*/ " (SELECT uuid FROM blob WHERE rid=mlink.fid)," /* Current file uuid */ +/*3*/ " cast(strftime('%%s',event.mtime) AS INTEGER)," +/*4*/ " coalesce(event.euser, event.user)," +/*5*/ " coalesce(event.ecomment, event.comment)," +/*6*/ " (SELECT uuid FROM blob WHERE rid=mlink.pid)," /* Parent file uuid */ +/*7*/ " event.bgcolor," +/*8*/ " b.size," +/*9*/ " (mlink.pid==0) AS isNew," +/*10*/ " (mlink.fid==0) AS isDel" + " FROM mlink, blob b, event, blob ci, filename" + " WHERE filename.name=%Q" + " AND mlink.fnid=filename.fnid" + " AND b.rid=mlink.fid" + " AND event.objid=mlink.mid" + " AND event.objid=ci.rid", + zFilename + ); + + if( zCheckin && *zCheckin ){ + char * zU = NULL; + int rc = name_to_uuid2( zCheckin, "ci", &zU ); + /*printf("zCheckin=[%s], zU=[%s]", zCheckin, zU);*/ + if(rc<=0){ + json_set_err((rc<0) ? FSL_JSON_E_AMBIGUOUS_UUID : FSL_JSON_E_RESOURCE_NOT_FOUND, + "Check-in hash %s.", (rc<0) ? "is ambiguous" : "not found"); + blob_reset(&sql); + return NULL; + } + blob_append_sql(&sql, " AND ci.uuid='%q'", zU); + free(zU); + }else{ + if( zAfter && *zAfter ){ + blob_append_sql(&sql, " AND event.mtime>=julianday('%q')", zAfter); + sort = 1; + }else if( zBefore && *zBefore ){ + blob_append_sql(&sql, " AND event.mtime<=julianday('%q')", zBefore); + } + } + + blob_append_sql(&sql," ORDER BY event.mtime %s /*sort*/", (sort>0?"ASC":"DESC")); + /*printf("SQL=\n%s\n",blob_str(&sql));*/ + db_prepare(&q, "%s", blob_sql_text(&sql)); + blob_reset(&sql); + + pay = cson_new_object(); + cson_object_set(pay, "name", json_new_string(zFilename)); + if( limit > 0 ){ + cson_object_set(pay, "limit", json_new_int(limit)); + } + checkins = cson_new_array(); + cson_object_set(pay, "checkins", cson_array_value(checkins)); + while( db_step(&q)==SQLITE_ROW ){ + cson_object * row = cson_new_object(); + int const isNew = db_column_int(&q,9); + int const isDel = db_column_int(&q,10); + cson_array_append( checkins, cson_object_value(row) ); + cson_object_set(row, "checkin", json_new_string( db_column_text(&q,1) )); + cson_object_set(row, "uuid", json_new_string( db_column_text(&q,2) )); + /*cson_object_set(row, "parentArtifact", json_new_string( db_column_text(&q,6) ));*/ + cson_object_set(row, "timestamp", json_new_int( db_column_int64(&q,3) )); + cson_object_set(row, "user", json_new_string( db_column_text(&q,4) )); + cson_object_set(row, "comment", json_new_string( db_column_text(&q,5) )); + /*cson_object_set(row, "bgColor", json_new_string( db_column_text(&q,7) ));*/ + cson_object_set(row, "size", json_new_int( db_column_int64(&q,8) )); + cson_object_set(row, "state", + json_new_string(json_artifact_status_to_string(isNew,isDel))); + if( (0 < limit) && (++currentRow >= limit) ){ + break; + } + } + db_finalize(&q); + + return pay ? cson_object_value(pay) : NULL; +} + + + +#endif /* FOSSIL_ENABLE_JSON */ ADDED src/json_login.c Index: src/json_login.c ================================================================== --- /dev/null +++ src/json_login.c @@ -0,0 +1,270 @@ +#ifdef FOSSIL_ENABLE_JSON +/* +** Copyright (c) 2011 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/ +** +*/ + +#include "config.h" +#include "json_login.h" + +#if INTERFACE +#include "json_detail.h" +#endif + + +/* +** Implementation of the /json/login page. +** +*/ +cson_value * json_page_login(){ + char preciseErrors = /* if true, "complete" JSON error codes are used, + else they are "dumbed down" to a generic login + error code. + */ +#if 1 + g.json.errorDetailParanoia ? 0 : 1 +#else + 0 +#endif + ; + /* + FIXME: we want to check the GET/POST args in this order: + + - GET: name, n, password, p + - POST: name, password + + but fossil's age-old behaviour of treating the last element of + PATH_INFO as the value for the name parameter breaks that. + + Summary: If we check for P("name") first, then P("n"), then ONLY a + GET param of "name" will match ("n" is not recognized). If we + reverse the order of the checks then both forms work. The + "p"/"password" check is not affected by this. + */ + char const * name = cson_value_get_cstr(json_req_payload_get("name")); + char const * pw = NULL; + char const * anonSeed = NULL; + cson_value * payload = NULL; + int uid = 0; + /* reminder to self: Fossil internally (for the sake of /wiki) + interprets paths in the form /foo/bar/baz such that P("name") == + "bar/baz". This collides with our name/password checking, and + thus we do some rather elaborate name=... checking. + */ + pw = cson_value_get_cstr(json_req_payload_get("password")); + if( !pw ){ + pw = PD("p",NULL); + if( !pw ){ + pw = PD("password",NULL); + } + } + if(!pw){ + g.json.resultCode = preciseErrors + ? FSL_JSON_E_LOGIN_FAILED_NOPW + : FSL_JSON_E_LOGIN_FAILED; + return NULL; + } + + if( !name ){ + name = PD("n",NULL); + if( !name ){ + name = PD("name",NULL); + if( !name ){ + g.json.resultCode = preciseErrors + ? FSL_JSON_E_LOGIN_FAILED_NONAME + : FSL_JSON_E_LOGIN_FAILED; + return NULL; + } + } + } + + if(0 == strcmp("anonymous",name)){ + /* check captcha/seed values... */ + enum { SeedBufLen = 100 /* in some JSON tests i once actually got an + 80-digit number. + */ + }; + static char seedBuffer[SeedBufLen]; + cson_value const * jseed = json_getenv(FossilJsonKeys.anonymousSeed); + seedBuffer[0] = 0; + if( !jseed ){ + jseed = json_req_payload_get(FossilJsonKeys.anonymousSeed); + if( !jseed ){ + jseed = json_getenv("cs") /* name used by HTML interface */; + } + } + if(jseed){ + if( cson_value_is_number(jseed) ){ + sprintf(seedBuffer, "%"CSON_INT_T_PFMT, cson_value_get_integer(jseed)); + anonSeed = seedBuffer; + }else if( cson_value_is_string(jseed) ){ + anonSeed = cson_string_cstr(cson_value_get_string(jseed)); + } + } + if(!anonSeed){ + g.json.resultCode = preciseErrors + ? FSL_JSON_E_LOGIN_FAILED_NOSEED + : FSL_JSON_E_LOGIN_FAILED; + return NULL; + } + } + +#if 0 + { + /* only for debugging the PD()-incorrect-result problem */ + cson_object * o = NULL; + uid = login_search_uid( &name, pw ); + payload = cson_value_new_object(); + o = cson_value_get_object(payload); + cson_object_set( o, "n", cson_value_new_string(name,strlen(name))); + cson_object_set( o, "p", cson_value_new_string(pw,strlen(pw))); + return payload; + } +#endif + uid = anonSeed + ? login_is_valid_anonymous(name, pw, anonSeed) + : login_search_uid(&name, pw) + ; + if( !uid ){ + g.json.resultCode = preciseErrors + ? FSL_JSON_E_LOGIN_FAILED_NOTFOUND + : FSL_JSON_E_LOGIN_FAILED; + return NULL; + }else{ + char * cookie = NULL; + cson_object * po; + char * cap = NULL; + if(anonSeed){ + login_set_anon_cookie(NULL, &cookie, 0); + }else{ + login_set_user_cookie(name, uid, &cookie, 0); + } + payload = cson_value_new_object(); + po = cson_value_get_object(payload); + cson_object_set(po, "authToken", json_new_string(cookie)); + free(cookie); + cson_object_set(po, "name", json_new_string(name)); + cap = db_text(NULL, "SELECT cap FROM user WHERE login=%Q", name); + cson_object_set(po, "capabilities", cap ? json_new_string(cap) : cson_value_null() ); + free(cap); + cson_object_set(po, "loginCookieName", json_new_string( login_cookie_name() ) ); + /* TODO: add loginExpiryTime to the payload. To do this properly + we "should" add an ([unsigned] int *) to + login_set_user_cookie() and login_set_anon_cookie(), to which + the expiry time is assigned. (Remember that JSON doesn't do + unsigned int.) + + For non-anonymous users we could also simply query the + user.cexpire db field after calling login_set_user_cookie(), + but for anonymous we need to get the time when the cookie is + set because anon does not get a db entry like normal users + do. Anonymous cookies currently have a hard-coded lifetime in + login_set_anon_cookie() (currently 6 hours), which we "should + arguably" change to use the time configured for non-anonymous + users (see login_set_user_cookie() for details). + */ + return payload; + } +} + +/* +** Impl of /json/logout. +** +*/ +cson_value * json_page_logout(){ + cson_value const *token = g.json.authToken; + /* Remember that json_bootstrap_late() replaces the login cookie + with the JSON auth token if the request contains it. If the + request is missing the auth token then this will fetch fossil's + original cookie. Either way, it's what we want :). + + We require the auth token to avoid someone maliciously + trying to log someone else out (not 100% sure if that + would be possible, given fossil's hardened cookie, but + I'll assume it would be for the time being). + */ + ; + if(!token){ + g.json.resultCode = FSL_JSON_E_MISSING_AUTH; + }else{ + login_clear_login_data(); + g.json.authToken = NULL /* memory is owned elsewhere.*/; + json_setenv(FossilJsonKeys.authToken, NULL); + } + return json_page_whoami(); +} + +/* +** Implementation of the /json/anonymousPassword page. +*/ +cson_value * json_page_anon_password(){ + cson_value * v = cson_value_new_object(); + cson_object * o = cson_value_get_object(v); + unsigned const int seed = captcha_seed(); + char const * zCaptcha = captcha_decode(seed); + cson_object_set(o, "seed", + cson_value_new_integer( (cson_int_t)seed ) + ); + cson_object_set(o, "password", + cson_value_new_string( zCaptcha, strlen(zCaptcha) ) + ); + return v; +} + + + +/* +** Implements the /json/whoami page/command. +*/ +cson_value * json_page_whoami(){ + cson_value * payload = NULL; + cson_object * obj = NULL; + Stmt q; + if(!g.json.authToken && g.userUid==0){ + /* assume we just logged out. */ + db_prepare(&q, "SELECT login, cap FROM user WHERE login='nobody'"); + } + else{ + db_prepare(&q, "SELECT login, cap FROM user WHERE uid=%d", + g.userUid); + } + if( db_step(&q)==SQLITE_ROW ){ + + /* reminder: we don't use g.zLogin because it's 0 for the guest + user and the HTML UI appears to currently allow the name to be + changed (but doing so would break other code). */ + char const * str; + payload = cson_value_new_object(); + obj = cson_value_get_object(payload); + str = (char const *)sqlite3_column_text(q.pStmt,0); + if( str ){ + cson_object_set( obj, "name", + cson_value_new_string(str,strlen(str)) ); + } + str = (char const *)sqlite3_column_text(q.pStmt,1); + if( str ){ + cson_object_set( obj, "capabilities", + cson_value_new_string(str,strlen(str)) ); + } + if( g.json.authToken ){ + cson_object_set( obj, "authToken", g.json.authToken ); + } + }else{ + g.json.resultCode = FSL_JSON_E_RESOURCE_NOT_FOUND; + } + db_finalize(&q); + return payload; +} +#endif /* FOSSIL_ENABLE_JSON */ ADDED src/json_query.c Index: src/json_query.c ================================================================== --- /dev/null +++ src/json_query.c @@ -0,0 +1,96 @@ +#ifdef FOSSIL_ENABLE_JSON +/* +** Copyright (c) 2011 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/ +** +*/ + +#include "config.h" +#include "json_query.h" + +#if INTERFACE +#include "json_detail.h" +#endif + + +/* +** Implementation of the /json/query page. +** +** Requires admin privileges. Intended primarily to assist me in +** coming up with JSON output structures for pending features. +** +** Options/parameters: +** +** sql=string - a SELECT statement +** +** format=string 'a' means each row is an Array of values, 'o' +** (default) creates each row as an Object. +** +** TODO: in CLI mode (only) use -S FILENAME to read the sql +** from a file. +*/ +cson_value * json_page_query(){ + char const * zSql = NULL; + cson_value * payV; + char const * zFmt; + Stmt q = empty_Stmt; + int check; + if(!g.perm.Admin && !g.perm.Setup){ + json_set_err(FSL_JSON_E_DENIED, + "Requires 'a' or 's' privileges."); + return NULL; + } + + if( cson_value_is_string(g.json.reqPayload.v) ){ + zSql = cson_string_cstr(cson_value_get_string(g.json.reqPayload.v)); + }else{ + zSql = json_find_option_cstr2("sql",NULL,"s",2); + } + + if(!zSql || !*zSql){ + json_set_err(FSL_JSON_E_MISSING_ARGS, + "'sql' (-s) argument is missing."); + return NULL; + } + + zFmt = json_find_option_cstr2("format",NULL,"f",3); + if(!zFmt) zFmt = "o"; + db_prepare(&q,"%s", zSql/*safe-for-%s*/); + if( 0 == sqlite3_column_count( q.pStmt ) ){ + json_set_err(FSL_JSON_E_USAGE, + "Input query has no result columns. " + "Only SELECT-like queries are supported."); + db_finalize(&q); + return NULL; + } + switch(*zFmt){ + case 'a': + check = cson_sqlite3_stmt_to_json(q.pStmt, &payV, 0); + break; + case 'o': + default: + check = cson_sqlite3_stmt_to_json(q.pStmt, &payV, 1); + }; + db_finalize(&q); + if(0 != check){ + json_set_err(FSL_JSON_E_UNKNOWN, + "Conversion to JSON failed with cson code #%d (%s).", + check, cson_rc_string(check)); + assert(NULL==payV); + } + return payV; + +} + +#endif /* FOSSIL_ENABLE_JSON */ ADDED src/json_report.c Index: src/json_report.c ================================================================== --- /dev/null +++ src/json_report.c @@ -0,0 +1,263 @@ +#ifdef FOSSIL_ENABLE_JSON +/* +** Copyright (c) 2011 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/ +** +*/ + +#include "config.h" +#include "json_report.h" + +#if INTERFACE +#include "json_detail.h" +#endif + + +static cson_value * json_report_create(); +static cson_value * json_report_get(); +static cson_value * json_report_list(); +static cson_value * json_report_run(); +static cson_value * json_report_save(); + +/* +** Mapping of /json/report/XXX commands/paths to callbacks. +*/ +static const JsonPageDef JsonPageDefs_Report[] = { +{"create", json_report_create, 0}, +{"get", json_report_get, 0}, +{"list", json_report_list, 0}, +{"run", json_report_run, 0}, +{"save", json_report_save, 0}, +/* Last entry MUST have a NULL name. */ +{NULL,NULL,0} +}; +/* +** Implementation of the /json/report page. +** +** +*/ +cson_value * json_page_report(){ + if(!g.perm.RdTkt && !g.perm.NewTkt ){ + json_set_err(FSL_JSON_E_DENIED, + "Requires 'r' or 'n' permissions."); + return NULL; + } + return json_page_dispatch_helper(JsonPageDefs_Report); +} + +/* +** Searches the environment for a "report" parameter +** (CLI: -report/-r #). +** +** If one is not found and argPos is >0 then json_command_arg() +** is checked. +** +** Returns >0 (the report number) on success . +*/ +static int json_report_get_number(int argPos){ + int nReport = json_find_option_int("report",NULL,"r",-1); + if( (nReport<=0) && cson_value_is_integer(g.json.reqPayload.v)){ + nReport = cson_value_get_integer(g.json.reqPayload.v); + } + if( (nReport <= 0) && (argPos>0) ){ + char const * arg = json_command_arg(argPos); + if(arg && fossil_isdigit(*arg)) { + nReport = atoi(arg); + } + } + return nReport; +} + +static cson_value * json_report_create(){ + json_set_err(FSL_JSON_E_NYI, NULL); + return NULL; +} + +static cson_value * json_report_get(){ + int nReport; + Stmt q = empty_Stmt; + cson_value * pay = NULL; + + if(!g.perm.TktFmt){ + json_set_err(FSL_JSON_E_DENIED, + "Requires 't' privileges."); + return NULL; + } + nReport = json_report_get_number(3); + if(nReport <=0){ + json_set_err(FSL_JSON_E_MISSING_ARGS, + "Missing or invalid 'report' (-r) parameter."); + return NULL; + } + + db_prepare(&q,"SELECT rn AS report," + " owner AS owner," + " title AS title," + " cast(strftime('%%s',mtime) as int) as timestamp," + " cols as columns," + " sqlcode as sqlCode" + " FROM reportfmt" + " WHERE rn=%d", + nReport); + if( SQLITE_ROW != db_step(&q) ){ + db_finalize(&q); + json_set_err(FSL_JSON_E_RESOURCE_NOT_FOUND, + "Report #%d not found.", nReport); + return NULL; + } + pay = cson_sqlite3_row_to_object(q.pStmt); + db_finalize(&q); + return pay; +} + +/* +** Impl of /json/report/list. +*/ +static cson_value * json_report_list(){ + Blob sql = empty_blob; + cson_value * pay = NULL; + if(!g.perm.RdTkt){ + json_set_err(FSL_JSON_E_DENIED, + "Requires 'r' privileges."); + return NULL; + } + blob_append(&sql, "SELECT" + " rn AS report," + " title as title," + " owner as owner" + " FROM reportfmt" + " WHERE 1" + " ORDER BY title", + -1); + pay = json_sql_to_array_of_obj(&sql, NULL, 1); + if(!pay){ + json_set_err(FSL_JSON_E_UNKNOWN, + "Quite unexpected: no ticket reports found."); + } + return pay; +} + +/* +** Impl for /json/report/run +** +** Options/arguments: +** +** report=int (CLI: -report # or -r #) is the report number to run. +** +** limit=int (CLI: -limit # or -n #) -n is for compat. with other commands. +** +** format=a|o Specifies result format: a=each row is an arry, o=each +** row is an object. Default=o. +*/ +static cson_value * json_report_run(){ + int nReport; + Stmt q = empty_Stmt; + cson_object * pay = NULL; + cson_array * tktList = NULL; + char const * zFmt; + char * zTitle = NULL; + Blob sql = empty_blob; + int limit = 0; + cson_value * colNames = NULL; + int i; + + if(!g.perm.RdTkt){ + json_set_err(FSL_JSON_E_DENIED, + "Requires 'r' privileges."); + return NULL; + } + nReport = json_report_get_number(3); + if(nReport <=0){ + json_set_err(FSL_JSON_E_MISSING_ARGS, + "Missing or invalid 'number' (-n) parameter."); + goto error; + } + zFmt = json_find_option_cstr2("format",NULL,"f",3); + if(!zFmt) zFmt = "o"; + db_prepare(&q, + "SELECT sqlcode, " + " title" + " FROM reportfmt" + " WHERE rn=%d", + nReport); + if(SQLITE_ROW != db_step(&q)){ + json_set_err(FSL_JSON_E_INVALID_ARGS, + "Report number %d not found.", + nReport); + db_finalize(&q); + goto error; + } + + limit = json_find_option_int("limit",NULL,"n",-1); + + + /* Copy over report's SQL...*/ + blob_append(&sql, db_column_text(&q,0), -1); + zTitle = mprintf("%s", db_column_text(&q,1)); + db_finalize(&q); + db_prepare(&q, "%s", blob_sql_text(&sql)); + + /** Build the response... */ + pay = cson_new_object(); + + cson_object_set(pay, "report", json_new_int(nReport)); + cson_object_set(pay, "title", json_new_string(zTitle)); + if(limit>0){ + cson_object_set(pay, "limit", json_new_int((limit<0) ? 0 : limit)); + } + free(zTitle); + zTitle = NULL; + + if(g.perm.TktFmt){ + cson_object_set(pay, "sqlcode", + cson_value_new_string(blob_str(&sql), + (unsigned int)blob_size(&sql))); + } + blob_reset(&sql); + + colNames = cson_sqlite3_column_names(q.pStmt); + cson_object_set( pay, "columnNames", colNames); + for( i = 0 ; ((limit>0) ?(i < limit) : 1) + && (SQLITE_ROW == db_step(&q)); + ++i){ + cson_value * row = ('a'==*zFmt) + ? cson_sqlite3_row_to_array(q.pStmt) + : cson_sqlite3_row_to_object2(q.pStmt, + cson_value_get_array(colNames)); + ; + if(row && !tktList){ + tktList = cson_new_array(); + } + cson_array_append(tktList, row); + } + db_finalize(&q); + cson_object_set(pay, "tickets", + tktList ? cson_array_value(tktList) : cson_value_null()); + + goto end; + + error: + assert(0 != g.json.resultCode); + cson_value_free( cson_object_value(pay) ); + pay = NULL; + end: + + return pay ? cson_object_value(pay) : NULL; + +} + +static cson_value * json_report_save(){ + return NULL; +} +#endif /* FOSSIL_ENABLE_JSON */ ADDED src/json_status.c Index: src/json_status.c ================================================================== --- /dev/null +++ src/json_status.c @@ -0,0 +1,178 @@ +#ifdef FOSSIL_ENABLE_JSON +/* +** Copyright (c) 2013 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/ +** +*/ + +#include "config.h" +#include "json_status.h" + +#if INTERFACE +#include "json_detail.h" +#endif + +/* +Reminder to check if a column exists: + +PRAGMA table_info(table_name) + +and search for a row where the 'name' field matches. + +That assumes, of course, that table_info()'s output format +is stable. +*/ + +/* +** Implementation of the /json/status page. +** +*/ +cson_value * json_page_status(){ + Stmt q = empty_Stmt; + cson_object * oPay; + /*cson_object * files;*/ + int vid, nErr = 0; + cson_object * tmpO; + char * zTmp; + i64 iMtime; + cson_array * aFiles; + + if(!db_open_local(0)){ + json_set_err(FSL_JSON_E_DB_NEEDS_CHECKOUT, NULL); + return NULL; + } + oPay = cson_new_object(); + cson_object_set(oPay, "repository", + json_new_string(db_repository_filename())); + cson_object_set(oPay, "localRoot", + json_new_string(g.zLocalRoot)); + vid = db_lget_int("checkout", 0); + if(!vid){ + json_set_err( FSL_JSON_E_UNKNOWN, "Can this even happen?" ); + return 0; + } + /* TODO: dupe show_common_info() state */ + tmpO = cson_new_object(); + cson_object_set(oPay, "checkout", cson_object_value(tmpO)); + + zTmp = db_text(0, "SELECT uuid FROM blob WHERE rid=%d", vid); + cson_object_set(tmpO, "uuid", json_new_string(zTmp) ); + free(zTmp); + + cson_object_set( tmpO, "tags", json_tags_for_checkin_rid(vid, 0) ); + + /* FIXME: optimize the datetime/timestamp queries into 1 query. */ + zTmp = db_text(0, "SELECT datetime(mtime) || " + "' UTC' FROM event WHERE objid=%d", + vid); + cson_object_set(tmpO, "datetime", json_new_string(zTmp)); + free(zTmp); + iMtime = db_int64(0, "SELECT CAST(strftime('%%s',mtime) AS INTEGER) " + "FROM event WHERE objid=%d", vid); + cson_object_set(tmpO, "timestamp", + cson_value_new_integer((cson_int_t)iMtime)); +#if 0 + /* TODO: add parent artifact info */ + tmpO = cson_new_object(); + cson_object_set( oPay, "parent", cson_object_value(tmpO) ); + cson_object_set( tmpO, "uuid", TODO ); + cson_object_set( tmpO, "timestamp", TODO ); +#endif + + /* Now get the list of non-pristine files... */ + aFiles = cson_new_array(); + cson_object_set( oPay, "files", cson_array_value( aFiles ) ); + + db_prepare(&q, + "SELECT pathname, deleted, chnged, rid, coalesce(origname!=pathname,0)" + " FROM vfile " + " WHERE is_selected(id)" + " AND (chnged OR deleted OR rid=0 OR pathname!=origname) ORDER BY 1" + ); + while( db_step(&q)==SQLITE_ROW ){ + const char *zPathname = db_column_text(&q,0); + int isDeleted = db_column_int(&q, 1); + int isChnged = db_column_int(&q,2); + int isNew = db_column_int(&q,3)==0; + int isRenamed = db_column_int(&q,4); + cson_object * oFile; + char const * zStatus = "???"; + char * zFullName = mprintf("%s%s", g.zLocalRoot, zPathname); + if( isDeleted ){ + zStatus = "deleted"; + }else if( isNew ){ + zStatus = "new" /* maintenance reminder: MUST come + BEFORE the isChnged checks. */; + }else if( isRenamed ){ + zStatus = "renamed"; + }else if( !file_isfile_or_link(zFullName) ){ + if( file_access(zFullName, F_OK)==0 ){ + zStatus = "notAFile"; + ++nErr; + }else{ + zStatus = "missing"; + ++nErr; + } + }else if( 2==isChnged ){ + zStatus = "updatedByMerge"; + }else if( 3==isChnged ){ + zStatus = "addedByMerge"; + }else if( 4==isChnged ){ + zStatus = "updatedByIntegrate"; + }else if( 5==isChnged ){ + zStatus = "addedByIntegrate"; + }else if( 1==isChnged ){ + if( file_contains_merge_marker(zFullName) ){ + zStatus = "conflict"; + }else{ + zStatus = "edited"; + } + } + + oFile = cson_new_object(); + cson_array_append( aFiles, cson_object_value(oFile) ); + /* optimization potential: move these keys into cson_strings + to take advantage of refcounting. */ + cson_object_set( oFile, "name", json_new_string( zPathname ) ); + cson_object_set( oFile, "status", json_new_string( zStatus ) ); + + free(zFullName); + } + cson_object_set( oPay, "errorCount", json_new_int( nErr ) ); + db_finalize(&q); + +#if 0 + /* TODO: add "merged with" status. First need (A) to decide on a + structure and (B) to set up some tests for the multi-merge + case.*/ + db_prepare(&q, "SELECT mhash, id FROM vmerge WHERE id<=0"); + while( db_step(&q)==SQLITE_ROW ){ + const char *zLabel = "MERGED_WITH"; + switch( db_column_int(&q, 1) ){ + case -1: zLabel = "CHERRYPICK "; break; + case -2: zLabel = "BACKOUT "; break; + case -4: zLabel = "INTEGRATE "; break; + } + blob_append(report, zPrefix, nPrefix); + blob_appendf(report, "%s %s\n", zLabel, db_column_text(&q, 0)); + } + db_finalize(&q); + if( nErr ){ + fossil_fatal("aborting due to prior errors"); + } +#endif + return cson_object_value( oPay ); +} + +#endif /* FOSSIL_ENABLE_JSON */ ADDED src/json_tag.c Index: src/json_tag.c ================================================================== --- /dev/null +++ src/json_tag.c @@ -0,0 +1,478 @@ +#ifdef FOSSIL_ENABLE_JSON +/* +** Copyright (c) 2011 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/ +** +*/ +#include "VERSION.h" +#include "config.h" +#include "json_tag.h" + +#if INTERFACE +#include "json_detail.h" +#endif + + +static cson_value * json_tag_add(); +static cson_value * json_tag_cancel(); +static cson_value * json_tag_find(); +static cson_value * json_tag_list(); +/* +** Mapping of /json/tag/XXX commands/paths to callbacks. +*/ +static const JsonPageDef JsonPageDefs_Tag[] = { +{"add", json_tag_add, 0}, +{"cancel", json_tag_cancel, 0}, +{"find", json_tag_find, 0}, +{"list", json_tag_list, 0}, +/* Last entry MUST have a NULL name. */ +{NULL,NULL,0} +}; + +/* +** Implements the /json/tag family of pages/commands. +** +*/ +cson_value * json_page_tag(){ + return json_page_dispatch_helper(&JsonPageDefs_Tag[0]); +} + + +/* +** Impl of /json/tag/add. +*/ +static cson_value * json_tag_add(){ + cson_value * payV = NULL; + cson_object * pay = NULL; + char const * zName = NULL; + char const * zCheckin = NULL; + char fRaw = 0; + char fPropagate = 0; + char const * zValue = NULL; + const char *zPrefix = NULL; + + if( !g.perm.Write ){ + json_set_err(FSL_JSON_E_DENIED, + "Requires 'i' permissions."); + return NULL; + } + fRaw = json_find_option_bool("raw",NULL,NULL,0); + fPropagate = json_find_option_bool("propagate",NULL,NULL,0); + zName = json_find_option_cstr("name",NULL,NULL); + zPrefix = fRaw ? "" : "sym-"; + if(!zName || !*zName){ + if(!fossil_has_json()){ + zName = json_command_arg(3); + } + if(!zName || !*zName){ + json_set_err(FSL_JSON_E_MISSING_ARGS, + "'name' parameter is missing."); + return NULL; + } + } + + zCheckin = json_find_option_cstr("checkin",NULL,NULL); + if( !zCheckin ){ + if(!fossil_has_json()){ + zCheckin = json_command_arg(4); + } + if(!zCheckin || !*zCheckin){ + json_set_err(FSL_JSON_E_MISSING_ARGS, + "'checkin' parameter is missing."); + return NULL; + } + } + + + zValue = json_find_option_cstr("value",NULL,NULL); + if(!zValue && !fossil_has_json()){ + zValue = json_command_arg(5); + } + + db_begin_transaction(); + tag_add_artifact(zPrefix, zName, zCheckin, zValue, + 1+fPropagate,NULL/*DateOvrd*/,NULL/*UserOvrd*/); + db_end_transaction(0); + + payV = cson_value_new_object(); + pay = cson_value_get_object(payV); + cson_object_set(pay, "name", json_new_string(zName) ); + cson_object_set(pay, "value", (zValue&&*zValue) + ? json_new_string(zValue) + : cson_value_null()); + cson_object_set(pay, "propagate", cson_value_new_bool(fPropagate)); + cson_object_set(pay, "raw", cson_value_new_bool(fRaw)); + { + Blob uu = empty_blob; + int rc; + blob_append(&uu, zName, -1); + rc = name_to_uuid(&uu, 9, "*"); + if(0!=rc){ + json_set_err(FSL_JSON_E_UNKNOWN,"Could not convert name back to artifact hash!"); + blob_reset(&uu); + goto error; + } + cson_object_set(pay, "appliedTo", json_new_string(blob_buffer(&uu))); + blob_reset(&uu); + } + + goto ok; + error: + assert( 0 != g.json.resultCode ); + cson_value_free(payV); + payV = NULL; + ok: + return payV; +} + + +/* +** Impl of /json/tag/cancel. +*/ +static cson_value * json_tag_cancel(){ + char const * zName = NULL; + char const * zCheckin = NULL; + char fRaw = 0; + const char *zPrefix = NULL; + + if( !g.perm.Write ){ + json_set_err(FSL_JSON_E_DENIED, + "Requires 'i' permissions."); + return NULL; + } + + fRaw = json_find_option_bool("raw",NULL,NULL,0); + zPrefix = fRaw ? "" : "sym-"; + zName = json_find_option_cstr("name",NULL,NULL); + if(!zName || !*zName){ + if(!fossil_has_json()){ + zName = json_command_arg(3); + } + if(!zName || !*zName){ + json_set_err(FSL_JSON_E_MISSING_ARGS, + "'name' parameter is missing."); + return NULL; + } + } + + zCheckin = json_find_option_cstr("checkin",NULL,NULL); + if( !zCheckin ){ + if(!fossil_has_json()){ + zCheckin = json_command_arg(4); + } + if(!zCheckin || !*zCheckin){ + json_set_err(FSL_JSON_E_MISSING_ARGS, + "'checkin' parameter is missing."); + return NULL; + } + } + /* FIXME?: verify that the tag is currently active. We have no real + error case unless we do that. + */ + db_begin_transaction(); + tag_add_artifact(zPrefix, zName, zCheckin, NULL, 0, 0, 0); + db_end_transaction(0); + return NULL; +} + + +/* +** Impl of /json/tag/find. +*/ +static cson_value * json_tag_find(){ + cson_value * payV = NULL; + cson_object * pay = NULL; + cson_value * listV = NULL; + cson_array * list = NULL; + char const * zName = NULL; + char const * zType = NULL; + char const * zType2 = NULL; + char fRaw = 0; + Stmt q = empty_Stmt; + int limit = 0; + int tagid = 0; + + if( !g.perm.Read ){ + json_set_err(FSL_JSON_E_DENIED, + "Requires 'o' permissions."); + return NULL; + } + zName = json_find_option_cstr("name",NULL,NULL); + if(!zName || !*zName){ + if(!fossil_has_json()){ + zName = json_command_arg(3); + } + if(!zName || !*zName){ + json_set_err(FSL_JSON_E_MISSING_ARGS, + "'name' parameter is missing."); + return NULL; + } + } + zType = json_find_option_cstr("type",NULL,"t"); + if(!zType || !*zType){ + zType = "*"; + zType2 = zType; + }else{ + switch(*zType){ + case 'c': zType = "ci"; zType2 = "checkin"; break; + case 'e': zType = "e"; zType2 = "event"; break; + case 'w': zType = "w"; zType2 = "wiki"; break; + case 't': zType = "t"; zType2 = "ticket"; break; + } + } + + limit = json_find_option_int("limit",NULL,"n",0); + fRaw = json_find_option_bool("raw",NULL,NULL,0); + + tagid = db_int(0, "SELECT tagid FROM tag WHERE tagname='%s' || %Q", + fRaw ? "" : "sym-", + zName); + + payV = cson_value_new_object(); + pay = cson_value_get_object(payV); + cson_object_set(pay, "name", json_new_string(zName)); + cson_object_set(pay, "raw", cson_value_new_bool(fRaw)); + cson_object_set(pay, "type", json_new_string(zType2)); + cson_object_set(pay, "limit", json_new_int(limit)); + +#if 1 + if( tagid<=0 ){ + cson_object_set(pay,"artifacts", cson_value_null()); + json_warn(FSL_JSON_W_TAG_NOT_FOUND, "Tag not found."); + return payV; + } +#endif + + if( fRaw ){ + db_prepare(&q, + "SELECT blob.uuid FROM tagxref, blob" + " WHERE tagid=(SELECT tagid FROM tag WHERE tagname=%Q)" + " AND tagxref.tagtype>0" + " AND blob.rid=tagxref.rid" + "%s LIMIT %d", + zName, + (limit>0)?"":"--", limit + ); + while( db_step(&q)==SQLITE_ROW ){ + if(!listV){ + listV = cson_value_new_array(); + list = cson_value_get_array(listV); + } + cson_array_append(list, cson_sqlite3_column_to_value(q.pStmt,0)); + } + db_finalize(&q); + }else{ + char const * zSqlBase = /*modified from timeline_query_for_tty()*/ + " SELECT" +#if 0 + " blob.rid AS rid," +#endif + " uuid AS uuid," + " cast(strftime('%s',event.mtime) as int) AS timestamp," + " coalesce(ecomment,comment) AS comment," + " coalesce(euser,user) AS user," + " CASE event.type" + " WHEN 'ci' THEN 'checkin'" + " WHEN 'w' THEN 'wiki'" + " WHEN 'e' THEN 'event'" + " WHEN 't' THEN 'ticket'" + " ELSE 'unknown'" + " END" + " AS eventType" + " FROM event, blob" + " WHERE blob.rid=event.objid" + ; + /* FIXME: re-add tags. */ + db_prepare(&q, + "%s" + " AND event.type GLOB '%q'" + " AND blob.rid IN (" + " SELECT rid FROM tagxref" + " WHERE tagtype>0 AND tagid=%d" + " )" + " ORDER BY event.mtime DESC" + "%s LIMIT %d", + zSqlBase /*safe-for-%s*/, zType, tagid, + (limit>0)?"":"--", limit + ); + listV = json_stmt_to_array_of_obj(&q, NULL); + db_finalize(&q); + } + + if(!listV) { + listV = cson_value_null(); + } + cson_object_set(pay, "artifacts", listV); + return payV; +} + + +/* +** Impl for /json/tag/list +** +** TODOs: +** +** Add -type TYPE (ci, w, e, t) +*/ +static cson_value * json_tag_list(){ + cson_value * payV = NULL; + cson_object * pay = NULL; + cson_value const * tagsVal = NULL; + char const * zCheckin = NULL; + char fRaw = 0; + char fTicket = 0; + Stmt q = empty_Stmt; + + if( !g.perm.Read ){ + json_set_err(FSL_JSON_E_DENIED, + "Requires 'o' permissions."); + return NULL; + } + + fRaw = json_find_option_bool("raw",NULL,NULL,0); + fTicket = json_find_option_bool("includeTickets","tkt","t",0); + zCheckin = json_find_option_cstr("checkin",NULL,NULL); + if( !zCheckin ){ + zCheckin = json_command_arg( g.json.dispatchDepth + 1); + if( !zCheckin && cson_value_is_string(g.json.reqPayload.v) ){ + zCheckin = cson_string_cstr(cson_value_get_string(g.json.reqPayload.v)); + assert(zCheckin); + } + } + payV = cson_value_new_object(); + pay = cson_value_get_object(payV); + cson_object_set(pay, "raw", cson_value_new_bool(fRaw) ); + if( zCheckin ){ + /** + Tags for a specific check-in. Output format: + + RAW mode: + + { + "sym-tagname": (value || null), + ...other tags... + } + + Non-raw: + + { + "tagname": (value || null), + ...other tags... + } + */ + cson_value * objV = NULL; + cson_object * obj = NULL; + int const rid = name_to_rid(zCheckin); + if(0==rid){ + json_set_err(FSL_JSON_E_UNRESOLVED_UUID, + "Could not find artifact for check-in [%s].", + zCheckin); + goto error; + } + cson_object_set(pay, "checkin", json_new_string(zCheckin)); + db_prepare(&q, + "SELECT tagname, value FROM tagxref, tag" + " WHERE tagxref.rid=%d AND tagxref.tagid=tag.tagid" + " AND tagtype>%d" + " ORDER BY tagname", + rid, + fRaw ? -1 : 0 + ); + while( SQLITE_ROW == db_step(&q) ){ + const char *zName = db_column_text(&q, 0); + const char *zValue = db_column_text(&q, 1); + if( fRaw==0 ){ + if( 0!=strncmp(zName, "sym-", 4) ) continue; + zName += 4; + assert( *zName ); + } + if(NULL==objV){ + objV = cson_value_new_object(); + obj = cson_value_get_object(objV); + tagsVal = objV; + cson_object_set( pay, "tags", objV ); + } + if( zValue && zValue[0] ){ + cson_object_set( obj, zName, json_new_string(zValue) ); + }else{ + cson_object_set( obj, zName, cson_value_null() ); + } + } + db_finalize(&q); + }else{/* all tags */ + /* Output format: + + RAW mode: + + ["tagname", "sym-tagname2",...] + + Non-raw: + + ["tagname", "tagname2",...] + + i don't really like the discrepancy in the format but this list + can get really long and (A) most tags don't have values, (B) i + don't want to bloat it more, and (C) cson_object_set() is O(N) + (N=current number of properties) because it uses an unsorted list + internally (for memory reasons), so this can slow down appreciably + on a long list. The culprit is really tkt- tags, as there is one + for each ticket (941 in the main fossil repo as of this writing). + */ + Blob sql = empty_blob; + cson_value * arV = NULL; + cson_array * ar = NULL; + blob_append(&sql, + "SELECT tagname FROM tag" + " WHERE EXISTS(SELECT 1 FROM tagxref" + " WHERE tagid=tag.tagid" + " AND tagtype>0)", + -1 + ); + if(!fTicket){ + blob_append(&sql, " AND tagname NOT GLOB('tkt-*') ", -1); + } + blob_append(&sql, + " ORDER BY tagname", -1); + db_prepare(&q, "%s", blob_sql_text(&sql)); + blob_reset(&sql); + cson_object_set(pay, "includeTickets", cson_value_new_bool(fTicket) ); + while( SQLITE_ROW == db_step(&q) ){ + const char *zName = db_column_text(&q, 0); + if(NULL==arV){ + arV = cson_value_new_array(); + ar = cson_value_get_array(arV); + cson_object_set(pay, "tags", arV); + tagsVal = arV; + } + else if( !fRaw && (0==strncmp(zName, "sym-", 4))){ + zName += 4; + assert( *zName ); + } + cson_array_append(ar, json_new_string(zName)); + } + db_finalize(&q); + } + + goto end; + error: + assert(0 != g.json.resultCode); + cson_value_free(payV); + payV = NULL; + end: + if( payV && !tagsVal ){ + cson_object_set( pay, "tags", cson_value_null() ); + } + return payV; +} +#endif /* FOSSIL_ENABLE_JSON */ ADDED src/json_timeline.c Index: src/json_timeline.c ================================================================== --- /dev/null +++ src/json_timeline.c @@ -0,0 +1,760 @@ +#ifdef FOSSIL_ENABLE_JSON +/* +** Copyright (c) 2011 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/ +** +*/ + +#include "VERSION.h" +#include "config.h" +#include "json_timeline.h" + +#if INTERFACE +#include "json_detail.h" +#endif + +static cson_value * json_timeline_branch(); +static cson_value * json_timeline_ci(); +static cson_value * json_timeline_ticket(); +/* +** Mapping of /json/timeline/XXX commands/paths to callbacks. +*/ +static const JsonPageDef JsonPageDefs_Timeline[] = { +/* the short forms are only enabled in CLI mode, to avoid + that we end up with HTTP clients using 3 different names + for the same requests. +*/ +{"branch", json_timeline_branch, 0}, +{"checkin", json_timeline_ci, 0}, +{"event" /* old name for technotes */, json_timeline_event, 0}, +{"technote", json_timeline_event, 0}, +{"ticket", json_timeline_ticket, 0}, +{"wiki", json_timeline_wiki, 0}, +/* Last entry MUST have a NULL name. */ +{NULL,NULL,0} +}; + + +/* +** Implements the /json/timeline family of pages/commands. Far from +** complete. +** +*/ +cson_value * json_page_timeline(){ +#if 0 + /* The original timeline code does not require 'h' access, + but it arguably should. For JSON mode i think one could argue + that History permissions are required. + */ + if(! g.perm.Hyperlink && !g.perm.Read ){ + json_set_err(FSL_JSON_E_DENIED, "Timeline requires 'h' or 'o' access."); + return NULL; + } +#endif + return json_page_dispatch_helper(&JsonPageDefs_Timeline[0]); +} + +/* +** Create a temporary table suitable for storing timeline data. +*/ +static void json_timeline_temp_table(void){ + /* Field order MUST match that from json_timeline_query()!!! */ + static const char zSql[] = + @ CREATE TEMP TABLE IF NOT EXISTS json_timeline( + @ sortId INTEGER PRIMARY KEY, + @ rid INTEGER, + @ uuid TEXT, + @ mtime INTEGER, + @ timestampString TEXT, + @ comment TEXT, + @ user TEXT, + @ isLeaf BOOLEAN, + @ bgColor TEXT, + @ eventType TEXT, + @ tags TEXT, + @ tagId INTEGER, + @ brief TEXT + @ ) + ; + db_multi_exec("%s", zSql /*safe-for-%s*/); +} + +/* +** Return a pointer to a constant string that forms the basis +** for a timeline query for the JSON interface. It MUST NOT +** be used in a formatted string argument. +*/ +char const * json_timeline_query(void){ + /* Field order MUST match that from json_timeline_temp_table()!!! */ + static const char zBaseSql[] = + @ SELECT + @ NULL, + @ blob.rid, + @ uuid, + @ CAST(strftime('%s',event.mtime) AS INTEGER), + @ datetime(event.mtime), + @ coalesce(ecomment, comment), + @ coalesce(euser, user), + @ blob.rid IN leaf, + @ bgcolor, + @ event.type, + @ (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, + @ tagid as tagId, + @ brief as brief + @ FROM event JOIN blob + @ WHERE blob.rid=event.objid + ; + return zBaseSql; +} + +/* +** Internal helper to append query information if the +** "tag" or "branch" request properties (CLI: --tag/--branch) +** are set. Limits the query to a particular branch/tag. +** +** tag works like HTML mode's "t" option and branch works like HTML +** mode's "r" option. They are very similar, but subtly different - +** tag mode shows only entries with a given tag but branch mode can +** also reveal some with "related" tags (meaning they were merged into +** the requested branch, or back). +** +** pSql is the target blob to append the query [subset] +** to. +** +** Returns a positive value if it modifies pSql, 0 if it +** does not. It returns a negative value if the tag +** provided to the request was not found (pSql is not modified +** in that case). +** +** If payload is not NULL then on success its "tag" or "branch" +** property is set to the tag/branch name found in the request. +** +** Only one of "tag" or "branch" modes will work at a time, and if +** both are specified, which one takes precedence is unspecified. +*/ +static char json_timeline_add_tag_branch_clause(Blob *pSql, + cson_object * pPayload){ + char const * zTag = NULL; + char const * zBranch = NULL; + char const * zMiOnly = NULL; + char const * zUnhide = NULL; + int tagid = 0; + if(! g.perm.Read ){ + return 0; + } + zTag = json_find_option_cstr("tag",NULL,NULL); + if(!zTag || !*zTag){ + zBranch = json_find_option_cstr("branch",NULL,NULL); + if(!zBranch || !*zBranch){ + return 0; + } + zTag = zBranch; + zMiOnly = json_find_option_cstr("mionly",NULL,NULL); + } + zUnhide = json_find_option_cstr("unhide",NULL,NULL); + tagid = db_int(0, "SELECT tagid FROM tag WHERE tagname='sym-%q'", + zTag); + if(tagid<=0){ + return -1; + } + if(pPayload){ + cson_object_set( pPayload, zBranch ? "branch" : "tag", json_new_string(zTag) ); + } + blob_appendf(pSql, + " AND (" + " EXISTS(SELECT 1 FROM tagxref" + " WHERE tagid=%d AND tagtype>0 AND rid=blob.rid)", + tagid); + if(!zUnhide){ + blob_appendf(pSql, + " AND NOT EXISTS(SELECT 1 FROM plink JOIN tagxref ON rid=blob.rid" + " WHERE tagid=%d AND tagtype>0 AND rid=blob.rid)", + TAG_HIDDEN); + } + if(zBranch){ + /* from "r" flag code in page_timeline().*/ + blob_appendf(pSql, + " OR EXISTS(SELECT 1 FROM plink JOIN tagxref ON rid=cid" + " WHERE tagid=%d AND tagtype>0 AND pid=blob.rid)", + tagid); + if( !zUnhide ){ + blob_appendf(pSql, + " AND NOT EXISTS(SELECT 1 FROM plink JOIN tagxref ON rid=cid" + " WHERE tagid=%d AND tagtype>0 AND pid=blob.rid)", + TAG_HIDDEN); + } + if( zMiOnly==0 ){ + blob_appendf(pSql, + " OR EXISTS(SELECT 1 FROM plink JOIN tagxref ON rid=pid" + " WHERE tagid=%d AND tagtype>0 AND cid=blob.rid)", + tagid); + if( !zUnhide ){ + blob_appendf(pSql, + " AND NOT EXISTS(SELECT 1 FROM plink JOIN tagxref ON rid=pid" + " WHERE tagid=%d AND tagtype>0 AND cid=blob.rid)", + TAG_HIDDEN); + } + } + } + blob_append(pSql," ) ",3); + return 1; +} +/* +** Helper for the timeline family of functions. Possibly appends 1 +** AND clause and an ORDER BY clause to pSql, depending on the state +** of the "after" ("a") or "before" ("b") environment parameters. +** This function gives "after" precedence over "before", and only +** applies one of them. +** +** Returns -1 if it adds a "before" clause, 1 if it adds +** an "after" clause, and 0 if adds only an order-by clause. +*/ +static char json_timeline_add_time_clause(Blob *pSql){ + char const * zAfter = NULL; + char const * zBefore = NULL; + int rc = 0; + zAfter = json_find_option_cstr("after",NULL,"a"); + zBefore = zAfter ? NULL : json_find_option_cstr("before",NULL,"b"); + + if(zAfter&&*zAfter){ + while( fossil_isspace(*zAfter) ) ++zAfter; + blob_appendf(pSql, + " AND event.mtime>=(SELECT julianday(%Q,fromLocal())) " + " ORDER BY event.mtime ASC ", + zAfter); + rc = 1; + }else if(zBefore && *zBefore){ + while( fossil_isspace(*zBefore) ) ++zBefore; + blob_appendf(pSql, + " AND event.mtime<=(SELECT julianday(%Q,fromLocal())) " + " ORDER BY event.mtime DESC ", + zBefore); + rc = -1; + }else{ + blob_append(pSql, " ORDER BY event.mtime DESC ", -1); + rc = 0; + } + return rc; +} + +/* +** Tries to figure out a timeline query length limit base on +** environment parameters. If it can it returns that value, +** else it returns some statically defined default value. +** +** Never returns a negative value. 0 means no limit. +*/ +static int json_timeline_limit(int defaultLimit){ + int limit = -1; + if(!g.isHTTP){/* CLI mode */ + char const * arg = find_option("limit","n",1); + if(arg && *arg){ + limit = atoi(arg); + } + } + if( (limit<0) && fossil_has_json() ){ + limit = json_getenv_int("limit",-1); + } + return (limit<0) ? defaultLimit : limit; +} + +/* +** Internal helper for the json_timeline_EVENTTYPE() family of +** functions. zEventType must be one of (ci, w, t). pSql must be a +** cleanly-initialized, empty Blob to store the sql in. If pPayload is +** not NULL it is assumed to be the pending response payload. If +** json_timeline_limit() returns non-0, this function adds a LIMIT +** clause to the generated SQL. +** +** If pPayload is not NULL then this might add properties to pPayload, +** reflecting options set in the request environment. +** +** Returns 0 on success. On error processing should not continue and +** the returned value should be used as g.json.resultCode. +*/ +static int json_timeline_setup_sql( char const * zEventType, + Blob * pSql, + cson_object * pPayload ){ + int limit; + assert( zEventType && *zEventType && pSql ); + json_timeline_temp_table(); + blob_append(pSql, "INSERT OR IGNORE INTO json_timeline ", -1); + blob_append(pSql, json_timeline_query(), -1 ); + blob_appendf(pSql, " AND event.type IN(%Q) ", zEventType); + if( json_timeline_add_tag_branch_clause(pSql, pPayload) < 0 ){ + return FSL_JSON_E_INVALID_ARGS; + } + json_timeline_add_time_clause(pSql); + limit = json_timeline_limit(20); + if(limit>0){ + blob_appendf(pSql,"LIMIT %d ",limit); + } + if(pPayload){ + cson_object_set(pPayload, "limit", json_new_int(limit)); + } + return 0; +} + + +/* +** If any files are associated with the given rid, a JSON array +** containing information about them is returned (and is owned by the +** caller). If no files are associated with it then NULL is returned. +** +** flags may optionally be a bitmask of json_get_changed_files flags, +** or 0 for defaults. +*/ +cson_value * json_get_changed_files(int rid, int flags){ + cson_value * rowsV = NULL; + cson_array * rows = NULL; + Stmt q = empty_Stmt; + db_prepare(&q, + "SELECT (pid<=0) AS isnew," + " (fid==0) AS isdel," + " (SELECT name FROM filename WHERE fnid=mlink.fnid) AS name," + " (SELECT uuid FROM blob WHERE rid=fid) as uuid," + " (SELECT uuid FROM blob WHERE rid=pid) as parent," + " blob.size as size" + " FROM mlink" + " LEFT JOIN blob ON blob.rid=fid" + " WHERE mid=%d AND pid!=fid" + " AND NOT mlink.isaux" + " ORDER BY name /*sort*/", + rid + ); + while( (SQLITE_ROW == db_step(&q)) ){ + cson_value * rowV = cson_value_new_object(); + cson_object * row = cson_value_get_object(rowV); + int const isNew = db_column_int(&q,0); + int const isDel = db_column_int(&q,1); + char * zDownload = NULL; + if(!rowsV){ + rowsV = cson_value_new_array(); + rows = cson_value_get_array(rowsV); + } + cson_array_append( rows, rowV ); + cson_object_set(row, "name", json_new_string(db_column_text(&q,2))); + cson_object_set(row, "uuid", json_new_string(db_column_text(&q,3))); + if(!isNew && (flags & json_get_changed_files_ELIDE_PARENT)){ + cson_object_set(row, "parent", json_new_string(db_column_text(&q,4))); + } + cson_object_set(row, "size", json_new_int(db_column_int(&q,5))); + + cson_object_set(row, "state", + json_new_string(json_artifact_status_to_string(isNew,isDel))); + zDownload = mprintf("/raw/%s?name=%s", + /* reminder: g.zBaseURL is of course not set for CLI mode. */ + db_column_text(&q,2), + db_column_text(&q,3)); + cson_object_set(row, "downloadPath", json_new_string(zDownload)); + free(zDownload); + } + db_finalize(&q); + return rowsV; +} + +static cson_value * json_timeline_branch(){ + cson_value * pay = NULL; + Blob sql = empty_blob; + Stmt q = empty_Stmt; + int limit = 0; + if(!g.perm.Read){ + json_set_err(FSL_JSON_E_DENIED, + "Requires 'o' permissions."); + return NULL; + } + json_timeline_temp_table(); + blob_append(&sql, + "SELECT" + " blob.rid AS rid," + " uuid AS uuid," + " CAST(strftime('%s',event.mtime) AS INTEGER) as timestamp," + " coalesce(ecomment, comment) as comment," + " coalesce(euser, user) as user," + " blob.rid IN leaf as isLeaf," + " bgcolor as bgColor" + " FROM event JOIN blob" + " WHERE blob.rid=event.objid", + -1); + + blob_append_sql(&sql, + " AND event.type='ci'" + " AND blob.rid IN (SELECT rid FROM tagxref" + " WHERE tagtype>0 AND tagid=%d AND srcid!=0)" + " ORDER BY event.mtime DESC", + TAG_BRANCH); + limit = json_timeline_limit(20); + if(limit>0){ + blob_append_sql(&sql," LIMIT %d ",limit); + } + db_prepare(&q,"%s", blob_sql_text(&sql)); + blob_reset(&sql); + pay = json_stmt_to_array_of_obj(&q, NULL); + db_finalize(&q); + assert(NULL != pay); + if(pay){ + /* get the array-form tags of each record. */ + cson_string * tags = cson_new_string("tags",4); + cson_string * isLeaf = cson_new_string("isLeaf",6); + cson_array * ar = cson_value_get_array(pay); + cson_object * outer = NULL; + unsigned int i = 0; + unsigned int len = cson_array_length_get(ar); + cson_value_add_reference( cson_string_value(tags) ); + cson_value_add_reference( cson_string_value(isLeaf) ); + for( ; i < len; ++i ){ + cson_object * row = cson_value_get_object(cson_array_get(ar,i)); + int rid = cson_value_get_integer(cson_object_get(row,"rid")); + assert( rid > 0 ); + cson_object_set_s(row, tags, json_tags_for_checkin_rid(rid,0)); + cson_object_set_s(row, isLeaf, + json_value_to_bool(cson_object_get(row,"isLeaf"))); + cson_object_set(row, "rid", NULL) + /* remove rid - we don't really want it to be public */; + } + cson_value_free( cson_string_value(tags) ); + cson_value_free( cson_string_value(isLeaf) ); + + /* now we wrap the payload in an outer shell, for consistency with + other /json/timeline/xyz APIs... + */ + outer = cson_new_object(); + if(limit>0){ + cson_object_set( outer, "limit", json_new_int(limit) ); + } + cson_object_set( outer, "timeline", pay ); + pay = cson_object_value(outer); + } + return pay; +} + +/* +** Implementation of /json/timeline/ci. +** +** Still a few TODOs (like figuring out how to structure +** inheritance info). +*/ +static cson_value * json_timeline_ci(){ + cson_value * payV = NULL; + cson_object * pay = NULL; + cson_value * tmp = NULL; + cson_value * listV = NULL; + cson_array * list = NULL; + int check = 0; + char verboseFlag; + Stmt q = empty_Stmt; + char warnRowToJsonFailed = 0; + Blob sql = empty_blob; + if( !g.perm.Hyperlink ){ + /* Reminder to self: HTML impl requires 'o' (Read) + rights. + */ + json_set_err( FSL_JSON_E_DENIED, "Check-in timeline requires 'h' access." ); + return NULL; + } + verboseFlag = json_find_option_bool("verbose",NULL,"v",0); + if( !verboseFlag ){ + verboseFlag = json_find_option_bool("files",NULL,"f",0); + } + payV = cson_value_new_object(); + pay = cson_value_get_object(payV); + check = json_timeline_setup_sql( "ci", &sql, pay ); + if(check){ + json_set_err(check, "Query initialization failed."); + goto error; + } +#define SET(K) if(0!=(check=cson_object_set(pay,K,tmp))){ \ + json_set_err((cson_rc.AllocError==check) \ + ? FSL_JSON_E_ALLOC : FSL_JSON_E_UNKNOWN,\ + "Object property insertion failed"); \ + goto error;\ + } (void)0 + +#if 0 + /* only for testing! */ + tmp = cson_value_new_string(blob_buffer(&sql),strlen(blob_buffer(&sql))); + SET("timelineSql"); +#endif + db_multi_exec("%s", blob_buffer(&sql)/*safe-for-%s*/); + blob_reset(&sql); + db_prepare(&q, "SELECT " + " rid AS rid" + " FROM json_timeline" + " ORDER BY rowid"); + listV = cson_value_new_array(); + list = cson_value_get_array(listV); + tmp = listV; + SET("timeline"); + while( (SQLITE_ROW == db_step(&q) )){ + /* convert each row into a JSON object...*/ + int const rid = db_column_int(&q,0); + cson_value * rowV = json_artifact_for_ci(rid, verboseFlag); + cson_object * row = cson_value_get_object(rowV); + if(!row){ + if( !warnRowToJsonFailed ){ + warnRowToJsonFailed = 1; + json_warn( FSL_JSON_W_ROW_TO_JSON_FAILED, + "Could not convert at least one timeline result row to JSON." ); + } + continue; + } + cson_array_append(list, rowV); + } +#undef SET + goto ok; + error: + assert( 0 != g.json.resultCode ); + cson_value_free(payV); + payV = NULL; + ok: + db_finalize(&q); + return payV; +} + +/* +** Implementation of /json/timeline/event. +** +*/ +cson_value * json_timeline_event(){ + /* This code is 95% the same as json_timeline_ci(), by the way. */ + cson_value * payV = NULL; + cson_object * pay = NULL; + cson_array * list = NULL; + int check = 0; + Stmt q = empty_Stmt; + Blob sql = empty_blob; + if( !g.perm.RdWiki ){ + json_set_err( FSL_JSON_E_DENIED, "Event timeline requires 'j' access."); + return NULL; + } + payV = cson_value_new_object(); + pay = cson_value_get_object(payV); + check = json_timeline_setup_sql( "e", &sql, pay ); + if(check){ + json_set_err(check, "Query initialization failed."); + goto error; + } + +#if 0 + /* only for testing! */ + cson_object_set(pay, "timelineSql", cson_value_new_string(blob_buffer(&sql),strlen(blob_buffer(&sql)))); +#endif + db_multi_exec("%s", blob_buffer(&sql) /*safe-for-%s*/); + blob_reset(&sql); + db_prepare(&q, "SELECT" + /* For events, the name is generally more useful than + the uuid, but the uuid is unambiguous and can be used + with commands like 'artifact'. */ + " substr((SELECT tagname FROM tag AS tn WHERE tn.tagid=json_timeline.tagId AND tagname LIKE 'event-%%'),7) AS name," + " uuid as uuid," + " mtime AS timestamp," + " comment AS comment, " + " user AS user," + " eventType AS eventType" + " FROM json_timeline" + " ORDER BY rowid"); + list = cson_new_array(); + json_stmt_to_array_of_obj(&q, list); + cson_object_set(pay, "timeline", cson_array_value(list)); + goto ok; + error: + assert( 0 != g.json.resultCode ); + cson_value_free(payV); + payV = NULL; + ok: + db_finalize(&q); + blob_reset(&sql); + return payV; +} + +/* +** Implementation of /json/timeline/wiki. +** +*/ +cson_value * json_timeline_wiki(){ + /* This code is 95% the same as json_timeline_ci(), by the way. */ + cson_value * payV = NULL; + cson_object * pay = NULL; + cson_array * list = NULL; + int check = 0; + Stmt q = empty_Stmt; + Blob sql = empty_blob; + if( !g.perm.RdWiki && !g.perm.Read ){ + json_set_err( FSL_JSON_E_DENIED, "Wiki timeline requires 'o' or 'j' access."); + return NULL; + } + payV = cson_value_new_object(); + pay = cson_value_get_object(payV); + check = json_timeline_setup_sql( "w", &sql, pay ); + if(check){ + json_set_err(check, "Query initialization failed."); + goto error; + } + +#if 0 + /* only for testing! */ + cson_object_set(pay, "timelineSql", cson_value_new_string(blob_buffer(&sql),strlen(blob_buffer(&sql)))); +#endif + db_multi_exec("%s", blob_buffer(&sql) /*safe-for-%s*/); + blob_reset(&sql); + db_prepare(&q, "SELECT" + " uuid AS uuid," + " mtime AS timestamp," +#if 0 + " timestampString AS timestampString," +#endif + " comment AS comment, " + " user AS user," + " eventType AS eventType" +#if 0 + /* can wiki pages have tags? */ + " tags AS tags," /*FIXME: split this into + a JSON array*/ + " tagId AS tagId," +#endif + " FROM json_timeline" + " ORDER BY rowid"); + list = cson_new_array(); + json_stmt_to_array_of_obj(&q, list); + cson_object_set(pay, "timeline", cson_array_value(list)); + goto ok; + error: + assert( 0 != g.json.resultCode ); + cson_value_free(payV); + payV = NULL; + ok: + db_finalize(&q); + blob_reset(&sql); + return payV; +} + +/* +** Implementation of /json/timeline/ticket. +** +*/ +static cson_value * json_timeline_ticket(){ + /* This code is 95% the same as json_timeline_ci(), by the way. */ + cson_value * payV = NULL; + cson_object * pay = NULL; + cson_value * tmp = NULL; + cson_value * listV = NULL; + cson_array * list = NULL; + int check = 0; + Stmt q = empty_Stmt; + Blob sql = empty_blob; + if( !g.perm.RdTkt && !g.perm.Read ){ + json_set_err(FSL_JSON_E_DENIED, "Ticket timeline requires 'o' or 'r' access."); + return NULL; + } + payV = cson_value_new_object(); + pay = cson_value_get_object(payV); + check = json_timeline_setup_sql( "t", &sql, pay ); + if(check){ + json_set_err(check, "Query initialization failed."); + goto error; + } + + db_multi_exec("%s", blob_buffer(&sql) /*safe-for-%s*/); +#define SET(K) if(0!=(check=cson_object_set(pay,K,tmp))){ \ + json_set_err((cson_rc.AllocError==check) \ + ? FSL_JSON_E_ALLOC : FSL_JSON_E_UNKNOWN, \ + "Object property insertion failed."); \ + goto error;\ + } (void)0 + +#if 0 + /* only for testing! */ + tmp = cson_value_new_string(blob_buffer(&sql),strlen(blob_buffer(&sql))); + SET("timelineSql"); +#endif + + blob_reset(&sql); + /* + REMINDER/FIXME(?): we have both uuid (the change uuid?) and + ticketUuid (the actual ticket). This is different from the wiki + timeline, where we only have the wiki page uuid. + */ + db_prepare(&q, "SELECT rid AS rid," + " uuid AS uuid," + " mtime AS timestamp," +#if 0 + " timestampString AS timestampString," +#endif + " user AS user," + " eventType AS eventType," + " comment AS comment," + " brief AS briefComment" + " FROM json_timeline" + " ORDER BY rowid"); + listV = cson_value_new_array(); + list = cson_value_get_array(listV); + tmp = listV; + SET("timeline"); + while( (SQLITE_ROW == db_step(&q) )){ + /* convert each row into a JSON object...*/ + int rc; + int const rid = db_column_int(&q,0); + Manifest * pMan = NULL; + cson_value * rowV; + cson_object * row; + /*printf("rid=%d\n",rid);*/ + pMan = manifest_get(rid, CFTYPE_TICKET, 0); + if(!pMan){ + /* this might be an attachment? I'm seeing this with + rid 15380, uuid [1292fef05f2472108]. + + /json/artifact/1292fef05f2472108 returns not-found, + probably because we haven't added artifact/ticket + yet(?). + */ + continue; + } + + rowV = cson_sqlite3_row_to_object(q.pStmt); + row = cson_value_get_object(rowV); + if(!row){ + manifest_destroy(pMan); + json_warn( FSL_JSON_W_ROW_TO_JSON_FAILED, + "Could not convert at least one timeline result row to JSON." ); + continue; + } + /* FIXME: certainly there's a more efficient way for use to get + the ticket UUIDs? + */ + cson_object_set(row,"ticketUuid",json_new_string(pMan->zTicketUuid)); + manifest_destroy(pMan); + rc = cson_array_append( list, rowV ); + if( 0 != rc ){ + cson_value_free(rowV); + g.json.resultCode = (cson_rc.AllocError==rc) + ? FSL_JSON_E_ALLOC + : FSL_JSON_E_UNKNOWN; + goto error; + } + } +#undef SET + goto ok; + error: + assert( 0 != g.json.resultCode ); + cson_value_free(payV); + payV = NULL; + ok: + blob_reset(&sql); + db_finalize(&q); + return payV; +} + +#endif /* FOSSIL_ENABLE_JSON */ ADDED src/json_user.c Index: src/json_user.c ================================================================== --- /dev/null +++ src/json_user.c @@ -0,0 +1,437 @@ +#ifdef FOSSIL_ENABLE_JSON +/* +** Copyright (c) 2011 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/ +** +*/ +#include "VERSION.h" +#include "config.h" +#include "json_user.h" + +#if INTERFACE +#include "json_detail.h" +#endif + +static cson_value * json_user_get(); +static cson_value * json_user_list(); +static cson_value * json_user_save(); + +/* +** Mapping of /json/user/XXX commands/paths to callbacks. +*/ +static const JsonPageDef JsonPageDefs_User[] = { +{"save", json_user_save, 0}, +{"get", json_user_get, 0}, +{"list", json_user_list, 0}, +/* Last entry MUST have a NULL name. */ +{NULL,NULL,0} +}; + + +/* +** Implements the /json/user family of pages/commands. +** +*/ +cson_value * json_page_user(){ + return json_page_dispatch_helper(&JsonPageDefs_User[0]); +} + + +/* +** Impl of /json/user/list. Requires admin/setup rights. +*/ +static cson_value * json_user_list(){ + cson_value * payV = NULL; + Stmt q; + if(!g.perm.Admin && !g.perm.Setup){ + json_set_err(FSL_JSON_E_DENIED, + "Requires 'a' or 's' privileges."); + return NULL; + } + db_prepare(&q,"SELECT uid AS uid," + " login AS name," + " cap AS capabilities," + " info AS info," + " mtime AS timestamp" + " FROM user ORDER BY login"); + payV = json_stmt_to_array_of_obj(&q, NULL); + db_finalize(&q); + if(NULL == payV){ + json_set_err(FSL_JSON_E_UNKNOWN, + "Could not convert user list to JSON."); + } + return payV; +} + +/* +** Creates a new JSON Object based on the db state of +** the given user name. On error (no record found) +** it returns NULL, else the caller owns the returned +** object. +*/ +static cson_value * json_load_user_by_name(char const * zName){ + cson_value * u = NULL; + Stmt q; + db_prepare(&q,"SELECT uid AS uid," + " login AS name," + " cap AS capabilities," + " info AS info," + " mtime AS timestamp" + " FROM user" + " WHERE login=%Q", + zName); + if( (SQLITE_ROW == db_step(&q)) ){ + u = cson_sqlite3_row_to_object(q.pStmt); + } + db_finalize(&q); + return u; +} + +/* +** Identical to json_load_user_by_name(), but expects a user ID. Returns +** NULL if no user found with that ID. +*/ +static cson_value * json_load_user_by_id(int uid){ + cson_value * u = NULL; + Stmt q; + db_prepare(&q,"SELECT uid AS uid," + " login AS name," + " cap AS capabilities," + " info AS info," + " mtime AS timestamp" + " FROM user" + " WHERE uid=%d", + uid); + if( (SQLITE_ROW == db_step(&q)) ){ + u = cson_sqlite3_row_to_object(q.pStmt); + } + db_finalize(&q); + return u; +} + + +/* +** Impl of /json/user/get. Requires admin or setup rights. +*/ +static cson_value * json_user_get(){ + cson_value * payV = NULL; + char const * pUser = NULL; + if(!g.perm.Admin && !g.perm.Setup){ + json_set_err(FSL_JSON_E_DENIED, + "Requires 'a' or 's' privileges."); + return NULL; + } + pUser = json_find_option_cstr2("name", NULL, NULL, g.json.dispatchDepth+1); + if(!pUser || !*pUser){ + json_set_err(FSL_JSON_E_MISSING_ARGS,"Missing 'name' property."); + return NULL; + } + payV = json_load_user_by_name(pUser); + if(!payV){ + json_set_err(FSL_JSON_E_RESOURCE_NOT_FOUND,"User not found."); + } + return payV; +} + +/* +** Expects pUser to contain fossil user fields in JSON form: name, +** uid, info, capabilities, password. +** +** At least one of (name, uid) must be included. All others are +** optional and their db fields will not be updated if those fields +** are not included in pUser. +** +** If uid is specified then name may refer to a _new_ name +** for a user, otherwise the name must refer to an existing user. +** If uid=-1 then the name must be specified and a new user is +** created (fails if one already exists). +** +** If uid is not set, this function might modify pUser to contain the +** db-found (or inserted) user ID. +** +** On error g.json's error state is set and one of the FSL_JSON_E_xxx +** values from FossilJsonCodes is returned. +** +** On success the db record for the given user is updated. +** +** Requires either Admin, Setup, or Password access. Non-admin/setup +** users can only change their own information. Non-setup users may +** not modify the 's' permission. Admin users without setup +** permissions may not edit any other user who has the 's' permission. +** +*/ +int json_user_update_from_json( cson_object * pUser ){ +#define CSTR(X) cson_string_cstr(cson_value_get_string( cson_object_get(pUser, X ) )) + char const * zName = CSTR("name"); + char const * zNameNew = zName; + char * zNameFree = NULL; + char const * zInfo = CSTR("info"); + char const * zCap = CSTR("capabilities"); + char const * zPW = CSTR("password"); + cson_value const * forceLogout = cson_object_get(pUser, "forceLogout"); + int gotFields = 0; +#undef CSTR + cson_int_t uid = cson_value_get_integer( cson_object_get(pUser, "uid") ); + char const tgtHasSetup = zCap && (NULL!=strchr(zCap, 's')); + char tgtHadSetup = 0; + Blob sql = empty_blob; + Stmt q = empty_Stmt; + + if(uid<=0 && (!zName||!*zName)){ + return json_set_err(FSL_JSON_E_MISSING_ARGS, + "One of 'uid' or 'name' is required."); + }else if(uid>0){ + zNameFree = db_text(NULL, "SELECT login FROM user WHERE uid=%d",uid); + if(!zNameFree){ + return json_set_err(FSL_JSON_E_RESOURCE_NOT_FOUND, + "No login found for uid %d.", uid); + } + zName = zNameFree; + }else if(-1==uid){ + /* try to create a new user */ + if(!g.perm.Admin && !g.perm.Setup){ + json_set_err(FSL_JSON_E_DENIED, + "Requires 'a' or 's' privileges."); + goto error; + }else if(!zName || !*zName){ + json_set_err(FSL_JSON_E_MISSING_ARGS, + "No name specified for new user."); + goto error; + }else if( db_exists("SELECT 1 FROM user WHERE login=%Q", zName) ){ + json_set_err(FSL_JSON_E_RESOURCE_ALREADY_EXISTS, + "User %s already exists.", zName); + goto error; + }else{ + Stmt ins = empty_Stmt; + db_unprotect(PROTECT_USER); + db_prepare(&ins, "INSERT INTO user (login) VALUES(%Q)",zName); + db_step( &ins ); + db_finalize(&ins); + db_protect_pop(); + uid = db_int(0,"SELECT uid FROM user WHERE login=%Q", zName); + assert(uid>0); + zNameNew = zName; + cson_object_set( pUser, "uid", cson_value_new_integer(uid) ); + } + }else{ + uid = db_int(0,"SELECT uid FROM user WHERE login=%Q", zName); + if(uid<=0){ + json_set_err(FSL_JSON_E_RESOURCE_NOT_FOUND, + "No login found for user [%s].", zName); + goto error; + } + cson_object_set( pUser, "uid", cson_value_new_integer(uid) ); + } + + /* Maintenance note: all error-returns from here on out should go + via 'goto error' in order to clean up. + */ + + if(uid != g.userUid){ + if(!g.perm.Admin && !g.perm.Setup){ + json_set_err(FSL_JSON_E_DENIED, + "Changing another user's data requires " + "'a' or 's' privileges."); + goto error; + } + } + /* check if the target uid currently has setup rights. */ + tgtHadSetup = db_int(0,"SELECT 1 FROM user where uid=%d" + " AND cap GLOB '*s*'", uid); + + if((tgtHasSetup || tgtHadSetup) && !g.perm.Setup){ + /* + Do not allow a non-setup user to set or remove setup + privileges. setup.c uses similar logic. + */ + json_set_err(FSL_JSON_E_DENIED, + "Modifying 's' users/privileges requires " + "'s' privileges."); + goto error; + } + /* + Potential todo: do not allow a setup user to remove 's' from + himself, to avoid locking himself out? + */ + + blob_append(&sql, "UPDATE user SET",-1 ); + blob_append(&sql, " mtime=cast(strftime('%s') AS INTEGER)", -1); + + if((uid>0) && zNameNew){ + /* Check for name change... */ + if(0!=strcmp(zName,zNameNew)){ + if( (!g.perm.Admin && !g.perm.Setup) + && (zName != zNameNew)){ + json_set_err( FSL_JSON_E_DENIED, + "Modifying user names requires 'a' or 's' privileges."); + goto error; + } + forceLogout = cson_value_true() + /* reminders: 1) does not allocate. + 2) we do this because changing a name + invalidates any login token because the old name + is part of the token hash. + */; + blob_append_sql(&sql, ", login=%Q", zNameNew); + ++gotFields; + } + } + + if( zCap && *zCap ){ + if(!g.perm.Admin || !g.perm.Setup){ + /* we "could" arguably silently ignore cap in this case. */ + json_set_err(FSL_JSON_E_DENIED, + "Changing capabilities requires 'a' or 's' privileges."); + goto error; + } + blob_append_sql(&sql, ", cap=%Q", zCap); + ++gotFields; + } + + if( zPW && *zPW ){ + if(!g.perm.Admin && !g.perm.Setup && !g.perm.Password){ + json_set_err( FSL_JSON_E_DENIED, + "Password change requires 'a', 's', " + "or 'p' permissions."); + goto error; + }else{ +#define TRY_LOGIN_GROUP 0 /* login group support is not yet implemented. */ +#if !TRY_LOGIN_GROUP + char * zPWHash = NULL; + ++gotFields; + zPWHash = sha1_shared_secret(zPW, zNameNew ? zNameNew : zName, NULL); + blob_append_sql(&sql, ", pw=%Q", zPWHash); + free(zPWHash); +#else + ++gotFields; + blob_append_sql(&sql, ", pw=coalesce(shared_secret(%Q,%Q," + "(SELECT value FROM config WHERE name='project-code')))", + zPW, zNameNew ? zNameNew : zName); + /* shared_secret() func is undefined? */ +#endif + } + } + + if( zInfo ){ + blob_append_sql(&sql, ", info=%Q", zInfo); + ++gotFields; + } + + if((g.perm.Admin || g.perm.Setup) + && forceLogout && cson_value_get_bool(forceLogout)){ + blob_append(&sql, ", cookie=NULL, cexpire=NULL", -1); + ++gotFields; + } + + if(!gotFields){ + json_set_err( FSL_JSON_E_MISSING_ARGS, + "Required user data are missing."); + goto error; + } + assert(uid>0); +#if !TRY_LOGIN_GROUP + blob_append_sql(&sql, " WHERE uid=%d", uid); +#else /* need name for login group support :/ */ + blob_append_sql(&sql, " WHERE login=%Q", zName); +#endif +#if 0 + puts(blob_str(&sql)); + cson_output_FILE( cson_object_value(pUser), stdout, NULL ); +#endif + db_unprotect(PROTECT_USER); + db_prepare(&q, "%s", blob_sql_text(&sql)); + db_exec(&q); + db_finalize(&q); + db_protect_pop(); +#if TRY_LOGIN_GROUP + if( zPW || cson_value_get_bool(forceLogout) ){ + Blob groupSql = empty_blob; + char * zErr = NULL; + blob_append_sql(&groupSql, + "INSERT INTO user(login)" + " SELECT %Q WHERE NOT EXISTS(SELECT 1 FROM user WHERE login=%Q);", + zName, zName + ); + blob_append(&groupSql, blob_str(&sql), blob_size(&sql)); + db_unprotect(PROTECT_USER); + login_group_sql(blob_str(&groupSql), NULL, NULL, &zErr); + db_protect_pop(); + blob_reset(&groupSql); + if( zErr ){ + json_set_err( FSL_JSON_E_UNKNOWN, + "Repo-group update at least partially failed: %s", + zErr); + free(zErr); + goto error; + } + } +#endif /* TRY_LOGIN_GROUP */ + +#undef TRY_LOGIN_GROUP + + free( zNameFree ); + blob_reset(&sql); + return 0; + + error: + assert(0 != g.json.resultCode); + free(zNameFree); + blob_reset(&sql); + return g.json.resultCode; +} + + +/* +** Impl of /json/user/save. +*/ +static cson_value * json_user_save(){ + /* try to get user info from GET/CLI args and construct + a JSON form of it... */ + cson_object * u = cson_new_object(); + char const * str = NULL; + int b = -1; + int i = -1; + int uid = -1; + cson_value * payload = NULL; + /* String properties... */ +#define PROP(LK,SK) str = json_find_option_cstr(LK,NULL,SK); \ + if(str){ cson_object_set(u, LK, json_new_string(str)); } (void)0 + PROP("name","n"); + PROP("password","p"); + PROP("info","i"); + PROP("capabilities","c"); +#undef PROP + /* Boolean properties... */ +#define PROP(LK,DFLT) b = json_find_option_bool(LK,NULL,NULL,DFLT); \ + if(DFLT!=b){ cson_object_set(u, LK, cson_value_new_bool(b ? 1 : 0)); } (void)0 + PROP("forceLogout",-1); +#undef PROP + +#define PROP(LK,DFLT) i = json_find_option_int(LK,NULL,NULL,DFLT); \ + if(DFLT != i){ cson_object_set(u, LK, cson_value_new_integer(i)); } (void)0 + PROP("uid",-99); +#undef PROP + if( g.json.reqPayload.o ){ + cson_object_merge( u, g.json.reqPayload.o, CSON_MERGE_NO_RECURSE ); + } + json_user_update_from_json( u ); + if(!g.json.resultCode){ + uid = cson_value_get_integer( cson_object_get(u, "uid") ); + assert((uid>0) && "Something went wrong in json_user_update_from_json()"); + payload = json_load_user_by_id(uid); + } + cson_free_object(u); + return payload; +} +#endif /* FOSSIL_ENABLE_JSON */ ADDED src/json_wiki.c Index: src/json_wiki.c ================================================================== --- /dev/null +++ src/json_wiki.c @@ -0,0 +1,617 @@ +#ifdef FOSSIL_ENABLE_JSON +/* +** Copyright (c) 2011-12 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/ +** +*/ +#include "VERSION.h" +#include "config.h" +#include "json_wiki.h" + +#if INTERFACE +#include "json_detail.h" +#endif + +static cson_value * json_wiki_create(); +static cson_value * json_wiki_get(); +static cson_value * json_wiki_list(); +static cson_value * json_wiki_preview(); +static cson_value * json_wiki_save(); +static cson_value * json_wiki_diff(); +/* +** Mapping of /json/wiki/XXX commands/paths to callbacks. +*/ +static const JsonPageDef JsonPageDefs_Wiki[] = { +{"create", json_wiki_create, 0}, +{"diff", json_wiki_diff, 0}, +{"get", json_wiki_get, 0}, +{"list", json_wiki_list, 0}, +{"preview", json_wiki_preview, 0}, +{"save", json_wiki_save, 0}, +{"timeline", json_timeline_wiki,0}, +/* Last entry MUST have a NULL name. */ +{NULL,NULL,0} +}; + + +/* +** Implements the /json/wiki family of pages/commands. +** +*/ +cson_value * json_page_wiki(){ + return json_page_dispatch_helper(JsonPageDefs_Wiki); +} + +/* +** Returns the UUID for the given wiki blob RID, or NULL if not +** found. The returned string is allocated via db_text() and must be +** free()d by the caller. +*/ +char * json_wiki_get_uuid_for_rid( int rid ) +{ + return db_text(NULL, + "SELECT b.uuid FROM tag t, tagxref x, blob b" + " WHERE x.tagid=t.tagid AND t.tagname GLOB 'wiki-*' " + " AND b.rid=x.rid AND b.rid=%d" + " ORDER BY x.mtime DESC LIMIT 1", + rid + ); +} + +/* +** Tries to load a wiki page from the given rid creates a JSON object +** representation of it. If the page is not found then NULL is +** returned. If contentFormat is positive then the page content +** is HTML-ized using fossil's conventional wiki format, if it is +** negative then no parsing is performed, if it is 0 then the content +** is not returned in the response. If contentFormat is 0 then the +** contentSize reflects the number of bytes, not characters, stored in +** the page. +** +** The returned value, if not NULL, is-a JSON Object owned by the +** caller. If it returns NULL then it may set g.json's error state. +*/ +cson_value * json_get_wiki_page_by_rid(int rid, int contentFormat){ + Manifest * pWiki = NULL; + if( NULL == (pWiki = manifest_get(rid, CFTYPE_WIKI, 0)) ){ + json_set_err( FSL_JSON_E_UNKNOWN, + "Error reading wiki page from manifest (rid=%d).", + rid ); + return NULL; + }else{ + unsigned int len = 0; + cson_object * pay = cson_new_object(); + char const * zBody = pWiki->zWiki; + char const * zFormat = NULL; + char * zUuid = json_wiki_get_uuid_for_rid(rid); + cson_object_set(pay,"name",json_new_string(pWiki->zWikiTitle)); + cson_object_set(pay,"uuid",json_new_string(zUuid)); + free(zUuid); + zUuid = NULL; + if( pWiki->nParent > 0 ){ + cson_object_set( pay, "parent", json_new_string(pWiki->azParent[0]) ) + /* Reminder: wiki pages do not branch and have only one parent + (except for the initial version, which has no parents). */; + } + /*cson_object_set(pay,"rid",json_new_int((cson_int_t)rid));*/ + cson_object_set(pay,"user",json_new_string(pWiki->zUser)); + cson_object_set(pay,FossilJsonKeys.timestamp, + json_julian_to_timestamp(pWiki->rDate)); + if(0 == contentFormat){ + cson_object_set(pay,"size", + json_new_int((cson_int_t)(zBody?strlen(zBody):0))); + }else{ + if( contentFormat>0 ){/*HTML-ize it*/ + Blob content = empty_blob; + Blob raw = empty_blob; + zFormat = "html"; + if(zBody && *zBody){ + const char *zMimetype = pWiki->zMimetype; + if( zMimetype==0 ) zMimetype = "text/x-fossil-wiki"; + zMimetype = wiki_filter_mimetypes(zMimetype); + blob_append(&raw,zBody,-1); + if( fossil_strcmp(zMimetype, "text/x-fossil-wiki")==0 ){ + wiki_convert(&raw,&content,0); + }else if( fossil_strcmp(zMimetype, "text/x-markdown")==0 ){ + markdown_to_html(&raw,0,&content); + }else if( fossil_strcmp(zMimetype, "text/plain")==0 ){ + htmlize_to_blob(&content,blob_str(&raw),blob_size(&raw)); + }else{ + json_set_err( FSL_JSON_E_UNKNOWN, + "Unsupported MIME type '%s' for wiki page '%s'.", + zMimetype, pWiki->zWikiTitle ); + blob_reset(&content); + blob_reset(&raw); + cson_free_object(pay); + manifest_destroy(pWiki); + return NULL; + } + len = (unsigned int)blob_size(&content); + } + cson_object_set(pay,"size",json_new_int((cson_int_t)len)); + cson_object_set(pay,"content", + cson_value_new_string(blob_buffer(&content),len)); + blob_reset(&content); + blob_reset(&raw); + }else{/*raw format*/ + zFormat = "raw"; + len = zBody ? strlen(zBody) : 0; + cson_object_set(pay,"size",json_new_int((cson_int_t)len)); + cson_object_set(pay,"content",cson_value_new_string(zBody,len)); + } + cson_object_set(pay,"contentFormat",json_new_string(zFormat)); + + } + /*TODO: add 'T' (tag) fields*/ + /*TODO: add the 'A' card (file attachment) entries?*/ + manifest_destroy(pWiki); + return cson_object_value(pay); + } +} + +/* +** Searches for the latest version of a wiki page with the given +** name. If found it behaves like json_get_wiki_page_by_rid(theRid, +** contentFormat), else it returns NULL. +*/ +cson_value * json_get_wiki_page_by_name(char const * zPageName, int contentFormat){ + int rid; + rid = db_int(0, + "SELECT x.rid FROM tag t, tagxref x, blob b" + " WHERE x.tagid=t.tagid AND t.tagname='wiki-%q' " + " AND b.rid=x.rid" + " ORDER BY x.mtime DESC LIMIT 1", + zPageName + ); + if( 0==rid ){ + json_set_err( FSL_JSON_E_RESOURCE_NOT_FOUND, "Wiki page not found: %s", + zPageName ); + return NULL; + } + return json_get_wiki_page_by_rid(rid, contentFormat); +} + + +/* +** Searches json_find_option_ctr("format",NULL,"f") for a flag. +** If not found it returns defaultValue else it returns a value +** depending on the first character of the format option: +** +** [h]tml = 1 +** [n]one = 0 +** [r]aw = -1 +** +** The return value is intended for use with +** json_get_wiki_page_by_rid() and friends. +*/ +int json_wiki_get_content_format_flag( int defaultValue ){ + int contentFormat = defaultValue; + char const * zFormat = json_find_option_cstr("format",NULL,"f"); + if( !zFormat || !*zFormat ){ + return contentFormat; + } + else if('r'==*zFormat){ + contentFormat = -1; + } + else if('h'==*zFormat){ + contentFormat = 1; + } + else if('n'==*zFormat){ + contentFormat = 0; + } + return contentFormat; +} + +/* +** Helper for /json/wiki/get and /json/wiki/preview. At least one of +** zPageName (wiki page name) or zSymname must be set to a +** non-empty/non-NULL value. zSymname takes precedence. On success +** the result of one of json_get_wiki_page_by_rid() or +** json_get_wiki_page_by_name() will be returned (owned by the +** caller). On error g.json's error state is set and NULL is returned. +*/ +static cson_value * json_wiki_get_by_name_or_symname(char const * zPageName, + char const * zSymname, + int contentFormat ){ + if(!zSymname || !*zSymname){ + return json_get_wiki_page_by_name(zPageName, contentFormat); + }else{ + int rid = symbolic_name_to_rid( zSymname ? zSymname : zPageName, "w" ); + if(rid<0){ + json_set_err(FSL_JSON_E_AMBIGUOUS_UUID, + "UUID [%s] is ambiguous.", zSymname); + return NULL; + }else if(rid==0){ + json_set_err(FSL_JSON_E_RESOURCE_NOT_FOUND, + "UUID [%s] does not resolve to a wiki page.", zSymname); + return NULL; + }else{ + return json_get_wiki_page_by_rid(rid, contentFormat); + } + } +} + +/* +** Implementation of /json/wiki/get. +** +*/ +static cson_value * json_wiki_get(){ + char const * zPageName; + char const * zSymName = NULL; + int contentFormat = -1; + if( !g.perm.RdWiki && !g.perm.Read ){ + json_set_err(FSL_JSON_E_DENIED, + "Requires 'o' or 'j' access."); + return NULL; + } + zPageName = json_find_option_cstr2("name",NULL,"n",g.json.dispatchDepth+1); + + zSymName = json_find_option_cstr("uuid",NULL,"u"); + + if((!zPageName||!*zPageName) && (!zSymName || !*zSymName)){ + json_set_err(FSL_JSON_E_MISSING_ARGS, + "At least one of the 'name' or 'uuid' arguments must be provided."); + return NULL; + } + + /* TODO: see if we have a page named zPageName. If not, try to resolve + zPageName as a UUID. + */ + + contentFormat = json_wiki_get_content_format_flag(contentFormat); + return json_wiki_get_by_name_or_symname( zPageName, zSymName, contentFormat ); +} + +/* +** Implementation of /json/wiki/preview. +** +*/ +static cson_value * json_wiki_preview(){ + char const * zContent = NULL; + cson_value * pay = NULL; + Blob contentOrig = empty_blob; + Blob contentHtml = empty_blob; + if( !g.perm.WrWiki ){ + json_set_err(FSL_JSON_E_DENIED, + "Requires 'k' access."); + return NULL; + } + zContent = cson_string_cstr(cson_value_get_string(g.json.reqPayload.v)); + if(!zContent) { + json_set_err(FSL_JSON_E_MISSING_ARGS, + "The 'payload' property must be a string containing the wiki code to preview."); + return NULL; + } + blob_append( &contentOrig, zContent, (int)cson_string_length_bytes(cson_value_get_string(g.json.reqPayload.v)) ); + wiki_convert( &contentOrig, &contentHtml, 0 ); + blob_reset( &contentOrig ); + pay = cson_value_new_string( blob_str(&contentHtml), (unsigned int)blob_size(&contentHtml)); + blob_reset( &contentHtml ); + return pay; +} + + +/* +** Internal impl of /wiki/save and /wiki/create. If createMode is 0 +** and the page already exists then a +** FSL_JSON_E_RESOURCE_ALREADY_EXISTS error is triggered. If +** createMode is false then the FSL_JSON_E_RESOURCE_NOT_FOUND is +** triggered if the page does not already exists. +** +** Note that the error triggered when createMode==0 and no such page +** exists is rather arbitrary - we could just as well create the entry +** here if it doesn't already exist. With that, save/create would +** become one operation. That said, i expect there are people who +** would categorize such behaviour as "being too clever" or "doing too +** much automatically" (and i would likely agree with them). +** +** If allowCreateIfNotExists is true then this function will allow a new +** page to be created even if createMode is false. +*/ +static cson_value * json_wiki_create_or_save(char createMode, + char allowCreateIfNotExists){ + Blob content = empty_blob; /* wiki page content */ + cson_value * nameV; /* wiki page name */ + char const * zPageName; /* cstr form of page name */ + cson_value * contentV; /* passed-in content */ + cson_value * emptyContent = NULL; /* placeholder for empty content. */ + cson_value * payV = NULL; /* payload/return value */ + cson_string const * jstr = NULL; /* temp for cson_value-to-cson_string conversions. */ + char const * zMimeType = 0; + unsigned int contentLen = 0; + int rid; + if( (createMode && !g.perm.NewWiki) + || (!createMode && !g.perm.WrWiki)){ + json_set_err(FSL_JSON_E_DENIED, + "Requires '%c' permissions.", + (createMode ? 'f' : 'k')); + return NULL; + } + nameV = json_req_payload_get("name"); + if(!nameV){ + json_set_err( FSL_JSON_E_MISSING_ARGS, + "'name' parameter is missing."); + return NULL; + } + zPageName = cson_string_cstr(cson_value_get_string(nameV)); + if(!zPageName || !*zPageName){ + json_set_err(FSL_JSON_E_INVALID_ARGS, + "'name' parameter must be a non-empty string."); + return NULL; + } + rid = db_int(0, + "SELECT x.rid FROM tag t, tagxref x" + " WHERE x.tagid=t.tagid AND t.tagname='wiki-%q'" + " ORDER BY x.mtime DESC LIMIT 1", + zPageName + ); + + if(rid){ + if(createMode){ + json_set_err(FSL_JSON_E_RESOURCE_ALREADY_EXISTS, + "Wiki page '%s' already exists.", + zPageName); + goto error; + } + }else if(!createMode && !allowCreateIfNotExists){ + json_set_err(FSL_JSON_E_RESOURCE_NOT_FOUND, + "Wiki page '%s' not found.", + zPageName); + goto error; + } + + contentV = json_req_payload_get("content"); + if( !contentV ){ + if( createMode || (!rid && allowCreateIfNotExists) ){ + contentV = emptyContent = cson_value_new_string("",0); + }else{ + json_set_err(FSL_JSON_E_MISSING_ARGS, + "'content' parameter is missing."); + goto error; + } + } + if( !cson_value_is_string(nameV) + || !cson_value_is_string(contentV)){ + json_set_err(FSL_JSON_E_INVALID_ARGS, + "'content' parameter must be a string."); + goto error; + } + jstr = cson_value_get_string(contentV); + contentLen = (int)cson_string_length_bytes(jstr); + if(contentLen){ + blob_append(&content, cson_string_cstr(jstr),contentLen); + } + + zMimeType = json_find_option_cstr("mimetype","mimetype","M"); + zMimeType = wiki_filter_mimetypes(zMimeType); + + wiki_cmd_commit(zPageName, rid, &content, zMimeType, 0); + blob_reset(&content); + /* + Our return value here has a race condition: if this operation + is called concurrently for the same wiki page via two requests, + payV could reflect the results of the other save operation. + */ + payV = json_get_wiki_page_by_name( + cson_string_cstr( + cson_value_get_string(nameV)), + 0); + goto ok; + error: + assert( 0 != g.json.resultCode ); + assert( NULL == payV ); + ok: + if( emptyContent ){ + /* We have some potentially tricky memory ownership + here, which is why we handle emptyContent separately. + + This is, in fact, overkill because cson_value_new_string("",0) + actually returns a shared singleton instance (i.e. doesn't + allocate), but that is a cson implementation detail which i + don't want leaking into this code... + */ + cson_value_free(emptyContent); + } + return payV; + +} + +/* +** Implementation of /json/wiki/create. +*/ +static cson_value * json_wiki_create(){ + return json_wiki_create_or_save(1,0); +} + +/* +** Implementation of /json/wiki/save. +*/ +static cson_value * json_wiki_save(){ + char const createIfNotExists = json_getenv_bool("createIfNotExists",0); + return json_wiki_create_or_save(0,createIfNotExists); +} + +/* +** Implementation of /json/wiki/list. +*/ +static cson_value * json_wiki_list(){ + cson_value * listV = NULL; + cson_array * list = NULL; + char const * zGlob = NULL; + Stmt q = empty_Stmt; + Blob sql = empty_blob; + char const verbose = json_find_option_bool("verbose",NULL,"v",0); + char fInvert = json_find_option_bool("invert",NULL,"i",0);; + + if( !g.perm.RdWiki && !g.perm.Read ){ + json_set_err(FSL_JSON_E_DENIED, + "Requires 'j' or 'o' permissions."); + return NULL; + } + blob_append(&sql,"SELECT" + " DISTINCT substr(tagname,6) as name" + " FROM tag JOIN tagxref USING('tagid')" + " WHERE tagname GLOB 'wiki-*'" + " AND TYPEOF(tagxref.value+0)='integer'", + /* ^^^ elide wiki- tags which are not wiki pages */ + -1); + zGlob = json_find_option_cstr("glob",NULL,"g"); + if(zGlob && *zGlob){ + blob_append_sql(&sql," AND name %s GLOB %Q", + fInvert ? "NOT" : "", zGlob); + }else{ + zGlob = json_find_option_cstr("like",NULL,"l"); + if(zGlob && *zGlob){ + blob_append_sql(&sql," AND name %s LIKE %Q", + fInvert ? "NOT" : "", zGlob); + } + } + blob_append(&sql," ORDER BY lower(name)", -1); + db_prepare(&q,"%s", blob_sql_text(&sql)); + blob_reset(&sql); + listV = cson_value_new_array(); + list = cson_value_get_array(listV); + while( SQLITE_ROW == db_step(&q) ){ + cson_value * v; + if( verbose ){ + char const * name = db_column_text(&q,0); + v = json_get_wiki_page_by_name(name,0); + }else{ + v = cson_sqlite3_column_to_value(q.pStmt,0); + } + if(!v){ + json_set_err(FSL_JSON_E_UNKNOWN, + "Could not convert wiki name column to JSON."); + goto error; + }else if( 0 != cson_array_append( list, v ) ){ + cson_value_free(v); + json_set_err(FSL_JSON_E_ALLOC,"Could not append wiki page name to array.") + /* OOM (or maybe numeric overflow) are the only realistic + error codes for that particular failure.*/; + goto error; + } + } + goto end; + error: + assert(0 != g.json.resultCode); + cson_value_free(listV); + listV = NULL; + end: + db_finalize(&q); + return listV; +} + +static cson_value * json_wiki_diff(){ + char const * zV1 = NULL; + char const * zV2 = NULL; + cson_object * pay = NULL; + int argPos = g.json.dispatchDepth; + int r1 = 0, r2 = 0; + Manifest * pW1 = NULL, *pW2 = NULL; + Blob w1 = empty_blob, w2 = empty_blob, d = empty_blob; + char const * zErrTag = NULL; + u64 diffFlags; + char * zUuid = NULL; + if( !g.perm.Hyperlink ){ + json_set_err(FSL_JSON_E_DENIED, + "Requires 'h' permissions."); + return NULL; + } + + + zV1 = json_find_option_cstr2( "v1",NULL, NULL, ++argPos ); + zV2 = json_find_option_cstr2( "v2",NULL, NULL, ++argPos ); + if(!zV1 || !*zV1 || !zV2 || !*zV2) { + json_set_err(FSL_JSON_E_INVALID_ARGS, + "Requires both 'v1' and 'v2' arguments."); + return NULL; + } + + r1 = symbolic_name_to_rid( zV1, "w" ); + zErrTag = zV1; + if(r1<0){ + goto ambiguous; + }else if(0==r1){ + goto invalid; + } + + r2 = symbolic_name_to_rid( zV2, "w" ); + zErrTag = zV2; + if(r2<0){ + goto ambiguous; + }else if(0==r2){ + goto invalid; + } + + zErrTag = zV1; + pW1 = manifest_get(r1, CFTYPE_WIKI, 0); + if( pW1==0 ) { + goto manifest; + } + zErrTag = zV2; + pW2 = manifest_get(r2, CFTYPE_WIKI, 0); + if( pW2==0 ) { + goto manifest; + } + + blob_init(&w1, pW1->zWiki, -1); + blob_zero(&w2); + blob_init(&w2, pW2->zWiki, -1); + blob_zero(&d); + diffFlags = DIFF_IGNORE_EOLWS | DIFF_STRIP_EOLCR; + text_diff(&w1, &w2, &d, 0, diffFlags); + blob_reset(&w1); + blob_reset(&w2); + + pay = cson_new_object(); + + zUuid = json_wiki_get_uuid_for_rid( pW1->rid ); + cson_object_set(pay, "v1", json_new_string(zUuid) ); + free(zUuid); + zUuid = json_wiki_get_uuid_for_rid( pW2->rid ); + cson_object_set(pay, "v2", json_new_string(zUuid) ); + free(zUuid); + zUuid = NULL; + + manifest_destroy(pW1); + manifest_destroy(pW2); + + cson_object_set(pay, "diff", + cson_value_new_string( blob_str(&d), + (unsigned int)blob_size(&d))); + + return cson_object_value(pay); + + manifest: + json_set_err(FSL_JSON_E_UNKNOWN, + "Could not load wiki manifest for UUID [%s].", + zErrTag); + goto end; + + ambiguous: + json_set_err(FSL_JSON_E_AMBIGUOUS_UUID, + "UUID [%s] is ambiguous.", zErrTag); + goto end; + + invalid: + json_set_err(FSL_JSON_E_RESOURCE_NOT_FOUND, + "UUID [%s] not found.", zErrTag); + goto end; + + end: + cson_free_object(pay); + return NULL; +} + +#endif /* FOSSIL_ENABLE_JSON */ ADDED src/leaf.c Index: src/leaf.c ================================================================== --- /dev/null +++ src/leaf.c @@ -0,0 +1,282 @@ +/* +** Copyright (c) 2011 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 code used to manage the "leaf" table of the +** repository. +** +** The LEAF table contains the rids for all leaves in the check-in DAG. +** A leaf is a check-in that has no children in the same branch. +*/ +#include "config.h" +#include "leaf.h" +#include + + +/* +** Return true if the check-in with RID=rid is a leaf. +** +** A leaf has no children in the same branch. +*/ +int is_a_leaf(int rid){ + int rc; + static const char zSql[] = + @ SELECT 1 FROM plink + @ WHERE pid=%d + @ 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') + ; + rc = db_int(0, zSql /*works-like:"%d,%d,%d"*/, + rid, TAG_BRANCH, TAG_BRANCH); + return rc==0; +} + +/* +** Count 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. +*/ +int count_nonbranch_children(int pid){ + int nNonBranch = 0; + static Stmt q; + static const char zSql[] = + @ SELECT count(*) FROM plink + @ WHERE pid=:pid 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') + ; + db_static_prepare(&q, zSql /*works-like: "%d,%d"*/, TAG_BRANCH, TAG_BRANCH); + db_bind_int(&q, ":pid", pid); + if( db_step(&q)==SQLITE_ROW ){ + nNonBranch = db_column_int(&q, 0); + } + db_reset(&q); + return nNonBranch; +} + + +/* +** Recompute the entire LEAF table. +** +** This can be expensive (5 seconds or so) for a really large repository. +** So it is only done for things like a rebuild. +*/ +void leaf_rebuild(void){ + db_multi_exec( + "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')", + TAG_BRANCH, TAG_BRANCH + ); +} + +/* +** A bag of check-ins whose leaf status needs to be checked. +*/ +static Bag needToCheck; + +/* +** Check to see if check-in "rid" is a leaf and either add it to the LEAF +** table if it is, or remove it if it is not. +*/ +void leaf_check(int rid){ + static Stmt checkIfLeaf; + static Stmt addLeaf; + static Stmt removeLeaf; + int rc; + + db_static_prepare(&checkIfLeaf, + "SELECT 1 FROM plink" + " WHERE pid=:rid" + " AND coalesce((SELECT value FROM tagxref" + " WHERE tagid=%d AND rid=:rid),'trunk')" + " == coalesce((SELECT value FROM tagxref" + " WHERE tagid=%d AND rid=plink.cid),'trunk');", + TAG_BRANCH, TAG_BRANCH + ); + db_bind_int(&checkIfLeaf, ":rid", rid); + rc = db_step(&checkIfLeaf); + db_reset(&checkIfLeaf); + if( rc==SQLITE_ROW ){ + db_static_prepare(&removeLeaf, "DELETE FROM leaf WHERE rid=:rid"); + db_bind_int(&removeLeaf, ":rid", rid); + db_step(&removeLeaf); + db_reset(&removeLeaf); + }else{ + db_static_prepare(&addLeaf, "INSERT OR IGNORE INTO leaf VALUES(:rid)"); + db_bind_int(&addLeaf, ":rid", rid); + db_step(&addLeaf); + db_reset(&addLeaf); + } +} + +/* +** Return an SQL expression (stored in memory obtained from fossil_malloc()) +** that is true if the SQL variable named "zVar" contains the rid with +** a CLOSED tag. In other words, return true if the leaf is closed. +** +** The result can be prefaced with a NOT operator to get all leaves that +** are open. +*/ +char *leaf_is_closed_sql(const char *zVar){ + return mprintf( + "EXISTS(SELECT 1 FROM tagxref AS tx" + " WHERE tx.rid=%s" + " AND tx.tagid=%d" + " AND tx.tagtype>0)", + zVar, TAG_CLOSED + ); +} + +/* +** Returns true if vid refers to a closed leaf, else false. vid is +** assumed to refer to a manifest, but this function does not verify +** that. +*/ +int leaf_is_closed(int vid){ + return db_exists("SELECT 1 FROM tagxref" + " WHERE tagid=%d AND rid=%d AND tagtype>0", + TAG_CLOSED, vid); +} + +/* +** Schedule a leaf check for "rid" and its parents. +*/ +void leaf_eventually_check(int rid){ + static Stmt parentsOf; + + db_static_prepare(&parentsOf, + "SELECT pid FROM plink WHERE cid=:rid AND pid>0" + ); + db_bind_int(&parentsOf, ":rid", rid); + bag_insert(&needToCheck, rid); + while( db_step(&parentsOf)==SQLITE_ROW ){ + bag_insert(&needToCheck, db_column_int(&parentsOf, 0)); + } + db_reset(&parentsOf); +} + +/* +** Do all pending leaf checks. +*/ +void leaf_do_pending_checks(void){ + int rid; + for(rid=bag_first(&needToCheck); rid; rid=bag_next(&needToCheck,rid)){ + leaf_check(rid); + } + bag_clear(&needToCheck); +} + +/* +** If check-in rid is an open-leaf and there exists another +** open leaf on the same branch, then return 1. +** +** If check-in rid is not an open leaf, or if it is the only open leaf +** on its branch, then return 0. +*/ +int leaf_ambiguity(int rid){ + int rc; /* Result */ + char zVal[30]; + if( !is_a_leaf(rid) ) return 0; + sqlite3_snprintf(sizeof(zVal), zVal, "%d", rid); + rc = db_exists( + "SELECT 1 FROM leaf" + " WHERE NOT %z" + " AND rid<>%d" + " AND (SELECT value FROM tagxref WHERE tagid=%d AND rid=leaf.rid)=" + " (SELECT value FROM tagxref WHERE tagid=%d AND rid=%d)" + " AND NOT %z", + leaf_is_closed_sql(zVal), rid, TAG_BRANCH, TAG_BRANCH, rid, + leaf_is_closed_sql("leaf.rid")); + return rc; +} + +/* +** If check-in rid is an open-leaf and there exists another open leaf +** on the same branch, then print a detailed warning showing all open +** leaves on that branch. +*/ +int leaf_ambiguity_warning(int rid, int currentCkout){ + char *zBr; + Stmt q; + int n = 0; + Blob msg; + if( leaf_ambiguity(rid)==0 ) return 0; + zBr = db_text(0, "SELECT value FROM tagxref WHERE tagid=%d AND rid=%d", + TAG_BRANCH, rid); + if( zBr==0 ) zBr = fossil_strdup("trunk"); + blob_init(&msg, 0, 0); + blob_appendf(&msg, "WARNING: multiple open leaf check-ins on %s:", zBr); + db_prepare(&q, + "SELECT" + " (SELECT uuid FROM blob WHERE rid=leaf.rid)," + " (SELECT datetime(mtime,toLocal()) FROM event WHERE objid=leaf.rid)," + " leaf.rid" + " FROM leaf" + " WHERE (SELECT value FROM tagxref WHERE tagid=%d AND rid=leaf.rid)=%Q" + " AND NOT %z" + " ORDER BY 2 DESC", + TAG_BRANCH, zBr, leaf_is_closed_sql("leaf.rid") + ); + while( db_step(&q)==SQLITE_ROW ){ + blob_appendf(&msg, "\n (%d) %s [%S]%s", + ++n, db_column_text(&q,1), db_column_text(&q,0), + db_column_int(&q,2)==currentCkout ? " (current)" : ""); + } + db_finalize(&q); + fossil_warning("%s",blob_str(&msg)); + blob_reset(&msg); + return 1; +} + +/* +** COMMAND: test-leaf-ambiguity +** +** Usage: %fossil NAME ... +** +** Resolve each name on the command line and call leaf_ambiguity_warning() +** for each resulting RID. +*/ +void leaf_ambiguity_warning_test(void){ + int i; + int rid; + int rc; + db_find_and_open_repository(0,0); + verify_all_options(); + for(i=2; i + * Copyright (c) 2010-2013, Pieter Noordhuis + * + * 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 + * + * Todo list: + * - Filter bogus Ctrl+ combinations. + * - Win32 support + * + * Bloat: + * - History search like Ctrl+r in readline? + * + * List of escape sequences used by this program, we do everything just + * with three 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 n chars + * + * CUB (CUrsor Backward) + * Sequence: ESC [ n D + * Effect: moves cursor backward n chars + * + * The following is used to get the terminal width if getting + * the width with the TIOCGWINSZ ioctl fails + * + * DSR (Device Status Report) + * Sequence: ESC [ 6 n + * Effect: reports the current cusor position as ESC [ n ; m R + * where n is the row and m is the column + * + * When multi line mode is enabled, we also use an additional escape + * sequence. However multi line editing is disabled by default. + * + * CUU (Cursor Up) + * Sequence: ESC [ n A + * Effect: moves cursor up of n chars. + * + * CUD (Cursor Down) + * Sequence: ESC [ n B + * Effect: moves cursor down of n chars. + * + * When linenoiseClearScreen() is called, two additional escape sequences + * are used in order to clear the screen and position the cursor at home + * position. + * + * CUP (Cursor position) + * Sequence: ESC [ H + * Effect: moves the cursor to upper left corner + * + * ED (Erase display) + * Sequence: ESC [ 2 J + * Effect: clear the whole screen + * + */ + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include "linenoise.h" + +#define LINENOISE_DEFAULT_HISTORY_MAX_LEN 100 +#define LINENOISE_MAX_LINE 4096 +static char *unsupported_term[] = {"dumb","cons25","emacs",NULL}; +static linenoiseCompletionCallback *completionCallback = NULL; +static linenoiseHintsCallback *hintsCallback = NULL; +static linenoiseFreeHintsCallback *freeHintsCallback = NULL; + +static struct termios orig_termios; /* In order to restore at exit.*/ +static int maskmode = 0; /* Show "***" instead of input. For passwords. */ +static int rawmode = 0; /* For atexit() function to check if restore is needed*/ +static int mlmode = 0; /* Multi line mode. Default is single line. */ +static int atexit_registered = 0; /* Register atexit just 1 time. */ +static int history_max_len = LINENOISE_DEFAULT_HISTORY_MAX_LEN; +static int history_len = 0; +static char **history = NULL; + +/* The linenoiseState structure represents the state during line editing. + * We pass this state to functions implementing specific editing + * functionalities. */ +struct linenoiseState { + int ifd; /* Terminal stdin file descriptor. */ + int ofd; /* Terminal stdout file descriptor. */ + char *buf; /* Edited line buffer. */ + size_t buflen; /* Edited line buffer size. */ + const char *prompt; /* Prompt to display. */ + size_t plen; /* Prompt length. */ + size_t pos; /* Current cursor position. */ + size_t oldpos; /* Previous refresh cursor position. */ + size_t len; /* Current edited line length. */ + size_t cols; /* Number of columns in terminal. */ + size_t maxrows; /* Maximum num of rows used so far (multiline mode) */ + int history_index; /* The history index we are currently editing. */ +}; + +enum KEY_ACTION{ + KEY_NULL = 0, /* NULL */ + CTRL_A = 1, /* Ctrl+a */ + CTRL_B = 2, /* Ctrl-b */ + CTRL_C = 3, /* Ctrl-c */ + CTRL_D = 4, /* Ctrl-d */ + CTRL_E = 5, /* Ctrl-e */ + CTRL_F = 6, /* Ctrl-f */ + CTRL_H = 8, /* Ctrl-h */ + TAB = 9, /* Tab */ + CTRL_K = 11, /* Ctrl+k */ + CTRL_L = 12, /* Ctrl+l */ + ENTER = 13, /* Enter */ + CTRL_N = 14, /* Ctrl-n */ + CTRL_P = 16, /* Ctrl-p */ + CTRL_T = 20, /* Ctrl-t */ + CTRL_U = 21, /* Ctrl+u */ + CTRL_W = 23, /* Ctrl+w */ + ESC = 27, /* Escape */ + BACKSPACE = 127 /* Backspace */ +}; + +static void linenoiseAtExit(void); +int linenoiseHistoryAdd(const char *line); +static void refreshLine(struct linenoiseState *l); + +/* Debugging macro. */ +#if 0 +FILE *lndebug_fp = NULL; +#define lndebug(...) \ + do { \ + if (lndebug_fp == NULL) { \ + lndebug_fp = fopen("/tmp/lndebug.txt","a"); \ + fprintf(lndebug_fp, \ + "[%d %d %d] p: %d, rows: %d, rpos: %d, max: %d, oldmax: %d\n", \ + (int)l->len,(int)l->pos,(int)l->oldpos,plen,rows,rpos, \ + (int)l->maxrows,old_rows); \ + } \ + fprintf(lndebug_fp, ", " __VA_ARGS__); \ + fflush(lndebug_fp); \ + } while (0) +#else +#define lndebug(fmt, ...) +#endif + +/* ======================= Low level terminal handling ====================== */ + +/* Enable "mask mode". When it is enabled, instead of the input that + * the user is typing, the terminal will just display a corresponding + * number of asterisks, like "****". This is useful for passwords and other + * secrets that should not be displayed. */ +void linenoiseMaskModeEnable(void) { + maskmode = 1; +} + +/* Disable mask mode. */ +void linenoiseMaskModeDisable(void) { + maskmode = 0; +} + +/* Set if to use or not the multi line mode. */ +void linenoiseSetMultiLine(int ml) { + mlmode = ml; +} + +/* Return true if the terminal name is in the list of terminals we know are + * not able to understand basic escape sequences. */ +static int isUnsupportedTerm(void) { + char *term = getenv("TERM"); + int j; + + if (term == NULL) return 0; + for (j = 0; unsupported_term[j]; j++) + if (!strcasecmp(term,unsupported_term[j])) return 1; + return 0; +} + +/* Raw mode: 1960 magic shit. */ +static int enableRawMode(int fd) { + struct termios raw; + + if (!isatty(STDIN_FILENO)) goto fatal; + if (!atexit_registered) { + atexit(linenoiseAtExit); + atexit_registered = 1; + } + if (tcgetattr(fd,&orig_termios) == -1) goto fatal; + + 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(fd,TCSAFLUSH,&raw) < 0) goto fatal; + rawmode = 1; + return 0; + +fatal: + errno = ENOTTY; + return -1; +} + +static void disableRawMode(int fd) { + /* Don't even check the return value as it's too late. */ + if (rawmode && tcsetattr(fd,TCSAFLUSH,&orig_termios) != -1) + rawmode = 0; +} + +/* Use the ESC [6n escape sequence to query the horizontal cursor position + * and return it. On error -1 is returned, on success the position of the + * cursor. */ +static int getCursorPosition(int ifd, int ofd) { + char buf[32]; + int cols, rows; + unsigned int i = 0; + + /* Report cursor location */ + if (write(ofd, "\x1b[6n", 4) != 4) return -1; + + /* Read the response: ESC [ rows ; cols R */ + while (i < sizeof(buf)-1) { + if (read(ifd,buf+i,1) != 1) break; + if (buf[i] == 'R') break; + i++; + } + buf[i] = '\0'; + + /* Parse it. */ + if (buf[0] != ESC || buf[1] != '[') return -1; + if (sscanf(buf+2,"%d;%d",&rows,&cols) != 2) return -1; + return cols; +} + +/* Try to get the number of columns in the current terminal, or assume 80 + * if it fails. */ +static int getColumns(int ifd, int ofd) { + struct winsize ws; + + if (ioctl(1, TIOCGWINSZ, &ws) == -1 || ws.ws_col == 0) { + /* ioctl() failed. Try to query the terminal itself. */ + int start, cols; + + /* Get the initial position so we can restore it later. */ + start = getCursorPosition(ifd,ofd); + if (start == -1) goto failed; + + /* Go to right margin and get position. */ + if (write(ofd,"\x1b[999C",6) != 6) goto failed; + cols = getCursorPosition(ifd,ofd); + if (cols == -1) goto failed; + + /* Restore position. */ + if (cols > start) { + char seq[32]; + snprintf(seq,32,"\x1b[%dD",cols-start); + if (write(ofd,seq,strlen(seq)) == -1) { + /* Can't recover... */ + } + } + return cols; + } else { + return ws.ws_col; + } + +failed: + return 80; +} + +/* Clear the screen. Used to handle ctrl+l */ +void linenoiseClearScreen(void) { + if (write(STDOUT_FILENO,"\x1b[H\x1b[2J",7) <= 0) { + /* nothing to do, just to avoid warning. */ + } +} + +/* Beep, used for completion when there is nothing to complete or when all + * the choices were already shown. */ +static void linenoiseBeep(void) { + fprintf(stderr, "\x7"); + fflush(stderr); +} + +/* ============================== Completion ================================ */ + +/* Free a list of completion option populated by linenoiseAddCompletion(). */ +static void freeCompletions(linenoiseCompletions *lc) { + size_t i; + for (i = 0; i < lc->len; i++) + free(lc->cvec[i]); + if (lc->cvec != NULL) + free(lc->cvec); +} + +/* This is an helper function for linenoiseEdit() and is called when the + * user types the key in order to complete the string currently in the + * input. + * + * The state of the editing is encapsulated into the pointed linenoiseState + * structure as described in the structure definition. */ +static int completeLine(struct linenoiseState *ls) { + linenoiseCompletions lc = { 0, NULL }; + int nread, nwritten; + char c = 0; + + completionCallback(ls->buf,&lc); + if (lc.len == 0) { + linenoiseBeep(); + } else { + size_t stop = 0, i = 0; + + while(!stop) { + /* Show completion or original buffer */ + if (i < lc.len) { + struct linenoiseState saved = *ls; + + ls->len = ls->pos = strlen(lc.cvec[i]); + ls->buf = lc.cvec[i]; + refreshLine(ls); + ls->len = saved.len; + ls->pos = saved.pos; + ls->buf = saved.buf; + } else { + refreshLine(ls); + } + + nread = read(ls->ifd,&c,1); + if (nread <= 0) { + freeCompletions(&lc); + return -1; + } + + switch(c) { + case 9: /* tab */ + i = (i+1) % (lc.len+1); + if (i == lc.len) linenoiseBeep(); + break; + case 27: /* escape */ + /* Re-show original buffer */ + if (i < lc.len) refreshLine(ls); + stop = 1; + break; + default: + /* Update buffer and return */ + if (i < lc.len) { + nwritten = snprintf(ls->buf,ls->buflen,"%s",lc.cvec[i]); + ls->len = ls->pos = nwritten; + } + stop = 1; + break; + } + } + } + + freeCompletions(&lc); + return c; /* Return last read character */ +} + +/* Register a callback function to be called for tab-completion. */ +void linenoiseSetCompletionCallback(linenoiseCompletionCallback *fn) { + completionCallback = fn; +} + +/* Register a hits function to be called to show hits to the user at the + * right of the prompt. */ +void linenoiseSetHintsCallback(linenoiseHintsCallback *fn) { + hintsCallback = fn; +} + +/* Register a function to free the hints returned by the hints callback + * registered with linenoiseSetHintsCallback(). */ +void linenoiseSetFreeHintsCallback(linenoiseFreeHintsCallback *fn) { + freeHintsCallback = fn; +} + +/* This function is used by the callback function registered by the user + * in order to add completion options given the input string when the + * user typed . See the example.c source code for a very easy to + * understand example. */ +void linenoiseAddCompletion(linenoiseCompletions *lc, const char *str) { + size_t len = strlen(str); + char *copy, **cvec; + + copy = malloc(len+1); + if (copy == NULL) return; + memcpy(copy,str,len+1); + cvec = realloc(lc->cvec,sizeof(char*)*(lc->len+1)); + if (cvec == NULL) { + free(copy); + return; + } + lc->cvec = cvec; + lc->cvec[lc->len++] = copy; +} + +/* =========================== Line editing ================================= */ + +/* We define a very simple "append buffer" structure, that is an heap + * allocated string where we can append to. This is useful in order to + * write all the escape sequences in a buffer and flush them to the standard + * output in a single call, to avoid flickering effects. */ +struct abuf { + char *b; + int len; +}; + +static void abInit(struct abuf *ab) { + ab->b = NULL; + ab->len = 0; +} + +static void abAppend(struct abuf *ab, const char *s, int len) { + char *new = realloc(ab->b,ab->len+len); + + if (new == NULL) return; + memcpy(new+ab->len,s,len); + ab->b = new; + ab->len += len; +} + +static void abFree(struct abuf *ab) { + free(ab->b); +} + +/* Helper of refreshSingleLine() and refreshMultiLine() to show hints + * to the right of the prompt. */ +void refreshShowHints(struct abuf *ab, struct linenoiseState *l, int plen) { + char seq[64]; + if (hintsCallback && plen+l->len < l->cols) { + int color = -1, bold = 0; + char *hint = hintsCallback(l->buf,&color,&bold); + if (hint) { + int hintlen = strlen(hint); + int hintmaxlen = l->cols-(plen+l->len); + if (hintlen > hintmaxlen) hintlen = hintmaxlen; + if (bold == 1 && color == -1) color = 37; + if (color != -1 || bold != 0) + snprintf(seq,64,"\033[%d;%d;49m",bold,color); + else + seq[0] = '\0'; + abAppend(ab,seq,strlen(seq)); + abAppend(ab,hint,hintlen); + if (color != -1 || bold != 0) + abAppend(ab,"\033[0m",4); + /* Call the function to free the hint returned. */ + if (freeHintsCallback) freeHintsCallback(hint); + } + } +} + +/* Single line low level line refresh. + * + * Rewrite the currently edited line accordingly to the buffer content, + * cursor position, and number of columns of the terminal. */ +static void refreshSingleLine(struct linenoiseState *l) { + char seq[64]; + size_t plen = strlen(l->prompt); + int fd = l->ofd; + char *buf = l->buf; + size_t len = l->len; + size_t pos = l->pos; + struct abuf ab; + + while((plen+pos) >= l->cols) { + buf++; + len--; + pos--; + } + while (plen+len > l->cols) { + len--; + } + + abInit(&ab); + /* Cursor to left edge */ + snprintf(seq,64,"\r"); + abAppend(&ab,seq,strlen(seq)); + /* Write the prompt and the current buffer content */ + abAppend(&ab,l->prompt,strlen(l->prompt)); + if (maskmode == 1) { + while (len--) abAppend(&ab,"*",1); + } else { + abAppend(&ab,buf,len); + } + /* Show hits if any. */ + refreshShowHints(&ab,l,plen); + /* Erase to right */ + snprintf(seq,64,"\x1b[0K"); + abAppend(&ab,seq,strlen(seq)); + /* Move cursor to original position. */ + snprintf(seq,64,"\r\x1b[%dC", (int)(pos+plen)); + abAppend(&ab,seq,strlen(seq)); + if (write(fd,ab.b,ab.len) == -1) {} /* Can't recover from write error. */ + abFree(&ab); +} + +/* Multi line low level line refresh. + * + * Rewrite the currently edited line accordingly to the buffer content, + * cursor position, and number of columns of the terminal. */ +static void refreshMultiLine(struct linenoiseState *l) { + char seq[64]; + int plen = strlen(l->prompt); + int rows = (plen+l->len+l->cols-1)/l->cols; /* rows used by current buf. */ + int rpos = (plen+l->oldpos+l->cols)/l->cols; /* cursor relative row. */ + int rpos2; /* rpos after refresh. */ + int col; /* colum position, zero-based. */ + int old_rows = l->maxrows; + int fd = l->ofd, j; + struct abuf ab; + + /* Update maxrows if needed. */ + if (rows > (int)l->maxrows) l->maxrows = rows; + + /* First step: clear all the lines used before. To do so start by + * going to the last row. */ + abInit(&ab); + if (old_rows-rpos > 0) { + lndebug("go down %d", old_rows-rpos); + snprintf(seq,64,"\x1b[%dB", old_rows-rpos); + abAppend(&ab,seq,strlen(seq)); + } + + /* Now for every row clear it, go up. */ + for (j = 0; j < old_rows-1; j++) { + lndebug("clear+up"); + snprintf(seq,64,"\r\x1b[0K\x1b[1A"); + abAppend(&ab,seq,strlen(seq)); + } + + /* Clean the top line. */ + lndebug("clear"); + snprintf(seq,64,"\r\x1b[0K"); + abAppend(&ab,seq,strlen(seq)); + + /* Write the prompt and the current buffer content */ + abAppend(&ab,l->prompt,strlen(l->prompt)); + if (maskmode == 1) { + unsigned int i; + for (i = 0; i < l->len; i++) abAppend(&ab,"*",1); + } else { + abAppend(&ab,l->buf,l->len); + } + + /* Show hits if any. */ + refreshShowHints(&ab,l,plen); + + /* If we are at the very end of the screen with our prompt, we need to + * emit a newline and move the prompt to the first column. */ + if (l->pos && + l->pos == l->len && + (l->pos+plen) % l->cols == 0) + { + lndebug(""); + abAppend(&ab,"\n",1); + snprintf(seq,64,"\r"); + abAppend(&ab,seq,strlen(seq)); + rows++; + if (rows > (int)l->maxrows) l->maxrows = rows; + } + + /* Move cursor to right position. */ + rpos2 = (plen+l->pos+l->cols)/l->cols; /* current cursor relative row. */ + lndebug("rpos2 %d", rpos2); + + /* Go up till we reach the expected positon. */ + if (rows-rpos2 > 0) { + lndebug("go-up %d", rows-rpos2); + snprintf(seq,64,"\x1b[%dA", rows-rpos2); + abAppend(&ab,seq,strlen(seq)); + } + + /* Set column. */ + col = (plen+(int)l->pos) % (int)l->cols; + lndebug("set col %d", 1+col); + if (col) + snprintf(seq,64,"\r\x1b[%dC", col); + else + snprintf(seq,64,"\r"); + abAppend(&ab,seq,strlen(seq)); + + lndebug("\n"); + l->oldpos = l->pos; + + if (write(fd,ab.b,ab.len) == -1) {} /* Can't recover from write error. */ + abFree(&ab); +} + +/* Calls the two low level functions refreshSingleLine() or + * refreshMultiLine() according to the selected mode. */ +static void refreshLine(struct linenoiseState *l) { + if (mlmode) + refreshMultiLine(l); + else + refreshSingleLine(l); +} + +/* Insert the character 'c' at cursor current position. + * + * On error writing to the terminal -1 is returned, otherwise 0. */ +int linenoiseEditInsert(struct linenoiseState *l, char c) { + if (l->len < l->buflen) { + if (l->len == l->pos) { + l->buf[l->pos] = c; + l->pos++; + l->len++; + l->buf[l->len] = '\0'; + if ((!mlmode && l->plen+l->len < l->cols && !hintsCallback)) { + /* Avoid a full update of the line in the + * trivial case. */ + char d = (maskmode==1) ? '*' : c; + if (write(l->ofd,&d,1) == -1) return -1; + } else { + refreshLine(l); + } + } else { + memmove(l->buf+l->pos+1,l->buf+l->pos,l->len-l->pos); + l->buf[l->pos] = c; + l->len++; + l->pos++; + l->buf[l->len] = '\0'; + refreshLine(l); + } + } + return 0; +} + +/* Move cursor on the left. */ +void linenoiseEditMoveLeft(struct linenoiseState *l) { + if (l->pos > 0) { + l->pos--; + refreshLine(l); + } +} + +/* Move cursor on the right. */ +void linenoiseEditMoveRight(struct linenoiseState *l) { + if (l->pos != l->len) { + l->pos++; + refreshLine(l); + } +} + +/* Move cursor to the start of the line. */ +void linenoiseEditMoveHome(struct linenoiseState *l) { + if (l->pos != 0) { + l->pos = 0; + refreshLine(l); + } +} + +/* Move cursor to the end of the line. */ +void linenoiseEditMoveEnd(struct linenoiseState *l) { + if (l->pos != l->len) { + l->pos = l->len; + refreshLine(l); + } +} + +/* Substitute the currently edited line with the next or previous history + * entry as specified by 'dir'. */ +#define LINENOISE_HISTORY_NEXT 0 +#define LINENOISE_HISTORY_PREV 1 +void linenoiseEditHistoryNext(struct linenoiseState *l, int dir) { + if (history_len > 1) { + /* Update the current history entry before to + * overwrite it with the next one. */ + free(history[history_len - 1 - l->history_index]); + history[history_len - 1 - l->history_index] = strdup(l->buf); + /* Show the new entry */ + l->history_index += (dir == LINENOISE_HISTORY_PREV) ? 1 : -1; + if (l->history_index < 0) { + l->history_index = 0; + return; + } else if (l->history_index >= history_len) { + l->history_index = history_len-1; + return; + } + strncpy(l->buf,history[history_len - 1 - l->history_index],l->buflen); + l->buf[l->buflen-1] = '\0'; + l->len = l->pos = strlen(l->buf); + refreshLine(l); + } +} + +/* Delete the character at the right of the cursor without altering the cursor + * position. Basically this is what happens with the "Delete" keyboard key. */ +void linenoiseEditDelete(struct linenoiseState *l) { + if (l->len > 0 && l->pos < l->len) { + memmove(l->buf+l->pos,l->buf+l->pos+1,l->len-l->pos-1); + l->len--; + l->buf[l->len] = '\0'; + refreshLine(l); + } +} + +/* Backspace implementation. */ +void linenoiseEditBackspace(struct linenoiseState *l) { + if (l->pos > 0 && l->len > 0) { + memmove(l->buf+l->pos-1,l->buf+l->pos,l->len-l->pos); + l->pos--; + l->len--; + l->buf[l->len] = '\0'; + refreshLine(l); + } +} + +/* Delete the previosu word, maintaining the cursor at the start of the + * current word. */ +void linenoiseEditDeletePrevWord(struct linenoiseState *l) { + size_t old_pos = l->pos; + size_t diff; + + while (l->pos > 0 && l->buf[l->pos-1] == ' ') + l->pos--; + while (l->pos > 0 && l->buf[l->pos-1] != ' ') + l->pos--; + diff = old_pos - l->pos; + memmove(l->buf+l->pos,l->buf+old_pos,l->len-old_pos+1); + l->len -= diff; + refreshLine(l); +} + +/* This function is the core of the line editing capability of linenoise. + * It expects 'fd' to be already in "raw mode" so that every key pressed + * will be returned ASAP to read(). + * + * The resulting string is put into 'buf' when the user type enter, or + * when ctrl+d is typed. + * + * The function returns the length of the current buffer. */ +static int linenoiseEdit(int stdin_fd, int stdout_fd, char *buf, size_t buflen, const char *prompt) +{ + struct linenoiseState l; + + /* Populate the linenoise state that we pass to functions implementing + * specific editing functionalities. */ + l.ifd = stdin_fd; + l.ofd = stdout_fd; + l.buf = buf; + l.buflen = buflen; + l.prompt = prompt; + l.plen = strlen(prompt); + l.oldpos = l.pos = 0; + l.len = 0; + l.cols = getColumns(stdin_fd, stdout_fd); + l.maxrows = 0; + l.history_index = 0; + + /* Buffer starts empty. */ + l.buf[0] = '\0'; + l.buflen--; /* Make sure there is always space for the nulterm */ + + /* The latest history entry is always our current buffer, that + * initially is just an empty string. */ + linenoiseHistoryAdd(""); + + if (write(l.ofd,prompt,l.plen) == -1) return -1; + while(1) { + char c; + int nread; + char seq[3]; + + nread = read(l.ifd,&c,1); + if (nread <= 0) return l.len; + + /* 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 == 9 && completionCallback != NULL) { + c = completeLine(&l); + /* Return on errors */ + if (c < 0) return l.len; + /* Read next character when 0 */ + if (c == 0) continue; + } + + switch(c) { + case ENTER: /* enter */ + history_len--; + free(history[history_len]); + if (mlmode) linenoiseEditMoveEnd(&l); + if (hintsCallback) { + /* Force a refresh without hints to leave the previous + * line as the user typed it after a newline. */ + linenoiseHintsCallback *hc = hintsCallback; + hintsCallback = NULL; + refreshLine(&l); + hintsCallback = hc; + } + return (int)l.len; + case CTRL_C: /* ctrl-c */ + errno = EAGAIN; + return -1; + case BACKSPACE: /* backspace */ + case 8: /* ctrl-h */ + linenoiseEditBackspace(&l); + break; + case CTRL_D: /* ctrl-d, remove char at right of cursor, or if the + line is empty, act as end-of-file. */ + if (l.len > 0) { + linenoiseEditDelete(&l); + } else { + history_len--; + free(history[history_len]); + return -1; + } + break; + case CTRL_T: /* ctrl-t, swaps current character with previous. */ + if (l.pos > 0 && l.pos < l.len) { + int aux = buf[l.pos-1]; + buf[l.pos-1] = buf[l.pos]; + buf[l.pos] = aux; + if (l.pos != l.len-1) l.pos++; + refreshLine(&l); + } + break; + case CTRL_B: /* ctrl-b */ + linenoiseEditMoveLeft(&l); + break; + case CTRL_F: /* ctrl-f */ + linenoiseEditMoveRight(&l); + break; + case CTRL_P: /* ctrl-p */ + linenoiseEditHistoryNext(&l, LINENOISE_HISTORY_PREV); + break; + case CTRL_N: /* ctrl-n */ + linenoiseEditHistoryNext(&l, LINENOISE_HISTORY_NEXT); + break; + case ESC: /* escape sequence */ + /* Read the next two bytes representing the escape sequence. + * Use two calls to handle slow terminals returning the two + * chars at different times. */ + if (read(l.ifd,seq,1) == -1) break; + if (read(l.ifd,seq+1,1) == -1) break; + + /* ESC [ sequences. */ + if (seq[0] == '[') { + if (seq[1] >= '0' && seq[1] <= '9') { + /* Extended escape, read additional byte. */ + if (read(l.ifd,seq+2,1) == -1) break; + if (seq[2] == '~') { + switch(seq[1]) { + case '3': /* Delete key. */ + linenoiseEditDelete(&l); + break; + } + } + } else { + switch(seq[1]) { + case 'A': /* Up */ + linenoiseEditHistoryNext(&l, LINENOISE_HISTORY_PREV); + break; + case 'B': /* Down */ + linenoiseEditHistoryNext(&l, LINENOISE_HISTORY_NEXT); + break; + case 'C': /* Right */ + linenoiseEditMoveRight(&l); + break; + case 'D': /* Left */ + linenoiseEditMoveLeft(&l); + break; + case 'H': /* Home */ + linenoiseEditMoveHome(&l); + break; + case 'F': /* End*/ + linenoiseEditMoveEnd(&l); + break; + } + } + } + + /* ESC O sequences. */ + else if (seq[0] == 'O') { + switch(seq[1]) { + case 'H': /* Home */ + linenoiseEditMoveHome(&l); + break; + case 'F': /* End*/ + linenoiseEditMoveEnd(&l); + break; + } + } + break; + default: + if (linenoiseEditInsert(&l,c)) return -1; + break; + case CTRL_U: /* Ctrl+u, delete the whole line. */ + buf[0] = '\0'; + l.pos = l.len = 0; + refreshLine(&l); + break; + case CTRL_K: /* Ctrl+k, delete from current to end of line. */ + buf[l.pos] = '\0'; + l.len = l.pos; + refreshLine(&l); + break; + case CTRL_A: /* Ctrl+a, go to the start of the line */ + linenoiseEditMoveHome(&l); + break; + case CTRL_E: /* ctrl+e, go to the end of the line */ + linenoiseEditMoveEnd(&l); + break; + case CTRL_L: /* ctrl+l, clear screen */ + linenoiseClearScreen(); + refreshLine(&l); + break; + case CTRL_W: /* ctrl+w, delete previous word */ + linenoiseEditDeletePrevWord(&l); + break; + } + } + return l.len; +} + +/* This special mode is used by linenoise in order to print scan codes + * on screen for debugging / development purposes. It is implemented + * by the linenoise_example program using the --keycodes option. */ +void linenoisePrintKeyCodes(void) { + char quit[4]; + + printf("Linenoise key codes debugging mode.\n" + "Press keys to see scan codes. Type 'quit' at any time to exit.\n"); + if (enableRawMode(STDIN_FILENO) == -1) return; + memset(quit,' ',4); + while(1) { + char c; + int nread; + + nread = read(STDIN_FILENO,&c,1); + if (nread <= 0) continue; + memmove(quit,quit+1,sizeof(quit)-1); /* shift string to left. */ + quit[sizeof(quit)-1] = c; /* Insert current char on the right. */ + if (memcmp(quit,"quit",sizeof(quit)) == 0) break; + + printf("'%c' %02x (%d) (type quit to exit)\n", + isprint(c) ? c : '?', (int)c, (int)c); + printf("\r"); /* Go left edge manually, we are in raw mode. */ + fflush(stdout); + } + disableRawMode(STDIN_FILENO); +} + +/* This function calls the line editing function linenoiseEdit() using + * the STDIN file descriptor set in raw mode. */ +static int linenoiseRaw(char *buf, size_t buflen, const char *prompt) { + int count; + + if (buflen == 0) { + errno = EINVAL; + return -1; + } + + if (enableRawMode(STDIN_FILENO) == -1) return -1; + count = linenoiseEdit(STDIN_FILENO, STDOUT_FILENO, buf, buflen, prompt); + disableRawMode(STDIN_FILENO); + printf("\n"); + return count; +} + +/* This function is called when linenoise() is called with the standard + * input file descriptor not attached to a TTY. So for example when the + * program using linenoise is called in pipe or with a file redirected + * to its standard input. In this case, we want to be able to return the + * line regardless of its length (by default we are limited to 4k). */ +static char *linenoiseNoTTY(void) { + char *line = NULL; + size_t len = 0, maxlen = 0; + + while(1) { + int c; + if (len == maxlen) { + char *oldval = line; + if (maxlen == 0) maxlen = 16; + maxlen *= 2; + line = realloc(line,maxlen); + if (line == NULL) { + if (oldval) free(oldval); + return NULL; + } + } + c = fgetc(stdin); + if (c == EOF || c == '\n') { + if (c == EOF && len == 0) { + free(line); + return NULL; + } else { + line[len] = '\0'; + return line; + } + } else { + line[len] = c; + len++; + } + } +} + +/* The high level function that is the main API of the linenoise library. + * This function checks if the terminal has basic capabilities, just checking + * for a blacklist of stupid terminals, and later either calls the line + * editing function or uses dummy fgets() so that you will be able to type + * something even in the most desperate of the conditions. */ +char *linenoise(const char *prompt) { + char buf[LINENOISE_MAX_LINE]; + int count; + + if (!isatty(STDIN_FILENO)) { + /* Not a tty: read from file / pipe. In this mode we don't want any + * limit to the line size, so we call a function to handle that. */ + return linenoiseNoTTY(); + } else if (isUnsupportedTerm()) { + size_t len; + + printf("%s",prompt); + fflush(stdout); + if (fgets(buf,LINENOISE_MAX_LINE,stdin) == NULL) return NULL; + len = strlen(buf); + while(len && (buf[len-1] == '\n' || buf[len-1] == '\r')) { + len--; + buf[len] = '\0'; + } + return strdup(buf); + } else { + count = linenoiseRaw(buf,LINENOISE_MAX_LINE,prompt); + if (count == -1) return NULL; + return strdup(buf); + } +} + +/* This is just a wrapper the user may want to call in order to make sure + * the linenoise returned buffer is freed with the same allocator it was + * created with. Useful when the main program is using an alternative + * allocator. */ +void linenoiseFree(void *ptr) { + free(ptr); +} + +/* ================================ History ================================= */ + +/* Free the history, but does not reset it. Only used when we have to + * exit() to avoid memory leaks are reported by valgrind & co. */ +static void freeHistory(void) { + if (history) { + int j; + + for (j = 0; j < history_len; j++) + free(history[j]); + free(history); + } +} + +/* At exit we'll try to fix the terminal to the initial conditions. */ +static void linenoiseAtExit(void) { + disableRawMode(STDIN_FILENO); + freeHistory(); +} + +/* This is the API call to add a new entry in the linenoise history. + * It uses a fixed array of char pointers that are shifted (memmoved) + * when the history max length is reached in order to remove the older + * entry and make room for the new one, so it is not exactly suitable for huge + * histories, but will work well for a few hundred of entries. + * + * 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; + + /* Initialization on first call. */ + if (history == NULL) { + history = malloc(sizeof(char*)*history_max_len); + if (history == NULL) return 0; + memset(history,0,(sizeof(char*)*history_max_len)); + } + + /* Don't add duplicated lines. */ + if (history_len && !strcmp(history[history_len-1], line)) return 0; + + /* Add an heap allocated copy of the line in the history. + * If we reached the max length, remove the older line. */ + 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; +} + +/* Set the maximum length for the history. This function can be called even + * if there is already some history, the function will make sure to retain + * just the latest 'len' elements if the new history length value is smaller + * than the amount of items already inside the history. */ +int linenoiseHistorySetMaxLen(int len) { + char **new; + + if (len < 1) return 0; + if (history) { + int tocopy = history_len; + + new = malloc(sizeof(char*)*len); + if (new == 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(new,0,sizeof(char*)*len); + memcpy(new,history+(history_len-tocopy), sizeof(char*)*tocopy); + free(history); + history = new; + } + 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) { + mode_t old_umask = umask(S_IXUSR|S_IRWXG|S_IRWXO); + FILE *fp; + int j; + + fp = fopen(filename,"w"); + umask(old_umask); + if (fp == NULL) return -1; + chmod(filename,S_IRUSR|S_IWUSR); + for (j = 0; j < history_len; j++) + fprintf(fp,"%s\n",history[j]); + 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 *p; + + p = strchr(buf,'\r'); + if (!p) p = strchr(buf,'\n'); + if (p) *p = '\0'; + linenoiseHistoryAdd(buf); + } + fclose(fp); + return 0; +} ADDED src/linenoise.h Index: src/linenoise.h ================================================================== --- /dev/null +++ src/linenoise.h @@ -0,0 +1,75 @@ +/* linenoise.h -- VERSION 1.0 + * + * 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-2014, Salvatore Sanfilippo + * Copyright (c) 2010-2013, Pieter Noordhuis + * + * 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 + +#ifdef __cplusplus +extern "C" { +#endif + +typedef struct linenoiseCompletions { + size_t len; + char **cvec; +} linenoiseCompletions; + +typedef void(linenoiseCompletionCallback)(const char *, linenoiseCompletions *); +typedef char*(linenoiseHintsCallback)(const char *, int *color, int *bold); +typedef void(linenoiseFreeHintsCallback)(void *); +void linenoiseSetCompletionCallback(linenoiseCompletionCallback *); +void linenoiseSetHintsCallback(linenoiseHintsCallback *); +void linenoiseSetFreeHintsCallback(linenoiseFreeHintsCallback *); +void linenoiseAddCompletion(linenoiseCompletions *, const char *); + +char *linenoise(const char *prompt); +void linenoiseFree(void *ptr); +int linenoiseHistoryAdd(const char *line); +int linenoiseHistorySetMaxLen(int len); +int linenoiseHistorySave(const char *filename); +int linenoiseHistoryLoad(const char *filename); +void linenoiseClearScreen(void); +void linenoiseSetMultiLine(int ml); +void linenoisePrintKeyCodes(void); +void linenoiseMaskModeEnable(void); +void linenoiseMaskModeDisable(void); + +#ifdef __cplusplus +} +#endif + +#endif /* __LINENOISE_H */ ADDED src/loadctrl.c Index: src/loadctrl.c ================================================================== --- /dev/null +++ src/loadctrl.c @@ -0,0 +1,67 @@ +/* +** 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 file contains code to check the host load-average and abort +** CPU-intensive operations if the load-average is too high. +*/ +#include "config.h" +#include "loadctrl.h" +#include + +/* +** Return the load average for the host processor +*/ +double load_average(void){ +#if !defined(_WIN32) && !defined(FOSSIL_OMIT_LOAD_AVERAGE) + double a[3]; + if( getloadavg(a, 3)>0 ){ + return a[0]>=0.000001 ? a[0] : 0.000001; + } +#endif + return 0.0; +} + +/* +** COMMAND: test-loadavg +** +** %fossil test-loadavg +** +** Print the load average on the host machine. +*/ +void loadavg_test_cmd(void){ + fossil_print("load-average: %f\n", load_average()); +} + +/* +** Abort the current operation of the load average of the host computer +** is too high. +*/ +void load_control(void){ + double mxLoad = atof(db_get("max-loadavg", 0)); + if( mxLoad<=0.0 || mxLoad>=load_average() ) return; + + style_set_current_feature("test"); + style_header("Server Overload"); + @

The server load is currently too high. + @ Please try again later.

+ @

Current load average: %f(load_average()).
+ @ Load average limit: %f(mxLoad)

+ style_finish_page(); + cgi_set_status(503,"Server Overload"); + cgi_reply(); + exit(0); +} Index: src/login.c ================================================================== --- src/login.c +++ src/login.c @@ -17,16 +17,20 @@ ** ** This file contains code for generating the login and logout screens. ** ** Notes: ** -** There are two special-case user-ids: "anonymous" and "nobody". +** There are four special-case user-ids: "anonymous", "nobody", +** "developer" and "reader". +** ** The capabilities of the nobody user are available to anyone, ** regardless of whether or not they are logged in. The capabilities ** of anonymous are only available after logging in, but the login ** screen displays the password for the anonymous login, so this -** should not prevent a human user from doing so. +** should not prevent a human user from doing so. The capabilities +** of developer and reader are inherited by any user that has the +** "v" and "u" capabilities, respectively. ** ** The nobody user has capabilities that you want spiders to have. ** The anonymous user has capabilities that you want people without ** logins to have. ** @@ -37,27 +41,63 @@ ** logs and downloading diffs of very version of the archive that ** has ever existed, and things like that. */ #include "config.h" #include "login.h" -#ifdef __MINGW32__ +#if defined(_WIN32) # include /* for Sleep */ -# define sleep Sleep /* windows does not have sleep, but Sleep */ +# if defined(__MINGW32__) || defined(_MSC_VER) +# define sleep Sleep /* windows does not have sleep, but Sleep */ +# endif #endif #include + +/* +** Return the login-group name. Or return 0 if this repository is +** not a member of a login-group. +*/ +const char *login_group_name(void){ + static const char *zGroup = 0; + static int once = 1; + if( once ){ + zGroup = db_get("login-group-name", 0); + once = 0; + } + return zGroup; +} + +/* +** Return a path appropriate for setting a cookie. +** +** The path is g.zTop for single-repo cookies. It is "/" for +** cookies of a login-group. +*/ +const char *login_cookie_path(void){ + if( login_group_name()==0 ){ + return g.zTop; + }else{ + return "/"; + } +} + /* -** Return the name of the login cookie +** Return the name of the login cookie. +** +** The login cookie name is always of the form: fossil-XXXXXXXXXXXXXXXX +** where the Xs are the first 16 characters of the login-group-code or +** of the project-code if we are not a member of any login-group. */ -static char *login_cookie_name(void){ +char *login_cookie_name(void){ static char *zCookieName = 0; if( zCookieName==0 ){ - int n = strlen(g.zTop); - zCookieName = malloc( n*2+16 ); - /* 0123456789 12345 */ - strcpy(zCookieName, "fossil_login_"); - encode16((unsigned char*)g.zTop, (unsigned char*)&zCookieName[13], n); + zCookieName = db_text(0, + "SELECT 'fossil-' || substr(value,1,16)" + " FROM config" + " WHERE name IN ('project-code','login-group-code')" + " ORDER BY name /*sort*/" + ); } return zCookieName; } /* @@ -72,349 +112,1032 @@ fossil_redirect_home(); } } /* -** The IP address of the client is stored as part of the anonymous -** login cookie for additional security. But some clients are behind -** firewalls that shift the IP address with each HTTP request. To -** allow such (broken) clients to log in, extract just a prefix of the -** IP address. +** Return an abbreviated project code. The abbreviation is the first +** 16 characters of the project code. +** +** Memory is obtained from malloc. */ -static char *ipPrefix(const char *zIP){ - int i, j; - for(i=j=0; zIP[i]; i++){ - if( zIP[i]=='.' ){ - j++; - if( j==2 ) break; - } - } - return mprintf("%.*s", i, zIP); -} - +static char *abbreviated_project_code(const char *zFullCode){ + return mprintf("%.16s", zFullCode); +} + /* ** Check to see if the anonymous login is valid. If it is valid, return ** the userid of the anonymous user. +** +** The zCS parameter is the "captcha seed" used for a specific +** anonymous login request. */ -static int isValidAnonymousLogin( +int login_is_valid_anonymous( const char *zUsername, /* The username. Must be "anonymous" */ - const char *zPassword /* The supplied password */ + const char *zPassword, /* The supplied password */ + const char *zCS /* The captcha seed value */ ){ - const char *zCS; /* The captcha seed value */ const char *zPw; /* The correct password shown in the captcha */ int uid; /* The user ID of anonymous */ if( zUsername==0 ) return 0; - if( zPassword==0 ) return 0; - if( strcmp(zUsername,"anonymous")!=0 ) return 0; - zCS = P("cs"); /* The "cs" parameter is the "captcha seed" */ - if( zCS==0 ) return 0; + else if( zPassword==0 ) return 0; + else if( zCS==0 ) return 0; + else if( fossil_strcmp(zUsername,"anonymous")!=0 ) return 0; zPw = captcha_decode((unsigned int)atoi(zCS)); - if( strcasecmp(zPw, zPassword)!=0 ) return 0; + if( fossil_stricmp(zPw, zPassword)!=0 ) return 0; uid = db_int(0, "SELECT uid FROM user WHERE login='anonymous'" " AND length(pw)>0 AND length(cap)>0"); return uid; } /* -** WEBPAGE: login -** WEBPAGE: logout -** WEBPAGE: my +** Make sure the accesslog table exists. Create it if it does not +*/ +void create_accesslog_table(void){ + db_multi_exec( + "CREATE TABLE IF NOT EXISTS repository.accesslog(" + " uname TEXT," + " ipaddr TEXT," + " success BOOLEAN," + " mtime TIMESTAMP" + ");" + ); +} + +/* +** Make a record of a login attempt, if login record keeping is enabled. +*/ +static void record_login_attempt( + const char *zUsername, /* Name of user logging in */ + const char *zIpAddr, /* IP address from which they logged in */ + int bSuccess /* True if the attempt was a success */ +){ + if( db_get_boolean("access-log", 0) ){ + create_accesslog_table(); + db_multi_exec( + "INSERT INTO accesslog(uname,ipaddr,success,mtime)" + "VALUES(%Q,%Q,%d,julianday('now'));", + zUsername, zIpAddr, bSuccess + ); + } + if( bSuccess ){ + alert_user_contact(zUsername); + } +} + +/* +** Searches for the user ID matching the given name and password. +** On success it returns a positive value. On error it returns 0. +** On serious (DB-level) error it will probably exit. +** +** zUsername uses double indirection because we may re-point *zUsername +** at a C string allocated with fossil_strdup() if you pass an email +** address instead and we find that address in the user table's info +** field, which is expected to contain a string of the form "Human Name +** ". In that case, *zUsername will point to that +** user's actual login name on return, causing a leak unless the caller +** is diligent enough to check whether its pointer was re-pointed. +** +** zPassword may be either the plain-text form or the encrypted +** form of the user's password. +*/ +int login_search_uid(const char **pzUsername, const char *zPasswd){ + char *zSha1Pw = sha1_shared_secret(zPasswd, *pzUsername, 0); + int uid = db_int(0, + "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))" + " AND (info NOT LIKE '%%expires 20%%'" + " OR substr(info,instr(lower(info),'expires')+8,10)>datetime('now'))", + *pzUsername, zSha1Pw, zPasswd + ); + + /* If we did not find a login on the first attempt, and the username + ** looks like an email address, then perhaps the user entered their + ** email address instead of their login. Try again to match the user + ** against email addresses contained in the "info" field. + */ + if( uid==0 && strchr(*pzUsername,'@')!=0 ){ + Stmt q; + db_prepare(&q, + "SELECT login FROM user" + " WHERE find_emailaddr(info)=%Q" + " AND instr(login,'@')==0", + *pzUsername + ); + while( db_step(&q)==SQLITE_ROW ){ + const char *zLogin = db_column_text(&q,0); + if( (uid = login_search_uid(&zLogin, zPasswd) ) != 0 ){ + *pzUsername = fossil_strdup(zLogin); + break; + } + } + db_finalize(&q); + } + free(zSha1Pw); + return uid; +} + +/* +** Generates a login cookie value for a non-anonymous user. +** +** The zHash parameter must be a random value which must be +** subsequently stored in user.cookie for later validation. +** +** The returned memory should be free()d after use. +*/ +char *login_gen_user_cookie_value(const char *zUsername, const char *zHash){ + char *zProjCode = db_get("project-code",NULL); + char *zCode = abbreviated_project_code(zProjCode); + free(zProjCode); + assert((zUsername && *zUsername) && "Invalid user data."); + return mprintf("%s/%z/%s", zHash, zCode, zUsername); +} + +/* +** Generates a login cookie for NON-ANONYMOUS users. Note that this +** function "could" figure out the uid by itself but it currently +** doesn't because the code which calls this already has the uid. +** +** This function also updates the user.cookie, user.ipaddr, +** and user.cexpire fields for the given user. +** +** If zDest is not NULL then the generated cookie is copied to +** *zDdest and ownership is transfered to the caller (who should +** eventually pass it to free()). +** +** If bSessionCookie is true, the cookie will be a session cookie, +** else a persistent cookie. If it's a session cookie, the +** [user].[cexpire] and [user].[cookie] entries will be modified as if +** it were a persistent cookie because doing so is necessary for +** fossil's own "is this cookie still valid?" checks to work. +*/ +void login_set_user_cookie( + const char *zUsername, /* User's name */ + int uid, /* User's ID */ + char **zDest, /* Optional: store generated cookie value. */ + int bSessionCookie /* True for session-only cookie */ +){ + const char *zCookieName = login_cookie_name(); + const char *zExpire = db_get("cookie-expire","8766"); + const int expires = atoi(zExpire)*3600; + char *zHash = 0; + char *zCookie; + const char *zIpAddr = PD("REMOTE_ADDR","nil"); /* IP address of user */ + + assert((zUsername && *zUsername) && (uid > 0) && "Invalid user data."); + zHash = db_text(0, + "SELECT cookie FROM user" + " WHERE uid=%d" + " AND cexpire>julianday('now')" + " AND length(cookie)>30", + uid); + if( zHash==0 ) zHash = db_text(0, "SELECT hex(randomblob(25))"); + zCookie = login_gen_user_cookie_value(zUsername, zHash); + cgi_set_cookie(zCookieName, zCookie, login_cookie_path(), + bSessionCookie ? 0 : expires); + record_login_attempt(zUsername, zIpAddr, 1); + db_unprotect(PROTECT_USER); + db_multi_exec("UPDATE user SET cookie=%Q," + " cexpire=julianday('now')+%d/86400.0 WHERE uid=%d", + zHash, expires, uid); + db_protect_pop(); + fossil_free(zHash); + if( zDest ){ + *zDest = zCookie; + }else{ + free(zCookie); + } +} + +/* Sets a cookie for an anonymous user login, which looks like this: +** +** HASH/TIME/anonymous +** +** Where HASH is the sha1sum of TIME/SECRET, in which SECRET is captcha-secret. +** +** If zCookieDest is not NULL then the generated cookie is assigned to +** *zCookieDest and the caller must eventually free() it. +** +** If bSessionCookie is true, the cookie will be a session cookie. +*/ +void login_set_anon_cookie(const char *zIpAddr, char **zCookieDest, + int bSessionCookie ){ + const char *zNow; /* Current time (julian day number) */ + char *zCookie; /* The login cookie */ + const char *zCookieName; /* Name of the login cookie */ + Blob b; /* Blob used during cookie construction */ + int expires = bSessionCookie ? 0 : 6*3600; + zCookieName = login_cookie_name(); + zNow = db_text("0", "SELECT julianday('now')"); + assert( zCookieName && zNow ); + blob_init(&b, zNow, -1); + blob_appendf(&b, "/%s", db_get("captcha-secret","")); + sha1sum_blob(&b, &b); + zCookie = mprintf("%s/%s/anonymous", blob_buffer(&b), zNow); + blob_reset(&b); + cgi_set_cookie(zCookieName, zCookie, login_cookie_path(), expires); + if( zCookieDest ){ + *zCookieDest = zCookie; + }else{ + free(zCookie); + } +} + +/* +** "Unsets" the login cookie (insofar as cookies can be unset) and +** clears the current user's (g.userUid) login information from the +** user table. Sets: user.cookie, user.ipaddr, user.cexpire. +** +** We could/should arguably clear out g.userUid and g.perm here, but +** we don't currently do not. +** +** This is a no-op if g.userUid is 0. +*/ +void login_clear_login_data(){ + if(!g.userUid){ + return; + }else{ + const char *cookie = login_cookie_name(); + /* To logout, change the cookie value to an empty string */ + cgi_set_cookie(cookie, "", + login_cookie_path(), -86400); + db_unprotect(PROTECT_USER); + db_multi_exec("UPDATE user SET cookie=NULL, ipaddr=NULL, " + " cexpire=0 WHERE uid=%d" + " AND login NOT IN ('anonymous','nobody'," + " 'developer','reader')", g.userUid); + db_protect_pop(); + cgi_replace_parameter(cookie, NULL); + cgi_replace_parameter("anon", NULL); + } +} + +/* +** Return true if the prefix of zStr matches zPattern. Return false if +** they are different. +** +** A lowercase character in zPattern will match either upper or lower +** case in zStr. But an uppercase in zPattern will only match an +** uppercase in zStr. +*/ +static int prefix_match(const char *zPattern, const char *zStr){ + int i; + char c; + for(i=0; (c = zPattern[i])!=0; i++){ + if( zStr[i]!=c && fossil_tolower(zStr[i])!=c ) return 0; + } + return 1; +} + +/* +** Look at the HTTP_USER_AGENT parameter and try to determine if the user agent +** is a manually operated browser or a bot. When in doubt, assume a bot. +** Return true if we believe the agent is a real person. +*/ +static int isHuman(const char *zAgent){ + int i; + if( zAgent==0 ) return 0; /* If no UserAgent, then probably a bot */ + for(i=0; zAgent[i]; i++){ + if( prefix_match("bot", zAgent+i) ) return 0; + if( prefix_match("spider", zAgent+i) ) return 0; + if( prefix_match("crawl", zAgent+i) ) return 0; + /* If a URI appears in the User-Agent, it is probably a bot */ + if( strncmp("http", zAgent+i,4)==0 ) return 0; + } + if( strncmp(zAgent, "Mozilla/", 8)==0 ){ + if( atoi(&zAgent[8])<4 ) return 0; /* Many bots advertise as Mozilla/3 */ + + /* 2016-05-30: A pernicious spider that likes to walk Fossil timelines has + ** been detected on the SQLite website. The spider changes its user-agent + ** string frequently, but it always seems to include the following text: + */ + if( sqlite3_strglob("*Safari/537.36Mozilla/5.0*", zAgent)==0 ) return 0; + + if( sqlite3_strglob("*Firefox/[1-9]*", zAgent)==0 ) return 1; + if( sqlite3_strglob("*Chrome/[1-9]*", zAgent)==0 ) return 1; + if( sqlite3_strglob("*(compatible;?MSIE?[1789]*", zAgent)==0 ) return 1; + if( sqlite3_strglob("*Trident/[1-9]*;?rv:[1-9]*", zAgent)==0 ){ + return 1; /* IE11+ */ + } + if( sqlite3_strglob("*AppleWebKit/[1-9]*(KHTML*", zAgent)==0 ) return 1; + if( sqlite3_strglob("*PaleMoon/[1-9]*", zAgent)==0 ) return 1; + return 0; + } + if( strncmp(zAgent, "Opera/", 6)==0 ) return 1; + if( strncmp(zAgent, "Safari/", 7)==0 ) return 1; + if( strncmp(zAgent, "Lynx/", 5)==0 ) return 1; + if( strncmp(zAgent, "NetSurf/", 8)==0 ) return 1; + return 0; +} + +/* +** Make a guess at whether or not the requestor is a mobile device or +** a desktop device (narrow screen vs. wide screen) based the HTTP_USER_AGENT +** parameter. Return true for mobile and false for desktop. +** +** Caution: This is only a guess. +** +** Algorithm derived from https://developer.mozilla.org/en-US/docs/Web/ +** HTTP/Browser_detection_using_the_user_agent#mobile_device_detection on +** 2021-03-01 +*/ +int user_agent_is_likely_mobile(void){ + const char *zAgent = P("HTTP_USER_AGENT"); + if( zAgent==0 ) return 0; + if( strstr(zAgent,"Mobi")!=0 ) return 1; + return 0; +} + +/* +** COMMAND: test-ishuman ** -** Generate the login page. -** +** Read lines of text from standard input. Interpret each line of text +** as a User-Agent string from an HTTP header. Label each line as HUMAN +** or ROBOT. +*/ +void test_ishuman(void){ + char zLine[3000]; + while( fgets(zLine, sizeof(zLine), stdin) ){ + fossil_print("%s %s", isHuman(zLine) ? "HUMAN" : "ROBOT", zLine); + } +} + +/* +** SQL function for constant time comparison of two values. +** Sets result to 0 if two values are equal. +*/ +static void constant_time_cmp_function( + sqlite3_context *context, + int argc, + sqlite3_value **argv +){ + const unsigned char *buf1, *buf2; + int len, i; + unsigned char rc = 0; + + assert( argc==2 ); + len = sqlite3_value_bytes(argv[0]); + if( len==0 || len!=sqlite3_value_bytes(argv[1]) ){ + rc = 1; + }else{ + buf1 = sqlite3_value_text(argv[0]); + buf2 = sqlite3_value_text(argv[1]); + for( i=0; i - @ You entered an incorrect old password while attempting to change - @ your password. Your password is unchanged. - @

- ; - }else if( strcmp(zNew1,zNew2)!=0 ){ - zErrMsg = - @

- @ The two copies of your new passwords do not match. - @ Your password is unchanged. - @

- ; - }else{ - char *zNewPw = sha1_shared_secret(zNew1, g.zLogin); - db_multi_exec( - "UPDATE user SET pw=%Q WHERE uid=%d", zNewPw, g.userUid - ); - redirect_to_g(); - return; - } - } - uid = isValidAnonymousLogin(zUsername, zPasswd); - if( uid>0 ){ - char *zNow; /* Current time (julian day number) */ - const char *zIpAddr; /* IP address of requestor */ - char *zCookie; /* The login cookie */ - const char *zCookieName; /* Name of the login cookie */ - Blob b; /* Blob used during cookie construction */ - - zIpAddr = PD("REMOTE_ADDR","nil"); - zCookieName = login_cookie_name(); - zNow = db_text("0", "SELECT julianday('now')"); - blob_init(&b, zNow, -1); - blob_appendf(&b, "/%z/%s", ipPrefix(zIpAddr), db_get("captcha-secret","")); - sha1sum_blob(&b, &b); - zCookie = sqlite3_mprintf("anon/%s/%s", zNow, blob_buffer(&b)); - blob_reset(&b); - free(zNow); - cgi_set_cookie(zCookieName, zCookie, 0, 6*3600); - redirect_to_g(); - } - if( zUsername!=0 && zPasswd!=0 && zPasswd[0]!=0 ){ - zSha1Pw = sha1_shared_secret(zPasswd, zUsername); - uid = db_int(0, - "SELECT uid FROM user" - " WHERE login=%Q" - " AND login NOT IN ('anonymous','nobody','developer','reader')" - " AND (pw=%Q OR pw=%Q)", - zUsername, zPasswd, zSha1Pw - ); - if( uid<=0 ){ - sleep(1); - zErrMsg = - @

- @ You entered an unknown user or an incorrect password. - @

- ; - }else{ - char *zCookie; - const char *zCookieName = login_cookie_name(); - const char *zExpire = db_get("cookie-expire","8766"); - int expires = atoi(zExpire)*3600; - const char *zIpAddr = PD("REMOTE_ADDR","nil"); - - zCookie = db_text(0, "SELECT '%d/' || hex(randomblob(25))", uid); - cgi_set_cookie(zCookieName, zCookie, 0, expires); - db_multi_exec( - "UPDATE user SET cookie=%Q, ipaddr=%Q, " - " cexpire=julianday('now')+%d/86400.0 WHERE uid=%d", - zCookie, zIpAddr, expires, uid - ); - redirect_to_g(); - } - } - style_header("Login/Logout"); - @ %s(zErrMsg) - @
- if( P("g") ){ - @ - } - @ - @ - @ - if( anonFlag ){ - @ - }else{ - @ - } - @ - @ - @ - @ - @ - if( g.zLogin==0 ){ - zAnonPw = db_text(0, "SELECT pw FROM user" - " WHERE login='anonymous'" - " AND cap!=''"); - } - @ - @ - @ - @ - @
User ID:
Password:
- @ - if( g.zLogin==0 ){ - @

Enter - }else{ - @

You are currently logged in as %h(g.zLogin)

- @

To change your login to a different user, enter - } - @ your user-id and password at the left and press the - @ "Login" button. Your user name will be stored in a browser cookie. - @ You must configure your web browser to accept cookies in order for - @ the login to take.

- if( zAnonPw ){ - unsigned int uSeed = captcha_seed(); - char const *zDecoded = captcha_decode(uSeed); - int bAutoCaptcha = db_get_boolean("auto-captcha", 0); - char *zCaptcha = captcha_render(zDecoded); - - @ - @

Visitors may enter anonymous as the user-ID with - @ the 8-character hexadecimal password shown below:

- @
-    @ %s(zCaptcha)
-    @ 
- if( bAutoCaptcha ) { - @ - } - @
- free(zCaptcha); - } - if( g.zLogin ){ - @

- @

To log off the system (and delete your login cookie) - @ press the following button:
- @

- } - @
- if( g.okPassword ){ - @

- @

To change your password, enter your old password and your - @ new password twice below then press the "Change Password" - @ button.

- @
- @ - @ - @ - @ - @ - @ - @ - @ - @ - @
Old Password:
New Password:
Repeat New Password:
- @
- } - style_footer(); -} - - - -/* -** This routine examines the login cookie to see if it exists and -** and is valid. If the login cookie checks out, it then sets -** g.zUserUuid appropriately. + const char *zGoto = P("g"); + int anonFlag; /* Login as "anonymous" would be useful */ + char *zErrMsg = ""; + int uid; /* User id logged in user */ + char *zSha1Pw; + const char *zIpAddr; /* IP address of requestor */ + const char *zReferer; + const int noAnon = P("noanon")!=0; + int rememberMe; /* If true, use persistent cookie, else + session cookie. Toggled per + checkbox. */ + + login_check_credentials(); + fossil_redirect_to_https_if_needed(1); + sqlite3_create_function(g.db, "constant_time_cmp", 2, SQLITE_UTF8, 0, + constant_time_cmp_function, 0, 0); + zUsername = P("u"); + zPasswd = P("p"); + anonFlag = g.zLogin==0 && PB("anon"); + /* Handle log-out requests */ + if( P("out") ){ + login_clear_login_data(); + redirect_to_g(); + return; + } + + /* Redirect for create-new-account requests */ + if( P("self") ){ + cgi_redirectf("%R/register"); + return; + } + + /* Deal with password-change requests */ + if( g.perm.Password && zPasswd + && (zNew1 = P("n1"))!=0 && (zNew2 = P("n2"))!=0 + ){ + /* If there is not a "real" login, we cannot change any password. */ + if( g.zLogin ){ + /* The user requests a password change */ + zSha1Pw = sha1_shared_secret(zPasswd, g.zLogin, 0); + if( db_int(1, "SELECT 0 FROM user" + " WHERE uid=%d" + " AND (constant_time_cmp(pw,%Q)=0" + " OR constant_time_cmp(pw,%Q)=0)", + g.userUid, zSha1Pw, zPasswd) ){ + sleep(1); + zErrMsg = + @

+ @ You entered an incorrect old password while attempting to change + @ your password. Your password is unchanged. + @

+ ; + }else if( fossil_strcmp(zNew1,zNew2)!=0 ){ + zErrMsg = + @

+ @ The two copies of your new passwords do not match. + @ Your password is unchanged. + @

+ ; + }else{ + char *zNewPw = sha1_shared_secret(zNew1, g.zLogin, 0); + char *zChngPw; + char *zErr; + int rc; + + db_unprotect(PROTECT_USER); + db_multi_exec( + "UPDATE user SET pw=%Q WHERE uid=%d", zNewPw, g.userUid + ); + zChngPw = mprintf( + "UPDATE user" + " SET pw=shared_secret(%Q,%Q," + " (SELECT value FROM config WHERE name='project-code'))" + " WHERE login=%Q", + zNew1, g.zLogin, g.zLogin + ); + fossil_free(zNewPw); + rc = login_group_sql(zChngPw, "

", "

\n", &zErr); + db_protect_pop(); + if( rc ){ + zErrMsg = mprintf("%s", zErr); + fossil_free(zErr); + }else{ + redirect_to_g(); + return; + } + } + }else{ + zErrMsg = + @

+ @ The password cannot be changed for this type of login. + @ The password is unchanged. + @

+ ; + } + } + zIpAddr = PD("REMOTE_ADDR","nil"); /* Complete IP address for logging */ + zReferer = P("HTTP_REFERER"); + uid = login_is_valid_anonymous(zUsername, zPasswd, P("cs")); + if(zUsername==0){ + /* Initial login page hit. */ + rememberMe = 0; + }else{ + rememberMe = P("remember")!=0; + } + if( uid>0 ){ + login_set_anon_cookie(zIpAddr, NULL, rememberMe?0:1); + record_login_attempt("anonymous", zIpAddr, 1); + redirect_to_g(); + } + if( zUsername!=0 && zPasswd!=0 && zPasswd[0]!=0 ){ + /* Attempting to log in as a user other than anonymous. + */ + uid = login_search_uid(&zUsername, zPasswd); + if( uid<=0 ){ + sleep(1); + zErrMsg = + @

+ @ You entered an unknown user or an incorrect password. + @

+ ; + record_login_attempt(zUsername, zIpAddr, 0); + cgi_set_status(401, "Unauthorized"); + }else{ + /* Non-anonymous login is successful. Set a cookie of the form: + ** + ** HASH/PROJECT/LOGIN + ** + ** where HASH is a random hex number, PROJECT is either project + ** code prefix, and LOGIN is the user name. + */ + login_set_user_cookie(zUsername, uid, NULL, rememberMe?0:1); + redirect_to_g(); + } + } + style_set_current_feature("login"); + style_header("Login/Logout"); + style_adunit_config(ADUNIT_OFF); + @ %s(zErrMsg) + if( zGoto && !noAnon ){ + char *zAbbrev = fossil_strdup(zGoto); + int i; + for(i=0; zAbbrev[i] && zAbbrev[i]!='?'; i++){} + zAbbrev[i] = 0; + if( g.zLogin ){ + @

Use a different login with greater privilege than %h(g.zLogin) + @ to access %h(zAbbrev). + }else if( anonFlag ){ + @

Login as anonymous or any named user + @ to access page %h(zAbbrev). + }else{ + @

Login as a named user to access page %h(zAbbrev). + } + fossil_free(zAbbrev); + } + if( g.sslNotAvailable==0 + && strncmp(g.zBaseURL,"https:",6)!=0 + && db_get_boolean("https-login",0) + ){ + form_begin(0, "https:%s/login", g.zBaseURL+5); + }else{ + form_begin(0, "%R/login"); + } + if( zGoto ){ + @ + }else if( zReferer && strncmp(g.zBaseURL, zReferer, strlen(g.zBaseURL))==0 ){ + @ + } + if( anonFlag ){ + @ + } + if( g.zLogin ){ + @

Currently logged in as %h(g.zLogin). + @

+ @ + }else{ + unsigned int uSeed = captcha_seed(); + if( g.zLogin==0 && (anonFlag || zGoto==0) ){ + zAnonPw = db_text(0, "SELECT pw FROM user" + " WHERE login='anonymous'" + " AND cap!=''"); + }else{ + zAnonPw = 0; + } + @ + if( P("HTTPS")==0 ){ + @ + @ + } + @ + @ + @ + @ + @ + @ + @ + @ + @ + @ + @ + @ + @ + @ + @ + if( !noAnon && login_self_register_available(0) ){ + @ + @ + @ + } + @ + if( zAnonPw && !noAnon ){ + const char *zDecoded = captcha_decode(uSeed); + int bAutoCaptcha = db_get_boolean("auto-captcha", 0); + char *zCaptcha = captcha_render(zDecoded); + + @

+ @ Visitors may enter anonymous as the user-ID with + @ the 8-character hexadecimal password shown below:

+ @
\ + @
+      @ %h(zCaptcha)
+      @ 
+ if( bAutoCaptcha ) { + @ + builtin_request_js("login.js"); + } + @
+ free(zCaptcha); + } + @ + } + if( login_is_individual() ){ + if( g.perm.EmailAlert && alert_enabled() ){ + @
+ @

Configure Email Alerts + @ for user %h(g.zLogin)

+ } + if( db_table_exists("repository","forumpost") ){ + @

+ @ Forum + @ post timeline for user %h(g.zLogin)

+ } + if( g.perm.Password ){ + char *zRPW = fossil_random_password(12); + @
+ @

Change Password for user %h(g.zLogin):

+ form_begin(0, "%R/login"); + @ + @ + @ + @ + @ + @ + @ + @ + @ + @
Old Password:
New Password: Suggestion: %z(zRPW)
Repeat New Password:
+ @ + } + } + style_finish_page(); +} + +/* +** Attempt to find login credentials for user zLogin on a peer repository +** with project code zCode. Transfer those credentials to the local +** repository. +** +** Return true if a transfer was made and false if not. +*/ +static int login_transfer_credentials( + const char *zLogin, /* Login we are looking for */ + const char *zCode, /* Project code of peer repository */ + const char *zHash /* HASH from login cookie HASH/CODE/LOGIN */ +){ + sqlite3 *pOther = 0; /* The other repository */ + sqlite3_stmt *pStmt; /* Query against the other repository */ + char *zSQL; /* SQL of the query against other repo */ + char *zOtherRepo; /* Filename of the other repository */ + int rc; /* Result code from SQLite library functions */ + int nXfer = 0; /* Number of credentials transferred */ + + zOtherRepo = db_text(0, + "SELECT value FROM config WHERE name='peer-repo-%q'", + zCode + ); + if( zOtherRepo==0 ) return 0; /* No such peer repository */ + + rc = sqlite3_open_v2( + zOtherRepo, &pOther, + SQLITE_OPEN_READWRITE | SQLITE_OPEN_CREATE, + g.zVfsName + ); + if( rc==SQLITE_OK ){ + sqlite3_create_function(pOther,"now",0,SQLITE_UTF8,0,db_now_function,0,0); + sqlite3_create_function(pOther, "constant_time_cmp", 2, SQLITE_UTF8, 0, + constant_time_cmp_function, 0, 0); + sqlite3_busy_timeout(pOther, 5000); + zSQL = mprintf( + "SELECT cexpire FROM user" + " WHERE login=%Q" + " AND length(cap)>0" + " AND length(pw)>0" + " AND cexpire>julianday('now')" + " AND constant_time_cmp(cookie,%Q)=0", + zLogin, zHash + ); + pStmt = 0; + rc = sqlite3_prepare_v2(pOther, zSQL, -1, &pStmt, 0); + if( rc==SQLITE_OK && sqlite3_step(pStmt)==SQLITE_ROW ){ + db_unprotect(PROTECT_USER); + db_multi_exec( + "UPDATE user SET cookie=%Q, cexpire=%.17g" + " WHERE login=%Q", + zHash, + sqlite3_column_double(pStmt, 0), zLogin + ); + db_protect_pop(); + nXfer++; + } + sqlite3_finalize(pStmt); + } + sqlite3_close(pOther); + fossil_free(zOtherRepo); + return nXfer; +} + +/* +** Return TRUE if zLogin is one of the special usernames +*/ +int login_is_special(const char *zLogin){ + if( fossil_strcmp(zLogin, "anonymous")==0 ) return 1; + if( fossil_strcmp(zLogin, "nobody")==0 ) return 1; + if( fossil_strcmp(zLogin, "developer")==0 ) return 1; + if( fossil_strcmp(zLogin, "reader")==0 ) return 1; + return 0; +} + +/* +** Lookup the uid for a non-built-in user with zLogin and zCookie. +** Return 0 if not found. +** +** Note that this only searches for logged-in entries with matching +** zCookie (db: user.cookie) entries. +*/ +static int login_find_user( + const char *zLogin, /* User name */ + const char *zCookie /* Login cookie value */ +){ + int uid; + if( login_is_special(zLogin) ) return 0; + uid = db_int(0, + "SELECT uid FROM user" + " WHERE login=%Q" + " AND cexpire>julianday('now')" + " AND length(cap)>0" + " AND length(pw)>0" + " AND constant_time_cmp(cookie,%Q)=0", + zLogin, zCookie + ); + return uid; +} + +/* +** Attempt to use Basic Authentication to establish the user. Return the +** (non-zero) uid if successful. Return 0 if it does not work. +*/ +static int login_basic_authentication(const char *zIpAddr){ + const char *zAuth = PD("HTTP_AUTHORIZATION", 0); + int i; + int uid = 0; + int nDecode = 0; + char *zDecode = 0; + const char *zUsername = 0; + const char *zPasswd = 0; + + if( zAuth==0 ) return 0; /* Fail: No Authentication: header */ + while( fossil_isspace(zAuth[0]) ) zAuth++; /* Skip leading whitespace */ + if( strncmp(zAuth, "Basic ", 6)!=0 ){ + return 0; /* Fail: Not Basic Authentication */ + } + + /* Parse out the username and password, separated by a ":" */ + zAuth += 6; + while( fossil_isspace(zAuth[0]) ) zAuth++; + zDecode = decode64(zAuth, &nDecode); + + for(i=0; zDecode[i] && zDecode[i]!=':'; i++){} + if( zDecode[i] ){ + zDecode[i] = 0; + zUsername = zDecode; + zPasswd = &zDecode[i+1]; + + /* Attempting to log in as the user provided by HTTP + ** basic auth + */ + uid = login_search_uid(&zUsername, zPasswd); + if( uid>0 ){ + record_login_attempt(zUsername, zIpAddr, 1); + }else{ + record_login_attempt(zUsername, zIpAddr, 0); + + /* The user attempted to login specifically with HTTP basic + ** auth, but provided invalid credentials. Inform them of + ** the failed login attempt via 401. + */ + cgi_set_status(401, "Unauthorized"); + cgi_reply(); + fossil_exit(0); + } + } + fossil_free(zDecode); + return uid; +} + +/* +** This routine examines the login cookie to see if it exists and +** is valid. If the login cookie checks out, it then sets global +** variables appropriately. +** +** g.userUid Database USER.UID value. Might be -1 for "nobody" +** g.zLogin Database USER.LOGIN value. NULL for user "nobody" +** g.perm Permissions granted to this user +** g.anon Permissions that would be available to anonymous +** g.isHuman True if the user is human, not a spider or robot ** */ void login_check_credentials(void){ int uid = 0; /* User id */ const char *zCookie; /* Text of the login cookie */ - const char *zRemoteAddr; /* IP address of the requestor */ + const char *zIpAddr; /* Raw IP address of the requestor */ const char *zCap = 0; /* Capability string */ + const char *zPublicPages = 0; /* GLOB patterns of public pages */ + const char *zLogin = 0; /* Login user for credentials */ /* Only run this check once. */ if( g.userUid!=0 ) return; + sqlite3_create_function(g.db, "constant_time_cmp", 2, SQLITE_UTF8, 0, + constant_time_cmp_function, 0, 0); /* If the HTTP connection is coming over 127.0.0.1 and if - ** local login is disabled and if we are using HTTP and not HTTPS, + ** local login is disabled and if we are using HTTP and not HTTPS, ** then there is no need to check user credentials. ** + ** This feature allows the "fossil ui" command to give the user + ** full access rights without having to log in. */ - zRemoteAddr = PD("REMOTE_ADDR","nil"); - if( strcmp(zRemoteAddr, "127.0.0.1")==0 + zIpAddr = PD("REMOTE_ADDR","nil"); + if( ( cgi_is_loopback(zIpAddr) + || (g.fSshClient & CGI_SSH_CLIENT)!=0 ) + && g.useLocalauth && db_get_int("localauth",0)==0 && P("HTTPS")==0 ){ - uid = db_int(0, "SELECT uid FROM user WHERE cap LIKE '%%s%%'"); + if( g.localOpen ) zLogin = db_lget("default-user",0); + if( zLogin!=0 ){ + uid = db_int(0, "SELECT uid FROM user WHERE login=%Q", zLogin); + }else{ + uid = db_int(0, "SELECT uid FROM user WHERE cap LIKE '%%s%%'"); + } g.zLogin = db_text("?", "SELECT login FROM user WHERE uid=%d", uid); - zCap = "s"; + zCap = "sx"; g.noPswd = 1; - strcpy(g.zCsrfToken, "localhost"); + g.isHuman = 1; + sqlite3_snprintf(sizeof(g.zCsrfToken), g.zCsrfToken, "localhost"); } /* Check the login cookie to see if it matches a known valid user. */ if( uid==0 && (zCookie = P(login_cookie_name()))!=0 ){ - if( isdigit(zCookie[0]) ){ - /* Cookies of the form "uid/randomness". There must be a - ** corresponding entry in the user table. */ - uid = db_int(0, - "SELECT uid FROM user" - " WHERE uid=%d" - " AND cookie=%Q" - " AND ipaddr=%Q" - " AND cexpire>julianday('now')", - atoi(zCookie), zCookie, zRemoteAddr - ); - }else if( memcmp(zCookie,"anon/",5)==0 ){ - /* Cookies of the form "anon/TIME/HASH". The TIME must not be - ** too old and the sha1 hash of TIME+IPADDR+SECRET must match HASH. + /* Parse the cookie value up into HASH/ARG/USER */ + char *zHash = fossil_strdup(zCookie); + char *zArg = 0; + char *zUser = 0; + int i, c; + for(i=0; (c = zHash[i])!=0; i++){ + if( c=='/' ){ + zHash[i++] = 0; + if( zArg==0 ){ + zArg = &zHash[i]; + }else{ + zUser = &zHash[i]; + break; + } + } + } + if( zUser==0 ){ + /* Invalid cookie */ + }else if( fossil_strcmp(zUser, "anonymous")==0 ){ + /* Cookies of the form "HASH/TIME/anonymous". The TIME must not be + ** too old and the sha1 hash of TIME/IPADDR/SECRET must match HASH. ** SECRET is the "captcha-secret" value in the repository. */ - double rTime; - int i; + double rTime = atof(zArg); Blob b; - rTime = atof(&zCookie[5]); - for(i=5; zCookie[i] && zCookie[i]!='/'; i++){} - blob_init(&b, &zCookie[5], i-5); - if( zCookie[i]=='/' ){ i++; } - blob_append(&b, "/", 1); - blob_appendf(&b, "%z/%s", ipPrefix(zRemoteAddr), - db_get("captcha-secret","")); + blob_zero(&b); + blob_appendf(&b, "%s/%s", zArg, db_get("captcha-secret","")); sha1sum_blob(&b, &b); - uid = db_int(0, - "SELECT uid FROM user WHERE login='anonymous'" - " AND length(cap)>0" - " AND length(pw)>0" - " AND %f+0.25>julianday('now')" - " AND %Q=%Q", - rTime, &zCookie[i], blob_buffer(&b) - ); + if( fossil_strcmp(zHash, blob_str(&b))==0 ){ + uid = db_int(0, + "SELECT uid FROM user WHERE login='anonymous'" + " AND length(cap)>0" + " AND length(pw)>0" + " AND %.17g+0.25>julianday('now')", + rTime + ); + } blob_reset(&b); + }else{ + /* Cookies of the form "HASH/CODE/USER". Search first in the + ** local user table, then the user table for project CODE if we + ** are part of a login-group. + */ + uid = login_find_user(zUser, zHash); + if( uid==0 && login_transfer_credentials(zUser,zArg,zHash) ){ + uid = login_find_user(zUser, zHash); + if( uid ) record_login_attempt(zUser, zIpAddr, 1); + } } - sqlite3_snprintf(sizeof(g.zCsrfToken), g.zCsrfToken, "%.10s", zCookie); + sqlite3_snprintf(sizeof(g.zCsrfToken), g.zCsrfToken, "%.10s", zHash); } /* If no user found and the REMOTE_USER environment variable is set, - ** the accept the value of REMOTE_USER as the user. + ** then accept the value of REMOTE_USER as the user. */ if( uid==0 ){ const char *zRemoteUser = P("REMOTE_USER"); if( zRemoteUser && db_get_boolean("remote_user_ok",0) ){ uid = db_int(0, "SELECT uid FROM user WHERE login=%Q" " AND length(cap)>0 AND length(pw)>0", zRemoteUser); } } + + /* If the request didn't provide a login cookie or the login cookie didn't + ** match a known valid user, check the HTTP "Authorization" header and + ** see if those credentials are valid for a known user. + */ + if( uid==0 && db_get_boolean("http_authentication_ok",0) ){ + uid = login_basic_authentication(zIpAddr); + } + + /* Check for magic query parameters "resid" (for the username) and + ** "token" for the password. Both values (if they exist) will be + ** obfuscated. + */ + if( uid==0 ){ + char *zUsr, *zPW; + if( (zUsr = unobscure(P("resid")))!=0 + && (zPW = unobscure(P("token")))!=0 + ){ + char *zSha1Pw = sha1_shared_secret(zPW, zUsr, 0); + uid = db_int(0, "SELECT uid FROM user" + " WHERE login=%Q" + " AND (constant_time_cmp(pw,%Q)=0" + " OR constant_time_cmp(pw,%Q)=0)", + zUsr, zSha1Pw, zPW); + fossil_free(zSha1Pw); + } + } /* If no user found yet, try to log in as "nobody" */ if( uid==0 ){ uid = db_int(0, "SELECT uid FROM user WHERE login='nobody'"); if( uid==0 ){ /* If there is no user "nobody", then make one up - with no privileges */ uid = -1; zCap = ""; } - strcpy(g.zCsrfToken, "none"); + sqlite3_snprintf(sizeof(g.zCsrfToken), g.zCsrfToken, "none"); } /* At this point, we know that uid!=0. Find the privileges associated ** with user uid. */ @@ -437,191 +1160,1048 @@ /* Set the global variables recording the userid and login. The ** "nobody" user is a special case in that g.zLogin==0. */ g.userUid = uid; - if( g.zLogin && strcmp(g.zLogin,"nobody")==0 ){ + if( fossil_strcmp(g.zLogin,"nobody")==0 ){ g.zLogin = 0; + } + if( PB("isrobot") ){ + g.isHuman = 0; + }else if( g.zLogin==0 ){ + g.isHuman = isHuman(P("HTTP_USER_AGENT")); + }else{ + g.isHuman = 1; } /* Set the capabilities */ - login_set_capabilities(zCap); + login_replace_capabilities(zCap, 0); + + /* The auto-hyperlink setting allows hyperlinks to be displayed for users + ** who do not have the "h" permission as long as their UserAgent string + ** makes it appear that they are human. Check to see if auto-hyperlink is + ** enabled for this repository and make appropriate adjustments to the + ** permission flags if it is. This should be done before the permissions + ** are (potentially) copied to the anonymous permission set; otherwise, + ** those will be out-of-sync. + */ + if( zCap[0] + && !g.perm.Hyperlink + && g.isHuman + && db_get_boolean("auto-hyperlink",1) + ){ + g.perm.Hyperlink = 1; + g.javascriptHyperlink = 1; + } + + /* + ** At this point, the capabilities for the logged in user are not going + ** to be modified anymore; therefore, we can copy them over to the ones + ** for the anonymous user. + ** + ** WARNING: In the future, please do not add code after this point that + ** modifies the capabilities for the logged in user. + */ login_set_anon_nobody_capabilities(); + + /* If the public-pages glob pattern is defined and REQUEST_URI matches + ** one of the globs in public-pages, then also add in all default-perms + ** permissions. + */ + zPublicPages = db_get("public-pages",0); + if( zPublicPages!=0 ){ + Glob *pGlob = glob_create(zPublicPages); + const char *zUri = PD("REQUEST_URI",""); + zUri += (int)strlen(g.zTop); + if( glob_match(pGlob, zUri) ){ + login_set_capabilities(db_get("default-perms", "u"), 0); + } + glob_free(pGlob); + } } /* -** Add the default privileges of users "nobody" and "anonymous" as appropriate -** for the user g.zLogin. +** Memory of settings +*/ +static int login_anon_once = 1; + +/* +** Add to g.perm the default privileges of users "nobody" and/or "anonymous" +** as appropriate for the user g.zLogin. +** +** This routine also sets up g.anon to be either a copy of g.perm for +** all logged in uses, or the privileges that would be available to "anonymous" +** if g.zLogin==0 (meaning that the user is "nobody"). */ void login_set_anon_nobody_capabilities(void){ - static int once = 1; - if( g.zLogin && once ){ + if( login_anon_once ){ const char *zCap; - /* All logged-in users inherit privileges from "nobody" */ + /* All users get privileges from "nobody" */ zCap = db_text("", "SELECT cap FROM user WHERE login = 'nobody'"); - login_set_capabilities(zCap); - if( strcmp(g.zLogin, "anonymous")!=0 ){ + login_set_capabilities(zCap, 0); + zCap = db_text("", "SELECT cap FROM user WHERE login = 'anonymous'"); + if( g.zLogin && fossil_strcmp(g.zLogin, "nobody")!=0 ){ /* All logged-in users inherit privileges from "anonymous" */ - zCap = db_text("", "SELECT cap FROM user WHERE login = 'anonymous'"); - login_set_capabilities(zCap); - } - once = 0; - } -} - -/* -** Set the global capability flags based on a capability string. -*/ -void login_set_capabilities(const char *zCap){ - static char *zDev = 0; - static char *zUser = 0; + login_set_capabilities(zCap, 0); + g.anon = g.perm; + }else{ + /* Record the privileges of anonymous in g.anon */ + g.anon = g.perm; + login_set_capabilities(zCap, LOGIN_ANON); + } + login_anon_once = 0; + } +} + +/* +** Flags passed into the 2nd argument of login_set/replace_capabilities(). +*/ +#if INTERFACE +#define LOGIN_IGNORE_UV 0x01 /* Ignore "u" and "v" */ +#define LOGIN_ANON 0x02 /* Use g.anon instead of g.perm */ +#endif + +/* +** Adds all capability flags in zCap to g.perm or g.anon. +*/ +void login_set_capabilities(const char *zCap, unsigned flags){ int i; + FossilUserPerms *p = (flags & LOGIN_ANON) ? &g.anon : &g.perm; + if(NULL==zCap){ + return; + } for(i=0; zCap[i]; i++){ switch( zCap[i] ){ - case 's': g.okSetup = 1; /* Fall thru into Admin */ - case 'a': g.okAdmin = g.okRdTkt = g.okWrTkt = g.okZip = - g.okRdWiki = g.okWrWiki = g.okNewWiki = - g.okApndWiki = g.okHistory = g.okClone = - g.okNewTkt = g.okPassword = g.okRdAddr = - g.okTktFmt = g.okAttach = g.okApndTkt = 1; - /* Fall thru into Read/Write */ - case 'i': g.okRead = g.okWrite = 1; break; - case 'o': g.okRead = 1; break; - case 'z': g.okZip = 1; break; - - case 'd': g.okDelete = 1; break; - case 'h': g.okHistory = 1; break; - case 'g': g.okClone = 1; break; - case 'p': g.okPassword = 1; break; - - case 'j': g.okRdWiki = 1; break; - case 'k': g.okWrWiki = g.okRdWiki = g.okApndWiki =1; break; - case 'm': g.okApndWiki = 1; break; - case 'f': g.okNewWiki = 1; break; - - case 'e': g.okRdAddr = 1; break; - case 'r': g.okRdTkt = 1; break; - case 'n': g.okNewTkt = 1; break; - case 'w': g.okWrTkt = g.okRdTkt = g.okNewTkt = - g.okApndTkt = 1; break; - case 'c': g.okApndTkt = 1; break; - case 't': g.okTktFmt = 1; break; - case 'b': g.okAttach = 1; break; - - /* The "u" privileges is a little different. It recursively + case 's': p->Setup = 1; /* Fall thru into Admin */ + case 'a': p->Admin = p->RdTkt = p->WrTkt = p->Zip = + p->RdWiki = p->WrWiki = p->NewWiki = + p->ApndWiki = p->Hyperlink = p->Clone = + p->NewTkt = p->Password = p->RdAddr = + p->TktFmt = p->Attach = p->ApndTkt = + p->ModWiki = p->ModTkt = + p->RdForum = p->WrForum = p->ModForum = + p->WrTForum = p->AdminForum = p->Chat = + p->EmailAlert = p->Announce = p->Debug = 1; + /* Fall thru into Read/Write */ + case 'i': p->Read = p->Write = 1; break; + case 'o': p->Read = 1; break; + case 'z': p->Zip = 1; break; + + case 'h': p->Hyperlink = 1; break; + case 'g': p->Clone = 1; break; + case 'p': p->Password = 1; break; + + case 'j': p->RdWiki = 1; break; + case 'k': p->WrWiki = p->RdWiki = p->ApndWiki =1; break; + case 'm': p->ApndWiki = 1; break; + case 'f': p->NewWiki = 1; break; + case 'l': p->ModWiki = 1; break; + + case 'e': p->RdAddr = 1; break; + case 'r': p->RdTkt = 1; break; + case 'n': p->NewTkt = 1; break; + case 'w': p->WrTkt = p->RdTkt = p->NewTkt = + p->ApndTkt = 1; break; + case 'c': p->ApndTkt = 1; break; + case 'q': p->ModTkt = 1; break; + case 't': p->TktFmt = 1; break; + case 'b': p->Attach = 1; break; + case 'x': p->Private = 1; break; + case 'y': p->WrUnver = 1; break; + + case '6': p->AdminForum = 1; + case '5': p->ModForum = 1; + case '4': p->WrTForum = 1; + case '3': p->WrForum = 1; + case '2': p->RdForum = 1; break; + + case '7': p->EmailAlert = 1; break; + case 'A': p->Announce = 1; break; + case 'C': p->Chat = 1; break; + case 'D': p->Debug = 1; break; + + /* The "u" privilege recursively ** inherits all privileges of the user named "reader" */ case 'u': { - if( zUser==0 ){ + if( p->XReader==0 ){ + const char *zUser; + p->XReader = 1; zUser = db_text("", "SELECT cap FROM user WHERE login='reader'"); - login_set_capabilities(zUser); + login_set_capabilities(zUser, flags); } break; } - /* The "v" privileges is a little different. It recursively + /* The "v" privilege recursively ** inherits all privileges of the user named "developer" */ case 'v': { - if( zDev==0 ){ + if( p->XDeveloper==0 ){ + const char *zDev; + p->XDeveloper = 1; zDev = db_text("", "SELECT cap FROM user WHERE login='developer'"); - login_set_capabilities(zDev); + login_set_capabilities(zDev, flags); } break; } } } } + +/* +** Zeroes out g.perm and calls login_set_capabilities(zCap,flags). +*/ +void login_replace_capabilities(const char *zCap, unsigned flags){ + memset(&g.perm, 0, sizeof(g.perm)); + login_set_capabilities(zCap, flags); + login_anon_once = 1; +} /* ** If the current login lacks any of the capabilities listed in ** the input, then return 0. If all capabilities are present, then ** return 1. +** +** As a special case, the 'L' pseudo-capability ID means "is logged +** in" and will return true for any non-guest user. */ -int login_has_capability(const char *zCap, int nCap){ +int login_has_capability(const char *zCap, int nCap, u32 flgs){ int i; int rc = 1; + FossilUserPerms *p = (flgs & LOGIN_ANON) ? &g.anon : &g.perm; if( nCap<0 ) nCap = strlen(zCap); for(i=0; iAdmin; break; + case 'b': rc = p->Attach; break; + case 'c': rc = p->ApndTkt; break; + /* d unused: see comment in capabilities.c */ + case 'e': rc = p->RdAddr; break; + case 'f': rc = p->NewWiki; break; + case 'g': rc = p->Clone; break; + case 'h': rc = p->Hyperlink; break; + case 'i': rc = p->Write; break; + case 'j': rc = p->RdWiki; break; + case 'k': rc = p->WrWiki; break; + case 'l': rc = p->ModWiki; break; + case 'm': rc = p->ApndWiki; break; + case 'n': rc = p->NewTkt; break; + case 'o': rc = p->Read; break; + case 'p': rc = p->Password; break; + case 'q': rc = p->ModTkt; break; + case 'r': rc = p->RdTkt; break; + case 's': rc = p->Setup; break; + case 't': rc = p->TktFmt; break; /* case 'u': READER */ /* case 'v': DEVELOPER */ - case 'w': rc = g.okWrTkt; break; - /* case 'x': */ - /* case 'y': */ - case 'z': rc = g.okZip; break; - default: rc = 0; break; + case 'w': rc = p->WrTkt; break; + case 'x': rc = p->Private; break; + case 'y': rc = p->WrUnver; break; + case 'z': rc = p->Zip; break; + case '2': rc = p->RdForum; break; + case '3': rc = p->WrForum; break; + case '4': rc = p->WrTForum; break; + case '5': rc = p->ModForum; break; + case '6': rc = p->AdminForum;break; + case '7': rc = p->EmailAlert;break; + case 'A': rc = p->Announce; break; + case 'C': rc = p->Chat; break; + case 'D': rc = p->Debug; break; + case 'L': rc = g.zLogin && *g.zLogin; break; + /* Mainenance reminder: '@' should not be used because + it would semantically collide with the @ in the + capexpr TH1 command. */ + default: rc = 0; break; } } return rc; } + +/* +** Change the login to zUser. +*/ +void login_as_user(const char *zUser){ + char *zCap = ""; /* New capabilities */ + + /* Turn off all capabilities from prior logins */ + memset( &g.perm, 0, sizeof(g.perm) ); + + /* Set the global variables recording the userid and login. The + ** "nobody" user is a special case in that g.zLogin==0. + */ + g.userUid = db_int(0, "SELECT uid FROM user WHERE login=%Q", zUser); + if( g.userUid==0 ){ + zUser = 0; + g.userUid = db_int(0, "SELECT uid FROM user WHERE login='nobody'"); + } + if( g.userUid ){ + zCap = db_text("", "SELECT cap FROM user WHERE uid=%d", g.userUid); + } + if( fossil_strcmp(zUser,"nobody")==0 ) zUser = 0; + g.zLogin = fossil_strdup(zUser); + + /* Set the capabilities */ + login_set_capabilities(zCap, 0); + login_anon_once = 1; + login_set_anon_nobody_capabilities(); +} + +/* +** Return true if the user is "nobody" +*/ +int login_is_nobody(void){ + return g.zLogin==0 || g.zLogin[0]==0 || fossil_strcmp(g.zLogin,"nobody")==0; +} + +/* +** Return true if the user is a specific individual, not "nobody" or +** "anonymous". +*/ +int login_is_individual(void){ + return g.zLogin!=0 && g.zLogin[0]!=0 && fossil_strcmp(g.zLogin,"nobody")!=0 + && fossil_strcmp(g.zLogin,"anonymous")!=0; +} + +/* +** Return the login name. If no login name is specified, return "nobody". +*/ +const char *login_name(void){ + return (g.zLogin && g.zLogin[0]) ? g.zLogin : "nobody"; +} /* ** Call this routine when the credential check fails. It causes ** a redirect to the "login" page. */ -void login_needed(void){ - const char *zUrl = PD("REQUEST_URI", "index"); - cgi_redirect(mprintf("login?g=%T", zUrl)); - /* NOTREACHED */ - assert(0); +void login_needed(int anonOk){ +#ifdef FOSSIL_ENABLE_JSON + if(g.json.isJsonMode){ + json_err( FSL_JSON_E_DENIED, NULL, 1 ); + fossil_exit(0); + /* NOTREACHED */ + assert(0); + }else +#endif /* FOSSIL_ENABLE_JSON */ + { + const char *zUrl = PD("REQUEST_URI", "index"); + const char *zQS = P("QUERY_STRING"); + char *zUrlNoQS; + int i; + Blob redir; + blob_init(&redir, 0, 0); + for(i=0; zUrl[i] && zUrl[i]!='?'; i++){} + zUrlNoQS = fossil_strndup(zUrl, i); + if( fossil_wants_https(1) ){ + blob_appendf(&redir, "%s/login?g=%T", g.zHttpsURL, zUrlNoQS); + }else{ + blob_appendf(&redir, "%R/login?g=%T", zUrlNoQS); + } + if( zQS && zQS[0] ){ + blob_appendf(&redir, "%%3f%T", zQS); + } + if( anonOk ) blob_append(&redir, "&anon", 5); + cgi_redirect(blob_str(&redir)); + /* NOTREACHED */ + assert(0); + } } /* -** Call this routine if the user lacks okHistory permission. If -** the anonymous user has okHistory permission, then paint a mesage +** Call this routine if the user lacks g.perm.Hyperlink permission. If +** the anonymous user has Hyperlink permission, then paint a mesage ** to inform the user that much more information is available by ** logging in as anonymous. */ void login_anonymous_available(void){ - if( !g.okHistory && - db_exists("SELECT 1 FROM user" - " WHERE login='anonymous'" - " AND cap LIKE '%%h%%'") ){ + if( !g.perm.Hyperlink && g.anon.Hyperlink ){ const char *zUrl = PD("REQUEST_URI", "index"); - @

Many hyperlinks are disabled.
- @ Use anonymous login + @

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

} } /* ** While rendering a form, call this routine to add the Anti-CSRF token ** as a hidden element of the form. */ void login_insert_csrf_secret(void){ - @ + @ } /* ** Before using the results of a form, first call this routine to verify -** that ths Anti-CSRF token is present and is valid. If the Anti-CSRF token -** is missing or is incorrect, that indicates a cross-site scripting attach -** so emits an error message and abort. +** that this Anti-CSRF token is present and is valid. If the Anti-CSRF token +** is missing or is incorrect, that indicates a cross-site scripting attack. +** If the event of an attack is detected, an error message is generated and +** all further processing is aborted. */ void login_verify_csrf_secret(void){ - const char *zCsrf; /* The CSRF secret */ if( g.okCsrf ) return; - if( (zCsrf = P("csrf"))!=0 && strcmp(zCsrf, g.zCsrfToken)==0 ){ + if( fossil_strcmp(P("csrf"), g.zCsrfToken)==0 ){ g.okCsrf = 1; return; } fossil_fatal("Cross-site request forgery attempt"); +} + +/* +** Check to see if the candidate username zUserID is already used. +** Return 1 if it is already in use. Return 0 if the name is +** available for a self-registeration. +*/ +static int login_self_choosen_userid_already_exists(const char *zUserID){ + int rc = db_exists( + "SELECT 1 FROM user WHERE login=%Q " + "UNION ALL " + "SELECT 1 FROM event WHERE user=%Q OR euser=%Q", + zUserID, zUserID, zUserID + ); + return rc; +} + +/* +** Check an email address and confirm that it is valid for self-registration. +** The email address is known already to be well-formed. Return true +** if the email address is on the allowed list. +** +** The default behavior is that any valid email address is accepted. +** But if the "auth-sub-email" setting exists and is not empty, then +** it is a comma-separated list of GLOB patterns for email addresses +** that are authorized to self-register. +*/ +int authorized_subscription_email(const char *zEAddr){ + char *zGlob = db_get("auth-sub-email",0); + Glob *pGlob; + char *zAddr; + int rc; + + if( zGlob==0 || zGlob[0]==0 ) return 1; + zGlob = fossil_strtolwr(fossil_strdup(zGlob)); + pGlob = glob_create(zGlob); + fossil_free(zGlob); + + zAddr = fossil_strtolwr(fossil_strdup(zEAddr)); + rc = glob_match(pGlob, zAddr); + fossil_free(zAddr); + glob_free(pGlob); + return rc!=0; +} + +/* +** WEBPAGE: register +** +** Page to allow users to self-register. The "self-register" setting +** must be enabled for this page to operate. +*/ +void register_page(void){ + const char *zUserID, *zPasswd, *zConfirm, *zEAddr; + const char *zDName; + unsigned int uSeed; + const char *zDecoded; + int iErrLine = -1; + const char *zErr = 0; + int captchaIsCorrect = 0; /* True on a correct captcha */ + char *zCaptcha = ""; /* Value of the captcha text */ + char *zPerms; /* Permissions for the default user */ + int canDoAlerts = 0; /* True if receiving email alerts is possible */ + int doAlerts = 0; /* True if subscription is wanted too */ + if( !db_get_boolean("self-register", 0) ){ + style_header("Registration not possible"); + @

This project does not allow user self-registration. Please contact the + @ project administrator to obtain an account.

+ style_finish_page(); + return; + } + zPerms = db_get("default-perms", "u"); + + /* Prompt the user for email alerts if this repository is configured for + ** email alerts and if the default permissions include "7" */ + canDoAlerts = alert_tables_exist() && db_int(0, + "SELECT fullcap(%Q) GLOB '*7*'", zPerms + ); + doAlerts = canDoAlerts && atoi(PD("alerts","1"))!=0; + + zUserID = PDT("u",""); + zPasswd = PDT("p",""); + zConfirm = PDT("cp",""); + zEAddr = PDT("ea",""); + zDName = PDT("dn",""); + + /* Verify user imputs */ + if( P("new")==0 || !cgi_csrf_safe(1) ){ + /* This is not a valid form submission. Fall through into + ** the form display */ + }else if( (captchaIsCorrect = captcha_is_correct(1))==0 ){ + iErrLine = 6; + zErr = "Incorrect CAPTCHA"; + }else if( strlen(zUserID)<6 ){ + iErrLine = 1; + zErr = "User ID too short. Must be at least 6 characters."; + }else if( sqlite3_strglob("*[^-a-zA-Z0-9_.]*",zUserID)==0 ){ + iErrLine = 1; + zErr = "User ID may not contain spaces or special characters."; + }else if( zDName[0]==0 ){ + iErrLine = 2; + zErr = "Required"; + }else if( zEAddr[0]==0 ){ + iErrLine = 3; + zErr = "Required"; + }else if( email_address_is_valid(zEAddr,0)==0 ){ + iErrLine = 3; + zErr = "Not a valid email address"; + }else if( authorized_subscription_email(zEAddr)==0 ){ + iErrLine = 3; + zErr = "Not an authorized email address"; + }else if( strlen(zPasswd)<6 ){ + iErrLine = 4; + zErr = "Password must be at least 6 characters long"; + }else if( fossil_strcmp(zPasswd,zConfirm)!=0 ){ + iErrLine = 5; + zErr = "Passwords do not match"; + }else if( login_self_choosen_userid_already_exists(zUserID) ){ + iErrLine = 1; + zErr = "This User ID is already taken. Choose something different."; + }else if( + /* If the email is found anywhere in USER.INFO... */ + db_exists("SELECT 1 FROM user WHERE info LIKE '%%%q%%'", zEAddr) + || + /* Or if the email is a verify subscriber email with an associated + ** user... */ + (alert_tables_exist() && + db_exists( + "SELECT 1 FROM subscriber WHERE semail=%Q AND suname IS NOT NULL" + " AND sverified",zEAddr)) + ){ + iErrLine = 3; + zErr = "This email address is already claimed by another user"; + }else{ + /* If all of the tests above have passed, that means that the submitted + ** form contains valid data and we can proceed to create the new login */ + Blob sql; + int uid; + char *zPass = sha1_shared_secret(zPasswd, zUserID, 0); + const char *zStartPerms = zPerms; + if( db_get_boolean("selfreg-verify",0) ){ + /* If email verification is required for self-registration, initalize + ** the new user capabilities to just "7" (Sign up for email). The + ** full "default-perms" permissions will be added when they click + ** the verification link on the email they are sent. */ + zStartPerms = "7"; + } + blob_init(&sql, 0, 0); + blob_append_sql(&sql, + "INSERT INTO user(login,pw,cap,info,mtime)\n" + "VALUES(%Q,%Q,%Q," + "'%q <%q>\nself-register from ip %q on '||datetime('now'),now())", + zUserID, zPass, zStartPerms, zDName, zEAddr, g.zIpAddr); + fossil_free(zPass); + db_unprotect(PROTECT_USER); + db_multi_exec("%s", blob_sql_text(&sql)); + db_protect_pop(); + uid = db_int(0, "SELECT uid FROM user WHERE login=%Q", zUserID); + login_set_user_cookie(zUserID, uid, NULL, 0); + if( doAlerts ){ + /* Also make the new user a subscriber. */ + Blob hdr, body; + AlertSender *pSender; + const char *zCode; /* New subscriber code (in hex) */ + const char *zGoto = P("g"); + int nsub = 0; + char ssub[20]; + CapabilityString *pCap; + pCap = capability_add(0, zPerms); + capability_expand(pCap); + ssub[nsub++] = 'a'; + if( capability_has_any(pCap,"o") ) ssub[nsub++] = 'c'; + if( capability_has_any(pCap,"2") ) ssub[nsub++] = 'f'; + if( capability_has_any(pCap,"r") ) ssub[nsub++] = 't'; + if( capability_has_any(pCap,"j") ) ssub[nsub++] = 'w'; + ssub[nsub] = 0; + capability_free(pCap); + /* Also add the user to the subscriber table. */ + zCode = db_text(0, + "INSERT INTO subscriber(semail,suname," + " sverified,sdonotcall,sdigest,ssub,sctime,mtime,smip,lastContact)" + " VALUES(%Q,%Q,%d,0,%d,%Q,now(),now(),%Q,now()/86400)" + " ON CONFLICT(semail) DO UPDATE" + " SET suname=excluded.suname" + " RETURNING hex(subscriberCode);", + /* semail */ zEAddr, + /* suname */ zUserID, + /* sverified */ 0, + /* sdigest */ 0, + /* ssub */ ssub, + /* smip */ g.zIpAddr + ); + if( db_exists("SELECT 1 FROM subscriber WHERE semail=%Q" + " AND sverified", zEAddr) ){ + /* This the case where the user was formerly a verified subscriber + ** and here they have also registered as a user as well. It is + ** not necessary to repeat the verfication step */ + redirect_to_g(); + } + /* A verification email */ + pSender = alert_sender_new(0,0); + blob_init(&hdr,0,0); + blob_init(&body,0,0); + blob_appendf(&hdr, "To: <%s>\n", zEAddr); + blob_appendf(&hdr, "Subject: Subscription verification\n"); + alert_append_confirmation_message(&body, zCode); + alert_send(pSender, &hdr, &body, 0); + style_header("Email Verification"); + if( pSender->zErr ){ + @

Internal Error

+ @

The following internal error was encountered while trying + @ to send the confirmation email: + @

+        @ %h(pSender->zErr)
+        @ 
+ }else{ + @

An email has been sent to "%h(zEAddr)". That email contains a + @ hyperlink that you must click to activate your account.

+ } + alert_sender_free(pSender); + if( zGoto ){ + @

Continue + } + style_finish_page(); + return; + } + redirect_to_g(); + } + + /* Prepare the captcha. */ + if( captchaIsCorrect ){ + uSeed = strtoul(P("captchaseed"),0,10); + }else{ + uSeed = captcha_seed(); + } + zDecoded = captcha_decode(uSeed); + zCaptcha = captcha_render(zDecoded); + + style_header("Register"); + /* Print out the registration form. */ + form_begin(0, "%R/register"); + if( P("g") ){ + @ + } + @

+ @ + @ + @ + @ + @ + if( iErrLine==1 ){ + @ + } + @ + @ + @ + @ + if( iErrLine==2 ){ + @ + } + @ + @ + @ + @ + @ + if( iErrLine==3 ){ + @ + } + if( canDoAlerts ){ + int a = atoi(PD("alerts","1")); + @ + @ + @ + } + @ + @ + @ + }else{ + @ + } + @ + if( iErrLine==4 ){ + @ + } + @ + @ + @ + @ + if( iErrLine==5 ){ + @ + } + @ + @ + @ + @ + if( iErrLine==6 ){ + @ + } + @ + @ + @ + @

+  @ %h(zCaptcha)
+  @ 
+ @ Enter this 8-letter code in the "Captcha" box above. + @
+ @ + style_finish_page(); + + free(zCaptcha); +} + +/* +** Run SQL on the repository database for every repository in our +** login group. The SQL is run in a separate database connection. +** +** Any members of the login group whose repository database file +** cannot be found is silently removed from the group. +** +** Error messages accumulate and are returned in *pzErrorMsg. The +** memory used to hold these messages should be freed using +** fossil_free() if one desired to avoid a memory leak. The +** zPrefix and zSuffix strings surround each error message. +** +** Return the number of errors. +*/ +int login_group_sql( + const char *zSql, /* The SQL to run */ + const char *zPrefix, /* Prefix to each error message */ + const char *zSuffix, /* Suffix to each error message */ + char **pzErrorMsg /* Write error message here, if not NULL */ +){ + sqlite3 *pPeer; /* Connection to another database */ + int nErr = 0; /* Number of errors seen so far */ + int rc; /* Result code from subroutine calls */ + char *zErr; /* SQLite error text */ + char *zSelfCode; /* Project code for ourself */ + Blob err; /* Accumulate errors here */ + Stmt q; /* Query of all peer-* entries in CONFIG */ + + if( zPrefix==0 ) zPrefix = ""; + if( zSuffix==0 ) zSuffix = ""; + if( pzErrorMsg ) *pzErrorMsg = 0; + zSelfCode = abbreviated_project_code(db_get("project-code", "x")); + blob_zero(&err); + db_prepare(&q, + "SELECT name, value FROM config" + " WHERE name GLOB 'peer-repo-*'" + " AND name <> 'peer-repo-%q'" + " ORDER BY +value", + zSelfCode + ); + while( db_step(&q)==SQLITE_ROW ){ + const char *zRepoName = db_column_text(&q, 1); + if( file_size(zRepoName, ExtFILE)<0 ){ + /* Silently remove non-existent repositories from the login group. */ + const char *zLabel = db_column_text(&q, 0); + db_unprotect(PROTECT_CONFIG); + db_multi_exec( + "DELETE FROM config WHERE name GLOB 'peer-*-%q'", + &zLabel[10] + ); + db_protect_pop(); + continue; + } + rc = sqlite3_open_v2( + zRepoName, &pPeer, + SQLITE_OPEN_READWRITE, + g.zVfsName + ); + if( rc!=SQLITE_OK ){ + blob_appendf(&err, "%s%s: %s%s", zPrefix, zRepoName, + sqlite3_errmsg(pPeer), zSuffix); + nErr++; + sqlite3_close(pPeer); + continue; + } + sqlite3_create_function(pPeer, "shared_secret", 3, SQLITE_UTF8, + 0, sha1_shared_secret_sql_function, 0, 0); + sqlite3_create_function(pPeer, "now", 0,SQLITE_UTF8,0,db_now_function,0,0); + sqlite3_busy_timeout(pPeer, 5000); + zErr = 0; + rc = sqlite3_exec(pPeer, zSql, 0, 0, &zErr); + if( zErr ){ + blob_appendf(&err, "%s%s: %s%s", zPrefix, zRepoName, zErr, zSuffix); + sqlite3_free(zErr); + nErr++; + }else if( rc!=SQLITE_OK ){ + blob_appendf(&err, "%s%s: %s%s", zPrefix, zRepoName, + sqlite3_errmsg(pPeer), zSuffix); + nErr++; + } + sqlite3_close(pPeer); + } + db_finalize(&q); + if( pzErrorMsg && blob_size(&err)>0 ){ + *pzErrorMsg = fossil_strdup(blob_str(&err)); + } + blob_reset(&err); + fossil_free(zSelfCode); + return nErr; +} + +/* +** Attempt to join a login-group. +** +** If problems arise, leave an error message in *pzErrMsg. +*/ +void login_group_join( + const char *zRepo, /* Repository file in the login group */ + int bPwRequired, /* True if the login,password is required */ + const char *zLogin, /* Login name for the other repo */ + const char *zPassword, /* Password to prove we are authorized to join */ + const char *zNewName, /* Name of new login group if making a new one */ + char **pzErrMsg /* Leave an error message here */ +){ + Blob fullName; /* Blob for finding full pathnames */ + sqlite3 *pOther; /* The other repository */ + int rc; /* Return code from sqlite3 functions */ + char *zOtherProjCode; /* Project code for pOther */ + char *zSelfRepo; /* Name of our repository */ + char *zSelfLabel; /* Project-name for our repository */ + char *zSelfProjCode; /* Our project-code */ + char *zSql; /* SQL to run on all peers */ + const char *zSelf; /* The ATTACH name of our repository */ + + *pzErrMsg = 0; /* Default to no errors */ + zSelf = "repository"; + + /* Get the full pathname of the other repository */ + file_canonical_name(zRepo, &fullName, 0); + zRepo = fossil_strdup(blob_str(&fullName)); + blob_reset(&fullName); + + /* Get the full pathname for our repository. Also the project code + ** and project name for ourself. */ + file_canonical_name(g.zRepositoryName, &fullName, 0); + zSelfRepo = fossil_strdup(blob_str(&fullName)); + blob_reset(&fullName); + zSelfProjCode = db_get("project-code", "unknown"); + zSelfLabel = db_get("project-name", 0); + if( zSelfLabel==0 ){ + zSelfLabel = zSelfProjCode; + } + + /* Make sure we are not trying to join ourselves */ + if( fossil_strcmp(zRepo, zSelfRepo)==0 ){ + *pzErrMsg = mprintf("The \"other\" repository is the same as this one."); + return; + } + + /* Make sure the other repository is a valid Fossil database */ + if( file_size(zRepo, ExtFILE)<0 ){ + *pzErrMsg = mprintf("repository file \"%s\" does not exist", zRepo); + return; + } + rc = sqlite3_open_v2( + zRepo, &pOther, + SQLITE_OPEN_READWRITE | SQLITE_OPEN_CREATE, + g.zVfsName + ); + if( rc!=SQLITE_OK ){ + *pzErrMsg = fossil_strdup(sqlite3_errmsg(pOther)); + }else{ + rc = sqlite3_exec(pOther, "SELECT count(*) FROM user", 0, 0, pzErrMsg); + } + sqlite3_close(pOther); + if( rc ) return; + + /* Attach the other repository. Make sure the username/password is + ** valid and has Setup permission. + */ + db_attach(zRepo, "other"); + zOtherProjCode = db_text("x", "SELECT value FROM other.config" + " WHERE name='project-code'"); + if( bPwRequired ){ + char *zPwHash; /* Password hash on pOther */ + zPwHash = sha1_shared_secret(zPassword, zLogin, zOtherProjCode); + if( !db_exists( + "SELECT 1 FROM other.user" + " WHERE login=%Q AND cap GLOB '*s*'" + " AND (pw=%Q OR pw=%Q)", + zLogin, zPassword, zPwHash) + ){ + db_detach("other"); + *pzErrMsg = "The supplied username/password does not correspond to a" + " user Setup permission on the other repository."; + return; + } + } + + /* Create all the necessary CONFIG table entries on both the + ** other repository and on our own repository. + */ + zSelfProjCode = abbreviated_project_code(zSelfProjCode); + zOtherProjCode = abbreviated_project_code(zOtherProjCode); + db_begin_transaction(); + db_unprotect(PROTECT_CONFIG); + db_multi_exec( + "DELETE FROM \"%w\".config WHERE name GLOB 'peer-*';" + "INSERT INTO \"%w\".config(name,value) VALUES('peer-repo-%q',%Q);" + "INSERT INTO \"%w\".config(name,value) " + " SELECT 'peer-name-%q', value FROM other.config" + " WHERE name='project-name';", + zSelf, + zSelf, zOtherProjCode, zRepo, + zSelf, zOtherProjCode + ); + db_multi_exec( + "INSERT OR IGNORE INTO other.config(name,value)" + " VALUES('login-group-name',%Q);" + "INSERT OR IGNORE INTO other.config(name,value)" + " VALUES('login-group-code',lower(hex(randomblob(8))));", + zNewName + ); + db_multi_exec( + "REPLACE INTO \"%w\".config(name,value)" + " SELECT name, value FROM other.config" + " WHERE name GLOB 'peer-*' OR name GLOB 'login-group-*'", + zSelf + ); + db_protect_pop(); + db_end_transaction(0); + db_multi_exec("DETACH other"); + + /* Propagate the changes to all other members of the login-group */ + zSql = mprintf( + "BEGIN;" + "REPLACE INTO config(name,value,mtime) VALUES('peer-name-%q',%Q,now());" + "REPLACE INTO config(name,value,mtime) VALUES('peer-repo-%q',%Q,now());" + "COMMIT;", + zSelfProjCode, zSelfLabel, zSelfProjCode, zSelfRepo + ); + db_unprotect(PROTECT_CONFIG); + login_group_sql(zSql, "
  • ", "
  • ", pzErrMsg); + db_protect_pop(); + fossil_free(zSql); +} + +/* +** Leave the login group that we are currently part of. +*/ +void login_group_leave(char **pzErrMsg){ + char *zProjCode; + char *zSql; + + *pzErrMsg = 0; + zProjCode = abbreviated_project_code(db_get("project-code","x")); + zSql = mprintf( + "DELETE FROM config WHERE name GLOB 'peer-*-%q';" + "DELETE FROM config" + " WHERE name='login-group-name'" + " AND (SELECT count(*) FROM config WHERE name GLOB 'peer-*')==0;", + zProjCode + ); + fossil_free(zProjCode); + db_unprotect(PROTECT_CONFIG); + login_group_sql(zSql, "
  • ", "
  • ", pzErrMsg); + fossil_free(zSql); + db_multi_exec( + "DELETE FROM config " + " WHERE name GLOB 'peer-*'" + " OR name GLOB 'login-group-*';" + ); + db_protect_pop(); +} + +/* +** COMMAND: login-group* +** +** Usage: %fossil login-group +** or: %fossil login-group join REPO [-name NAME] +** or: %fossil login-group leave +** +** With no arguments, this command shows the login-group to which the +** repository belongs. +** +** The "join" command adds this repository to login group to which REPO +** belongs, or creates a new login group between itself and REPO if REPO +** does not already belong to a login-group. When creating a new login- +** group, the name of the new group is determined by the "--name" option. +** +** The "leave" command takes the repository out of whatever login group +** it is currently a part of. +** +** About Login Groups: +** +** A login-group is a set of repositories that share user credentials. +** If a user is logged into one member of the group, then that user can +** access any other group member as long as they have an entry in the +** USER table of that member. If a user changes their password using +** web interface, their password is also automatically changed in every +** other member of the login group. +*/ +void login_group_command(void){ + const char *zLGName; + const char *zCmd; + int nCmd; + Stmt q; + db_find_and_open_repository(0,0); + if( g.argc>2 ){ + zCmd = g.argv[2]; + nCmd = (int)strlen(zCmd); + if( strncmp(zCmd,"join",nCmd)==0 && nCmd>=1 ){ + const char *zNewName = find_option("name",0,1); + const char *zOther; + char *zErr = 0; + verify_all_options(); + if( g.argc!=4 ){ + fossil_fatal("unknown extra arguments to \"login-group join\""); + } + zOther = g.argv[3]; + login_group_leave(&zErr); + sqlite3_free(zErr); + zErr = 0; + login_group_join(zOther,0,0,0,zNewName,&zErr); + if( zErr ){ + fossil_fatal("%s", zErr); + } + }else if( strncmp(zCmd,"leave",nCmd)==0 && nCmd>=1 ){ + verify_all_options(); + if( g.argc!=3 ){ + fossil_fatal("unknown extra arguments to \"login-group leave\""); + } + zLGName = login_group_name(); + if( zLGName ){ + char *zErr = 0; + fossil_print("Leaving login-group \"%s\"\n", zLGName); + login_group_leave(&zErr); + if( zErr ) fossil_fatal("Oops: %s", zErr); + return; + } + }else{ + fossil_fatal("unknown command \"%s\" - should be \"join\" or \"leave\"", + zCmd); + } + } + /* Show the current login group information */ + zLGName = login_group_name(); + if( zLGName==0 ){ + fossil_print("Not currently a part of any login-group\n"); + return; + } + fossil_print("Now part of login-group \"%s\" with:\n", zLGName); + db_prepare(&q, "SELECT value FROM config WHERE name LIKE 'peer-name-%%'"); + while( db_step(&q)==SQLITE_ROW ){ + fossil_print(" %s\n", db_column_text(&q,0)); + } + db_finalize(&q); + } ADDED src/login.js Index: src/login.js ================================================================== --- /dev/null +++ src/login.js @@ -0,0 +1,6 @@ +/* Javascript code to handle button actions on the login page */ +var autofillButton = document.getElementById('autofillButton'); +autofillButton.onclick = function(){ + document.getElementById('u').value = 'anonymous'; + document.getElementById('p').value = autofillButton.getAttribute('data-af'); +}; ADDED src/lookslike.c Index: src/lookslike.c ================================================================== --- /dev/null +++ src/lookslike.c @@ -0,0 +1,464 @@ +/* +** Copyright (c) 2013 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 code used to try to guess if a particular file is +** text or binary, what types of line endings it uses, is it UTF8 or +** UTF16, etc. +*/ +#include "config.h" +#include "lookslike.h" +#include + + +#if INTERFACE + +/* +** This macro is designed to return non-zero if the specified blob contains +** data that MAY be binary in nature; otherwise, zero will be returned. +*/ +#define looks_like_binary(blob) \ + ((looks_like_utf8((blob), LOOK_BINARY) & LOOK_BINARY) != LOOK_NONE) + +/* +** Output flags for the looks_like_utf8() and looks_like_utf16() routines used +** to convey status information about the blob content. +*/ +#define LOOK_NONE ((int)0x00000000) /* Nothing special was found. */ +#define LOOK_NUL ((int)0x00000001) /* One or more NUL chars were found. */ +#define LOOK_CR ((int)0x00000002) /* One or more CR chars were found. */ +#define LOOK_LONE_CR ((int)0x00000004) /* An unpaired CR char was found. */ +#define LOOK_LF ((int)0x00000008) /* One or more LF chars were found. */ +#define LOOK_LONE_LF ((int)0x00000010) /* An unpaired LF char was found. */ +#define LOOK_CRLF ((int)0x00000020) /* One or more CR/LF pairs were found. */ +#define LOOK_LONG ((int)0x00000040) /* An over length line was found. */ +#define LOOK_ODD ((int)0x00000080) /* An odd number of bytes was found. */ +#define LOOK_SHORT ((int)0x00000100) /* Unable to perform full check. */ +#define LOOK_INVALID ((int)0x00000200) /* Invalid sequence was found. */ +#define LOOK_BINARY (LOOK_NUL | LOOK_LONG | LOOK_SHORT) /* May be binary. */ +#define LOOK_EOL (LOOK_LONE_CR | LOOK_LONE_LF | LOOK_CRLF) /* Line seps. */ +#endif /* INTERFACE */ + +/* definitions for various UTF-8 sequence lengths, encoded as start value + * and size of each valid range belonging to some lead byte*/ +#define US2A 0x80, 0x01 /* for lead byte 0xC0 */ +#define US2B 0x80, 0x40 /* for lead bytes 0xC2-0xDF */ +#define US3A 0xA0, 0x20 /* for lead byte 0xE0 */ +#define US3B 0x80, 0x40 /* for lead bytes 0xE1-0xEF */ +#define US4A 0x90, 0x30 /* for lead byte 0xF0 */ +#define US4B 0x80, 0x40 /* for lead bytes 0xF1-0xF3 */ +#define US4C 0x80, 0x10 /* for lead byte 0xF4 */ +#define US0A 0x00, 0x00 /* for any other lead byte */ + +/* a table used for quick lookup of the definition that goes with a + * particular lead byte */ +static const unsigned char lb_tab[] = { + US0A, US0A, US0A, US0A, US0A, US0A, US0A, US0A, + US0A, US0A, US0A, US0A, US0A, US0A, US0A, US0A, + US0A, US0A, US0A, US0A, US0A, US0A, US0A, US0A, + US0A, US0A, US0A, US0A, US0A, US0A, US0A, US0A, + US0A, US0A, US0A, US0A, US0A, US0A, US0A, US0A, + US0A, US0A, US0A, US0A, US0A, US0A, US0A, US0A, + US0A, US0A, US0A, US0A, US0A, US0A, US0A, US0A, + US0A, US0A, US0A, US0A, US0A, US0A, US0A, US0A, + US2A, US0A, US2B, US2B, US2B, US2B, US2B, US2B, + US2B, US2B, US2B, US2B, US2B, US2B, US2B, US2B, + US2B, US2B, US2B, US2B, US2B, US2B, US2B, US2B, + US2B, US2B, US2B, US2B, US2B, US2B, US2B, US2B, + US3A, US3B, US3B, US3B, US3B, US3B, US3B, US3B, + US3B, US3B, US3B, US3B, US3B, US3B, US3B, US3B, + US4A, US4B, US4B, US4B, US4C, US0A, US0A, US0A, + US0A, US0A, US0A, US0A, US0A, US0A, US0A, US0A +}; + +/* +** This function attempts to scan each logical line within the blob to +** determine the type of content it appears to contain. The return value +** is a combination of one or more of the LOOK_XXX flags (see above): +** +** !LOOK_BINARY -- The content appears to consist entirely of text; however, +** the encoding may not be UTF-8. +** +** LOOK_BINARY -- The content appears to be binary because it contains one +** or more embedded NUL characters or an extremely long line. +** Since this function does not understand UTF-16, it may +** falsely consider UTF-16 text to be binary. +** +** Additional flags (i.e. those other than the ones included in LOOK_BINARY) +** may be present in the result as well; however, they should not impact the +** determination of text versus binary content. +** +************************************ WARNING ********************************** +** +** This function does not validate that the blob content is properly formed +** UTF-8. It assumes that all code points are the same size. It does not +** validate any code points. It makes no attempt to detect if any [invalid] +** switches between UTF-8 and other encodings occur. +** +** The only code points that this function cares about are the NUL character, +** carriage-return, and line-feed. +** +** This function examines the contents of the blob until one of the flags +** specified in "stopFlags" is set. +** +************************************ WARNING ********************************** +*/ +int looks_like_utf8(const Blob *pContent, int stopFlags){ + const char *z = blob_buffer(pContent); + unsigned int n = blob_size(pContent); + int j, c, flags = LOOK_NONE; /* Assume UTF-8 text, prove otherwise */ + + if( n==0 ) return flags; /* Empty file -> text */ + c = *z; + if( c==0 ){ + flags |= LOOK_NUL; /* NUL character in a file -> binary */ + }else if( c=='\r' ){ + flags |= LOOK_CR; + if( n<=1 || z[1]!='\n' ){ + flags |= LOOK_LONE_CR; /* Not enough chars or next char not LF */ + } + } + j = (c!='\n'); + if( !j ) flags |= (LOOK_LF | LOOK_LONE_LF); /* Found LF as first char */ + while( !(flags&stopFlags) && --n>0 ){ + int c2 = c; + c = *++z; ++j; + if( c==0 ){ + flags |= LOOK_NUL; /* NUL character in a file -> binary */ + }else if( c=='\n' ){ + flags |= LOOK_LF; + if( c2=='\r' ){ + flags |= (LOOK_CR | LOOK_CRLF); /* Found LF preceded by CR */ + }else{ + flags |= LOOK_LONE_LF; + } + if( j>LENGTH_MASK ){ + flags |= LOOK_LONG; /* Very long line -> binary */ + } + j = 0; + }else if( c=='\r' ){ + flags |= LOOK_CR; + if( n<=1 || z[1]!='\n' ){ + flags |= LOOK_LONE_CR; /* Not enough chars or next char not LF */ + } + } + } + if( n ){ + flags |= LOOK_SHORT; /* The whole blob was not examined */ + } + if( j>LENGTH_MASK ){ + flags |= LOOK_LONG; /* Very long line -> binary */ + } + return flags; +} + +/* +** Checks for proper UTF-8. It uses the method described in: +** http://en.wikipedia.org/wiki/UTF-8#Invalid_byte_sequences +** except for the "overlong form" of \u0000 which is not considered +** invalid here: Some languages like Java and Tcl use it. This function +** also considers valid the derivatives CESU-8 & WTF-8 (as described in +** the same wikipedia article referenced previously). For UTF-8 characters +** > 0x7f, the variable 'c' not necessary means the real lead byte. +** It's number of higher 1-bits indicate the number of continuation +** bytes that are expected to be followed. E.g. when 'c' has a value +** in the range 0xc0..0xdf it means that after 'c' a single continuation +** byte is expected. A value 0xe0..0xef means that after 'c' two more +** continuation bytes are expected. +*/ + +int invalid_utf8( + const Blob *pContent +){ + const unsigned char *z = (unsigned char *) blob_buffer(pContent); + unsigned int n = blob_size(pContent); + unsigned char c; /* lead byte to be handled. */ + + if( n==0 ) return 0; /* Empty file -> OK */ + c = *z; + while( --n>0 ){ + if( c>=0x80 ){ + const unsigned char *def; /* pointer to range table*/ + + c <<= 1; /* multiply by 2 and get rid of highest bit */ + def = &lb_tab[c]; /* search fb's valid range in table */ + if( (unsigned int)(*++z-def[0])>=def[1] ){ + return LOOK_INVALID; /* Invalid UTF-8 */ + } + c = (c>=0xC0) ? (c|3) : ' '; /* determine next lead byte */ + } else { + c = *++z; + } + } + return (c>=0x80) ? LOOK_INVALID : 0; /* Final lead byte must be ASCII. */ +} + +/* +** Define the type needed to represent a Unicode (UTF-16) character. +*/ +#ifndef WCHAR_T +# ifdef _WIN32 +# define WCHAR_T wchar_t +# else +# define WCHAR_T unsigned short +# endif +#endif + +/* +** Maximum length of a line in a text file, in UTF-16 characters. (4096) +** The number of bytes represented by this value cannot exceed LENGTH_MASK +** bytes, because that is the line buffer size used by the diff engine. +*/ +#define UTF16_LENGTH_MASK_SZ (LENGTH_MASK_SZ-(sizeof(WCHAR_T)-sizeof(char))) +#define UTF16_LENGTH_MASK ((1<> 8) & 0xff)) +#define UTF16_SWAP_IF(expr,ch) ((expr) ? UTF16_SWAP((ch)) : (ch)) + +/* +** This function attempts to scan each logical line within the blob to +** determine the type of content it appears to contain. The return value +** is a combination of one or more of the LOOK_XXX flags (see above): +** +** !LOOK_BINARY -- The content appears to consist entirely of text; however, +** the encoding may not be UTF-16. +** +** LOOK_BINARY -- The content appears to be binary because it contains one +** or more embedded NUL characters or an extremely long line. +** Since this function does not understand UTF-8, it may +** falsely consider UTF-8 text to be binary. +** +** Additional flags (i.e. those other than the ones included in LOOK_BINARY) +** may be present in the result as well; however, they should not impact the +** determination of text versus binary content. +** +************************************ WARNING ********************************** +** +** This function does not validate that the blob content is properly formed +** UTF-16. It assumes that all code points are the same size. It does not +** validate any code points. It makes no attempt to detect if any [invalid] +** switches between the UTF-16be and UTF-16le encodings occur. +** +** The only code points that this function cares about are the NUL character, +** carriage-return, and line-feed. +** +** This function examines the contents of the blob until one of the flags +** specified in "stopFlags" is set. +** +************************************ WARNING ********************************** +*/ +int looks_like_utf16(const Blob *pContent, int bReverse, int stopFlags){ + const WCHAR_T *z = (WCHAR_T *)blob_buffer(pContent); + unsigned int n = blob_size(pContent); + int j, c, flags = LOOK_NONE; /* Assume UTF-16 text, prove otherwise */ + + if( n%sizeof(WCHAR_T) ){ + flags |= LOOK_ODD; /* Odd number of bytes -> binary (UTF-8?) */ + } + if( n binary (UTF-8?) */ + c = *z; + if( bReverse ){ + c = UTF16_SWAP(c); + } + if( c==0 ){ + flags |= LOOK_NUL; /* NUL character in a file -> binary */ + }else if( c=='\r' ){ + flags |= LOOK_CR; + if( n<(2*sizeof(WCHAR_T)) || UTF16_SWAP_IF(bReverse, z[1])!='\n' ){ + flags |= LOOK_LONE_CR; /* Not enough chars or next char not LF */ + } + } + j = (c!='\n'); + if( !j ) flags |= (LOOK_LF | LOOK_LONE_LF); /* Found LF as first char */ + while( !(flags&stopFlags) && ((n-=sizeof(WCHAR_T))>=sizeof(WCHAR_T)) ){ + int c2 = c; + c = *++z; + if( bReverse ){ + c = UTF16_SWAP(c); + } + ++j; + if( c==0 ){ + flags |= LOOK_NUL; /* NUL character in a file -> binary */ + }else if( c=='\n' ){ + flags |= LOOK_LF; + if( c2=='\r' ){ + flags |= (LOOK_CR | LOOK_CRLF); /* Found LF preceded by CR */ + }else{ + flags |= LOOK_LONE_LF; + } + if( j>UTF16_LENGTH_MASK ){ + flags |= LOOK_LONG; /* Very long line -> binary */ + } + j = 0; + }else if( c=='\r' ){ + flags |= LOOK_CR; + if( n<(2*sizeof(WCHAR_T)) || UTF16_SWAP_IF(bReverse, z[1])!='\n' ){ + flags |= LOOK_LONE_CR; /* Not enough chars or next char not LF */ + } + } + } + if( n ){ + flags |= LOOK_SHORT; /* The whole blob was not examined */ + } + if( j>UTF16_LENGTH_MASK ){ + flags |= LOOK_LONG; /* Very long line -> binary */ + } + return flags; +} + +/* +** This function returns an array of bytes representing the byte-order-mark +** for UTF-8. +*/ +const unsigned char *get_utf8_bom(int *pnByte){ + static const unsigned char bom[] = { + 0xef, 0xbb, 0xbf, 0x00, 0x00, 0x00 + }; + if( pnByte ) *pnByte = 3; + return bom; +} + +/* +** This function returns non-zero if the blob starts with a UTF-8 +** byte-order-mark (BOM). +*/ +int starts_with_utf8_bom(const Blob *pContent, int *pnByte){ + const char *z = blob_buffer(pContent); + int bomSize = 0; + const unsigned char *bom = get_utf8_bom(&bomSize); + + if( pnByte ) *pnByte = bomSize; + if( blob_size(pContent)=(2*bomSize) && z[2]==0 && z[3]==0 ) goto noBom; + memcpy(&i0, z, sizeof(i0)); + if( i0==0xfeff ){ + if( pbReverse ) *pbReverse = 0; + }else if( i0==0xfffe ){ + if( pbReverse ) *pbReverse = 1; + }else{ + static const int one = 1; + noBom: + if( pbReverse ) *pbReverse = *(char *) &one; + return 0; /* No: UTF-16 byte-order-mark not found. */ + } + if( pnByte ) *pnByte = bomSize; + return 1; /* Yes. */ +} + +/* +** Returns non-zero if the specified content could be valid UTF-16. +*/ +int could_be_utf16(const Blob *pContent, int *pbReverse){ + return (blob_size(pContent) % sizeof(WCHAR_T) == 0) ? + starts_with_utf16_bom(pContent, 0, pbReverse) : 0; +} + + +/* +** COMMAND: test-looks-like-utf +** +** Usage: %fossil test-looks-like-utf FILENAME +** +** Options: +** -n|--limit N Repeat looks-like function N times, for +** performance measurement. Default = 1 +** --utf8 Ignoring BOM and file size, force UTF-8 checking +** --utf16 Ignoring BOM and file size, force UTF-16 checking +** +** FILENAME is the name of a file to check for textual content in the UTF-8 +** and/or UTF-16 encodings. +*/ +void looks_like_utf_test_cmd(void){ + Blob blob; /* the contents of the specified file */ + int fUtf8 = 0; /* return value of starts_with_utf8_bom() */ + int fUtf16 = 0; /* return value of starts_with_utf16_bom() */ + int fUnicode = 0; /* return value of could_be_utf16() */ + int lookFlags = 0; /* output flags from looks_like_utf8/utf16() */ + int bRevUtf16 = 0; /* non-zero -> UTF-16 byte order reversed */ + int fForceUtf8 = find_option("utf8",0,0)!=0; + int fForceUtf16 = find_option("utf16",0,0)!=0; + const char *zCount = find_option("limit","n",1); + int nRepeat = 1; + + if( g.argc!=3 ) usage("FILENAME"); + if( zCount ){ + nRepeat = atoi(zCount); + } + blob_read_from_file(&blob, g.argv[2], ExtFILE); + while( --nRepeat >= 0 ){ + fUtf8 = starts_with_utf8_bom(&blob, 0); + fUtf16 = starts_with_utf16_bom(&blob, 0, &bRevUtf16); + if( fForceUtf8 ){ + fUnicode = 0; + }else{ + fUnicode = could_be_utf16(&blob, 0) || fForceUtf16; + } + if( fUnicode ){ + lookFlags = looks_like_utf16(&blob, bRevUtf16, 0); + }else{ + lookFlags = looks_like_utf8(&blob, 0) | invalid_utf8(&blob); + } + } + fossil_print("File \"%s\" has %d bytes.\n",g.argv[2],blob_size(&blob)); + fossil_print("Starts with UTF-8 BOM: %s\n",fUtf8?"yes":"no"); + fossil_print("Starts with UTF-16 BOM: %s\n", + fUtf16?(bRevUtf16?"reversed":"yes"):"no"); + fossil_print("Looks like UTF-%s: %s\n",fUnicode?"16":"8", + (lookFlags&LOOK_BINARY)?"no":"yes"); + fossil_print("Has flag LOOK_NUL: %s\n",(lookFlags&LOOK_NUL)?"yes":"no"); + fossil_print("Has flag LOOK_CR: %s\n",(lookFlags&LOOK_CR)?"yes":"no"); + fossil_print("Has flag LOOK_LONE_CR: %s\n", + (lookFlags&LOOK_LONE_CR)?"yes":"no"); + fossil_print("Has flag LOOK_LF: %s\n",(lookFlags&LOOK_LF)?"yes":"no"); + fossil_print("Has flag LOOK_LONE_LF: %s\n", + (lookFlags&LOOK_LONE_LF)?"yes":"no"); + fossil_print("Has flag LOOK_CRLF: %s\n",(lookFlags&LOOK_CRLF)?"yes":"no"); + fossil_print("Has flag LOOK_LONG: %s\n",(lookFlags&LOOK_LONG)?"yes":"no"); + fossil_print("Has flag LOOK_INVALID: %s\n", + (lookFlags&LOOK_INVALID)?"yes":"no"); + fossil_print("Has flag LOOK_ODD: %s\n",(lookFlags&LOOK_ODD)?"yes":"no"); + fossil_print("Has flag LOOK_SHORT: %s\n",(lookFlags&LOOK_SHORT)?"yes":"no"); + blob_reset(&blob); +} Index: src/main.c ================================================================== --- src/main.c +++ src/main.c @@ -2,11 +2,11 @@ ** Copyright (c) 2006 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: @@ -16,128 +16,246 @@ ******************************************************************************* ** ** This module codes the main() procedure that runs first when the ** program is invoked. */ +#include "VERSION.h" #include "config.h" +#if defined(_WIN32) +# include +# include +# define isatty(h) _isatty(h) +# define GETPID (int)GetCurrentProcessId +#endif #include "main.h" #include #include #include #include #include - - +#include /* atexit() */ +#if !defined(_WIN32) +# include /* errno global */ +# include +# include +# define GETPID getpid +#endif +#ifdef FOSSIL_ENABLE_SSL +# include "openssl/crypto.h" +#endif +#if defined(FOSSIL_ENABLE_MINIZ) +# define MINIZ_HEADER_FILE_ONLY +# include "miniz.c" +#else +# include +#endif #if INTERFACE - -/* -** Number of elements in an array -*/ -#define count(X) (sizeof(X)/sizeof(X[0])) +#ifdef FOSSIL_ENABLE_TCL +# include "tcl.h" +#endif +#ifdef FOSSIL_ENABLE_JSON +# include "cson_amalgamation.h" /* JSON API. */ +# include "json_detail.h" +#endif +#ifdef HAVE_BACKTRACE +# include +#endif /* -** Size of a UUID in characters +** Default length of a timeout for serving an HTTP request. Changable +** using the "--timeout N" command-line option or via "timeout: N" in the +** CGI script. */ -#define UUID_SIZE 40 +#ifndef FOSSIL_DEFAULT_TIMEOUT +# define FOSSIL_DEFAULT_TIMEOUT 600 /* 10 minutes */ +#endif /* ** Maximum number of auxiliary parameters on reports */ #define MX_AUX 5 /* -** All global variables are in this structure. +** Holds flags for fossil user permissions. +*/ +struct FossilUserPerms { + char Setup; /* s: use Setup screens on web interface */ + char Admin; /* a: administrative permission */ + char Password; /* p: change password */ + char Query; /* q: create new reports */ + char Write; /* i: xfer inbound. check-in */ + char Read; /* o: xfer outbound. check-out */ + char Hyperlink; /* h: enable the display of hyperlinks */ + char Clone; /* g: clone */ + char RdWiki; /* j: view wiki via web */ + char NewWiki; /* f: create new wiki via web */ + char ApndWiki; /* m: append to wiki via web */ + char WrWiki; /* k: edit wiki via web */ + char ModWiki; /* l: approve and publish wiki content (Moderator) */ + char RdTkt; /* r: view tickets via web */ + char NewTkt; /* n: create new tickets */ + char ApndTkt; /* c: append to tickets via the web */ + char WrTkt; /* w: make changes to tickets via web */ + char ModTkt; /* q: approve and publish ticket changes (Moderator) */ + char Attach; /* b: add attachments */ + char TktFmt; /* t: create new ticket report formats */ + char RdAddr; /* e: read email addresses or other private data */ + char Zip; /* z: download zipped artifact via /zip URL */ + char Private; /* x: can send and receive private content */ + char WrUnver; /* y: can push unversioned content */ + char RdForum; /* 2: Read forum posts */ + char WrForum; /* 3: Create new forum posts */ + char WrTForum; /* 4: Post to forums not subject to moderation */ + char ModForum; /* 5: Moderate (approve or reject) forum posts */ + char AdminForum; /* 6: Grant capability 4 to other users */ + char EmailAlert; /* 7: Sign up for email notifications */ + char Announce; /* A: Send announcements */ + char Chat; /* C: read or write the chatroom */ + char Debug; /* D: show extra Fossil debugging features */ + /* These last two are included to block infinite recursion */ + char XReader; /* u: Inherit all privileges of "reader" */ + char XDeveloper; /* v: Inherit all privileges of "developer" */ +}; + +#ifdef FOSSIL_ENABLE_TCL +/* +** All Tcl related context information is in this structure. This structure +** definition has been copied from and should be kept in sync with the one in +** "th_tcl.c". */ +struct TclContext { + int argc; /* Number of original (expanded) arguments. */ + char **argv; /* Full copy of the original (expanded) arguments. */ + void *hLibrary; /* The Tcl library module handle. */ + void *xFindExecutable; /* See tcl_FindExecutableProc in th_tcl.c. */ + void *xCreateInterp; /* See tcl_CreateInterpProc in th_tcl.c. */ + void *xDeleteInterp; /* See tcl_DeleteInterpProc in th_tcl.c. */ + void *xFinalize; /* See tcl_FinalizeProc in th_tcl.c. */ + Tcl_Interp *interp; /* The on-demand created Tcl interpreter. */ + int useObjProc; /* Non-zero if an objProc can be called directly. */ + int useTip285; /* Non-zero if TIP #285 is available. */ + char *setup; /* The optional Tcl setup script. */ + void *xPreEval; /* Optional, called before Tcl_Eval*(). */ + void *pPreContext; /* Optional, provided to xPreEval(). */ + void *xPostEval; /* Optional, called after Tcl_Eval*(). */ + void *pPostContext; /* Optional, provided to xPostEval(). */ +}; +#endif + struct Global { int argc; char **argv; /* Command-line arguments to the program */ - int isConst; /* True if the output is unchanging */ + char *nameOfExe; /* Full path of executable. */ + const char *zErrlog; /* Log errors to this file, if not NULL */ + int isConst; /* True if the output is unchanging & cacheable */ + const char *zVfsName; /* The VFS to use for database connections */ sqlite3 *db; /* The connection to the databases */ sqlite3 *dbConfig; /* Separate connection for global_config table */ - int useAttach; /* True if global_config is attached to repository */ - int configOpen; /* True if the config database is open */ - long long int now; /* Seconds since 1970 */ + char *zAuxSchema; /* Main repository aux-schema */ + int dbIgnoreErrors; /* Ignore database errors if true */ + char *zConfigDbName; /* Path of the config database. NULL if not open */ + sqlite3_int64 now; /* Seconds since 1970 */ int repositoryOpen; /* True if the main repository database is open */ - char *zRepositoryName; /* Name of the repository database */ - const char *zHome; /* Name of user home directory */ + unsigned iRepoDataVers; /* Initial data version for repository database */ + char *zRepositoryOption; /* Most recent cached repository option value */ + char *zRepositoryName; /* Name of the repository database file */ + char *zLocalDbName; /* Name of the local database file */ + char *zOpenRevision; /* Check-in version to use during database open */ + const char *zCmdName; /* Name of the Fossil command currently running */ int localOpen; /* True if the local database is open */ char *zLocalRoot; /* The directory holding the local database */ - int minPrefix; /* Number of digits needed for a distinct UUID */ - int fSqlTrace; /* True if -sqltrace flag is present */ - int fSqlPrint; /* True if -sqlprint flag is present */ + int minPrefix; /* Number of digits needed for a distinct hash */ + int eHashPolicy; /* Current hash policy. One of HPOLICY_* */ + int fSqlTrace; /* True if --sqltrace flag is present */ + int fSqlStats; /* True if --sqltrace or --sqlstats are present */ + int fSqlPrint; /* True if --sqlprint flag is present */ + int fCgiTrace; /* True if --cgitrace is enabled */ int fQuiet; /* True if -quiet flag is present */ + int fJail; /* True if running with a chroot jail */ int fHttpTrace; /* Trace outbound HTTP requests */ - int fNoSync; /* Do not do an autosync even. --nosync */ + int fAnyTrace; /* Any kind of tracing */ + char *zHttpAuth; /* HTTP Authorization user:pass information */ + int fSystemTrace; /* Trace calls to fossil_system(), --systemtrace */ + int fSshTrace; /* Trace the SSH setup traffic */ + int fSshClient; /* HTTP client flags for SSH client */ + int fNoHttpCompress; /* Do not compress HTTP traffic (for debugging) */ + char *zSshCmd; /* SSH command string */ + int fNoSync; /* Do not do an autosync ever. --nosync */ + int fIPv4; /* Use only IPv4, not IPv6. --ipv4 */ char *zPath; /* Name of webpage being served */ char *zExtra; /* Extra path information past the webpage name */ char *zBaseURL; /* Full text of the URL being served */ + char *zHttpsURL; /* zBaseURL translated to https: */ char *zTop; /* Parent directory of zPath */ + int nExtraURL; /* Extra bytes added to SCRIPT_NAME */ + const char *zExtRoot; /* Document root for the /ext sub-website */ const char *zContentType; /* The content type of the input HTTP request */ int iErrPriority; /* Priority of current error message */ char *zErrMsg; /* Text of an error message */ + int sslNotAvailable; /* SSL is not available. Do not redirect to https: */ Blob cgiIn; /* Input to an xfer www method */ - int cgiOutput; /* Write error and status messages to CGI */ + int cgiOutput; /* 0: command-line 1: CGI. 2: after CGI */ int xferPanic; /* Write error messages in XFER protocol */ int fullHttpReply; /* True for full HTTP reply. False for CGI reply */ Th_Interp *interp; /* The TH1 interpreter */ + char *th1Setup; /* The TH1 post-creation setup script, if any */ + int th1Flags; /* The TH1 integration state flags */ FILE *httpIn; /* Accept HTTP input from here */ FILE *httpOut; /* Send HTTP output here */ int xlinkClusterOnly; /* Set when cloning. Only process clusters */ int fTimeFormat; /* 1 for UTC. 2 for localtime. 0 not yet selected */ int *aCommitFile; /* Array of files to be committed */ int markPrivate; /* All new artifacts are private if true */ - - int urlIsFile; /* True if a "file:" url */ - int urlIsHttps; /* True if a "https:" url */ - char *urlName; /* Hostname for http: or filename for file: */ - char *urlHostname; /* The HOST: parameter on http headers */ - char *urlProtocol; /* "http" or "https" */ - int urlPort; /* TCP port number for http: or https: */ - int urlDfltPort; /* The default port for the given protocol */ - char *urlPath; /* Pathname for http: */ - char *urlUser; /* User id for http: */ - char *urlPasswd; /* Password for http: */ - char *urlCanonical; /* Canonical representation of the URL */ - char *urlProxyAuth; /* Proxy-Authorizer: string */ - int dontKeepUrl; /* Do not persist the URL */ - - const char *zLogin; /* Login name. "" if not logged in. */ + char *ckinLockFail; /* Check-in lock failure received from server */ + int clockSkewSeen; /* True if clocks on client and server out of sync */ + int wikiFlags; /* Wiki conversion flags applied to %W */ + char isHTTP; /* True if server/CGI modes, else assume CLI. */ + char javascriptHyperlink; /* If true, set href= using script, not HTML */ + Blob httpHeader; /* Complete text of the HTTP request header */ + UrlData url; /* Information about current URL */ + const char *zLogin; /* Login name. NULL or "" if not logged in. */ + const char *zCkoutAlias; /* doc/ uses this branch as an alias for "ckout" */ + const char *zMainMenuFile; /* --mainmenu FILE from server/ui/cgi */ + const char *zSSLIdentity; /* Value of --ssl-identity option, filename of + ** SSL client identity */ +#if defined(_WIN32) && USE_SEE + const char *zPidKey; /* Saved value of the --usepidkey option. Only + * applicable when using SEE on Windows. */ +#endif + int useLocalauth; /* No login required if from 127.0.0.1 */ int noPswd; /* Logged in without password (on 127.0.0.1) */ int userUid; /* Integer user id */ + int isHuman; /* True if access by a human, not a spider or bot */ + int comFmtFlags; /* Zero or more "COMMENT_PRINT_*" bit flags, should be + ** accessed through get_comment_format(). */ /* Information used to populate the RCVFROM table */ int rcvid; /* The rcvid. 0 if not yet defined. */ char *zIpAddr; /* The remote IP address */ char *zNonce; /* The nonce used for login */ - - /* permissions used by the server */ - int okSetup; /* s: use Setup screens on web interface */ - int okAdmin; /* a: administrative permission */ - int okDelete; /* d: delete wiki or tickets */ - int okPassword; /* p: change password */ - int okQuery; /* q: create new reports */ - int okWrite; /* i: xfer inbound. checkin */ - int okRead; /* o: xfer outbound. checkout */ - int okHistory; /* h: access historical information. */ - int okClone; /* g: clone */ - int okRdWiki; /* j: view wiki via web */ - int okNewWiki; /* f: create new wiki via web */ - int okApndWiki; /* m: append to wiki via web */ - int okWrWiki; /* k: edit wiki via web */ - int okRdTkt; /* r: view tickets via web */ - int okNewTkt; /* n: create new tickets */ - int okApndTkt; /* c: append to tickets via the web */ - int okWrTkt; /* w: make changes to tickets via web */ - int okAttach; /* b: add attachments */ - int okTktFmt; /* t: create new ticket report formats */ - int okRdAddr; /* e: read email addresses or other private data */ - int okZip; /* z: download zipped artifact via /zip URL */ + + /* permissions available to current user */ + struct FossilUserPerms perm; + + /* permissions available to current user or to "anonymous". + ** This is the logical union of perm permissions above with + ** the value that perm would take if g.zLogin were "anonymous". */ + struct FossilUserPerms anon; + +#ifdef FOSSIL_ENABLE_TCL + /* all Tcl related context necessary for integration */ + struct TclContext tcl; +#endif /* For defense against Cross-site Request Forgery attacks */ char zCsrfToken[12]; /* Value of the anti-CSRF token */ int okCsrf; /* Anti-CSRF token is present and valid */ + int parseCnt[10]; /* Counts of artifacts parsed */ FILE *fDebug; /* Write debug information here, if the file exists */ +#ifdef FOSSIL_ENABLE_TH1_HOOKS + int fNoThHook; /* Disable all TH1 command/webpage hooks */ +#endif int thTrace; /* True to enable TH1 debugging output */ Blob thLog; /* Text of the TH1 debugging output */ int isHome; /* True if rendering the "home" page */ @@ -146,10 +264,71 @@ const char *azAuxName[MX_AUX]; /* Name of each aux() or option() value */ char *azAuxParam[MX_AUX]; /* Param of each aux() or option() value */ const char *azAuxVal[MX_AUX]; /* Value of each aux() or option() value */ const char **azAuxOpt[MX_AUX]; /* Options of each option() value */ int anAuxCols[MX_AUX]; /* Number of columns for option() values */ + int allowSymlinks; /* Cached "allow-symlinks" option */ + int mainTimerId; /* Set to fossil_timer_start() */ + int nPendingRequest; /* # of HTTP requests in "fossil server" */ + int nRequest; /* Total # of HTTP request */ + int bAvoidDeltaManifests; /* Avoid using delta manifests if true */ +#ifdef FOSSIL_ENABLE_JSON + struct FossilJsonBits { + int isJsonMode; /* True if running in JSON mode, else + false. This changes how errors are + reported. In JSON mode we try to + always output JSON-form error + responses and always (in CGI mode) + exit() with code 0 to avoid an HTTP + 500 error. + */ + int preserveRc; /* Do not convert error codes into 0. + * This is primarily intended for use + * by the test suite. */ + int resultCode; /* used for passing back specific codes + ** from /json callbacks. */ + int errorDetailParanoia; /* 0=full error codes, 1=%10, 2=%100, 3=%1000 */ + cson_output_opt outOpt; /* formatting options for JSON mode. */ + cson_value *authToken; /* authentication token */ + const char *jsonp; /* Name of JSONP function wrapper. */ + unsigned char dispatchDepth /* Tells JSON command dispatching + which argument we are currently + working on. For this purpose, arg#0 + is the "json" path/CLI arg. + */; + struct { /* "garbage collector" */ + cson_value *v; + cson_array *a; + } gc; + struct { /* JSON POST data. */ + cson_value *v; + cson_array *a; + int offset; /* Tells us which PATH_INFO/CLI args + part holds the "json" command, so + that we can account for sub-repos + and path prefixes. This is handled + differently for CLI and CGI modes. + */ + const char *commandStr /*"command" request param.*/; + } cmd; + struct { /* JSON POST data. */ + cson_value *v; + cson_object *o; + } post; + struct { /* GET/COOKIE params in JSON mode. */ + cson_value *v; + cson_object *o; + } param; + struct { + cson_value *v; + cson_object *o; + } reqPayload; /* request payload object (if any) */ + cson_array *warnings; /* response warnings */ + int timerId; /* fetched from fossil_timer_start() */ + } json; +#endif /* FOSSIL_ENABLE_JSON */ + int diffCnt[3]; /* Counts for DIFF_NUMSTAT: files, ins, del */ }; /* ** Macro for debugging: */ @@ -158,278 +337,692 @@ #endif Global g; /* -** The table of web pages supported by this application is generated -** automatically by the "mkindex" program and written into a file -** named "page_index.h". We include that file here to get access -** to the table. -*/ -#include "page_index.h" - -/* -** Search for a function whose name matches zName. Write a pointer to -** that function into *pxFunc and return 0. If no match is found, -** return 1. If the command is ambiguous return 2; -** -** The NameMap structure and the tables we are searching against are -** defined in the page_index.h header file which is automatically -** generated by mkindex.c program. -*/ -static int name_search( - const char *zName, /* The name we are looking for */ - const NameMap *aMap, /* Search in this array */ - int nMap, /* Number of slots in aMap[] */ - int *pIndex /* OUT: The index in aMap[] of the match */ -){ - int upr, lwr, cnt, m, i; - int n = strlen(zName); - lwr = 0; - upr = nMap-1; - while( lwr<=upr ){ - int mid, c; - mid = (upr+lwr)/2; - c = strcmp(zName, aMap[mid].zName); - if( c==0 ){ - *pIndex = mid; - return 0; - }else if( c<0 ){ - upr = mid - 1; - }else{ - lwr = mid + 1; - } - } - for(m=cnt=0, i=upr-2; i<=upr+3 && i1); -} - - -/* -** This procedure runs first. -*/ -int main(int argc, char **argv){ - const char *zCmdName; - int idx; - int rc; - - sqlite3_config(SQLITE_CONFIG_LOG, fossil_sqlite_log, 0); - g.now = time(0); +** atexit() handler which frees up "some" of the resources +** used by fossil. +*/ +static void fossil_atexit(void) { + static int once = 0; + if( once++ ) return; /* Ensure that this routine only runs once */ +#if USE_SEE + /* + ** Zero, unlock, and free the saved database encryption key now. + */ + db_unsave_encryption_key(); +#endif +#if defined(_WIN32) || (defined(__BIONIC__) && !defined(FOSSIL_HAVE_GETPASS)) + /* + ** Free the secure getpass() buffer now. + */ + freepass(); +#endif +#if defined(_WIN32) && !defined(_WIN64) && defined(FOSSIL_ENABLE_TCL) && \ + defined(USE_TCL_STUBS) + /* + ** If Tcl is compiled on Windows using the latest MinGW, Fossil can crash + ** when exiting while a stubs-enabled Tcl is still loaded. This is due to + ** a bug in MinGW, see: + ** + ** http://comments.gmane.org/gmane.comp.gnu.mingw.user/41724 + ** + ** The workaround is to manually unload the loaded Tcl library prior to + ** exiting the process. This issue does not impact 64-bit Windows. + */ + unloadTcl(g.interp, &g.tcl); +#endif +#ifdef FOSSIL_ENABLE_JSON + cson_value_free(g.json.gc.v); + memset(&g.json, 0, sizeof(g.json)); +#endif + free(g.zErrMsg); + if(g.db){ + db_close(0); + } + manifest_clear_cache(); + content_clear_cache(1); + rebuild_clear_cache(); + /* + ** FIXME: The next two lines cannot always be enabled; however, they + ** are very useful for tracking down TH1 memory leaks. + */ + if( fossil_getenv("TH1_DELETE_INTERP")!=0 ){ + if( g.interp ){ + Th_DeleteInterp(g.interp); g.interp = 0; + } +#if defined(TH_MEMDEBUG) + if( Th_GetOutstandingMalloc()!=0 ){ + fossil_print("Th_GetOutstandingMalloc() => %d\n", + Th_GetOutstandingMalloc()); + } + assert( Th_GetOutstandingMalloc()==0 ); +#endif + } +} + +/* +** Convert all arguments from mbcs (or unicode) to UTF-8. Then +** search g.argv for arguments "--args FILENAME". If found, then +** (1) remove the two arguments from g.argv +** (2) Read the file FILENAME +** (3) Use the contents of FILE to replace the two removed arguments: +** (a) Ignore blank lines in the file +** (b) Each non-empty line of the file is an argument, except +** (c) If the line begins with "-" and contains a space, it is broken +** into two arguments at the space. +*/ +void expand_args_option(int argc, void *argv){ + Blob file = empty_blob; /* Content of the file */ + Blob line = empty_blob; /* One line of the file */ + unsigned int nLine; /* Number of lines in the file*/ + unsigned int i, j, k; /* Loop counters */ + int n; /* Number of bytes in one line */ + unsigned int nArg; /* Number of new arguments */ + char *z; /* General use string pointer */ + char **newArgv; /* New expanded g.argv under construction */ + const char *zFileName; /* input file name */ + FILE *inFile; /* input FILE */ +#if defined(_WIN32) + wchar_t buf[MAX_PATH]; +#endif + g.argc = argc; g.argv = argv; - if( getenv("GATEWAY_INTERFACE")!=0 ){ - zCmdName = "cgi"; - }else if( argc<2 ){ - fprintf(stderr, "Usage: %s COMMAND ...\n", argv[0]); - exit(1); - }else{ - g.fQuiet = find_option("quiet", 0, 0)!=0; - g.fSqlTrace = find_option("sqltrace", 0, 0)!=0; - g.fSqlPrint = find_option("sqlprint", 0, 0)!=0; - g.fHttpTrace = find_option("httptrace", 0, 0)!=0; - g.zLogin = find_option("user", "U", 1); - zCmdName = argv[1]; - } - rc = name_search(zCmdName, aCommand, count(aCommand), &idx); - if( rc==1 ){ - fprintf(stderr,"%s: unknown command: %s\n" - "%s: use \"help\" for more information\n", - argv[0], zCmdName, argv[0]); - return 1; - }else if( rc==2 ){ - fprintf(stderr,"%s: ambiguous command prefix: %s\n" - "%s: use \"help\" for more information\n", - argv[0], zCmdName, argv[0]); - return 1; - } - aCommand[idx].xFunc(); - return 0; -} - -/* -** The following variable becomes true while processing a fatal error -** or a panic. If additional "recursive-fatal" errors occur while -** shutting down, the recursive errors are silently ignored. -*/ -static int mainInFatalError = 0; - -/* -** Print an error message, rollback all databases, and quit. These -** routines never return. -*/ -void fossil_panic(const char *zFormat, ...){ - char *z; - va_list ap; - static int once = 1; - mainInFatalError = 1; - va_start(ap, zFormat); - z = vmprintf(zFormat, ap); - va_end(ap); - if( g.cgiOutput && once ){ - once = 0; - cgi_printf("

    %h

    ", z); - cgi_reply(); - }else{ - fprintf(stderr, "%s: %s\n", g.argv[0], z); - } - db_force_rollback(); - exit(1); -} -void fossil_fatal(const char *zFormat, ...){ - char *z; - va_list ap; - mainInFatalError = 1; - va_start(ap, zFormat); - z = vmprintf(zFormat, ap); - va_end(ap); - if( g.cgiOutput ){ - g.cgiOutput = 0; - cgi_printf("

    %h

    ", z); - cgi_reply(); - }else{ - fprintf(stderr, "%s: %s\n", g.argv[0], z); - } - db_force_rollback(); - exit(1); -} - -/* This routine works like fossil_fatal() except that if called -** recursively, the recursive call is a no-op. -** -** Use this in places where an error might occur while doing -** fatal error shutdown processing. Unlike fossil_panic() and -** fossil_fatal() which never return, this routine might return if -** the fatal error handing is already in process. The caller must -** be prepared for this routine to return. -*/ -void fossil_fatal_recursive(const char *zFormat, ...){ - char *z; - va_list ap; - if( mainInFatalError ) return; - mainInFatalError = 1; - va_start(ap, zFormat); - z = vmprintf(zFormat, ap); - va_end(ap); - if( g.cgiOutput ){ - g.cgiOutput = 0; - cgi_printf("

    %h

    ", z); - cgi_reply(); - }else{ - fprintf(stderr, "%s: %s\n", g.argv[0], z); - } - db_force_rollback(); - exit(1); -} - - -/* Print a warning message */ -void fossil_warning(const char *zFormat, ...){ - char *z; - va_list ap; - va_start(ap, zFormat); - z = vmprintf(zFormat, ap); - va_end(ap); - if( g.cgiOutput ){ - cgi_printf("

    %h

    ", z); - }else{ - fprintf(stderr, "%s: %s\n", g.argv[0], z); - } -} - -/* -** Return a name for an SQLite error code -*/ -static const char *sqlite_error_code_name(int iCode){ + sqlite3_initialize(); +#if defined(_WIN32) && defined(BROKEN_MINGW_CMDLINE) + for(i=0; i=g.argc-1 ) return; + + zFileName = g.argv[i+1]; + if( strcmp(zFileName,"-")==0 ){ + inFile = stdin; + }else if( !file_isfile(zFileName, ExtFILE) ){ + fossil_fatal("Not an ordinary file: \"%s\"", zFileName); + }else{ + inFile = fossil_fopen(zFileName,"rb"); + if( inFile==0 ){ + fossil_fatal("Cannot open -args file [%s]", zFileName); + } + } + blob_read_from_channel(&file, inFile, -1); + if(stdin != inFile){ + fclose(inFile); + } + inFile = NULL; + blob_to_utf8_no_bom(&file, 1); + z = blob_str(&file); + for(k=0, nLine=1; z[k]; k++) if( z[k]=='\n' ) nLine++; + if( nLine>100000000 ) fossil_fatal("too many command-line arguments"); + nArg = g.argc + nLine*2; + newArgv = fossil_malloc( sizeof(char*)*nArg ); + for(j=0; j0 ){ + if( n<1 ){ + /* Reminder: corner-case: a line with 1 byte and no newline. */ + continue; + } + z = blob_buffer(&line); + if('\n'==z[n-1]){ + z[n-1] = 0; + } + + if((n>1) && ('\r'==z[n-2])){ + if(n==2) continue /*empty line*/; + z[n-2] = 0; + } + if(!z[0]) continue; + if( j>=nArg ){ + fossil_fatal("malformed command-line arguments"); + } + newArgv[j++] = z; + if( z[0]=='-' ){ + for(k=1; z[k] && !fossil_isspace(z[k]); k++){} + if( z[k] ){ + z[k] = 0; + k++; + if( z[k] ) newArgv[j++] = &z[k]; + } + } + } + i += 2; + while( i=2 ) break; + if( fd<0 ) x = errno; + }while( nTry++ < 2 ); + if( fd<2 ){ + g.cgiOutput = 1; + g.httpOut = stdout; + g.fullHttpReply = !g.isHTTP; + fossil_panic("file descriptor 2 is not open. (fd=%d, errno=%d)", + fd, x); + } + } +#endif + g.zCmdName = zCmdName; + rc = dispatch_name_search(zCmdName, CMDFLAG_COMMAND|CMDFLAG_PREFIX, &pCmd); + if( rc==1 && g.argc==2 && file_is_repository(g.argv[1]) ){ + /* If the command-line is "fossil ABC" and "ABC" is no a valid command, + ** but "ABC" is the name of a repository file, make the command be + ** "fossil ui ABC" instead. + */ + char **zNewArgv = fossil_malloc( sizeof(char*)*4 ); + zNewArgv[0] = g.argv[0]; + zNewArgv[1] = "ui"; + zNewArgv[2] = g.argv[1]; + zNewArgv[3] = 0; + g.argc = 3; + g.argv = zNewArgv; + g.zCmdName = zCmdName = "ui"; + rc = dispatch_name_search(zCmdName, CMDFLAG_COMMAND|CMDFLAG_PREFIX, &pCmd); + } + if( rc==1 ){ +#ifdef FOSSIL_ENABLE_TH1_HOOKS + if( !g.isHTTP && !g.fNoThHook ){ + rc = Th_CommandHook(zCmdName, 0); + }else{ + rc = TH_OK; + } + if( rc==TH_OK || rc==TH_RETURN || rc==TH_CONTINUE ){ + if( rc==TH_OK || rc==TH_RETURN ){ +#endif + fossil_fatal("%s: unknown command: %s\n" + "%s: use \"help\" for more information", + g.argv[0], zCmdName, g.argv[0]); +#ifdef FOSSIL_ENABLE_TH1_HOOKS + } + if( !g.isHTTP && !g.fNoThHook && (rc==TH_OK || rc==TH_CONTINUE) ){ + Th_CommandNotify(zCmdName, 0); + } + } + fossil_exit(0); +#endif + }else if( rc==2 ){ + Blob couldbe; + blob_init(&couldbe,0,0); + dispatch_matching_names(zCmdName, &couldbe); + fossil_print("%s: ambiguous command prefix: %s\n" + "%s: could be any of:%s\n" + "%s: use \"help\" for more information\n", + g.argv[0], zCmdName, g.argv[0], blob_str(&couldbe), g.argv[0]); + fossil_exit(1); + } +#ifdef FOSSIL_ENABLE_JSON + else if( rc==0 && strcmp("json",pCmd->zName)==0 ){ + g.json.isJsonMode = 1; + }else{ + assert(!g.json.isJsonMode && "JSON-mode misconfiguration."); + } +#endif + atexit( fossil_atexit ); +#ifdef FOSSIL_ENABLE_TH1_HOOKS + /* + ** The TH1 return codes from the hook will be handled as follows: + ** + ** TH_OK: The xFunc() and the TH1 notification will both be executed. + ** + ** TH_ERROR: The xFunc() will be skipped, the TH1 notification will be + ** skipped. If the xFunc() is being hooked, the error message + ** will be emitted. + ** + ** TH_BREAK: The xFunc() and the TH1 notification will both be skipped. + ** + ** TH_RETURN: The xFunc() will be executed, the TH1 notification will be + ** skipped. + ** + ** TH_CONTINUE: The xFunc() will be skipped, the TH1 notification will be + ** executed. + */ + if( !g.isHTTP && !g.fNoThHook ){ + rc = Th_CommandHook(pCmd->zName, pCmd->eCmdFlags); + }else{ + rc = TH_OK; + } + if( rc==TH_OK || rc==TH_RETURN || rc==TH_CONTINUE ){ + if( rc==TH_OK || rc==TH_RETURN ){ +#endif + pCmd->xFunc(); +#ifdef FOSSIL_ENABLE_TH1_HOOKS + } + if( !g.isHTTP && !g.fNoThHook && (rc==TH_OK || rc==TH_CONTINUE) ){ + Th_CommandNotify(pCmd->zName, pCmd->eCmdFlags); + } + } +#endif + fossil_exit(0); + /*NOT_REACHED*/ + return 0; } /* ** Print a usage comment and quit */ void usage(const char *zFormat){ - fprintf(stderr, "Usage: %s %s %s\n", g.argv[0], g.argv[1], zFormat); - exit(1); + fossil_fatal("Usage: %s %s %s", g.argv[0], g.argv[1], zFormat); } /* ** Remove n elements from g.argv beginning with the i-th element. */ -void remove_from_argv(int i, int n){ +static void remove_from_argv(int i, int n){ int j; for(j=i+n; j= g.argc) break; + if( i+hasArg >= g.argc ) break; z = g.argv[i]; if( z[0]!='-' ) continue; z++; if( z[0]=='-' ){ if( z[1]==0 ){ - remove_from_argv(i, 1); + /* Stop processing at "--" without consuming it. + verify_all_options() will consume this flag. */ break; } z++; } if( strncmp(z,zLong,nLong)==0 ){ @@ -440,172 +1033,406 @@ }else if( z[nLong]==0 ){ zReturn = g.argv[i+hasArg]; remove_from_argv(i, 1+hasArg); break; } - }else if( zShort!=0 && strcmp(z,zShort)==0 ){ + }else if( fossil_strcmp(z,zShort)==0 ){ zReturn = g.argv[i+hasArg]; remove_from_argv(i, 1+hasArg); break; } } return zReturn; } + +/* Return true if zOption exists in the command-line arguments, +** but do not remove it from the list or otherwise process it. +*/ +int has_option(const char *zOption){ + int i; + int n = (int)strlen(zOption); + for(i=1; imxLen ) mxLen = len; - } - nCol = 80/(mxLen+2); - if( nCol==0 ) nCol = 1; - nRow = (nWord + nCol - 1)/nCol; - for(i=0; i + @ %h(blob_str(&versionInfo)) + @
    + style_finish_page(); +} + /* ** Set the g.zBaseURL value to the full URL for the toplevel of ** the fossil tree. Set g.zTop to g.zBaseURL without the ** leading "http://" and the host and port. +** +** The g.zBaseURL is normally set based on HTTP_HOST and SCRIPT_NAME +** environment variables. However, if zAltBase is not NULL then it +** is the argument to the --baseurl option command-line option and +** g.zBaseURL and g.zTop is set from that instead. */ -void set_base_url(void){ +void set_base_url(const char *zAltBase){ int i; - const char *zHost = PD("HTTP_HOST",""); - const char *zMode = PD("HTTPS","off"); - const char *zCur = PD("SCRIPT_NAME","/"); - - i = strlen(zCur); - while( i>0 && zCur[i-1]=='/' ) i--; - if( strcmp(zMode,"on")==0 ){ - g.zBaseURL = mprintf("https://%s%.*s", zHost, i, zCur); - g.zTop = &g.zBaseURL[8+strlen(zHost)]; - }else{ - g.zBaseURL = mprintf("http://%s%.*s", zHost, i, zCur); - g.zTop = &g.zBaseURL[7+strlen(zHost)]; + const char *zHost; + const char *zMode; + const char *zCur; + + if( g.zBaseURL!=0 ) return; + if( zAltBase ){ + int i, n, c; + g.zTop = g.zBaseURL = mprintf("%s", zAltBase); + i = (int)strlen(g.zBaseURL); + while( i>3 && g.zBaseURL[i-1]=='/' ){ i--; } + g.zBaseURL[i] = 0; + if( strncmp(g.zTop, "http://", 7)==0 ){ + /* it is HTTP, replace prefix with HTTPS. */ + g.zHttpsURL = mprintf("https://%s", &g.zTop[7]); + }else if( strncmp(g.zTop, "https://", 8)==0 ){ + /* it is already HTTPS, use it. */ + g.zHttpsURL = mprintf("%s", g.zTop); + }else{ + fossil_fatal("argument to --baseurl should be 'http://host/path'" + " or 'https://host/path'"); + } + for(i=n=0; (c = g.zTop[i])!=0; i++){ + if( c=='/' ){ + n++; + if( n==3 ){ + g.zTop += i; + break; + } + } + } + if( n==2 ) g.zTop = ""; + if( g.zTop==g.zBaseURL ){ + fossil_fatal("argument to --baseurl should be 'http://host/path'" + " or 'https://host/path'"); + } + if( g.zTop[1]==0 ) g.zTop++; + }else{ + char *z; + zHost = PD("HTTP_HOST",""); + z = fossil_strdup(zHost); + for(i=0; z[i]; i++){ + if( z[i]<='Z' && z[i]>='A' ) z[i] += 'a' - 'A'; + } + if( i>3 && z[i-1]=='0' && z[i-2]=='8' && z[i-3]==':' ) i -= 3; + if( i && z[i-1]=='.' ) i--; + z[i] = 0; + zMode = PD("HTTPS","off"); + zCur = PD("SCRIPT_NAME","/"); + i = strlen(zCur); + while( i>0 && zCur[i-1]=='/' ) i--; + if( fossil_stricmp(zMode,"on")==0 ){ + g.zBaseURL = mprintf("https://%s%.*s", z, i, zCur); + g.zTop = &g.zBaseURL[8+strlen(z)]; + g.zHttpsURL = g.zBaseURL; + }else{ + g.zBaseURL = mprintf("http://%s%.*s", z, i, zCur); + g.zTop = &g.zBaseURL[7+strlen(z)]; + g.zHttpsURL = mprintf("https://%s%.*s", z, i, zCur); + } + fossil_free(z); + } + if( db_is_writeable("repository") ){ + int nBase = (int)strlen(g.zBaseURL); + char *zBase = g.zBaseURL; + if( g.nExtraURL>0 && g.nExtraURL0 && zDir[i]!='/'; i--){} - if( zDir[i]!='/' ) fossil_panic("bad repository name: %s", zRepo); - zDir[i] = 0; - chdir(zDir); - chroot(zDir); - zDir[i] = '/'; - zRepo = &zDir[i]; + if( !noJail ){ + if( file_isdir(zDir, ExtFILE)==1 ){ + if( file_chdir(zDir, 1) ){ + fossil_panic("unable to chroot into %s", zDir); + } + g.fJail = 1; + zRepo = "/"; + }else{ + for(i=strlen(zDir)-1; i>0 && zDir[i]!='/'; i--){} + if( zDir[i]!='/' ) fossil_fatal("bad repository name: %s", zRepo); + if( i>0 ){ + zDir[i] = 0; + if( file_chdir(zDir, 1) ){ + fossil_fatal("unable to chroot into %s", zDir); + } + zDir[i] = '/'; + } + zRepo = &zDir[i]; + } } if( stat(zRepo, &sStat)!=0 ){ fossil_fatal("cannot stat() repository: %s", zRepo); } - setgid(sStat.st_gid); - setuid(sStat.st_uid); - if( g.db!=0 ){ - db_close(); + i = setgid(sStat.st_gid); + i = i || setuid(sStat.st_uid); + if(i){ + fossil_fatal("setgid/uid() failed with errno %d", errno); + } + if( g.db==0 && file_isfile(zRepo, ExtFILE) ){ db_open_repository(zRepo); } } #endif return zRepo; } + +/* +** Called whenever a crash is encountered while processing a webpage. +*/ +void sigsegv_handler(int x){ +#if HAVE_BACKTRACE + void *array[20]; + size_t size; + char **strings; + size_t i; + Blob out; + size = backtrace(array, sizeof(array)/sizeof(array[0])); + strings = backtrace_symbols(array, size); + blob_init(&out, 0, 0); + blob_appendf(&out, "Segfault"); + for(i=0; iNot Found
    - cgi_set_status(404, "not found"); - cgi_reply(); - } - return; - } + char *zRepo; /* Candidate repository name */ + char *zToFree = 0; /* Malloced memory that needs to be freed */ + const char *zCleanRepo; /* zRepo with surplus leading "/" removed */ + const char *zOldScript = PD("SCRIPT_NAME", ""); /* Original SCRIPT_NAME */ + char *zNewScript; /* Revised SCRIPT_NAME after processing */ + int j, k; /* Loop variables */ + i64 szFile; /* File size of the candidate repository */ + + i = zPathInfo[0]!=0; + if( fossil_strcmp(g.zRepositoryName, "/")==0 ){ + zBase++; +#if defined(_WIN32) || defined(__CYGWIN__) + if( sqlite3_strglob("/[a-zA-Z]:/*", zPathInfo)==0 ) i = 4; +#endif + } + while( 1 ){ + while( zPathInfo[i] && zPathInfo[i]!='/' ){ i++; } + + /* The candidate repository name is some prefix of the PATH_INFO + ** with ".fossil" appended */ + zRepo = zToFree = mprintf("%s%.*s.fossil",zBase,i,zPathInfo); + if( g.fHttpTrace ){ + @ + fprintf(stderr, "# looking for repository named \"%s\"\n", zRepo); + } + + + /* For safety -- to prevent an attacker from accessing arbitrary disk + ** files by sending a maliciously crafted request URI to a public + ** server -- make sure the repository basename contains no + ** characters other than alphanumerics, "/", "_", "-", and ".", and + ** that "-" never occurs immediately after a "/" and that "." is always + ** surrounded by two alphanumerics. Any character that does not + ** satisfy these constraints is converted into "_". + */ + szFile = 0; + for(j=strlen(zBase)+1, k=0; zRepo[j] && k + fprintf(stderr, "# unsafe pathname rejected: %s\n", zRepo); + } + break; + } + + /* Check to see if a file name zRepo exists. If a file named zRepo + ** does not exist, szFile will become -1. If the file does exist, + ** then szFile will become zero (for an empty file) or positive. + ** Special case: Assume any file with a basename of ".fossil" does + ** not exist. + */ + zCleanRepo = file_cleanup_fullpath(zRepo); + if( szFile==0 && sqlite3_strglob("*/.fossil",zRepo)!=0 ){ + szFile = file_size(zCleanRepo, ExtFILE); + if( g.fHttpTrace ){ + char zBuf[24]; + sqlite3_snprintf(sizeof(zBuf), zBuf, "%lld", szFile); + @ + fprintf(stderr, "# file_size(%s) = %s\n", zCleanRepo, zBuf); + } + } + + /* If no file named by zRepo exists, remove the added ".fossil" suffix + ** and check to see if there is a file or directory with the same + ** name as the raw PATH_INFO text. + */ + if( szFile<0 && i>0 ){ + const char *zMimetype; + assert( fossil_strcmp(&zRepo[j], ".fossil")==0 ); + zRepo[j] = 0; /* Remove the ".fossil" suffix */ + + /* The PATH_INFO prefix seen so far is a valid directory. + ** Continue the loop with the next element of the PATH_INFO */ + if( zPathInfo[i]=='/' && file_isdir(zCleanRepo, ExtFILE)==1 ){ + fossil_free(zToFree); + i++; + continue; + } + + /* If zRepo is the name of an ordinary file that matches the + ** "--file GLOB" pattern, then the CGI reply is the text of + ** of the file. + ** + ** For safety, do not allow any file whose name contains ".fossil" + ** to be returned this way, to prevent complete repositories from + ** being delivered accidently. This is not intended to be a + ** general-purpose web server. The "--file GLOB" mechanism is + ** designed to allow the delivery of a few static images or HTML + ** pages. + */ + if( pFileGlob!=0 + && file_isfile(zCleanRepo, ExtFILE) + && glob_match(pFileGlob, file_cleanup_fullpath(zRepo)) + && sqlite3_strglob("*.fossil*",zRepo)!=0 + && (zMimetype = mimetype_from_name(zRepo))!=0 + && strcmp(zMimetype, "application/x-fossil-artifact")!=0 + ){ + Blob content; + blob_read_from_file(&content, file_cleanup_fullpath(zRepo), ExtFILE); + cgi_set_content_type(zMimetype); + cgi_set_content(&content); + cgi_reply(); + return; + } + zRepo[j] = '.'; + } + + /* If we reach this point, it means that the search of the PATH_INFO + ** string is finished. Either zRepo contains the name of the + ** repository to be used, or else no repository could be found and + ** some kind of error response is required. + */ + if( szFile<1024 ){ + set_base_url(0); + if( (zPathInfo[0]==0 || strcmp(zPathInfo,"/")==0) + && allowRepoList + && repo_list_page() ){ + /* Will return a list of repositories */ + }else if( zNotFound ){ + cgi_redirect(zNotFound); + }else{ +#ifdef FOSSIL_ENABLE_JSON + if(g.json.isJsonMode){ + json_err(FSL_JSON_E_RESOURCE_NOT_FOUND,NULL,1); + return; + } +#endif + @ + @ + @ + @

    Not Found

    + @ + cgi_set_status(404, "Not Found"); + cgi_reply(); + } + return; + } + break; + } + + /* Add the repository name (without the ".fossil" suffix) to the end + ** of SCRIPT_NAME and g.zTop and g.zBaseURL and remove the repository + ** name from the beginning of PATH_INFO. + */ zNewScript = mprintf("%s%.*s", zOldScript, i, zPathInfo); + if( g.zTop ) g.zTop = mprintf("%R%.*s", i, zPathInfo); + if( g.zBaseURL ) g.zBaseURL = mprintf("%s%.*s", g.zBaseURL, i, zPathInfo); cgi_replace_parameter("PATH_INFO", &zPathInfo[i+1]); zPathInfo += i; cgi_replace_parameter("SCRIPT_NAME", zNewScript); - db_open_repository(zRepo); + db_open_repository(file_cleanup_fullpath(zRepo)); if( g.fHttpTrace ){ - fprintf(stderr, + @ + @ + @ + fprintf(stderr, "# repository: [%s]\n" - "# new PATH_INFO = [%s]\n" - "# new SCRIPT_NAME = [%s]\n", + "# translated PATH_INFO = [%s]\n" + "# translated SCRIPT_NAME = [%s]\n", zRepo, zPathInfo, zNewScript); + if( g.zTop ){ + @ + fprintf(stderr, "# translated g.zTop = [%s]\n", g.zTop); + } + if( g.zBaseURL ){ + @ + fprintf(stderr, "# translated g.zBaseURL = [%s]\n", g.zBaseURL); + } } } - /* Find the page that the user has requested, construct and deliver that - ** page. + /* At this point, the appropriate repository database file will have + ** been opened. + */ + + + /* + ** Check to see if the first term of PATH_INFO specifies an + ** alternative skin. This will be the case if the first term of + ** PATH_INFO begins with "draftN/" where N is an integer between 1 + ** and 9. If so, activate the skin associated with that draft. + */ + if( zPathInfo && strncmp(zPathInfo,"/draft",6)==0 + && zPathInfo[6]>='1' && zPathInfo[6]<='9' + && (zPathInfo[7]=='/' || zPathInfo[7]==0) + ){ + int iSkin = zPathInfo[6] - '0'; + char *zNewScript; + skin_use_draft(iSkin); + zNewScript = mprintf("%T/draft%d", P("SCRIPT_NAME"), iSkin); + if( g.zTop ) g.zTop = mprintf("%R/draft%d", iSkin); + if( g.zBaseURL ) g.zBaseURL = mprintf("%s/draft%d", g.zBaseURL, iSkin); + zPathInfo += 7; + g.nExtraURL += 7; + cgi_replace_parameter("PATH_INFO", zPathInfo); + cgi_replace_parameter("SCRIPT_NAME", zNewScript); + etag_cancel(); + } + + /* If the content type is application/x-fossil or + ** application/x-fossil-debug, then a sync/push/pull/clone is + ** desired, so default the PATH_INFO to /xfer */ - if( g.zContentType && memcmp(g.zContentType, "application/x-fossil", 20)==0 ){ + if( g.zContentType && + strncmp(g.zContentType, "application/x-fossil", 20)==0 ){ + /* Special case: If the content mimetype shows that it is "fossil sync" + ** payload, then pretend that the PATH_INFO is /xfer so that we always + ** invoke the sync page. */ zPathInfo = "/xfer"; } - set_base_url(); - if( zPathInfo==0 || zPathInfo[0]==0 + + /* Use the first element of PATH_INFO as the page name + ** and deliver the appropriate page back to the user. + */ + set_base_url(0); + if( fossil_redirect_to_https_if_needed(2) ) return; + if( zPathInfo==0 || zPathInfo[0]==0 || (zPathInfo[0]=='/' && zPathInfo[1]==0) ){ - fossil_redirect_home(); + /* Second special case: If the PATH_INFO is blank, issue a redirect to + ** the home page identified by the "index-page" setting in the repository + ** CONFIG table, to "/index" if there no "index-page" setting. */ +#ifdef FOSSIL_ENABLE_JSON + if(g.json.isJsonMode){ + json_err(FSL_JSON_E_RESOURCE_NOT_FOUND,NULL,1); + fossil_exit(0); + } +#endif + fossil_redirect_home() /*does not return*/; }else{ zPath = mprintf("%s", zPathInfo); } - /* Remove the leading "/" at the beginning of the path. + /* Make g.zPath point to the first element of the path. Make + ** g.zExtra point to everything past that point. */ - g.zPath = &zPath[1]; - for(i=1; zPath[i] && zPath[i]!='/'; i++){} - if( zPath[i]=='/' ){ - zPath[i] = 0; - g.zExtra = &zPath[i+1]; - }else{ - g.zExtra = 0; + while(1){ + g.zPath = &zPath[1]; + for(i=1; zPath[i] && zPath[i]!='/'; i++){} + if( zPath[i]=='/' ){ + zPath[i] = 0; + g.zExtra = &zPath[i+1]; + }else{ + g.zExtra = 0; + } + break; } if( g.zExtra ){ /* CGI parameters get this treatment elsewhere, but places like getfile ** will use g.zExtra directly. + ** Reminder: the login mechanism uses 'name' differently, and may + ** eventually have a problem/collision with this. + ** + ** Disabled by stephan when running in JSON mode because this + ** particular parameter name is very common and i have had no end + ** of grief with this handling. The JSON API never relies on the + ** handling below, and by disabling it in JSON mode I can remove + ** lots of special-case handling in several JSON handlers. */ - dehttpize(g.zExtra); - cgi_set_parameter_nocopy("name", g.zExtra); +#ifdef FOSSIL_ENABLE_JSON + if(g.json.isJsonMode==0){ +#endif + dehttpize(g.zExtra); + cgi_set_parameter_nocopy("name", g.zExtra, 1); +#ifdef FOSSIL_ENABLE_JSON + } +#endif } - + /* Locate the method specified by the path and execute the function ** that implements that method. */ - if( name_search(g.zPath, aWebpage, count(aWebpage), &idx) && - name_search("not_found", aWebpage, count(aWebpage), &idx) ){ - cgi_set_status(404,"Not Found"); - @

    Not Found

    - @

    Page not found: %h(g.zPath)

    + if( dispatch_name_search(g.zPath-1, CMDFLAG_WEBPAGE, &pCmd) + && dispatch_alias(g.zPath-1, &pCmd) + ){ +#ifdef FOSSIL_ENABLE_JSON + if(g.json.isJsonMode!=0){ + json_err(FSL_JSON_E_RESOURCE_NOT_FOUND,NULL,0); + }else +#endif + { +#ifdef FOSSIL_ENABLE_TH1_HOOKS + int rc; + if( !g.fNoThHook ){ + rc = Th_WebpageHook(g.zPath, 0); + }else{ + rc = TH_OK; + } + if( rc==TH_OK || rc==TH_RETURN || rc==TH_CONTINUE ){ + if( rc==TH_OK || rc==TH_RETURN ){ +#endif + cgi_set_status(404,"Not Found"); + @

    Not Found

    + @

    Page not found: %h(g.zPath)

    +#ifdef FOSSIL_ENABLE_TH1_HOOKS + } + if( !g.fNoThHook && (rc==TH_OK || rc==TH_CONTINUE) ){ + Th_WebpageNotify(g.zPath, 0); + } + } +#endif + } + }else if( pCmd->xFunc!=page_xfer && db_schema_is_outofdate() ){ +#ifdef FOSSIL_ENABLE_JSON + if(g.json.isJsonMode!=0){ + json_err(FSL_JSON_E_DB_NEEDS_REBUILD,NULL,0); + }else +#endif + { + @

    Server Configuration Error

    + @

    The database schema on the server is out-of-date. Please ask + @ the administrator to run fossil rebuild.

    + } }else{ - aWebpage[idx].xFunc(); +#ifdef FOSSIL_ENABLE_JSON + static int jsonOnce = 0; + if( jsonOnce==0 && g.json.isJsonMode!=0 ){ + assert(json_is_bootstrapped_early()); + json_bootstrap_late(); + jsonOnce = 1; + } +#endif + if( (pCmd->eCmdFlags & CMDFLAG_RAWCONTENT)==0 ){ + cgi_decode_post_parameters(); + } + if( g.fCgiTrace ){ + fossil_trace("######## Calling %s #########\n", pCmd->zName); + cgi_print_all(1, 1); + } +#ifdef FOSSIL_ENABLE_TH1_HOOKS + { + /* + ** The TH1 return codes from the hook will be handled as follows: + ** + ** TH_OK: The xFunc() and the TH1 notification will both be executed. + ** + ** TH_ERROR: The xFunc() will be skipped, the TH1 notification will be + ** skipped. If the xFunc() is being hooked, the error message + ** will be emitted. + ** + ** TH_BREAK: The xFunc() and the TH1 notification will both be skipped. + ** + ** TH_RETURN: The xFunc() will be executed, the TH1 notification will be + ** skipped. + ** + ** TH_CONTINUE: The xFunc() will be skipped, the TH1 notification will be + ** executed. + */ + int rc; + if( !g.fNoThHook ){ + rc = Th_WebpageHook(pCmd->zName+1, pCmd->eCmdFlags); + }else{ + rc = TH_OK; + } + if( rc==TH_OK || rc==TH_RETURN || rc==TH_CONTINUE ){ + if( rc==TH_OK || rc==TH_RETURN ){ +#endif + pCmd->xFunc(); +#ifdef FOSSIL_ENABLE_TH1_HOOKS + } + if( !g.fNoThHook && (rc==TH_OK || rc==TH_CONTINUE) ){ + Th_WebpageNotify(pCmd->zName+1, pCmd->eCmdFlags); + } + } + } +#endif } /* Return the result. */ cgi_reply(); } + +/* If the CGI program contains one or more lines of the form +** +** redirect: repository-filename http://hostname/path/%s +** +** then control jumps here. Search each repository for an artifact ID +** or ticket ID that matches the "name" query parameter. If there is +** no "name" query parameter, use PATH_INFO instead. If a match is +** found, redirect to the corresponding URL. Substitute "%s" in the +** URL with the value of the name query parameter before the redirect. +** +** If there is a line of the form: +** +** redirect: * URL +** +** Then a redirect is made to URL if no match is found. If URL contains +** "%s" then substitute the "name" query parameter. If REPO is "*" and +** URL does not contains "%s" and does not contain "?" then append +** PATH_INFO and QUERY_STRING to the URL prior to the redirect. +** +** If no matches are found and if there is no "*" entry, then generate +** a primitive error message. +** +** USE CASES: +** +** (1) Suppose you have two related projects projA and projB. You can +** use this feature to set up an /info page that covers both +** projects. +** +** redirect: /fossils/projA.fossil /proj-a/info/%s +** redirect: /fossils/projB.fossil /proj-b/info/%s +** +** Then visits to the /info/HASH page will redirect to the +** first project that contains that hash. +** +** (2) Use the "*" form for to redirect legacy URLs. On the Fossil +** website we have an CGI at http://fossil.com/index.html (note +** ".com" instead of ".org") that looks like this: +** +** #!/usr/bin/fossil +** redirect: * https://fossil-scm.org/home +** +** Thus requests to the .com website redirect to the .org website. +*/ +static void redirect_web_page(int nRedirect, char **azRedirect){ + int i; /* Loop counter */ + const char *zNotFound = 0; /* Not found URL */ + const char *zName = P("name"); + set_base_url(0); + if( zName==0 ){ + zName = P("PATH_INFO"); + if( zName && zName[0]=='/' ) zName++; + } + if( zName ){ + for(i=0; i + @ No Such Object + @ + @

    No such object: %h(zName)

    + @ + cgi_reply(); + } +} /* -** COMMAND: cgi -** -** Usage: %fossil ?cgi? SCRIPT -** -** The SCRIPT argument is the name of a file that is the CGI script -** that is being run. The command name, "cgi", may be omitted if -** the GATEWAY_INTERFACE environment variable is set to "CGI" (which -** should always be the case for CGI scripts run by a webserver.) The -** SCRIPT file should look something like this: +** COMMAND: cgi* +** +** Usage: %fossil ?cgi? FILE +** +** This command causes Fossil to generate reply to a CGI request. +** +** The FILE argument is the name of a control file that provides Fossil +** with important information such as where to find its repository. In +** a typical CGI deployment, FILE is the name of the CGI script and will +** typically look something like this: ** ** #!/usr/bin/fossil ** repository: /home/somebody/project.db ** -** The second line defines the name of the repository. After locating -** the repository, fossil will generate a webpage on stdout based on -** the values of standard CGI environment variables. +** The command name, "cgi", may be omitted if the GATEWAY_INTERFACE +** environment variable is set to "CGI", which should always be the +** case for CGI scripts run by a webserver. Fossil ignores any lines +** that begin with "#". +** +** The following control lines are recognized: +** +** repository: PATH Name of the Fossil repository +** +** directory: PATH Name of a directory containing many Fossil +** repositories whose names all end with ".fossil". +** There should only be one of "repository:" +** or "directory:" +** +** notfound: URL When in "directory:" mode, redirect to +** URL if no suitable repository is found. +** +** repolist When in "directory:" mode, display a page +** showing a list of available repositories if +** the URL is "/". +** +** localauth Grant administrator privileges to connections +** from 127.0.0.1 or ::1. +** +** skin: LABEL Use the built-in skin called LABEL rather than +** the default. If there are no skins called LABEL +** then this line is a no-op. +** +** files: GLOBLIST GLOBLIST is a comma-separated list of GLOB +** patterns that specify files that can be +** returned verbatim. This feature allows Fossil +** to act as a web server returning static +** content. +** +** setenv: NAME VALUE Set environment variable NAME to VALUE. Or +** if VALUE is omitted, unset NAME. +** +** HOME: PATH Shorthand for "setenv: HOME PATH" +** +** cgi-debug: FILE Causing debugging information to be written +** into FILE. +** +** errorlog: FILE Warnings, errors, and panics written to FILE. +** +** timeout: SECONDS Do not run for longer than SECONDS. The default +** timeout is FOSSIL_DEFAULT_TIMEOUT (600) seconds. +** +** extroot: DIR Directory that is the root of the sub-CGI tree +** on the /ext page. +** +** redirect: REPO URL Extract the "name" query parameter and search +** REPO for a check-in or ticket that matches the +** value of "name", then redirect to URL. There +** can be multiple "redirect:" lines that are +** processed in order. If the REPO is "*", then +** an unconditional redirect to URL is taken. +** +** jsmode: VALUE Specifies the delivery mode for JavaScript +** files. See the help text for the --jsmode +** flag of the http command. +** +** mainmenu: FILE Override the mainmenu config setting with the +** contents of the given file. +** +** Most CGI files contain only a "repository:" line. It is uncommon to +** use any other option. +** +** See also: [[http]], [[server]], [[winsrv]] */ void cmd_cgi(void){ const char *zFile; const char *zNotFound = 0; - Blob config, line, key, value; - if( g.argc==3 && strcmp(g.argv[1],"cgi")==0 ){ - zFile = g.argv[2]; - }else{ - zFile = g.argv[1]; - } + char **azRedirect = 0; /* List of repositories to redirect to */ + int nRedirect = 0; /* Number of entries in azRedirect */ + Glob *pFileGlob = 0; /* Pattern for files */ + int allowRepoList = 0; /* Allow lists of repository files */ + Blob config, line, key, value, value2; + /* Initialize the CGI environment. */ g.httpOut = stdout; g.httpIn = stdin; -#ifdef __MINGW32__ - /* Set binary mode on windows to avoid undesired translations - ** between \n and \r\n. */ - setmode(_fileno(g.httpOut), _O_BINARY); - setmode(_fileno(g.httpIn), _O_BINARY); -#endif -#ifdef __EMX__ - /* Similar hack for OS/2 */ - setmode(fileno(g.httpOut), O_BINARY); - setmode(fileno(g.httpIn), O_BINARY); -#endif + fossil_binary_mode(g.httpOut); + fossil_binary_mode(g.httpIn); g.cgiOutput = 1; - blob_read_from_file(&config, zFile); + fossil_set_timeout(FOSSIL_DEFAULT_TIMEOUT); + /* Find the name of the CGI control file */ + if( g.argc==3 && fossil_strcmp(g.argv[1],"cgi")==0 ){ + zFile = g.argv[2]; + }else if( g.argc>=2 ){ + zFile = g.argv[1]; + }else{ + cgi_panic("No CGI control file specified"); + } + /* Read and parse the CGI control file. */ + blob_read_from_file(&config, zFile, ExtFILE); while( blob_line(&config, &line) ){ if( !blob_token(&line, &key) ) continue; if( blob_buffer(&key)[0]=='#' ) continue; - if( blob_eq(&key, "debug:") && blob_token(&line, &value) ){ - g.fDebug = fopen(blob_str(&value), "a"); - blob_reset(&value); - continue; - } - if( blob_eq(&key, "HOME:") && blob_token(&line, &value) ){ - cgi_setenv("HOME", blob_str(&value)); - blob_reset(&value); - continue; - } - if( blob_eq(&key, "repository:") && blob_token(&line, &value) ){ + if( blob_eq(&key, "repository:") && blob_tail(&line, &value) ){ + /* repository: FILENAME + ** + ** The name of the Fossil repository to be served via CGI. Most + ** fossil CGI scripts have a single non-comment line that contains + ** this one entry. + */ + blob_trim(&value); db_open_repository(blob_str(&value)); blob_reset(&value); continue; } if( blob_eq(&key, "directory:") && blob_token(&line, &value) ){ - db_close(); + /* directory: DIRECTORY + ** + ** If repository: is omitted, then terms of the PATH_INFO cgi parameter + ** are appended to DIRECTORY looking for a repository (whose name ends + ** in ".fossil") or a file in "files:". + */ + db_close(1); g.zRepositoryName = mprintf("%s", blob_str(&value)); blob_reset(&value); continue; } if( blob_eq(&key, "notfound:") && blob_token(&line, &value) ){ + /* notfound: URL + ** + ** If using directory: and no suitable repository or file is found, + ** then redirect to URL. + */ zNotFound = mprintf("%s", blob_str(&value)); blob_reset(&value); continue; } + if( blob_eq(&key, "localauth") ){ + /* localauth + ** + ** Grant "administrator" privileges to users connecting with HTTP + ** from IP address 127.0.0.1. Do not bother checking credentials. + */ + g.useLocalauth = 1; + continue; + } + if( blob_eq(&key, "repolist") ){ + /* repolist + ** + ** If using "directory:" and the URL is "/" then generate a page + ** showing a list of available repositories. + */ + allowRepoList = 1; + continue; + } + if( blob_eq(&key, "redirect:") && blob_token(&line, &value) + && blob_token(&line, &value2) ){ + /* See the header comment on the redirect_web_page() function + ** above for details. */ + nRedirect++; + azRedirect = fossil_realloc(azRedirect, 2*nRedirect*sizeof(char*)); + azRedirect[nRedirect*2-2] = mprintf("%s", blob_str(&value)); + azRedirect[nRedirect*2-1] = mprintf("%s", blob_str(&value2)); + blob_reset(&value); + blob_reset(&value2); + continue; + } + if( blob_eq(&key, "files:") && blob_token(&line, &value) ){ + /* files: GLOBLIST + ** + ** GLOBLIST is a comma-separated list of filename globs. For + ** example: *.html,*.css,*.js + ** + ** If the repository: line is omitted and then PATH_INFO is searched + ** for files that match any of these GLOBs and if any such file is + ** found it is returned verbatim. This feature allows "fossil server" + ** to function as a primitive web-server delivering arbitrary content. + */ + pFileGlob = glob_create(blob_str(&value)); + blob_reset(&value); + continue; + } + if( blob_eq(&key, "setenv:") && blob_token(&line, &value) ){ + /* setenv: NAME VALUE + ** setenv: NAME + ** + ** Sets environment variable NAME to VALUE. If VALUE is omitted, then + ** the environment variable is unset. + */ + blob_token(&line,&value2); + fossil_setenv(blob_str(&value), blob_str(&value2)); + blob_reset(&value); + blob_reset(&value2); + continue; + } + if( blob_eq(&key, "errorlog:") && blob_token(&line, &value) ){ + /* errorlog: FILENAME + ** + ** Causes messages from warnings, errors, and panics to be appended + ** to FILENAME. + */ + g.zErrlog = mprintf("%s", blob_str(&value)); + blob_reset(&value); + continue; + } + if( blob_eq(&key, "extroot:") && blob_token(&line, &value) ){ + /* extroot: DIRECTORY + ** + ** Enables the /ext webpage to use sub-cgi rooted at DIRECTORY + */ + g.zExtRoot = mprintf("%s", blob_str(&value)); + blob_reset(&value); + continue; + } + if( blob_eq(&key, "timeout:") && blob_token(&line, &value) ){ + /* timeout: SECONDS + ** + ** Set an alarm() that kills the process after SECONDS. The + ** default value is FOSSIL_DEFAULT_TIMEOUT (600) seconds. + */ + fossil_set_timeout(atoi(blob_str(&value))); + continue; + } + if( blob_eq(&key, "HOME:") && blob_token(&line, &value) ){ + /* HOME: VALUE + ** + ** Set CGI parameter "HOME" to VALUE. This is legacy. Use + ** setenv: instead. + */ + cgi_setenv("HOME", blob_str(&value)); + blob_reset(&value); + continue; + } + if( blob_eq(&key, "skin:") && blob_token(&line, &value) ){ + /* skin: LABEL + ** + ** Use one of the built-in skins defined by LABEL. LABEL is the + ** name of the subdirectory under the skins/ directory that holds + ** the elements of the built-in skin. If LABEL does not match, + ** this directive is a silent no-op. + */ + fossil_free(skin_use_alternative(blob_str(&value), 1)); + blob_reset(&value); + continue; + } + if( blob_eq(&key, "jsmode:") && blob_token(&line, &value) ){ + /* jsmode: MODE + ** + ** Change how JavaScript resources are delivered with each HTML + ** page. MODE is "inline" to put all JS inline, or "separate" to + ** cause each JS file to be requested using a separate HTTP request, + ** or "bundled" to have all JS files to be fetched with a single + ** auxiliary HTTP request. Noting, however, that "single" might + ** actually mean more than one, depending on the script-timing + ** requirements of any given page. + */ + builtin_set_js_delivery_mode(blob_str(&value),0); + blob_reset(&value); + continue; + } + if( blob_eq(&key, "mainmenu:") && blob_token(&line, &value) ){ + /* mainmenu: FILENAME + ** + ** Use the contents of FILENAME as the value of the site's + ** "mainmenu" setting, overriding the contents (for this + ** request) of the db-side setting or the hard-coded default. + */ + g.zMainMenuFile = mprintf("%s", blob_str(&value)); + blob_reset(&value); + continue; + } + if( blob_eq(&key, "cgi-debug:") && blob_token(&line, &value) ){ + /* cgi-debug: FILENAME + ** + ** Causes output from cgi_debug() and CGIDEBUG(()) calls to go + ** into FILENAME. Useful for debugging CGI configuration problems. + */ + char *zNow = cgi_iso8601_datestamp(); + cgi_load_environment(); + g.fDebug = fossil_fopen(blob_str(&value), "ab"); + blob_reset(&value); + cgi_debug("-------- BEGIN cgi at %s --------\n", zNow); + fossil_free(zNow); + cgi_print_all(1,2); + continue; + } } blob_reset(&config); - if( g.db==0 && g.zRepositoryName==0 ){ + if( g.db==0 && g.zRepositoryName==0 && nRedirect==0 ){ cgi_panic("Unable to find or open the project repository"); } cgi_init(); - process_one_web_page(zNotFound); + if( nRedirect ){ + redirect_web_page(nRedirect, azRedirect); + }else{ + process_one_web_page(zNotFound, pFileGlob, allowRepoList); + } } /* -** If g.argv[2] exists then it is either the name of a repository +** If g.argv[arg] exists then it is either the name of a repository ** that will be used by a server, or else it is a directory that -** contains multiple repositories that can be served. If g.argv[2] +** contains multiple repositories that can be served. If g.argv[arg] ** is a directory, the repositories it contains must be named -** "*.fossil". If g.argv[2] does not exists, then we must be within -** a check-out and the repository to be served is the repository of +** "*.fossil". If g.argv[arg] does not exist, then we must be within +** an open check-out and the repository to serve is the repository of ** that check-out. ** -** Open the respository to be served if it is known. If g.argv[2] is +** Open the repository to be served if it is known. If g.argv[arg] is ** a directory full of repositories, then set g.zRepositoryName to ** the name of that directory and the specific repository will be ** opened later by process_one_web_page() based on the content of ** the PATH_INFO variable. ** -** If disallowDir is set, then the directory full of repositories method -** is disallowed. +** If the fCreate flag is set, then create the repository if it +** does not already exist. Always use "auto" hash-policy in this case. */ -static void find_server_repository(int disallowDir){ - if( g.argc<3 ){ +static void find_server_repository(int arg, int fCreate){ + if( g.argc<=arg ){ db_must_be_within_tree(); - }else if( !disallowDir && file_isdir(g.argv[2])==1 ){ - g.zRepositoryName = mprintf("%s", g.argv[2]); - file_simplify_name(g.zRepositoryName, -1); + }else{ + const char *zRepo = g.argv[arg]; + int isDir = file_isdir(zRepo, ExtFILE); + if( isDir==1 ){ + g.zRepositoryName = mprintf("%s", zRepo); + file_simplify_name(g.zRepositoryName, -1, 0); + }else{ + if( isDir==0 && fCreate ){ + const char *zPassword; + db_create_repository(zRepo); + db_open_repository(zRepo); + db_begin_transaction(); + g.eHashPolicy = HPOLICY_SHA3; + db_set_int("hash-policy", HPOLICY_SHA3, 0); + db_initial_setup(0, "now", g.zLogin); + db_end_transaction(0); + fossil_print("project-id: %s\n", db_get("project-code", 0)); + fossil_print("server-id: %s\n", db_get("server-code", 0)); + zPassword = db_text(0, "SELECT pw FROM user WHERE login=%Q", g.zLogin); + fossil_print("admin-user: %s (initial password is \"%s\")\n", + g.zLogin, zPassword); + cache_initialize(); + g.zLogin = 0; + g.userUid = 0; + }else{ + db_open_repository(zRepo); + } + } + } +} + +#if defined(_WIN32) && USE_SEE +/* +** This function attempts to parse a string value in the following +** format: +** +** "%lu:%p:%u" +** +** There are three parts, which must be delimited by colons. The +** first part is an unsigned long integer in base-10 (decimal) format. +** The second part is a numerical representation of a native pointer, +** in the appropriate implementation defined format. The third part +** is an unsigned integer in base-10 (decimal) format. +** +** If the specified value cannot be parsed, for any reason, a fatal +** error will be raised and the process will be terminated. +*/ +void parse_pid_key_value( + const char *zPidKey, /* The value to be parsed. */ + DWORD *pProcessId, /* The extracted process identifier. */ + LPVOID *ppAddress, /* The extracted pointer value. */ + SIZE_T *pnSize /* The extracted size value. */ +){ + unsigned int nSize = 0; + if( sscanf(zPidKey, "%lu:%p:%u", pProcessId, ppAddress, &nSize)==3 ){ + *pnSize = (SIZE_T)nSize; }else{ - db_open_repository(g.argv[2]); + fossil_fatal("failed to parse pid key"); + } +} +#endif + +/* +** WEBPAGE: test-pid +** +** Return the process identifier of the running Fossil server instance. +** +** Query parameters: +** +** usepidkey When present and available, also return the +** address and size, within this server process, +** of the saved database encryption key. This +** is only supported when using SEE on Windows. +*/ +void test_pid_page(void){ + login_check_credentials(); + if( !g.perm.Setup ){ login_needed(0); return; } +#if defined(_WIN32) && USE_SEE + if( P("usepidkey")!=0 ){ + if( g.zPidKey ){ + @ %s(g.zPidKey) + return; + }else{ + const char *zSavedKey = db_get_saved_encryption_key(); + size_t savedKeySize = db_get_saved_encryption_key_size(); + if( zSavedKey!=0 && savedKeySize>0 ){ + @ %lu(GetCurrentProcessId()):%p(zSavedKey):%u(savedKeySize) + return; + } + } } +#endif + @ %d(GETPID()) } /* -** undocumented format: -** -** fossil http REPOSITORY INFILE OUTFILE IPADDR -** -** The argv==6 form is used by the win32 server only. -** -** COMMAND: http -** -** Usage: %fossil http REPOSITORY [--notfound URL] +** COMMAND: http* +** +** Usage: %fossil http ?REPOSITORY? ?OPTIONS? ** ** Handle a single HTTP request appearing on stdin. The resulting webpage ** is delivered on stdout. This method is used to launch an HTTP request -** handler from inetd, for example. The argument is the name of the -** repository. -** -** If REPOSITORY is a directory that contains one or more respositories -** with names of the form "*.fossil" then the first element of the URL -** pathname selects among the various repositories. If the pathname does -** not select a valid repository and the --notfound option is available, -** then the server redirects (HTTP code 302) to the URL of --notfound. -*/ -void cmd_http(void){ - const char *zIpAddr; - const char *zNotFound; - zNotFound = find_option("notfound", 0, 1); - if( g.argc!=2 && g.argc!=3 && g.argc!=6 ){ - cgi_panic("no repository specified"); - } - g.cgiOutput = 1; - g.fullHttpReply = 1; - if( g.argc==6 ){ - g.httpIn = fopen(g.argv[3], "rb"); - g.httpOut = fopen(g.argv[4], "wb"); - zIpAddr = g.argv[5]; - }else{ - g.httpIn = stdin; - g.httpOut = stdout; - zIpAddr = 0; - } - find_server_repository(0); - g.zRepositoryName = enter_chroot_jail(g.zRepositoryName); - cgi_handle_http_request(zIpAddr); - process_one_web_page(zNotFound); -} - -/* -** COMMAND: test-http -** Works like the http command but gives setup permission to all users. -*/ -void cmd_test_http(void){ - login_set_capabilities("s"); - cmd_http(); -} - -#ifndef __MINGW32__ -#if !defined(__DARWIN__) && !defined(__APPLE__) -/* -** Search for an executable on the PATH environment variable. -** Return true (1) if found and false (0) if not found. -*/ -static int binaryOnPath(const char *zBinary){ - const char *zPath = getenv("PATH"); - char *zFull; - int i; - int bExists; - while( zPath && zPath[0] ){ - while( zPath[0]==':' ) zPath++; - for(i=0; zPath[i] && zPath[i]!=':'; i++){} - zFull = mprintf("%.*s/%s", i, zPath, zBinary); - bExists = access(zFull, X_OK); - free(zFull); - if( bExists==0 ) return 1; - zPath += i; - } - return 0; -} -#endif -#endif - -/* -** COMMAND: server -** COMMAND: ui -** -** Usage: %fossil server ?-P|--port TCPPORT? ?REPOSITORY? -** Or: %fossil ui ?-P|--port TCPPORT? ?REPOSITORY? +** handler from inetd, for example. The argument is the name of the +** repository. +** +** If REPOSITORY is a directory that contains one or more repositories, +** either directly in REPOSITORY itself or in subdirectories, and +** with names of the form "*.fossil" then a prefix of the URL pathname +** selects from among the various repositories. If the pathname does +** not select a valid repository and the --notfound option is available, +** then the server redirects (HTTP code 302) to the URL of --notfound. +** When REPOSITORY is a directory, the pathname must contain only +** alphanumerics, "_", "/", "-" and "." and no "-" may occur after a "/" +** and every "." must be surrounded on both sides by alphanumerics or else +** a 404 error is returned. Static content files in the directory are +** returned if they match comma-separate GLOB pattern specified by --files +** and do not match "*.fossil*" and have a well-known suffix. +** +** The --host option can be used to specify the hostname for the server. +** The --https option indicates that the request came from HTTPS rather +** than HTTP. If --nossl is given, then SSL connections will not be available, +** thus also no redirecting from http: to https: will take place. +** +** If the --localauth option is given, then automatic login is performed +** for requests coming from localhost, if the "localauth" setting is not +** enabled. +** +** Options: +** --baseurl URL base URL (useful with reverse proxies) +** --ckout-alias N Treat URIs of the form /doc/N/... as if they were +** /doc/ckout/... +** --extroot DIR document root for the /ext extension mechanism +** --files GLOB comma-separate glob patterns for static file to serve +** --host NAME specify hostname of the server +** --https signal a request coming in via https +** --in FILE Take input from FILE instead of standard input +** --ipaddr ADDR Assume the request comes from the given IP address +** --jsmode MODE Determine how JavaScript is delivered with pages. +** Mode can be one of: +** inline All JavaScript is inserted inline at +** one or more points in the HTML file. +** separate Separate HTTP requests are made for +** each JavaScript file. +** bundled Groups JavaScript files into one or +** more bundled requests which +** concatenate scripts together. +** Depending on the needs of any given page, inline +** and bundled modes might result in a single +** amalgamated script or several, but both approaches +** result in fewer HTTP requests than the separate mode. +** --localauth enable automatic login for local connections +** --nocompress do not compress HTTP replies +** --nodelay omit backoffice processing if it would delay process exit +** --nojail drop root privilege but do not enter the chroot jail +** --nossl signal that no SSL connections are available +** --notfound URL use URL as "HTTP 404, object not found" page. +** --out FILE write results to FILE instead of to standard output +** --repolist If REPOSITORY is directory, URL "/" lists all repos +** --scgi Interpret input as SCGI rather than HTTP +** --skin LABEL Use override skin LABEL +** --th-trace trace TH1 execution (for debugging purposes) +** --mainmenu FILE Override the mainmenu config setting with the contents +** of the given file. +** --usepidkey Use saved encryption key from parent process. This is +** only necessary when using SEE on Windows. +** +** See also: [[cgi]], [[server]], [[winsrv]] +*/ +void cmd_http(void){ + const char *zIpAddr = 0; + const char *zNotFound; + const char *zHost; + const char *zAltBase; + const char *zFileGlob; + const char *zInFile; + const char *zOutFile; + int useSCGI; + int noJail; + int allowRepoList; + + Th_InitTraceLog(); + builtin_set_js_delivery_mode(find_option("jsmode",0,1),0); + + /* The winhttp module passes the --files option as --files-urlenc with + ** the argument being URL encoded, to avoid wildcard expansion in the + ** shell. This option is for internal use and is undocumented. + */ + zFileGlob = find_option("files-urlenc",0,1); + if( zFileGlob ){ + char *z = mprintf("%s", zFileGlob); + dehttpize(z); + zFileGlob = z; + }else{ + zFileGlob = find_option("files",0,1); + } + skin_override(); + zNotFound = find_option("notfound", 0, 1); + noJail = find_option("nojail",0,0)!=0; + allowRepoList = find_option("repolist",0,0)!=0; + g.useLocalauth = find_option("localauth", 0, 0)!=0; + g.sslNotAvailable = find_option("nossl", 0, 0)!=0; + g.fNoHttpCompress = find_option("nocompress",0,0)!=0; + g.zExtRoot = find_option("extroot",0,1); + g.zCkoutAlias = find_option("ckout-alias",0,1); + zInFile = find_option("in",0,1); + if( zInFile ){ + backoffice_disable(); + g.httpIn = fossil_fopen(zInFile, "rb"); + if( g.httpIn==0 ) fossil_fatal("cannot open \"%s\" for reading", zInFile); + }else{ + g.httpIn = stdin; + } + zOutFile = find_option("out",0,1); + if( zOutFile ){ + g.httpOut = fossil_fopen(zOutFile, "wb"); + if( g.httpOut==0 ) fossil_fatal("cannot open \"%s\" for writing", zOutFile); + }else{ + g.httpOut = stdout; + } + zIpAddr = find_option("ipaddr",0,1); + useSCGI = find_option("scgi", 0, 0)!=0; + zAltBase = find_option("baseurl", 0, 1); + if( find_option("nodelay",0,0)!=0 ) backoffice_no_delay(); + if( zAltBase ) set_base_url(zAltBase); + if( find_option("https",0,0)!=0 ){ + zIpAddr = fossil_getenv("REMOTE_HOST"); /* From stunnel */ + cgi_replace_parameter("HTTPS","on"); + } + zHost = find_option("host", 0, 1); + if( zHost ) cgi_replace_parameter("HTTP_HOST",zHost); + g.zMainMenuFile = find_option("mainmenu",0,1); + if( g.zMainMenuFile!=0 && file_size(g.zMainMenuFile,ExtFILE)<0 ){ + fossil_fatal("Cannot read --mainmenu file %s", g.zMainMenuFile); + } + + /* We should be done with options.. */ + verify_all_options(); + + if( g.argc!=2 && g.argc!=3 ) usage("?REPOSITORY?"); + g.cgiOutput = 1; + g.fullHttpReply = 1; + find_server_repository(2, 0); + if( zIpAddr==0 ){ + zIpAddr = cgi_ssh_remote_addr(0); + if( zIpAddr && zIpAddr[0] ){ + g.fSshClient |= CGI_SSH_CLIENT; + } + } + g.zRepositoryName = enter_chroot_jail(g.zRepositoryName, noJail); + if( useSCGI ){ + cgi_handle_scgi_request(); + }else if( g.fSshClient & CGI_SSH_CLIENT ){ + ssh_request_loop(zIpAddr, glob_create(zFileGlob)); + }else{ + cgi_handle_http_request(zIpAddr); + } + process_one_web_page(zNotFound, glob_create(zFileGlob), allowRepoList); +} + +/* +** Process all requests in a single SSH connection if possible. +*/ +void ssh_request_loop(const char *zIpAddr, Glob *FileGlob){ + blob_zero(&g.cgiIn); + do{ + cgi_handle_ssh_http_request(zIpAddr); + process_one_web_page(0, FileGlob, 0); + blob_reset(&g.cgiIn); + } while ( g.fSshClient & CGI_SSH_FOSSIL || + g.fSshClient & CGI_SSH_COMPAT ); +} + +/* +** Note that the following command is used by ssh:// processing. +** +** COMMAND: test-http +** +** Works like the [[http]] command but gives setup permission to all users. +** +** Options: +** --th-trace Trace TH1 execution (for debugging purposes) +** --usercap CAP User capability string (Default: "sx") +** +*/ +void cmd_test_http(void){ + const char *zIpAddr; /* IP address of remote client */ + const char *zUserCap; + + Th_InitTraceLog(); + zUserCap = find_option("usercap",0,1); + if( zUserCap==0 ){ + g.useLocalauth = 1; + zUserCap = "sx"; + } + login_set_capabilities(zUserCap, 0); + g.httpIn = stdin; + g.httpOut = stdout; + fossil_binary_mode(g.httpOut); + fossil_binary_mode(g.httpIn); + g.zExtRoot = find_option("extroot",0,1); + find_server_repository(2, 0); + g.cgiOutput = 1; + g.fNoHttpCompress = 1; + g.fullHttpReply = 1; + g.sslNotAvailable = 1; /* Avoid attempts to redirect */ + zIpAddr = cgi_ssh_remote_addr(0); + if( zIpAddr && zIpAddr[0] ){ + g.fSshClient |= CGI_SSH_CLIENT; + ssh_request_loop(zIpAddr, 0); + }else{ + cgi_set_parameter("REMOTE_ADDR", "127.0.0.1"); + cgi_handle_http_request(0); + process_one_web_page(0, 0, 1); + } +} + +/* +** Respond to a SIGALRM by writing a message to the error log (if there +** is one) and exiting. +*/ +#ifndef _WIN32 +static void sigalrm_handler(int x){ + fossil_panic("TIMEOUT"); +} +#endif + +/* +** Arrange to timeout using SIGALRM after N seconds. Or if N==0, cancel +** any pending timeout. +** +** Bugs: +** (1) This only works on unix systems. +** (2) Any call to sleep() or sqlite3_sleep() will cancel the alarm. +*/ +void fossil_set_timeout(int N){ +#ifndef _WIN32 + signal(SIGALRM, sigalrm_handler); + alarm(N); +#endif +} + +/* +** COMMAND: server* +** COMMAND: ui +** +** Usage: %fossil server ?OPTIONS? ?REPOSITORY? +** or: %fossil ui ?OPTIONS? ?REPOSITORY? ** ** Open a socket and begin listening and responding to HTTP requests on ** TCP port 8080, or on any other TCP port defined by the -P or -** --port option. The optional argument is the name of the repository. -** The repository argument may be omitted if the working directory is -** within an open checkout. +** --port option. The optional REPOSITORY argument is the name of the +** Fossil repository to be served. The REPOSITORY argument may be omitted +** if the working directory is within an open checkout, in which case the +** repository associated with that checkout is used. ** ** The "ui" command automatically starts a web browser after initializing -** the web server. +** the web server. The "ui" command also binds to 127.0.0.1 and so will +** only process HTTP traffic from the local machine. +** +** If REPOSITORY is a directory name which is the root of a +** checkout, then use the repository associated with that checkout. +** This only works for the "fossil ui" command, not the "fossil server" +** command. +** +** If REPOSITORY begins with a "HOST:" or "USER@HOST:" prefix, then +** the command is run on the remote host specified and the results are +** tunneled back to the local machine via SSH. This feature only works for +** the "fossil ui" command, not the "fossil server" command. +** +** REPOSITORY may also be a directory (aka folder) that contains one or +** more repositories with names ending in ".fossil". In this case, a +** prefix of the URL pathname is used to search the directory for an +** appropriate repository. To thwart mischief, the pathname in the URL must +** contain only alphanumerics, "_", "/", "-", and ".", and no "-" may +** occur after "/", and every "." must be surrounded on both sides by +** alphanumerics. Any pathname that does not satisfy these constraints +** results in a 404 error. Files in REPOSITORY that match the comma-separated +** list of glob patterns given by --files and that have known suffixes +** such as ".txt" or ".html" or ".jpeg" and do not match the pattern +** "*.fossil*" will be served as static content. With the "ui" command, +** the REPOSITORY can only be a directory if the --notfound option is +** also present. +** +** For the special case REPOSITORY name of "/", the global configuration +** database is consulted for a list of all known repositories. The --repolist +** option is implied by this special case. See also the "fossil all ui" +** command. +** +** By default, the "ui" command provides full administrative access without +** having to log in. This can be disabled by turning off the "localauth" +** setting. Automatic login for the "server" command is available if the +** --localauth option is present and the "localauth" setting is off and the +** connection is from localhost. The "ui" command also enables --repolist +** by default. +** +** Options: +** --baseurl URL Use URL as the base (useful for reverse proxies) +** --ckout-alias NAME Treat URIs of the form /doc/NAME/... as if they were +** /doc/ckout/... +** --create Create a new REPOSITORY if it does not already exist +** --extroot DIR Document root for the /ext extension mechanism +** --files GLOBLIST Comma-separated list of glob patterns for static files +** --fossilcmd PATH Full pathname of the "fossil" executable on the remote +** system when REPOSITORY is remote. Default: "fossil" +** --localauth enable automatic login for requests from localhost +** --localhost listen on 127.0.0.1 only (always true for "ui") +** --https Indicates that the input is coming through a reverse +** proxy that has already translated HTTPS into HTTP. +** --jsmode MODE Determine how JavaScript is delivered with pages. +** Mode can be one of: +** inline All JavaScript is inserted inline at +** the end of the HTML file. +** separate Separate HTTP requests are made for +** each JavaScript file. +** bundled One single separate HTTP fetches all +** JavaScript concatenated together. +** Depending on the needs of any given page, inline +** and bundled modes might result in a single +** amalgamated script or several, but both approaches +** result in fewer HTTP requests than the separate mode. +** --max-latency N Do not let any single HTTP request run for more than N +** seconds (only works on unix) +** --nobrowser Do not automatically launch a web-browser for the +** "fossil ui" command. +** --nocompress Do not compress HTTP replies +** --nojail Drop root privileges but do not enter the chroot jail +** --nossl signal that no SSL connections are available (Always +** set by default for the "ui" command) +** --notfound URL Redirect +** --page PAGE Start "ui" on PAGE. ex: --page "timeline?y=ci" +** -P|--port TCPPORT listen to request on port TCPPORT +** --th-trace trace TH1 execution (for debugging purposes) +** --repolist If REPOSITORY is dir, URL "/" lists repos. +** --scgi Accept SCGI rather than HTTP +** --skin LABEL Use override skin LABEL +** --mainmenu FILE Override the mainmenu config setting with the contents +** of the given file. +** --usepidkey Use saved encryption key from parent process. This is +** only necessary when using SEE on Windows. ** -** In the "server" command, the REPOSITORY can be a directory (aka folder) -** that contains one or more respositories with names ending in ".fossil". -** In that case, the first element of the URL is used to select among the -** various repositories. +** See also: [[cgi]], [[http]], [[winsrv]] */ void cmd_webserver(void){ int iPort, mxPort; /* Range of TCP ports allowed */ const char *zPort; /* Value of the --port option */ - char *zBrowser; /* Name of web browser program */ + const char *zBrowser; /* Name of web browser program */ char *zBrowserCmd = 0; /* Command to launch the web browser */ int isUiCmd; /* True if command is "ui", not "server' */ const char *zNotFound; /* The --notfound option or NULL */ + int flags = 0; /* Server flags */ +#if !defined(_WIN32) + int noJail; /* Do not enter the chroot jail */ + const char *zTimeout = 0; /* Max runtime of any single HTTP request */ +#endif + int allowRepoList; /* List repositories on URL "/" */ + const char *zAltBase; /* Argument to the --baseurl option */ + const char *zFileGlob; /* Static content must match this */ + char *zIpAddr = 0; /* Bind to this IP address */ + int fCreate = 0; /* The --create flag */ + int fNoBrowser = 0; /* Do not auto-launch web-browser */ + const char *zInitPage = 0; /* Start on this page. --page option */ + int findServerArg = 2; /* argv index for find_server_repository() */ + char *zRemote = 0; /* Remote host on which to run "fossil ui" */ + const char *zJsMode; /* The --jsmode parameter */ + const char *zFossilCmd =0; /* Name of "fossil" binary on remote system */ + -#ifdef __MINGW32__ +#if defined(_WIN32) const char *zStopperFile; /* Name of file used to terminate server */ zStopperFile = find_option("stopper", 0, 1); #endif - g.thTrace = find_option("th-trace", 0, 0)!=0; - if( g.thTrace ){ - blob_zero(&g.thLog); + if( g.zErrlog==0 ){ + g.zErrlog = "-"; + } + g.zExtRoot = find_option("extroot",0,1); + zJsMode = find_option("jsmode",0,1); + builtin_set_js_delivery_mode(zJsMode,0); + zFileGlob = find_option("files-urlenc",0,1); + if( zFileGlob ){ + char *z = mprintf("%s", zFileGlob); + dehttpize(z); + zFileGlob = z; + }else{ + zFileGlob = find_option("files",0,1); } + skin_override(); +#if !defined(_WIN32) + noJail = find_option("nojail",0,0)!=0; + zTimeout = find_option("max-latency",0,1); +#endif + g.useLocalauth = find_option("localauth", 0, 0)!=0; + Th_InitTraceLog(); zPort = find_option("port", "P", 1); - zNotFound = find_option("notfound", 0, 1); - if( g.argc!=2 && g.argc!=3 ) usage("?REPOSITORY?"); isUiCmd = g.argv[1][0]=='u'; - find_server_repository(isUiCmd); + if( isUiCmd ){ + zInitPage = find_option("page", 0, 1); + zFossilCmd = find_option("fossilcmd", 0, 1); + } + zNotFound = find_option("notfound", 0, 1); + allowRepoList = find_option("repolist",0,0)!=0; + if( find_option("nocompress",0,0)!=0 ) g.fNoHttpCompress = 1; + zAltBase = find_option("baseurl", 0, 1); + fCreate = find_option("create",0,0)!=0; + if( find_option("scgi", 0, 0)!=0 ) flags |= HTTP_SERVER_SCGI; + if( zAltBase ){ + set_base_url(zAltBase); + } + g.sslNotAvailable = find_option("nossl", 0, 0)!=0 || isUiCmd; + fNoBrowser = find_option("nobrowser", 0, 0)!=0; + if( find_option("https",0,0)!=0 ){ + cgi_replace_parameter("HTTPS","on"); + } + if( find_option("localhost", 0, 0)!=0 ){ + flags |= HTTP_SERVER_LOCALHOST; + } + g.zCkoutAlias = find_option("ckout-alias",0,1); + g.zMainMenuFile = find_option("mainmenu",0,1); + if( g.zMainMenuFile!=0 && file_size(g.zMainMenuFile,ExtFILE)<0 ){ + fossil_fatal("Cannot read --mainmenu file %s", g.zMainMenuFile); + } + /* We should be done with options.. */ + verify_all_options(); + + if( g.argc!=2 && g.argc!=3 ) usage("?REPOSITORY?"); + if( isUiCmd && 3==g.argc && file_isdir(g.argv[2], ExtFILE)>0 ){ + /* If REPOSITORY arg is the root of a checkout, + ** chdir to that checkout so that the current version + ** gets highlighted in the timeline by default. */ + const char * zDir = g.argv[2]; + if(dir_has_ckout_db(zDir)){ + if(0!=file_chdir(zDir, 0)){ + fossil_fatal("Cannot chdir to %s", zDir); + } + findServerArg = 99; + fCreate = 0; + g.argv[2] = 0; + --g.argc; + } + } + if( isUiCmd && 3==g.argc + && (zRemote = (char*)file_skip_userhost(g.argv[2]))!=0 + ){ + /* The REPOSITORY argument has a USER@HOST: or HOST: prefix */ + const char *zRepoTail = file_skip_userhost(g.argv[2]); + unsigned x; + int n; + sqlite3_randomness(2,&x); + zPort = mprintf("%d", 8100+(x%32000)); + n = (int)(zRepoTail - g.argv[2]) - 1; + zRemote = mprintf("%.*s", n, g.argv[2]); + g.argv[2] = (char*)zRepoTail; + } + if( isUiCmd ){ + flags |= HTTP_SERVER_LOCALHOST|HTTP_SERVER_REPOLIST; + g.useLocalauth = 1; + allowRepoList = 1; + } + if( !zRemote ){ + find_server_repository(findServerArg, fCreate); + } + if( zInitPage==0 ){ + if( isUiCmd && g.localOpen ){ + zInitPage = "timeline?c=current"; + }else{ + zInitPage = ""; + } + } if( zPort ){ + if( strchr(zPort,':') ){ + int i; + for(i=strlen(zPort)-1; i>=0 && zPort[i]!=':'; i--){} + if( i>0 ){ + if( zPort[0]=='[' && zPort[i-1]==']' ){ + zIpAddr = mprintf("%.*s", i-2, zPort+1); + }else{ + zIpAddr = mprintf("%.*s", i, zPort); + } + zPort += i+1; + } + } iPort = mxPort = atoi(zPort); }else{ iPort = db_get_int("http-port", 8080); mxPort = iPort+100; } -#ifndef __MINGW32__ - /* Unix implementation */ - if( isUiCmd ){ -#if !defined(__DARWIN__) && !defined(__APPLE__) - zBrowser = db_get("web-browser", 0); - if( zBrowser==0 ){ - static char *azBrowserProg[] = { "xdg-open", "gnome-open", "firefox" }; - int i; - zBrowser = "echo"; - for(i=0; i4 ){ + @

    Generate a message to the error log + @ by clicking on one of the following cases: + }else{ + @

    This is the test page for case=%d(iCase). All possible cases: + } + for(i=1; i<=7; i++){ + @ [%d(i)] + } + @

    + @

      + @
    1. Call fossil_warning() + if( iCase==1 ){ + fossil_warning("Test warning message from /test-warning"); + } + @
    2. Call db_begin_transaction() + if( iCase==2 ){ + db_begin_transaction(); + } + @
    3. Call db_end_transaction() + if( iCase==3 ){ + db_end_transaction(0); + } + @
    4. warning during SQL + if( iCase==4 ){ + Stmt q; + db_prepare(&q, "SELECT uuid FROM blob LIMIT 5"); + db_step(&q); + sqlite3_log(SQLITE_ERROR, "Test warning message during SQL"); + db_finalize(&q); + } + @
    5. simulate segfault handling + if( iCase==5 ){ + sigsegv_handler(0); + } + @
    6. call webpage_assert(0) + if( iCase==6 ){ + webpage_assert( 5==7 ); + } + @
    7. call webpage_error()" + if( iCase==7 ){ + cgi_reset_content(); + webpage_error("Case 7 from /test-warning"); + } + @
    + @

    End of test

    + style_finish_page(); +} Index: src/main.mk ================================================================== --- src/main.mk +++ src/main.mk @@ -1,772 +1,2137 @@ -# DO NOT EDIT +# +############################################################################## +# WARNING: DO NOT EDIT, AUTOMATICALLY GENERATED FILE (SEE "src/makemake.tcl") +############################################################################## # # This file is automatically generated. Instead of editing this -# file, edit "makemake.tcl" then run "tclsh makemake.tcl >main.mk" +# file, edit "makemake.tcl" then run "tclsh makemake.tcl" # to regenerate this file. # -# This file is included by linux-gcc.mk or linux-mingw.mk or possible -# some other makefiles. This file contains the rules that are common -# to building regardless of the target. +# This file is included by primary Makefile. # -XTCC = $(TCC) $(CFLAGS) -I. -I$(SRCDIR) +XBCC = $(BCC) $(BCCFLAGS) +XTCC = $(TCC) -I. -I$(SRCDIR) -I$(OBJDIR) $(TCCFLAGS) +TESTFLAGS := -quiet SRC = \ $(SRCDIR)/add.c \ + $(SRCDIR)/ajax.c \ + $(SRCDIR)/alerts.c \ $(SRCDIR)/allrepo.c \ $(SRCDIR)/attach.c \ + $(SRCDIR)/backlink.c \ + $(SRCDIR)/backoffice.c \ $(SRCDIR)/bag.c \ + $(SRCDIR)/bisect.c \ $(SRCDIR)/blob.c \ $(SRCDIR)/branch.c \ $(SRCDIR)/browse.c \ + $(SRCDIR)/builtin.c \ + $(SRCDIR)/bundle.c \ + $(SRCDIR)/cache.c \ + $(SRCDIR)/capabilities.c \ $(SRCDIR)/captcha.c \ $(SRCDIR)/cgi.c \ + $(SRCDIR)/chat.c \ $(SRCDIR)/checkin.c \ $(SRCDIR)/checkout.c \ $(SRCDIR)/clearsign.c \ $(SRCDIR)/clone.c \ + $(SRCDIR)/color.c \ $(SRCDIR)/comformat.c \ $(SRCDIR)/configure.c \ $(SRCDIR)/content.c \ + $(SRCDIR)/cookies.c \ $(SRCDIR)/db.c \ $(SRCDIR)/delta.c \ $(SRCDIR)/deltacmd.c \ + $(SRCDIR)/deltafunc.c \ $(SRCDIR)/descendants.c \ $(SRCDIR)/diff.c \ $(SRCDIR)/diffcmd.c \ + $(SRCDIR)/dispatch.c \ $(SRCDIR)/doc.c \ $(SRCDIR)/encode.c \ + $(SRCDIR)/etag.c \ + $(SRCDIR)/event.c \ + $(SRCDIR)/export.c \ + $(SRCDIR)/extcgi.c \ $(SRCDIR)/file.c \ + $(SRCDIR)/fileedit.c \ $(SRCDIR)/finfo.c \ + $(SRCDIR)/foci.c \ + $(SRCDIR)/forum.c \ + $(SRCDIR)/fshell.c \ + $(SRCDIR)/fusefs.c \ + $(SRCDIR)/fuzz.c \ + $(SRCDIR)/glob.c \ $(SRCDIR)/graph.c \ + $(SRCDIR)/gzip.c \ + $(SRCDIR)/hname.c \ + $(SRCDIR)/hook.c \ $(SRCDIR)/http.c \ $(SRCDIR)/http_socket.c \ + $(SRCDIR)/http_ssl.c \ $(SRCDIR)/http_transport.c \ + $(SRCDIR)/import.c \ $(SRCDIR)/info.c \ + $(SRCDIR)/interwiki.c \ + $(SRCDIR)/json.c \ + $(SRCDIR)/json_artifact.c \ + $(SRCDIR)/json_branch.c \ + $(SRCDIR)/json_config.c \ + $(SRCDIR)/json_diff.c \ + $(SRCDIR)/json_dir.c \ + $(SRCDIR)/json_finfo.c \ + $(SRCDIR)/json_login.c \ + $(SRCDIR)/json_query.c \ + $(SRCDIR)/json_report.c \ + $(SRCDIR)/json_status.c \ + $(SRCDIR)/json_tag.c \ + $(SRCDIR)/json_timeline.c \ + $(SRCDIR)/json_user.c \ + $(SRCDIR)/json_wiki.c \ + $(SRCDIR)/leaf.c \ + $(SRCDIR)/loadctrl.c \ $(SRCDIR)/login.c \ + $(SRCDIR)/lookslike.c \ $(SRCDIR)/main.c \ $(SRCDIR)/manifest.c \ + $(SRCDIR)/markdown.c \ + $(SRCDIR)/markdown_html.c \ $(SRCDIR)/md5.c \ $(SRCDIR)/merge.c \ $(SRCDIR)/merge3.c \ + $(SRCDIR)/moderate.c \ $(SRCDIR)/name.c \ + $(SRCDIR)/patch.c \ + $(SRCDIR)/path.c \ + $(SRCDIR)/piechart.c \ + $(SRCDIR)/pikchr.c \ + $(SRCDIR)/pikchrshow.c \ $(SRCDIR)/pivot.c \ + $(SRCDIR)/popen.c \ $(SRCDIR)/pqueue.c \ $(SRCDIR)/printf.c \ + $(SRCDIR)/publish.c \ + $(SRCDIR)/purge.c \ $(SRCDIR)/rebuild.c \ + $(SRCDIR)/regexp.c \ + $(SRCDIR)/repolist.c \ $(SRCDIR)/report.c \ $(SRCDIR)/rss.c \ $(SRCDIR)/schema.c \ $(SRCDIR)/search.c \ + $(SRCDIR)/security_audit.c \ $(SRCDIR)/setup.c \ + $(SRCDIR)/setupuser.c \ $(SRCDIR)/sha1.c \ + $(SRCDIR)/sha1hard.c \ + $(SRCDIR)/sha3.c \ $(SRCDIR)/shun.c \ + $(SRCDIR)/sitemap.c \ $(SRCDIR)/skins.c \ + $(SRCDIR)/smtp.c \ + $(SRCDIR)/sqlcmd.c \ + $(SRCDIR)/stash.c \ $(SRCDIR)/stat.c \ + $(SRCDIR)/statrep.c \ $(SRCDIR)/style.c \ $(SRCDIR)/sync.c \ $(SRCDIR)/tag.c \ + $(SRCDIR)/tar.c \ + $(SRCDIR)/terminal.c \ $(SRCDIR)/th_main.c \ $(SRCDIR)/timeline.c \ $(SRCDIR)/tkt.c \ $(SRCDIR)/tktsetup.c \ $(SRCDIR)/undo.c \ + $(SRCDIR)/unicode.c \ + $(SRCDIR)/unversioned.c \ $(SRCDIR)/update.c \ $(SRCDIR)/url.c \ $(SRCDIR)/user.c \ + $(SRCDIR)/utf8.c \ + $(SRCDIR)/util.c \ $(SRCDIR)/verify.c \ $(SRCDIR)/vfile.c \ $(SRCDIR)/wiki.c \ $(SRCDIR)/wikiformat.c \ + $(SRCDIR)/winfile.c \ $(SRCDIR)/winhttp.c \ $(SRCDIR)/xfer.c \ + $(SRCDIR)/xfersetup.c \ $(SRCDIR)/zip.c +EXTRA_FILES = \ + $(SRCDIR)/../skins/ardoise/css.txt \ + $(SRCDIR)/../skins/ardoise/details.txt \ + $(SRCDIR)/../skins/ardoise/footer.txt \ + $(SRCDIR)/../skins/ardoise/header.txt \ + $(SRCDIR)/../skins/black_and_white/css.txt \ + $(SRCDIR)/../skins/black_and_white/details.txt \ + $(SRCDIR)/../skins/black_and_white/footer.txt \ + $(SRCDIR)/../skins/black_and_white/header.txt \ + $(SRCDIR)/../skins/blitz/css.txt \ + $(SRCDIR)/../skins/blitz/details.txt \ + $(SRCDIR)/../skins/blitz/footer.txt \ + $(SRCDIR)/../skins/blitz/header.txt \ + $(SRCDIR)/../skins/blitz/ticket.txt \ + $(SRCDIR)/../skins/bootstrap/css.txt \ + $(SRCDIR)/../skins/bootstrap/details.txt \ + $(SRCDIR)/../skins/bootstrap/footer.txt \ + $(SRCDIR)/../skins/bootstrap/header.txt \ + $(SRCDIR)/../skins/darkmode/css.txt \ + $(SRCDIR)/../skins/darkmode/details.txt \ + $(SRCDIR)/../skins/darkmode/footer.txt \ + $(SRCDIR)/../skins/darkmode/header.txt \ + $(SRCDIR)/../skins/default/css.txt \ + $(SRCDIR)/../skins/default/details.txt \ + $(SRCDIR)/../skins/default/footer.txt \ + $(SRCDIR)/../skins/default/header.txt \ + $(SRCDIR)/../skins/eagle/css.txt \ + $(SRCDIR)/../skins/eagle/details.txt \ + $(SRCDIR)/../skins/eagle/footer.txt \ + $(SRCDIR)/../skins/eagle/header.txt \ + $(SRCDIR)/../skins/khaki/css.txt \ + $(SRCDIR)/../skins/khaki/details.txt \ + $(SRCDIR)/../skins/khaki/footer.txt \ + $(SRCDIR)/../skins/khaki/header.txt \ + $(SRCDIR)/../skins/original/css.txt \ + $(SRCDIR)/../skins/original/details.txt \ + $(SRCDIR)/../skins/original/footer.txt \ + $(SRCDIR)/../skins/original/header.txt \ + $(SRCDIR)/../skins/plain_gray/css.txt \ + $(SRCDIR)/../skins/plain_gray/details.txt \ + $(SRCDIR)/../skins/plain_gray/footer.txt \ + $(SRCDIR)/../skins/plain_gray/header.txt \ + $(SRCDIR)/../skins/xekri/css.txt \ + $(SRCDIR)/../skins/xekri/details.txt \ + $(SRCDIR)/../skins/xekri/footer.txt \ + $(SRCDIR)/../skins/xekri/header.txt \ + $(SRCDIR)/accordion.js \ + $(SRCDIR)/alerts/bflat2.wav \ + $(SRCDIR)/alerts/bflat3.wav \ + $(SRCDIR)/alerts/bloop.wav \ + $(SRCDIR)/alerts/plunk.wav \ + $(SRCDIR)/chat.js \ + $(SRCDIR)/ci_edit.js \ + $(SRCDIR)/copybtn.js \ + $(SRCDIR)/default.css \ + $(SRCDIR)/diff.tcl \ + $(SRCDIR)/forum.js \ + $(SRCDIR)/fossil.bootstrap.js \ + $(SRCDIR)/fossil.confirmer.js \ + $(SRCDIR)/fossil.copybutton.js \ + $(SRCDIR)/fossil.dom.js \ + $(SRCDIR)/fossil.fetch.js \ + $(SRCDIR)/fossil.info-diff.js \ + $(SRCDIR)/fossil.numbered-lines.js \ + $(SRCDIR)/fossil.page.brlist.js \ + $(SRCDIR)/fossil.page.fileedit.js \ + $(SRCDIR)/fossil.page.forumpost.js \ + $(SRCDIR)/fossil.page.pikchrshow.js \ + $(SRCDIR)/fossil.page.whistory.js \ + $(SRCDIR)/fossil.page.wikiedit.js \ + $(SRCDIR)/fossil.pikchr.js \ + $(SRCDIR)/fossil.popupwidget.js \ + $(SRCDIR)/fossil.storage.js \ + $(SRCDIR)/fossil.tabs.js \ + $(SRCDIR)/fossil.wikiedit-wysiwyg.js \ + $(SRCDIR)/graph.js \ + $(SRCDIR)/hbmenu.js \ + $(SRCDIR)/href.js \ + $(SRCDIR)/login.js \ + $(SRCDIR)/markdown.md \ + $(SRCDIR)/menu.js \ + $(SRCDIR)/sbsdiff.js \ + $(SRCDIR)/scroll.js \ + $(SRCDIR)/skin.js \ + $(SRCDIR)/sorttable.js \ + $(SRCDIR)/sounds/0.wav \ + $(SRCDIR)/sounds/1.wav \ + $(SRCDIR)/sounds/2.wav \ + $(SRCDIR)/sounds/3.wav \ + $(SRCDIR)/sounds/4.wav \ + $(SRCDIR)/sounds/5.wav \ + $(SRCDIR)/sounds/6.wav \ + $(SRCDIR)/sounds/7.wav \ + $(SRCDIR)/sounds/8.wav \ + $(SRCDIR)/sounds/9.wav \ + $(SRCDIR)/sounds/a.wav \ + $(SRCDIR)/sounds/b.wav \ + $(SRCDIR)/sounds/c.wav \ + $(SRCDIR)/sounds/d.wav \ + $(SRCDIR)/sounds/e.wav \ + $(SRCDIR)/sounds/f.wav \ + $(SRCDIR)/style.admin_log.css \ + $(SRCDIR)/style.fileedit.css \ + $(SRCDIR)/style.wikiedit.css \ + $(SRCDIR)/tree.js \ + $(SRCDIR)/useredit.js \ + $(SRCDIR)/wiki.wiki + TRANS_SRC = \ - add_.c \ - allrepo_.c \ - attach_.c \ - bag_.c \ - blob_.c \ - branch_.c \ - browse_.c \ - captcha_.c \ - cgi_.c \ - checkin_.c \ - checkout_.c \ - clearsign_.c \ - clone_.c \ - comformat_.c \ - configure_.c \ - content_.c \ - db_.c \ - delta_.c \ - deltacmd_.c \ - descendants_.c \ - diff_.c \ - diffcmd_.c \ - doc_.c \ - encode_.c \ - file_.c \ - finfo_.c \ - graph_.c \ - http_.c \ - http_socket_.c \ - http_transport_.c \ - info_.c \ - login_.c \ - main_.c \ - manifest_.c \ - md5_.c \ - merge_.c \ - merge3_.c \ - name_.c \ - pivot_.c \ - pqueue_.c \ - printf_.c \ - rebuild_.c \ - report_.c \ - rss_.c \ - schema_.c \ - search_.c \ - setup_.c \ - sha1_.c \ - shun_.c \ - skins_.c \ - stat_.c \ - style_.c \ - sync_.c \ - tag_.c \ - th_main_.c \ - timeline_.c \ - tkt_.c \ - tktsetup_.c \ - undo_.c \ - update_.c \ - url_.c \ - user_.c \ - verify_.c \ - vfile_.c \ - wiki_.c \ - wikiformat_.c \ - winhttp_.c \ - xfer_.c \ - zip_.c + $(OBJDIR)/add_.c \ + $(OBJDIR)/ajax_.c \ + $(OBJDIR)/alerts_.c \ + $(OBJDIR)/allrepo_.c \ + $(OBJDIR)/attach_.c \ + $(OBJDIR)/backlink_.c \ + $(OBJDIR)/backoffice_.c \ + $(OBJDIR)/bag_.c \ + $(OBJDIR)/bisect_.c \ + $(OBJDIR)/blob_.c \ + $(OBJDIR)/branch_.c \ + $(OBJDIR)/browse_.c \ + $(OBJDIR)/builtin_.c \ + $(OBJDIR)/bundle_.c \ + $(OBJDIR)/cache_.c \ + $(OBJDIR)/capabilities_.c \ + $(OBJDIR)/captcha_.c \ + $(OBJDIR)/cgi_.c \ + $(OBJDIR)/chat_.c \ + $(OBJDIR)/checkin_.c \ + $(OBJDIR)/checkout_.c \ + $(OBJDIR)/clearsign_.c \ + $(OBJDIR)/clone_.c \ + $(OBJDIR)/color_.c \ + $(OBJDIR)/comformat_.c \ + $(OBJDIR)/configure_.c \ + $(OBJDIR)/content_.c \ + $(OBJDIR)/cookies_.c \ + $(OBJDIR)/db_.c \ + $(OBJDIR)/delta_.c \ + $(OBJDIR)/deltacmd_.c \ + $(OBJDIR)/deltafunc_.c \ + $(OBJDIR)/descendants_.c \ + $(OBJDIR)/diff_.c \ + $(OBJDIR)/diffcmd_.c \ + $(OBJDIR)/dispatch_.c \ + $(OBJDIR)/doc_.c \ + $(OBJDIR)/encode_.c \ + $(OBJDIR)/etag_.c \ + $(OBJDIR)/event_.c \ + $(OBJDIR)/export_.c \ + $(OBJDIR)/extcgi_.c \ + $(OBJDIR)/file_.c \ + $(OBJDIR)/fileedit_.c \ + $(OBJDIR)/finfo_.c \ + $(OBJDIR)/foci_.c \ + $(OBJDIR)/forum_.c \ + $(OBJDIR)/fshell_.c \ + $(OBJDIR)/fusefs_.c \ + $(OBJDIR)/fuzz_.c \ + $(OBJDIR)/glob_.c \ + $(OBJDIR)/graph_.c \ + $(OBJDIR)/gzip_.c \ + $(OBJDIR)/hname_.c \ + $(OBJDIR)/hook_.c \ + $(OBJDIR)/http_.c \ + $(OBJDIR)/http_socket_.c \ + $(OBJDIR)/http_ssl_.c \ + $(OBJDIR)/http_transport_.c \ + $(OBJDIR)/import_.c \ + $(OBJDIR)/info_.c \ + $(OBJDIR)/interwiki_.c \ + $(OBJDIR)/json_.c \ + $(OBJDIR)/json_artifact_.c \ + $(OBJDIR)/json_branch_.c \ + $(OBJDIR)/json_config_.c \ + $(OBJDIR)/json_diff_.c \ + $(OBJDIR)/json_dir_.c \ + $(OBJDIR)/json_finfo_.c \ + $(OBJDIR)/json_login_.c \ + $(OBJDIR)/json_query_.c \ + $(OBJDIR)/json_report_.c \ + $(OBJDIR)/json_status_.c \ + $(OBJDIR)/json_tag_.c \ + $(OBJDIR)/json_timeline_.c \ + $(OBJDIR)/json_user_.c \ + $(OBJDIR)/json_wiki_.c \ + $(OBJDIR)/leaf_.c \ + $(OBJDIR)/loadctrl_.c \ + $(OBJDIR)/login_.c \ + $(OBJDIR)/lookslike_.c \ + $(OBJDIR)/main_.c \ + $(OBJDIR)/manifest_.c \ + $(OBJDIR)/markdown_.c \ + $(OBJDIR)/markdown_html_.c \ + $(OBJDIR)/md5_.c \ + $(OBJDIR)/merge_.c \ + $(OBJDIR)/merge3_.c \ + $(OBJDIR)/moderate_.c \ + $(OBJDIR)/name_.c \ + $(OBJDIR)/patch_.c \ + $(OBJDIR)/path_.c \ + $(OBJDIR)/piechart_.c \ + $(OBJDIR)/pikchr_.c \ + $(OBJDIR)/pikchrshow_.c \ + $(OBJDIR)/pivot_.c \ + $(OBJDIR)/popen_.c \ + $(OBJDIR)/pqueue_.c \ + $(OBJDIR)/printf_.c \ + $(OBJDIR)/publish_.c \ + $(OBJDIR)/purge_.c \ + $(OBJDIR)/rebuild_.c \ + $(OBJDIR)/regexp_.c \ + $(OBJDIR)/repolist_.c \ + $(OBJDIR)/report_.c \ + $(OBJDIR)/rss_.c \ + $(OBJDIR)/schema_.c \ + $(OBJDIR)/search_.c \ + $(OBJDIR)/security_audit_.c \ + $(OBJDIR)/setup_.c \ + $(OBJDIR)/setupuser_.c \ + $(OBJDIR)/sha1_.c \ + $(OBJDIR)/sha1hard_.c \ + $(OBJDIR)/sha3_.c \ + $(OBJDIR)/shun_.c \ + $(OBJDIR)/sitemap_.c \ + $(OBJDIR)/skins_.c \ + $(OBJDIR)/smtp_.c \ + $(OBJDIR)/sqlcmd_.c \ + $(OBJDIR)/stash_.c \ + $(OBJDIR)/stat_.c \ + $(OBJDIR)/statrep_.c \ + $(OBJDIR)/style_.c \ + $(OBJDIR)/sync_.c \ + $(OBJDIR)/tag_.c \ + $(OBJDIR)/tar_.c \ + $(OBJDIR)/terminal_.c \ + $(OBJDIR)/th_main_.c \ + $(OBJDIR)/timeline_.c \ + $(OBJDIR)/tkt_.c \ + $(OBJDIR)/tktsetup_.c \ + $(OBJDIR)/undo_.c \ + $(OBJDIR)/unicode_.c \ + $(OBJDIR)/unversioned_.c \ + $(OBJDIR)/update_.c \ + $(OBJDIR)/url_.c \ + $(OBJDIR)/user_.c \ + $(OBJDIR)/utf8_.c \ + $(OBJDIR)/util_.c \ + $(OBJDIR)/verify_.c \ + $(OBJDIR)/vfile_.c \ + $(OBJDIR)/wiki_.c \ + $(OBJDIR)/wikiformat_.c \ + $(OBJDIR)/winfile_.c \ + $(OBJDIR)/winhttp_.c \ + $(OBJDIR)/xfer_.c \ + $(OBJDIR)/xfersetup_.c \ + $(OBJDIR)/zip_.c OBJ = \ $(OBJDIR)/add.o \ + $(OBJDIR)/ajax.o \ + $(OBJDIR)/alerts.o \ $(OBJDIR)/allrepo.o \ $(OBJDIR)/attach.o \ + $(OBJDIR)/backlink.o \ + $(OBJDIR)/backoffice.o \ $(OBJDIR)/bag.o \ + $(OBJDIR)/bisect.o \ $(OBJDIR)/blob.o \ $(OBJDIR)/branch.o \ $(OBJDIR)/browse.o \ + $(OBJDIR)/builtin.o \ + $(OBJDIR)/bundle.o \ + $(OBJDIR)/cache.o \ + $(OBJDIR)/capabilities.o \ $(OBJDIR)/captcha.o \ $(OBJDIR)/cgi.o \ + $(OBJDIR)/chat.o \ $(OBJDIR)/checkin.o \ $(OBJDIR)/checkout.o \ $(OBJDIR)/clearsign.o \ $(OBJDIR)/clone.o \ + $(OBJDIR)/color.o \ $(OBJDIR)/comformat.o \ $(OBJDIR)/configure.o \ $(OBJDIR)/content.o \ + $(OBJDIR)/cookies.o \ $(OBJDIR)/db.o \ $(OBJDIR)/delta.o \ $(OBJDIR)/deltacmd.o \ + $(OBJDIR)/deltafunc.o \ $(OBJDIR)/descendants.o \ $(OBJDIR)/diff.o \ $(OBJDIR)/diffcmd.o \ + $(OBJDIR)/dispatch.o \ $(OBJDIR)/doc.o \ $(OBJDIR)/encode.o \ + $(OBJDIR)/etag.o \ + $(OBJDIR)/event.o \ + $(OBJDIR)/export.o \ + $(OBJDIR)/extcgi.o \ $(OBJDIR)/file.o \ + $(OBJDIR)/fileedit.o \ $(OBJDIR)/finfo.o \ + $(OBJDIR)/foci.o \ + $(OBJDIR)/forum.o \ + $(OBJDIR)/fshell.o \ + $(OBJDIR)/fusefs.o \ + $(OBJDIR)/fuzz.o \ + $(OBJDIR)/glob.o \ $(OBJDIR)/graph.o \ + $(OBJDIR)/gzip.o \ + $(OBJDIR)/hname.o \ + $(OBJDIR)/hook.o \ $(OBJDIR)/http.o \ $(OBJDIR)/http_socket.o \ + $(OBJDIR)/http_ssl.o \ $(OBJDIR)/http_transport.o \ + $(OBJDIR)/import.o \ $(OBJDIR)/info.o \ + $(OBJDIR)/interwiki.o \ + $(OBJDIR)/json.o \ + $(OBJDIR)/json_artifact.o \ + $(OBJDIR)/json_branch.o \ + $(OBJDIR)/json_config.o \ + $(OBJDIR)/json_diff.o \ + $(OBJDIR)/json_dir.o \ + $(OBJDIR)/json_finfo.o \ + $(OBJDIR)/json_login.o \ + $(OBJDIR)/json_query.o \ + $(OBJDIR)/json_report.o \ + $(OBJDIR)/json_status.o \ + $(OBJDIR)/json_tag.o \ + $(OBJDIR)/json_timeline.o \ + $(OBJDIR)/json_user.o \ + $(OBJDIR)/json_wiki.o \ + $(OBJDIR)/leaf.o \ + $(OBJDIR)/loadctrl.o \ $(OBJDIR)/login.o \ + $(OBJDIR)/lookslike.o \ $(OBJDIR)/main.o \ $(OBJDIR)/manifest.o \ + $(OBJDIR)/markdown.o \ + $(OBJDIR)/markdown_html.o \ $(OBJDIR)/md5.o \ $(OBJDIR)/merge.o \ $(OBJDIR)/merge3.o \ + $(OBJDIR)/moderate.o \ $(OBJDIR)/name.o \ + $(OBJDIR)/patch.o \ + $(OBJDIR)/path.o \ + $(OBJDIR)/piechart.o \ + $(OBJDIR)/pikchr.o \ + $(OBJDIR)/pikchrshow.o \ $(OBJDIR)/pivot.o \ + $(OBJDIR)/popen.o \ $(OBJDIR)/pqueue.o \ $(OBJDIR)/printf.o \ + $(OBJDIR)/publish.o \ + $(OBJDIR)/purge.o \ $(OBJDIR)/rebuild.o \ + $(OBJDIR)/regexp.o \ + $(OBJDIR)/repolist.o \ $(OBJDIR)/report.o \ $(OBJDIR)/rss.o \ $(OBJDIR)/schema.o \ $(OBJDIR)/search.o \ + $(OBJDIR)/security_audit.o \ $(OBJDIR)/setup.o \ + $(OBJDIR)/setupuser.o \ $(OBJDIR)/sha1.o \ + $(OBJDIR)/sha1hard.o \ + $(OBJDIR)/sha3.o \ $(OBJDIR)/shun.o \ + $(OBJDIR)/sitemap.o \ $(OBJDIR)/skins.o \ + $(OBJDIR)/smtp.o \ + $(OBJDIR)/sqlcmd.o \ + $(OBJDIR)/stash.o \ $(OBJDIR)/stat.o \ + $(OBJDIR)/statrep.o \ $(OBJDIR)/style.o \ $(OBJDIR)/sync.o \ $(OBJDIR)/tag.o \ + $(OBJDIR)/tar.o \ + $(OBJDIR)/terminal.o \ $(OBJDIR)/th_main.o \ $(OBJDIR)/timeline.o \ $(OBJDIR)/tkt.o \ $(OBJDIR)/tktsetup.o \ $(OBJDIR)/undo.o \ + $(OBJDIR)/unicode.o \ + $(OBJDIR)/unversioned.o \ $(OBJDIR)/update.o \ $(OBJDIR)/url.o \ $(OBJDIR)/user.o \ + $(OBJDIR)/utf8.o \ + $(OBJDIR)/util.o \ $(OBJDIR)/verify.o \ $(OBJDIR)/vfile.o \ $(OBJDIR)/wiki.o \ $(OBJDIR)/wikiformat.o \ + $(OBJDIR)/winfile.o \ $(OBJDIR)/winhttp.o \ $(OBJDIR)/xfer.o \ + $(OBJDIR)/xfersetup.o \ $(OBJDIR)/zip.o - -APPNAME = fossil$(E) - - - all: $(OBJDIR) $(APPNAME) -install: $(APPNAME) - mv $(APPNAME) $(INSTALLDIR) +install: all + mkdir -p $(INSTALLDIR) + cp $(APPNAME) $(INSTALLDIR) + +codecheck: $(TRANS_SRC) $(OBJDIR)/codecheck1 + $(OBJDIR)/codecheck1 $(TRANS_SRC) $(OBJDIR): -mkdir $(OBJDIR) -translate: $(SRCDIR)/translate.c - $(BCC) -o translate $(SRCDIR)/translate.c - -makeheaders: $(SRCDIR)/makeheaders.c - $(BCC) -o makeheaders $(SRCDIR)/makeheaders.c - -mkindex: $(SRCDIR)/mkindex.c - $(BCC) -o mkindex $(SRCDIR)/mkindex.c - -# WARNING. DANGER. Running the testsuite modifies the repository the -# build is done from, i.e. the checkout belongs to. Do not sync/push -# the repository after running the tests. -test: $(APPNAME) - $(TCLSH) test/tester.tcl $(APPNAME) - -VERSION.h: $(SRCDIR)/../manifest.uuid $(SRCDIR)/../manifest - awk '{ printf "#define MANIFEST_UUID \"%s\"\n", $$1}' $(SRCDIR)/../manifest.uuid >VERSION.h - awk '{ printf "#define MANIFEST_VERSION \"[%.10s]\"\n", $$1}' $(SRCDIR)/../manifest.uuid >>VERSION.h - awk '$$1=="D"{printf "#define MANIFEST_DATE \"%s %s\"\n", substr($$2,1,10),substr($$2,12)}' $(SRCDIR)/../manifest >>VERSION.h - -$(APPNAME): headers $(OBJ) $(OBJDIR)/sqlite3.o $(OBJDIR)/th.o $(OBJDIR)/th_lang.o - $(TCC) -o $(APPNAME) $(OBJ) $(OBJDIR)/sqlite3.o $(OBJDIR)/th.o $(OBJDIR)/th_lang.o $(LIB) +$(OBJDIR)/translate: $(SRCDIR)/translate.c + $(XBCC) -o $(OBJDIR)/translate $(SRCDIR)/translate.c + +$(OBJDIR)/makeheaders: $(SRCDIR)/makeheaders.c + $(XBCC) -o $(OBJDIR)/makeheaders $(SRCDIR)/makeheaders.c + +$(OBJDIR)/mkindex: $(SRCDIR)/mkindex.c + $(XBCC) -o $(OBJDIR)/mkindex $(SRCDIR)/mkindex.c + +$(OBJDIR)/mkbuiltin: $(SRCDIR)/mkbuiltin.c + $(XBCC) -o $(OBJDIR)/mkbuiltin $(SRCDIR)/mkbuiltin.c + +$(OBJDIR)/mkversion: $(SRCDIR)/mkversion.c + $(XBCC) -o $(OBJDIR)/mkversion $(SRCDIR)/mkversion.c + +$(OBJDIR)/codecheck1: $(SRCDIR)/codecheck1.c + $(XBCC) -o $(OBJDIR)/codecheck1 $(SRCDIR)/codecheck1.c + +# Run the test suite. +# Other flags that can be included in TESTFLAGS are: +# +# -halt Stop testing after the first failed test +# -keep Keep the temporary workspace for debugging +# -prot Write a detailed log of the tests to the file ./prot +# -verbose Include even more details in the output +# -quiet Hide most output from the terminal +# -strict Treat known bugs as failures +# +# TESTFLAGS can also include names of specific test files to limit +# the run to just those test cases. +# +test: $(OBJDIR) $(APPNAME) + $(TCLSH) $(SRCDIR)/../test/tester.tcl $(APPNAME) $(TESTFLAGS) + +$(OBJDIR)/VERSION.h: $(SRCDIR)/../manifest.uuid $(SRCDIR)/../manifest $(SRCDIR)/../VERSION $(OBJDIR)/mkversion $(OBJDIR)/phony.h + $(OBJDIR)/mkversion $(SRCDIR)/../manifest.uuid $(SRCDIR)/../manifest $(SRCDIR)/../VERSION >$(OBJDIR)/VERSION.h + +$(OBJDIR)/phony.h: + # Force rebuild of VERSION.h every time we run "make" + +# Setup the options used to compile the included SQLite library. +SQLITE_OPTIONS = -DNDEBUG=1 \ + -DSQLITE_DQS=0 \ + -DSQLITE_THREADSAFE=0 \ + -DSQLITE_DEFAULT_MEMSTATUS=0 \ + -DSQLITE_DEFAULT_WAL_SYNCHRONOUS=1 \ + -DSQLITE_LIKE_DOESNT_MATCH_BLOBS \ + -DSQLITE_OMIT_DECLTYPE \ + -DSQLITE_OMIT_DEPRECATED \ + -DSQLITE_OMIT_PROGRESS_CALLBACK \ + -DSQLITE_OMIT_SHARED_CACHE \ + -DSQLITE_OMIT_LOAD_EXTENSION \ + -DSQLITE_MAX_EXPR_DEPTH=0 \ + -DSQLITE_USE_ALLOCA \ + -DSQLITE_ENABLE_LOCKING_STYLE=0 \ + -DSQLITE_DEFAULT_FILE_FORMAT=4 \ + -DSQLITE_ENABLE_EXPLAIN_COMMENTS \ + -DSQLITE_ENABLE_FTS4 \ + -DSQLITE_ENABLE_DBSTAT_VTAB \ + -DSQLITE_ENABLE_JSON1 \ + -DSQLITE_ENABLE_FTS5 \ + -DSQLITE_ENABLE_STMTVTAB \ + -DSQLITE_HAVE_ZLIB \ + -DSQLITE_INTROSPECTION_PRAGMAS \ + -DSQLITE_ENABLE_DBPAGE_VTAB \ + -DSQLITE_TRUSTED_SCHEMA=0 + +# Setup the options used to compile the included SQLite shell. +SHELL_OPTIONS = -DNDEBUG=1 \ + -DSQLITE_DQS=0 \ + -DSQLITE_THREADSAFE=0 \ + -DSQLITE_DEFAULT_MEMSTATUS=0 \ + -DSQLITE_DEFAULT_WAL_SYNCHRONOUS=1 \ + -DSQLITE_LIKE_DOESNT_MATCH_BLOBS \ + -DSQLITE_OMIT_DECLTYPE \ + -DSQLITE_OMIT_DEPRECATED \ + -DSQLITE_OMIT_PROGRESS_CALLBACK \ + -DSQLITE_OMIT_SHARED_CACHE \ + -DSQLITE_OMIT_LOAD_EXTENSION \ + -DSQLITE_MAX_EXPR_DEPTH=0 \ + -DSQLITE_USE_ALLOCA \ + -DSQLITE_ENABLE_LOCKING_STYLE=0 \ + -DSQLITE_DEFAULT_FILE_FORMAT=4 \ + -DSQLITE_ENABLE_EXPLAIN_COMMENTS \ + -DSQLITE_ENABLE_FTS4 \ + -DSQLITE_ENABLE_DBSTAT_VTAB \ + -DSQLITE_ENABLE_JSON1 \ + -DSQLITE_ENABLE_FTS5 \ + -DSQLITE_ENABLE_STMTVTAB \ + -DSQLITE_HAVE_ZLIB \ + -DSQLITE_INTROSPECTION_PRAGMAS \ + -DSQLITE_ENABLE_DBPAGE_VTAB \ + -DSQLITE_TRUSTED_SCHEMA=0 \ + -Dmain=sqlite3_shell \ + -DSQLITE_SHELL_IS_UTF8=1 \ + -DSQLITE_OMIT_LOAD_EXTENSION=1 \ + -DUSE_SYSTEM_SQLITE=$(USE_SYSTEM_SQLITE) \ + -DSQLITE_SHELL_DBNAME_PROC=sqlcmd_get_dbname \ + -DSQLITE_SHELL_INIT_PROC=sqlcmd_init_proc + +# Setup the options used to compile the included miniz library. +MINIZ_OPTIONS = -DMINIZ_NO_STDIO \ + -DMINIZ_NO_TIME \ + -DMINIZ_NO_ARCHIVE_APIS + +# The USE_SYSTEM_SQLITE variable may be undefined, set to 0, or set +# to 1. If it is set to 1, then there is no need to build or link +# the sqlite3.o object. Instead, the system SQLite will be linked +# using -lsqlite3. +SQLITE3_OBJ.0 = $(OBJDIR)/sqlite3.o +SQLITE3_OBJ.1 = +SQLITE3_OBJ. = $(SQLITE3_OBJ.0) + +# The FOSSIL_ENABLE_MINIZ variable may be undefined, set to 0, or +# set to 1. If it is set to 1, the miniz library included in the +# source tree should be used; otherwise, it should not. +MINIZ_OBJ.0 = +MINIZ_OBJ.1 = $(OBJDIR)/miniz.o +MINIZ_OBJ. = $(MINIZ_OBJ.0) + +# The USE_LINENOISE variable may be undefined, set to 0, or set +# to 1. If it is set to 0, then there is no need to build or link +# the linenoise.o object. +LINENOISE_DEF.0 = +LINENOISE_DEF.1 = -DHAVE_LINENOISE +LINENOISE_DEF. = $(LINENOISE_DEF.0) +LINENOISE_OBJ.0 = +LINENOISE_OBJ.1 = $(OBJDIR)/linenoise.o +LINENOISE_OBJ. = $(LINENOISE_OBJ.0) + +# The USE_SEE variable may be undefined, 0 or 1. If undefined or +# 0, ordinary SQLite is used. If 1, then sqlite3-see.c (not part of +# the source tree) is used and extra flags are provided to enable +# the SQLite Encryption Extension. +SQLITE3_SRC.0 = sqlite3.c +SQLITE3_SRC.1 = sqlite3-see.c +SQLITE3_SRC. = sqlite3.c +SQLITE3_SRC = $(SRCDIR)/$(SQLITE3_SRC.$(USE_SEE)) +SQLITE3_SHELL_SRC.0 = shell.c +SQLITE3_SHELL_SRC.1 = shell-see.c +SQLITE3_SHELL_SRC. = shell.c +SQLITE3_SHELL_SRC = $(SRCDIR)/$(SQLITE3_SHELL_SRC.$(USE_SEE)) +SEE_FLAGS.0 = +SEE_FLAGS.1 = -DSQLITE_HAS_CODEC -DSQLITE_SHELL_DBKEY_PROC=fossil_key +SEE_FLAGS. = +SEE_FLAGS = $(SEE_FLAGS.$(USE_SEE)) + + +EXTRAOBJ = \ + $(SQLITE3_OBJ.$(USE_SYSTEM_SQLITE)) \ + $(MINIZ_OBJ.$(FOSSIL_ENABLE_MINIZ)) \ + $(LINENOISE_OBJ.$(USE_LINENOISE)) \ + $(OBJDIR)/shell.o \ + $(OBJDIR)/th.o \ + $(OBJDIR)/th_lang.o \ + $(OBJDIR)/th_tcl.o \ + $(OBJDIR)/cson_amalgamation.o + + +$(APPNAME): $(OBJDIR)/headers $(OBJDIR)/codecheck1 $(EXTRAOBJ) $(OBJ) + $(OBJDIR)/codecheck1 $(TRANS_SRC) + $(TCC) $(TCCFLAGS) -o $(APPNAME) $(EXTRAOBJ) $(OBJ) $(LIB) # This rule prevents make from using its default rules to try build # an executable named "manifest" out of the file named "manifest.c" # -$(SRCDIR)/../manifest: +$(SRCDIR)/../manifest: # noop -clean: - rm -f $(OBJDIR)/*.o *_.c $(APPNAME) VERSION.h - rm -f translate makeheaders mkindex page_index.h headers - rm -f add.h allrepo.h attach.h bag.h blob.h branch.h browse.h captcha.h cgi.h checkin.h checkout.h clearsign.h clone.h comformat.h configure.h content.h db.h delta.h deltacmd.h descendants.h diff.h diffcmd.h doc.h encode.h file.h finfo.h graph.h http.h http_socket.h http_transport.h info.h login.h main.h manifest.h md5.h merge.h merge3.h name.h pivot.h pqueue.h printf.h rebuild.h report.h rss.h schema.h search.h setup.h sha1.h shun.h skins.h stat.h style.h sync.h tag.h th_main.h timeline.h tkt.h tktsetup.h undo.h update.h url.h user.h verify.h vfile.h wiki.h wikiformat.h winhttp.h xfer.h zip.h - -page_index.h: $(TRANS_SRC) mkindex - ./mkindex $(TRANS_SRC) >$@ -headers: page_index.h makeheaders VERSION.h - ./makeheaders add_.c:add.h allrepo_.c:allrepo.h attach_.c:attach.h bag_.c:bag.h blob_.c:blob.h branch_.c:branch.h browse_.c:browse.h captcha_.c:captcha.h cgi_.c:cgi.h checkin_.c:checkin.h checkout_.c:checkout.h clearsign_.c:clearsign.h clone_.c:clone.h comformat_.c:comformat.h configure_.c:configure.h content_.c:content.h db_.c:db.h delta_.c:delta.h deltacmd_.c:deltacmd.h descendants_.c:descendants.h diff_.c:diff.h diffcmd_.c:diffcmd.h doc_.c:doc.h encode_.c:encode.h file_.c:file.h finfo_.c:finfo.h graph_.c:graph.h http_.c:http.h http_socket_.c:http_socket.h http_transport_.c:http_transport.h info_.c:info.h login_.c:login.h main_.c:main.h manifest_.c:manifest.h md5_.c:md5.h merge_.c:merge.h merge3_.c:merge3.h name_.c:name.h pivot_.c:pivot.h pqueue_.c:pqueue.h printf_.c:printf.h rebuild_.c:rebuild.h report_.c:report.h rss_.c:rss.h schema_.c:schema.h search_.c:search.h setup_.c:setup.h sha1_.c:sha1.h shun_.c:shun.h skins_.c:skins.h stat_.c:stat.h style_.c:style.h sync_.c:sync.h tag_.c:tag.h th_main_.c:th_main.h timeline_.c:timeline.h tkt_.c:tkt.h tktsetup_.c:tktsetup.h undo_.c:undo.h update_.c:update.h url_.c:url.h user_.c:user.h verify_.c:verify.h vfile_.c:vfile.h wiki_.c:wiki.h wikiformat_.c:wikiformat.h winhttp_.c:winhttp.h xfer_.c:xfer.h zip_.c:zip.h $(SRCDIR)/sqlite3.h $(SRCDIR)/th.h VERSION.h - touch headers -headers: Makefile +clean: + -rm -rf $(OBJDIR)/* $(APPNAME) + + +$(OBJDIR)/page_index.h: $(TRANS_SRC) $(OBJDIR)/mkindex + $(OBJDIR)/mkindex $(TRANS_SRC) >$@ + +$(OBJDIR)/builtin_data.h: $(OBJDIR)/mkbuiltin $(EXTRA_FILES) + $(OBJDIR)/mkbuiltin --prefix $(SRCDIR)/ $(EXTRA_FILES) >$@ + +$(OBJDIR)/headers: $(OBJDIR)/page_index.h $(OBJDIR)/builtin_data.h $(OBJDIR)/makeheaders $(OBJDIR)/VERSION.h + $(OBJDIR)/makeheaders $(OBJDIR)/add_.c:$(OBJDIR)/add.h \ + $(OBJDIR)/ajax_.c:$(OBJDIR)/ajax.h \ + $(OBJDIR)/alerts_.c:$(OBJDIR)/alerts.h \ + $(OBJDIR)/allrepo_.c:$(OBJDIR)/allrepo.h \ + $(OBJDIR)/attach_.c:$(OBJDIR)/attach.h \ + $(OBJDIR)/backlink_.c:$(OBJDIR)/backlink.h \ + $(OBJDIR)/backoffice_.c:$(OBJDIR)/backoffice.h \ + $(OBJDIR)/bag_.c:$(OBJDIR)/bag.h \ + $(OBJDIR)/bisect_.c:$(OBJDIR)/bisect.h \ + $(OBJDIR)/blob_.c:$(OBJDIR)/blob.h \ + $(OBJDIR)/branch_.c:$(OBJDIR)/branch.h \ + $(OBJDIR)/browse_.c:$(OBJDIR)/browse.h \ + $(OBJDIR)/builtin_.c:$(OBJDIR)/builtin.h \ + $(OBJDIR)/bundle_.c:$(OBJDIR)/bundle.h \ + $(OBJDIR)/cache_.c:$(OBJDIR)/cache.h \ + $(OBJDIR)/capabilities_.c:$(OBJDIR)/capabilities.h \ + $(OBJDIR)/captcha_.c:$(OBJDIR)/captcha.h \ + $(OBJDIR)/cgi_.c:$(OBJDIR)/cgi.h \ + $(OBJDIR)/chat_.c:$(OBJDIR)/chat.h \ + $(OBJDIR)/checkin_.c:$(OBJDIR)/checkin.h \ + $(OBJDIR)/checkout_.c:$(OBJDIR)/checkout.h \ + $(OBJDIR)/clearsign_.c:$(OBJDIR)/clearsign.h \ + $(OBJDIR)/clone_.c:$(OBJDIR)/clone.h \ + $(OBJDIR)/color_.c:$(OBJDIR)/color.h \ + $(OBJDIR)/comformat_.c:$(OBJDIR)/comformat.h \ + $(OBJDIR)/configure_.c:$(OBJDIR)/configure.h \ + $(OBJDIR)/content_.c:$(OBJDIR)/content.h \ + $(OBJDIR)/cookies_.c:$(OBJDIR)/cookies.h \ + $(OBJDIR)/db_.c:$(OBJDIR)/db.h \ + $(OBJDIR)/delta_.c:$(OBJDIR)/delta.h \ + $(OBJDIR)/deltacmd_.c:$(OBJDIR)/deltacmd.h \ + $(OBJDIR)/deltafunc_.c:$(OBJDIR)/deltafunc.h \ + $(OBJDIR)/descendants_.c:$(OBJDIR)/descendants.h \ + $(OBJDIR)/diff_.c:$(OBJDIR)/diff.h \ + $(OBJDIR)/diffcmd_.c:$(OBJDIR)/diffcmd.h \ + $(OBJDIR)/dispatch_.c:$(OBJDIR)/dispatch.h \ + $(OBJDIR)/doc_.c:$(OBJDIR)/doc.h \ + $(OBJDIR)/encode_.c:$(OBJDIR)/encode.h \ + $(OBJDIR)/etag_.c:$(OBJDIR)/etag.h \ + $(OBJDIR)/event_.c:$(OBJDIR)/event.h \ + $(OBJDIR)/export_.c:$(OBJDIR)/export.h \ + $(OBJDIR)/extcgi_.c:$(OBJDIR)/extcgi.h \ + $(OBJDIR)/file_.c:$(OBJDIR)/file.h \ + $(OBJDIR)/fileedit_.c:$(OBJDIR)/fileedit.h \ + $(OBJDIR)/finfo_.c:$(OBJDIR)/finfo.h \ + $(OBJDIR)/foci_.c:$(OBJDIR)/foci.h \ + $(OBJDIR)/forum_.c:$(OBJDIR)/forum.h \ + $(OBJDIR)/fshell_.c:$(OBJDIR)/fshell.h \ + $(OBJDIR)/fusefs_.c:$(OBJDIR)/fusefs.h \ + $(OBJDIR)/fuzz_.c:$(OBJDIR)/fuzz.h \ + $(OBJDIR)/glob_.c:$(OBJDIR)/glob.h \ + $(OBJDIR)/graph_.c:$(OBJDIR)/graph.h \ + $(OBJDIR)/gzip_.c:$(OBJDIR)/gzip.h \ + $(OBJDIR)/hname_.c:$(OBJDIR)/hname.h \ + $(OBJDIR)/hook_.c:$(OBJDIR)/hook.h \ + $(OBJDIR)/http_.c:$(OBJDIR)/http.h \ + $(OBJDIR)/http_socket_.c:$(OBJDIR)/http_socket.h \ + $(OBJDIR)/http_ssl_.c:$(OBJDIR)/http_ssl.h \ + $(OBJDIR)/http_transport_.c:$(OBJDIR)/http_transport.h \ + $(OBJDIR)/import_.c:$(OBJDIR)/import.h \ + $(OBJDIR)/info_.c:$(OBJDIR)/info.h \ + $(OBJDIR)/interwiki_.c:$(OBJDIR)/interwiki.h \ + $(OBJDIR)/json_.c:$(OBJDIR)/json.h \ + $(OBJDIR)/json_artifact_.c:$(OBJDIR)/json_artifact.h \ + $(OBJDIR)/json_branch_.c:$(OBJDIR)/json_branch.h \ + $(OBJDIR)/json_config_.c:$(OBJDIR)/json_config.h \ + $(OBJDIR)/json_diff_.c:$(OBJDIR)/json_diff.h \ + $(OBJDIR)/json_dir_.c:$(OBJDIR)/json_dir.h \ + $(OBJDIR)/json_finfo_.c:$(OBJDIR)/json_finfo.h \ + $(OBJDIR)/json_login_.c:$(OBJDIR)/json_login.h \ + $(OBJDIR)/json_query_.c:$(OBJDIR)/json_query.h \ + $(OBJDIR)/json_report_.c:$(OBJDIR)/json_report.h \ + $(OBJDIR)/json_status_.c:$(OBJDIR)/json_status.h \ + $(OBJDIR)/json_tag_.c:$(OBJDIR)/json_tag.h \ + $(OBJDIR)/json_timeline_.c:$(OBJDIR)/json_timeline.h \ + $(OBJDIR)/json_user_.c:$(OBJDIR)/json_user.h \ + $(OBJDIR)/json_wiki_.c:$(OBJDIR)/json_wiki.h \ + $(OBJDIR)/leaf_.c:$(OBJDIR)/leaf.h \ + $(OBJDIR)/loadctrl_.c:$(OBJDIR)/loadctrl.h \ + $(OBJDIR)/login_.c:$(OBJDIR)/login.h \ + $(OBJDIR)/lookslike_.c:$(OBJDIR)/lookslike.h \ + $(OBJDIR)/main_.c:$(OBJDIR)/main.h \ + $(OBJDIR)/manifest_.c:$(OBJDIR)/manifest.h \ + $(OBJDIR)/markdown_.c:$(OBJDIR)/markdown.h \ + $(OBJDIR)/markdown_html_.c:$(OBJDIR)/markdown_html.h \ + $(OBJDIR)/md5_.c:$(OBJDIR)/md5.h \ + $(OBJDIR)/merge_.c:$(OBJDIR)/merge.h \ + $(OBJDIR)/merge3_.c:$(OBJDIR)/merge3.h \ + $(OBJDIR)/moderate_.c:$(OBJDIR)/moderate.h \ + $(OBJDIR)/name_.c:$(OBJDIR)/name.h \ + $(OBJDIR)/patch_.c:$(OBJDIR)/patch.h \ + $(OBJDIR)/path_.c:$(OBJDIR)/path.h \ + $(OBJDIR)/piechart_.c:$(OBJDIR)/piechart.h \ + $(OBJDIR)/pikchr_.c:$(OBJDIR)/pikchr.h \ + $(OBJDIR)/pikchrshow_.c:$(OBJDIR)/pikchrshow.h \ + $(OBJDIR)/pivot_.c:$(OBJDIR)/pivot.h \ + $(OBJDIR)/popen_.c:$(OBJDIR)/popen.h \ + $(OBJDIR)/pqueue_.c:$(OBJDIR)/pqueue.h \ + $(OBJDIR)/printf_.c:$(OBJDIR)/printf.h \ + $(OBJDIR)/publish_.c:$(OBJDIR)/publish.h \ + $(OBJDIR)/purge_.c:$(OBJDIR)/purge.h \ + $(OBJDIR)/rebuild_.c:$(OBJDIR)/rebuild.h \ + $(OBJDIR)/regexp_.c:$(OBJDIR)/regexp.h \ + $(OBJDIR)/repolist_.c:$(OBJDIR)/repolist.h \ + $(OBJDIR)/report_.c:$(OBJDIR)/report.h \ + $(OBJDIR)/rss_.c:$(OBJDIR)/rss.h \ + $(OBJDIR)/schema_.c:$(OBJDIR)/schema.h \ + $(OBJDIR)/search_.c:$(OBJDIR)/search.h \ + $(OBJDIR)/security_audit_.c:$(OBJDIR)/security_audit.h \ + $(OBJDIR)/setup_.c:$(OBJDIR)/setup.h \ + $(OBJDIR)/setupuser_.c:$(OBJDIR)/setupuser.h \ + $(OBJDIR)/sha1_.c:$(OBJDIR)/sha1.h \ + $(OBJDIR)/sha1hard_.c:$(OBJDIR)/sha1hard.h \ + $(OBJDIR)/sha3_.c:$(OBJDIR)/sha3.h \ + $(OBJDIR)/shun_.c:$(OBJDIR)/shun.h \ + $(OBJDIR)/sitemap_.c:$(OBJDIR)/sitemap.h \ + $(OBJDIR)/skins_.c:$(OBJDIR)/skins.h \ + $(OBJDIR)/smtp_.c:$(OBJDIR)/smtp.h \ + $(OBJDIR)/sqlcmd_.c:$(OBJDIR)/sqlcmd.h \ + $(OBJDIR)/stash_.c:$(OBJDIR)/stash.h \ + $(OBJDIR)/stat_.c:$(OBJDIR)/stat.h \ + $(OBJDIR)/statrep_.c:$(OBJDIR)/statrep.h \ + $(OBJDIR)/style_.c:$(OBJDIR)/style.h \ + $(OBJDIR)/sync_.c:$(OBJDIR)/sync.h \ + $(OBJDIR)/tag_.c:$(OBJDIR)/tag.h \ + $(OBJDIR)/tar_.c:$(OBJDIR)/tar.h \ + $(OBJDIR)/terminal_.c:$(OBJDIR)/terminal.h \ + $(OBJDIR)/th_main_.c:$(OBJDIR)/th_main.h \ + $(OBJDIR)/timeline_.c:$(OBJDIR)/timeline.h \ + $(OBJDIR)/tkt_.c:$(OBJDIR)/tkt.h \ + $(OBJDIR)/tktsetup_.c:$(OBJDIR)/tktsetup.h \ + $(OBJDIR)/undo_.c:$(OBJDIR)/undo.h \ + $(OBJDIR)/unicode_.c:$(OBJDIR)/unicode.h \ + $(OBJDIR)/unversioned_.c:$(OBJDIR)/unversioned.h \ + $(OBJDIR)/update_.c:$(OBJDIR)/update.h \ + $(OBJDIR)/url_.c:$(OBJDIR)/url.h \ + $(OBJDIR)/user_.c:$(OBJDIR)/user.h \ + $(OBJDIR)/utf8_.c:$(OBJDIR)/utf8.h \ + $(OBJDIR)/util_.c:$(OBJDIR)/util.h \ + $(OBJDIR)/verify_.c:$(OBJDIR)/verify.h \ + $(OBJDIR)/vfile_.c:$(OBJDIR)/vfile.h \ + $(OBJDIR)/wiki_.c:$(OBJDIR)/wiki.h \ + $(OBJDIR)/wikiformat_.c:$(OBJDIR)/wikiformat.h \ + $(OBJDIR)/winfile_.c:$(OBJDIR)/winfile.h \ + $(OBJDIR)/winhttp_.c:$(OBJDIR)/winhttp.h \ + $(OBJDIR)/xfer_.c:$(OBJDIR)/xfer.h \ + $(OBJDIR)/xfersetup_.c:$(OBJDIR)/xfersetup.h \ + $(OBJDIR)/zip_.c:$(OBJDIR)/zip.h \ + $(SRCDIR)/sqlite3.h \ + $(SRCDIR)/th.h \ + $(OBJDIR)/VERSION.h + touch $(OBJDIR)/headers +$(OBJDIR)/headers: Makefile +$(OBJDIR)/json.o $(OBJDIR)/json_artifact.o $(OBJDIR)/json_branch.o $(OBJDIR)/json_config.o $(OBJDIR)/json_diff.o $(OBJDIR)/json_dir.o $(OBJDIR)/json_finfo.o $(OBJDIR)/json_login.o $(OBJDIR)/json_query.o $(OBJDIR)/json_report.o $(OBJDIR)/json_status.o $(OBJDIR)/json_tag.o $(OBJDIR)/json_timeline.o $(OBJDIR)/json_user.o $(OBJDIR)/json_wiki.o : $(SRCDIR)/json_detail.h Makefile: -add_.c: $(SRCDIR)/add.c translate - ./translate $(SRCDIR)/add.c >add_.c - -$(OBJDIR)/add.o: add_.c add.h $(SRCDIR)/config.h - $(XTCC) -o $(OBJDIR)/add.o -c add_.c - -add.h: headers -allrepo_.c: $(SRCDIR)/allrepo.c translate - ./translate $(SRCDIR)/allrepo.c >allrepo_.c - -$(OBJDIR)/allrepo.o: allrepo_.c allrepo.h $(SRCDIR)/config.h - $(XTCC) -o $(OBJDIR)/allrepo.o -c allrepo_.c - -allrepo.h: headers -attach_.c: $(SRCDIR)/attach.c translate - ./translate $(SRCDIR)/attach.c >attach_.c - -$(OBJDIR)/attach.o: attach_.c attach.h $(SRCDIR)/config.h - $(XTCC) -o $(OBJDIR)/attach.o -c attach_.c - -attach.h: headers -bag_.c: $(SRCDIR)/bag.c translate - ./translate $(SRCDIR)/bag.c >bag_.c - -$(OBJDIR)/bag.o: bag_.c bag.h $(SRCDIR)/config.h - $(XTCC) -o $(OBJDIR)/bag.o -c bag_.c - -bag.h: headers -blob_.c: $(SRCDIR)/blob.c translate - ./translate $(SRCDIR)/blob.c >blob_.c - -$(OBJDIR)/blob.o: blob_.c blob.h $(SRCDIR)/config.h - $(XTCC) -o $(OBJDIR)/blob.o -c blob_.c - -blob.h: headers -branch_.c: $(SRCDIR)/branch.c translate - ./translate $(SRCDIR)/branch.c >branch_.c - -$(OBJDIR)/branch.o: branch_.c branch.h $(SRCDIR)/config.h - $(XTCC) -o $(OBJDIR)/branch.o -c branch_.c - -branch.h: headers -browse_.c: $(SRCDIR)/browse.c translate - ./translate $(SRCDIR)/browse.c >browse_.c - -$(OBJDIR)/browse.o: browse_.c browse.h $(SRCDIR)/config.h - $(XTCC) -o $(OBJDIR)/browse.o -c browse_.c - -browse.h: headers -captcha_.c: $(SRCDIR)/captcha.c translate - ./translate $(SRCDIR)/captcha.c >captcha_.c - -$(OBJDIR)/captcha.o: captcha_.c captcha.h $(SRCDIR)/config.h - $(XTCC) -o $(OBJDIR)/captcha.o -c captcha_.c - -captcha.h: headers -cgi_.c: $(SRCDIR)/cgi.c translate - ./translate $(SRCDIR)/cgi.c >cgi_.c - -$(OBJDIR)/cgi.o: cgi_.c cgi.h $(SRCDIR)/config.h - $(XTCC) -o $(OBJDIR)/cgi.o -c cgi_.c - -cgi.h: headers -checkin_.c: $(SRCDIR)/checkin.c translate - ./translate $(SRCDIR)/checkin.c >checkin_.c - -$(OBJDIR)/checkin.o: checkin_.c checkin.h $(SRCDIR)/config.h - $(XTCC) -o $(OBJDIR)/checkin.o -c checkin_.c - -checkin.h: headers -checkout_.c: $(SRCDIR)/checkout.c translate - ./translate $(SRCDIR)/checkout.c >checkout_.c - -$(OBJDIR)/checkout.o: checkout_.c checkout.h $(SRCDIR)/config.h - $(XTCC) -o $(OBJDIR)/checkout.o -c checkout_.c - -checkout.h: headers -clearsign_.c: $(SRCDIR)/clearsign.c translate - ./translate $(SRCDIR)/clearsign.c >clearsign_.c - -$(OBJDIR)/clearsign.o: clearsign_.c clearsign.h $(SRCDIR)/config.h - $(XTCC) -o $(OBJDIR)/clearsign.o -c clearsign_.c - -clearsign.h: headers -clone_.c: $(SRCDIR)/clone.c translate - ./translate $(SRCDIR)/clone.c >clone_.c - -$(OBJDIR)/clone.o: clone_.c clone.h $(SRCDIR)/config.h - $(XTCC) -o $(OBJDIR)/clone.o -c clone_.c - -clone.h: headers -comformat_.c: $(SRCDIR)/comformat.c translate - ./translate $(SRCDIR)/comformat.c >comformat_.c - -$(OBJDIR)/comformat.o: comformat_.c comformat.h $(SRCDIR)/config.h - $(XTCC) -o $(OBJDIR)/comformat.o -c comformat_.c - -comformat.h: headers -configure_.c: $(SRCDIR)/configure.c translate - ./translate $(SRCDIR)/configure.c >configure_.c - -$(OBJDIR)/configure.o: configure_.c configure.h $(SRCDIR)/config.h - $(XTCC) -o $(OBJDIR)/configure.o -c configure_.c - -configure.h: headers -content_.c: $(SRCDIR)/content.c translate - ./translate $(SRCDIR)/content.c >content_.c - -$(OBJDIR)/content.o: content_.c content.h $(SRCDIR)/config.h - $(XTCC) -o $(OBJDIR)/content.o -c content_.c - -content.h: headers -db_.c: $(SRCDIR)/db.c translate - ./translate $(SRCDIR)/db.c >db_.c - -$(OBJDIR)/db.o: db_.c db.h $(SRCDIR)/config.h - $(XTCC) -o $(OBJDIR)/db.o -c db_.c - -db.h: headers -delta_.c: $(SRCDIR)/delta.c translate - ./translate $(SRCDIR)/delta.c >delta_.c - -$(OBJDIR)/delta.o: delta_.c delta.h $(SRCDIR)/config.h - $(XTCC) -o $(OBJDIR)/delta.o -c delta_.c - -delta.h: headers -deltacmd_.c: $(SRCDIR)/deltacmd.c translate - ./translate $(SRCDIR)/deltacmd.c >deltacmd_.c - -$(OBJDIR)/deltacmd.o: deltacmd_.c deltacmd.h $(SRCDIR)/config.h - $(XTCC) -o $(OBJDIR)/deltacmd.o -c deltacmd_.c - -deltacmd.h: headers -descendants_.c: $(SRCDIR)/descendants.c translate - ./translate $(SRCDIR)/descendants.c >descendants_.c - -$(OBJDIR)/descendants.o: descendants_.c descendants.h $(SRCDIR)/config.h - $(XTCC) -o $(OBJDIR)/descendants.o -c descendants_.c - -descendants.h: headers -diff_.c: $(SRCDIR)/diff.c translate - ./translate $(SRCDIR)/diff.c >diff_.c - -$(OBJDIR)/diff.o: diff_.c diff.h $(SRCDIR)/config.h - $(XTCC) -o $(OBJDIR)/diff.o -c diff_.c - -diff.h: headers -diffcmd_.c: $(SRCDIR)/diffcmd.c translate - ./translate $(SRCDIR)/diffcmd.c >diffcmd_.c - -$(OBJDIR)/diffcmd.o: diffcmd_.c diffcmd.h $(SRCDIR)/config.h - $(XTCC) -o $(OBJDIR)/diffcmd.o -c diffcmd_.c - -diffcmd.h: headers -doc_.c: $(SRCDIR)/doc.c translate - ./translate $(SRCDIR)/doc.c >doc_.c - -$(OBJDIR)/doc.o: doc_.c doc.h $(SRCDIR)/config.h - $(XTCC) -o $(OBJDIR)/doc.o -c doc_.c - -doc.h: headers -encode_.c: $(SRCDIR)/encode.c translate - ./translate $(SRCDIR)/encode.c >encode_.c - -$(OBJDIR)/encode.o: encode_.c encode.h $(SRCDIR)/config.h - $(XTCC) -o $(OBJDIR)/encode.o -c encode_.c - -encode.h: headers -file_.c: $(SRCDIR)/file.c translate - ./translate $(SRCDIR)/file.c >file_.c - -$(OBJDIR)/file.o: file_.c file.h $(SRCDIR)/config.h - $(XTCC) -o $(OBJDIR)/file.o -c file_.c - -file.h: headers -finfo_.c: $(SRCDIR)/finfo.c translate - ./translate $(SRCDIR)/finfo.c >finfo_.c - -$(OBJDIR)/finfo.o: finfo_.c finfo.h $(SRCDIR)/config.h - $(XTCC) -o $(OBJDIR)/finfo.o -c finfo_.c - -finfo.h: headers -graph_.c: $(SRCDIR)/graph.c translate - ./translate $(SRCDIR)/graph.c >graph_.c - -$(OBJDIR)/graph.o: graph_.c graph.h $(SRCDIR)/config.h - $(XTCC) -o $(OBJDIR)/graph.o -c graph_.c - -graph.h: headers -http_.c: $(SRCDIR)/http.c translate - ./translate $(SRCDIR)/http.c >http_.c - -$(OBJDIR)/http.o: http_.c http.h $(SRCDIR)/config.h - $(XTCC) -o $(OBJDIR)/http.o -c http_.c - -http.h: headers -http_socket_.c: $(SRCDIR)/http_socket.c translate - ./translate $(SRCDIR)/http_socket.c >http_socket_.c - -$(OBJDIR)/http_socket.o: http_socket_.c http_socket.h $(SRCDIR)/config.h - $(XTCC) -o $(OBJDIR)/http_socket.o -c http_socket_.c - -http_socket.h: headers -http_transport_.c: $(SRCDIR)/http_transport.c translate - ./translate $(SRCDIR)/http_transport.c >http_transport_.c - -$(OBJDIR)/http_transport.o: http_transport_.c http_transport.h $(SRCDIR)/config.h - $(XTCC) -o $(OBJDIR)/http_transport.o -c http_transport_.c - -http_transport.h: headers -info_.c: $(SRCDIR)/info.c translate - ./translate $(SRCDIR)/info.c >info_.c - -$(OBJDIR)/info.o: info_.c info.h $(SRCDIR)/config.h - $(XTCC) -o $(OBJDIR)/info.o -c info_.c - -info.h: headers -login_.c: $(SRCDIR)/login.c translate - ./translate $(SRCDIR)/login.c >login_.c - -$(OBJDIR)/login.o: login_.c login.h $(SRCDIR)/config.h - $(XTCC) -o $(OBJDIR)/login.o -c login_.c - -login.h: headers -main_.c: $(SRCDIR)/main.c translate - ./translate $(SRCDIR)/main.c >main_.c - -$(OBJDIR)/main.o: main_.c main.h page_index.h $(SRCDIR)/config.h - $(XTCC) -o $(OBJDIR)/main.o -c main_.c - -main.h: headers -manifest_.c: $(SRCDIR)/manifest.c translate - ./translate $(SRCDIR)/manifest.c >manifest_.c - -$(OBJDIR)/manifest.o: manifest_.c manifest.h $(SRCDIR)/config.h - $(XTCC) -o $(OBJDIR)/manifest.o -c manifest_.c - -manifest.h: headers -md5_.c: $(SRCDIR)/md5.c translate - ./translate $(SRCDIR)/md5.c >md5_.c - -$(OBJDIR)/md5.o: md5_.c md5.h $(SRCDIR)/config.h - $(XTCC) -o $(OBJDIR)/md5.o -c md5_.c - -md5.h: headers -merge_.c: $(SRCDIR)/merge.c translate - ./translate $(SRCDIR)/merge.c >merge_.c - -$(OBJDIR)/merge.o: merge_.c merge.h $(SRCDIR)/config.h - $(XTCC) -o $(OBJDIR)/merge.o -c merge_.c - -merge.h: headers -merge3_.c: $(SRCDIR)/merge3.c translate - ./translate $(SRCDIR)/merge3.c >merge3_.c - -$(OBJDIR)/merge3.o: merge3_.c merge3.h $(SRCDIR)/config.h - $(XTCC) -o $(OBJDIR)/merge3.o -c merge3_.c - -merge3.h: headers -name_.c: $(SRCDIR)/name.c translate - ./translate $(SRCDIR)/name.c >name_.c - -$(OBJDIR)/name.o: name_.c name.h $(SRCDIR)/config.h - $(XTCC) -o $(OBJDIR)/name.o -c name_.c - -name.h: headers -pivot_.c: $(SRCDIR)/pivot.c translate - ./translate $(SRCDIR)/pivot.c >pivot_.c - -$(OBJDIR)/pivot.o: pivot_.c pivot.h $(SRCDIR)/config.h - $(XTCC) -o $(OBJDIR)/pivot.o -c pivot_.c - -pivot.h: headers -pqueue_.c: $(SRCDIR)/pqueue.c translate - ./translate $(SRCDIR)/pqueue.c >pqueue_.c - -$(OBJDIR)/pqueue.o: pqueue_.c pqueue.h $(SRCDIR)/config.h - $(XTCC) -o $(OBJDIR)/pqueue.o -c pqueue_.c - -pqueue.h: headers -printf_.c: $(SRCDIR)/printf.c translate - ./translate $(SRCDIR)/printf.c >printf_.c - -$(OBJDIR)/printf.o: printf_.c printf.h $(SRCDIR)/config.h - $(XTCC) -o $(OBJDIR)/printf.o -c printf_.c - -printf.h: headers -rebuild_.c: $(SRCDIR)/rebuild.c translate - ./translate $(SRCDIR)/rebuild.c >rebuild_.c - -$(OBJDIR)/rebuild.o: rebuild_.c rebuild.h $(SRCDIR)/config.h - $(XTCC) -o $(OBJDIR)/rebuild.o -c rebuild_.c - -rebuild.h: headers -report_.c: $(SRCDIR)/report.c translate - ./translate $(SRCDIR)/report.c >report_.c - -$(OBJDIR)/report.o: report_.c report.h $(SRCDIR)/config.h - $(XTCC) -o $(OBJDIR)/report.o -c report_.c - -report.h: headers -rss_.c: $(SRCDIR)/rss.c translate - ./translate $(SRCDIR)/rss.c >rss_.c - -$(OBJDIR)/rss.o: rss_.c rss.h $(SRCDIR)/config.h - $(XTCC) -o $(OBJDIR)/rss.o -c rss_.c - -rss.h: headers -schema_.c: $(SRCDIR)/schema.c translate - ./translate $(SRCDIR)/schema.c >schema_.c - -$(OBJDIR)/schema.o: schema_.c schema.h $(SRCDIR)/config.h - $(XTCC) -o $(OBJDIR)/schema.o -c schema_.c - -schema.h: headers -search_.c: $(SRCDIR)/search.c translate - ./translate $(SRCDIR)/search.c >search_.c - -$(OBJDIR)/search.o: search_.c search.h $(SRCDIR)/config.h - $(XTCC) -o $(OBJDIR)/search.o -c search_.c - -search.h: headers -setup_.c: $(SRCDIR)/setup.c translate - ./translate $(SRCDIR)/setup.c >setup_.c - -$(OBJDIR)/setup.o: setup_.c setup.h $(SRCDIR)/config.h - $(XTCC) -o $(OBJDIR)/setup.o -c setup_.c - -setup.h: headers -sha1_.c: $(SRCDIR)/sha1.c translate - ./translate $(SRCDIR)/sha1.c >sha1_.c - -$(OBJDIR)/sha1.o: sha1_.c sha1.h $(SRCDIR)/config.h - $(XTCC) -o $(OBJDIR)/sha1.o -c sha1_.c - -sha1.h: headers -shun_.c: $(SRCDIR)/shun.c translate - ./translate $(SRCDIR)/shun.c >shun_.c - -$(OBJDIR)/shun.o: shun_.c shun.h $(SRCDIR)/config.h - $(XTCC) -o $(OBJDIR)/shun.o -c shun_.c - -shun.h: headers -skins_.c: $(SRCDIR)/skins.c translate - ./translate $(SRCDIR)/skins.c >skins_.c - -$(OBJDIR)/skins.o: skins_.c skins.h $(SRCDIR)/config.h - $(XTCC) -o $(OBJDIR)/skins.o -c skins_.c - -skins.h: headers -stat_.c: $(SRCDIR)/stat.c translate - ./translate $(SRCDIR)/stat.c >stat_.c - -$(OBJDIR)/stat.o: stat_.c stat.h $(SRCDIR)/config.h - $(XTCC) -o $(OBJDIR)/stat.o -c stat_.c - -stat.h: headers -style_.c: $(SRCDIR)/style.c translate - ./translate $(SRCDIR)/style.c >style_.c - -$(OBJDIR)/style.o: style_.c style.h $(SRCDIR)/config.h - $(XTCC) -o $(OBJDIR)/style.o -c style_.c - -style.h: headers -sync_.c: $(SRCDIR)/sync.c translate - ./translate $(SRCDIR)/sync.c >sync_.c - -$(OBJDIR)/sync.o: sync_.c sync.h $(SRCDIR)/config.h - $(XTCC) -o $(OBJDIR)/sync.o -c sync_.c - -sync.h: headers -tag_.c: $(SRCDIR)/tag.c translate - ./translate $(SRCDIR)/tag.c >tag_.c - -$(OBJDIR)/tag.o: tag_.c tag.h $(SRCDIR)/config.h - $(XTCC) -o $(OBJDIR)/tag.o -c tag_.c - -tag.h: headers -th_main_.c: $(SRCDIR)/th_main.c translate - ./translate $(SRCDIR)/th_main.c >th_main_.c - -$(OBJDIR)/th_main.o: th_main_.c th_main.h $(SRCDIR)/config.h - $(XTCC) -o $(OBJDIR)/th_main.o -c th_main_.c - -th_main.h: headers -timeline_.c: $(SRCDIR)/timeline.c translate - ./translate $(SRCDIR)/timeline.c >timeline_.c - -$(OBJDIR)/timeline.o: timeline_.c timeline.h $(SRCDIR)/config.h - $(XTCC) -o $(OBJDIR)/timeline.o -c timeline_.c - -timeline.h: headers -tkt_.c: $(SRCDIR)/tkt.c translate - ./translate $(SRCDIR)/tkt.c >tkt_.c - -$(OBJDIR)/tkt.o: tkt_.c tkt.h $(SRCDIR)/config.h - $(XTCC) -o $(OBJDIR)/tkt.o -c tkt_.c - -tkt.h: headers -tktsetup_.c: $(SRCDIR)/tktsetup.c translate - ./translate $(SRCDIR)/tktsetup.c >tktsetup_.c - -$(OBJDIR)/tktsetup.o: tktsetup_.c tktsetup.h $(SRCDIR)/config.h - $(XTCC) -o $(OBJDIR)/tktsetup.o -c tktsetup_.c - -tktsetup.h: headers -undo_.c: $(SRCDIR)/undo.c translate - ./translate $(SRCDIR)/undo.c >undo_.c - -$(OBJDIR)/undo.o: undo_.c undo.h $(SRCDIR)/config.h - $(XTCC) -o $(OBJDIR)/undo.o -c undo_.c - -undo.h: headers -update_.c: $(SRCDIR)/update.c translate - ./translate $(SRCDIR)/update.c >update_.c - -$(OBJDIR)/update.o: update_.c update.h $(SRCDIR)/config.h - $(XTCC) -o $(OBJDIR)/update.o -c update_.c - -update.h: headers -url_.c: $(SRCDIR)/url.c translate - ./translate $(SRCDIR)/url.c >url_.c - -$(OBJDIR)/url.o: url_.c url.h $(SRCDIR)/config.h - $(XTCC) -o $(OBJDIR)/url.o -c url_.c - -url.h: headers -user_.c: $(SRCDIR)/user.c translate - ./translate $(SRCDIR)/user.c >user_.c - -$(OBJDIR)/user.o: user_.c user.h $(SRCDIR)/config.h - $(XTCC) -o $(OBJDIR)/user.o -c user_.c - -user.h: headers -verify_.c: $(SRCDIR)/verify.c translate - ./translate $(SRCDIR)/verify.c >verify_.c - -$(OBJDIR)/verify.o: verify_.c verify.h $(SRCDIR)/config.h - $(XTCC) -o $(OBJDIR)/verify.o -c verify_.c - -verify.h: headers -vfile_.c: $(SRCDIR)/vfile.c translate - ./translate $(SRCDIR)/vfile.c >vfile_.c - -$(OBJDIR)/vfile.o: vfile_.c vfile.h $(SRCDIR)/config.h - $(XTCC) -o $(OBJDIR)/vfile.o -c vfile_.c - -vfile.h: headers -wiki_.c: $(SRCDIR)/wiki.c translate - ./translate $(SRCDIR)/wiki.c >wiki_.c - -$(OBJDIR)/wiki.o: wiki_.c wiki.h $(SRCDIR)/config.h - $(XTCC) -o $(OBJDIR)/wiki.o -c wiki_.c - -wiki.h: headers -wikiformat_.c: $(SRCDIR)/wikiformat.c translate - ./translate $(SRCDIR)/wikiformat.c >wikiformat_.c - -$(OBJDIR)/wikiformat.o: wikiformat_.c wikiformat.h $(SRCDIR)/config.h - $(XTCC) -o $(OBJDIR)/wikiformat.o -c wikiformat_.c - -wikiformat.h: headers -winhttp_.c: $(SRCDIR)/winhttp.c translate - ./translate $(SRCDIR)/winhttp.c >winhttp_.c - -$(OBJDIR)/winhttp.o: winhttp_.c winhttp.h $(SRCDIR)/config.h - $(XTCC) -o $(OBJDIR)/winhttp.o -c winhttp_.c - -winhttp.h: headers -xfer_.c: $(SRCDIR)/xfer.c translate - ./translate $(SRCDIR)/xfer.c >xfer_.c - -$(OBJDIR)/xfer.o: xfer_.c xfer.h $(SRCDIR)/config.h - $(XTCC) -o $(OBJDIR)/xfer.o -c xfer_.c - -xfer.h: headers -zip_.c: $(SRCDIR)/zip.c translate - ./translate $(SRCDIR)/zip.c >zip_.c - -$(OBJDIR)/zip.o: zip_.c zip.h $(SRCDIR)/config.h - $(XTCC) -o $(OBJDIR)/zip.o -c zip_.c - -zip.h: headers -$(OBJDIR)/sqlite3.o: $(SRCDIR)/sqlite3.c - $(XTCC) -DSQLITE_OMIT_LOAD_EXTENSION=1 -DSQLITE_THREADSAFE=0 -DSQLITE_DEFAULT_FILE_FORMAT=4 -Dlocaltime=fossil_localtime -DSQLITE_ENABLE_LOCKING_STYLE=0 -c $(SRCDIR)/sqlite3.c -o $(OBJDIR)/sqlite3.o +$(OBJDIR)/add_.c: $(SRCDIR)/add.c $(OBJDIR)/translate + $(OBJDIR)/translate $(SRCDIR)/add.c >$@ + +$(OBJDIR)/add.o: $(OBJDIR)/add_.c $(OBJDIR)/add.h $(SRCDIR)/config.h + $(XTCC) -o $(OBJDIR)/add.o -c $(OBJDIR)/add_.c + +$(OBJDIR)/add.h: $(OBJDIR)/headers + +$(OBJDIR)/ajax_.c: $(SRCDIR)/ajax.c $(OBJDIR)/translate + $(OBJDIR)/translate $(SRCDIR)/ajax.c >$@ + +$(OBJDIR)/ajax.o: $(OBJDIR)/ajax_.c $(OBJDIR)/ajax.h $(SRCDIR)/config.h + $(XTCC) -o $(OBJDIR)/ajax.o -c $(OBJDIR)/ajax_.c + +$(OBJDIR)/ajax.h: $(OBJDIR)/headers + +$(OBJDIR)/alerts_.c: $(SRCDIR)/alerts.c $(OBJDIR)/translate + $(OBJDIR)/translate $(SRCDIR)/alerts.c >$@ + +$(OBJDIR)/alerts.o: $(OBJDIR)/alerts_.c $(OBJDIR)/alerts.h $(SRCDIR)/config.h + $(XTCC) -o $(OBJDIR)/alerts.o -c $(OBJDIR)/alerts_.c + +$(OBJDIR)/alerts.h: $(OBJDIR)/headers + +$(OBJDIR)/allrepo_.c: $(SRCDIR)/allrepo.c $(OBJDIR)/translate + $(OBJDIR)/translate $(SRCDIR)/allrepo.c >$@ + +$(OBJDIR)/allrepo.o: $(OBJDIR)/allrepo_.c $(OBJDIR)/allrepo.h $(SRCDIR)/config.h + $(XTCC) -o $(OBJDIR)/allrepo.o -c $(OBJDIR)/allrepo_.c + +$(OBJDIR)/allrepo.h: $(OBJDIR)/headers + +$(OBJDIR)/attach_.c: $(SRCDIR)/attach.c $(OBJDIR)/translate + $(OBJDIR)/translate $(SRCDIR)/attach.c >$@ + +$(OBJDIR)/attach.o: $(OBJDIR)/attach_.c $(OBJDIR)/attach.h $(SRCDIR)/config.h + $(XTCC) -o $(OBJDIR)/attach.o -c $(OBJDIR)/attach_.c + +$(OBJDIR)/attach.h: $(OBJDIR)/headers + +$(OBJDIR)/backlink_.c: $(SRCDIR)/backlink.c $(OBJDIR)/translate + $(OBJDIR)/translate $(SRCDIR)/backlink.c >$@ + +$(OBJDIR)/backlink.o: $(OBJDIR)/backlink_.c $(OBJDIR)/backlink.h $(SRCDIR)/config.h + $(XTCC) -o $(OBJDIR)/backlink.o -c $(OBJDIR)/backlink_.c + +$(OBJDIR)/backlink.h: $(OBJDIR)/headers + +$(OBJDIR)/backoffice_.c: $(SRCDIR)/backoffice.c $(OBJDIR)/translate + $(OBJDIR)/translate $(SRCDIR)/backoffice.c >$@ + +$(OBJDIR)/backoffice.o: $(OBJDIR)/backoffice_.c $(OBJDIR)/backoffice.h $(SRCDIR)/config.h + $(XTCC) -o $(OBJDIR)/backoffice.o -c $(OBJDIR)/backoffice_.c + +$(OBJDIR)/backoffice.h: $(OBJDIR)/headers + +$(OBJDIR)/bag_.c: $(SRCDIR)/bag.c $(OBJDIR)/translate + $(OBJDIR)/translate $(SRCDIR)/bag.c >$@ + +$(OBJDIR)/bag.o: $(OBJDIR)/bag_.c $(OBJDIR)/bag.h $(SRCDIR)/config.h + $(XTCC) -o $(OBJDIR)/bag.o -c $(OBJDIR)/bag_.c + +$(OBJDIR)/bag.h: $(OBJDIR)/headers + +$(OBJDIR)/bisect_.c: $(SRCDIR)/bisect.c $(OBJDIR)/translate + $(OBJDIR)/translate $(SRCDIR)/bisect.c >$@ + +$(OBJDIR)/bisect.o: $(OBJDIR)/bisect_.c $(OBJDIR)/bisect.h $(SRCDIR)/config.h + $(XTCC) -o $(OBJDIR)/bisect.o -c $(OBJDIR)/bisect_.c + +$(OBJDIR)/bisect.h: $(OBJDIR)/headers + +$(OBJDIR)/blob_.c: $(SRCDIR)/blob.c $(OBJDIR)/translate + $(OBJDIR)/translate $(SRCDIR)/blob.c >$@ + +$(OBJDIR)/blob.o: $(OBJDIR)/blob_.c $(OBJDIR)/blob.h $(SRCDIR)/config.h + $(XTCC) -o $(OBJDIR)/blob.o -c $(OBJDIR)/blob_.c + +$(OBJDIR)/blob.h: $(OBJDIR)/headers + +$(OBJDIR)/branch_.c: $(SRCDIR)/branch.c $(OBJDIR)/translate + $(OBJDIR)/translate $(SRCDIR)/branch.c >$@ + +$(OBJDIR)/branch.o: $(OBJDIR)/branch_.c $(OBJDIR)/branch.h $(SRCDIR)/config.h + $(XTCC) -o $(OBJDIR)/branch.o -c $(OBJDIR)/branch_.c + +$(OBJDIR)/branch.h: $(OBJDIR)/headers + +$(OBJDIR)/browse_.c: $(SRCDIR)/browse.c $(OBJDIR)/translate + $(OBJDIR)/translate $(SRCDIR)/browse.c >$@ + +$(OBJDIR)/browse.o: $(OBJDIR)/browse_.c $(OBJDIR)/browse.h $(SRCDIR)/config.h + $(XTCC) -o $(OBJDIR)/browse.o -c $(OBJDIR)/browse_.c + +$(OBJDIR)/browse.h: $(OBJDIR)/headers + +$(OBJDIR)/builtin_.c: $(SRCDIR)/builtin.c $(OBJDIR)/translate + $(OBJDIR)/translate $(SRCDIR)/builtin.c >$@ + +$(OBJDIR)/builtin.o: $(OBJDIR)/builtin_.c $(OBJDIR)/builtin.h $(OBJDIR)/builtin_data.h $(SRCDIR)/config.h + $(XTCC) -o $(OBJDIR)/builtin.o -c $(OBJDIR)/builtin_.c + +$(OBJDIR)/builtin.h: $(OBJDIR)/headers + +$(OBJDIR)/bundle_.c: $(SRCDIR)/bundle.c $(OBJDIR)/translate + $(OBJDIR)/translate $(SRCDIR)/bundle.c >$@ + +$(OBJDIR)/bundle.o: $(OBJDIR)/bundle_.c $(OBJDIR)/bundle.h $(SRCDIR)/config.h + $(XTCC) -o $(OBJDIR)/bundle.o -c $(OBJDIR)/bundle_.c + +$(OBJDIR)/bundle.h: $(OBJDIR)/headers + +$(OBJDIR)/cache_.c: $(SRCDIR)/cache.c $(OBJDIR)/translate + $(OBJDIR)/translate $(SRCDIR)/cache.c >$@ + +$(OBJDIR)/cache.o: $(OBJDIR)/cache_.c $(OBJDIR)/cache.h $(SRCDIR)/config.h + $(XTCC) -o $(OBJDIR)/cache.o -c $(OBJDIR)/cache_.c + +$(OBJDIR)/cache.h: $(OBJDIR)/headers + +$(OBJDIR)/capabilities_.c: $(SRCDIR)/capabilities.c $(OBJDIR)/translate + $(OBJDIR)/translate $(SRCDIR)/capabilities.c >$@ + +$(OBJDIR)/capabilities.o: $(OBJDIR)/capabilities_.c $(OBJDIR)/capabilities.h $(SRCDIR)/config.h + $(XTCC) -o $(OBJDIR)/capabilities.o -c $(OBJDIR)/capabilities_.c + +$(OBJDIR)/capabilities.h: $(OBJDIR)/headers + +$(OBJDIR)/captcha_.c: $(SRCDIR)/captcha.c $(OBJDIR)/translate + $(OBJDIR)/translate $(SRCDIR)/captcha.c >$@ + +$(OBJDIR)/captcha.o: $(OBJDIR)/captcha_.c $(OBJDIR)/captcha.h $(SRCDIR)/config.h + $(XTCC) -o $(OBJDIR)/captcha.o -c $(OBJDIR)/captcha_.c + +$(OBJDIR)/captcha.h: $(OBJDIR)/headers + +$(OBJDIR)/cgi_.c: $(SRCDIR)/cgi.c $(OBJDIR)/translate + $(OBJDIR)/translate $(SRCDIR)/cgi.c >$@ + +$(OBJDIR)/cgi.o: $(OBJDIR)/cgi_.c $(OBJDIR)/cgi.h $(SRCDIR)/config.h + $(XTCC) -o $(OBJDIR)/cgi.o -c $(OBJDIR)/cgi_.c + +$(OBJDIR)/cgi.h: $(OBJDIR)/headers + +$(OBJDIR)/chat_.c: $(SRCDIR)/chat.c $(OBJDIR)/translate + $(OBJDIR)/translate $(SRCDIR)/chat.c >$@ + +$(OBJDIR)/chat.o: $(OBJDIR)/chat_.c $(OBJDIR)/chat.h $(SRCDIR)/config.h + $(XTCC) -o $(OBJDIR)/chat.o -c $(OBJDIR)/chat_.c + +$(OBJDIR)/chat.h: $(OBJDIR)/headers + +$(OBJDIR)/checkin_.c: $(SRCDIR)/checkin.c $(OBJDIR)/translate + $(OBJDIR)/translate $(SRCDIR)/checkin.c >$@ + +$(OBJDIR)/checkin.o: $(OBJDIR)/checkin_.c $(OBJDIR)/checkin.h $(SRCDIR)/config.h + $(XTCC) -o $(OBJDIR)/checkin.o -c $(OBJDIR)/checkin_.c + +$(OBJDIR)/checkin.h: $(OBJDIR)/headers + +$(OBJDIR)/checkout_.c: $(SRCDIR)/checkout.c $(OBJDIR)/translate + $(OBJDIR)/translate $(SRCDIR)/checkout.c >$@ + +$(OBJDIR)/checkout.o: $(OBJDIR)/checkout_.c $(OBJDIR)/checkout.h $(SRCDIR)/config.h + $(XTCC) -o $(OBJDIR)/checkout.o -c $(OBJDIR)/checkout_.c + +$(OBJDIR)/checkout.h: $(OBJDIR)/headers + +$(OBJDIR)/clearsign_.c: $(SRCDIR)/clearsign.c $(OBJDIR)/translate + $(OBJDIR)/translate $(SRCDIR)/clearsign.c >$@ + +$(OBJDIR)/clearsign.o: $(OBJDIR)/clearsign_.c $(OBJDIR)/clearsign.h $(SRCDIR)/config.h + $(XTCC) -o $(OBJDIR)/clearsign.o -c $(OBJDIR)/clearsign_.c + +$(OBJDIR)/clearsign.h: $(OBJDIR)/headers + +$(OBJDIR)/clone_.c: $(SRCDIR)/clone.c $(OBJDIR)/translate + $(OBJDIR)/translate $(SRCDIR)/clone.c >$@ + +$(OBJDIR)/clone.o: $(OBJDIR)/clone_.c $(OBJDIR)/clone.h $(SRCDIR)/config.h + $(XTCC) -o $(OBJDIR)/clone.o -c $(OBJDIR)/clone_.c + +$(OBJDIR)/clone.h: $(OBJDIR)/headers + +$(OBJDIR)/color_.c: $(SRCDIR)/color.c $(OBJDIR)/translate + $(OBJDIR)/translate $(SRCDIR)/color.c >$@ + +$(OBJDIR)/color.o: $(OBJDIR)/color_.c $(OBJDIR)/color.h $(SRCDIR)/config.h + $(XTCC) -o $(OBJDIR)/color.o -c $(OBJDIR)/color_.c + +$(OBJDIR)/color.h: $(OBJDIR)/headers + +$(OBJDIR)/comformat_.c: $(SRCDIR)/comformat.c $(OBJDIR)/translate + $(OBJDIR)/translate $(SRCDIR)/comformat.c >$@ + +$(OBJDIR)/comformat.o: $(OBJDIR)/comformat_.c $(OBJDIR)/comformat.h $(SRCDIR)/config.h + $(XTCC) -o $(OBJDIR)/comformat.o -c $(OBJDIR)/comformat_.c + +$(OBJDIR)/comformat.h: $(OBJDIR)/headers + +$(OBJDIR)/configure_.c: $(SRCDIR)/configure.c $(OBJDIR)/translate + $(OBJDIR)/translate $(SRCDIR)/configure.c >$@ + +$(OBJDIR)/configure.o: $(OBJDIR)/configure_.c $(OBJDIR)/configure.h $(SRCDIR)/config.h + $(XTCC) -o $(OBJDIR)/configure.o -c $(OBJDIR)/configure_.c + +$(OBJDIR)/configure.h: $(OBJDIR)/headers + +$(OBJDIR)/content_.c: $(SRCDIR)/content.c $(OBJDIR)/translate + $(OBJDIR)/translate $(SRCDIR)/content.c >$@ + +$(OBJDIR)/content.o: $(OBJDIR)/content_.c $(OBJDIR)/content.h $(SRCDIR)/config.h + $(XTCC) -o $(OBJDIR)/content.o -c $(OBJDIR)/content_.c + +$(OBJDIR)/content.h: $(OBJDIR)/headers + +$(OBJDIR)/cookies_.c: $(SRCDIR)/cookies.c $(OBJDIR)/translate + $(OBJDIR)/translate $(SRCDIR)/cookies.c >$@ + +$(OBJDIR)/cookies.o: $(OBJDIR)/cookies_.c $(OBJDIR)/cookies.h $(SRCDIR)/config.h + $(XTCC) -o $(OBJDIR)/cookies.o -c $(OBJDIR)/cookies_.c + +$(OBJDIR)/cookies.h: $(OBJDIR)/headers + +$(OBJDIR)/db_.c: $(SRCDIR)/db.c $(OBJDIR)/translate + $(OBJDIR)/translate $(SRCDIR)/db.c >$@ + +$(OBJDIR)/db.o: $(OBJDIR)/db_.c $(OBJDIR)/db.h $(SRCDIR)/config.h + $(XTCC) -o $(OBJDIR)/db.o -c $(OBJDIR)/db_.c + +$(OBJDIR)/db.h: $(OBJDIR)/headers + +$(OBJDIR)/delta_.c: $(SRCDIR)/delta.c $(OBJDIR)/translate + $(OBJDIR)/translate $(SRCDIR)/delta.c >$@ + +$(OBJDIR)/delta.o: $(OBJDIR)/delta_.c $(OBJDIR)/delta.h $(SRCDIR)/config.h + $(XTCC) -o $(OBJDIR)/delta.o -c $(OBJDIR)/delta_.c + +$(OBJDIR)/delta.h: $(OBJDIR)/headers + +$(OBJDIR)/deltacmd_.c: $(SRCDIR)/deltacmd.c $(OBJDIR)/translate + $(OBJDIR)/translate $(SRCDIR)/deltacmd.c >$@ + +$(OBJDIR)/deltacmd.o: $(OBJDIR)/deltacmd_.c $(OBJDIR)/deltacmd.h $(SRCDIR)/config.h + $(XTCC) -o $(OBJDIR)/deltacmd.o -c $(OBJDIR)/deltacmd_.c + +$(OBJDIR)/deltacmd.h: $(OBJDIR)/headers + +$(OBJDIR)/deltafunc_.c: $(SRCDIR)/deltafunc.c $(OBJDIR)/translate + $(OBJDIR)/translate $(SRCDIR)/deltafunc.c >$@ + +$(OBJDIR)/deltafunc.o: $(OBJDIR)/deltafunc_.c $(OBJDIR)/deltafunc.h $(SRCDIR)/config.h + $(XTCC) -o $(OBJDIR)/deltafunc.o -c $(OBJDIR)/deltafunc_.c + +$(OBJDIR)/deltafunc.h: $(OBJDIR)/headers + +$(OBJDIR)/descendants_.c: $(SRCDIR)/descendants.c $(OBJDIR)/translate + $(OBJDIR)/translate $(SRCDIR)/descendants.c >$@ + +$(OBJDIR)/descendants.o: $(OBJDIR)/descendants_.c $(OBJDIR)/descendants.h $(SRCDIR)/config.h + $(XTCC) -o $(OBJDIR)/descendants.o -c $(OBJDIR)/descendants_.c + +$(OBJDIR)/descendants.h: $(OBJDIR)/headers + +$(OBJDIR)/diff_.c: $(SRCDIR)/diff.c $(OBJDIR)/translate + $(OBJDIR)/translate $(SRCDIR)/diff.c >$@ + +$(OBJDIR)/diff.o: $(OBJDIR)/diff_.c $(OBJDIR)/diff.h $(SRCDIR)/config.h + $(XTCC) -o $(OBJDIR)/diff.o -c $(OBJDIR)/diff_.c + +$(OBJDIR)/diff.h: $(OBJDIR)/headers + +$(OBJDIR)/diffcmd_.c: $(SRCDIR)/diffcmd.c $(OBJDIR)/translate + $(OBJDIR)/translate $(SRCDIR)/diffcmd.c >$@ + +$(OBJDIR)/diffcmd.o: $(OBJDIR)/diffcmd_.c $(OBJDIR)/diffcmd.h $(SRCDIR)/config.h + $(XTCC) -o $(OBJDIR)/diffcmd.o -c $(OBJDIR)/diffcmd_.c + +$(OBJDIR)/diffcmd.h: $(OBJDIR)/headers + +$(OBJDIR)/dispatch_.c: $(SRCDIR)/dispatch.c $(OBJDIR)/translate + $(OBJDIR)/translate $(SRCDIR)/dispatch.c >$@ + +$(OBJDIR)/dispatch.o: $(OBJDIR)/dispatch_.c $(OBJDIR)/dispatch.h $(OBJDIR)/page_index.h $(SRCDIR)/config.h + $(XTCC) -o $(OBJDIR)/dispatch.o -c $(OBJDIR)/dispatch_.c + +$(OBJDIR)/dispatch.h: $(OBJDIR)/headers + +$(OBJDIR)/doc_.c: $(SRCDIR)/doc.c $(OBJDIR)/translate + $(OBJDIR)/translate $(SRCDIR)/doc.c >$@ + +$(OBJDIR)/doc.o: $(OBJDIR)/doc_.c $(OBJDIR)/doc.h $(SRCDIR)/config.h + $(XTCC) -o $(OBJDIR)/doc.o -c $(OBJDIR)/doc_.c + +$(OBJDIR)/doc.h: $(OBJDIR)/headers + +$(OBJDIR)/encode_.c: $(SRCDIR)/encode.c $(OBJDIR)/translate + $(OBJDIR)/translate $(SRCDIR)/encode.c >$@ + +$(OBJDIR)/encode.o: $(OBJDIR)/encode_.c $(OBJDIR)/encode.h $(SRCDIR)/config.h + $(XTCC) -o $(OBJDIR)/encode.o -c $(OBJDIR)/encode_.c + +$(OBJDIR)/encode.h: $(OBJDIR)/headers + +$(OBJDIR)/etag_.c: $(SRCDIR)/etag.c $(OBJDIR)/translate + $(OBJDIR)/translate $(SRCDIR)/etag.c >$@ + +$(OBJDIR)/etag.o: $(OBJDIR)/etag_.c $(OBJDIR)/etag.h $(SRCDIR)/config.h + $(XTCC) -o $(OBJDIR)/etag.o -c $(OBJDIR)/etag_.c + +$(OBJDIR)/etag.h: $(OBJDIR)/headers + +$(OBJDIR)/event_.c: $(SRCDIR)/event.c $(OBJDIR)/translate + $(OBJDIR)/translate $(SRCDIR)/event.c >$@ + +$(OBJDIR)/event.o: $(OBJDIR)/event_.c $(OBJDIR)/event.h $(SRCDIR)/config.h + $(XTCC) -o $(OBJDIR)/event.o -c $(OBJDIR)/event_.c + +$(OBJDIR)/event.h: $(OBJDIR)/headers + +$(OBJDIR)/export_.c: $(SRCDIR)/export.c $(OBJDIR)/translate + $(OBJDIR)/translate $(SRCDIR)/export.c >$@ + +$(OBJDIR)/export.o: $(OBJDIR)/export_.c $(OBJDIR)/export.h $(SRCDIR)/config.h + $(XTCC) -o $(OBJDIR)/export.o -c $(OBJDIR)/export_.c + +$(OBJDIR)/export.h: $(OBJDIR)/headers + +$(OBJDIR)/extcgi_.c: $(SRCDIR)/extcgi.c $(OBJDIR)/translate + $(OBJDIR)/translate $(SRCDIR)/extcgi.c >$@ + +$(OBJDIR)/extcgi.o: $(OBJDIR)/extcgi_.c $(OBJDIR)/extcgi.h $(SRCDIR)/config.h + $(XTCC) -o $(OBJDIR)/extcgi.o -c $(OBJDIR)/extcgi_.c + +$(OBJDIR)/extcgi.h: $(OBJDIR)/headers + +$(OBJDIR)/file_.c: $(SRCDIR)/file.c $(OBJDIR)/translate + $(OBJDIR)/translate $(SRCDIR)/file.c >$@ + +$(OBJDIR)/file.o: $(OBJDIR)/file_.c $(OBJDIR)/file.h $(SRCDIR)/config.h + $(XTCC) -o $(OBJDIR)/file.o -c $(OBJDIR)/file_.c + +$(OBJDIR)/file.h: $(OBJDIR)/headers + +$(OBJDIR)/fileedit_.c: $(SRCDIR)/fileedit.c $(OBJDIR)/translate + $(OBJDIR)/translate $(SRCDIR)/fileedit.c >$@ + +$(OBJDIR)/fileedit.o: $(OBJDIR)/fileedit_.c $(OBJDIR)/fileedit.h $(SRCDIR)/config.h + $(XTCC) -o $(OBJDIR)/fileedit.o -c $(OBJDIR)/fileedit_.c + +$(OBJDIR)/fileedit.h: $(OBJDIR)/headers + +$(OBJDIR)/finfo_.c: $(SRCDIR)/finfo.c $(OBJDIR)/translate + $(OBJDIR)/translate $(SRCDIR)/finfo.c >$@ + +$(OBJDIR)/finfo.o: $(OBJDIR)/finfo_.c $(OBJDIR)/finfo.h $(SRCDIR)/config.h + $(XTCC) -o $(OBJDIR)/finfo.o -c $(OBJDIR)/finfo_.c + +$(OBJDIR)/finfo.h: $(OBJDIR)/headers + +$(OBJDIR)/foci_.c: $(SRCDIR)/foci.c $(OBJDIR)/translate + $(OBJDIR)/translate $(SRCDIR)/foci.c >$@ + +$(OBJDIR)/foci.o: $(OBJDIR)/foci_.c $(OBJDIR)/foci.h $(SRCDIR)/config.h + $(XTCC) -o $(OBJDIR)/foci.o -c $(OBJDIR)/foci_.c + +$(OBJDIR)/foci.h: $(OBJDIR)/headers + +$(OBJDIR)/forum_.c: $(SRCDIR)/forum.c $(OBJDIR)/translate + $(OBJDIR)/translate $(SRCDIR)/forum.c >$@ + +$(OBJDIR)/forum.o: $(OBJDIR)/forum_.c $(OBJDIR)/forum.h $(SRCDIR)/config.h + $(XTCC) -o $(OBJDIR)/forum.o -c $(OBJDIR)/forum_.c + +$(OBJDIR)/forum.h: $(OBJDIR)/headers + +$(OBJDIR)/fshell_.c: $(SRCDIR)/fshell.c $(OBJDIR)/translate + $(OBJDIR)/translate $(SRCDIR)/fshell.c >$@ + +$(OBJDIR)/fshell.o: $(OBJDIR)/fshell_.c $(OBJDIR)/fshell.h $(SRCDIR)/config.h + $(XTCC) -o $(OBJDIR)/fshell.o -c $(OBJDIR)/fshell_.c + +$(OBJDIR)/fshell.h: $(OBJDIR)/headers + +$(OBJDIR)/fusefs_.c: $(SRCDIR)/fusefs.c $(OBJDIR)/translate + $(OBJDIR)/translate $(SRCDIR)/fusefs.c >$@ + +$(OBJDIR)/fusefs.o: $(OBJDIR)/fusefs_.c $(OBJDIR)/fusefs.h $(SRCDIR)/config.h + $(XTCC) -o $(OBJDIR)/fusefs.o -c $(OBJDIR)/fusefs_.c + +$(OBJDIR)/fusefs.h: $(OBJDIR)/headers + +$(OBJDIR)/fuzz_.c: $(SRCDIR)/fuzz.c $(OBJDIR)/translate + $(OBJDIR)/translate $(SRCDIR)/fuzz.c >$@ + +$(OBJDIR)/fuzz.o: $(OBJDIR)/fuzz_.c $(OBJDIR)/fuzz.h $(SRCDIR)/config.h + $(XTCC) -o $(OBJDIR)/fuzz.o -c $(OBJDIR)/fuzz_.c + +$(OBJDIR)/fuzz.h: $(OBJDIR)/headers + +$(OBJDIR)/glob_.c: $(SRCDIR)/glob.c $(OBJDIR)/translate + $(OBJDIR)/translate $(SRCDIR)/glob.c >$@ + +$(OBJDIR)/glob.o: $(OBJDIR)/glob_.c $(OBJDIR)/glob.h $(SRCDIR)/config.h + $(XTCC) -o $(OBJDIR)/glob.o -c $(OBJDIR)/glob_.c + +$(OBJDIR)/glob.h: $(OBJDIR)/headers + +$(OBJDIR)/graph_.c: $(SRCDIR)/graph.c $(OBJDIR)/translate + $(OBJDIR)/translate $(SRCDIR)/graph.c >$@ + +$(OBJDIR)/graph.o: $(OBJDIR)/graph_.c $(OBJDIR)/graph.h $(SRCDIR)/config.h + $(XTCC) -o $(OBJDIR)/graph.o -c $(OBJDIR)/graph_.c + +$(OBJDIR)/graph.h: $(OBJDIR)/headers + +$(OBJDIR)/gzip_.c: $(SRCDIR)/gzip.c $(OBJDIR)/translate + $(OBJDIR)/translate $(SRCDIR)/gzip.c >$@ + +$(OBJDIR)/gzip.o: $(OBJDIR)/gzip_.c $(OBJDIR)/gzip.h $(SRCDIR)/config.h + $(XTCC) -o $(OBJDIR)/gzip.o -c $(OBJDIR)/gzip_.c + +$(OBJDIR)/gzip.h: $(OBJDIR)/headers + +$(OBJDIR)/hname_.c: $(SRCDIR)/hname.c $(OBJDIR)/translate + $(OBJDIR)/translate $(SRCDIR)/hname.c >$@ + +$(OBJDIR)/hname.o: $(OBJDIR)/hname_.c $(OBJDIR)/hname.h $(SRCDIR)/config.h + $(XTCC) -o $(OBJDIR)/hname.o -c $(OBJDIR)/hname_.c + +$(OBJDIR)/hname.h: $(OBJDIR)/headers + +$(OBJDIR)/hook_.c: $(SRCDIR)/hook.c $(OBJDIR)/translate + $(OBJDIR)/translate $(SRCDIR)/hook.c >$@ + +$(OBJDIR)/hook.o: $(OBJDIR)/hook_.c $(OBJDIR)/hook.h $(SRCDIR)/config.h + $(XTCC) -o $(OBJDIR)/hook.o -c $(OBJDIR)/hook_.c + +$(OBJDIR)/hook.h: $(OBJDIR)/headers + +$(OBJDIR)/http_.c: $(SRCDIR)/http.c $(OBJDIR)/translate + $(OBJDIR)/translate $(SRCDIR)/http.c >$@ + +$(OBJDIR)/http.o: $(OBJDIR)/http_.c $(OBJDIR)/http.h $(SRCDIR)/config.h + $(XTCC) -o $(OBJDIR)/http.o -c $(OBJDIR)/http_.c + +$(OBJDIR)/http.h: $(OBJDIR)/headers + +$(OBJDIR)/http_socket_.c: $(SRCDIR)/http_socket.c $(OBJDIR)/translate + $(OBJDIR)/translate $(SRCDIR)/http_socket.c >$@ + +$(OBJDIR)/http_socket.o: $(OBJDIR)/http_socket_.c $(OBJDIR)/http_socket.h $(SRCDIR)/config.h + $(XTCC) -o $(OBJDIR)/http_socket.o -c $(OBJDIR)/http_socket_.c + +$(OBJDIR)/http_socket.h: $(OBJDIR)/headers + +$(OBJDIR)/http_ssl_.c: $(SRCDIR)/http_ssl.c $(OBJDIR)/translate + $(OBJDIR)/translate $(SRCDIR)/http_ssl.c >$@ + +$(OBJDIR)/http_ssl.o: $(OBJDIR)/http_ssl_.c $(OBJDIR)/http_ssl.h $(SRCDIR)/config.h + $(XTCC) -o $(OBJDIR)/http_ssl.o -c $(OBJDIR)/http_ssl_.c + +$(OBJDIR)/http_ssl.h: $(OBJDIR)/headers + +$(OBJDIR)/http_transport_.c: $(SRCDIR)/http_transport.c $(OBJDIR)/translate + $(OBJDIR)/translate $(SRCDIR)/http_transport.c >$@ + +$(OBJDIR)/http_transport.o: $(OBJDIR)/http_transport_.c $(OBJDIR)/http_transport.h $(SRCDIR)/config.h + $(XTCC) -o $(OBJDIR)/http_transport.o -c $(OBJDIR)/http_transport_.c + +$(OBJDIR)/http_transport.h: $(OBJDIR)/headers + +$(OBJDIR)/import_.c: $(SRCDIR)/import.c $(OBJDIR)/translate + $(OBJDIR)/translate $(SRCDIR)/import.c >$@ + +$(OBJDIR)/import.o: $(OBJDIR)/import_.c $(OBJDIR)/import.h $(SRCDIR)/config.h + $(XTCC) -o $(OBJDIR)/import.o -c $(OBJDIR)/import_.c + +$(OBJDIR)/import.h: $(OBJDIR)/headers + +$(OBJDIR)/info_.c: $(SRCDIR)/info.c $(OBJDIR)/translate + $(OBJDIR)/translate $(SRCDIR)/info.c >$@ + +$(OBJDIR)/info.o: $(OBJDIR)/info_.c $(OBJDIR)/info.h $(SRCDIR)/config.h + $(XTCC) -o $(OBJDIR)/info.o -c $(OBJDIR)/info_.c + +$(OBJDIR)/info.h: $(OBJDIR)/headers + +$(OBJDIR)/interwiki_.c: $(SRCDIR)/interwiki.c $(OBJDIR)/translate + $(OBJDIR)/translate $(SRCDIR)/interwiki.c >$@ + +$(OBJDIR)/interwiki.o: $(OBJDIR)/interwiki_.c $(OBJDIR)/interwiki.h $(SRCDIR)/config.h + $(XTCC) -o $(OBJDIR)/interwiki.o -c $(OBJDIR)/interwiki_.c + +$(OBJDIR)/interwiki.h: $(OBJDIR)/headers + +$(OBJDIR)/json_.c: $(SRCDIR)/json.c $(OBJDIR)/translate + $(OBJDIR)/translate $(SRCDIR)/json.c >$@ + +$(OBJDIR)/json.o: $(OBJDIR)/json_.c $(OBJDIR)/json.h $(SRCDIR)/config.h + $(XTCC) -o $(OBJDIR)/json.o -c $(OBJDIR)/json_.c + +$(OBJDIR)/json.h: $(OBJDIR)/headers + +$(OBJDIR)/json_artifact_.c: $(SRCDIR)/json_artifact.c $(OBJDIR)/translate + $(OBJDIR)/translate $(SRCDIR)/json_artifact.c >$@ + +$(OBJDIR)/json_artifact.o: $(OBJDIR)/json_artifact_.c $(OBJDIR)/json_artifact.h $(SRCDIR)/config.h + $(XTCC) -o $(OBJDIR)/json_artifact.o -c $(OBJDIR)/json_artifact_.c + +$(OBJDIR)/json_artifact.h: $(OBJDIR)/headers + +$(OBJDIR)/json_branch_.c: $(SRCDIR)/json_branch.c $(OBJDIR)/translate + $(OBJDIR)/translate $(SRCDIR)/json_branch.c >$@ + +$(OBJDIR)/json_branch.o: $(OBJDIR)/json_branch_.c $(OBJDIR)/json_branch.h $(SRCDIR)/config.h + $(XTCC) -o $(OBJDIR)/json_branch.o -c $(OBJDIR)/json_branch_.c + +$(OBJDIR)/json_branch.h: $(OBJDIR)/headers + +$(OBJDIR)/json_config_.c: $(SRCDIR)/json_config.c $(OBJDIR)/translate + $(OBJDIR)/translate $(SRCDIR)/json_config.c >$@ + +$(OBJDIR)/json_config.o: $(OBJDIR)/json_config_.c $(OBJDIR)/json_config.h $(SRCDIR)/config.h + $(XTCC) -o $(OBJDIR)/json_config.o -c $(OBJDIR)/json_config_.c + +$(OBJDIR)/json_config.h: $(OBJDIR)/headers + +$(OBJDIR)/json_diff_.c: $(SRCDIR)/json_diff.c $(OBJDIR)/translate + $(OBJDIR)/translate $(SRCDIR)/json_diff.c >$@ + +$(OBJDIR)/json_diff.o: $(OBJDIR)/json_diff_.c $(OBJDIR)/json_diff.h $(SRCDIR)/config.h + $(XTCC) -o $(OBJDIR)/json_diff.o -c $(OBJDIR)/json_diff_.c + +$(OBJDIR)/json_diff.h: $(OBJDIR)/headers + +$(OBJDIR)/json_dir_.c: $(SRCDIR)/json_dir.c $(OBJDIR)/translate + $(OBJDIR)/translate $(SRCDIR)/json_dir.c >$@ + +$(OBJDIR)/json_dir.o: $(OBJDIR)/json_dir_.c $(OBJDIR)/json_dir.h $(SRCDIR)/config.h + $(XTCC) -o $(OBJDIR)/json_dir.o -c $(OBJDIR)/json_dir_.c + +$(OBJDIR)/json_dir.h: $(OBJDIR)/headers + +$(OBJDIR)/json_finfo_.c: $(SRCDIR)/json_finfo.c $(OBJDIR)/translate + $(OBJDIR)/translate $(SRCDIR)/json_finfo.c >$@ + +$(OBJDIR)/json_finfo.o: $(OBJDIR)/json_finfo_.c $(OBJDIR)/json_finfo.h $(SRCDIR)/config.h + $(XTCC) -o $(OBJDIR)/json_finfo.o -c $(OBJDIR)/json_finfo_.c + +$(OBJDIR)/json_finfo.h: $(OBJDIR)/headers + +$(OBJDIR)/json_login_.c: $(SRCDIR)/json_login.c $(OBJDIR)/translate + $(OBJDIR)/translate $(SRCDIR)/json_login.c >$@ + +$(OBJDIR)/json_login.o: $(OBJDIR)/json_login_.c $(OBJDIR)/json_login.h $(SRCDIR)/config.h + $(XTCC) -o $(OBJDIR)/json_login.o -c $(OBJDIR)/json_login_.c + +$(OBJDIR)/json_login.h: $(OBJDIR)/headers + +$(OBJDIR)/json_query_.c: $(SRCDIR)/json_query.c $(OBJDIR)/translate + $(OBJDIR)/translate $(SRCDIR)/json_query.c >$@ + +$(OBJDIR)/json_query.o: $(OBJDIR)/json_query_.c $(OBJDIR)/json_query.h $(SRCDIR)/config.h + $(XTCC) -o $(OBJDIR)/json_query.o -c $(OBJDIR)/json_query_.c + +$(OBJDIR)/json_query.h: $(OBJDIR)/headers + +$(OBJDIR)/json_report_.c: $(SRCDIR)/json_report.c $(OBJDIR)/translate + $(OBJDIR)/translate $(SRCDIR)/json_report.c >$@ + +$(OBJDIR)/json_report.o: $(OBJDIR)/json_report_.c $(OBJDIR)/json_report.h $(SRCDIR)/config.h + $(XTCC) -o $(OBJDIR)/json_report.o -c $(OBJDIR)/json_report_.c + +$(OBJDIR)/json_report.h: $(OBJDIR)/headers + +$(OBJDIR)/json_status_.c: $(SRCDIR)/json_status.c $(OBJDIR)/translate + $(OBJDIR)/translate $(SRCDIR)/json_status.c >$@ + +$(OBJDIR)/json_status.o: $(OBJDIR)/json_status_.c $(OBJDIR)/json_status.h $(SRCDIR)/config.h + $(XTCC) -o $(OBJDIR)/json_status.o -c $(OBJDIR)/json_status_.c + +$(OBJDIR)/json_status.h: $(OBJDIR)/headers + +$(OBJDIR)/json_tag_.c: $(SRCDIR)/json_tag.c $(OBJDIR)/translate + $(OBJDIR)/translate $(SRCDIR)/json_tag.c >$@ + +$(OBJDIR)/json_tag.o: $(OBJDIR)/json_tag_.c $(OBJDIR)/json_tag.h $(SRCDIR)/config.h + $(XTCC) -o $(OBJDIR)/json_tag.o -c $(OBJDIR)/json_tag_.c + +$(OBJDIR)/json_tag.h: $(OBJDIR)/headers + +$(OBJDIR)/json_timeline_.c: $(SRCDIR)/json_timeline.c $(OBJDIR)/translate + $(OBJDIR)/translate $(SRCDIR)/json_timeline.c >$@ + +$(OBJDIR)/json_timeline.o: $(OBJDIR)/json_timeline_.c $(OBJDIR)/json_timeline.h $(SRCDIR)/config.h + $(XTCC) -o $(OBJDIR)/json_timeline.o -c $(OBJDIR)/json_timeline_.c + +$(OBJDIR)/json_timeline.h: $(OBJDIR)/headers + +$(OBJDIR)/json_user_.c: $(SRCDIR)/json_user.c $(OBJDIR)/translate + $(OBJDIR)/translate $(SRCDIR)/json_user.c >$@ + +$(OBJDIR)/json_user.o: $(OBJDIR)/json_user_.c $(OBJDIR)/json_user.h $(SRCDIR)/config.h + $(XTCC) -o $(OBJDIR)/json_user.o -c $(OBJDIR)/json_user_.c + +$(OBJDIR)/json_user.h: $(OBJDIR)/headers + +$(OBJDIR)/json_wiki_.c: $(SRCDIR)/json_wiki.c $(OBJDIR)/translate + $(OBJDIR)/translate $(SRCDIR)/json_wiki.c >$@ + +$(OBJDIR)/json_wiki.o: $(OBJDIR)/json_wiki_.c $(OBJDIR)/json_wiki.h $(SRCDIR)/config.h + $(XTCC) -o $(OBJDIR)/json_wiki.o -c $(OBJDIR)/json_wiki_.c + +$(OBJDIR)/json_wiki.h: $(OBJDIR)/headers + +$(OBJDIR)/leaf_.c: $(SRCDIR)/leaf.c $(OBJDIR)/translate + $(OBJDIR)/translate $(SRCDIR)/leaf.c >$@ + +$(OBJDIR)/leaf.o: $(OBJDIR)/leaf_.c $(OBJDIR)/leaf.h $(SRCDIR)/config.h + $(XTCC) -o $(OBJDIR)/leaf.o -c $(OBJDIR)/leaf_.c + +$(OBJDIR)/leaf.h: $(OBJDIR)/headers + +$(OBJDIR)/loadctrl_.c: $(SRCDIR)/loadctrl.c $(OBJDIR)/translate + $(OBJDIR)/translate $(SRCDIR)/loadctrl.c >$@ + +$(OBJDIR)/loadctrl.o: $(OBJDIR)/loadctrl_.c $(OBJDIR)/loadctrl.h $(SRCDIR)/config.h + $(XTCC) -o $(OBJDIR)/loadctrl.o -c $(OBJDIR)/loadctrl_.c + +$(OBJDIR)/loadctrl.h: $(OBJDIR)/headers + +$(OBJDIR)/login_.c: $(SRCDIR)/login.c $(OBJDIR)/translate + $(OBJDIR)/translate $(SRCDIR)/login.c >$@ + +$(OBJDIR)/login.o: $(OBJDIR)/login_.c $(OBJDIR)/login.h $(SRCDIR)/config.h + $(XTCC) -o $(OBJDIR)/login.o -c $(OBJDIR)/login_.c + +$(OBJDIR)/login.h: $(OBJDIR)/headers + +$(OBJDIR)/lookslike_.c: $(SRCDIR)/lookslike.c $(OBJDIR)/translate + $(OBJDIR)/translate $(SRCDIR)/lookslike.c >$@ + +$(OBJDIR)/lookslike.o: $(OBJDIR)/lookslike_.c $(OBJDIR)/lookslike.h $(SRCDIR)/config.h + $(XTCC) -o $(OBJDIR)/lookslike.o -c $(OBJDIR)/lookslike_.c + +$(OBJDIR)/lookslike.h: $(OBJDIR)/headers + +$(OBJDIR)/main_.c: $(SRCDIR)/main.c $(OBJDIR)/translate + $(OBJDIR)/translate $(SRCDIR)/main.c >$@ + +$(OBJDIR)/main.o: $(OBJDIR)/main_.c $(OBJDIR)/main.h $(SRCDIR)/config.h + $(XTCC) -o $(OBJDIR)/main.o -c $(OBJDIR)/main_.c + +$(OBJDIR)/main.h: $(OBJDIR)/headers + +$(OBJDIR)/manifest_.c: $(SRCDIR)/manifest.c $(OBJDIR)/translate + $(OBJDIR)/translate $(SRCDIR)/manifest.c >$@ + +$(OBJDIR)/manifest.o: $(OBJDIR)/manifest_.c $(OBJDIR)/manifest.h $(SRCDIR)/config.h + $(XTCC) -o $(OBJDIR)/manifest.o -c $(OBJDIR)/manifest_.c + +$(OBJDIR)/manifest.h: $(OBJDIR)/headers + +$(OBJDIR)/markdown_.c: $(SRCDIR)/markdown.c $(OBJDIR)/translate + $(OBJDIR)/translate $(SRCDIR)/markdown.c >$@ + +$(OBJDIR)/markdown.o: $(OBJDIR)/markdown_.c $(OBJDIR)/markdown.h $(SRCDIR)/config.h + $(XTCC) -o $(OBJDIR)/markdown.o -c $(OBJDIR)/markdown_.c + +$(OBJDIR)/markdown.h: $(OBJDIR)/headers + +$(OBJDIR)/markdown_html_.c: $(SRCDIR)/markdown_html.c $(OBJDIR)/translate + $(OBJDIR)/translate $(SRCDIR)/markdown_html.c >$@ + +$(OBJDIR)/markdown_html.o: $(OBJDIR)/markdown_html_.c $(OBJDIR)/markdown_html.h $(SRCDIR)/config.h + $(XTCC) -o $(OBJDIR)/markdown_html.o -c $(OBJDIR)/markdown_html_.c + +$(OBJDIR)/markdown_html.h: $(OBJDIR)/headers + +$(OBJDIR)/md5_.c: $(SRCDIR)/md5.c $(OBJDIR)/translate + $(OBJDIR)/translate $(SRCDIR)/md5.c >$@ + +$(OBJDIR)/md5.o: $(OBJDIR)/md5_.c $(OBJDIR)/md5.h $(SRCDIR)/config.h + $(XTCC) -o $(OBJDIR)/md5.o -c $(OBJDIR)/md5_.c + +$(OBJDIR)/md5.h: $(OBJDIR)/headers + +$(OBJDIR)/merge_.c: $(SRCDIR)/merge.c $(OBJDIR)/translate + $(OBJDIR)/translate $(SRCDIR)/merge.c >$@ + +$(OBJDIR)/merge.o: $(OBJDIR)/merge_.c $(OBJDIR)/merge.h $(SRCDIR)/config.h + $(XTCC) -o $(OBJDIR)/merge.o -c $(OBJDIR)/merge_.c + +$(OBJDIR)/merge.h: $(OBJDIR)/headers + +$(OBJDIR)/merge3_.c: $(SRCDIR)/merge3.c $(OBJDIR)/translate + $(OBJDIR)/translate $(SRCDIR)/merge3.c >$@ + +$(OBJDIR)/merge3.o: $(OBJDIR)/merge3_.c $(OBJDIR)/merge3.h $(SRCDIR)/config.h + $(XTCC) -o $(OBJDIR)/merge3.o -c $(OBJDIR)/merge3_.c + +$(OBJDIR)/merge3.h: $(OBJDIR)/headers + +$(OBJDIR)/moderate_.c: $(SRCDIR)/moderate.c $(OBJDIR)/translate + $(OBJDIR)/translate $(SRCDIR)/moderate.c >$@ + +$(OBJDIR)/moderate.o: $(OBJDIR)/moderate_.c $(OBJDIR)/moderate.h $(SRCDIR)/config.h + $(XTCC) -o $(OBJDIR)/moderate.o -c $(OBJDIR)/moderate_.c + +$(OBJDIR)/moderate.h: $(OBJDIR)/headers + +$(OBJDIR)/name_.c: $(SRCDIR)/name.c $(OBJDIR)/translate + $(OBJDIR)/translate $(SRCDIR)/name.c >$@ + +$(OBJDIR)/name.o: $(OBJDIR)/name_.c $(OBJDIR)/name.h $(SRCDIR)/config.h + $(XTCC) -o $(OBJDIR)/name.o -c $(OBJDIR)/name_.c + +$(OBJDIR)/name.h: $(OBJDIR)/headers + +$(OBJDIR)/patch_.c: $(SRCDIR)/patch.c $(OBJDIR)/translate + $(OBJDIR)/translate $(SRCDIR)/patch.c >$@ + +$(OBJDIR)/patch.o: $(OBJDIR)/patch_.c $(OBJDIR)/patch.h $(SRCDIR)/config.h + $(XTCC) -o $(OBJDIR)/patch.o -c $(OBJDIR)/patch_.c + +$(OBJDIR)/patch.h: $(OBJDIR)/headers + +$(OBJDIR)/path_.c: $(SRCDIR)/path.c $(OBJDIR)/translate + $(OBJDIR)/translate $(SRCDIR)/path.c >$@ + +$(OBJDIR)/path.o: $(OBJDIR)/path_.c $(OBJDIR)/path.h $(SRCDIR)/config.h + $(XTCC) -o $(OBJDIR)/path.o -c $(OBJDIR)/path_.c + +$(OBJDIR)/path.h: $(OBJDIR)/headers + +$(OBJDIR)/piechart_.c: $(SRCDIR)/piechart.c $(OBJDIR)/translate + $(OBJDIR)/translate $(SRCDIR)/piechart.c >$@ + +$(OBJDIR)/piechart.o: $(OBJDIR)/piechart_.c $(OBJDIR)/piechart.h $(SRCDIR)/config.h + $(XTCC) -o $(OBJDIR)/piechart.o -c $(OBJDIR)/piechart_.c + +$(OBJDIR)/piechart.h: $(OBJDIR)/headers + +$(OBJDIR)/pikchr_.c: $(SRCDIR)/pikchr.c $(OBJDIR)/translate + $(OBJDIR)/translate $(SRCDIR)/pikchr.c >$@ + +$(OBJDIR)/pikchr.o: $(OBJDIR)/pikchr_.c $(OBJDIR)/pikchr.h $(SRCDIR)/config.h + $(XTCC) -o $(OBJDIR)/pikchr.o -c $(OBJDIR)/pikchr_.c + +$(OBJDIR)/pikchr.h: $(OBJDIR)/headers + +$(OBJDIR)/pikchrshow_.c: $(SRCDIR)/pikchrshow.c $(OBJDIR)/translate + $(OBJDIR)/translate $(SRCDIR)/pikchrshow.c >$@ + +$(OBJDIR)/pikchrshow.o: $(OBJDIR)/pikchrshow_.c $(OBJDIR)/pikchrshow.h $(SRCDIR)/config.h + $(XTCC) -o $(OBJDIR)/pikchrshow.o -c $(OBJDIR)/pikchrshow_.c + +$(OBJDIR)/pikchrshow.h: $(OBJDIR)/headers + +$(OBJDIR)/pivot_.c: $(SRCDIR)/pivot.c $(OBJDIR)/translate + $(OBJDIR)/translate $(SRCDIR)/pivot.c >$@ + +$(OBJDIR)/pivot.o: $(OBJDIR)/pivot_.c $(OBJDIR)/pivot.h $(SRCDIR)/config.h + $(XTCC) -o $(OBJDIR)/pivot.o -c $(OBJDIR)/pivot_.c + +$(OBJDIR)/pivot.h: $(OBJDIR)/headers + +$(OBJDIR)/popen_.c: $(SRCDIR)/popen.c $(OBJDIR)/translate + $(OBJDIR)/translate $(SRCDIR)/popen.c >$@ + +$(OBJDIR)/popen.o: $(OBJDIR)/popen_.c $(OBJDIR)/popen.h $(SRCDIR)/config.h + $(XTCC) -o $(OBJDIR)/popen.o -c $(OBJDIR)/popen_.c + +$(OBJDIR)/popen.h: $(OBJDIR)/headers + +$(OBJDIR)/pqueue_.c: $(SRCDIR)/pqueue.c $(OBJDIR)/translate + $(OBJDIR)/translate $(SRCDIR)/pqueue.c >$@ + +$(OBJDIR)/pqueue.o: $(OBJDIR)/pqueue_.c $(OBJDIR)/pqueue.h $(SRCDIR)/config.h + $(XTCC) -o $(OBJDIR)/pqueue.o -c $(OBJDIR)/pqueue_.c + +$(OBJDIR)/pqueue.h: $(OBJDIR)/headers + +$(OBJDIR)/printf_.c: $(SRCDIR)/printf.c $(OBJDIR)/translate + $(OBJDIR)/translate $(SRCDIR)/printf.c >$@ + +$(OBJDIR)/printf.o: $(OBJDIR)/printf_.c $(OBJDIR)/printf.h $(SRCDIR)/config.h + $(XTCC) -o $(OBJDIR)/printf.o -c $(OBJDIR)/printf_.c + +$(OBJDIR)/printf.h: $(OBJDIR)/headers + +$(OBJDIR)/publish_.c: $(SRCDIR)/publish.c $(OBJDIR)/translate + $(OBJDIR)/translate $(SRCDIR)/publish.c >$@ + +$(OBJDIR)/publish.o: $(OBJDIR)/publish_.c $(OBJDIR)/publish.h $(SRCDIR)/config.h + $(XTCC) -o $(OBJDIR)/publish.o -c $(OBJDIR)/publish_.c + +$(OBJDIR)/publish.h: $(OBJDIR)/headers + +$(OBJDIR)/purge_.c: $(SRCDIR)/purge.c $(OBJDIR)/translate + $(OBJDIR)/translate $(SRCDIR)/purge.c >$@ + +$(OBJDIR)/purge.o: $(OBJDIR)/purge_.c $(OBJDIR)/purge.h $(SRCDIR)/config.h + $(XTCC) -o $(OBJDIR)/purge.o -c $(OBJDIR)/purge_.c + +$(OBJDIR)/purge.h: $(OBJDIR)/headers + +$(OBJDIR)/rebuild_.c: $(SRCDIR)/rebuild.c $(OBJDIR)/translate + $(OBJDIR)/translate $(SRCDIR)/rebuild.c >$@ + +$(OBJDIR)/rebuild.o: $(OBJDIR)/rebuild_.c $(OBJDIR)/rebuild.h $(SRCDIR)/config.h + $(XTCC) -o $(OBJDIR)/rebuild.o -c $(OBJDIR)/rebuild_.c + +$(OBJDIR)/rebuild.h: $(OBJDIR)/headers + +$(OBJDIR)/regexp_.c: $(SRCDIR)/regexp.c $(OBJDIR)/translate + $(OBJDIR)/translate $(SRCDIR)/regexp.c >$@ + +$(OBJDIR)/regexp.o: $(OBJDIR)/regexp_.c $(OBJDIR)/regexp.h $(SRCDIR)/config.h + $(XTCC) -o $(OBJDIR)/regexp.o -c $(OBJDIR)/regexp_.c + +$(OBJDIR)/regexp.h: $(OBJDIR)/headers + +$(OBJDIR)/repolist_.c: $(SRCDIR)/repolist.c $(OBJDIR)/translate + $(OBJDIR)/translate $(SRCDIR)/repolist.c >$@ + +$(OBJDIR)/repolist.o: $(OBJDIR)/repolist_.c $(OBJDIR)/repolist.h $(SRCDIR)/config.h + $(XTCC) -o $(OBJDIR)/repolist.o -c $(OBJDIR)/repolist_.c + +$(OBJDIR)/repolist.h: $(OBJDIR)/headers + +$(OBJDIR)/report_.c: $(SRCDIR)/report.c $(OBJDIR)/translate + $(OBJDIR)/translate $(SRCDIR)/report.c >$@ + +$(OBJDIR)/report.o: $(OBJDIR)/report_.c $(OBJDIR)/report.h $(SRCDIR)/config.h + $(XTCC) -o $(OBJDIR)/report.o -c $(OBJDIR)/report_.c + +$(OBJDIR)/report.h: $(OBJDIR)/headers + +$(OBJDIR)/rss_.c: $(SRCDIR)/rss.c $(OBJDIR)/translate + $(OBJDIR)/translate $(SRCDIR)/rss.c >$@ + +$(OBJDIR)/rss.o: $(OBJDIR)/rss_.c $(OBJDIR)/rss.h $(SRCDIR)/config.h + $(XTCC) -o $(OBJDIR)/rss.o -c $(OBJDIR)/rss_.c + +$(OBJDIR)/rss.h: $(OBJDIR)/headers + +$(OBJDIR)/schema_.c: $(SRCDIR)/schema.c $(OBJDIR)/translate + $(OBJDIR)/translate $(SRCDIR)/schema.c >$@ + +$(OBJDIR)/schema.o: $(OBJDIR)/schema_.c $(OBJDIR)/schema.h $(SRCDIR)/config.h + $(XTCC) -o $(OBJDIR)/schema.o -c $(OBJDIR)/schema_.c + +$(OBJDIR)/schema.h: $(OBJDIR)/headers + +$(OBJDIR)/search_.c: $(SRCDIR)/search.c $(OBJDIR)/translate + $(OBJDIR)/translate $(SRCDIR)/search.c >$@ + +$(OBJDIR)/search.o: $(OBJDIR)/search_.c $(OBJDIR)/search.h $(SRCDIR)/config.h + $(XTCC) -o $(OBJDIR)/search.o -c $(OBJDIR)/search_.c + +$(OBJDIR)/search.h: $(OBJDIR)/headers + +$(OBJDIR)/security_audit_.c: $(SRCDIR)/security_audit.c $(OBJDIR)/translate + $(OBJDIR)/translate $(SRCDIR)/security_audit.c >$@ + +$(OBJDIR)/security_audit.o: $(OBJDIR)/security_audit_.c $(OBJDIR)/security_audit.h $(SRCDIR)/config.h + $(XTCC) -o $(OBJDIR)/security_audit.o -c $(OBJDIR)/security_audit_.c + +$(OBJDIR)/security_audit.h: $(OBJDIR)/headers + +$(OBJDIR)/setup_.c: $(SRCDIR)/setup.c $(OBJDIR)/translate + $(OBJDIR)/translate $(SRCDIR)/setup.c >$@ + +$(OBJDIR)/setup.o: $(OBJDIR)/setup_.c $(OBJDIR)/setup.h $(SRCDIR)/config.h + $(XTCC) -o $(OBJDIR)/setup.o -c $(OBJDIR)/setup_.c + +$(OBJDIR)/setup.h: $(OBJDIR)/headers + +$(OBJDIR)/setupuser_.c: $(SRCDIR)/setupuser.c $(OBJDIR)/translate + $(OBJDIR)/translate $(SRCDIR)/setupuser.c >$@ + +$(OBJDIR)/setupuser.o: $(OBJDIR)/setupuser_.c $(OBJDIR)/setupuser.h $(SRCDIR)/config.h + $(XTCC) -o $(OBJDIR)/setupuser.o -c $(OBJDIR)/setupuser_.c + +$(OBJDIR)/setupuser.h: $(OBJDIR)/headers + +$(OBJDIR)/sha1_.c: $(SRCDIR)/sha1.c $(OBJDIR)/translate + $(OBJDIR)/translate $(SRCDIR)/sha1.c >$@ + +$(OBJDIR)/sha1.o: $(OBJDIR)/sha1_.c $(OBJDIR)/sha1.h $(SRCDIR)/config.h + $(XTCC) -o $(OBJDIR)/sha1.o -c $(OBJDIR)/sha1_.c + +$(OBJDIR)/sha1.h: $(OBJDIR)/headers + +$(OBJDIR)/sha1hard_.c: $(SRCDIR)/sha1hard.c $(OBJDIR)/translate + $(OBJDIR)/translate $(SRCDIR)/sha1hard.c >$@ + +$(OBJDIR)/sha1hard.o: $(OBJDIR)/sha1hard_.c $(OBJDIR)/sha1hard.h $(SRCDIR)/config.h + $(XTCC) -o $(OBJDIR)/sha1hard.o -c $(OBJDIR)/sha1hard_.c + +$(OBJDIR)/sha1hard.h: $(OBJDIR)/headers + +$(OBJDIR)/sha3_.c: $(SRCDIR)/sha3.c $(OBJDIR)/translate + $(OBJDIR)/translate $(SRCDIR)/sha3.c >$@ + +$(OBJDIR)/sha3.o: $(OBJDIR)/sha3_.c $(OBJDIR)/sha3.h $(SRCDIR)/config.h + $(XTCC) -o $(OBJDIR)/sha3.o -c $(OBJDIR)/sha3_.c + +$(OBJDIR)/sha3.h: $(OBJDIR)/headers + +$(OBJDIR)/shun_.c: $(SRCDIR)/shun.c $(OBJDIR)/translate + $(OBJDIR)/translate $(SRCDIR)/shun.c >$@ + +$(OBJDIR)/shun.o: $(OBJDIR)/shun_.c $(OBJDIR)/shun.h $(SRCDIR)/config.h + $(XTCC) -o $(OBJDIR)/shun.o -c $(OBJDIR)/shun_.c + +$(OBJDIR)/shun.h: $(OBJDIR)/headers + +$(OBJDIR)/sitemap_.c: $(SRCDIR)/sitemap.c $(OBJDIR)/translate + $(OBJDIR)/translate $(SRCDIR)/sitemap.c >$@ + +$(OBJDIR)/sitemap.o: $(OBJDIR)/sitemap_.c $(OBJDIR)/sitemap.h $(SRCDIR)/config.h + $(XTCC) -o $(OBJDIR)/sitemap.o -c $(OBJDIR)/sitemap_.c + +$(OBJDIR)/sitemap.h: $(OBJDIR)/headers + +$(OBJDIR)/skins_.c: $(SRCDIR)/skins.c $(OBJDIR)/translate + $(OBJDIR)/translate $(SRCDIR)/skins.c >$@ + +$(OBJDIR)/skins.o: $(OBJDIR)/skins_.c $(OBJDIR)/skins.h $(SRCDIR)/config.h + $(XTCC) -o $(OBJDIR)/skins.o -c $(OBJDIR)/skins_.c + +$(OBJDIR)/skins.h: $(OBJDIR)/headers + +$(OBJDIR)/smtp_.c: $(SRCDIR)/smtp.c $(OBJDIR)/translate + $(OBJDIR)/translate $(SRCDIR)/smtp.c >$@ + +$(OBJDIR)/smtp.o: $(OBJDIR)/smtp_.c $(OBJDIR)/smtp.h $(SRCDIR)/config.h + $(XTCC) -o $(OBJDIR)/smtp.o -c $(OBJDIR)/smtp_.c + +$(OBJDIR)/smtp.h: $(OBJDIR)/headers + +$(OBJDIR)/sqlcmd_.c: $(SRCDIR)/sqlcmd.c $(OBJDIR)/translate + $(OBJDIR)/translate $(SRCDIR)/sqlcmd.c >$@ + +$(OBJDIR)/sqlcmd.o: $(OBJDIR)/sqlcmd_.c $(OBJDIR)/sqlcmd.h $(SRCDIR)/config.h + $(XTCC) -o $(OBJDIR)/sqlcmd.o -c $(OBJDIR)/sqlcmd_.c + +$(OBJDIR)/sqlcmd.h: $(OBJDIR)/headers + +$(OBJDIR)/stash_.c: $(SRCDIR)/stash.c $(OBJDIR)/translate + $(OBJDIR)/translate $(SRCDIR)/stash.c >$@ + +$(OBJDIR)/stash.o: $(OBJDIR)/stash_.c $(OBJDIR)/stash.h $(SRCDIR)/config.h + $(XTCC) -o $(OBJDIR)/stash.o -c $(OBJDIR)/stash_.c + +$(OBJDIR)/stash.h: $(OBJDIR)/headers + +$(OBJDIR)/stat_.c: $(SRCDIR)/stat.c $(OBJDIR)/translate + $(OBJDIR)/translate $(SRCDIR)/stat.c >$@ + +$(OBJDIR)/stat.o: $(OBJDIR)/stat_.c $(OBJDIR)/stat.h $(SRCDIR)/config.h + $(XTCC) -o $(OBJDIR)/stat.o -c $(OBJDIR)/stat_.c + +$(OBJDIR)/stat.h: $(OBJDIR)/headers + +$(OBJDIR)/statrep_.c: $(SRCDIR)/statrep.c $(OBJDIR)/translate + $(OBJDIR)/translate $(SRCDIR)/statrep.c >$@ + +$(OBJDIR)/statrep.o: $(OBJDIR)/statrep_.c $(OBJDIR)/statrep.h $(SRCDIR)/config.h + $(XTCC) -o $(OBJDIR)/statrep.o -c $(OBJDIR)/statrep_.c + +$(OBJDIR)/statrep.h: $(OBJDIR)/headers + +$(OBJDIR)/style_.c: $(SRCDIR)/style.c $(OBJDIR)/translate + $(OBJDIR)/translate $(SRCDIR)/style.c >$@ + +$(OBJDIR)/style.o: $(OBJDIR)/style_.c $(OBJDIR)/style.h $(SRCDIR)/config.h + $(XTCC) -o $(OBJDIR)/style.o -c $(OBJDIR)/style_.c + +$(OBJDIR)/style.h: $(OBJDIR)/headers + +$(OBJDIR)/sync_.c: $(SRCDIR)/sync.c $(OBJDIR)/translate + $(OBJDIR)/translate $(SRCDIR)/sync.c >$@ + +$(OBJDIR)/sync.o: $(OBJDIR)/sync_.c $(OBJDIR)/sync.h $(SRCDIR)/config.h + $(XTCC) -o $(OBJDIR)/sync.o -c $(OBJDIR)/sync_.c + +$(OBJDIR)/sync.h: $(OBJDIR)/headers + +$(OBJDIR)/tag_.c: $(SRCDIR)/tag.c $(OBJDIR)/translate + $(OBJDIR)/translate $(SRCDIR)/tag.c >$@ + +$(OBJDIR)/tag.o: $(OBJDIR)/tag_.c $(OBJDIR)/tag.h $(SRCDIR)/config.h + $(XTCC) -o $(OBJDIR)/tag.o -c $(OBJDIR)/tag_.c + +$(OBJDIR)/tag.h: $(OBJDIR)/headers + +$(OBJDIR)/tar_.c: $(SRCDIR)/tar.c $(OBJDIR)/translate + $(OBJDIR)/translate $(SRCDIR)/tar.c >$@ + +$(OBJDIR)/tar.o: $(OBJDIR)/tar_.c $(OBJDIR)/tar.h $(SRCDIR)/config.h + $(XTCC) -o $(OBJDIR)/tar.o -c $(OBJDIR)/tar_.c + +$(OBJDIR)/tar.h: $(OBJDIR)/headers + +$(OBJDIR)/terminal_.c: $(SRCDIR)/terminal.c $(OBJDIR)/translate + $(OBJDIR)/translate $(SRCDIR)/terminal.c >$@ + +$(OBJDIR)/terminal.o: $(OBJDIR)/terminal_.c $(OBJDIR)/terminal.h $(SRCDIR)/config.h + $(XTCC) -o $(OBJDIR)/terminal.o -c $(OBJDIR)/terminal_.c + +$(OBJDIR)/terminal.h: $(OBJDIR)/headers + +$(OBJDIR)/th_main_.c: $(SRCDIR)/th_main.c $(OBJDIR)/translate + $(OBJDIR)/translate $(SRCDIR)/th_main.c >$@ + +$(OBJDIR)/th_main.o: $(OBJDIR)/th_main_.c $(OBJDIR)/th_main.h $(SRCDIR)/config.h + $(XTCC) -o $(OBJDIR)/th_main.o -c $(OBJDIR)/th_main_.c + +$(OBJDIR)/th_main.h: $(OBJDIR)/headers + +$(OBJDIR)/timeline_.c: $(SRCDIR)/timeline.c $(OBJDIR)/translate + $(OBJDIR)/translate $(SRCDIR)/timeline.c >$@ + +$(OBJDIR)/timeline.o: $(OBJDIR)/timeline_.c $(OBJDIR)/timeline.h $(SRCDIR)/config.h + $(XTCC) -o $(OBJDIR)/timeline.o -c $(OBJDIR)/timeline_.c + +$(OBJDIR)/timeline.h: $(OBJDIR)/headers + +$(OBJDIR)/tkt_.c: $(SRCDIR)/tkt.c $(OBJDIR)/translate + $(OBJDIR)/translate $(SRCDIR)/tkt.c >$@ + +$(OBJDIR)/tkt.o: $(OBJDIR)/tkt_.c $(OBJDIR)/tkt.h $(SRCDIR)/config.h + $(XTCC) -o $(OBJDIR)/tkt.o -c $(OBJDIR)/tkt_.c + +$(OBJDIR)/tkt.h: $(OBJDIR)/headers + +$(OBJDIR)/tktsetup_.c: $(SRCDIR)/tktsetup.c $(OBJDIR)/translate + $(OBJDIR)/translate $(SRCDIR)/tktsetup.c >$@ + +$(OBJDIR)/tktsetup.o: $(OBJDIR)/tktsetup_.c $(OBJDIR)/tktsetup.h $(SRCDIR)/config.h + $(XTCC) -o $(OBJDIR)/tktsetup.o -c $(OBJDIR)/tktsetup_.c + +$(OBJDIR)/tktsetup.h: $(OBJDIR)/headers + +$(OBJDIR)/undo_.c: $(SRCDIR)/undo.c $(OBJDIR)/translate + $(OBJDIR)/translate $(SRCDIR)/undo.c >$@ + +$(OBJDIR)/undo.o: $(OBJDIR)/undo_.c $(OBJDIR)/undo.h $(SRCDIR)/config.h + $(XTCC) -o $(OBJDIR)/undo.o -c $(OBJDIR)/undo_.c + +$(OBJDIR)/undo.h: $(OBJDIR)/headers + +$(OBJDIR)/unicode_.c: $(SRCDIR)/unicode.c $(OBJDIR)/translate + $(OBJDIR)/translate $(SRCDIR)/unicode.c >$@ + +$(OBJDIR)/unicode.o: $(OBJDIR)/unicode_.c $(OBJDIR)/unicode.h $(SRCDIR)/config.h + $(XTCC) -o $(OBJDIR)/unicode.o -c $(OBJDIR)/unicode_.c + +$(OBJDIR)/unicode.h: $(OBJDIR)/headers + +$(OBJDIR)/unversioned_.c: $(SRCDIR)/unversioned.c $(OBJDIR)/translate + $(OBJDIR)/translate $(SRCDIR)/unversioned.c >$@ + +$(OBJDIR)/unversioned.o: $(OBJDIR)/unversioned_.c $(OBJDIR)/unversioned.h $(SRCDIR)/config.h + $(XTCC) -o $(OBJDIR)/unversioned.o -c $(OBJDIR)/unversioned_.c + +$(OBJDIR)/unversioned.h: $(OBJDIR)/headers + +$(OBJDIR)/update_.c: $(SRCDIR)/update.c $(OBJDIR)/translate + $(OBJDIR)/translate $(SRCDIR)/update.c >$@ + +$(OBJDIR)/update.o: $(OBJDIR)/update_.c $(OBJDIR)/update.h $(SRCDIR)/config.h + $(XTCC) -o $(OBJDIR)/update.o -c $(OBJDIR)/update_.c + +$(OBJDIR)/update.h: $(OBJDIR)/headers + +$(OBJDIR)/url_.c: $(SRCDIR)/url.c $(OBJDIR)/translate + $(OBJDIR)/translate $(SRCDIR)/url.c >$@ + +$(OBJDIR)/url.o: $(OBJDIR)/url_.c $(OBJDIR)/url.h $(SRCDIR)/config.h + $(XTCC) -o $(OBJDIR)/url.o -c $(OBJDIR)/url_.c + +$(OBJDIR)/url.h: $(OBJDIR)/headers + +$(OBJDIR)/user_.c: $(SRCDIR)/user.c $(OBJDIR)/translate + $(OBJDIR)/translate $(SRCDIR)/user.c >$@ + +$(OBJDIR)/user.o: $(OBJDIR)/user_.c $(OBJDIR)/user.h $(SRCDIR)/config.h + $(XTCC) -o $(OBJDIR)/user.o -c $(OBJDIR)/user_.c + +$(OBJDIR)/user.h: $(OBJDIR)/headers + +$(OBJDIR)/utf8_.c: $(SRCDIR)/utf8.c $(OBJDIR)/translate + $(OBJDIR)/translate $(SRCDIR)/utf8.c >$@ + +$(OBJDIR)/utf8.o: $(OBJDIR)/utf8_.c $(OBJDIR)/utf8.h $(SRCDIR)/config.h + $(XTCC) -o $(OBJDIR)/utf8.o -c $(OBJDIR)/utf8_.c + +$(OBJDIR)/utf8.h: $(OBJDIR)/headers + +$(OBJDIR)/util_.c: $(SRCDIR)/util.c $(OBJDIR)/translate + $(OBJDIR)/translate $(SRCDIR)/util.c >$@ + +$(OBJDIR)/util.o: $(OBJDIR)/util_.c $(OBJDIR)/util.h $(SRCDIR)/config.h + $(XTCC) -o $(OBJDIR)/util.o -c $(OBJDIR)/util_.c + +$(OBJDIR)/util.h: $(OBJDIR)/headers + +$(OBJDIR)/verify_.c: $(SRCDIR)/verify.c $(OBJDIR)/translate + $(OBJDIR)/translate $(SRCDIR)/verify.c >$@ + +$(OBJDIR)/verify.o: $(OBJDIR)/verify_.c $(OBJDIR)/verify.h $(SRCDIR)/config.h + $(XTCC) -o $(OBJDIR)/verify.o -c $(OBJDIR)/verify_.c + +$(OBJDIR)/verify.h: $(OBJDIR)/headers + +$(OBJDIR)/vfile_.c: $(SRCDIR)/vfile.c $(OBJDIR)/translate + $(OBJDIR)/translate $(SRCDIR)/vfile.c >$@ + +$(OBJDIR)/vfile.o: $(OBJDIR)/vfile_.c $(OBJDIR)/vfile.h $(SRCDIR)/config.h + $(XTCC) -o $(OBJDIR)/vfile.o -c $(OBJDIR)/vfile_.c + +$(OBJDIR)/vfile.h: $(OBJDIR)/headers + +$(OBJDIR)/wiki_.c: $(SRCDIR)/wiki.c $(OBJDIR)/translate + $(OBJDIR)/translate $(SRCDIR)/wiki.c >$@ + +$(OBJDIR)/wiki.o: $(OBJDIR)/wiki_.c $(OBJDIR)/wiki.h $(SRCDIR)/config.h + $(XTCC) -o $(OBJDIR)/wiki.o -c $(OBJDIR)/wiki_.c + +$(OBJDIR)/wiki.h: $(OBJDIR)/headers + +$(OBJDIR)/wikiformat_.c: $(SRCDIR)/wikiformat.c $(OBJDIR)/translate + $(OBJDIR)/translate $(SRCDIR)/wikiformat.c >$@ + +$(OBJDIR)/wikiformat.o: $(OBJDIR)/wikiformat_.c $(OBJDIR)/wikiformat.h $(SRCDIR)/config.h + $(XTCC) -o $(OBJDIR)/wikiformat.o -c $(OBJDIR)/wikiformat_.c + +$(OBJDIR)/wikiformat.h: $(OBJDIR)/headers + +$(OBJDIR)/winfile_.c: $(SRCDIR)/winfile.c $(OBJDIR)/translate + $(OBJDIR)/translate $(SRCDIR)/winfile.c >$@ + +$(OBJDIR)/winfile.o: $(OBJDIR)/winfile_.c $(OBJDIR)/winfile.h $(SRCDIR)/config.h + $(XTCC) -o $(OBJDIR)/winfile.o -c $(OBJDIR)/winfile_.c + +$(OBJDIR)/winfile.h: $(OBJDIR)/headers + +$(OBJDIR)/winhttp_.c: $(SRCDIR)/winhttp.c $(OBJDIR)/translate + $(OBJDIR)/translate $(SRCDIR)/winhttp.c >$@ + +$(OBJDIR)/winhttp.o: $(OBJDIR)/winhttp_.c $(OBJDIR)/winhttp.h $(SRCDIR)/config.h + $(XTCC) -o $(OBJDIR)/winhttp.o -c $(OBJDIR)/winhttp_.c + +$(OBJDIR)/winhttp.h: $(OBJDIR)/headers + +$(OBJDIR)/xfer_.c: $(SRCDIR)/xfer.c $(OBJDIR)/translate + $(OBJDIR)/translate $(SRCDIR)/xfer.c >$@ + +$(OBJDIR)/xfer.o: $(OBJDIR)/xfer_.c $(OBJDIR)/xfer.h $(SRCDIR)/config.h + $(XTCC) -o $(OBJDIR)/xfer.o -c $(OBJDIR)/xfer_.c + +$(OBJDIR)/xfer.h: $(OBJDIR)/headers + +$(OBJDIR)/xfersetup_.c: $(SRCDIR)/xfersetup.c $(OBJDIR)/translate + $(OBJDIR)/translate $(SRCDIR)/xfersetup.c >$@ + +$(OBJDIR)/xfersetup.o: $(OBJDIR)/xfersetup_.c $(OBJDIR)/xfersetup.h $(SRCDIR)/config.h + $(XTCC) -o $(OBJDIR)/xfersetup.o -c $(OBJDIR)/xfersetup_.c + +$(OBJDIR)/xfersetup.h: $(OBJDIR)/headers + +$(OBJDIR)/zip_.c: $(SRCDIR)/zip.c $(OBJDIR)/translate + $(OBJDIR)/translate $(SRCDIR)/zip.c >$@ + +$(OBJDIR)/zip.o: $(OBJDIR)/zip_.c $(OBJDIR)/zip.h $(SRCDIR)/config.h + $(XTCC) -o $(OBJDIR)/zip.o -c $(OBJDIR)/zip_.c + +$(OBJDIR)/zip.h: $(OBJDIR)/headers + +$(OBJDIR)/sqlite3.o: $(SQLITE3_SRC) + $(XTCC) $(SQLITE_OPTIONS) $(SQLITE_CFLAGS) $(SEE_FLAGS) \ + -c $(SQLITE3_SRC) -o $@ +$(OBJDIR)/shell.o: $(SQLITE3_SHELL_SRC) $(SRCDIR)/sqlite3.h + $(XTCC) $(SHELL_OPTIONS) $(SHELL_CFLAGS) $(SEE_FLAGS) $(LINENOISE_DEF.$(USE_LINENOISE)) -c $(SQLITE3_SHELL_SRC) -o $@ + +$(OBJDIR)/linenoise.o: $(SRCDIR)/linenoise.c $(SRCDIR)/linenoise.h + $(XTCC) -c $(SRCDIR)/linenoise.c -o $@ $(OBJDIR)/th.o: $(SRCDIR)/th.c - $(XTCC) -I$(SRCDIR) -c $(SRCDIR)/th.c -o $(OBJDIR)/th.o + $(XTCC) -c $(SRCDIR)/th.c -o $@ $(OBJDIR)/th_lang.o: $(SRCDIR)/th_lang.c - $(XTCC) -I$(SRCDIR) -c $(SRCDIR)/th_lang.c -o $(OBJDIR)/th_lang.o + $(XTCC) -c $(SRCDIR)/th_lang.c -o $@ + +$(OBJDIR)/th_tcl.o: $(SRCDIR)/th_tcl.c + $(XTCC) -c $(SRCDIR)/th_tcl.c -o $@ + + +$(OBJDIR)/miniz.o: $(SRCDIR)/miniz.c + $(XTCC) $(MINIZ_OPTIONS) -c $(SRCDIR)/miniz.c -o $@ + +$(OBJDIR)/cson_amalgamation.o: $(SRCDIR)/cson_amalgamation.c + $(XTCC) -c $(SRCDIR)/cson_amalgamation.c -o $@ + +# +# The list of all the targets that do not correspond to real files. This stops +# 'make' from getting confused when someone makes an error in a rule. +# + +.PHONY: all install test clean Index: src/makeheaders.c ================================================================== --- src/makeheaders.c +++ src/makeheaders.c @@ -1,26 +1,53 @@ -static const char ident[] = "@(#) $Header: /cvstrac/cvstrac/makeheaders.c,v 1.4 2005/03/16 22:17:51 drh Exp $"; /* ** 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".) - +** +** Copyright 1993 D. Richard Hipp. All rights reserved. +** +** Redistribution and use in source and binary forms, with or +** without modification, are permitted provided that the following +** conditions are met: +** +** 1. Redistributions of source code must retain the above copyright +** notice, this list of conditions and the following disclaimer. +** +** 2. 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 "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 author 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. +** ** 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. -** appropriate header files. */ #include #include #include #include #include #include -#ifndef WIN32 -# include +#include + +#if defined( __MINGW32__) || defined(__DMC__) || defined(_MSC_VER) || defined(__POCC__) +# ifndef WIN32 +# define WIN32 +# endif #else -# include +# include #endif /* ** Macros for debugging. */ @@ -84,19 +111,19 @@ ** ** struct Xyzzy; ** ** Not every object has a forward declaration. If it does, thought, the ** forward declaration will be contained in the zFwd field for C and -** the zFwdCpp for C++. The zDecl field contains the complete -** declaration text. +** the zFwdCpp for C++. The zDecl field contains the complete +** declaration text. */ typedef struct Decl Decl; struct Decl { char *zName; /* Name of the object being declared. The appearance ** of this name is a source file triggers the declaration ** to be added to the header for that file. */ - char *zFile; /* File from which extracted. */ + const char *zFile; /* File from which extracted. */ char *zIf; /* Surround the declaration with this #if */ char *zFwd; /* A forward declaration. NULL if there is none. */ char *zFwdCpp; /* Use this forward declaration for C++. */ char *zDecl; /* A full declaration of this object */ char *zExtra; /* Extra declaration text inserted into class objects */ @@ -135,11 +162,11 @@ ** in the output when using the -H option.) ** ** EXPORT scope The object is visible and usable everywhere. ** ** The DP_Flag is a temporary use flag that is used during processing to -** prevent an infinite loop. It's use is localized. +** prevent an infinite loop. It's use is localized. ** ** The DP_Cplusplus, DP_ExternCReqd and DP_ExternReqd flags are permanent ** and are used to specify what type of declaration the object requires. */ #define DP_Forward 0x001 /* Has a forward declaration in this file */ @@ -173,11 +200,11 @@ ** Be careful not to confuse PS_Export with DP_Export or ** PS_Local with DP_Local. Their names are similar, but the meanings ** of these flags are very different. */ #define PS_Extern 0x000800 /* "extern" has been seen */ -#define PS_Export 0x001000 /* If between "#if EXPORT_INTERFACE" +#define PS_Export 0x001000 /* If between "#if EXPORT_INTERFACE" ** and "#endif" */ #define PS_Export2 0x002000 /* If "EXPORT" seen */ #define PS_Typedef 0x004000 /* If "typedef" has been seen */ #define PS_Static 0x008000 /* If "static" has been seen */ #define PS_Interface 0x010000 /* If within #if INTERFACE..#endif */ @@ -203,11 +230,11 @@ #define TY_Union 0x04000000 #define TY_Enumeration 0x08000000 #define TY_Defunct 0x10000000 /* Used to erase a declaration */ /* -** Each nested #if (or #ifdef or #ifndef) is stored in a stack of +** Each nested #if (or #ifdef or #ifndef) is stored in a stack of ** instances of the following structure. */ typedef struct Ifmacro Ifmacro; struct Ifmacro { int nLine; /* Line number where this macro occurs */ @@ -265,18 +292,18 @@ int flags; /* One or more DP_, PS_ and/or TY_ flags */ InFile *pNext; /* Next input file in the list of them all */ IdentTable idTable; /* All identifiers in this input file */ }; -/* +/* ** An unbounded string is able to grow without limit. We use these ** to construct large in-memory strings from lots of smaller components. */ typedef struct String String; struct String { int nAlloc; /* Number of bytes allocated */ - int nUsed; /* Number of bytes used (not counting null terminator) */ + int nUsed; /* Number of bytes used (not counting nul terminator) */ char *zText; /* Text of the string */ }; /* ** The following structure contains a lot of state information used @@ -300,19 +327,23 @@ /* ** The following text line appears at the top of every file generated ** by this program. By recognizing this line, the program can be sure ** never to read a file that it generated itself. +** +** The "#undef INTERFACE" part is a hack to work around a name collision +** in MSVC 2008. */ -const char zTopLine[] = - "/* \aThis file was automatically generated. Do not edit! */\n"; +const char zTopLine[] = + "/* \aThis file was automatically generated. Do not edit! */\n" + "#undef INTERFACE\n"; #define nTopLine (sizeof(zTopLine)-1) /* ** The name of the file currently being parsed. */ -static char *zFilename; +static const char *zFilename; /* ** The stack of #if macros for the file currently being parsed. */ static Ifmacro *ifStack = 0; @@ -670,11 +701,11 @@ struct stat sStat; FILE *pIn; char *zBuf; int n; - if( stat(zFilename,&sStat)!=0 + if( stat(zFilename,&sStat)!=0 #ifndef WIN32 || !S_ISREG(sStat.st_mode) #endif ){ return 0; @@ -716,11 +747,11 @@ #define TT_String 6 /* String or character constants. ".." or '.' */ #define TT_Braces 7 /* All text between { and a matching } */ #define TT_EOF 8 /* End of file */ #define TT_Error 9 /* An error condition */ #define TT_BlockComment 10 /* A C-Style comment at the left margin that - * spans multple lines */ + * spans multiple lines */ #define TT_Other 0 /* None of the above */ /* ** Get a single low-level token from the input file. Update the ** file pointer so that it points to the first character beyond the @@ -857,12 +888,12 @@ } } } i++; } - if( z[i] ){ - i += 2; + if( z[i] ){ + i += 2; }else{ isBlockComment = 0; fprintf(stderr,"%s:%d: Unterminated comment\n", zFilename, startLine); nErr++; @@ -874,11 +905,11 @@ pToken->eType = TT_Other; pToken->nText = 1 + (z[i+1]=='+'); } break; - case '0': + case '0': if( z[i+1]=='x' || z[i+1]=='X' ){ /* A hex constant */ i += 2; while( isxdigit(z[i]) ){ i++; } }else{ @@ -931,11 +962,11 @@ while( isalnum(z[i]) || z[i]=='_' ){ i++; }; pToken->eType = TT_Id; pToken->nText = i - pIn->i; break; - case ':': + case ':': pToken->eType = TT_Other; pToken->nText = 1 + (z[i+1]==':'); break; case '=': @@ -945,11 +976,11 @@ case '-': case '*': case '%': case '^': case '&': - case '|': + case '|': pToken->eType = TT_Other; pToken->nText = 1 + (z[i+1]=='='); break; default: @@ -985,11 +1016,14 @@ /* printf("%04d: Type=%d nIf=%d [%.*s]\n", pToken->nLine,pToken->eType,nIf,pToken->nText, pToken->eType!=TT_Space ? pToken->zText : ""); */ pToken->pComment = blockComment; switch( pToken->eType ){ - case TT_Comment: + case TT_Comment: /*0123456789 12345678 */ + if( strncmp(pToken->zText, "/*MAKEHEADERS-STOP", 18)==0 ) return nErr; + break; + case TT_Space: break; case TT_BlockComment: if( doc_flag ){ @@ -1032,11 +1066,11 @@ } } /* NOT REACHED */ } -/* +/* ** This routine looks for identifiers (strings of contiguous alphanumeric ** characters) within a preprocessor directive and adds every such string ** found to the given identifier table */ static void FindIdentifiersInMacro(Token *pToken, IdentTable *pTable){ @@ -1074,11 +1108,11 @@ ** ** The number of errors encountered is returned. An error is an ** unterminated token. */ static int GetBigToken(InStream *pIn, Token *pToken, IdentTable *pTable){ - const char *z, *zStart; + const char *zStart; int iStart; int nBrace; int c; int nLine; int nErr; @@ -1103,11 +1137,10 @@ default: return nErr; } - z = pIn->z; iStart = pIn->i; zStart = pToken->zText; nLine = pToken->nLine; nBrace = 1; while( nBrace ){ @@ -1125,11 +1158,11 @@ case TT_Id: if( pTable ){ IdentTableInsert(pTable,pToken->zText,pToken->nText); } break; - + case TT_Preprocessor: if( pTable!=0 ){ FindIdentifiersInMacro(pToken,pTable); } break; @@ -1231,11 +1264,11 @@ exit(1); } pList = TokenizeFile(zFile,&sTable); for(p=pList; p; p=p->pNext){ int j; - switch( p->eType ){ + switch( p->eType ){ case TT_Space: printf("%4d: Space\n",p->nLine); break; case TT_Id: printf("%4d: Id %.*s\n",p->nLine,p->nText,p->zText); @@ -1298,11 +1331,11 @@ needSpace = 1; break; default: c = pFirst->zText[0]; - printf("%s%.*s", + printf("%s%.*s", (needSpace && (c=='*' || c=='{')) ? " " : "", pFirst->nText, pFirst->zText); needSpace = pFirst->zText[0]==','; break; } @@ -1339,13 +1372,13 @@ StringInit(&str); pLast = pLast->pNext; while( pFirst!=pLast ){ if( pFirst==pSkip ){ iSkip = nSkip; } - if( iSkip>0 ){ + if( iSkip>0 ){ iSkip--; - pFirst=pFirst->pNext; + pFirst=pFirst->pNext; continue; } switch( pFirst->eType ){ case TT_Preprocessor: StringAppend(&str,"\n",1); @@ -1352,13 +1385,13 @@ StringAppend(&str,pFirst->zText,pFirst->nText); StringAppend(&str,"\n",1); needSpace = 0; break; - case TT_Id: + case TT_Id: switch( pFirst->zText[0] ){ - case 'E': + case 'E': if( pFirst->nText==6 && strncmp(pFirst->zText,"EXPORT",6)==0 ){ skipOne = 1; } break; case 'P': @@ -1372,10 +1405,11 @@ default: break; } if( skipOne ){ pFirst = pFirst->pNext; + skipOne = 0; continue; } /* Fall thru to the next case */ case TT_Number: if( needSpace ){ @@ -1438,10 +1472,11 @@ } for(pEnd=pName->pNext; pEnd && pEnd->eType!=TT_Braces; pEnd=pEnd->pNext){ switch( pEnd->zText[0] ){ case '(': + case ')': case '*': case '[': case '=': case ';': return 0; @@ -1455,11 +1490,11 @@ ** At this point, we know we have a type declaration that is bounded ** by pList and pEnd and has the name pName. */ /* - ** If the braces are followed immedately by a semicolon, then we are + ** If the braces are followed immediately by a semicolon, then we are ** dealing a type declaration only. There is not variable definition ** following the type declaration. So reset... */ if( pEnd->pNext==0 || pEnd->pNext->zText[0]==';' ){ *pReset = ';'; @@ -1613,17 +1648,17 @@ pLast = pLast->pNext; for(p=pFirst; p && p!=pLast; p=p->pNext){ if( p->eType==TT_Id ){ static IdentTable sReserved; static int isInit = 0; - static char *aWords[] = { "char", "class", - "const", "double", "enum", "extern", "EXPORT", "ET_PROC", + static const char *aWords[] = { "char", "class", + "const", "double", "enum", "extern", "EXPORT", "ET_PROC", "float", "int", "long", "PRIVATE", "PROTECTED", "PUBLIC", - "register", "static", "struct", "sizeof", "signed", "typedef", + "register", "static", "struct", "sizeof", "signed", "typedef", "union", "volatile", "virtual", "void", }; - + if( !isInit ){ int i; for(i=0; ipPrev; while( pFirst->zText[0]=='P' ){ int rc = 1; switch( pFirst->nText ){ case 6: rc = strncmp(pFirst->zText,"PUBLIC",6); break; @@ -1701,10 +1734,20 @@ pDecl->extraType = PS_Private; } } StringAppend(&str, " ", 0); zDecl = TokensToString(pFirst, pLast, ";\n", pClass, 2); + if(strncmp(zDecl, pClass->zText, pClass->nText)==0){ + /* If member initializer list is found after a constructor, + ** skip that part. */ + char * colon = strchr(zDecl, ':'); + if(colon!=0 && colon[1]!=0){ + *colon++ = ';'; + *colon++ = '\n'; + *colon = 0; + } + } StringAppend(&str, zDecl, 0); SafeFree(zDecl); pDecl->zExtra = StrDup(StringGet(&str), 0); StringReset(&str); return 0; @@ -1736,11 +1779,11 @@ pCode = pLast; while( pLast && pLast!=pFirst && pLast->zText[0]!=')' ){ pLast = pLast->pPrev; } if( pLast==0 || pLast==pFirst || pFirst->pNext==pLast ){ - fprintf(stderr,"%s:%d: Unrecognized syntax.\n", + fprintf(stderr,"%s:%d: Unrecognized syntax.\n", zFilename, pFirst->nLine); return 1; } if( flags & (PS_Interface|PS_Export|PS_Local) ){ fprintf(stderr,"%s:%d: Missing \"inline\" on function or procedure.\n", @@ -1751,11 +1794,14 @@ if( pName==0 ){ fprintf(stderr,"%s:%d: Malformed function or procedure definition.\n", zFilename, pFirst->nLine); return 1; } - + if( strncmp(pName->zText,"main",pName->nText)==0 ){ + /* skip main() decl. */ + return 0; + } /* ** At this point we've isolated a procedure declaration between pFirst ** and pLast with the name pName. */ #ifdef DEBUG @@ -1817,11 +1863,11 @@ return 1; } #ifdef DEBUG if( debugMask & PARSER ){ - printf("**** Found inline routine: %.*s on line %d...\n", + printf("**** Found inline routine: %.*s on line %d...\n", pName->nText, pName->zText, pFirst->nLine); PrintTokens(pFirst,pEnd); printf("\n"); } #endif @@ -1851,16 +1897,16 @@ ** ** pEnd is the token that ends the object. It can be either a ';' or ** a '='. If it is '=', then assume we have a variable definition. ** ** If pEnd is ';', then the determination is more difficult. We have -** to search for an occurance of an ID followed immediately by '('. +** to search for an occurrence of an ID followed immediately by '('. ** If found, we have a prototype. Otherwise we are dealing with a ** variable definition. */ static int isVariableDef(Token *pFirst, Token *pEnd){ - if( pEnd && pEnd->zText[0]=='=' && + if( pEnd && pEnd->zText[0]=='=' && (pEnd->pPrev->nText!=8 || strncmp(pEnd->pPrev->zText,"operator",8)!=0) ){ return 1; } while( pFirst && pFirst!=pEnd && pFirst->pNext && pFirst->pNext!=pEnd ){ @@ -1870,10 +1916,22 @@ pFirst = pFirst->pNext; } return 1; } +/* +** Return TRUE if pFirst is the first token of a static assert. +*/ +static int isStaticAssert(Token *pFirst){ + if( (pFirst->nText==13 && strncmp(pFirst->zText, "static_assert", 13)==0) + || (pFirst->nText==14 && strncmp(pFirst->zText, "_Static_assert", 14)==0) + ){ + return 1; + }else{ + return 0; + } +} /* ** This routine is called whenever we encounter a ";" or "=". The stuff ** between pFirst and pLast constitutes either a typedef or a global ** variable definition. Do the right thing. @@ -1917,11 +1975,11 @@ } while( pFirst!=0 && pFirst->pNext!=pEnd && ((pFirst->nText==6 && strncmp(pFirst->zText,"static",6)==0) || (pFirst->nText==5 && strncmp(pFirst->zText,"LOCAL",6)==0)) ){ - /* Lose the initial "static" or local from local variables. + /* Lose the initial "static" or local from local variables. ** We'll prepend "extern" later. */ pFirst = pFirst->pNext; isLocal = 1; } if( pFirst==0 || !isLocal ){ @@ -1928,23 +1986,30 @@ return nErr; } }else if( flags & PS_Method ){ /* Methods are declared by their class. Don't declare separately. */ return nErr; + }else if( isStaticAssert(pFirst) ){ + return 0; } isVar = (flags & (PS_Typedef|PS_Method))==0 && isVariableDef(pFirst,pEnd); - if( isVar && (flags & (PS_Interface|PS_Export|PS_Local))!=0 + if( isVar && (flags & (PS_Interface|PS_Export|PS_Local))!=0 && (flags & PS_Extern)==0 ){ fprintf(stderr,"%s:%d: Can't define a variable in this context\n", zFilename, pFirst->nLine); nErr++; } pName = FindDeclName(pFirst,pEnd->pPrev); if( pName==0 ){ - fprintf(stderr,"%s:%d: Can't find a name for the object declared here.\n", - zFilename, pFirst->nLine); - return nErr+1; + if( pFirst->nText==4 && strncmp(pFirst->zText,"enum",4)==0 ){ + /* Ignore completely anonymous enums. See documentation section 3.8.1. */ + return nErr; + }else{ + fprintf(stderr,"%s:%d: Can't find a name for the object declared here.\n", + zFilename, pFirst->nLine); + return nErr+1; + } } #ifdef DEBUG if( debugMask & PARSER ){ if( flags & PS_Typedef ){ @@ -2063,11 +2128,11 @@ nCmd++; } if( nCmd==5 && strncmp(zCmd,"endif",5)==0 ){ /* - ** Pop the if stack + ** Pop the if stack */ pIf = ifStack; if( pIf==0 ){ fprintf(stderr,"%s:%d: extra '#endif'.\n",zFilename,pToken->nLine); return 1; @@ -2074,11 +2139,11 @@ } ifStack = pIf->pNext; SafeFree(pIf); }else if( nCmd==6 && strncmp(zCmd,"define",6)==0 ){ /* - ** Record a #define if we are in PS_Interface or PS_Export + ** Record a #define if we are in PS_Interface or PS_Export */ Decl *pDecl; if( !(flags & (PS_Local|PS_Interface|PS_Export)) ){ return 0; } zArg = &zCmd[6]; while( *zArg && isspace(*zArg) && *zArg!='\n' ){ @@ -2097,11 +2162,11 @@ }else if( flags & PS_Local ){ DeclSetProperty(pDecl,DP_Local); } }else if( nCmd==7 && strncmp(zCmd,"include",7)==0 ){ /* - ** Record an #include if we are in PS_Interface or PS_Export + ** Record an #include if we are in PS_Interface or PS_Export */ Include *pInclude; char *zIf; if( !(flags & (PS_Interface|PS_Export)) ){ return 0; } @@ -2141,30 +2206,32 @@ zArg = &zCmd[2]; while( *zArg && isspace(*zArg) && *zArg!='\n' ){ zArg++; } if( *zArg==0 || *zArg=='\n' ){ return 0; } - nArg = pToken->nText + (int)pToken->zText - (int)zArg; + nArg = pToken->nText + (int)(pToken->zText - zArg); if( nArg==9 && strncmp(zArg,"INTERFACE",9)==0 ){ PushIfMacro(0,0,0,pToken->nLine,PS_Interface); }else if( nArg==16 && strncmp(zArg,"EXPORT_INTERFACE",16)==0 ){ PushIfMacro(0,0,0,pToken->nLine,PS_Export); }else if( nArg==15 && strncmp(zArg,"LOCAL_INTERFACE",15)==0 ){ PushIfMacro(0,0,0,pToken->nLine,PS_Local); + }else if( nArg==15 && strncmp(zArg,"MAKEHEADERS_STOPLOCAL_INTERFACE",15)==0 ){ + PushIfMacro(0,0,0,pToken->nLine,PS_Local); }else{ PushIfMacro(0,zArg,nArg,pToken->nLine,0); } }else if( nCmd==5 && strncmp(zCmd,"ifdef",5)==0 ){ - /* + /* ** Push an #ifdef. */ zArg = &zCmd[5]; while( *zArg && isspace(*zArg) && *zArg!='\n' ){ zArg++; } if( *zArg==0 || *zArg=='\n' ){ return 0; } - nArg = pToken->nText + (int)pToken->zText - (int)zArg; + nArg = pToken->nText + (int)(pToken->zText - zArg); PushIfMacro("defined",zArg,nArg,pToken->nLine,0); }else if( nCmd==6 && strncmp(zCmd,"ifndef",6)==0 ){ /* ** Push an #ifndef. */ @@ -2171,15 +2238,15 @@ zArg = &zCmd[6]; while( *zArg && isspace(*zArg) && *zArg!='\n' ){ zArg++; } if( *zArg==0 || *zArg=='\n' ){ return 0; } - nArg = pToken->nText + (int)pToken->zText - (int)zArg; + nArg = pToken->nText + (int)(pToken->zText - zArg); PushIfMacro("!defined",zArg,nArg,pToken->nLine,0); }else if( nCmd==4 && strncmp(zCmd,"else",4)==0 ){ /* - ** Invert the #if on the top of the stack + ** Invert the #if on the top of the stack */ if( ifStack==0 ){ fprintf(stderr,"%s:%d: '#else' without an '#if'\n",zFilename, pToken->nLine); return 1; @@ -2192,33 +2259,33 @@ }else{ pIf->flags = 0; } }else{ /* - ** This directive can be safely ignored + ** This directive can be safely ignored */ return 0; } - /* - ** Recompute the preset flags + /* + ** Recompute the preset flags */ *pPresetFlags = 0; for(pIf = ifStack; pIf; pIf=pIf->pNext){ *pPresetFlags |= pIf->flags; } - + return nErr; } /* ** Parse an entire file. Return the number of errors. ** ** pList is a list of tokens in the file. Whitespace tokens have been ** eliminated, and text with {...} has been collapsed into a ** single TT_Brace token. -** +** ** initFlags are a set of parse flags that should always be set for this ** file. For .c files this is normally 0. For .h files it is PS_Interface. */ static int ParseFile(Token *pList, int initFlags){ int nErr = 0; @@ -2247,11 +2314,11 @@ pStart = 0; flags = presetFlags; break; case '=': - if( pList->pPrev->nText==8 + if( pList->pPrev->nText==8 && strncmp(pList->pPrev->zText,"operator",8)==0 ){ break; } nErr += ProcessDecl(pStart,pList,flags); pStart = 0; @@ -2317,11 +2384,13 @@ pStart = pList; } break; case 'i': - if( pList->nText==6 && strncmp(pList->zText,"inline",6)==0 ){ + if( pList->nText==6 && strncmp(pList->zText,"inline",6)==0 + && (flags & PS_Static)==0 + ){ nErr += ProcessInlineProc(pList,flags,&resetFlag); } break; case 'L': @@ -2437,11 +2506,11 @@ pDecl->zExtra = 0; } /* ** Reset the DP_Forward and DP_Declared flags on all Decl structures. -** Set both flags for anything that is tagged as local and isn't +** Set both flags for anything that is tagged as local and isn't ** in the file zFilename so that it won't be printing in other files. */ static void ResetDeclFlags(char *zFilename){ Decl *pDecl; @@ -2540,11 +2609,11 @@ int flag; int isCpp; /* True if generating C++ */ int doneTypedef = 0; /* True if a typedef has been done for this object */ /* printf("BEGIN %s of %s\n",needFullDecl?"FULL":"PROTOTYPE",pDecl->zName);*/ - /* + /* ** For any object that has a forward declaration, go ahead and do the ** forward declaration first. */ isCpp = (pState->flags & DP_Cplusplus) != 0; for(p=pDecl; p; p=p->pSameName){ @@ -2592,22 +2661,22 @@ ** function on a recursive call with the same pDecl. Hence, recursive ** calls to this function (through ScanText()) can never change the ** value of DP_Flag out from under us. */ for(p=pDecl; p; p=p->pSameName){ - if( !DeclHasProperty(p,DP_Declared) - && (p->zFwd==0 || needFullDecl) + if( !DeclHasProperty(p,DP_Declared) + && (p->zFwd==0 || needFullDecl) && p->zDecl!=0 ){ DeclSetProperty(p,DP_Forward|DP_Declared|DP_Flag); }else{ DeclClearProperty(p,DP_Flag); } } /* - ** Call ScanText() recusively (this routine is called from ScanText()) + ** Call ScanText() recursively (this routine is called from ScanText()) ** to include declarations required to come before these declarations. */ for(p=pDecl; p; p=p->pSameName){ if( DeclHasProperty(p,DP_Flag) ){ if( p->zDecl[0]=='#' ){ @@ -2701,12 +2770,12 @@ ** by sToken. */ pDecl = FindDecl(sToken.zText,sToken.nText); if( pDecl==0 ) continue; - /* - ** If we get this far, we've found an identifier that has a + /* + ** If we get this far, we've found an identifier that has a ** declaration in the database. Now see if we the full declaration ** or just a forward declaration. */ GetNonspaceToken(&sIn,&sNext); if( sNext.zText[0]=='*' ){ @@ -2727,21 +2796,21 @@ /* printf("END SCANTEXT\n"); */ } /* ** Provide a full declaration to any object which so far has had only -** a foward declaration. +** a forward declaration. */ static void CompleteForwardDeclarations(GenState *pState){ Decl *pDecl; int progress; do{ progress = 0; for(pDecl=pDeclFirst; pDecl; pDecl=pDecl->pNext){ - if( DeclHasProperty(pDecl,DP_Forward) - && !DeclHasProperty(pDecl,DP_Declared) + if( DeclHasProperty(pDecl,DP_Forward) + && !DeclHasProperty(pDecl,DP_Declared) ){ DeclareObject(pDecl,pState,1); progress = 1; assert( DeclHasProperty(pDecl,DP_Declared) ); } @@ -2797,11 +2866,11 @@ }else if( strncmp(zOldVersion,zTopLine,nTopLine)!=0 ){ if( report ) fprintf(report,"error!\n"); fprintf(stderr, "%s: Can't overwrite this file because it wasn't previously\n" "%*s generated by 'makeheaders'.\n", - pFile->zHdr, strlen(pFile->zHdr), ""); + pFile->zHdr, (int)strlen(pFile->zHdr), ""); nErr++; }else if( strcmp(zOldVersion,zNewVersion)!=0 ){ if( report ) fprintf(report,"updated\n"); if( WriteFile(pFile->zHdr,zNewVersion) ){ fprintf(stderr,"%s: Can't write to file\n",pFile->zHdr); @@ -2808,11 +2877,11 @@ nErr++; } }else if( report ){ fprintf(report,"unchanged\n"); } - SafeFree(zOldVersion); + SafeFree(zOldVersion); IdentTableReset(&includeTable); StringReset(&outStr); return nErr; } @@ -2844,11 +2913,11 @@ } ChangeIfContext(0,&sState); printf("%s",StringGet(&outStr)); IdentTableReset(&includeTable); StringReset(&outStr); - return 0; + return 0; } #ifdef DEBUG /* ** Return the number of characters in the given string prior to the @@ -2952,18 +3021,18 @@ if( nLabel==0 ) continue; zLabel[nLabel] = 0; InsertExtraDecl(pDecl); zDecl = pDecl->zDecl; if( zDecl==0 ) zDecl = pDecl->zFwd; - printf("%s %s %s %d %d %d %d %d %d\n", + printf("%s %s %s %p %d %d %d %d %d\n", pDecl->zName, zLabel, pDecl->zFile, - pDecl->pComment ? (int)pDecl->pComment/sizeof(Token) : 0, + pDecl->pComment, pDecl->pComment ? pDecl->pComment->nText+1 : 0, - pDecl->zIf ? strlen(pDecl->zIf)+1 : 0, - zDecl ? strlen(zDecl) : 0, + pDecl->zIf ? (int)strlen(pDecl->zIf)+1 : 0, + zDecl ? (int)strlen(zDecl) : 0, pDecl->pComment ? pDecl->pComment->nLine : 0, pDecl->tokenCode.nText ? pDecl->tokenCode.nText+1 : 0 ); if( pDecl->pComment ){ printf("%.*s\n",pDecl->pComment->nText, pDecl->pComment->zText); @@ -3006,15 +3075,21 @@ int nSrc; char *zSrc; InFile *pFile; int i; - /* - ** Get the name of the input file to be scanned + /* + ** Get the name of the input file to be scanned. The input file is + ** everything before the first ':' or the whole file if no ':' is seen. + ** + ** Except, on windows, ignore any ':' that occurs as the second character + ** since it might be part of the drive specifier. So really, the ":' has + ** to be the 3rd or later character in the name. This precludes 1-character + ** file names, which really should not be a problem. */ zSrc = zArg; - for(nSrc=0; zSrc[nSrc] && zArg[nSrc]!=':'; nSrc++){} + for(nSrc=2; zSrc[nSrc] && zArg[nSrc]!=':'; nSrc++){} pFile = SafeMalloc( sizeof(InFile) ); memset(pFile,0,sizeof(InFile)); pFile->zSrc = StrDup(zSrc,nSrc); /* Figure out if we are dealing with C or C++ code. Assume any @@ -3031,11 +3106,11 @@ */ if( zSrc[nSrc]==':' ){ int nHdr; char *zHdr; zHdr = &zSrc[nSrc+1]; - for(nHdr=0; zHdr[nHdr] && zHdr[nHdr]!=':'; nHdr++){} + for(nHdr=0; zHdr[nHdr]; nHdr++){} pFile->zHdr = StrDup(zHdr,nHdr); } /* Look for any 'c' or 'C' in the suffix of the file name and change ** that character to 'h' or 'H' respectively. If no 'c' or 'C' is found, @@ -3059,11 +3134,11 @@ } } /* ** If pFile->zSrc contains no 'c' or 'C' in its extension, it - ** must be a header file. In that case, we need to set the + ** must be a header file. In that case, we need to set the ** PS_Interface flag. */ pFile->flags |= PS_Interface; for(i=nSrc-1; i>0 && zSrc[i]!='.'; i--){ if( zSrc[i]=='c' || zSrc[i]=='C' ){ @@ -3070,11 +3145,11 @@ pFile->flags &= ~PS_Interface; break; } } - /* Done! + /* Done! */ return pFile; } /* MS-Windows and MS-DOS both have the following serious OS bug: the @@ -3122,11 +3197,11 @@ while( c!=EOF ){ while( c!=EOF && isspace(c) ){ if( c=='\n' ){ startOfLine = 1; } - c = getc(in); + c = getc(in); if( startOfLine && c=='#' ){ while( c!=EOF && c!='\n' ){ c = getc(in); } } @@ -3144,11 +3219,11 @@ if( nAlloc==0 ){ nAlloc = 100 + argc; zNew = malloc( sizeof(char*) * nAlloc ); }else{ nAlloc *= 2; - zNew = realloc( zNew, sizeof(char*) * nAlloc ); + zNew = realloc( zNew, sizeof(char*) * nAlloc ); } } if( zNew ){ int j = nNew + index; zNew[j] = malloc( n + 1 ); @@ -3156,10 +3231,11 @@ strcpy( zNew[j], zBuf ); } } } } + fclose(in); newArgc = argc + nNew - 1; for(i=0; i<=index; i++){ zNew[i] = argv[i]; } for(i=nNew + index + 1; iThe Makeheaders Program

    This document describes makeheaders, -a tool that automatically generates ``.h'' +a tool that automatically generates “.h” files for a C or C++ programming project.

    Table Of Contents

    +
  • 1,0 Background + +
  • 2.0 Running The Makeheaders Program + +
  • 3.0 Preparing Source Files For Use With Makeheaders + +
  • 4.0 Using Makeheaders To Generate Documentation + +
  • 5.0 Compiling The Makeheaders Program + +
  • 6.0 History + +
  • 7.0 Summary And Conclusion +

    1.0 Background

    A piece of C source code can be one of two things: a declaration or a definition. @@ -67,11 +69,11 @@

    • Typedefs.
    • Structure, union and enumeration declarations.
    • Function and procedure prototypes.
    • Preprocessor macros and #defines. -
    • ``extern'' variable declarations. +
    • extern” variable declarations.

    Definitions in C, on the other hand, include these kinds of things: @@ -89,20 +91,20 @@ is the interface and the definition is the implementation.

    In C programs, it has always been the tradition that declarations are -put in files with the ``.h'' suffix and definitions are -placed in ``.c'' files. -The .c files contain ``#include'' preprocessor statements +put in files with the “.h” suffix and definitions are +placed in “.c” files. +The .c files contain “#include” preprocessor statements that cause the contents of .h files to be included as part of the source code when the .c file is compiled. In this way, the .h files define the interface to a subsystem and the .c files define how the subsystem is implemented.

    - +

    1.1 Problems With The Traditional Approach

    As the art of computer programming continues to advance, and the size and complexity of programs continues to swell, the traditional C @@ -116,11 +118,11 @@

    1. In large codes with many source files, it becomes difficult to determine which .h files should be included in which .c files.

    2. -It is typically the case the a .h file will be forced to include +It is typically the case that a .h file will be forced to include another .h files, which in turn might include other .h files, and so forth. The .c file must be recompiled when any of the .h files in this chain are altered, but it can be difficult to determine what .h files are found in the include chain. @@ -143,26 +145,26 @@ change most frequently. This means that the entire program must be recompiled frequently, leading to a lengthy modify-compile-test cycle and a corresponding decrease in programmer productivity.

    3. -The C programming language requires that declarations depending upon +The C programming language requires that declarations depending upon each other must occur in a particular order. In a program with complex, interwoven data structures, the correct -declaration order can become very difficult to determine manually, +declaration order can become very difficult to determine manually, especially when the declarations involved are spread out over several files.

    - +

    1.2 The Makeheaders Solution

    The makeheaders program is designed to ameliorate the problems associated with the traditional C programming model by automatically generating -the interface information in the .h files from +the interface information in the .h files from interface information contained in other .h files and from implementation information in the .c files. When the makeheaders program is run, it scans the source files for a project, then generates a series of new .h files, one for each .c file. @@ -195,11 +197,11 @@

  • The generated .h file contains the minimal set of declarations needed by the .c file. This means that when something changes, a minimal amount of recompilation is required to produce an updated executable. -Experience has shown that this gives a dramatic improvement +Experience has shown that this gives a dramatic improvement in programmer productivity by facilitating a rapid modify-compile-test cycle during development.

  • The makeheaders program automatically sorts declarations into the correct order, completely eliminating the wearisome and error-prone @@ -215,11 +217,11 @@ And the burden of running makeheaders is light. It will easily process tens of thousands of lines of source code per second.

    - +

    2.0 Running The Makeheaders Program

    The makeheaders program is very easy to run. If you have a collection of C source code and include files in the working @@ -237,11 +239,11 @@ the declarations will be copied into the generated .h files as appropriate. But if makeheaders sees that the .h file that it has generated is no different from the .h file it generated last time, it doesn't update the file. -This prevents the corresponding .c files from having to +This prevents the corresponding .c files from having to be needlessly recompiled.

    There are several options to the makeheaders program that can @@ -262,11 +264,11 @@

    A similar option is -H. Like the lower-case -h option, big -H generates a single include file on standard output. But unlike small -h, the big -H only emits prototypes and declarations that -have been designated as ``exportable''. +have been designated as “exportable”. The idea is that -H will generate an include file that defines the interface to a library. More will be said about this in section 3.4.

    @@ -293,30 +295,30 @@ then you can supply an empty header filename, like this:
        makeheaders alpha.c beta.c gamma.c:
     
    In this example, makeheaders will scan the three files named -``alpha.c'', -``beta.c'' and -``gamma.c'' +“alpha.c”, +“beta.c” and +“gamma.c” but because of the colon on the end of third filename it will only generate headers for the first two files. Unfortunately, -it is not possible to get makeheaders to process any file whose +it is not possible to get makeheaders to process any file whose name contains a colon.

    In a large project, the length of the command line for makeheaders can become very long. If the operating system doesn't support long command lines (example: DOS and Win32) you may not be able to list all of the input files in the space available. -In that case, you can use the ``-f'' option followed +In that case, you can use the “-f” option followed by the name of a file to cause makeheaders to read command line options and filename from the file instead of from the command line. -For example, you might prepare a file named ``mkhdr.dat'' +For example, you might prepare a file named “mkhdr.dat” that contains text like this:

       src/alpha.c:hdr/alpha.h
       src/beta.c:hdr/beta.h
       src/gamma.c:hdr/gamma.h
    @@ -327,25 +329,25 @@
       makeheaders -f mkhdr.dat
     

    -The ``-local'' option causes makeheaders to -generate of prototypes for ``static'' functions and +The “-local” option causes makeheaders to +generate of prototypes for “static” functions and procedures. Such prototypes are normally omitted.

    -Finally, makeheaders also includes a ``-doc'' option. +Finally, makeheaders also includes a “-doc” option. This command line option prevents makeheaders from generating any headers at all. -Instead, makeheaders will write to standard output +Instead, makeheaders will write to standard output information about every definition and declaration that it encounters in its scan of source files. The information output includes the type of the definition or -declaration and any comment that preceeds the definition or +declaration and any comment that precedes the definition or declaration. The output is in a format that can be easily parsed, and is intended to be read by another program that will generate documentation about the program. We'll talk more about this feature later. @@ -352,22 +354,24 @@

    If you forget what command line options are available, or forget their exact name, you can invoke makeheaders using an unknown -command line option (like ``--help'' or ``-?'') +command line option (like “--help” or +“-?”) and it will print a summary of the available options on standard error. -If you need to process a file whose name begins with ``-'', -you can prepend a ``./'' to its name in order to get it +If you need to process a file whose name begins with +“-”, +you can prepend a “./” to its name in order to get it accepted by the command line parser. -Or, you can insert the special option ``--'' on the command -line to cause all subsequent command line arguments to be treated as -filenames even if their names beginn with ``-''. +Or, you can insert the special option “--” on the +command line to cause all subsequent command line arguments to be treated as +filenames even if their names begin with “-”.

    - +

    3.0 Preparing Source Files For Use With Makeheaders

    Very little has to be done to prepare source files for use with makeheaders since makeheaders will read and understand ordinary @@ -375,34 +379,35 @@ But it is important that you structure your files in a way that makes sense in the makeheaders context. This section will describe several typical uses of makeheaders.

    - +

    3.1 The Basic Setup

    -The simpliest way to use makeheaders is to put all definitions in +The simplest way to use makeheaders is to put all definitions in one or more .c files and all structure and type declarations in separate .h files. -The only restriction is that you should take care to chose basenames -for your .h files that are different from the basenames for you +The only restriction is that you should take care to chose basenames +for your .h files that are different from the basenames for your .c files. -Recall that if your .c file is named (for example) ``alpha.c'' +Recall that if your .c file is named (for example) +“alpha.c” makeheaders will attempt to generate a corresponding header file -named ``alpha.h''. +named “alpha.h”. For that reason, you don't want to use that name for any of the .h files you write since that will prevent makeheaders from generating the .h file automatically.

    -The structure of a .c file intented for use with makeheaders is very +The structure of a .c file intended for use with makeheaders is very simple. -All you have to do is add a single ``#include'' to the top -of the file that sources the header file that makeheaders will generate. -Hence, the beginning of a source file named ``alpha.c'' +All you have to do is add a single “#include” to the +top of the file that sources the header file that makeheaders will generate. +Hence, the beginning of a source file named “alpha.c” might look something like this:

        /*
    @@ -415,14 +420,14 @@
     
     

    Your manually generated header files require no special attention at all. Code them as you normally would. However, makeheaders will work better if you omit the -``#if'' statements people often put around the outside of +“#if” statements people often put around the outside of header files that prevent the files from being included more than once. -For example, to create a header file named ``beta.h'', many -people will habitually write the following: +For example, to create a header file named “beta.h”, +many people will habitually write the following:

        #ifndef BETA_H
        #define BETA_H
     
    @@ -435,11 +440,11 @@
     Remember that the header files you write will never really be
     included by any C code.
     Instead, makeheaders will scan your header files to extract only
     those declarations that are needed by individual .c files and then
     copy those declarations to the .h files corresponding to the .c files.
    -Hence, the ``#if'' wrapper serves no useful purpose.
    +Hence, the “#if” wrapper serves no useful purpose.
     But it does make makeheaders work harder, forcing it to put
     the statements
     
     
        #if !defined(BETA_H)
    @@ -459,26 +464,26 @@
        makeheaders *.[ch]
     
    The makeheaders program will scan all of the .c files and all of the manually written .h files and then automatically generate .h files -corresponding to all .c files. +corresponding to all .c files.

    Note that -the wildcard expression used in the above example, -``*.[ch]'', +the wildcard expression used in the above example, +“*.[ch]”, will expand to include all .h files in the current directory, both those entered manually be the programmer and others generated automatically by a prior run of makeheaders. But that is not a problem. -The makeheaders program will recognize and ignore any files it +The makeheaders program will recognize and ignore any files it has previously generated that show up on its input list.

    - +

    3.2 What Declarations Get Copied

    The following list details all of the code constructs that makeheaders will extract and place in @@ -489,14 +494,14 @@

  • When a function is defined in any .c file, a prototype of that function is placed in the generated .h file of every .c file that calls the function.

    -

    If the ``static'' keyword of C appears at the beginning -of the function definition, the prototype is suppressed. -If you use the ``LOCAL'' keyword where you would normally -say ``static'', then a prototype is generated, but it +

    If the “static” keyword of C appears at the +beginning of the function definition, the prototype is suppressed. +If you use the “LOCAL” keyword where you would normally +say “static”, then a prototype is generated, but it will only appear in the single header file that corresponds to the source file containing the function. For example, if the file alpha.c contains the following:

       LOCAL int testFunc(void){
    @@ -509,32 +514,34 @@
       LOCAL int testFunc(void);
     
    However, no other generated header files will contain a prototype for testFunc() since the function has only file scope.

    -

    When the ``LOCAL'' keyword is used, makeheaders will also -generate a #define for LOCAL, like this: +

    When the “LOCAL” keyword is used, makeheaders will +also generate a #define for LOCAL, like this:

        #define LOCAL static
     
    so that the C compiler will know what it means.

    -

    If you invoke makeheaders with a ``-local'' command-line -option, then it treats the ``static'' keyword like -``LOCAL'' and generates prototypes in the header file -that corresponds to the source file containing the function defintiion.

    +

    If you invoke makeheaders with a “-local” +command-line option, then it treats the “static” +keyword like “LOCAL” and generates prototypes in the +header file that corresponds to the source file containing the function +definition.

  • -When a global variable is defined in a .c file, an ``extern'' +When a global variable is defined in a .c file, an +“extern” declaration of that variable is placed in the header of every .c file that uses the variable.

  • When a structure, union or enumeration declaration or a function prototype or a C++ class declaration appears in a -manually produced .h file, that declaration is copied into the +manually produced .h file, that declaration is copied into the automatically generated .h files of all .c files that use the structure, union, enumeration, function or class. But declarations that appear in a .c file are considered private to that .c file and are not copied into @@ -550,12 +557,12 @@

  • When a structure, union or enumeration declaration appears in a .h file, makeheaders will automatically generate a typedef that allows the declaration to be referenced without -the ``struct'', ``union'' or ``enum'' -qualifier. +the “struct”, “union” or +“enum” qualifier. In other words, if makeheaders sees the code:
       struct Examp { /* ... */ };
     
    it will automatically generate a corresponding typedef like this: @@ -572,25 +579,25 @@

    As a final note, we observe that automatically generated declarations are ordered as required by the ANSI-C programming language. -If the declaration of some structure ``X'' requires a prior -declaration of another structure ``Y'', then Y will appear -first in the generated headers. +If the declaration of some structure “X” requires a +prior declaration of another structure “Y”, then Y will +appear first in the generated headers.

    - +

    3.3 How To Avoid Having To Write Any Header Files

    In my experience, large projects work better if all of the manually written code is placed in .c files and all .h files are generated automatically. -This is slightly different for the traditional C method of placing +This is slightly different from the traditional C method of placing the interface in .h files and the implementation in .c files, but -it is a refreshing change that brings a noticable improvement to the +it is a refreshing change that brings a noticeable improvement to the coding experience. Others, I believe, share this view since I've noticed recent languages (ex: java, tcl, perl, awk) tend to support the one-file approach to coding as the only option.

    @@ -610,19 +617,19 @@ it were a .h file by enclosing that part of the .c file within:
        #if INTERFACE
        #endif
     
    -Thus any structure definitions that appear after the -``#if INTERFACE'' but before the corresponding -``#endif'' are eligable to be copied into the +Thus any structure definitions that appear after the +“#if INTERFACE” but before the corresponding +“#endif” are eligible to be copied into the automatically generated .h files of other .c files.

    -If you use the ``#if INTERFACE'' mechanism in a .c file, +If you use the “#if INTERFACE” mechanism in a .c file, then the generated header for that .c file will contain a line like this:

        #define INTERFACE 0
     
    @@ -637,18 +644,18 @@

    Note that you don't have to use this approach exclusively. You can put some declarations in .h files and others within the -``#if INTERFACE'' regions of .c files. +“#if INTERFACE” regions of .c files. Makeheaders treats all declarations alike, no matter where they come from. You should also note that a single .c file can contain as many -``#if INTERFACE'' regions as desired. +“#if INTERFACE” regions as desired.

    - +

    3.4 Designating Declarations For Export

    In a large project, one will often construct a hierarchy of interfaces. @@ -664,11 +671,11 @@ like this, but makeheaders does.

    Using makeheaders, it is possible to designate routines and data -structures as being for ``export''. +structures as being for “export”. Exported objects are visible not only to other files within the same library or subassembly but also to other libraries and subassemblies in the larger program. By default, makeheaders only makes objects visible to other members of the same library. @@ -677,11 +684,11 @@

    That isn't the complete truth, actually. The semantics of C are such that once an object becomes visible outside of a single source file, it is also visible to any user of the library that is made from the source file. -Makeheaders can not prevent outsiders for using non-exported resources, +Makeheaders can not prevent outsiders from using non-exported resources, but it can discourage the practice by refusing to provide prototypes and declarations for the services it does not want to export. Thus the only real effect of the making an object exportable is to include it in the output makeheaders generates when it is run using the -H command line option. @@ -690,14 +697,14 @@

    But trouble quickly arises when we attempt to devise a mechanism for telling makeheaders which prototypes it should export and which it should keep local. -The built-in ``static'' keyword of C works well for prohibiting -prototypes from leaving a single source file, but because C doesn't +The built-in “static” keyword of C works well for +prohibiting prototypes from leaving a single source file, but because C doesn't support a linkage hierarchy, there is nothing in the C language to help us. -We'll have to invite our own keyword: ``EXPORT'' +We'll have to invite our own keyword: “EXPORT

    Makeheaders allows the EXPORT keyword to precede any function or procedure definition. @@ -726,36 +733,36 @@

        #if EXPORT_INTERFACE
        #endif
     
    will become part of the exported interface. -The ``#if EXPORT_INTERFACE'' mechanism can be used in either -.c or .h files. -(The ``#if INTERFACE'' can also be used in both .h and .c files, -but since it's use in a .h file would be redundant, we haven't mentioned -it before.) +The “#if EXPORT_INTERFACE” mechanism can be used in +either .c or .h files. +(The “#if INTERFACE” can also be used in both .h and +.c files, but since it's use in a .h file would be redundant, we haven't +mentioned it before.)

    - +

    3.5 Local declarations processed by makeheaders

    Structure declarations and typedefs that appear in .c files are normally ignored by makeheaders. Such declarations are only intended for use by the source file in which they appear and so makeheaders doesn't need to copy them into any generated header files. -We call such declarations ``private''. +We call such declarations “private”.

    Sometimes it is convenient to have makeheaders sort a sequence of private declarations into the correct order for us automatically. Or, we could have static functions and procedures for which we would like makeheaders to generate prototypes, but the arguments to these functions and procedures uses private declarations. -In both of these cases, we want makeheaders to be aware of the +In both of these cases, we want makeheaders to be aware of the private declarations and copy them into the local header file, but we don't want makeheaders to propagate the declarations outside of the file in which they are declared.

    @@ -764,24 +771,26 @@ within
       #if LOCAL_INTERFACE
       #endif
     
    -A ``LOCAL_INTERFACE'' block works very much like the -``INTERFACE'' and ``EXPORT_INTERFACE'' +A “LOCAL_INTERFACE” block works very much like the +“INTERFACE” and +“EXPORT_INTERFACE” blocks described above, except that makeheaders insures that the objects declared in a LOCAL_INTERFACE are only visible to the file containing the LOCAL_INTERFACE.

    - +

    3.6 Using Makeheaders With C++ Code

    You can use makeheaders to generate header files for C++ code, in addition to C. -Makeheaders will recognize and copy both ``class'' declarations +Makeheaders will recognize and copy both “class” +declarations and inline function definitions, and it knows not to try to generate prototypes for methods.

    @@ -805,21 +814,21 @@ C++ input. Makeheaders will recognize that its source code is C++ by the suffix on the source code filename. Simple ".c" or ".h" suffixes are assumed to be ANSI-C. Anything else, including ".cc", ".C" and ".cpp" is assumed to be C++. The name of the header file generated by makeheaders is derived from -by the name of the source file by converting every "c" to "h" and +the name of the source file by converting every "c" to "h" and every "C" to "H" in the suffix of the filename. Thus the C++ source -file ``alpha.cpp'' will induce makeheaders to -generate a header file named ``alpha.hpp''. +file “alpha.cpp” will induce makeheaders to +generate a header file named “alpha.hpp”.

    Makeheaders augments class definitions by inserting prototypes to -methods were appropriate. If a method definition begins with one -of the special keywords PUBLIC, PROTECTED, or +methods where appropriate. If a method definition begins with one +of the special keywords PUBLIC, PROTECTED, or PRIVATE (in upper-case to distinguish them from the regular C++ keywords with the same meaning) then a prototype for that method will be inserted into the class definition. If none of these keywords appear, then the prototype is not inserted. For example, in the following code, the constructor is not explicitly @@ -859,33 +868,33 @@

  • The first form is preferred because only a single declaration of the constructor is required. The second form requires two declarations, -one in the class definition and one on the defintion of the constructor. +one in the class definition and one on the definition of the constructor.

    3.6.1 C++ Limitations

    Makeheaders does not understand more recent C++ syntax such as templates and namespaces. -Perhaps these issued will be addressed in future revisions. +Perhaps these issues will be addressed in future revisions.

    - +

    3.7 Conditional Compilation

    -The makeheaders program understands and tracks the conditional +The makeheaders program understands and tracks the conditional compilation constructs in the source code files it scans. Hence, if the following code appears in a source file

       #ifdef UNIX
    -  #  define WORKS_WELL  1
    +  #  define WORKS_WELL 1
       #else
    -  #  define WORKS_WELL  0
    +  #  define WORKS_WELL 0
       #endif
     
    then the next patch of code will appear in the generated header for every .c file that uses the WORKS_WELL constant:
    @@ -903,11 +912,11 @@
       #endif
     
    and treats the enclosed text as a comment.

    - +

    3.8 Caveats

    The makeheaders system is designed to be robust but it is possible for a devious programmer to fool the system, @@ -918,12 +927,12 @@

    Makeheaders does not understand the old K&R style of function and procedure definitions. It only understands the modern ANSI-C style, and will probably become very confused if it encounters an old K&R function. -You should take care to avoid putting K&R function defintions -in your code, therefore. +Therefore you should take care to avoid putting K&R function definitions +in your code.

    Makeheaders does not understand when you define more than one global variable with the same type separated by a comma. @@ -942,10 +951,32 @@ Since global variables ought to be exceedingly rare, and since it is good style to declare them separately anyhow, this restriction is not seen as a terrible hardship.

    +

    +Makeheaders does not support defining an enumerated or aggregate type in +the same statement as a variable declaration. None of the following +statements work completely: +

    +struct {int field;} a;
    +struct Tag {int field;} b;
    +struct Tag c;
    +
    +Instead, define types separately from variables: +
    +#if INTERFACE
    +struct Tag {int field;};
    +#endif
    +Tag a;
    +Tag b; /* No more than one variable per declaration. */
    +Tag c; /* So must put each on its own line. */
    +
    +See 3.2 What Declarations Get Copied for details, +including on the automatic typedef. +

    +

    The makeheaders program processes its source file prior to sending those files through the C preprocessor. Hence, if you hide important structure information in preprocessor defines, makeheaders might not be able to successfully extract the information @@ -973,11 +1004,26 @@ As long as you avoid excessive cleverness, makeheaders will probably be able to figure out what you want and will do the right thing.

    - +

    +Makeheaders has limited understanding of enums. In particular, it does +not realize the significance of enumerated values, so the enum is not +emitted in the header files when its enumerated values are used unless +the name associated with the enum is also used. Moreover, enums can be +completely anonymous, e.g. “enum {X, Y, Z};”. +Makeheaders ignores such enums so they can at least be used within a +single source file. Makeheaders expects you to use #define constants +instead. If you want enum features that #define lacks, and you need the +enum in the interface, bypass makeheaders and write a header file by +hand, or teach makeheaders to emit the enum definition when any of the +enumerated values are used, rather than only when the top-level name (if +any) is used. +

    + +

    4.0 Using Makeheaders To Generate Documentation

    Many people have observed the advantages of generating program documentation directly from the source code: @@ -993,72 +1039,98 @@ in the code, it is not necessary to make a corresponding change in a separate document. Just rerun the documentation generator. The makeheaders program does not generate program documentation itself. But you can use makeheaders to parse the program source code, extract -the information that is relavant to the documentation and to pass this +the information that is relevant to the documentation and to pass this information to another tool to do the actual documentation preparation.

    -When makeheaders is run with the ``-doc'' option, it emits -no header files at all. +When makeheaders is run with the “-doc” option, it +emits no header files at all. Instead, it does a complete dump of its internal tables to standard -outputs in a form that is easily parsed. +output in a form that is easily parsed. This output can then be used by another program (the implementation of which is left as an exercise to the reader) that will use the information to prepare suitable documentation.

    -The ``-doc'' option causes makeheaders to print information -to standard output about all of the following objects: +The “-doc” option causes makeheaders to print +information to standard output about all of the following objects:

      -
    • C++ Class declarations +
    • C++ class declarations
    • Structure and union declarations
    • Enumerations
    • Typedefs
    • Procedure and function definitions
    • Global variables -
    • Preprocessor macros (ex: ``#define'') +
    • Preprocessor macros (ex: “#define”)
    For each of these objects, the following information is output:
    • The name of the object.
    • The type of the object. (Structure, typedef, macro, etc.)
    • Flags to indicate if the declaration is exported (contained within an EXPORT_INTERFACE block) or local (contained with LOCAL_INTERFACE).
    • A flag to indicate if the object is declared in a C++ file.
    • The name of the file in which the object was declared. -
    • The complete text of any block comment that preceeds the declarations. +
    • The complete text of any block comment that precedes the declarations.
    • If the declaration occurred inside a preprocessor conditional - (``#if'') then the text of that conditional is provided. + (“#if”) then the text of that conditional is + provided.
    • The complete text of a declaration for the object.
    The exact output format will not be described here. It is simple to understand and parse and should be obvious to anyone who inspects some sample output.

    - +

    5.0 Compiling The Makeheaders Program

    The source code for makeheaders is a single file of ANSI-C code, -less than 3000 lines in length. +approximately 3000 lines in length. The program makes only modest demands of the system and C library and should compile without alteration on most ANSI C compilers and on most operating systems. It is known to compile using several variations of GCC for Unix as well as Cygwin32 and MSVC 5.0 for Win32.

    - -

    6.0 Summary And Conclusion

    +
    +

    6.0 History

    + +

    +The makeheaders program was first written by D. Richard Hipp +(also the original author of +SQLite and +Fossil) in 1993. +Hipp open-sourced the project immediately, but it never caught +on with any other developers and it continued to be used mostly +by Hipp himself for over a decade. When Hipp was first writing +the Fossil version control system in 2006 and 2007, he used +makeheaders on that project to help simplify the source code. +As the popularity of Fossil increased, the makeheaders +that was incorporated into the Fossil source tree became the +"official" makeheaders implementation. +

    + +

    +As this paragraph is being composed (2016-11-05), Fossil is the +only project known to Hipp that is still using makeheaders. On +the other hand, makeheaders has served the Fossil project well and +there are no plans remove it. +

    + + +

    7.0 Summary And Conclusion

    -The makeheaders program will automatically generate a minimal header file +The makeheaders program will automatically generate a minimal header file for each of a set of C source and header files, and will generate a composite header file for the entire source file suite, for either internal or external use. It can also be used as the parser in an automated program documentation system. @@ -1073,5 +1145,6 @@ In at least two cases, makeheaders has facilitated development of programs that would have otherwise been all but impossible due to their size and complexity.

    + Index: src/makemake.tcl ================================================================== --- src/makemake.tcl +++ src/makemake.tcl @@ -1,207 +1,2256 @@ #!/usr/bin/tclsh # -# Run this TCL script to generate the "main.mk" makefile. +# ### Run this Tcl script EVERY time you modify it in any way! ### # +# This Tcl script generates make files for various platforms. The makefiles +# then need to be committed. +# +# If you modify this file then: +# +# 1. cd src; tclsh makemake.tcl +# +# 2. if errors are reported, fix them and go to step 1 +# +# 3. if "fossil diff" reports changes in any of the generated +# files, commit the changed files to the repo +# +# Files generated include: +# +# src/main.mk # makefile for all unix systems +# win/Makefile.mingw # makefile for mingw on windows +# win/Makefile.* # makefiles for other windows compilers +# +# Add new source files by listing the files (without their .c suffix) +# in the "src" variable. Add new resource files to the "extra_files" +# variable. There are other variables that you can alter, down to +# the "STOP HERE" comment. The stuff below "STOP HERE" should rarely need +# to change. After modification, go to step 1 above. +# +# Delete unused source files in the "src" variable, then go to step 1 above. +# +############################################################################# # Basenames of all source files that get preprocessed using -# "translate" and "makeheaders" +# "translate" and "makeheaders". To add new C-language source files to the +# project, simply add the basename to this list and rerun this script. +# +# Set the separate extra_files variable further down for how to add non-C +# files, such as string and BLOB resources. # set src { add + ajax + alerts allrepo attach + backlink + backoffice bag + bisect blob branch browse + builtin + bundle + cache + capabilities captcha cgi + chat checkin checkout clearsign clone + color comformat configure content + cookies db delta deltacmd + deltafunc descendants diff diffcmd + dispatch doc encode + etag + event + extcgi + export file + fileedit finfo + foci + forum + fshell + fusefs + fuzz + glob graph + gzip + hname + hook http http_socket http_transport + import info + interwiki + json + json_artifact + json_branch + json_config + json_diff + json_dir + json_finfo + json_login + json_query + json_report + json_status + json_tag + json_timeline + json_user + json_wiki + leaf + loadctrl login + lookslike main manifest + markdown + markdown_html md5 merge merge3 + moderate name + patch + path + piechart + pikchr + pikchrshow pivot + popen pqueue printf + publish + purge rebuild + regexp + repolist report rss schema search + security_audit setup + setupuser sha1 + sha1hard + sha3 shun + sitemap skins + smtp + sqlcmd + stash stat + statrep style sync tag + tar + terminal th_main timeline tkt tktsetup undo + unicode + unversioned update url user + utf8 + util verify vfile wiki wikiformat + winfile winhttp xfer + xfersetup zip + http_ssl +} + +# Additional resource files that get built into the executable. +# +set extra_files { + diff.tcl + markdown.md + wiki.wiki + *.js + default.css + style.*.css + ../skins/*/*.txt + sounds/*.wav + alerts/*.wav +} + +# Options used to compile the included SQLite library. +# +set SQLITE_OPTIONS { + -DNDEBUG=1 + -DSQLITE_DQS=0 + -DSQLITE_THREADSAFE=0 + -DSQLITE_DEFAULT_MEMSTATUS=0 + -DSQLITE_DEFAULT_WAL_SYNCHRONOUS=1 + -DSQLITE_LIKE_DOESNT_MATCH_BLOBS + -DSQLITE_OMIT_DECLTYPE + -DSQLITE_OMIT_DEPRECATED + -DSQLITE_OMIT_PROGRESS_CALLBACK + -DSQLITE_OMIT_SHARED_CACHE + -DSQLITE_OMIT_LOAD_EXTENSION + -DSQLITE_MAX_EXPR_DEPTH=0 + -DSQLITE_USE_ALLOCA + -DSQLITE_ENABLE_LOCKING_STYLE=0 + -DSQLITE_DEFAULT_FILE_FORMAT=4 + -DSQLITE_ENABLE_EXPLAIN_COMMENTS + -DSQLITE_ENABLE_FTS4 + -DSQLITE_ENABLE_DBSTAT_VTAB + -DSQLITE_ENABLE_JSON1 + -DSQLITE_ENABLE_FTS5 + -DSQLITE_ENABLE_STMTVTAB + -DSQLITE_HAVE_ZLIB + -DSQLITE_INTROSPECTION_PRAGMAS + -DSQLITE_ENABLE_DBPAGE_VTAB + -DSQLITE_TRUSTED_SCHEMA=0 +} +#lappend SQLITE_OPTIONS -DSQLITE_ENABLE_FTS3=1 +#lappend SQLITE_OPTIONS -DSQLITE_ENABLE_STAT4 +#lappend SQLITE_OPTIONS -DSQLITE_WIN32_NO_ANSI +#lappend SQLITE_OPTIONS -DSQLITE_WINNT_MAX_PATH_CHARS=4096 + +# Options used to compile the included SQLite shell. +# +set SHELL_OPTIONS [concat $SQLITE_OPTIONS { + -Dmain=sqlite3_shell + -DSQLITE_SHELL_IS_UTF8=1 + -DSQLITE_OMIT_LOAD_EXTENSION=1 + -DUSE_SYSTEM_SQLITE=$(USE_SYSTEM_SQLITE) + -DSQLITE_SHELL_DBNAME_PROC=sqlcmd_get_dbname + -DSQLITE_SHELL_INIT_PROC=sqlcmd_init_proc +}] + +# miniz (libz drop-in alternative) precompiler flags. +# +set MINIZ_OPTIONS { + -DMINIZ_NO_STDIO + -DMINIZ_NO_TIME + -DMINIZ_NO_ARCHIVE_APIS } +# Options used to compile the included SQLite shell on Windows. +# +set SHELL_WIN32_OPTIONS $SHELL_OPTIONS +lappend SHELL_WIN32_OPTIONS -Daccess=file_access +lappend SHELL_WIN32_OPTIONS -Dsystem=fossil_system +lappend SHELL_WIN32_OPTIONS -Dgetenv=fossil_getenv +lappend SHELL_WIN32_OPTIONS -Dfopen=fossil_fopen + +# STOP HERE. +# Unless the build procedures changes, you should not have to edit anything +# below this line. +############################################################################# + # Name of the final application # set name fossil -puts {# DO NOT EDIT +# The "writeln" command sends output to the target makefile. +# +proc writeln {args} { + global output_file + if {[lindex $args 0]=="-nonewline"} { + puts -nonewline $output_file [lindex $args 1] + } else { + puts $output_file [lindex $args 0] + } +} + +# Expand any wildcards in "extra_files" +set new_extra_files {} +foreach file $extra_files { + foreach x [glob -nocomplain $file] { + lappend new_extra_files $x + } +} +set extra_files $new_extra_files + +############################################################################## +############################################################################## +############################################################################## +# Start by generating the "main.mk" makefile used for all unix systems. +# +puts "building main.mk" +set output_file [open main.mk w] +fconfigure $output_file -translation binary + +writeln {# +############################################################################## +# WARNING: DO NOT EDIT, AUTOMATICALLY GENERATED FILE (SEE "src/makemake.tcl") +############################################################################## # # This file is automatically generated. Instead of editing this -# file, edit "makemake.tcl" then run "tclsh makemake.tcl >main.mk" +# file, edit "makemake.tcl" then run "tclsh makemake.tcl" # to regenerate this file. # -# This file is included by linux-gcc.mk or linux-mingw.mk or possible -# some other makefiles. This file contains the rules that are common -# to building regardless of the target. -# - -XTCC = $(TCC) $(CFLAGS) -I. -I$(SRCDIR) - -} -puts -nonewline "SRC =" -foreach s [lsort $src] { - puts -nonewline " \\\n \$(SRCDIR)/$s.c" -} -puts "\n" -puts -nonewline "TRANS_SRC =" -foreach s [lsort $src] { - puts -nonewline " \\\n ${s}_.c" -} -puts "\n" -puts -nonewline "OBJ =" -foreach s [lsort $src] { - puts -nonewline " \\\n \$(OBJDIR)/$s.o" -} -puts "\n" -puts "APPNAME = $name\$(E)" -puts "\n" - -puts { +# This file is included by primary Makefile. +# + +XBCC = $(BCC) $(BCCFLAGS) +XTCC = $(TCC) -I. -I$(SRCDIR) -I$(OBJDIR) $(TCCFLAGS) + +TESTFLAGS := -quiet +} +writeln -nonewline "SRC =" +foreach s [lsort $src] { + writeln -nonewline " \\\n \$(SRCDIR)/$s.c" +} +writeln "\n" +writeln -nonewline "EXTRA_FILES =" +foreach s [lsort $extra_files] { + writeln -nonewline " \\\n \$(SRCDIR)/$s" +} +writeln "\n" +writeln -nonewline "TRANS_SRC =" +foreach s [lsort $src] { + writeln -nonewline " \\\n \$(OBJDIR)/${s}_.c" +} +writeln "\n" +writeln -nonewline "OBJ =" +foreach s [lsort $src] { + writeln -nonewline " \\\n \$(OBJDIR)/$s.o" +} + +writeln [string map [list \ + <<>> [join $SQLITE_OPTIONS " \\\n "] \ + <<>> [join $SHELL_OPTIONS " \\\n "] \ + <<>> [join $MINIZ_OPTIONS " \\\n "]] { all: $(OBJDIR) $(APPNAME) -install: $(APPNAME) - mv $(APPNAME) $(INSTALLDIR) +install: all + mkdir -p $(INSTALLDIR) + cp $(APPNAME) $(INSTALLDIR) + +codecheck: $(TRANS_SRC) $(OBJDIR)/codecheck1 + $(OBJDIR)/codecheck1 $(TRANS_SRC) $(OBJDIR): -mkdir $(OBJDIR) -translate: $(SRCDIR)/translate.c - $(BCC) -o translate $(SRCDIR)/translate.c - -makeheaders: $(SRCDIR)/makeheaders.c - $(BCC) -o makeheaders $(SRCDIR)/makeheaders.c - -mkindex: $(SRCDIR)/mkindex.c - $(BCC) -o mkindex $(SRCDIR)/mkindex.c - -# WARNING. DANGER. Running the testsuite modifies the repository the +$(OBJDIR)/translate: $(SRCDIR)/translate.c + $(XBCC) -o $(OBJDIR)/translate $(SRCDIR)/translate.c + +$(OBJDIR)/makeheaders: $(SRCDIR)/makeheaders.c + $(XBCC) -o $(OBJDIR)/makeheaders $(SRCDIR)/makeheaders.c + +$(OBJDIR)/mkindex: $(SRCDIR)/mkindex.c + $(XBCC) -o $(OBJDIR)/mkindex $(SRCDIR)/mkindex.c + +$(OBJDIR)/mkbuiltin: $(SRCDIR)/mkbuiltin.c + $(XBCC) -o $(OBJDIR)/mkbuiltin $(SRCDIR)/mkbuiltin.c + +$(OBJDIR)/mkversion: $(SRCDIR)/mkversion.c + $(XBCC) -o $(OBJDIR)/mkversion $(SRCDIR)/mkversion.c + +$(OBJDIR)/codecheck1: $(SRCDIR)/codecheck1.c + $(XBCC) -o $(OBJDIR)/codecheck1 $(SRCDIR)/codecheck1.c + +# Run the test suite. +# Other flags that can be included in TESTFLAGS are: +# +# -halt Stop testing after the first failed test +# -keep Keep the temporary workspace for debugging +# -prot Write a detailed log of the tests to the file ./prot +# -verbose Include even more details in the output +# -quiet Hide most output from the terminal +# -strict Treat known bugs as failures +# +# TESTFLAGS can also include names of specific test files to limit +# the run to just those test cases. +# +test: $(OBJDIR) $(APPNAME) + $(TCLSH) $(SRCDIR)/../test/tester.tcl $(APPNAME) $(TESTFLAGS) + +$(OBJDIR)/VERSION.h: $(SRCDIR)/../manifest.uuid $(SRCDIR)/../manifest $(SRCDIR)/../VERSION $(OBJDIR)/mkversion $(OBJDIR)/phony.h + $(OBJDIR)/mkversion $(SRCDIR)/../manifest.uuid \ + $(SRCDIR)/../manifest \ + $(SRCDIR)/../VERSION >$(OBJDIR)/VERSION.h + +$(OBJDIR)/phony.h: + # Force rebuild of VERSION.h every time we run "make" + +# Setup the options used to compile the included SQLite library. +SQLITE_OPTIONS = <<>> + +# Setup the options used to compile the included SQLite shell. +SHELL_OPTIONS = <<>> + +# Setup the options used to compile the included miniz library. +MINIZ_OPTIONS = <<>> + +# The USE_SYSTEM_SQLITE variable may be undefined, set to 0, or set +# to 1. If it is set to 1, then there is no need to build or link +# the sqlite3.o object. Instead, the system SQLite will be linked +# using -lsqlite3. +SQLITE3_OBJ.0 = $(OBJDIR)/sqlite3.o +SQLITE3_OBJ.1 = +SQLITE3_OBJ. = $(SQLITE3_OBJ.0) + +# The FOSSIL_ENABLE_MINIZ variable may be undefined, set to 0, or +# set to 1. If it is set to 1, the miniz library included in the +# source tree should be used; otherwise, it should not. +MINIZ_OBJ.0 = +MINIZ_OBJ.1 = $(OBJDIR)/miniz.o +MINIZ_OBJ. = $(MINIZ_OBJ.0) + +# The USE_LINENOISE variable may be undefined, set to 0, or set +# to 1. If it is set to 0, then there is no need to build or link +# the linenoise.o object. +LINENOISE_DEF.0 = +LINENOISE_DEF.1 = -DHAVE_LINENOISE +LINENOISE_DEF. = $(LINENOISE_DEF.0) +LINENOISE_OBJ.0 = +LINENOISE_OBJ.1 = $(OBJDIR)/linenoise.o +LINENOISE_OBJ. = $(LINENOISE_OBJ.0) + +# The USE_SEE variable may be undefined, 0 or 1. If undefined or +# 0, ordinary SQLite is used. If 1, then sqlite3-see.c (not part of +# the source tree) is used and extra flags are provided to enable +# the SQLite Encryption Extension. +SQLITE3_SRC.0 = sqlite3.c +SQLITE3_SRC.1 = sqlite3-see.c +SQLITE3_SRC. = sqlite3.c +SQLITE3_SRC = $(SRCDIR)/$(SQLITE3_SRC.$(USE_SEE)) +SQLITE3_SHELL_SRC.0 = shell.c +SQLITE3_SHELL_SRC.1 = shell-see.c +SQLITE3_SHELL_SRC. = shell.c +SQLITE3_SHELL_SRC = $(SRCDIR)/$(SQLITE3_SHELL_SRC.$(USE_SEE)) +SEE_FLAGS.0 = +SEE_FLAGS.1 = -DSQLITE_HAS_CODEC -DSQLITE_SHELL_DBKEY_PROC=fossil_key +SEE_FLAGS. = +SEE_FLAGS = $(SEE_FLAGS.$(USE_SEE)) +}] + +writeln [string map [list <<>> \\] { +EXTRAOBJ = <<>> + $(SQLITE3_OBJ.$(USE_SYSTEM_SQLITE)) <<>> + $(MINIZ_OBJ.$(FOSSIL_ENABLE_MINIZ)) <<>> + $(LINENOISE_OBJ.$(USE_LINENOISE)) <<>> + $(OBJDIR)/shell.o <<>> + $(OBJDIR)/th.o <<>> + $(OBJDIR)/th_lang.o <<>> + $(OBJDIR)/th_tcl.o <<>> + $(OBJDIR)/cson_amalgamation.o +}] + +writeln { +$(APPNAME): $(OBJDIR)/headers $(OBJDIR)/codecheck1 $(EXTRAOBJ) $(OBJ) + $(OBJDIR)/codecheck1 $(TRANS_SRC) + $(TCC) $(TCCFLAGS) -o $(APPNAME) $(EXTRAOBJ) $(OBJ) $(LIB) + +# This rule prevents make from using its default rules to try build +# an executable named "manifest" out of the file named "manifest.c" +# +$(SRCDIR)/../manifest: + # noop + +clean: + -rm -rf $(OBJDIR)/* $(APPNAME) + +} + +set mhargs {} +foreach s [lsort $src] { + append mhargs "\$(OBJDIR)/${s}_.c:\$(OBJDIR)/$s.h <<>>" + set extra_h($s) { } +} +append mhargs "\$(SRCDIR)/sqlite3.h <<>>" +append mhargs "\$(SRCDIR)/th.h <<>>" +#append mhargs "\$(SRCDIR)/cson_amalgamation.h <<>>" +append mhargs "\$(OBJDIR)/VERSION.h " +set mhargs [string map [list <<>> \\\n\t] $mhargs] +writeln "\$(OBJDIR)/page_index.h: \$(TRANS_SRC) \$(OBJDIR)/mkindex" +writeln "\t\$(OBJDIR)/mkindex \$(TRANS_SRC) >\$@\n" + +writeln "\$(OBJDIR)/builtin_data.h: \$(OBJDIR)/mkbuiltin \$(EXTRA_FILES)" +writeln "\t\$(OBJDIR)/mkbuiltin --prefix \$(SRCDIR)/ \$(EXTRA_FILES) >\$@\n" + +writeln "\$(OBJDIR)/headers:\t\$(OBJDIR)/page_index.h \$(OBJDIR)/builtin_data.h \$(OBJDIR)/makeheaders \$(OBJDIR)/VERSION.h" +writeln "\t\$(OBJDIR)/makeheaders $mhargs" +writeln "\ttouch \$(OBJDIR)/headers" +writeln "\$(OBJDIR)/headers: Makefile" +writeln "\$(OBJDIR)/json.o \$(OBJDIR)/json_artifact.o \$(OBJDIR)/json_branch.o \$(OBJDIR)/json_config.o \$(OBJDIR)/json_diff.o \$(OBJDIR)/json_dir.o \$(OBJDIR)/json_finfo.o \$(OBJDIR)/json_login.o \$(OBJDIR)/json_query.o \$(OBJDIR)/json_report.o \$(OBJDIR)/json_status.o \$(OBJDIR)/json_tag.o \$(OBJDIR)/json_timeline.o \$(OBJDIR)/json_user.o \$(OBJDIR)/json_wiki.o : \$(SRCDIR)/json_detail.h" + +writeln "Makefile:" +set extra_h(dispatch) " \$(OBJDIR)/page_index.h " +set extra_h(builtin) " \$(OBJDIR)/builtin_data.h " + +foreach s [lsort $src] { + writeln "\$(OBJDIR)/${s}_.c:\t\$(SRCDIR)/$s.c \$(OBJDIR)/translate" + writeln "\t\$(OBJDIR)/translate \$(SRCDIR)/$s.c >\$@\n" + writeln "\$(OBJDIR)/$s.o:\t\$(OBJDIR)/${s}_.c \$(OBJDIR)/$s.h$extra_h($s)\$(SRCDIR)/config.h" + writeln "\t\$(XTCC) -o \$(OBJDIR)/$s.o -c \$(OBJDIR)/${s}_.c\n" + writeln "\$(OBJDIR)/$s.h:\t\$(OBJDIR)/headers\n" +} + +writeln "\$(OBJDIR)/sqlite3.o:\t\$(SQLITE3_SRC)" +writeln "\t\$(XTCC) \$(SQLITE_OPTIONS) \$(SQLITE_CFLAGS) \$(SEE_FLAGS) \\" +writeln "\t\t-c \$(SQLITE3_SRC) -o \$@" + +writeln "\$(OBJDIR)/shell.o:\t\$(SQLITE3_SHELL_SRC) \$(SRCDIR)/sqlite3.h" +writeln "\t\$(XTCC) \$(SHELL_OPTIONS) \$(SHELL_CFLAGS) \$(SEE_FLAGS) \$(LINENOISE_DEF.\$(USE_LINENOISE)) -c \$(SQLITE3_SHELL_SRC) -o \$@\n" + +writeln "\$(OBJDIR)/linenoise.o:\t\$(SRCDIR)/linenoise.c \$(SRCDIR)/linenoise.h" +writeln "\t\$(XTCC) -c \$(SRCDIR)/linenoise.c -o \$@\n" + +writeln "\$(OBJDIR)/th.o:\t\$(SRCDIR)/th.c" +writeln "\t\$(XTCC) -c \$(SRCDIR)/th.c -o \$@\n" + +writeln "\$(OBJDIR)/th_lang.o:\t\$(SRCDIR)/th_lang.c" +writeln "\t\$(XTCC) -c \$(SRCDIR)/th_lang.c -o \$@\n" + +writeln "\$(OBJDIR)/th_tcl.o:\t\$(SRCDIR)/th_tcl.c" +writeln "\t\$(XTCC) -c \$(SRCDIR)/th_tcl.c -o \$@\n" + +writeln { +$(OBJDIR)/miniz.o: $(SRCDIR)/miniz.c + $(XTCC) $(MINIZ_OPTIONS) -c $(SRCDIR)/miniz.c -o $@ + +$(OBJDIR)/cson_amalgamation.o: $(SRCDIR)/cson_amalgamation.c + $(XTCC) -c $(SRCDIR)/cson_amalgamation.c -o $@ + +# +# The list of all the targets that do not correspond to real files. This stops +# 'make' from getting confused when someone makes an error in a rule. +# + +.PHONY: all install test clean +} + +close $output_file +# +# End of the main.mk output +############################################################################## +############################################################################## +############################################################################## +# Begin win/Makefile.mingw output +# +puts "building ../win/Makefile.mingw" +set output_file [open ../win/Makefile.mingw w] +fconfigure $output_file -translation binary + +writeln {#!/usr/bin/make +# +############################################################################## +# WARNING: DO NOT EDIT, AUTOMATICALLY GENERATED FILE (SEE "src/makemake.tcl") +############################################################################## +# +# This file is automatically generated. Instead of editing this +# file, edit "makemake.tcl" then run "tclsh makemake.tcl" +# to regenerate this file. +# +# This is a makefile for use on Cygwin/Darwin/FreeBSD/Linux/Windows using +# MinGW or MinGW-w64. +# +# Some of the special options which can be passed to make +# USE_WINDOWS=1 if building under a windows command prompt +# X64=1 if using an unprefixed 64-bit mingw compiler +# + +#### Select one of MinGW, MinGW-w64 (32-bit) or MinGW-w64 (64-bit) compilers. +# By default, this is an empty string (i.e. use the native compiler). +# +PREFIX = +# PREFIX = mingw32- +# PREFIX = i686-pc-mingw32- +# PREFIX = i686-w64-mingw32- +# PREFIX = x86_64-w64-mingw32- + +#### The toplevel directory of the source tree. Fossil can be built +# in a directory that is separate from the source tree. Just change +# the following to point from the build directory to the src/ folder. +# +SRCDIR = src + +#### The directory into which object code files should be written. +# +OBJDIR = wbld + +#### C compiler for use in building executables that will run on +# the platform that is doing the build. This is used to compile +# code-generator programs as part of the build process. See TCC +# and TCCEXE below for the C compiler for building the finished +# binary. +# +BCCEXE = gcc + +#### C Compiler and options for use in building executables that +# will run on the platform that is doing the build. This is used +# to compile code-generator programs as part of the build process. +# See TCC below for the C compiler for building the finished binary. +# +BCC = $(BCCEXE) + +#### Enable compiling with debug symbols (much larger binary) +# +# FOSSIL_ENABLE_SYMBOLS = 1 + +#### Enable JSON (http://www.json.org) support using "cson" +# +# FOSSIL_ENABLE_JSON = 1 + +#### Enable HTTPS support via OpenSSL (links to libssl and libcrypto) +# +# FOSSIL_ENABLE_SSL = 1 + +#### Automatically build OpenSSL when building Fossil (causes rebuild +# issues when building incrementally). +# +# FOSSIL_BUILD_SSL = 1 + +#### Enable relative paths in external diff/gdiff +# +# FOSSIL_ENABLE_EXEC_REL_PATHS = 1 + +#### Enable TH1 scripts in embedded documentation files +# +# FOSSIL_ENABLE_TH1_DOCS = 1 + +#### Enable hooks for commands and web pages via TH1 +# +# FOSSIL_ENABLE_TH1_HOOKS = 1 + +#### Enable scripting support via Tcl/Tk +# +# FOSSIL_ENABLE_TCL = 1 + +#### Load Tcl using the stubs library mechanism +# +# FOSSIL_ENABLE_TCL_STUBS = 1 + +#### Load Tcl using the private stubs mechanism +# +# FOSSIL_ENABLE_TCL_PRIVATE_STUBS = 1 + +#### Use 'system' SQLite +# +# USE_SYSTEM_SQLITE = 1 + +#### Use POSIX memory APIs from "sys/mman.h" +# +# USE_MMAN_H = 1 + +#### Use the SQLite Encryption Extension +# +# USE_SEE = 1 + +#### Use the miniz compression library +# +# FOSSIL_ENABLE_MINIZ = 1 + +#### Use the Tcl source directory instead of the install directory? +# This is useful when Tcl has been compiled statically with MinGW. +# +FOSSIL_TCL_SOURCE = 1 + +#### Check if the workaround for the MinGW command line handling needs to +# be enabled by default. This check may be somewhat fragile due to the +# use of "findstring". +# +ifndef MINGW_IS_32BIT_ONLY +ifeq (,$(findstring w64-mingw32,$(PREFIX))) +MINGW_IS_32BIT_ONLY = 1 +endif +endif + +#### The directories where the zlib include and library files are located. +# +ZINCDIR = $(SRCDIR)/../compat/zlib +ZLIBDIR = $(SRCDIR)/../compat/zlib + +#### Make an attempt to detect if Fossil is being built for the x64 processor +# architecture. This check may be somewhat fragile due to "findstring". +# +ifndef X64 +ifneq (,$(findstring x86_64-w64-mingw32,$(PREFIX))) +X64 = 1 +endif +endif + +#### Determine if the optimized assembly routines provided with zlib should be +# used, taking into account whether zlib is actually enabled and the target +# processor architecture. +# +ifndef X64 +SSLCONFIG = mingw +ifndef FOSSIL_ENABLE_MINIZ +ZLIBCONFIG = LOC="-DASMV -DASMINF" OBJA="inffas86.o match.o" +ZLIBTARGETS = $(ZLIBDIR)/inffas86.o $(ZLIBDIR)/match.o +else +ZLIBCONFIG = +ZLIBTARGETS = +endif +else +SSLCONFIG = mingw64 +ZLIBCONFIG = +ZLIBTARGETS = +endif + +#### Disable creation of the OpenSSL shared libraries. Also, disable support +# for SSLv3 (i.e. thereby forcing the use of TLS). +# +SSLCONFIG += no-ssl3 no-weak-ssl-ciphers no-shared + +#### When using zlib, make sure that OpenSSL is configured to use the zlib +# that Fossil knows about (i.e. the one within the source tree). +# +ifndef FOSSIL_ENABLE_MINIZ +SSLCONFIG += --with-zlib-lib=$(PWD)/$(ZLIBDIR) --with-zlib-include=$(PWD)/$(ZLIBDIR) zlib +endif + +#### The directories where the OpenSSL include and library files are located. +# +OPENSSLDIR = $(SRCDIR)/../compat/openssl +OPENSSLINCDIR = $(OPENSSLDIR)/include +OPENSSLLIBDIR = $(OPENSSLDIR) + +#### Either the directory where the Tcl library is installed or the Tcl +# source code directory resides (depending on the value of the macro +# FOSSIL_TCL_SOURCE). If this points to the Tcl install directory, +# this directory must have "include" and "lib" sub-directories. If +# this points to the Tcl source code directory, this directory must +# have "generic" and "win" sub-directories. The recommended usage +# here is to use the Sysinternals junction tool to create a hard +# link between a "tcl-8.x" sub-directory of the Fossil source code +# directory and the target Tcl directory. This removes the need to +# hard-code the necessary paths in this Makefile. +# +TCLDIR = $(SRCDIR)/../compat/tcl-8.6 + +#### The Tcl source code directory. This defaults to the same value as +# TCLDIR macro (above), which may not be correct. This value will +# only be used if the FOSSIL_TCL_SOURCE macro is defined. +# +TCLSRCDIR = $(TCLDIR) + +#### The Tcl include and library directories. These values will only be +# used if the FOSSIL_TCL_SOURCE macro is not defined. +# +TCLINCDIR = $(TCLDIR)/include +TCLLIBDIR = $(TCLDIR)/lib + +#### Tcl: Which Tcl library do we want to use (8.4, 8.5, 8.6, etc)? +# +ifdef FOSSIL_ENABLE_TCL_STUBS +ifndef FOSSIL_ENABLE_TCL_PRIVATE_STUBS +LIBTCL = -ltclstub86 +endif +TCLTARGET = libtclstub86.a +else +LIBTCL = -ltcl86 +TCLTARGET = binaries +endif + +#### C compiler for use in building executables that will run on the +# target platform. This is usually the same as BCCEXE, unless you +# are cross-compiling. This C compiler builds the finished binary +# for fossil. See BCC and BCCEXE above for the C compiler for +# building intermediate code-generator tools. +# +TCCEXE = gcc + +#### C compiler and options for use in building executables that will +# run on the target platform. This is usually the almost the same +# as BCC, unless you are cross-compiling. This C compiler builds +# the finished binary for fossil. The BCC compiler above is used +# for building intermediate code-generator tools. +# +TCC = $(PREFIX)$(TCCEXE) -Wall -Wdeclaration-after-statement + +#### Add the necessary command line options to build with debugging +# symbols, if enabled. +# +ifdef FOSSIL_ENABLE_SYMBOLS +TCC += -g +else +TCC += -Os +endif + +#### When not using the miniz compression library, zlib is required. +# +ifndef FOSSIL_ENABLE_MINIZ +TCC += -L$(ZLIBDIR) -I$(ZINCDIR) +endif + +#### Compile resources for use in building executables that will run +# on the target platform. +# +RCC = $(PREFIX)windres -I$(SRCDIR) + +ifndef FOSSIL_ENABLE_MINIZ +RCC += -I$(ZINCDIR) +endif + +# With HTTPS support +ifdef FOSSIL_ENABLE_SSL +TCC += -L$(OPENSSLLIBDIR) -I$(OPENSSLINCDIR) +RCC += -I$(OPENSSLINCDIR) +endif + +# With Tcl support +ifdef FOSSIL_ENABLE_TCL +ifdef FOSSIL_TCL_SOURCE +TCC += -L$(TCLSRCDIR)/win -I$(TCLSRCDIR)/generic -I$(TCLSRCDIR)/win +RCC += -I$(TCLSRCDIR)/generic -I$(TCLSRCDIR)/win +else +TCC += -L$(TCLLIBDIR) -I$(TCLINCDIR) +RCC += -I$(TCLINCDIR) +endif +endif + +# With miniz (i.e. instead of zlib) +ifdef FOSSIL_ENABLE_MINIZ +TCC += -DFOSSIL_ENABLE_MINIZ=1 +RCC += -DFOSSIL_ENABLE_MINIZ=1 +endif + +# With MinGW command line handling workaround +ifdef MINGW_IS_32BIT_ONLY +TCC += -DBROKEN_MINGW_CMDLINE=1 +RCC += -DBROKEN_MINGW_CMDLINE=1 +endif + +# With HTTPS support +ifdef FOSSIL_ENABLE_SSL +TCC += -DFOSSIL_ENABLE_SSL=1 +RCC += -DFOSSIL_ENABLE_SSL=1 +endif + +# With relative paths in external diff/gdiff +ifdef FOSSIL_ENABLE_EXEC_REL_PATHS +TCC += -DFOSSIL_ENABLE_EXEC_REL_PATHS=1 +RCC += -DFOSSIL_ENABLE_EXEC_REL_PATHS=1 +endif + +# With TH1 embedded docs support +ifdef FOSSIL_ENABLE_TH1_DOCS +TCC += -DFOSSIL_ENABLE_TH1_DOCS=1 +RCC += -DFOSSIL_ENABLE_TH1_DOCS=1 +endif + +# With TH1 hook support +ifdef FOSSIL_ENABLE_TH1_HOOKS +TCC += -DFOSSIL_ENABLE_TH1_HOOKS=1 +RCC += -DFOSSIL_ENABLE_TH1_HOOKS=1 +endif + +# With Tcl support +ifdef FOSSIL_ENABLE_TCL +TCC += -DFOSSIL_ENABLE_TCL=1 +RCC += -DFOSSIL_ENABLE_TCL=1 +# Either statically linked or via stubs +ifdef FOSSIL_ENABLE_TCL_STUBS +TCC += -DFOSSIL_ENABLE_TCL_STUBS=1 -DUSE_TCL_STUBS +RCC += -DFOSSIL_ENABLE_TCL_STUBS=1 -DUSE_TCL_STUBS +ifdef FOSSIL_ENABLE_TCL_PRIVATE_STUBS +TCC += -DFOSSIL_ENABLE_TCL_PRIVATE_STUBS=1 +RCC += -DFOSSIL_ENABLE_TCL_PRIVATE_STUBS=1 +endif +else +TCC += -DSTATIC_BUILD +RCC += -DSTATIC_BUILD +endif +endif + +# With JSON support +ifdef FOSSIL_ENABLE_JSON +TCC += -DFOSSIL_ENABLE_JSON=1 +RCC += -DFOSSIL_ENABLE_JSON=1 +endif + +# With "sys/mman.h" support +ifdef USE_MMAN_H +TCC += -DUSE_MMAN_H=1 +RCC += -DUSE_MMAN_H=1 +endif + +# With SQLite Encryption Extension support +ifdef USE_SEE +TCC += -DUSE_SEE=1 +RCC += -DUSE_SEE=1 +endif + +#### The option -static has no effect on MinGW(-w64), only dynamic +# executables can be built when linking with MSVCRT. OpenSSL +# (optional) and zlib (required) however are always linked in +# statically. Therefore, the FOSSIL_DYNAMIC_BUILD option does +# not really apply to MinGW (i.e. since ALL external libraries +# are NOT linked dynamically). +# +# LIB = -static + +#### MinGW: If available, use the Unicode capable runtime startup code. +# +ifndef MINGW_IS_32BIT_ONLY +LIB += -municode +endif + +#### SQLite: If enabled, use the system SQLite library. +# +ifdef USE_SYSTEM_SQLITE +LIB += -lsqlite3 +endif + +#### OpenSSL: Add the necessary libraries required, if enabled. +# +ifdef FOSSIL_ENABLE_SSL +LIB += -lssl -lcrypto -lgdi32 -lcrypt32 +endif + +#### Tcl: Add the necessary libraries required, if enabled. +# +ifdef FOSSIL_ENABLE_TCL +LIB += $(LIBTCL) +endif + +#### Extra arguments for linking the finished binary. Fossil needs +# to link against the Z-Lib compression library. There are no +# other mandatory dependencies. +# +LIB += -lmingwex + +#### When not using the miniz compression library, zlib is required. +# +ifndef FOSSIL_ENABLE_MINIZ +LIB += -lz +endif + +#### These libraries MUST appear in the same order as they do for Tcl +# or linking with it will not work (exact reason unknown). +# +ifdef FOSSIL_ENABLE_TCL +ifdef FOSSIL_ENABLE_TCL_STUBS +LIB += -lkernel32 -lws2_32 +else +LIB += -lnetapi32 -lkernel32 -luser32 -ladvapi32 -lws2_32 +endif +else +LIB += -lkernel32 -lws2_32 +endif + +#### Library required for DNS lookups. +# +LIB += -ldnsapi + +#### Tcl shell for use in running the fossil test suite. This is only +# used for testing. +# +TCLSH = tclsh + +#### Nullsoft installer MakeNSIS location +# +MAKENSIS = "$(PROGRAMFILES)\NSIS\MakeNSIS.exe" + +#### Inno Setup executable location +# +INNOSETUP = "$(PROGRAMFILES)\Inno Setup 5\ISCC.exe" + +#### Include a configuration file that can override any one of these settings. +# +-include config.w32 + +# STOP HERE +# You should not need to change anything below this line +#-------------------------------------------------------- +XBCC = $(BCC) $(CFLAGS) +XTCC = $(TCC) $(CFLAGS) -I. -I$(SRCDIR) +} +writeln -nonewline "SRC =" +foreach s [lsort $src] { + writeln -nonewline " \\\n \$(SRCDIR)/$s.c" +} +writeln "\n" +writeln -nonewline "EXTRA_FILES =" +foreach s [lsort $extra_files] { + writeln -nonewline " \\\n \$(SRCDIR)/$s" +} +writeln "\n" +writeln -nonewline "TRANS_SRC =" +foreach s [lsort $src] { + writeln -nonewline " \\\n \$(OBJDIR)/${s}_.c" +} +writeln "\n" +writeln -nonewline "OBJ =" +foreach s [lsort $src] { + writeln -nonewline " \\\n \$(OBJDIR)/$s.o" +} +writeln "\n" +writeln "APPNAME = ${name}.exe" +writeln "APPTARGETS =" +writeln { +#### If the USE_WINDOWS variable exists, it is assumed that we are building +# inside of a Windows-style shell; otherwise, it is assumed that we are +# building inside of a Unix-style shell. Note that the "move" command is +# broken when attempting to use it from the Windows shell via MinGW make +# because the SHELL variable is only used for certain commands that are +# recognized internally by make. +# +ifdef USE_WINDOWS +TRANSLATE = $(subst /,\,$(OBJDIR)/translate.exe) +MAKEHEADERS = $(subst /,\,$(OBJDIR)/makeheaders.exe) +MKINDEX = $(subst /,\,$(OBJDIR)/mkindex.exe) +MKBUILTIN = $(subst /,\,$(OBJDIR)/mkbuiltin.exe) +MKVERSION = $(subst /,\,$(OBJDIR)/mkversion.exe) +CODECHECK1 = $(subst /,\,$(OBJDIR)/codecheck1.exe) +CAT = type +CP = copy +GREP = find +MV = copy +RM = del /Q +MKDIR = -mkdir +RMDIR = rmdir /S /Q +else +TRANSLATE = $(OBJDIR)/translate.exe +MAKEHEADERS = $(OBJDIR)/makeheaders.exe +MKINDEX = $(OBJDIR)/mkindex.exe +MKBUILTIN = $(OBJDIR)/mkbuiltin.exe +MKVERSION = $(OBJDIR)/mkversion.exe +CODECHECK1 = $(OBJDIR)/codecheck1.exe +CAT = cat +CP = cp +GREP = grep +MV = mv +RM = rm -f +MKDIR = -mkdir -p +RMDIR = rm -rf +endif} + +writeln { +all: $(OBJDIR) $(APPNAME) + +$(OBJDIR)/fossil.o: $(SRCDIR)/../win/fossil.rc $(OBJDIR)/VERSION.h +ifdef USE_WINDOWS + $(CAT) $(subst /,\,$(SRCDIR)\miniz.c) | $(GREP) "define MZ_VERSION" > $(subst /,\,$(OBJDIR)\minizver.h) + $(CP) $(subst /,\,$(SRCDIR)\..\win\fossil.rc) $(subst /,\,$(OBJDIR)) + $(CP) $(subst /,\,$(SRCDIR)\..\win\fossil.ico) $(subst /,\,$(OBJDIR)) + $(CP) $(subst /,\,$(SRCDIR)\..\win\fossil.exe.manifest) $(subst /,\,$(OBJDIR)) +else + $(CAT) $(SRCDIR)/miniz.c | $(GREP) "define MZ_VERSION" > $(OBJDIR)/minizver.h + $(CP) $(SRCDIR)/../win/fossil.rc $(OBJDIR) + $(CP) $(SRCDIR)/../win/fossil.ico $(OBJDIR) + $(CP) $(SRCDIR)/../win/fossil.exe.manifest $(OBJDIR) +endif + $(RCC) $(OBJDIR)/fossil.rc -o $(OBJDIR)/fossil.o + +install: $(OBJDIR) $(APPNAME) +ifdef USE_WINDOWS + $(MKDIR) $(subst /,\,$(INSTALLDIR)) + $(CP) $(subst /,\,$(APPNAME)) $(subst /,\,$(INSTALLDIR)) +else + $(MKDIR) $(INSTALLDIR) + $(CP) $(APPNAME) $(INSTALLDIR) +endif + +$(OBJDIR): +ifdef USE_WINDOWS + $(MKDIR) $(subst /,\,$(OBJDIR)) +else + $(MKDIR) $(OBJDIR) +endif + +$(TRANSLATE): $(SRCDIR)/translate.c + $(XBCC) -o $@ $(SRCDIR)/translate.c + +$(MAKEHEADERS): $(SRCDIR)/makeheaders.c + $(XBCC) -o $@ $(SRCDIR)/makeheaders.c + +$(MKINDEX): $(SRCDIR)/mkindex.c + $(XBCC) -o $@ $(SRCDIR)/mkindex.c + +$(MKBUILTIN): $(SRCDIR)/mkbuiltin.c + $(XBCC) -o $@ $(SRCDIR)/mkbuiltin.c + +$(MKVERSION): $(SRCDIR)/mkversion.c + $(XBCC) -o $@ $(SRCDIR)/mkversion.c + +$(CODECHECK1): $(SRCDIR)/codecheck1.c + $(XBCC) -o $@ $(SRCDIR)/codecheck1.c + +# WARNING. DANGER. Running the test suite modifies the repository the # build is done from, i.e. the checkout belongs to. Do not sync/push # the repository after running the tests. -test: $(APPNAME) - $(TCLSH) test/tester.tcl $(APPNAME) - -VERSION.h: $(SRCDIR)/../manifest.uuid $(SRCDIR)/../manifest - awk '{ printf "#define MANIFEST_UUID \"%s\"\n", $$1}' \ - $(SRCDIR)/../manifest.uuid >VERSION.h - awk '{ printf "#define MANIFEST_VERSION \"[%.10s]\"\n", $$1}' \ - $(SRCDIR)/../manifest.uuid >>VERSION.h - awk '$$1=="D"{printf "#define MANIFEST_DATE \"%s %s\"\n",\ - substr($$2,1,10),substr($$2,12)}' \ - $(SRCDIR)/../manifest >>VERSION.h - -$(APPNAME): headers $(OBJ) $(OBJDIR)/sqlite3.o $(OBJDIR)/th.o $(OBJDIR)/th_lang.o - $(TCC) -o $(APPNAME) $(OBJ) $(OBJDIR)/sqlite3.o $(OBJDIR)/th.o $(OBJDIR)/th_lang.o $(LIB) +test: $(OBJDIR) $(APPNAME) + $(TCLSH) $(SRCDIR)/../test/tester.tcl $(APPNAME) + +$(OBJDIR)/VERSION.h: $(SRCDIR)/../manifest.uuid $(SRCDIR)/../manifest $(MKVERSION) $(OBJDIR)/phony.h + $(MKVERSION) $(SRCDIR)/../manifest.uuid $(SRCDIR)/../manifest $(SRCDIR)/../VERSION >$@ + +$(OBJDIR)/phony.h: + # Force rebuild of VERSION.h every time "make" is run + +# The USE_SYSTEM_SQLITE variable may be undefined, set to 0, or set +# to 1. If it is set to 1, then there is no need to build or link +# the sqlite3.o object. Instead, the system SQLite will be linked +# using -lsqlite3. +SQLITE3_OBJ.0 = $(OBJDIR)/sqlite3.o +SQLITE3_OBJ.1 = +SQLITE3_OBJ. = $(SQLITE3_OBJ.0) + +# The FOSSIL_ENABLE_MINIZ variable may be undefined, set to 0, or +# set to 1. If it is set to 1, the miniz library included in the +# source tree should be used; otherwise, it should not. +MINIZ_OBJ.0 = +MINIZ_OBJ.1 = $(OBJDIR)/miniz.o +MINIZ_OBJ. = $(MINIZ_OBJ.0) + +# The USE_SEE variable may be undefined, 0 or 1. If undefined or +# 0, ordinary SQLite is used. If 1, then sqlite3-see.c (not part of +# the source tree) is used and extra flags are provided to enable +# the SQLite Encryption Extension. +SQLITE3_SRC.0 = sqlite3.c +SQLITE3_SRC.1 = sqlite3-see.c +SQLITE3_SRC. = sqlite3.c +SQLITE3_SRC = $(SRCDIR)/$(SQLITE3_SRC.$(USE_SEE)) +SQLITE3_SHELL_SRC.0 = shell.c +SQLITE3_SHELL_SRC.1 = shell-see.c +SQLITE3_SHELL_SRC. = shell.c +SQLITE3_SHELL_SRC = $(SRCDIR)/$(SQLITE3_SHELL_SRC.$(USE_SEE)) +SEE_FLAGS.0 = +SEE_FLAGS.1 = -DSQLITE_HAS_CODEC -DSQLITE_SHELL_DBKEY_PROC=fossil_key +SEE_FLAGS. = +SEE_FLAGS = $(SEE_FLAGS.$(USE_SEE)) +} + +writeln [string map [list <<>> \\] { +EXTRAOBJ = <<>> + $(SQLITE3_OBJ.$(USE_SYSTEM_SQLITE)) <<>> + $(MINIZ_OBJ.$(FOSSIL_ENABLE_MINIZ)) <<>> + $(OBJDIR)/shell.o <<>> + $(OBJDIR)/th.o <<>> + $(OBJDIR)/th_lang.o <<>> + $(OBJDIR)/th_tcl.o <<>> + $(OBJDIR)/cson_amalgamation.o +}] + +writeln { +$(ZLIBDIR)/inffas86.o: + $(TCC) -c -o $@ -DASMINF -I$(ZLIBDIR) -O3 $(ZLIBDIR)/contrib/inflate86/inffas86.c + +$(ZLIBDIR)/match.o: + $(TCC) -c -o $@ -DASMV $(ZLIBDIR)/contrib/asm686/match.S + +zlib: $(ZLIBTARGETS) + $(MAKE) -C $(ZLIBDIR) PREFIX=$(PREFIX) CC=$(PREFIX)$(TCCEXE) $(ZLIBCONFIG) -f win32/Makefile.gcc libz.a + +clean-zlib: + $(MAKE) -C $(ZLIBDIR) PREFIX=$(PREFIX) CC=$(PREFIX)$(TCCEXE) -f win32/Makefile.gcc clean + +ifdef FOSSIL_ENABLE_MINIZ +BLDTARGETS = +else +BLDTARGETS = zlib +endif + +openssl: $(BLDTARGETS) + cd $(OPENSSLLIBDIR);./Configure --cross-compile-prefix=$(PREFIX) $(SSLCONFIG) + sed -i -e 's/^PERL=C:\\.*$$/PERL=perl.exe/i' $(OPENSSLLIBDIR)/Makefile + $(MAKE) -C $(OPENSSLLIBDIR) PREFIX=$(PREFIX) CC=$(PREFIX)$(TCCEXE) build_libs + +clean-openssl: + $(MAKE) -C $(OPENSSLLIBDIR) PREFIX=$(PREFIX) CC=$(PREFIX)$(TCCEXE) clean + +tcl: + cd $(TCLSRCDIR)/win;./configure + $(MAKE) -C $(TCLSRCDIR)/win PREFIX=$(PREFIX) CC=$(PREFIX)$(TCCEXE) $(TCLTARGET) + +clean-tcl: + $(MAKE) -C $(TCLSRCDIR)/win PREFIX=$(PREFIX) CC=$(PREFIX)$(TCCEXE) distclean + +APPTARGETS += $(BLDTARGETS) + +ifdef FOSSIL_BUILD_SSL +APPTARGETS += openssl +endif + +$(APPNAME): $(APPTARGETS) $(OBJDIR)/headers $(CODECHECK1) $(EXTRAOBJ) $(OBJ) $(OBJDIR)/fossil.o + $(CODECHECK1) $(TRANS_SRC) + $(TCC) -o $@ $(EXTRAOBJ) $(OBJ) $(OBJDIR)/fossil.o $(LIB) # This rule prevents make from using its default rules to try build # an executable named "manifest" out of the file named "manifest.c" # -$(SRCDIR)/../manifest: +$(SRCDIR)/../manifest: # noop -clean: - rm -f $(OBJDIR)/*.o *_.c $(APPNAME) VERSION.h - rm -f translate makeheaders mkindex page_index.h headers} +clean: +ifdef USE_WINDOWS + $(RM) $(subst /,\,$(APPNAME)) + $(RMDIR) $(subst /,\,$(OBJDIR)) +else + $(RM) $(APPNAME) + $(RMDIR) $(OBJDIR) +endif -set hfiles {} -foreach s [lsort $src] {lappend hfiles $s.h} -puts "\trm -f $hfiles\n" +setup: $(OBJDIR) $(APPNAME) + $(MAKENSIS) ./setup/fossil.nsi + +innosetup: $(OBJDIR) $(APPNAME) + $(INNOSETUP) ./setup/fossil.iss -DAppVersion=$(shell $(CAT) ./VERSION) +} set mhargs {} foreach s [lsort $src] { - append mhargs " ${s}_.c:$s.h" - set extra_h($s) {} -} -append mhargs " \$(SRCDIR)/sqlite3.h" -append mhargs " \$(SRCDIR)/th.h" -append mhargs " VERSION.h" -puts "page_index.h: \$(TRANS_SRC) mkindex" -puts "\t./mkindex \$(TRANS_SRC) >$@" -puts "headers:\tpage_index.h makeheaders VERSION.h" -puts "\t./makeheaders $mhargs" -puts "\ttouch headers" -puts "headers: Makefile" -puts "Makefile:" -set extra_h(main) page_index.h - -foreach s [lsort $src] { - puts "${s}_.c:\t\$(SRCDIR)/$s.c translate" - puts "\t./translate \$(SRCDIR)/$s.c >${s}_.c\n" - puts "\$(OBJDIR)/$s.o:\t${s}_.c $s.h $extra_h($s) \$(SRCDIR)/config.h" - puts "\t\$(XTCC) -o \$(OBJDIR)/$s.o -c ${s}_.c\n" - puts "$s.h:\theaders" -# puts "\t./makeheaders $mhargs\n\ttouch headers\n" -# puts "\t./makeheaders ${s}_.c:${s}.h\n" -} - - -puts "\$(OBJDIR)/sqlite3.o:\t\$(SRCDIR)/sqlite3.c" -set opt {-DSQLITE_OMIT_LOAD_EXTENSION=1} -append opt " -DSQLITE_THREADSAFE=0 -DSQLITE_DEFAULT_FILE_FORMAT=4" -#append opt " -DSQLITE_ENABLE_FTS3=1" -append opt " -Dlocaltime=fossil_localtime" -append opt " -DSQLITE_ENABLE_LOCKING_STYLE=0" -puts "\t\$(XTCC) $opt -c \$(SRCDIR)/sqlite3.c -o \$(OBJDIR)/sqlite3.o\n" - -puts "\$(OBJDIR)/th.o:\t\$(SRCDIR)/th.c" -puts "\t\$(XTCC) -I\$(SRCDIR) -c \$(SRCDIR)/th.c -o \$(OBJDIR)/th.o\n" - -puts "\$(OBJDIR)/th_lang.o:\t\$(SRCDIR)/th_lang.c" -puts "\t\$(XTCC) -I\$(SRCDIR) -c \$(SRCDIR)/th_lang.c -o \$(OBJDIR)/th_lang.o\n" + if {[string length $mhargs] > 0} {append mhargs " \\\n\t\t"} + append mhargs "\$(OBJDIR)/${s}_.c:\$(OBJDIR)/$s.h" + set extra_h($s) { } +} +append mhargs " \\\n\t\t\$(SRCDIR)/sqlite3.h" +append mhargs " \\\n\t\t\$(SRCDIR)/th.h" +append mhargs " \\\n\t\t\$(OBJDIR)/VERSION.h" +writeln "\$(OBJDIR)/page_index.h: \$(TRANS_SRC) \$(MKINDEX)" +writeln "\t\$(MKINDEX) \$(TRANS_SRC) >\$@\n" + +writeln "\$(OBJDIR)/builtin_data.h:\t\$(MKBUILTIN) \$(EXTRA_FILES)" +writeln "\t\$(MKBUILTIN) --prefix \$(SRCDIR)/ \$(EXTRA_FILES) >\$@\n" + +writeln "\$(OBJDIR)/headers:\t\$(OBJDIR)/page_index.h \$(OBJDIR)/builtin_data.h \$(MAKEHEADERS) \$(OBJDIR)/VERSION.h" +writeln "\t\$(MAKEHEADERS) $mhargs" +writeln "\techo Done >\$(OBJDIR)/headers\n" +writeln "\$(OBJDIR)/headers: Makefile\n" +writeln "Makefile:\n" +set extra_h(main) " \$(OBJDIR)/page_index.h " +set extra_h(builtin) " \$(OBJDIR)/builtin_data.h " + +foreach s [lsort $src] { + writeln "\$(OBJDIR)/${s}_.c:\t\$(SRCDIR)/$s.c \$(TRANSLATE)" + writeln "\t\$(TRANSLATE) \$(SRCDIR)/$s.c >\$@\n" + writeln "\$(OBJDIR)/$s.o:\t\$(OBJDIR)/${s}_.c \$(OBJDIR)/$s.h$extra_h($s)\$(SRCDIR)/config.h" + writeln "\t\$(XTCC) -o \$(OBJDIR)/$s.o -c \$(OBJDIR)/${s}_.c\n" + writeln "\$(OBJDIR)/${s}.h:\t\$(OBJDIR)/headers\n" +} + +writeln {MINGW_OPTIONS = -D_HAVE__MINGW_H +} + +set SQLITE_WIN32_OPTIONS $SQLITE_OPTIONS +lappend SQLITE_WIN32_OPTIONS -DSQLITE_WIN32_NO_ANSI + +set MINGW_SQLITE_OPTIONS $SQLITE_WIN32_OPTIONS +lappend MINGW_SQLITE_OPTIONS {$(MINGW_OPTIONS)} +lappend MINGW_SQLITE_OPTIONS -DSQLITE_USE_MALLOC_H +lappend MINGW_SQLITE_OPTIONS -DSQLITE_USE_MSIZE + +set MINIZ_WIN32_OPTIONS $MINIZ_OPTIONS + +set j " \\\n " +writeln "SQLITE_OPTIONS = [join $MINGW_SQLITE_OPTIONS $j]\n" +set j " \\\n " +writeln "SHELL_OPTIONS = [join $SHELL_WIN32_OPTIONS $j]\n" +set j " \\\n " +writeln "MINIZ_OPTIONS = [join $MINIZ_WIN32_OPTIONS $j]\n" + +writeln "\$(OBJDIR)/sqlite3.o:\t\$(SQLITE3_SRC) \$(SRCDIR)/../win/Makefile.mingw" +writeln "\t\$(XTCC) \$(SQLITE_OPTIONS) \$(SQLITE_CFLAGS) \$(SEE_FLAGS) \\" +writeln "\t\t-c \$(SQLITE3_SRC) -o \$@\n" + +writeln "\$(OBJDIR)/cson_amalgamation.o:\t\$(SRCDIR)/cson_amalgamation.c" +writeln "\t\$(XTCC) -c \$(SRCDIR)/cson_amalgamation.c -o \$@\n" +writeln "\$(OBJDIR)/json.o \$(OBJDIR)/json_artifact.o \$(OBJDIR)/json_branch.o \$(OBJDIR)/json_config.o \$(OBJDIR)/json_diff.o \$(OBJDIR)/json_dir.o \$(OBJDIR)/jsos_finfo.o \$(OBJDIR)/json_login.o \$(OBJDIR)/json_query.o \$(OBJDIR)/json_report.o \$(OBJDIR)/json_status.o \$(OBJDIR)/json_tag.o \$(OBJDIR)/json_timeline.o \$(OBJDIR)/json_user.o \$(OBJDIR)/json_wiki.o : \$(SRCDIR)/json_detail.h\n" + +writeln "\$(OBJDIR)/shell.o:\t\$(SQLITE3_SHELL_SRC) \$(SRCDIR)/sqlite3.h \$(SRCDIR)/../win/Makefile.mingw" +writeln "\t\$(XTCC) \$(SHELL_OPTIONS) \$(SHELL_CFLAGS) \$(SEE_FLAGS) -c \$(SQLITE3_SHELL_SRC) -o \$@\n" + +writeln "\$(OBJDIR)/th.o:\t\$(SRCDIR)/th.c" +writeln "\t\$(XTCC) -c \$(SRCDIR)/th.c -o \$@\n" + +writeln "\$(OBJDIR)/th_lang.o:\t\$(SRCDIR)/th_lang.c" +writeln "\t\$(XTCC) -c \$(SRCDIR)/th_lang.c -o \$@\n" + +writeln "\$(OBJDIR)/th_tcl.o:\t\$(SRCDIR)/th_tcl.c" +writeln "\t\$(XTCC) -c \$(SRCDIR)/th_tcl.c -o \$@\n" + +writeln "\$(OBJDIR)/miniz.o:\t\$(SRCDIR)/miniz.c" +writeln "\t\$(XTCC) \$(MINIZ_OPTIONS) -c \$(SRCDIR)/miniz.c -o \$@\n" + +close $output_file +# +# End of the win/Makefile.mingw output +############################################################################## +############################################################################## +############################################################################## +# Begin win/Makefile.dmc output +# +puts "building ../win/Makefile.dmc" +set output_file [open ../win/Makefile.dmc w] +fconfigure $output_file -translation binary + +writeln {# +############################################################################## +# WARNING: DO NOT EDIT, AUTOMATICALLY GENERATED FILE (SEE "src/makemake.tcl") +############################################################################## +# +# This file is automatically generated. Instead of editing this +# file, edit "makemake.tcl" then run "tclsh makemake.tcl" +# to regenerate this file. +# +B = .. +SRCDIR = $B\src +OBJDIR = . +O = .obj +E = .exe + + +# Maybe DMDIR, SSL or INCL needs adjustment +DMDIR = c:\DM +INCL = -I. -I$(SRCDIR) -I$B\win\include -I$(DMDIR)\extra\include + +#SSL = -DFOSSIL_ENABLE_SSL=1 +SSL = + +CFLAGS = -o +BCC = $(DMDIR)\bin\dmc $(CFLAGS) +TCC = $(DMDIR)\bin\dmc $(CFLAGS) $(DMCDEF) $(SSL) $(INCL) +LIBS = $(DMDIR)\extra\lib\ zlib wsock32 advapi32 dnsapi +} +writeln "SQLITE_OPTIONS = [join $SQLITE_OPTIONS { }]\n" +writeln "SHELL_OPTIONS = [join $SHELL_WIN32_OPTIONS { }]\n" +writeln -nonewline "SRC =" +foreach s [lsort $src] { + writeln -nonewline " ${s}_.c" +} +writeln "\n" +writeln -nonewline "OBJ = " +foreach s [lsort $src] { + writeln -nonewline "\$(OBJDIR)\\$s\$O " +} +writeln "\$(OBJDIR)\\shell\$O \$(OBJDIR)\\sqlite3\$O \$(OBJDIR)\\th\$O \$(OBJDIR)\\th_lang\$O" +writeln { + +RC=$(DMDIR)\bin\rcc +RCFLAGS=-32 -w1 -I$(SRCDIR) /D__DMC__ + +APPNAME = $(OBJDIR)\fossil$(E) + +all: $(APPNAME) + +$(APPNAME) : translate$E mkindex$E codecheck1$E headers $(OBJ) $(OBJDIR)\link + cd $(OBJDIR) + codecheck1$E $(SRC) + $(DMDIR)\bin\link @link + +$(OBJDIR)\fossil.res: $B\win\fossil.rc + $(RC) $(RCFLAGS) -o$@ $** + +$(OBJDIR)\link: $B\win\Makefile.dmc $(OBJDIR)\fossil.res} +writeln -nonewline "\t+echo " +foreach s [lsort $src] { + writeln -nonewline "$s " +} +writeln "shell sqlite3 th th_lang > \$@" +writeln "\t+echo fossil >> \$@" +writeln "\t+echo fossil >> \$@" +writeln "\t+echo \$(LIBS) >> \$@" +writeln "\t+echo. >> \$@" +writeln "\t+echo fossil >> \$@" + +writeln { +translate$E: $(SRCDIR)\translate.c + $(BCC) -o$@ $** + +makeheaders$E: $(SRCDIR)\makeheaders.c + $(BCC) -o$@ $** + +mkindex$E: $(SRCDIR)\mkindex.c + $(BCC) -o$@ $** + +mkbuiltin$E: $(SRCDIR)\mkbuiltin.c + $(BCC) -o$@ $** + +mkversion$E: $(SRCDIR)\mkversion.c + $(BCC) -o$@ $** + +codecheck1$E: $(SRCDIR)\codecheck1.c + $(BCC) -o$@ $** + +$(OBJDIR)\shell$O : $(SRCDIR)\shell.c + $(TCC) -o$@ -c $(SHELL_OPTIONS) $(SQLITE_OPTIONS) $(SHELL_CFLAGS) $** + +$(OBJDIR)\sqlite3$O : $(SRCDIR)\sqlite3.c + $(TCC) -o$@ -c $(SQLITE_OPTIONS) $(SQLITE_CFLAGS) $** + +$(OBJDIR)\th$O : $(SRCDIR)\th.c + $(TCC) -o$@ -c $** + +$(OBJDIR)\th_lang$O : $(SRCDIR)\th_lang.c + $(TCC) -o$@ -c $** + +$(OBJDIR)\cson_amalgamation.h : $(SRCDIR)\cson_amalgamation.h + cp $@ $@ + +VERSION.h : mkversion$E $B\manifest.uuid $B\manifest $B\VERSION + +$** > $@ + +page_index.h: mkindex$E $(SRC) + +$** > $@ + +builtin_data.h: mkbuiltin$E $(EXTRA_FILES) + mkbuiltin$E --prefix $(SRCDIR)/ $(EXTRA_FILES) > $@ + +clean: + -del $(OBJDIR)\*.obj + -del *.obj *_.c *.h *.map + +realclean: + -del $(APPNAME) translate$E mkindex$E makeheaders$E mkversion$E codecheck1$E mkbuiltin$E + +$(OBJDIR)\json$O : $(SRCDIR)\json_detail.h +$(OBJDIR)\json_artifact$O : $(SRCDIR)\json_detail.h +$(OBJDIR)\json_branch$O : $(SRCDIR)\json_detail.h +$(OBJDIR)\json_config$O : $(SRCDIR)\json_detail.h +$(OBJDIR)\json_diff$O : $(SRCDIR)\json_detail.h +$(OBJDIR)\json_dir$O : $(SRCDIR)\json_detail.h +$(OBJDIR)\json_finfo$O : $(SRCDIR)\json_detail.h +$(OBJDIR)\json_login$O : $(SRCDIR)\json_detail.h +$(OBJDIR)\json_query$O : $(SRCDIR)\json_detail.h +$(OBJDIR)\json_report$O : $(SRCDIR)\json_detail.h +$(OBJDIR)\json_status$O : $(SRCDIR)\json_detail.h +$(OBJDIR)\json_tag$O : $(SRCDIR)\json_detail.h +$(OBJDIR)\json_timeline$O : $(SRCDIR)\json_detail.h +$(OBJDIR)\json_user$O : $(SRCDIR)\json_detail.h +$(OBJDIR)\json_wiki$O : $(SRCDIR)\json_detail.h + +} +foreach s [lsort $src] { + writeln "\$(OBJDIR)\\$s\$O : ${s}_.c ${s}.h" + writeln "\t\$(TCC) -o\$@ -c ${s}_.c\n" + writeln "${s}_.c : \$(SRCDIR)\\$s.c" + writeln "\t+translate\$E \$** > \$@\n" +} + +writeln -nonewline "headers: makeheaders\$E page_index.h builtin_data.h VERSION.h\n\t +makeheaders\$E " +foreach s [lsort $src] { + writeln -nonewline "${s}_.c:$s.h " +} +writeln "\$(SRCDIR)\\sqlite3.h \$(SRCDIR)\\th.h VERSION.h \$(SRCDIR)\\cson_amalgamation.h" +writeln "\t@copy /Y nul: headers" + +close $output_file +# +# End of the win/Makefile.dmc output +############################################################################## +############################################################################## +############################################################################## +# Begin win/Makefile.msc output +# +puts "building ../win/Makefile.msc" +set output_file [open ../win/Makefile.msc w] +fconfigure $output_file -translation binary + +writeln {# +############################################################################## +# WARNING: DO NOT EDIT, AUTOMATICALLY GENERATED FILE (SEE "src/makemake.tcl") +############################################################################## +# +# +# This file is automatically generated. Instead of editing this +# file, edit "makemake.tcl" then run "tclsh makemake.tcl" +# to regenerate this file. +# +B = .. +SRCDIR = $(B)\src +T = . +OBJDIR = $(T) +OX = $(OBJDIR) +O = .obj +E = .exe +P = .pdb +DBGOPTS = /Od + +INSTALLDIR = . +!ifdef DESTDIR +INSTALLDIR = $(DESTDIR)\$(INSTALLDIR) +!endif + +# When building out of source, this Makefile needs to know the path to the base +# top-level directory for this project. Pass it on NMAKE command line via make +# variable B: +# NMAKE /f "path\to\this\Makefile" B="path/to/fossil/root" +# +# NOTE: Make sure B path has no trailing backslash, UNIX-style path is OK too. +# +!if !exist("$(B)\.fossil-settings") +!error Please specify path to project base directory: B="path/to/fossil" +!endif + +# Perl is only necessary if OpenSSL support is enabled and it is built from +# source code. The PERLDIR environment variable, if it exists, should point +# to the directory containing the main Perl executable specified here (i.e. +# "perl.exe"). +PERL = perl.exe + +# Enable use of available compiler optimizations? +!ifndef OPTIMIZATIONS +OPTIMIZATIONS = 2 +!endif + +# Enable debugging symbols? +!ifndef DEBUG +DEBUG = 0 +!endif +!ifdef FOSSIL_DEBUG +DEBUG = 1 +!endif + +# Build the OpenSSL libraries? +!ifndef FOSSIL_BUILD_SSL +FOSSIL_BUILD_SSL = 0 +!endif + +# Build the included zlib library? +!ifndef FOSSIL_BUILD_ZLIB +FOSSIL_BUILD_ZLIB = 1 +!endif + +# Link everything except SQLite dynamically? +!ifndef FOSSIL_DYNAMIC_BUILD +FOSSIL_DYNAMIC_BUILD = 0 +!endif + +# Enable relative paths in external diff/gdiff? +!ifndef FOSSIL_ENABLE_EXEC_REL_PATHS +FOSSIL_ENABLE_EXEC_REL_PATHS = 0 +!endif + +# Enable the JSON API? +!ifndef FOSSIL_ENABLE_JSON +FOSSIL_ENABLE_JSON = 0 +!endif + +# Enable use of miniz instead of zlib? +!ifndef FOSSIL_ENABLE_MINIZ +FOSSIL_ENABLE_MINIZ = 0 +!endif + +# Enable OpenSSL support? +!ifndef FOSSIL_ENABLE_SSL +FOSSIL_ENABLE_SSL = 0 +!endif + +# Enable the Tcl integration subsystem? +!ifndef FOSSIL_ENABLE_TCL +FOSSIL_ENABLE_TCL = 0 +!endif + +# Enable TH1 scripts in embedded documentation files? +!ifndef FOSSIL_ENABLE_TH1_DOCS +FOSSIL_ENABLE_TH1_DOCS = 0 +!endif + +# Enable TH1 hooks for commands and web pages? +!ifndef FOSSIL_ENABLE_TH1_HOOKS +FOSSIL_ENABLE_TH1_HOOKS = 0 +!endif + +# Enable support for Windows XP with Visual Studio 201x? +!ifndef FOSSIL_ENABLE_WINXP +FOSSIL_ENABLE_WINXP = 0 +!endif + +# Enable support for the SQLite Encryption Extension? +!ifndef USE_SEE +USE_SEE = 0 +!endif + +!if $(FOSSIL_ENABLE_SSL)!=0 +SSLDIR = $(B)\compat\openssl +SSLINCDIR = $(SSLDIR)\include +!if $(FOSSIL_DYNAMIC_BUILD)!=0 +SSLLIBDIR = $(SSLDIR) +!else +SSLLIBDIR = $(SSLDIR) +!endif +SSLLFLAGS = /nologo /opt:ref /debug +SSLLIB = libssl.lib libcrypto.lib user32.lib gdi32.lib crypt32.lib +!if "$(PLATFORM)"=="amd64" || "$(PLATFORM)"=="x64" +!message Using 'x64' platform for OpenSSL... +SSLCONFIG = VC-WIN64A no-asm no-ssl3 no-weak-ssl-ciphers +!if $(FOSSIL_DYNAMIC_BUILD)!=0 +SSLCONFIG = $(SSLCONFIG) shared +!else +SSLCONFIG = $(SSLCONFIG) no-shared +!endif +!elseif "$(PLATFORM)"=="ia64" +!message Using 'ia64' platform for OpenSSL... +SSLCONFIG = VC-WIN64I no-asm no-ssl3 no-weak-ssl-ciphers +!if $(FOSSIL_DYNAMIC_BUILD)!=0 +SSLCONFIG = $(SSLCONFIG) shared +!else +SSLCONFIG = $(SSLCONFIG) no-shared +!endif +!else +!message Assuming 'x86' platform for OpenSSL... +SSLCONFIG = VC-WIN32 no-asm no-ssl3 no-weak-ssl-ciphers +!if $(FOSSIL_DYNAMIC_BUILD)!=0 +SSLCONFIG = $(SSLCONFIG) shared +!else +SSLCONFIG = $(SSLCONFIG) no-shared +!endif +!endif +!endif + +!if $(FOSSIL_ENABLE_TCL)!=0 +TCLDIR = $(B)\compat\tcl-8.6 +TCLSRCDIR = $(TCLDIR) +TCLINCDIR = $(TCLSRCDIR)\generic +!endif + +# zlib options +ZINCDIR = $(B)\compat\zlib +ZLIBDIR = $(B)\compat\zlib + +!if $(FOSSIL_DYNAMIC_BUILD)!=0 +ZLIB = zdll.lib +!else +ZLIB = zlib.lib +!endif + +INCL = /I. /I"$(OX)" /I"$(SRCDIR)" /I"$(B)\win\include" + +!if $(FOSSIL_ENABLE_MINIZ)==0 +INCL = $(INCL) /I"$(ZINCDIR)" +!endif + +!if $(FOSSIL_ENABLE_SSL)!=0 +INCL = $(INCL) /I"$(SSLINCDIR)" +!endif + +!if $(FOSSIL_ENABLE_TCL)!=0 +INCL = $(INCL) /I"$(TCLINCDIR)" +!endif + +CFLAGS = /nologo +LDFLAGS = + +CFLAGS = $(CFLAGS) /D_CRT_SECURE_NO_DEPRECATE /D_CRT_SECURE_NO_WARNINGS +CFLAGS = $(CFLAGS) /D_CRT_NONSTDC_NO_DEPRECATE /D_CRT_NONSTDC_NO_WARNINGS + +!if $(FOSSIL_DYNAMIC_BUILD)!=0 +LDFLAGS = $(LDFLAGS) /MANIFEST +!else +LDFLAGS = $(LDFLAGS) /NODEFAULTLIB:msvcrt /MANIFEST:NO +!endif + +!if $(FOSSIL_ENABLE_WINXP)!=0 +XPCFLAGS = $(XPCFLAGS) /D_WIN32_WINNT=0x0501 /D_USING_V110_SDK71_=1 +CFLAGS = $(CFLAGS) $(XPCFLAGS) +!if "$(PLATFORM)"=="amd64" || "$(PLATFORM)"=="x64" +XPLDFLAGS = $(XPLDFLAGS) /SUBSYSTEM:CONSOLE,5.02 +!else +XPLDFLAGS = $(XPLDFLAGS) /SUBSYSTEM:CONSOLE,5.01 +!endif +LDFLAGS = $(LDFLAGS) $(XPLDFLAGS) +!endif + +!if $(FOSSIL_DYNAMIC_BUILD)!=0 +!if $(DEBUG)!=0 +CRTFLAGS = /MDd +!else +CRTFLAGS = /MD +!endif +!else +!if $(DEBUG)!=0 +CRTFLAGS = /MTd +!else +CRTFLAGS = /MT +!endif +!endif + +!if $(OPTIMIZATIONS)>3 +RELOPTS = /Os +!elseif $(OPTIMIZATIONS)>2 +RELOPTS = /Ox +!elseif $(OPTIMIZATIONS)>1 +RELOPTS = /O2 +!elseif $(OPTIMIZATIONS)>0 +RELOPTS = /O1 +!else +RELOPTS = +!endif + +!if $(DEBUG)!=0 +CFLAGS = $(CFLAGS) /Zi $(CRTFLAGS) $(DBGOPTS) /DFOSSIL_DEBUG /DTH_MEMDEBUG +LDFLAGS = $(LDFLAGS) /DEBUG +!else +CFLAGS = $(CFLAGS) $(CRTFLAGS) $(RELOPTS) +!endif + +BCC = $(CC) $(CFLAGS) +TCC = $(CC) /c $(CFLAGS) $(MSCDEF) $(INCL) +RCC = $(RC) /D_WIN32 /D_MSC_VER $(MSCDEF) $(INCL) +MTC = mt +LIBS = ws2_32.lib advapi32.lib dnsapi.lib +LIBDIR = + +!if $(FOSSIL_DYNAMIC_BUILD)!=0 +TCC = $(TCC) /DFOSSIL_DYNAMIC_BUILD=1 +RCC = $(RCC) /DFOSSIL_DYNAMIC_BUILD=1 +!endif + +!if $(FOSSIL_ENABLE_MINIZ)==0 +LIBS = $(LIBS) $(ZLIB) +LIBDIR = $(LIBDIR) /LIBPATH:"$(ZLIBDIR)" +!endif + +!if $(FOSSIL_ENABLE_MINIZ)!=0 +TCC = $(TCC) /DFOSSIL_ENABLE_MINIZ=1 +RCC = $(RCC) /DFOSSIL_ENABLE_MINIZ=1 +!endif + +!if $(FOSSIL_ENABLE_JSON)!=0 +TCC = $(TCC) /DFOSSIL_ENABLE_JSON=1 +RCC = $(RCC) /DFOSSIL_ENABLE_JSON=1 +!endif + +!if $(FOSSIL_ENABLE_SSL)!=0 +TCC = $(TCC) /DFOSSIL_ENABLE_SSL=1 +RCC = $(RCC) /DFOSSIL_ENABLE_SSL=1 +LIBS = $(LIBS) $(SSLLIB) +LIBDIR = $(LIBDIR) /LIBPATH:"$(SSLLIBDIR)" +!endif + +!if $(FOSSIL_ENABLE_EXEC_REL_PATHS)!=0 +TCC = $(TCC) /DFOSSIL_ENABLE_EXEC_REL_PATHS=1 +RCC = $(RCC) /DFOSSIL_ENABLE_EXEC_REL_PATHS=1 +!endif + +!if $(FOSSIL_ENABLE_TH1_DOCS)!=0 +TCC = $(TCC) /DFOSSIL_ENABLE_TH1_DOCS=1 +RCC = $(RCC) /DFOSSIL_ENABLE_TH1_DOCS=1 +!endif + +!if $(FOSSIL_ENABLE_TH1_HOOKS)!=0 +TCC = $(TCC) /DFOSSIL_ENABLE_TH1_HOOKS=1 +RCC = $(RCC) /DFOSSIL_ENABLE_TH1_HOOKS=1 +!endif + +!if $(FOSSIL_ENABLE_TCL)!=0 +TCC = $(TCC) /DFOSSIL_ENABLE_TCL=1 +RCC = $(RCC) /DFOSSIL_ENABLE_TCL=1 +TCC = $(TCC) /DFOSSIL_ENABLE_TCL_STUBS=1 +RCC = $(RCC) /DFOSSIL_ENABLE_TCL_STUBS=1 +TCC = $(TCC) /DFOSSIL_ENABLE_TCL_PRIVATE_STUBS=1 +RCC = $(RCC) /DFOSSIL_ENABLE_TCL_PRIVATE_STUBS=1 +TCC = $(TCC) /DUSE_TCL_STUBS=1 +RCC = $(RCC) /DUSE_TCL_STUBS=1 +!endif + +!if $(USE_SEE)!=0 +TCC = $(TCC) /DUSE_SEE=1 +RCC = $(RCC) /DUSE_SEE=1 +!endif +} +regsub -all {[-]D} [join $SQLITE_WIN32_OPTIONS { }] {/D} MSC_SQLITE_OPTIONS +set j " \\\n " +writeln "SQLITE_OPTIONS = [join $MSC_SQLITE_OPTIONS $j]\n" + +regsub -all {[-]D} [join $SHELL_WIN32_OPTIONS { }] {/D} MSC_SHELL_OPTIONS +set j " \\\n " +writeln "SHELL_OPTIONS = [join $MSC_SHELL_OPTIONS $j]\n" + +regsub -all {[-]D} [join $MINIZ_WIN32_OPTIONS { }] {/D} MSC_MINIZ_OPTIONS +set j " \\\n " +writeln "MINIZ_OPTIONS = [join $MSC_MINIZ_OPTIONS $j]\n" + +writeln -nonewline "SRC = " +set i 0 +foreach s [lsort $src] { + if {$i > 0} { + writeln " \\" + writeln -nonewline " " + } + writeln -nonewline "\"\$(OX)\\${s}_.c\""; incr i +} +writeln "\n" +writeln -nonewline "EXTRA_FILES = " +set i 0 +foreach s [lsort $extra_files] { + if {$i > 0} { + writeln " \\" + writeln -nonewline " " + } + set s [regsub -all / $s \\] + writeln -nonewline "\"\$(SRCDIR)\\${s}\""; incr i +} +writeln "\n" +set AdditionalObj [list shell sqlite3 th th_lang th_tcl cson_amalgamation] +writeln -nonewline "OBJ = " +set i 0 +foreach s [lsort [concat $src $AdditionalObj]] { + if {$i > 0} { + writeln " \\" + writeln -nonewline " " + } + writeln -nonewline "\"\$(OX)\\$s\$O\""; incr i +} +if {$i > 0} { + writeln " \\" +} +writeln "!if \$(FOSSIL_ENABLE_MINIZ)!=0" +writeln -nonewline " " +writeln "\"\$(OX)\\miniz\$O\" \\"; incr i +writeln "!endif" +writeln -nonewline " \"\$(OX)\\fossil.res\"\n\n" +writeln [string map [list <<>> \\] { +!ifndef BASEAPPNAME +BASEAPPNAME = fossil +!endif + +APPNAME = $(OX)\$(BASEAPPNAME)$(E) +PDBNAME = $(OX)\$(BASEAPPNAME)$(P) +APPTARGETS = + +all: "$(OX)" "$(APPNAME)" + +$(BASEAPPNAME): "$(APPNAME)" + +$(BASEAPPNAME)$(E): "$(APPNAME)" + +install: "$(APPNAME)" + echo F | xcopy /Y "$(APPNAME)" "$(INSTALLDIR)"\* +!if $(DEBUG)!=0 + echo F | xcopy /Y "$(PDBNAME)" "$(INSTALLDIR)"\* +!endif + +$(OX): + @-mkdir $@ + +zlib: + @echo Building zlib from "$(ZLIBDIR)"... +!if $(FOSSIL_ENABLE_WINXP)!=0 + @pushd "$(ZLIBDIR)" && $(MAKE) /f win32\Makefile.msc $(ZLIB) "CC=cl $(XPCFLAGS)" "LD=link $(XPLDFLAGS)" && popd +!else + @pushd "$(ZLIBDIR)" && $(MAKE) /f win32\Makefile.msc $(ZLIB) && popd +!endif + +clean-zlib: + @pushd "$(ZLIBDIR)" && $(MAKE) /f win32\Makefile.msc clean && popd + +!if $(FOSSIL_ENABLE_SSL)!=0 +openssl: + @echo Building OpenSSL from "$(SSLDIR)"... +!ifdef PERLDIR + @pushd "$(SSLDIR)" && "$(PERLDIR)\$(PERL)" Configure $(SSLCONFIG) && popd +!else + @pushd "$(SSLDIR)" && "$(PERL)" Configure $(SSLCONFIG) && popd +!endif +!if $(FOSSIL_ENABLE_WINXP)!=0 + @pushd "$(SSLDIR)" && $(MAKE) "CC=cl $(XPCFLAGS)" "LFLAGS=$(XPLDFLAGS)" && popd +!else + @pushd "$(SSLDIR)" && $(MAKE) && popd +!endif + +clean-openssl: + @pushd "$(SSLDIR)" && $(MAKE) clean && popd +!endif + +!if $(FOSSIL_ENABLE_MINIZ)==0 +!if $(FOSSIL_BUILD_ZLIB)!=0 +APPTARGETS = $(APPTARGETS) zlib +!endif +!endif + +!if $(FOSSIL_ENABLE_SSL)!=0 +!if $(FOSSIL_BUILD_SSL)!=0 +APPTARGETS = $(APPTARGETS) openssl +!endif +!endif + +"$(APPNAME)" : $(APPTARGETS) "$(OBJDIR)\translate$E" "$(OBJDIR)\mkindex$E" "$(OBJDIR)\codecheck1$E" "$(OX)\headers" $(OBJ) "$(OX)\linkopts" + "$(OBJDIR)\codecheck1$E" $(SRC) + link $(LDFLAGS) /OUT:$@ /PDB:$(@D)\ $(LIBDIR) Wsetargv.obj "$(OX)\fossil.res" @"$(OX)\linkopts" + if exist "$(B)\win\fossil.exe.manifest" <<>> + $(MTC) -nologo -manifest "$(B)\win\fossil.exe.manifest" -outputresource:$@;1 + +"$(OX)\linkopts": "$(B)\win\Makefile.msc"}] +set redir {>} +foreach s [lsort [concat $src $AdditionalObj]] { + writeln "\techo \"\$(OX)\\$s.obj\" $redir \$@" + set redir {>>} +} +set redir {>>} +writeln "!if \$(FOSSIL_ENABLE_MINIZ)!=0" +writeln "\techo \"\$(OX)\\miniz.obj\" $redir \$@" +writeln "!endif" +writeln "\techo \$(LIBS) $redir \$@" +writeln { +"$(OBJDIR)\translate$E": "$(SRCDIR)\translate.c" + $(BCC) /Fe$@ /Fo$(@D)\ /Fd$(@D)\ $** + +"$(OBJDIR)\makeheaders$E": "$(SRCDIR)\makeheaders.c" + $(BCC) /Fe$@ /Fo$(@D)\ /Fd$(@D)\ $** + +"$(OBJDIR)\mkindex$E": "$(SRCDIR)\mkindex.c" + $(BCC) /Fe$@ /Fo$(@D)\ /Fd$(@D)\ $** + +"$(OBJDIR)\mkbuiltin$E": "$(SRCDIR)\mkbuiltin.c" + $(BCC) /Fe$@ /Fo$(@D)\ /Fd$(@D)\ $** + +"$(OBJDIR)\mkversion$E": "$(SRCDIR)\mkversion.c" + $(BCC) /Fe$@ /Fo$(@D)\ /Fd$(@D)\ $** + +"$(OBJDIR)\codecheck1$E": "$(SRCDIR)\codecheck1.c" + $(BCC) /Fe$@ /Fo$(@D)\ /Fd$(@D)\ $** + +!if $(USE_SEE)!=0 +SEE_FLAGS = /DSQLITE_HAS_CODEC=1 /DSQLITE_SHELL_DBKEY_PROC=fossil_key +SQLITE3_SHELL_SRC = $(SRCDIR)\shell-see.c +SQLITE3_SRC = $(SRCDIR)\sqlite3-see.c +!else +SEE_FLAGS = +SQLITE3_SHELL_SRC = $(SRCDIR)\shell.c +SQLITE3_SRC = $(SRCDIR)\sqlite3.c +!endif + +"$(OX)\shell$O" : "$(SQLITE3_SHELL_SRC)" "$(B)\win\Makefile.msc" + $(TCC) /Fo$@ /Fd$(@D)\ $(SHELL_OPTIONS) $(SQLITE_OPTIONS) $(SHELL_CFLAGS) $(SEE_FLAGS) -c "$(SQLITE3_SHELL_SRC)" + +"$(OX)\sqlite3$O" : "$(SQLITE3_SRC)" "$(B)\win\Makefile.msc" + $(TCC) /Fo$@ /Fd$(@D)\ -c $(SQLITE_OPTIONS) $(SQLITE_CFLAGS) $(SEE_FLAGS) "$(SQLITE3_SRC)" + +"$(OX)\th$O" : "$(SRCDIR)\th.c" + $(TCC) /Fo$@ /Fd$(@D)\ -c $** + +"$(OX)\th_lang$O" : "$(SRCDIR)\th_lang.c" + $(TCC) /Fo$@ /Fd$(@D)\ -c $** + +"$(OX)\th_tcl$O" : "$(SRCDIR)\th_tcl.c" + $(TCC) /Fo$@ /Fd$(@D)\ -c $** + +"$(OX)\miniz$O" : "$(SRCDIR)\miniz.c" + $(TCC) /Fo$@ /Fd$(@D)\ -c $(MINIZ_OPTIONS) $** + +"$(OX)\VERSION.h" : "$(OBJDIR)\mkversion$E" "$(B)\manifest.uuid" "$(B)\manifest" "$(B)\VERSION" "$(B)\phony.h" + "$(OBJDIR)\mkversion$E" "$(B)\manifest.uuid" "$(B)\manifest" "$(B)\VERSION" > $@ + +"$(B)\phony.h" : + rem Force rebuild of VERSION.h whenever nmake is run + +"$(OX)\cson_amalgamation$O" : "$(SRCDIR)\cson_amalgamation.c" + $(TCC) /Fo$@ /Fd$(@D)\ -c $** + +"$(OX)\page_index.h": "$(OBJDIR)\mkindex$E" $(SRC) + $** > $@ + +"$(OX)\builtin_data.h": "$(OBJDIR)\mkbuiltin$E" "$(OX)\builtin_data.reslist" + "$(OBJDIR)\mkbuiltin$E" --prefix "$(SRCDIR)/" --reslist "$(OX)\builtin_data.reslist" > $@ + +cleanx: + -del "$(OX)\*.obj" 2>NUL + -del "$(OBJDIR)\*.obj" 2>NUL + -del "$(OX)\*_.c" 2>NUL + -del "$(OX)\*.h" 2>NUL + -del "$(OX)\*.ilk" 2>NUL + -del "$(OX)\*.map" 2>NUL + -del "$(OX)\*.res" 2>NUL + -del "$(OX)\*.reslist" 2>NUL + -del "$(OX)\headers" 2>NUL + -del "$(OX)\linkopts" 2>NUL + -del "$(OX)\vc*.pdb" 2>NUL + +clean: cleanx + -del "$(APPNAME)" 2>NUL + -del "$(PDBNAME)" 2>NUL + -del "$(OBJDIR)\translate$E" 2>NUL + -del "$(OBJDIR)\translate$P" 2>NUL + -del "$(OBJDIR)\mkindex$E" 2>NUL + -del "$(OBJDIR)\mkindex$P" 2>NUL + -del "$(OBJDIR)\makeheaders$E" 2>NUL + -del "$(OBJDIR)\makeheaders$P" 2>NUL + -del "$(OBJDIR)\mkversion$E" 2>NUL + -del "$(OBJDIR)\mkversion$P" 2>NUL + -del "$(OBJDIR)\mkcss$E" 2>NUL + -del "$(OBJDIR)\mkcss$P" 2>NUL + -del "$(OBJDIR)\codecheck1$E" 2>NUL + -del "$(OBJDIR)\codecheck1$P" 2>NUL + -del "$(OBJDIR)\mkbuiltin$E" 2>NUL + -del "$(OBJDIR)\mkbuiltin$P" 2>NUL + +realclean: clean + +"$(OBJDIR)\json$O" : "$(SRCDIR)\json_detail.h" +"$(OBJDIR)\json_artifact$O" : "$(SRCDIR)\json_detail.h" +"$(OBJDIR)\json_branch$O" : "$(SRCDIR)\json_detail.h" +"$(OBJDIR)\json_config$O" : "$(SRCDIR)\json_detail.h" +"$(OBJDIR)\json_diff$O" : "$(SRCDIR)\json_detail.h" +"$(OBJDIR)\json_dir$O" : "$(SRCDIR)\json_detail.h" +"$(OBJDIR)\json_finfo$O" : "$(SRCDIR)\json_detail.h" +"$(OBJDIR)\json_login$O" : "$(SRCDIR)\json_detail.h" +"$(OBJDIR)\json_query$O" : "$(SRCDIR)\json_detail.h" +"$(OBJDIR)\json_report$O" : "$(SRCDIR)\json_detail.h" +"$(OBJDIR)\json_status$O" : "$(SRCDIR)\json_detail.h" +"$(OBJDIR)\json_tag$O" : "$(SRCDIR)\json_detail.h" +"$(OBJDIR)\json_timeline$O" : "$(SRCDIR)\json_detail.h" +"$(OBJDIR)\json_user$O" : "$(SRCDIR)\json_detail.h" +"$(OBJDIR)\json_wiki$O" : "$(SRCDIR)\json_detail.h" +} + +writeln {"$(OX)\builtin_data.reslist": $(EXTRA_FILES) "$(B)\win\Makefile.msc"} +set redir {>} +foreach s [lsort $extra_files] { + writeln "\techo \"\$(SRCDIR)\\${s}\" $redir \$@" + set redir {>>} +} + +writeln "" +foreach s [lsort $src] { + writeln "\"\$(OX)\\$s\$O\" : \"\$(OX)\\${s}_.c\" \"\$(OX)\\${s}.h\"" + writeln "\t\$(TCC) /Fo\$@ /Fd\$(@D)\\ -c \"\$(OX)\\${s}_.c\"\n" + writeln "\"\$(OX)\\${s}_.c\" : \"\$(SRCDIR)\\$s.c\"" + writeln "\t\"\$(OBJDIR)\\translate\$E\" \$** > \$@\n" +} + +writeln "\"\$(OX)\\fossil.res\" : \"\$(B)\\win\\fossil.rc\"" +writeln "\t\$(RCC) /fo \$@ \$**\n" + +writeln "\"\$(OX)\\headers\": \"\$(OBJDIR)\\makeheaders\$E\" \"\$(OX)\\page_index.h\" \"\$(OX)\\builtin_data.h\" \"\$(OX)\\VERSION.h\"" +writeln -nonewline "\t\"\$(OBJDIR)\\makeheaders\$E\" " +set i 0 +foreach s [lsort $src] { + if {$i > 0} { + writeln " \\" + writeln -nonewline "\t\t\t" + } + writeln -nonewline "\"\$(OX)\\${s}_.c\":\"\$(OX)\\$s.h\""; incr i +} +writeln " \\\n\t\t\t\"\$(SRCDIR)\\sqlite3.h\" \\" +writeln "\t\t\t\"\$(SRCDIR)\\th.h\" \\" +writeln "\t\t\t\"\$(OX)\\VERSION.h\" \\" +writeln "\t\t\t\"\$(SRCDIR)\\cson_amalgamation.h\"" +writeln "\t@copy /Y nul: $@" + + +close $output_file +# +# End of the win/Makefile.msc output +############################################################################## +############################################################################## +############################################################################## +# Begin win/Makefile.PellesCGMake output +# +puts "building ../win/Makefile.PellesCGMake" +set output_file [open ../win/Makefile.PellesCGMake w] +fconfigure $output_file -translation binary + +writeln [string map [list \ + <<>> [join $SQLITE_WIN32_OPTIONS { }] \ + <<>> [join $SHELL_WIN32_OPTIONS { }]] {# +############################################################################## +# WARNING: DO NOT EDIT, AUTOMATICALLY GENERATED FILE (SEE "src/makemake.tcl") +############################################################################## +# +# This file is automatically generated. Instead of editing this +# file, edit "makemake.tcl" then run "tclsh makemake.tcl" +# to regenerate this file. +# +# HowTo +# ----- +# +# This is a Makefile to compile fossil with PellesC from +# http://www.smorgasbordet.com/pellesc/index.htm +# In addition to the Compiler envrionment, you need +# gmake from http://sourceforge.net/projects/unxutils/, Pelles make version +# couldn't handle the complex dependencies in this build +# zlib sources +# Then you do +# 1. create a directory PellesC in the project root directory +# 2. Change the variables PellesCDir/ZLIBSRCDIR to the path of your installation +# 3. open a dos prompt window and change working directory into PellesC (step 1) +# 4. run gmake -f ..\win\Makefile.PellesCGMake +# +# this file is tested with +# PellesC 5.00.13 +# gmake 3.80 +# zlib sources 1.2.5 +# Windows XP SP 2 +# and +# PellesC 6.00.4 +# gmake 3.80 +# zlib sources 1.2.5 +# Windows 7 Home Premium +# + +# +PellesCDir=c:\Programme\PellesC + +# Select between 32/64 bit code, default is 32 bit +#TARGETVERSION=64 + +ifeq ($(TARGETVERSION),64) +# 64 bit version +TARGETMACHINE_CC=amd64 +TARGETMACHINE_LN=amd64 +TARGETEXTEND=64 +else +# 32 bit version +TARGETMACHINE_CC=x86 +TARGETMACHINE_LN=ix86 +TARGETEXTEND= +endif + +# define the project directories +B=.. +SRCDIR=$(B)/src/ +WINDIR=$(B)/win/ +ZLIBSRCDIR=../../zlib/ + +# define linker command and options +LINK=$(PellesCDir)/bin/polink.exe +LINKFLAGS=-subsystem:console -machine:$(TARGETMACHINE_LN) /LIBPATH:$(PellesCDir)\lib\win$(TARGETEXTEND) /LIBPATH:$(PellesCDir)\lib kernel32.lib advapi32.lib delayimp$(TARGETEXTEND).lib Wsock32.lib dnsapi.lib Crtmt$(TARGETEXTEND).lib + +# define standard C-compiler and flags, used to compile +# the fossil binary. Some special definitions follow for +# special files follow +CC=$(PellesCDir)\bin\pocc.exe +DEFINES=-D_pgmptr=g.argv[0] +CCFLAGS=-T$(TARGETMACHINE_CC)-coff -Ot -W2 -Gd -Go -Ze -MT $(DEFINES) +INCLUDE=/I $(PellesCDir)\Include\Win /I $(PellesCDir)\Include /I $(ZLIBSRCDIR) /I $(SRCDIR) + +# define commands for building the windows resource files +RESOURCE=fossil.res +RC=$(PellesCDir)\bin\porc.exe +RCFLAGS=$(INCLUDE) -D__POCC__=1 -D_M_X$(TARGETVERSION) + +# define the special utilities files, needed to generate +# the automatically generated source files +UTILS=translate.exe mkindex.exe makeheaders.exe mkbuiltin.exe +UTILS_OBJ=$(UTILS:.exe=.obj) +UTILS_SRC=$(foreach uf,$(UTILS),$(SRCDIR)$(uf:.exe=.c)) + +# define the SQLite files, which need special flags on compile +SQLITESRC=sqlite3.c +ORIGSQLITESRC=$(foreach sf,$(SQLITESRC),$(SRCDIR)$(sf)) +SQLITEOBJ=$(foreach sf,$(SQLITESRC),$(sf:.c=.obj)) +SQLITEDEFINES=<<>> + +# define the SQLite shell files, which need special flags on compile +SQLITESHELLSRC=shell.c +ORIGSQLITESHELLSRC=$(foreach sf,$(SQLITESHELLSRC),$(SRCDIR)$(sf)) +SQLITESHELLOBJ=$(foreach sf,$(SQLITESHELLSRC),$(sf:.c=.obj)) +SQLITESHELLDEFINES=<<>> + +# define the th scripting files, which need special flags on compile +THSRC=th.c th_lang.c +ORIGTHSRC=$(foreach sf,$(THSRC),$(SRCDIR)$(sf)) +THOBJ=$(foreach sf,$(THSRC),$(sf:.c=.obj)) + +# define the zlib files, needed by this compile +ZLIBSRC=adler32.c compress.c crc32.c deflate.c gzclose.c gzlib.c gzread.c gzwrite.c infback.c inffast.c inflate.c inftrees.c trees.c uncompr.c zutil.c +ORIGZLIBSRC=$(foreach sf,$(ZLIBSRC),$(ZLIBSRCDIR)$(sf)) +ZLIBOBJ=$(foreach sf,$(ZLIBSRC),$(sf:.c=.obj)) + +# define all fossil sources, using the standard compile and +# source generation. These are all files in SRCDIR, which are not +# mentioned as special files above: +ORIGSRC=$(filter-out $(UTILS_SRC) $(ORIGTHSRC) $(ORIGSQLITESRC) $(ORIGSQLITESHELLSRC),$(wildcard $(SRCDIR)*.c)) +SRC=$(subst $(SRCDIR),,$(ORIGSRC)) +TRANSLATEDSRC=$(SRC:.c=_.c) +TRANSLATEDOBJ=$(TRANSLATEDSRC:.c=.obj) + +# main target file is the application +APPLICATION=fossil.exe + +# define the standard make target +.PHONY: default +default: page_index.h builtin_data.h headers $(APPLICATION) + +# symbolic target to generate the source generate utils +.PHONY: utils +utils: $(UTILS) + +# link utils +$(UTILS) version.exe: %.exe: %.obj + $(LINK) $(LINKFLAGS) -out:"$@" $< + +# compiling standard fossil utils +$(UTILS_OBJ): %.obj: $(SRCDIR)%.c + $(CC) $(CCFLAGS) $(INCLUDE) "$<" -Fo"$@" + +# compile special windows utils +version.obj: $(SRCDIR)mkversion.c + $(CC) $(CCFLAGS) $(INCLUDE) "$<" -Fo"$@" + +# generate the translated c-source files +$(TRANSLATEDSRC): %_.c: $(SRCDIR)%.c translate.exe + translate.exe $< >$@ + +# generate the index source, containing all web references,.. +page_index.h: $(TRANSLATEDSRC) mkindex.exe + mkindex.exe $(TRANSLATEDSRC) >$@ + +builtin_data.h: $(EXTRA_FILES) mkbuiltin.exe + mkbuiltin.exe --prefix $(SRCDIR)/ $(EXTRA_FILES) >$@ + +# extracting version info from manifest +VERSION.h: version.exe ..\manifest.uuid ..\manifest ..\VERSION + version.exe ..\manifest.uuid ..\manifest ..\VERSION >$@ + +# generate the simplified headers +headers: makeheaders.exe page_index.h builtin_data.h VERSION.h ../src/sqlite3.h ../src/th.h + makeheaders.exe $(foreach ts,$(TRANSLATEDSRC),$(ts):$(ts:_.c=.h)) ../src/sqlite3.h ../src/th.h VERSION.h + echo Done >$@ + +# compile C sources with relevant options + +$(TRANSLATEDOBJ): %_.obj: %_.c %.h + $(CC) $(CCFLAGS) $(INCLUDE) "$<" -Fo"$@" + +$(SQLITEOBJ): %.obj: $(SRCDIR)%.c $(SRCDIR)%.h + $(CC) $(CCFLAGS) $(SQLITEDEFINES) $(INCLUDE) "$<" -Fo"$@" + +$(SQLITESHELLOBJ): %.obj: $(SRCDIR)%.c + $(CC) $(CCFLAGS) $(SQLITESHELLDEFINES) $(INCLUDE) "$<" -Fo"$@" + +$(THOBJ): %.obj: $(SRCDIR)%.c $(SRCDIR)th.h + $(CC) $(CCFLAGS) $(INCLUDE) "$<" -Fo"$@" + +$(ZLIBOBJ): %.obj: $(ZLIBSRCDIR)%.c + $(CC) $(CCFLAGS) $(INCLUDE) "$<" -Fo"$@" + +# create the windows resource with icon and version info +$(RESOURCE): %.res: ../win/%.rc ../win/*.ico + $(RC) $(RCFLAGS) $< -Fo"$@" + +# link the application +$(APPLICATION): $(TRANSLATEDOBJ) $(SQLITEOBJ) $(SQLITESHELLOBJ) $(THOBJ) $(ZLIBOBJ) headers $(RESOURCE) + $(LINK) $(LINKFLAGS) -out:"$@" $(TRANSLATEDOBJ) $(SQLITEOBJ) $(SQLITESHELLOBJ) $(THOBJ) $(ZLIBOBJ) $(RESOURCE) + +# cleanup + +.PHONY: clean +clean: + -del /F $(TRANSLATEDOBJ) $(SQLITEOBJ) $(THOBJ) $(ZLIBOBJ) $(UTILS_OBJ) version.obj + -del /F $(TRANSLATEDSRC) + -del /F *.h headers + -del /F $(RESOURCE) + +.PHONY: clobber +clobber: clean + -del /F *.exe +}] Index: src/manifest.c ================================================================== --- src/manifest.c +++ src/manifest.c @@ -26,143 +26,536 @@ #if INTERFACE /* ** Types of control files */ +#define CFTYPE_ANY 0 #define CFTYPE_MANIFEST 1 #define CFTYPE_CLUSTER 2 #define CFTYPE_CONTROL 3 #define CFTYPE_WIKI 4 #define CFTYPE_TICKET 5 #define CFTYPE_ATTACHMENT 6 +#define CFTYPE_EVENT 7 +#define CFTYPE_FORUM 8 + +/* +** File permissions used by Fossil internally. +*/ +#define PERM_REG 0 /* regular file */ +#define PERM_EXE 1 /* executable */ +#define PERM_LNK 2 /* symlink */ + +/* +** Flags for use with manifest_crosslink(). +*/ +#define MC_NONE 0 /* default handling */ +#define MC_PERMIT_HOOKS 1 /* permit hooks to execute */ +#define MC_NO_ERRORS 2 /* do not issue errors for a bad parse */ + +/* +** A single F-card within a manifest +*/ +struct ManifestFile { + char *zName; /* Name of a file */ + char *zUuid; /* Artifact hash for the file */ + char *zPerm; /* File permissions */ + char *zPrior; /* Prior name if the name was changed */ +}; + /* ** A parsed manifest or cluster. */ struct Manifest { Blob content; /* The original content blob */ int type; /* Type of artifact. One of CFTYPE_xxxxx */ + int rid; /* The blob-id for this manifest */ + const char *zBaseline;/* Baseline manifest. The B card. */ + Manifest *pBaseline; /* The actual baseline manifest */ char *zComment; /* Decoded comment. The C card. */ double rDate; /* Date and time from D card. 0.0 if no D card. */ char *zUser; /* Name of the user from the U card. */ char *zRepoCksum; /* MD5 checksum of the baseline content. R card. */ char *zWiki; /* Text of the wiki page. W card. */ char *zWikiTitle; /* Name of the wiki page. L card. */ + char *zMimetype; /* Mime type of wiki or comment text. N card. */ + char *zThreadTitle; /* The forum thread title. H card */ + double rEventDate; /* Date of an event. E card. */ + char *zEventId; /* Artifact hash for an event. E card. */ char *zTicketUuid; /* UUID for a ticket. K card. */ char *zAttachName; /* Filename of an attachment. A card. */ - char *zAttachSrc; /* UUID of document being attached. A card. */ + char *zAttachSrc; /* Artifact hash for document being attached. A card. */ char *zAttachTarget; /* Ticket or wiki that attachment applies to. A card */ + char *zThreadRoot; /* Thread root artifact. G card */ + char *zInReplyTo; /* Forum in-reply-to artifact. I card */ int nFile; /* Number of F cards */ int nFileAlloc; /* Slots allocated in aFile[] */ - struct { - char *zName; /* Name of a file */ - char *zUuid; /* UUID of the file */ - char *zPerm; /* File permissions */ - char *zPrior; /* Prior name if the name was changed */ - int iRename; /* index of renamed name in prior/next manifest */ - } *aFile; /* One entry for each F card */ + int iFile; /* Index of current file in iterator */ + ManifestFile *aFile; /* One entry for each F-card */ int nParent; /* Number of parents. */ int nParentAlloc; /* Slots allocated in azParent[] */ - char **azParent; /* UUIDs of parents. One for each P card argument */ + char **azParent; /* Hashes of parents. One for each P card argument */ + int nCherrypick; /* Number of entries in aCherrypick[] */ + struct { + char *zCPTarget; /* Hash for cherry-picked version w/ +|- prefix */ + char *zCPBase; /* Hash for cherry-pick baseline. NULL for singletons */ + } *aCherrypick; int nCChild; /* Number of cluster children */ int nCChildAlloc; /* Number of closts allocated in azCChild[] */ - char **azCChild; /* UUIDs of referenced objects in a cluster. M cards */ + char **azCChild; /* Hashes of referenced objects in a cluster. M cards */ int nTag; /* Number of T Cards */ int nTagAlloc; /* Slots allocated in aTag[] */ - struct { + struct TagType { char *zName; /* Name of the tag */ - char *zUuid; /* UUID that the tag is applied to */ + char *zUuid; /* Hash of artifact that the tag is applied to */ char *zValue; /* Value if the tag is really a property */ } *aTag; /* One for each T card */ int nField; /* Number of J cards */ int nFieldAlloc; /* Slots allocated in aField[] */ - struct { + struct { char *zName; /* Key or field name */ char *zValue; /* Value of the field */ } *aField; /* One for each J card */ }; #endif +/* +** Allowed and required card types in each style of artifact +*/ +static struct { + const char *zAllowed; /* Allowed cards. Human-readable */ + const char *zRequired; /* Required cards. Human-readable */ +} manifestCardTypes[] = { + /* Allowed Required */ + /* CFTYPE_MANIFEST 1 */ { "BCDFNPQRTUZ", "DZ" }, + /* Wants to be "CDUZ" ----^^^^ + ** but we must limit for historical compatibility */ + /* CFTYPE_CLUSTER 2 */ { "MZ", "MZ" }, + /* CFTYPE_CONTROL 3 */ { "DTUZ", "DTUZ" }, + /* CFTYPE_WIKI 4 */ { "CDLNPUWZ", "DLUWZ" }, + /* CFTYPE_TICKET 5 */ { "DJKUZ", "DJKUZ" }, + /* CFTYPE_ATTACHMENT 6 */ { "ACDNUZ", "ADZ" }, + /* CFTYPE_EVENT 7 */ { "CDENPTUWZ", "DEWZ" }, + /* CFTYPE_FORUM 8 */ { "DGHINPUWZ", "DUWZ" }, +}; + +/* +** Names of manifest types +*/ +static const char *const azNameOfMType[] = { + "manifest", + "cluster", + "tag", + "wiki", + "ticket", + "attachment", + "technote", + "forum post" +}; + +/* +** A cache of parsed manifests. This reduces the number of +** calls to manifest_parse() when doing a rebuild. +*/ +#define MX_MANIFEST_CACHE 6 +static struct { + int nxAge; + int aAge[MX_MANIFEST_CACHE]; + Manifest *apManifest[MX_MANIFEST_CACHE]; +} manifestCache; + +/* +** True if manifest_crosslink_begin() has been called but +** manifest_crosslink_end() is still pending. +*/ +static int manifest_crosslink_busy = 0; + +/* +** There are some triggers that need to fire whenever new content +** is added to the EVENT table, to make corresponding changes to the +** PENDING_ALERT and CHAT tables. These are done with TEMP triggers +** which are created as needed. The reasons for using TEMP triggers: +** +** * A small minority of invocations of Fossil need to use those triggers. +** So we save CPU cycles in the common case by not having to parse the +** trigger definition +** +** * We don't have to worry about dangling table references inside +** of triggers. For example, we can create a trigger that adds +** to the CHAT table. But an admin can still drop that CHAT table +** at any moment, since the trigger that refers to CHAT is a TEMP +** trigger and won't persist to cause problems. +** +** * Because TEMP triggers are defined by the specific version of the +** application that is running, we don't have to worry with legacy +** compatibility of the triggers. +** +** This boolean variable is set when the TEMP triggers for EVENT +** have been created. +*/ +static int manifest_event_triggers_are_enabled = 0; /* ** Clear the memory allocated in a manifest object */ -void manifest_clear(Manifest *p){ - blob_reset(&p->content); - free(p->aFile); - free(p->azParent); - free(p->azCChild); - free(p->aTag); - free(p->aField); - memset(p, 0, sizeof(*p)); +void manifest_destroy(Manifest *p){ + if( p ){ + blob_reset(&p->content); + fossil_free(p->aFile); + fossil_free(p->azParent); + fossil_free(p->azCChild); + fossil_free(p->aTag); + fossil_free(p->aField); + fossil_free(p->aCherrypick); + if( p->pBaseline ) manifest_destroy(p->pBaseline); + memset(p, 0, sizeof(*p)); + fossil_free(p); + } +} + +/* +** Given a string of upper-case letters, compute a mask of the letters +** present. For example, "ABC" computes 0x0007. "DE" gives 0x0018". +*/ +static unsigned int manifest_card_mask(const char *z){ + unsigned int m = 0; + char c; + while( (c = *(z++))>='A' && c<='Z' ){ + m |= 1 << (c - 'A'); + } + return m; +} + +/* +** Given an integer mask representing letters A-Z, return the +** letter which is the first bit set in the mask. Example: +** 0x03520 gives 'F' since the F-bit is the lowest. +*/ +static char maskToType(unsigned int x){ + char c = 'A'; + if( x==0 ) return '?'; + while( (x&1)==0 ){ x >>= 1; c++; } + return c; +} + +/* +** Add an element to the manifest cache using LRU replacement. +*/ +void manifest_cache_insert(Manifest *p){ + while( p ){ + int i; + Manifest *pBaseline = p->pBaseline; + p->pBaseline = 0; + for(i=0; i=MX_MANIFEST_CACHE ){ + int oldest = 0; + int oldestAge = manifestCache.aAge[0]; + for(i=1; irid==rid ){ + p = manifestCache.apManifest[i]; + manifestCache.apManifest[i] = 0; + return p; + } + } + return 0; +} + +/* +** Clear the manifest cache. +*/ +void manifest_cache_clear(void){ + int i; + for(i=0; i=n ) return; + z += i; + n -= i; + *pz = z; + for(i=n-1; i>=0; i--){ + if( z[i]=='\n' && strncmp(&z[i],"\n-----BEGIN PGP SIGNATURE-", 25)==0 ){ + n = i+1; + break; + } + } + *pn = n; + return; +} + +/* +** Verify the Z-card checksum on the artifact, if there is such a +** checksum. Return 0 if there is no Z-card. Return 1 if the Z-card +** exists and is correct. Return 2 if the Z-card exists and has the wrong +** value. +** +** 0123456789 123456789 123456789 123456789 +** Z aea84f4f863865a8d59d0384e4d2a41c +*/ +static int verify_z_card(const char *z, int n, Blob *pErr){ + const char *zHash; + if( n<35 ) return 0; + if( z[n-35]!='Z' || z[n-34]!=' ' ) return 0; + md5sum_init(); + md5sum_step_text(z, n-35); + zHash = md5sum_finish(0); + if( memcmp(&z[n-33], zHash, 32)==0 ){ + return 1; + }else{ + blob_appendf(pErr, "incorrect Z-card cksum: expected %.32s", zHash); + return 2; + } +} + +/* +** A structure used for rapid parsing of the Manifest file +*/ +typedef struct ManifestText ManifestText; +struct ManifestText { + char *z; /* The first character of the next token */ + char *zEnd; /* One character beyond the end of the manifest */ + int atEol; /* True if z points to the start of a new line */ +}; + +/* +** Return a pointer to the next token. The token is zero-terminated. +** Return NULL if there are no more tokens on the current line. +*/ +static char *next_token(ManifestText *p, int *pLen){ + char *zStart; + int n; + if( p->atEol ) return 0; + zStart = p->z; + n = strcspn(p->z, " \n"); + p->atEol = p->z[n]=='\n'; + p->z[n] = 0; + p->z += n+1; + if( pLen ) *pLen = n; + return zStart; +} + +/* +** Return the card-type for the next card. Or, return 0 if there are no +** more cards or if we are not at the end of the current card. +*/ +static char next_card(ManifestText *p){ + 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; +} + +/* +** Shorthand for a control-artifact parsing error +*/ +#define SYNTAX(T) {zErr=(T); goto manifest_syntax_error;} + +/* +** A cache of manifest IDs which manifest_parse() has seen in this +** session. +*/ +static Bag seenManifests = Bag_INIT; +/* +** Frees all memory owned by the manifest "has-seen" cache. Intended +** to be called only from the app's atexit() handler. +*/ +void manifest_clear_cache(){ + bag_clear(&seenManifests); } /* ** Parse a blob into a Manifest object. The Manifest object ** takes over the input blob and will free it when the ** Manifest object is freed. Zeros are inserted into the blob ** as string terminators so that blob should not be used again. ** -** Return TRUE if the content really is a control file of some -** kind. Return FALSE if there are syntax errors. +** Return a pointer to an allocated Manifest object if the content +** really is a structural artifact of some kind. The returned Manifest +** object needs to be freed by a subsequent call to manifest_destroy(). +** Return NULL if there are syntax errors or if the input blob does +** not describe a valid structural artifact. ** -** This routine is strict about the format of a control file. +** This routine is strict about the format of a structural artifacts. ** The format must match exactly or else it is rejected. This -** rule minimizes the risk that a content file will be mistaken -** for a control file simply because they look the same. +** rule minimizes the risk that a content artifact will be mistaken +** for a structural artifact simply because they look the same. ** -** The pContent is reset. If TRUE is returned, then pContent will -** be reset when the Manifest object is cleared. If FALSE is +** The pContent is reset. If a pointer is returned, then pContent will +** be reset when the Manifest object is cleared. If NULL is ** returned then the Manifest object is cleared automatically ** and pContent is reset before the return. ** -** The entire file can be PGP clear-signed. The signature is ignored. -** The file consists of zero or more cards, one card per line. +** The entire input blob can be PGP clear-signed. The signature is ignored. +** The artifact consists of zero or more cards, one card per line. ** (Except: the content of the W card can extend of multiple lines.) ** Each card is divided into tokens by a single space character. ** The first token is a single upper-case letter which is the card type. ** The card type determines the other parameters to the card. ** Cards must occur in lexicographical order. */ -int manifest_parse(Manifest *p, Blob *pContent){ - int seenHeader = 0; - int seenZ = 0; +Manifest *manifest_parse(Blob *pContent, int rid, Blob *pErr){ + Manifest *p; int i, lineNo=0; - Blob line, token, a1, a2, a3, a4; + ManifestText x; char cPrevType = 0; + char cType; + char *z; + int n; + char *zUuid; + int sz = 0; + int isRepeat; + int nSelfTag = 0; /* Number of T cards referring to this manifest */ + int nSimpleTag = 0; /* Number of T cards with "+" prefix */ + const char *zErr = 0; + unsigned int m; + unsigned int seenCard = 0; /* Which card types have been seen */ + char zErrBuf[100]; /* Write error messages here */ + if( rid==0 ){ + isRepeat = 1; + }else if( bag_find(&seenManifests, rid) ){ + isRepeat = 1; + }else{ + isRepeat = 0; + bag_insert(&seenManifests, rid); + } + + /* Every structural artifact ends with a '\n' character. Exit early + ** if that is not the case for this artifact. + */ + if( !isRepeat ) g.parseCnt[0]++; + z = blob_materialize(pContent); + n = blob_size(pContent); + if( n<=0 || z[n-1]!='\n' ){ + blob_reset(pContent); + blob_appendf(pErr, "%s", n ? "not terminated with \\n" : "zero-length"); + return 0; + } + + /* Strip off the PGP signature if there is one. + */ + remove_pgp_signature((const char**)&z, &n); + + /* Verify that the first few characters of the artifact look like + ** a control artifact. + */ + if( n<10 || z[0]<'A' || z[0]>'Z' || z[1]!=' ' ){ + blob_reset(pContent); + blob_appendf(pErr, "line 1 not recognized"); + return 0; + } + /* Then verify the Z-card. + */ +#if 1 + /* Disable this ***ONLY*** (ONLY!) when testing hand-written inputs + for card-related syntax errors. */ + if( verify_z_card(z, n, pErr)==2 ){ + blob_reset(pContent); + return 0; + } +#else +#warning ACHTUNG - z-card check is disabled for testing purposes. + if(0 && verify_z_card(NULL, 0, NULL)){ + /*avoid unused static func error*/ + } +#endif + + /* Allocate a Manifest object to hold the parsed control artifact. + */ + p = fossil_malloc( sizeof(*p) ); memset(p, 0, sizeof(*p)); memcpy(&p->content, pContent, sizeof(p->content)); + p->rid = rid; blob_zero(pContent); pContent = &p->content; - blob_zero(&a1); - blob_zero(&a2); - blob_zero(&a3); - md5sum_init(); - while( blob_line(pContent, &line) ){ - char *z = blob_buffer(&line); + /* Begin parsing, card by card. + */ + x.z = z; + x.zEnd = &z[n]; + x.atEol = 1; + while( (cType = next_card(&x))!=0 ){ + if( cTypezEventId==0 ){ + SYNTAX("cards not in lexicographical order"); + } + } lineNo++; - if( z[0]=='-' ){ - if( strncmp(z, "-----BEGIN PGP ", 15)!=0 ){ - goto manifest_syntax_error; - } - if( seenHeader ){ - break; - } - while( blob_line(pContent, &line)>2 ){} - if( blob_line(pContent, &line)==0 ) break; - z = blob_buffer(&line); - } - if( z[0]'Z' ) SYNTAX("bad card type"); + seenCard |= 1 << (cType-'A'); + cPrevType = cType; + switch( cType ){ /* ** A ?? ** ** Identifies an attachment to either a wiki page or a ticket. ** is the artifact that is the attachment. @@ -169,50 +562,64 @@ ** is omitted to delete an attachment. is the name of ** a wiki page or ticket to which that attachment is connected. */ case 'A': { char *zName, *zTarget, *zSrc; - md5sum_step_text(blob_buffer(&line), blob_size(&line)); - if( blob_token(&line, &a1)==0 ) goto manifest_syntax_error; - if( blob_token(&line, &a2)==0 ) goto manifest_syntax_error; + int nTarget = 0, nSrc = 0; + zName = next_token(&x, 0); + zTarget = next_token(&x, &nTarget); + zSrc = next_token(&x, &nSrc); + if( zName==0 || zTarget==0 ) goto manifest_syntax_error; if( p->zAttachName!=0 ) goto manifest_syntax_error; - zName = blob_terminate(&a1); - zTarget = blob_terminate(&a2); - blob_token(&line, &a3); - zSrc = blob_terminate(&a3); defossilize(zName); - if( !file_is_simple_pathname(zName) ){ - goto manifest_syntax_error; + if( !file_is_simple_pathname_nonstrict(zName) ){ + SYNTAX("invalid filename on A-card"); } defossilize(zTarget); - if( (blob_size(&a2)!=UUID_SIZE || !validate16(zTarget, UUID_SIZE)) + if( !hname_validate(zTarget,nTarget) && !wiki_name_is_wellformed((const unsigned char *)zTarget) ){ - goto manifest_syntax_error; + SYNTAX("invalid target on A-card"); } - if( blob_size(&a3)>0 - && (blob_size(&a3)!=UUID_SIZE || !validate16(zSrc, UUID_SIZE)) ){ - goto manifest_syntax_error; + if( zSrc && !hname_validate(zSrc,nSrc) ){ + SYNTAX("invalid source on A-card"); } p->zAttachName = (char*)file_tail(zName); p->zAttachSrc = zSrc; p->zAttachTarget = zTarget; + p->type = CFTYPE_ATTACHMENT; + break; + } + + /* + ** B + ** + ** A B-line gives the artifact hash for the baseline of a delta-manifest. + */ + case 'B': { + if( p->zBaseline ) SYNTAX("more than one B-card"); + p->zBaseline = next_token(&x, &sz); + if( p->zBaseline==0 ) SYNTAX("missing hash on B-card"); + if( !hname_validate(p->zBaseline,sz) ){ + SYNTAX("invalid hash on B-card"); + } + p->type = CFTYPE_MANIFEST; break; } + /* ** C ** ** Comment text is fossil-encoded. There may be no more than - ** one C line. C lines are required for manifests and are - ** disallowed on all other control files. + ** 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': { - md5sum_step_text(blob_buffer(&line), blob_size(&line)); - if( p->zComment!=0 ) goto manifest_syntax_error; - if( blob_token(&line, &a1)==0 ) goto manifest_syntax_error; - if( blob_token(&line, &a2)!=0 ) goto manifest_syntax_error; - p->zComment = blob_terminate(&a1); + if( p->zComment!=0 ) SYNTAX("more than one C-card"); + p->zComment = next_token(&x, 0); + if( p->zComment==0 ) SYNTAX("missing comment text on C-card"); defossilize(p->zComment); break; } /* @@ -221,66 +628,137 @@ ** 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': { - char *zDate; - md5sum_step_text(blob_buffer(&line), blob_size(&line)); - if( p->rDate!=0.0 ) goto manifest_syntax_error; - if( blob_token(&line, &a1)==0 ) goto manifest_syntax_error; - if( blob_token(&line, &a2)!=0 ) goto manifest_syntax_error; - zDate = blob_terminate(&a1); - p->rDate = db_double(0.0, "SELECT julianday(%Q)", zDate); + if( p->rDate>0.0 ) SYNTAX("more than one D-card"); + p->rDate = db_double(0.0, "SELECT julianday(%Q)", next_token(&x,0)); + if( p->rDate<=0.0 ) SYNTAX("cannot parse date on D-card"); + break; + } + + /* + ** E + ** + ** 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': { + if( p->rEventDate>0.0 ) SYNTAX("more than one E-card"); + p->rEventDate = db_double(0.0,"SELECT julianday(%Q)", next_token(&x,0)); + if( p->rEventDate<=0.0 ) SYNTAX("malformed date on E-card"); + p->zEventId = next_token(&x, &sz); + if( p->zEventId==0 ) SYNTAX("missing hash on E-card"); + if( !hname_validate(p->zEventId, sz) ){ + SYNTAX("malformed hash on E-card"); + } + p->type = CFTYPE_EVENT; break; } /* - ** F ?? ?? + ** F ?? ?? ?? ** ** 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. */ case 'F': { - char *zName, *zUuid, *zPerm, *zPriorName; - md5sum_step_text(blob_buffer(&line), blob_size(&line)); - if( blob_token(&line, &a1)==0 ) goto manifest_syntax_error; - if( blob_token(&line, &a2)==0 ) goto manifest_syntax_error; - zName = blob_terminate(&a1); - zUuid = blob_terminate(&a2); - blob_token(&line, &a3); - zPerm = blob_terminate(&a3); - if( blob_size(&a2)!=UUID_SIZE ) goto manifest_syntax_error; - if( !validate16(zUuid, UUID_SIZE) ) goto manifest_syntax_error; + char *zName, *zPerm, *zPriorName; + zName = next_token(&x,0); + if( zName==0 ) SYNTAX("missing filename on F-card"); defossilize(zName); - if( !file_is_simple_pathname(zName) ){ - goto manifest_syntax_error; + if( !file_is_simple_pathname_nonstrict(zName) ){ + SYNTAX("F-card filename is not a simple path"); + } + zUuid = next_token(&x, &sz); + if( p->zBaseline==0 || zUuid!=0 ){ + if( zUuid==0 ) SYNTAX("missing hash on F-card"); + if( !hname_validate(zUuid,sz) ){ + SYNTAX("F-card hash invalid"); + } } - blob_token(&line, &a4); - zPriorName = blob_terminate(&a4); - if( zPriorName[0] ){ + zPerm = next_token(&x,0); + zPriorName = next_token(&x,0); + if( zPriorName ){ defossilize(zPriorName); - if( !file_is_simple_pathname(zPriorName) ){ - goto manifest_syntax_error; + if( !file_is_simple_pathname_nonstrict(zPriorName) ){ + SYNTAX("F-card old filename is not a simple path"); } - }else{ - zPriorName = 0; } if( p->nFile>=p->nFileAlloc ){ p->nFileAlloc = p->nFileAlloc*2 + 10; - p->aFile = realloc(p->aFile, p->nFileAlloc*sizeof(p->aFile[0]) ); - if( p->aFile==0 ) fossil_panic("out of memory"); + p->aFile = fossil_realloc(p->aFile, + p->nFileAlloc*sizeof(p->aFile[0]) ); } i = p->nFile++; + if( i>0 && fossil_strcmp(p->aFile[i-1].zName, zName)>=0 ){ + SYNTAX("incorrect F-card sort order"); + } + if( file_is_reserved_name(zName,-1) ){ + /* If reserved names leaked into historical manifests due to + ** slack oversight by older versions of Fossil, simply ignore + ** those files */ + p->nFile--; + break; + } p->aFile[i].zName = zName; p->aFile[i].zUuid = zUuid; p->aFile[i].zPerm = zPerm; p->aFile[i].zPrior = zPriorName; - p->aFile[i].iRename = -1; - if( i>0 && strcmp(p->aFile[i-1].zName, zName)>=0 ){ - goto manifest_syntax_error; + p->type = CFTYPE_MANIFEST; + break; + } + + /* + ** G + ** + ** A G-card identifies the initial root forum post for the thread + ** of which this post is a part. Forum posts only. + */ + case 'G': { + if( p->zThreadRoot!=0 ) SYNTAX("more than one G-card"); + p->zThreadRoot = next_token(&x, &sz); + if( p->zThreadRoot==0 ) SYNTAX("missing hash on G-card"); + if( !hname_validate(p->zThreadRoot,sz) ){ + SYNTAX("Invalid hash on G-card"); + } + p->type = CFTYPE_FORUM; + break; + } + + /* + ** H + ** + ** The title for a forum thread. + */ + case 'H': { + if( p->zThreadTitle!=0 ) SYNTAX("more than one H-card"); + p->zThreadTitle = next_token(&x,0); + if( p->zThreadTitle==0 ) SYNTAX("missing title on H-card"); + defossilize(p->zThreadTitle); + p->type = CFTYPE_FORUM; + break; + } + + /* + ** I + ** + ** A I-card identifies another forum post that the current forum post + ** is in reply to. + */ + case 'I': { + if( p->zInReplyTo!=0 ) SYNTAX("more than one I-card"); + p->zInReplyTo = next_token(&x, &sz); + if( p->zInReplyTo==0 ) SYNTAX("missing hash on I-card"); + if( !hname_validate(p->zInReplyTo,sz) ){ + SYNTAX("Invalid hash on I-card"); } + p->type = CFTYPE_FORUM; break; } /* ** J ?? @@ -290,29 +768,27 @@ ** value. If is omitted then it is understood to be an ** empty string. */ case 'J': { char *zName, *zValue; - md5sum_step_text(blob_buffer(&line), blob_size(&line)); - if( blob_token(&line, &a1)==0 ) goto manifest_syntax_error; - blob_token(&line, &a2); - if( blob_token(&line, &a3)!=0 ) goto manifest_syntax_error; - zName = blob_terminate(&a1); - zValue = blob_terminate(&a2); + zName = next_token(&x,0); + zValue = next_token(&x,0); + if( zName==0 ) SYNTAX("name missing from J-card"); + if( zValue==0 ) zValue = ""; defossilize(zValue); if( p->nField>=p->nFieldAlloc ){ p->nFieldAlloc = p->nFieldAlloc*2 + 10; - p->aField = realloc(p->aField, + p->aField = fossil_realloc(p->aField, p->nFieldAlloc*sizeof(p->aField[0]) ); - if( p->aField==0 ) fossil_panic("out of memory"); } i = p->nField++; p->aField[i].zName = zName; p->aField[i].zValue = zValue; - if( i>0 && strcmp(p->aField[i-1].zName, zName)>=0 ){ - goto manifest_syntax_error; + if( i>0 && fossil_strcmp(p->aField[i-1].zName, zName)>=0 ){ + SYNTAX("incorrect J-card sort order"); } + p->type = CFTYPE_TICKET; break; } /* @@ -320,18 +796,17 @@ ** ** A K-line gives the UUID for the ticket which this control file ** is amending. */ case 'K': { - char *zUuid; - md5sum_step_text(blob_buffer(&line), blob_size(&line)); - if( blob_token(&line, &a1)==0 ) goto manifest_syntax_error; - zUuid = blob_terminate(&a1); - if( blob_size(&a1)!=UUID_SIZE ) goto manifest_syntax_error; - if( !validate16(zUuid, UUID_SIZE) ) goto manifest_syntax_error; - if( p->zTicketUuid!=0 ) goto manifest_syntax_error; - p->zTicketUuid = zUuid; + if( p->zTicketUuid!=0 ) SYNTAX("more than one K-card"); + p->zTicketUuid = next_token(&x, &sz); + if( sz!=HNAME_LEN_SHA1 ) SYNTAX("K-card UUID is the wrong size"); + if( !validate16(p->zTicketUuid, sz) ){ + SYNTAX("invalid K-card UUID"); + } + p->type = CFTYPE_TICKET; break; } /* ** L @@ -338,88 +813,124 @@ ** ** The wiki page title is fossil-encoded. There may be no more than ** one L line. */ case 'L': { - md5sum_step_text(blob_buffer(&line), blob_size(&line)); - if( p->zWikiTitle!=0 ) goto manifest_syntax_error; - if( blob_token(&line, &a1)==0 ) goto manifest_syntax_error; - if( blob_token(&line, &a2)!=0 ) goto manifest_syntax_error; - p->zWikiTitle = blob_terminate(&a1); + if( p->zWikiTitle!=0 ) SYNTAX("more than one L-card"); + p->zWikiTitle = next_token(&x,0); + if( p->zWikiTitle==0 ) SYNTAX("missing title on L-card"); defossilize(p->zWikiTitle); if( !wiki_name_is_wellformed((const unsigned char *)p->zWikiTitle) ){ - goto manifest_syntax_error; + SYNTAX("L-card has malformed wiki name"); } + p->type = CFTYPE_WIKI; break; } /* - ** M + ** M ** - ** An M-line identifies another artifact by its UUID. M-lines + ** An M-line identifies another artifact by its hash. M-lines ** occur in clusters only. */ case 'M': { - char *zUuid; - md5sum_step_text(blob_buffer(&line), blob_size(&line)); - if( blob_token(&line, &a1)==0 ) goto manifest_syntax_error; - zUuid = blob_terminate(&a1); - if( blob_size(&a1)!=UUID_SIZE ) goto manifest_syntax_error; - if( !validate16(zUuid, UUID_SIZE) ) goto manifest_syntax_error; + zUuid = next_token(&x, &sz); + if( zUuid==0 ) SYNTAX("missing hash on M-card"); + if( !hname_validate(zUuid,sz) ){ + SYNTAX("Invalid hash on M-card"); + } if( p->nCChild>=p->nCChildAlloc ){ p->nCChildAlloc = p->nCChildAlloc*2 + 10; - p->azCChild = - realloc(p->azCChild, p->nCChildAlloc*sizeof(p->azCChild[0]) ); - if( p->azCChild==0 ) fossil_panic("out of memory"); + p->azCChild = fossil_realloc(p->azCChild + , p->nCChildAlloc*sizeof(p->azCChild[0]) ); } i = p->nCChild++; p->azCChild[i] = zUuid; - if( i>0 && strcmp(p->azCChild[i-1], zUuid)>=0 ){ - goto manifest_syntax_error; + if( i>0 && fossil_strcmp(p->azCChild[i-1], zUuid)>=0 ){ + SYNTAX("M-card in the wrong order"); } + p->type = CFTYPE_CLUSTER; + break; + } + + /* + ** N + ** + ** An N-line identifies the mimetype of wiki or comment text. + */ + case 'N': { + if( p->zMimetype!=0 ) SYNTAX("more than one N-card"); + p->zMimetype = next_token(&x,0); + if( p->zMimetype==0 ) SYNTAX("missing mimetype on N-card"); + defossilize(p->zMimetype); break; } /* ** P ... ** - ** Specify one or more other artifacts where are the parents of + ** 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. + ** others are parents by merge. Note that the initial empty + ** check-in historically has an empty P-card, so empty P-cards + ** must be accepted. */ case 'P': { - md5sum_step_text(blob_buffer(&line), blob_size(&line)); - while( blob_token(&line, &a1) ){ - char *zUuid; - if( blob_size(&a1)!=UUID_SIZE ) goto manifest_syntax_error; - zUuid = blob_terminate(&a1); - if( !validate16(zUuid, UUID_SIZE) ) goto manifest_syntax_error; + while( (zUuid = next_token(&x, &sz))!=0 ){ + if( !hname_validate(zUuid, sz) ){ + SYNTAX("invalid hash on P-card"); + } if( p->nParent>=p->nParentAlloc ){ p->nParentAlloc = p->nParentAlloc*2 + 5; - p->azParent = realloc(p->azParent, p->nParentAlloc*sizeof(char*)); - if( p->azParent==0 ) fossil_panic("out of memory"); + p->azParent = fossil_realloc(p->azParent, + p->nParentAlloc*sizeof(char*)); } i = p->nParent++; p->azParent[i] = zUuid; } break; } + + /* + ** Q (+|-) ?? + ** + ** Specify one or a range of check-ins that are cherrypicked into + ** this check-in ("+") or backed out of this check-in ("-"). + */ + case 'Q': { + if( (zUuid=next_token(&x, &sz))==0 ) SYNTAX("missing hash on Q-card"); + if( zUuid[0]!='+' && zUuid[0]!='-' ){ + SYNTAX("Q-card does not begin with '+' or '-'"); + } + if( !hname_validate(&zUuid[1], sz-1) ){ + SYNTAX("invalid hash on Q-card"); + } + n = p->nCherrypick; + p->nCherrypick++; + p->aCherrypick = fossil_realloc(p->aCherrypick, + p->nCherrypick*sizeof(p->aCherrypick[0])); + p->aCherrypick[n].zCPTarget = zUuid; + p->aCherrypick[n].zCPBase = zUuid = next_token(&x, &sz); + if( zUuid && !hname_validate(zUuid,sz) ){ + SYNTAX("invalid second hash on Q-card"); + } + p->type = CFTYPE_MANIFEST; + break; + } /* ** R ** - ** Specify the MD5 checksum of the entire baseline in a - ** manifest. + ** Specify the MD5 checksum over the name and content of all files + ** in the manifest. */ case 'R': { - md5sum_step_text(blob_buffer(&line), blob_size(&line)); - if( p->zRepoCksum!=0 ) goto manifest_syntax_error; - if( blob_token(&line, &a1)==0 ) goto manifest_syntax_error; - if( blob_token(&line, &a2)!=0 ) goto manifest_syntax_error; - if( blob_size(&a1)!=32 ) goto manifest_syntax_error; - p->zRepoCksum = blob_terminate(&a1); - if( !validate16(p->zRepoCksum, 32) ) goto manifest_syntax_error; + if( p->zRepoCksum!=0 ) SYNTAX("more than one R-card"); + p->zRepoCksum = next_token(&x, &sz); + if( sz!=32 ) SYNTAX("wrong size cksum on R-card"); + if( !validate16(p->zRepoCksum, 32) ) SYNTAX("malformed R-card cksum"); + p->type = CFTYPE_MANIFEST; break; } /* ** T (+|*|-) ?? @@ -429,58 +940,53 @@ ** 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 . If is "*" then the tag is - ** applied to the current manifest. If is provided then + ** applied to the current manifest. If 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': { - char *zName, *zUuid, *zValue; - md5sum_step_text(blob_buffer(&line), blob_size(&line)); - if( blob_token(&line, &a1)==0 ){ - goto manifest_syntax_error; - } - if( blob_token(&line, &a2)==0 ){ - goto manifest_syntax_error; - } - zName = blob_terminate(&a1); - zUuid = blob_terminate(&a2); - if( blob_token(&line, &a3)==0 ){ - zValue = 0; - }else{ - zValue = blob_terminate(&a3); - defossilize(zValue); - } - if( blob_size(&a2)==UUID_SIZE && validate16(zUuid, UUID_SIZE) ){ - /* A valid uuid */ - }else if( blob_size(&a2)==1 && zUuid[0]=='*' ){ - zUuid = 0; - }else{ - goto manifest_syntax_error; + char *zName, *zValue; + zName = next_token(&x, 0); + if( zName==0 ) SYNTAX("missing name on T-card"); + zUuid = next_token(&x, &sz); + if( zUuid==0 ) SYNTAX("missing artifact hash on T-card"); + zValue = next_token(&x, 0); + if( zValue ) defossilize(zValue); + if( hname_validate(zUuid, sz) ){ + /* A valid artifact hash */ + }else if( sz==1 && zUuid[0]=='*' ){ + zUuid = 0; + nSelfTag++; + }else{ + SYNTAX("malformed artifact hash on T-card"); } defossilize(zName); if( zName[0]!='-' && zName[0]!='+' && zName[0]!='*' ){ - goto manifest_syntax_error; + SYNTAX("T-card name does not begin with '-', '+', or '*'"); } + if( zName[0]=='+' ) nSimpleTag++; if( validate16(&zName[1], strlen(&zName[1])) ){ - /* Do not allow tags whose names look like UUIDs */ - goto manifest_syntax_error; + /* Do not allow tags whose names look like a hash */ + SYNTAX("T-card name looks like a hexadecimal hash"); } if( p->nTag>=p->nTagAlloc ){ p->nTagAlloc = p->nTagAlloc*2 + 10; - p->aTag = realloc(p->aTag, p->nTagAlloc*sizeof(p->aTag[0]) ); - if( p->aTag==0 ) fossil_panic("out of memory"); + p->aTag = fossil_realloc(p->aTag, p->nTagAlloc*sizeof(p->aTag[0]) ); } i = p->nTag++; p->aTag[i].zName = zName; p->aTag[i].zUuid = zUuid; p->aTag[i].zValue = zValue; - if( i>0 && strcmp(p->aTag[i-1].zName, zName)>=0 ){ - goto manifest_syntax_error; + if( i>0 ){ + int c = fossil_strcmp(p->aTag[i-1].zName, zName); + if( c>0 || (c==0 && fossil_strcmp(p->aTag[i-1].zUuid, zUuid)>=0) ){ + SYNTAX("T-card in the wrong order"); + } } break; } /* @@ -489,19 +995,17 @@ ** 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': { - md5sum_step_text(blob_buffer(&line), blob_size(&line)); - if( p->zUser!=0 ) goto manifest_syntax_error; - if( blob_token(&line, &a1)==0 ){ + if( p->zUser!=0 ) SYNTAX("more than one U-card"); + p->zUser = next_token(&x, 0); + if( p->zUser==0 || p->zUser[0]==0 ){ p->zUser = "anonymous"; }else{ - p->zUser = blob_terminate(&a1); defossilize(p->zUser); } - if( blob_token(&line, &a2)!=0 ) goto manifest_syntax_error; break; } /* ** W @@ -509,26 +1013,29 @@ ** The next 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': { - int size; + char *zSize; + unsigned size, oldsize, c; Blob wiki; - md5sum_step_text(blob_buffer(&line), blob_size(&line)); - if( blob_token(&line, &a1)==0 ) goto manifest_syntax_error; - if( blob_token(&line, &a2)!=0 ) goto manifest_syntax_error; - if( !blob_is_int(&a1, &size) ) goto manifest_syntax_error; - if( size<0 ) goto manifest_syntax_error; - if( p->zWiki!=0 ) goto manifest_syntax_error; + zSize = next_token(&x, 0); + if( zSize==0 ) SYNTAX("missing size on W-card"); + if( x.atEol==0 ) SYNTAX("no content after W-card"); + for(oldsize=size=0; (c = zSize[0])>='0' && c<='9'; zSize++){ + size = oldsize*10 + c - '0'; + if( sizezWiki!=0 ) SYNTAX("more than one W-card"); blob_zero(&wiki); - if( blob_extract(pContent, size+1, &wiki)!=size+1 ){ - goto manifest_syntax_error; - } - p->zWiki = blob_buffer(&wiki); - md5sum_step_text(p->zWiki, size+1); - if( p->zWiki[size]!='\n' ) goto manifest_syntax_error; - p->zWiki[size] = 0; + if( (&x.z[size+1])>=x.zEnd )SYNTAX("not enough content after W-card"); + p->zWiki = x.z; + x.z += size; + if( x.z[0]!='\n' ) SYNTAX("W-card content no \\n terminated"); + x.z[0] = 0; + x.z++; break; } /* @@ -541,127 +1048,402 @@ ** 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': { - int rc; - Blob hash; - if( blob_token(&line, &a1)==0 ) goto manifest_syntax_error; - if( blob_token(&line, &a2)!=0 ) goto manifest_syntax_error; - if( blob_size(&a1)!=32 ) goto manifest_syntax_error; - if( !validate16(blob_buffer(&a1), 32) ) goto manifest_syntax_error; - md5sum_finish(&hash); - rc = blob_compare(&hash, &a1); - blob_reset(&hash); - if( rc!=0 ) goto manifest_syntax_error; - seenZ = 1; + zUuid = next_token(&x, &sz); + if( sz!=32 ) SYNTAX("wrong size for Z-card cksum"); + if( !validate16(zUuid, 32) ) SYNTAX("malformed Z-card cksum"); break; } default: { - goto manifest_syntax_error; - } - } - } - if( !seenHeader ) goto manifest_syntax_error; - - if( p->nFile>0 || p->zRepoCksum!=0 ){ - if( p->nCChild>0 ) goto manifest_syntax_error; - if( p->rDate==0.0 ) goto manifest_syntax_error; - if( p->nField>0 ) goto manifest_syntax_error; - if( p->zTicketUuid ) goto manifest_syntax_error; - if( p->zWiki ) goto manifest_syntax_error; - if( p->zWikiTitle ) goto manifest_syntax_error; - if( p->zTicketUuid ) goto manifest_syntax_error; - if( p->zAttachName ) goto manifest_syntax_error; - p->type = CFTYPE_MANIFEST; - }else if( p->nCChild>0 ){ - if( p->rDate>0.0 ) goto manifest_syntax_error; - if( p->zComment!=0 ) goto manifest_syntax_error; - if( p->zUser!=0 ) goto manifest_syntax_error; - if( p->nTag>0 ) goto manifest_syntax_error; - if( p->nParent>0 ) goto manifest_syntax_error; - if( p->nField>0 ) goto manifest_syntax_error; - if( p->zTicketUuid ) goto manifest_syntax_error; - if( p->zWiki ) goto manifest_syntax_error; - if( p->zWikiTitle ) goto manifest_syntax_error; - if( p->zAttachName ) goto manifest_syntax_error; - if( !seenZ ) goto manifest_syntax_error; - p->type = CFTYPE_CLUSTER; - }else if( p->nField>0 ){ - if( p->rDate==0.0 ) goto manifest_syntax_error; - if( p->zWiki ) goto manifest_syntax_error; - if( p->zWikiTitle ) goto manifest_syntax_error; - if( p->nCChild>0 ) goto manifest_syntax_error; - if( p->nTag>0 ) goto manifest_syntax_error; - if( p->zTicketUuid==0 ) goto manifest_syntax_error; - if( p->zUser==0 ) goto manifest_syntax_error; - if( p->zAttachName ) goto manifest_syntax_error; - if( !seenZ ) goto manifest_syntax_error; - p->type = CFTYPE_TICKET; - }else if( p->zWiki!=0 ){ - if( p->rDate==0.0 ) goto manifest_syntax_error; - if( p->nCChild>0 ) goto manifest_syntax_error; - if( p->nTag>0 ) goto manifest_syntax_error; - if( p->zTicketUuid!=0 ) goto manifest_syntax_error; - if( p->zWikiTitle==0 ) goto manifest_syntax_error; - if( p->zAttachName ) goto manifest_syntax_error; - if( !seenZ ) goto manifest_syntax_error; - p->type = CFTYPE_WIKI; - }else if( p->nTag>0 ){ - if( p->rDate<=0.0 ) goto manifest_syntax_error; - if( p->nParent>0 ) goto manifest_syntax_error; - if( p->zWikiTitle ) goto manifest_syntax_error; - if( p->zTicketUuid ) goto manifest_syntax_error; - if( p->zAttachName ) goto manifest_syntax_error; - if( !seenZ ) goto manifest_syntax_error; - p->type = CFTYPE_CONTROL; - }else if( p->zAttachName ){ - if( p->nCChild>0 ) goto manifest_syntax_error; - if( p->rDate==0.0 ) goto manifest_syntax_error; - if( p->zTicketUuid ) goto manifest_syntax_error; - if( p->zWikiTitle ) goto manifest_syntax_error; - if( !seenZ ) goto manifest_syntax_error; - p->type = CFTYPE_ATTACHMENT; - }else{ - if( p->nCChild>0 ) goto manifest_syntax_error; - if( p->rDate<=0.0 ) goto manifest_syntax_error; - if( p->nParent>0 ) goto manifest_syntax_error; - if( p->nField>0 ) goto manifest_syntax_error; - if( p->zTicketUuid ) goto manifest_syntax_error; - if( p->zWiki ) goto manifest_syntax_error; - if( p->zWikiTitle ) goto manifest_syntax_error; - if( p->zTicketUuid ) goto manifest_syntax_error; - if( p->zAttachName ) goto manifest_syntax_error; - p->type = CFTYPE_MANIFEST; - } - md5sum_init(); - return 1; - -manifest_syntax_error: - /*fprintf(stderr, "Manifest error on line %i\n", lineNo);fflush(stderr);*/ - md5sum_init(); - manifest_clear(p); - return 0; + SYNTAX("unrecognized card"); + } + } + } + if( x.ztype==0 ){ + if( p->zComment!=0 || p->nFile>0 || p->nParent>0 ){ + p->type = CFTYPE_MANIFEST; + }else{ + p->type = CFTYPE_CONTROL; + } + } + + /* Verify that no disallowed cards are present for this artifact type */ + m = manifest_card_mask(manifestCardTypes[p->type-1].zAllowed); + if( seenCard & ~m ){ + sqlite3_snprintf(sizeof(zErrBuf), zErrBuf, "%c-card in %s", + maskToType(seenCard & ~m), + azNameOfMType[p->type-1]); + zErr = zErrBuf; + goto manifest_syntax_error; + } + + /* Verify that all required cards are present for this artifact type */ + m = manifest_card_mask(manifestCardTypes[p->type-1].zRequired); + if( ~seenCard & m ){ + sqlite3_snprintf(sizeof(zErrBuf), zErrBuf, "%c-card missing in %s", + maskToType(~seenCard & m), + azNameOfMType[p->type-1]); + zErr = zErrBuf; + goto manifest_syntax_error; + } + + /* Additional checks based on artifact type */ + switch( p->type ){ + case CFTYPE_CONTROL: { + if( nSelfTag ) SYNTAX("self-referential T-card in control artifact"); + break; + } + case CFTYPE_EVENT: { + if( p->nTag!=nSelfTag ){ + SYNTAX("non-self-referential T-card in technote"); + } + if( p->nTag!=nSimpleTag ){ + SYNTAX("T-card with '*' or '-' in technote"); + } + break; + } + case CFTYPE_FORUM: { + if( p->zThreadTitle && p->zInReplyTo ){ + SYNTAX("cannot have I-card and H-card in a forum post"); + } + if( p->nParent>1 ) SYNTAX("too many arguments to P-card"); + break; + } + } + + md5sum_init(); + if( !isRepeat ) g.parseCnt[p->type]++; + return p; + +manifest_syntax_error: + { + char *zUuid = rid_to_uuid(rid); + if( zUuid ){ + blob_appendf(pErr, "artifact [%s] ", zUuid); + fossil_free(zUuid); + } + } + if( zErr ){ + blob_appendf(pErr, "line %d: %s", lineNo, zErr); + }else{ + blob_appendf(pErr, "unknown error on line %d", lineNo); + } + md5sum_init(); + manifest_destroy(p); + return 0; +} + +/* +** Get a manifest given the rid for the control artifact. Return +** a pointer to the manifest on success or NULL if there is a failure. +*/ +Manifest *manifest_get(int rid, int cfType, Blob *pErr){ + Blob content; + Manifest *p; + if( !rid ) return 0; + p = manifest_cache_find(rid); + if( p ){ + if( cfType!=CFTYPE_ANY && cfType!=p->type ){ + manifest_cache_insert(p); + p = 0; + } + return p; + } + content_get(rid, &content); + p = manifest_parse(&content, rid, pErr); + if( p && cfType!=CFTYPE_ANY && cfType!=p->type ){ + manifest_destroy(p); + p = 0; + } + return p; +} + +/* +** Given a check-in name, load and parse the manifest for that check-in. +** Throw a fatal error if anything goes wrong. +*/ +Manifest *manifest_get_by_name(const char *zName, int *pRid){ + int rid; + Manifest *p; + + rid = name_to_typed_rid(zName, "ci"); + if( !is_a_version(rid) ){ + fossil_fatal("no such check-in: %s", zName); + } + if( pRid ) *pRid = rid; + p = manifest_get(rid, CFTYPE_MANIFEST, 0); + if( p==0 ){ + fossil_fatal("cannot parse manifest for check-in: %s", zName); + } + return p; +} + +/* +** The input blob is text that may or may not be a valid Fossil +** control artifact of some kind. This routine returns true if +** the input is a well-formed control artifact and false if it +** is not. +** +** This routine is optimized to return false quickly and with minimal +** work in the common case where the input is some random file. +*/ +int manifest_is_well_formed(const char *zIn, int nIn){ + int i; + int iRes; + Manifest *pManifest; + Blob copy, errmsg; + remove_pgp_signature(&zIn, &nIn); + + /* Check to see that the file begins with a "card" */ + if( nIn<3 ) return 0; + if( zIn[0]<'A' || zIn[0]>'M' || zIn[1]!=' ' ) return 0; + + /* Check to see that the first card is followed by one more card */ + for(i=2; i=nIn-3 ) return 0; + i++; + if( !fossil_isupper(zIn[i]) || zIn[i]3 ) n = atoi(g.argv[3]); + isWF = manifest_is_well_formed(blob_buffer(&b), blob_size(&b)); + fossil_print("manifest_is_well_formed() reports the input %s\n", + isWF ? "is ok" : "contains errors"); + for(i=0; i0 && db_step(&q)==SQLITE_ROW ){ + int id = db_column_int(&q,0); + fossil_print("Checking %d \r", id); + nTest++; + fflush(stdout); + blob_init(&err, 0, 0); + if( bWellFormed ){ + Blob content; + int isWF; + content_get(id, &content); + isWF = manifest_is_well_formed(blob_buffer(&content),blob_size(&content)); + p = manifest_parse(&content, id, &err); + if( isWF && p==0 ){ + fossil_print("%d ERROR: manifest_is_well_formed() reported true " + "but manifest_parse() reports an error: %s\n", + id, blob_str(&err)); + nErr++; + }else if( !isWF && p!=0 ){ + fossil_print("%d ERROR: manifest_is_well_formed() reported false " + "but manifest_parse() found nothing wrong.\n", id); + nErr++; + } + }else{ + p = manifest_get(id, CFTYPE_ANY, &err); + if( p==0 ){ + fossil_print("%d ERROR: %s\n", id, blob_str(&err)); + nErr++; + } + } + blob_reset(&err); + manifest_destroy(p); + } + db_finalize(&q); + fossil_print("%d tests with %d errors\n", nTest, nErr); +} + +/* +** Fetch the baseline associated with the delta-manifest p. +** Return 0 on success. If unable to parse the baseline, +** throw an error. If the baseline is a manifest, throw an +** error if throwError is true, or record that p is an orphan +** and return 1 if throwError is false. +*/ +static int fetch_baseline(Manifest *p, int throwError){ + if( p->zBaseline!=0 && p->pBaseline==0 ){ + int rid = uuid_to_rid(p->zBaseline, 1); + p->pBaseline = manifest_get(rid, CFTYPE_MANIFEST, 0); + if( p->pBaseline==0 ){ + if( !throwError ){ + db_multi_exec( + "INSERT OR IGNORE INTO orphan(rid, baseline) VALUES(%d,%d)", + p->rid, rid + ); + return 1; + } + fossil_fatal("cannot access baseline manifest %S", p->zBaseline); + } + } + return 0; +} + +/* +** Rewind a manifest-file iterator back to the beginning of the manifest. +*/ +void manifest_file_rewind(Manifest *p){ + p->iFile = 0; + fetch_baseline(p, 1); + if( p->pBaseline ){ + p->pBaseline->iFile = 0; + } +} + +/* +** Advance to the next manifest-file. +** +** Return NULL for end-of-records or if there is an error. If an error +** occurs and pErr!=0 then store 1 in *pErr. +*/ +ManifestFile *manifest_file_next( + Manifest *p, + int *pErr +){ + ManifestFile *pOut = 0; + if( pErr ) *pErr = 0; + if( p->pBaseline==0 ){ + /* Manifest p is a baseline-manifest. Just scan down the list + ** of files. */ + if( p->iFilenFile ) pOut = &p->aFile[p->iFile++]; + }else{ + /* Manifest p is a delta-manifest. Scan the baseline but amend the + ** file list in the baseline with changes described by p. + */ + Manifest *pB = p->pBaseline; + int cmp; + while(1){ + if( pB->iFile>=pB->nFile ){ + /* We have used all entries out of the baseline. Return the next + ** entry from the delta. */ + if( p->iFilenFile ) pOut = &p->aFile[p->iFile++]; + break; + }else if( p->iFile>=p->nFile ){ + /* We have used all entries from the delta. Return the next + ** entry from the baseline. */ + if( pB->iFilenFile ) pOut = &pB->aFile[pB->iFile++]; + break; + }else if( (cmp = fossil_strcmp(pB->aFile[pB->iFile].zName, + p->aFile[p->iFile].zName)) < 0 ){ + /* The next baseline entry comes before the next delta entry. + ** So return the baseline entry. */ + pOut = &pB->aFile[pB->iFile++]; + break; + }else if( cmp>0 ){ + /* The next delta entry comes before the next baseline + ** entry so return the delta entry */ + pOut = &p->aFile[p->iFile++]; + break; + }else if( p->aFile[p->iFile].zUuid ){ + /* The next delta entry is a replacement for the next baseline + ** entry. Skip the baseline entry and return the delta entry */ + pB->iFile++; + pOut = &p->aFile[p->iFile++]; + break; + }else{ + /* 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->iFile++; + p->iFile++; + continue; + } + } + } + return pOut; } /* ** Translate a filename into a filename-id (fnid). Create a new fnid ** if no previously exists. @@ -682,204 +1464,663 @@ db_exec(&s1); fnid = db_last_insert_rowid(); } return fnid; } + +/* +** Compute an appropriate mlink.mperm integer for the permission string +** of a file. +*/ +int manifest_file_mperm(const ManifestFile *pFile){ + int mperm = PERM_REG; + if( pFile && pFile->zPerm){ + if( strstr(pFile->zPerm,"x")!=0 ){ + mperm = PERM_EXE; + }else if( strstr(pFile->zPerm,"l")!=0 ){ + mperm = PERM_LNK; + } + } + return mperm; +} /* ** Add a single entry to the mlink table. Also add the filename to ** the filename table if it is not there already. +** +** An mlink entry is always created if isPrimary is true. But if +** isPrimary is false (meaning that pmid is a merge parent of mid) +** then the mlink entry is only created if there is already an mlink +** from primary parent for the same file. */ static void add_one_mlink( + int pmid, /* The parent manifest */ + const char *zFromUuid, /* Artifact hash for content in parent */ int mid, /* The record ID of the manifest */ - const char *zFromUuid, /* UUID for the mlink.pid field */ - const char *zToUuid, /* UUID for the mlink.fid field */ + const char *zToUuid, /* artifact hash for content in child */ const char *zFilename, /* Filename */ - const char *zPrior /* Previous filename. NULL if unchanged */ + const char *zPrior, /* Previous filename. NULL if unchanged */ + int isPublic, /* True if mid is not a private manifest */ + int isPrimary, /* pmid is the primary parent of mid */ + int mperm /* 1: exec, 2: symlink */ ){ int fnid, pfnid, pid, fid; - static Stmt s1; + int doInsert; + static Stmt s1, s2; fnid = filename_to_fnid(zFilename); if( zPrior==0 ){ pfnid = 0; }else{ pfnid = filename_to_fnid(zPrior); } - if( zFromUuid==0 ){ + if( zFromUuid==0 || zFromUuid[0]==0 ){ pid = 0; }else{ pid = uuid_to_rid(zFromUuid, 1); } - if( zToUuid==0 ){ + if( zToUuid==0 || zToUuid[0]==0 ){ fid = 0; }else{ fid = uuid_to_rid(zToUuid, 1); - } - db_static_prepare(&s1, - "INSERT INTO mlink(mid,pid,fid,fnid,pfnid)" - "VALUES(:m,:p,:f,:n,:pfn)" - ); - db_bind_int(&s1, ":m", mid); - db_bind_int(&s1, ":p", pid); - db_bind_int(&s1, ":f", fid); - db_bind_int(&s1, ":n", fnid); - db_bind_int(&s1, ":pfn", pfnid); - db_exec(&s1); + if( isPublic ) content_make_public(fid); + } + if( isPrimary ){ + doInsert = 1; + }else{ + db_static_prepare(&s2, + "SELECT 1 FROM mlink WHERE mid=:m AND fnid=:n AND NOT isaux" + ); + db_bind_int(&s2, ":m", mid); + db_bind_int(&s2, ":n", fnid); + doInsert = db_step(&s2)==SQLITE_ROW; + db_reset(&s2); + } + if( doInsert ){ + db_static_prepare(&s1, + "INSERT INTO mlink(mid,fid,pmid,pid,fnid,pfnid,mperm,isaux)" + "VALUES(:m,:f,:pm,:p,:n,:pfn,:mp,:isaux)" + ); + db_bind_int(&s1, ":m", mid); + db_bind_int(&s1, ":f", fid); + db_bind_int(&s1, ":pm", pmid); + db_bind_int(&s1, ":p", pid); + db_bind_int(&s1, ":n", fnid); + db_bind_int(&s1, ":pfn", pfnid); + db_bind_int(&s1, ":mp", mperm); + db_bind_int(&s1, ":isaux", isPrimary==0); + db_exec(&s1); + } if( pid && fid ){ - content_deltify(pid, fid, 0); + content_deltify(pid, &fid, 1, 0); } } /* -** Locate a file named zName in the aFile[] array of the given -** manifest. We assume that filenames are in sorted order. -** Use a binary search. Return turn the index of the matching -** entry. Or return -1 if not found. +** Do a binary search to find a file in the p->aFile[] array. +** +** As an optimization, guess that the file we seek is at index p->iFile. +** That will usually be the case. If it is not found there, then do the +** actual binary search. +** +** Update p->iFile to be the index of the file that is found. */ -static int find_file_in_manifest(Manifest *p, const char *zName){ +static ManifestFile *manifest_file_seek_base( + Manifest *p, /* Manifest to search */ + const char *zName, /* Name of the file we are looking for */ + int bBest /* 0: exact match only. 1: closest match */ +){ int lwr, upr; int c; int i; + if( p->aFile==0 ){ + return 0; + } lwr = 0; upr = p->nFile - 1; + if( p->iFile>=lwr && p->iFileaFile[p->iFile+1].zName, zName); + if( c==0 ){ + return &p->aFile[++p->iFile]; + }else if( c>0 ){ + upr = p->iFile; + }else{ + lwr = p->iFile+1; + } + } while( lwr<=upr ){ i = (lwr+upr)/2; - c = strcmp(p->aFile[i].zName, zName); + c = fossil_strcmp(p->aFile[i].zName, zName); if( c<0 ){ lwr = i+1; }else if( c>0 ){ upr = i-1; }else{ - return i; + p->iFile = i; + return &p->aFile[i]; + } + } + if( bBest ){ + if( lwr>=p->nFile ) lwr = p->nFile-1; + i = (int)strlen(zName); + if( strncmp(zName, p->aFile[lwr].zName, i)==0 ) return &p->aFile[lwr]; + } + return 0; +} + +/* +** Locate a file named zName in the aFile[] array of the given manifest. +** Return a pointer to the appropriate ManifestFile object. Return NULL +** if not found. +** +** This routine works even if p is a delta-manifest. The pointer +** returned might be to the baseline. +** +** We assume that filenames are in sorted order and use a binary search. +*/ +ManifestFile *manifest_file_seek(Manifest *p, const char *zName, int bBest){ + ManifestFile *pFile; + + pFile = manifest_file_seek_base(p, zName, p->zBaseline ? 0 : bBest); + if( pFile && pFile->zUuid==0 ) return 0; + if( pFile==0 && p->zBaseline ){ + fetch_baseline(p, 1); + pFile = manifest_file_seek_base(p->pBaseline, zName,bBest); + } + return pFile; +} + +/* +** Look for a file in a manifest, taking the case-sensitive option +** into account. If case-sensitive is off, then files in any case +** will match. +*/ +ManifestFile *manifest_file_find(Manifest *p, const char *zName){ + int i; + Manifest *pBase; + if( filenames_are_case_sensitive() ){ + return manifest_file_seek(p, zName, 0); + } + for(i=0; inFile; i++){ + if( fossil_stricmp(zName, p->aFile[i].zName)==0 ){ + return &p->aFile[i]; + } + } + if( p->zBaseline==0 ) return 0; + fetch_baseline(p, 1); + pBase = p->pBaseline; + if( pBase==0 ) return 0; + for(i=0; inFile; i++){ + if( fossil_stricmp(zName, pBase->aFile[i].zName)==0 ){ + return &pBase->aFile[i]; } } - return -1; + return 0; } /* -** Add mlink table entries associated with manifest cid. The -** parent manifest is pid. +** 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 -** and/or name going from pid to cid. +** 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 +** +** 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 void add_mlink(int pid, Manifest *pParent, int cid, Manifest *pChild){ - Manifest other; +static void add_mlink( + int pmid, Manifest *pParent, /* Parent check-in */ + int mid, Manifest *pChild, /* The child check-in */ + int isPrim /* TRUE if pmid is the primary parent of mid */ +){ Blob otherContent; - int i, j; + int otherRid; + int i, rc; + ManifestFile *pChildFile, *pParentFile; + Manifest **ppOther; + static Stmt eq; + int isPublic; /* True if pChild is non-private */ + + /* If mlink table entires are already exist for the pmid-to-mid transition, + ** then abort early doing no work. + */ + db_static_prepare(&eq, "SELECT 1 FROM mlink WHERE mid=:mid AND pmid=:pmid"); + db_bind_int(&eq, ":mid", mid); + db_bind_int(&eq, ":pmid", pmid); + rc = db_step(&eq); + db_reset(&eq); + if( rc==SQLITE_ROW ) return; - if( db_exists("SELECT 1 FROM mlink WHERE mid=%d", cid) ){ - return; - } + /* Compute the value of the missing pParent or pChild parameter. + ** Fetch the baseline check-ins for both. + */ assert( pParent==0 || pChild==0 ); if( pParent==0 ){ - pParent = &other; - content_get(pid, &otherContent); - }else{ - pChild = &other; - content_get(cid, &otherContent); - } - if( blob_size(&otherContent)==0 ) return; - if( manifest_parse(&other, &otherContent)==0 ) return; - content_deltify(pid, cid, 0); - - /* Use the iRename fields to find the cross-linkage between - ** renamed files. */ - for(j=0; jnFile; j++){ - const char *zPrior = pChild->aFile[j].zPrior; - if( zPrior && zPrior[0] ){ - i = find_file_in_manifest(pParent, zPrior); - if( i>=0 ){ - pChild->aFile[j].iRename = i; - pParent->aFile[i].iRename = j; - } - } - } - - /* Construct the mlink entries */ - for(i=j=0; inFile && jnFile; ){ - int c; - if( pParent->aFile[i].iRename>=0 ){ - i++; - }else if( (c = strcmp(pParent->aFile[i].zName, pChild->aFile[j].zName))<0 ){ - add_one_mlink(cid, pParent->aFile[i].zUuid,0,pParent->aFile[i].zName,0); - i++; - }else if( c>0 ){ - int rn = pChild->aFile[j].iRename; - if( rn>=0 ){ - add_one_mlink(cid, pParent->aFile[rn].zUuid, pChild->aFile[j].zUuid, - pChild->aFile[j].zName, pParent->aFile[rn].zName); - }else{ - add_one_mlink(cid, 0, pChild->aFile[j].zUuid, pChild->aFile[j].zName,0); - } - j++; - }else{ - if( strcmp(pParent->aFile[i].zUuid, pChild->aFile[j].zUuid)!=0 ){ - add_one_mlink(cid, pParent->aFile[i].zUuid, pChild->aFile[j].zUuid, - pChild->aFile[j].zName, 0); - } - i++; - j++; - } - } - while( inFile ){ - if( pParent->aFile[i].iRename<0 ){ - add_one_mlink(cid, pParent->aFile[i].zUuid, 0, pParent->aFile[i].zName,0); - } - i++; - } - while( jnFile ){ - int rn = pChild->aFile[j].iRename; - if( rn>=0 ){ - add_one_mlink(cid, pParent->aFile[rn].zUuid, pChild->aFile[j].zUuid, - pChild->aFile[j].zName, pParent->aFile[rn].zName); - }else{ - add_one_mlink(cid, 0, pChild->aFile[j].zUuid, pChild->aFile[j].zName,0); - } - j++; - } - manifest_clear(&other); -} - -/* -** True if manifest_crosslink_begin() has been called but -** manifest_crosslink_end() is still pending. -*/ -static int manifest_crosslink_busy = 0; + ppOther = &pParent; + otherRid = pmid; + }else{ + ppOther = &pChild; + otherRid = mid; + } + if( (*ppOther = manifest_cache_find(otherRid))==0 ){ + content_get(otherRid, &otherContent); + if( blob_size(&otherContent)==0 ) return; + *ppOther = manifest_parse(&otherContent, otherRid, 0); + if( *ppOther==0 ) return; + } + if( fetch_baseline(pParent, 0) || fetch_baseline(pChild, 0) ){ + manifest_destroy(*ppOther); + return; + } + isPublic = !content_is_private(mid); + + /* 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( !isPrim && otherRid==mid + && !db_exists("SELECT 1 FROM blob WHERE uuid=%Q AND size>0", + pChild->azParent[0]) + ){ + manifest_cache_insert(*ppOther); + return; + } + + /* 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->zBaseline==0)==(pChild->zBaseline==0) ){ + content_deltify(pmid, &mid, 1, 0); + }else if( pChild->zBaseline==0 && pParent->zBaseline!=0 ){ + content_deltify(pParent->pBaseline->rid, &mid, 1, 0); + } + + /* Remember all children less than a few seconds younger than their parent, + ** as we might want to fudge the times for those children. + */ + if( pChild->rDaterDate+AGE_FUDGE_WINDOW + && manifest_crosslink_busy + ){ + db_multi_exec( + "INSERT OR REPLACE INTO time_fudge VALUES(%d, %.17g, %d, %.17g);", + pParent->rid, pParent->rDate, pChild->rid, pChild->rDate + ); + } + + /* First look at all files in pChild, ignoring its baseline. This + ** is where most of the changes will be found. + */ + for(i=0, pChildFile=pChild->aFile; inFile; i++, pChildFile++){ + int mperm = manifest_file_mperm(pChildFile); + if( pChildFile->zPrior ){ + pParentFile = manifest_file_seek(pParent, pChildFile->zPrior, 0); + if( pParentFile ){ + /* File with name change */ + add_one_mlink(pmid, pParentFile->zUuid, mid, pChildFile->zUuid, + pChildFile->zName, pChildFile->zPrior, + isPublic, isPrim, mperm); + }else{ + /* File name changed, but the old name is not found in the parent! + ** Treat this like a new file. */ + add_one_mlink(pmid, 0, mid, pChildFile->zUuid, pChildFile->zName, 0, + isPublic, isPrim, mperm); + } + }else{ + pParentFile = manifest_file_seek(pParent, pChildFile->zName, 0); + if( pParentFile==0 ){ + if( pChildFile->zUuid ){ + /* A new file */ + add_one_mlink(pmid, 0, mid, pChildFile->zUuid, pChildFile->zName, 0, + isPublic, isPrim, mperm); + } + }else if( fossil_strcmp(pChildFile->zUuid, pParentFile->zUuid)!=0 + || manifest_file_mperm(pParentFile)!=mperm ){ + /* Changes in file content or permissions */ + add_one_mlink(pmid, pParentFile->zUuid, mid, pChildFile->zUuid, + pChildFile->zName, 0, isPublic, isPrim, mperm); + } + } + } + if( pParent->zBaseline && pChild->zBaseline ){ + /* 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=pParent->aFile; inFile; i++, pParentFile++){ + if( pParentFile->zUuid ){ + pChildFile = manifest_file_seek_base(pChild, pParentFile->zName, 0); + if( pChildFile==0 ){ + /* The child file reverts to baseline. Show this as a change */ + pChildFile = manifest_file_seek(pChild, pParentFile->zName, 0); + if( pChildFile ){ + add_one_mlink(pmid, pParentFile->zUuid, mid, pChildFile->zUuid, + pChildFile->zName, 0, isPublic, isPrim, + manifest_file_mperm(pChildFile)); + } + } + }else{ + pChildFile = manifest_file_seek(pChild, pParentFile->zName, 0); + if( pChildFile ){ + /* File resurrected in the child after having been deleted in + ** the parent. Show this as an added file. */ + add_one_mlink(pmid, 0, mid, pChildFile->zUuid, pChildFile->zName, 0, + isPublic, isPrim, manifest_file_mperm(pChildFile)); + } + } + } + }else if( pChild->zBaseline==0 ){ + /* pChild is a baseline. Look for files that are present in pParent + ** but are missing from pChild and mark them as having been deleted. */ + manifest_file_rewind(pParent); + while( (pParentFile = manifest_file_next(pParent,0))!=0 ){ + pChildFile = manifest_file_seek(pChild, pParentFile->zName, 0); + if( pChildFile==0 && pParentFile->zUuid!=0 ){ + add_one_mlink(pmid, pParentFile->zUuid, mid, 0, pParentFile->zName, 0, + isPublic, isPrim, 0); + } + } + } + manifest_cache_insert(*ppOther); + + /* If pParent is the primary parent of pChild, also run this analysis + ** for all merge parents of pChild + */ + if( isPrim ){ + for(i=1; inParent; i++){ + pmid = uuid_to_rid(pChild->azParent[i], 0); + if( pmid<=0 ) continue; + add_mlink(pmid, 0, mid, pChild, 0); + } + for(i=0; inCherrypick; i++){ + if( pChild->aCherrypick[i].zCPTarget[0]=='+' + && (pmid = uuid_to_rid(pChild->aCherrypick[i].zCPTarget+1, 0))>0 + ){ + add_mlink(pmid, 0, mid, pChild, 0); + } + } + } +} + +/* +** For a check-in with RID "rid" that has nParent parent check-ins given +** by the hashes in azParent[], create all appropriate plink and mlink table +** entries. +** +** The primary parent is the first hash on the azParent[] list. +** +** Return the RID of the primary parent. +*/ +static int manifest_add_checkin_linkages( + int rid, /* The RID of the check-in */ + Manifest *p, /* Manifest for this check-in */ + int nParent, /* Number of parents for this check-in */ + char * const * azParent /* hashes for each parent */ +){ + int i; + int parentid = 0; + char zBaseId[30]; /* Baseline manifest RID for deltas. "NULL" otherwise */ + Stmt q; + int nLink; + + if( p->zBaseline ){ + sqlite3_snprintf(sizeof(zBaseId), zBaseId, "%d", + uuid_to_rid(p->zBaseline,1)); + }else{ + sqlite3_snprintf(sizeof(zBaseId), zBaseId, "NULL"); + } + for(i=0; irDate, zBaseId/*safe-for-%s*/); + if( i==0 ) parentid = pid; + } + add_mlink(parentid, 0, rid, p, 1); + nLink = nParent; + for(i=0; inCherrypick; i++){ + if( p->aCherrypick[i].zCPTarget[0]=='+' ) nLink++; + } + if( nLink>1 ){ + /* Change MLINK.PID from 0 to -1 for files that are added by merge. */ + db_multi_exec( + "UPDATE mlink SET pid=-1" + " WHERE mid=%d" + " AND pid=0" + " AND fnid IN " + " (SELECT fnid FROM mlink WHERE mid=%d GROUP BY fnid" + " HAVING count(*)<%d)", + rid, rid, nLink + ); + } + db_prepare(&q, "SELECT cid, isprim FROM plink WHERE pid=%d", rid); + while( db_step(&q)==SQLITE_ROW ){ + int cid = db_column_int(&q, 0); + int isprim = db_column_int(&q, 1); + add_mlink(rid, p, cid, 0, isprim); + } + db_finalize(&q); + if( nParent==0 ){ + /* For root files (files without parents) add mlink entries + ** showing all content as new. */ + int isPublic = !content_is_private(rid); + for(i=0; inFile; i++){ + add_one_mlink(0, 0, rid, p->aFile[i].zUuid, p->aFile[i].zName, 0, + isPublic, 1, manifest_file_mperm(&p->aFile[i])); + } + } + return parentid; +} + +/* +** There exists a "parent" tag against checkin rid that has value zValue. +** If value is well-formed (meaning that it is a list of hashes), then use +** zValue to reparent check-in rid. +*/ +void manifest_reparent_checkin(int rid, const char *zValue){ + int nParent = 0; + char *zCopy = 0; + char **azParent = 0; + Manifest *p = 0; + int i, j; + int n = (int)strlen(zValue); + int mxParent = (n+1)/(HNAME_MIN+1); + + if( mxParent<1 ) return; + zCopy = fossil_strdup(zValue); + azParent = fossil_malloc( sizeof(azParent[0])*mxParent ); + for(nParent=0, i=0; zCopy[i]; i++){ + char *z = &zCopy[i]; + azParent[nParent++] = z; + if( nParent>mxParent ) goto reparent_abort; + for(j=HNAME_MIN; z[j]>' '; j++){} + if( !hname_validate(z, j) ) goto reparent_abort; + if( z[j]==0 ) break; + z[j] = 0; + i += j; + } + p = manifest_get(rid, CFTYPE_MANIFEST, 0); + if( p!=0 ){ + db_multi_exec( + "DELETE FROM plink WHERE cid=%d;" + "DELETE FROM mlink WHERE mid=%d;", + rid, rid + ); + manifest_add_checkin_linkages(rid,p,nParent,azParent); + manifest_destroy(p); + } +reparent_abort: + fossil_free(azParent); + fossil_free(zCopy); +} /* ** Setup to do multiple manifest_crosslink() calls. -** This is only required if processing ticket changes. +** +** This routine creates TEMP tables for holding information for +** processing that must be deferred until all artifacts have been +** seen at least once. The deferred processing is accomplished +** by the call to manifest_crosslink_end(). */ void manifest_crosslink_begin(void){ assert( manifest_crosslink_busy==0 ); manifest_crosslink_busy = 1; + manifest_create_event_triggers(); db_begin_transaction(); - db_multi_exec("CREATE TEMP TABLE pending_tkt(uuid TEXT UNIQUE)"); + db_multi_exec( + "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 */ + ");" + ); +} + +/* +** Add a new entry to the pending_xlink table. +*/ +static void add_pending_crosslink(char cType, const char *zId){ + assert( manifest_crosslink_busy==1 ); + db_multi_exec( + "INSERT OR IGNORE INTO pending_xlink VALUES('%c%q')", + cType, zId + ); } +#if INTERFACE +/* Timestamps might be adjusted slightly to ensure that check-ins 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 */ + +#endif /* LOCAL_INTERFACE */ + /* ** Finish up a sequence of manifest_crosslink calls. */ -void manifest_crosslink_end(void){ - Stmt q; +int manifest_crosslink_end(int flags){ + Stmt q, u; + int i; + int rc = TH_OK; + int permitHooks = (flags & MC_PERMIT_HOOKS); + const char *zScript = 0; assert( manifest_crosslink_busy==1 ); - db_prepare(&q, "SELECT uuid FROM pending_tkt"); + if( permitHooks ){ + rc = xfer_run_common_script(); + if( rc==TH_OK ){ + zScript = xfer_ticket_code(); + } + } + db_prepare(&q, + "SELECT rid, value FROM tagxref" + " WHERE tagid=%d AND tagtype=1", + TAG_PARENT + ); + while( db_step(&q)==SQLITE_ROW ){ + int rid = db_column_int(&q,0); + const char *zValue = db_column_text(&q,1); + manifest_reparent_checkin(rid, zValue); + } + db_finalize(&q); + db_prepare(&q, "SELECT id FROM pending_xlink"); while( db_step(&q)==SQLITE_ROW ){ - const char *zUuid = db_column_text(&q, 0); - ticket_rebuild_entry(zUuid); + const char *zId = db_column_text(&q, 0); + char cType; + if( zId==0 || zId[0]==0 ) continue; + cType = zId[0]; + zId++; + if( cType=='t' ){ + ticket_rebuild_entry(zId); + if( permitHooks && rc==TH_OK ){ + rc = xfer_run_script(zScript, zId, 0); + } + }else if( cType=='w' ){ + backlink_wiki_refresh(zId); + } + } + db_finalize(&q); + db_multi_exec("DROP TABLE pending_xlink"); + + /* If multiple check-ins happen close together in time, adjust their + ** times by a few milliseconds to make sure they appear in chronological + ** order. + */ + db_prepare(&q, + "UPDATE time_fudge SET m1=m2-:incr WHERE m1>=m2 AND m1zTicketUuid ); if( !isNew ){ for(i=0; inField; i++){ - if( strcmp(pManifest->aField[i].zName, zStatusColumn)==0 ){ + if( fossil_strcmp(pManifest->aField[i].zName, zStatusColumn)==0 ){ zNewStatus = pManifest->aField[i].zValue; } } if( zNewStatus ){ - blob_appendf(&comment, "%h ticket [%.10s]: %s", - zNewStatus, pManifest->zTicketUuid, zTitle + blob_appendf(&comment, "%h ticket [%!S|%S]: %h", + zNewStatus, pManifest->zTicketUuid, pManifest->zTicketUuid, zTitle ); if( pManifest->nField>1 ){ blob_appendf(&comment, " plus %d other change%s", pManifest->nField-1, pManifest->nField==2 ? "" : "s"); } - blob_appendf(&brief, "%h ticket [%.10s].", - zNewStatus, pManifest->zTicketUuid); + blob_appendf(&brief, "%h ticket [%!S|%S].", + zNewStatus, pManifest->zTicketUuid, pManifest->zTicketUuid); }else{ - zNewStatus = db_text("unknown", - "SELECT %s FROM ticket WHERE tkt_uuid='%s'", + zNewStatus = db_text("unknown", + "SELECT \"%w\" FROM ticket WHERE tkt_uuid=%Q", zStatusColumn, pManifest->zTicketUuid ); - blob_appendf(&comment, "Ticket [%.10s] %s status still %h with " + blob_appendf(&comment, "Ticket [%!S|%S] %h status still %h with " "%d other change%s", - pManifest->zTicketUuid, zTitle, zNewStatus, pManifest->nField, - pManifest->nField==1 ? "" : "s" + pManifest->zTicketUuid, pManifest->zTicketUuid, zTitle, zNewStatus, + pManifest->nField, pManifest->nField==1 ? "" : "s" ); - free(zNewStatus); - blob_appendf(&brief, "Ticket [%.10s]: %d change%s", - pManifest->zTicketUuid, pManifest->nField, + fossil_free(zNewStatus); + blob_appendf(&brief, "Ticket [%!S|%S]: %d change%s", + pManifest->zTicketUuid, pManifest->zTicketUuid, pManifest->nField, pManifest->nField==1 ? "" : "s" ); } }else{ - blob_appendf(&comment, "New ticket [%.10s] %h.", - pManifest->zTicketUuid, zTitle + blob_appendf(&comment, "New ticket [%!S|%S] %h.", + pManifest->zTicketUuid, pManifest->zTicketUuid, zTitle ); - blob_appendf(&brief, "New ticket [%.10s].", pManifest->zTicketUuid); + blob_appendf(&brief, "New ticket [%!S|%S].", pManifest->zTicketUuid, + pManifest->zTicketUuid); } - free(zTitle); + fossil_free(zTitle); + manifest_create_event_triggers(); db_multi_exec( "REPLACE INTO event(type,tagid,mtime,objid,user,comment,brief)" "VALUES('t',%d,%.17g,%d,%Q,%Q,%Q)", tktTagId, pManifest->rDate, rid, pManifest->zUser, blob_str(&comment), blob_str(&brief) ); blob_reset(&comment); blob_reset(&brief); } + +/* +** Add an extra line of text to the end of a manifest to prevent it being +** recognized as a valid manifest. +** +** This routine is called prior to writing out the text of a manifest as +** the "manifest" file in the root of a repository when +** "fossil setting manifest on" is enabled. That way, if the files of +** the project are imported into a different Fossil project, the manifest +** file will not be interpreted as a control artifact in that other project. +** +** Normally it is sufficient to simply append the extra line of text. +** However, if the manifest is PGP signed then the extra line has to be +** inserted before the PGP signature (thus invalidating the signature). +*/ +void sterilize_manifest(Blob *p, int eType){ + char *z, *zOrig; + int n, nOrig; + static const char zExtraLine[] = + "# Remove this line to create a well-formed Fossil %s.\n"; + const char *zType = eType==CFTYPE_MANIFEST ? "manifest" : "control artifact"; + + z = zOrig = blob_materialize(p); + n = nOrig = blob_size(p); + remove_pgp_signature((const char **)&z, &n); + if( z==zOrig ){ + blob_appendf(p, zExtraLine/*works-like:"%s"*/, zType); + }else{ + int iEnd; + Blob copy; + memcpy(©, p, sizeof(copy)); + blob_init(p, 0, 0); + iEnd = (int)(&z[n] - zOrig); + blob_append(p, zOrig, iEnd); + blob_appendf(p, zExtraLine/*works-like:"%s"*/, zType); + blob_append(p, &zOrig[iEnd], -1); + blob_zero(©); + } +} + +/* +** This is the comparison function used to sort the tag array. +*/ +static int tag_compare(const void *a, const void *b){ + struct TagType *pA = (struct TagType*)a; + struct TagType *pB = (struct TagType*)b; + int c; + c = fossil_strcmp(pA->zUuid, pB->zUuid); + if( c==0 ){ + c = fossil_strcmp(pA->zName, pB->zName); + } + return c; +} + +/* +** 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 +** p->nParent==0. +*/ +static void manifest_add_fwt_plink(int rid, Manifest *p){ + int i; + int parentId = 0; + assert(p->type==CFTYPE_WIKI || + p->type==CFTYPE_FORUM || + p->type==CFTYPE_EVENT); + for(i=0; inParent; ++i){ + int const pid = uuid_to_rid(p->azParent[i], 1); + if(0==i){ + parentId = pid; + } + db_multi_exec( + "INSERT OR IGNORE INTO plink" + "(pid, cid, isprim, mtime, baseid)" + "VALUES(%d, %d, %d, %.17g, NULL)", + pid, rid, i==0, p->rDate); + } + if(parentId){ + tag_propagate_all(parentId); + } +} /* ** Scan artifact rid/pContent to see if it is a control artifact of -** any key: +** any type: ** ** * Manifest ** * Control ** * Wiki Page ** * Ticket Change ** * Cluster +** * Attachment +** * Event +** * Forum post ** ** If the input is a control artifact, then make appropriate entries ** in the auxiliary tables of the database in order to crosslink the ** artifact. ** -** If global variable g.xlinkClusterOnly is true, then ignore all +** If global variable g.xlinkClusterOnly is true, then ignore all ** control artifacts other than clusters. +** +** This routine always resets the pContent blob before returning. ** ** Historical note: This routine original processed manifests only. ** Processing for other control artifacts was added later. The name ** of the routine, "manifest_crosslink", and the name of this source ** file, is a legacy of its original use. */ -int manifest_crosslink(int rid, Blob *pContent){ - int i; - Manifest m; - Stmt q; +int manifest_crosslink(int rid, Blob *pContent, int flags){ + int i, rc = TH_OK; + Manifest *p; int parentid = 0; + int permitHooks = (flags & MC_PERMIT_HOOKS); + const char *zScript = 0; + const char *zUuid = 0; - if( manifest_parse(&m, pContent)==0 ){ + if( g.fSqlTrace ){ + fossil_trace("-- manifest_crosslink(%d)\n", rid); + } + manifest_create_event_triggers(); + if( (p = manifest_cache_find(rid))!=0 ){ + blob_reset(pContent); + }else if( (p = manifest_parse(pContent, rid, 0))==0 ){ + assert( blob_is_reset(pContent) || pContent==0 ); + if( (flags & MC_NO_ERRORS)==0 ){ + char * zErrUuid = db_text(0, "SELECT uuid FROM blob WHERE rid=%d",rid); + fossil_error(1, "syntax error in manifest [%S]", zErrUuid); + fossil_free(zErrUuid); + } + return 0; + } + if( g.xlinkClusterOnly && p->type!=CFTYPE_CLUSTER ){ + manifest_destroy(p); + assert( blob_is_reset(pContent) ); + if( (flags & MC_NO_ERRORS)==0 ) fossil_error(1, "no manifest"); return 0; } - if( g.xlinkClusterOnly && m.type!=CFTYPE_CLUSTER ){ - manifest_clear(&m); + if( p->type==CFTYPE_MANIFEST && fetch_baseline(p, 0) ){ + manifest_destroy(p); + assert( blob_is_reset(pContent) ); + if( (flags & MC_NO_ERRORS)==0 ){ + fossil_error(1, "cannot fetch baseline for manifest [%S]", + db_text(0, "SELECT uuid FROM blob WHERE rid=%d",rid)); + } return 0; } db_begin_transaction(); - if( m.type==CFTYPE_MANIFEST ){ + if( p->type==CFTYPE_MANIFEST ){ + if( permitHooks ){ + zScript = xfer_commit_code(); + zUuid = db_text(0, "SELECT uuid FROM blob WHERE rid=%d", rid); + } + if( p->nCherrypick && db_table_exists("repository","cherrypick") ){ + int i; + for(i=0; inCherrypick; i++){ + db_multi_exec( + "REPLACE INTO cherrypick(parentid,childid,isExclude)" + " SELECT rid, %d, %d FROM blob WHERE uuid=%Q", + rid, p->aCherrypick[i].zCPTarget[0]=='-', + p->aCherrypick[i].zCPTarget+1 + ); + } + } if( !db_exists("SELECT 1 FROM mlink WHERE mid=%d", rid) ){ char *zCom; - for(i=0; inParent,p->azParent); + search_doc_touch('c', rid, 0); + assert( manifest_event_triggers_are_enabled ); + zCom = db_text(0, "REPLACE INTO event(type,mtime,objid,user,comment," - "bgcolor,euser,ecomment)" + "bgcolor,euser,ecomment,omtime)" "VALUES('ci'," " coalesce(" " (SELECT julianday(value) FROM tagxref WHERE tagid=%d AND rid=%d)," " %.17g" " )," " %d,%Q,%Q," " (SELECT value FROM tagxref WHERE tagid=%d AND rid=%d AND tagtype>0)," " (SELECT value FROM tagxref WHERE tagid=%d AND rid=%d)," - " (SELECT value FROM tagxref WHERE tagid=%d AND rid=%d));", - TAG_DATE, rid, m.rDate, - rid, m.zUser, m.zComment, + " (SELECT value FROM tagxref WHERE tagid=%d AND rid=%d),%.17g)" + "RETURNING coalesce(ecomment,comment);", + TAG_DATE, rid, p->rDate, + rid, p->zUser, p->zComment, TAG_BGCOLOR, rid, TAG_USER, rid, - TAG_COMMENT, rid + TAG_COMMENT, rid, p->rDate ); - zCom = db_text(0, "SELECT coalesce(ecomment, comment) FROM event" - " WHERE rowid=last_insert_rowid()"); - wiki_extract_links(zCom, rid, 0, m.rDate, 1, WIKI_INLINE); - free(zCom); + backlink_extract(zCom, 0, rid, BKLNK_COMMENT, p->rDate, 1); + fossil_free(zCom); + + /* 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( p->zBaseline!=0 ){ + static int once = 1; + if( once ){ + db_set_int("seen-delta-manifest", 1, 0); + once = 0; + } + } } } - if( m.type==CFTYPE_CLUSTER ){ - tag_insert("cluster", 1, 0, rid, m.rDate, rid); - for(i=0; itype==CFTYPE_CLUSTER ){ + static Stmt del1; + tag_insert("cluster", 1, 0, rid, p->rDate, rid); + db_static_prepare(&del1, "DELETE FROM unclustered WHERE rid=:rid"); + for(i=0; inCChild; i++){ int mid; - mid = uuid_to_rid(m.azCChild[i], 1); + mid = uuid_to_rid(p->azCChild[i], 1); if( mid>0 ){ - db_multi_exec("DELETE FROM unclustered WHERE rid=%d", mid); + db_bind_int(&del1, ":rid", mid); + db_step(&del1); + db_reset(&del1); } } } - if( m.type==CFTYPE_CONTROL || m.type==CFTYPE_MANIFEST ){ - for(i=0; itype==CFTYPE_CONTROL + || p->type==CFTYPE_MANIFEST + || p->type==CFTYPE_EVENT + ){ + for(i=0; inTag; i++){ int tid; int type; - if( m.aTag[i].zUuid ){ - tid = uuid_to_rid(m.aTag[i].zUuid, 1); + if( p->aTag[i].zUuid ){ + tid = uuid_to_rid(p->aTag[i].zUuid, 1); }else{ tid = rid; } if( tid ){ - switch( m.aTag[i].zName[0] ){ - case '-': type = 0; break; /* Cancel prior occurances */ + switch( p->aTag[i].zName[0] ){ + case '-': type = 0; break; /* Cancel prior occurrences */ case '+': type = 1; break; /* Apply to target only */ case '*': type = 2; break; /* Propagate to descendants */ default: - fossil_fatal("unknown tag type in manifest: %s", m.aTag); + fossil_error(1, "unknown tag type in manifest: %s", p->aTag); + manifest_destroy(p); return 0; } - tag_insert(&m.aTag[i].zName[1], type, m.aTag[i].zValue, - rid, m.rDate, tid); + tag_insert(&p->aTag[i].zName[1], type, p->aTag[i].zValue, + rid, p->rDate, tid); } } if( parentid ){ tag_propagate_all(parentid); } } - if( m.type==CFTYPE_WIKI ){ - char *zTag = mprintf("wiki-%s", m.zWikiTitle); + if(p->type==CFTYPE_WIKI || p->type==CFTYPE_FORUM + || p->type==CFTYPE_EVENT){ + manifest_add_fwt_plink(rid, p); + } + if( p->type==CFTYPE_WIKI ){ + char *zTag = mprintf("wiki-%s", p->zWikiTitle); + int prior = 0; + char cPrefix; + int nWiki; + char zLength[40]; + + while( fossil_isspace(p->zWiki[0]) ) p->zWiki++; + nWiki = strlen(p->zWiki); + sqlite3_snprintf(sizeof(zLength), zLength, "%d", nWiki); + tag_insert(zTag, 1, zLength, rid, p->rDate, rid); + fossil_free(zTag); + if(p->nParent){ + prior = fast_uuid_to_rid(p->azParent[0]); + } + if( prior ){ + content_deltify(prior, &rid, 1, 0); + } + if( nWiki<=0 ){ + cPrefix = '-'; + }else if( !prior ){ + cPrefix = '+'; + }else{ + cPrefix = ':'; + } + search_doc_touch('w',rid,p->zWikiTitle); + if( manifest_crosslink_busy ){ + add_pending_crosslink('w',p->zWikiTitle); + }else{ + backlink_wiki_refresh(p->zWikiTitle); + } + assert( manifest_event_triggers_are_enabled ); + db_multi_exec( + "REPLACE INTO event(type,mtime,objid,user,comment)" + "VALUES('w',%.17g,%d,%Q,'%c%q');", + p->rDate, rid, p->zUser, cPrefix, p->zWikiTitle + ); + } + if( p->type==CFTYPE_EVENT ){ + char *zTag = mprintf("event-%s", p->zEventId); int tagid = tag_findid(zTag, 1); - int prior; - char *zComment; + int prior = 0, subsequent; int nWiki; char zLength[40]; - while( isspace(m.zWiki[0]) ) m.zWiki++; - nWiki = strlen(m.zWiki); + Stmt qatt; + while( fossil_isspace(p->zWiki[0]) ) p->zWiki++; + nWiki = strlen(p->zWiki); sqlite3_snprintf(sizeof(zLength), zLength, "%d", nWiki); - tag_insert(zTag, 1, zLength, rid, m.rDate, rid); - free(zTag); - prior = db_int(0, + tag_insert(zTag, 1, zLength, rid, p->rDate, rid); + fossil_free(zTag); + if(p->nParent){ + prior = fast_uuid_to_rid(p->azParent[0]); + } + subsequent = db_int(0, + /* BUG: this check is only correct if subsequent + version has already been crosslinked. */ "SELECT rid FROM tagxref" - " WHERE tagid=%d AND mtime<%.17g" - " ORDER BY mtime DESC", - tagid, m.rDate + " WHERE tagid=%d AND mtime>=%.17g AND rid!=%d" + " ORDER BY mtime", + tagid, p->rDate, rid ); if( prior ){ - content_deltify(prior, rid, 0); + content_deltify(prior, &rid, 1, 0); + if( !subsequent ){ + db_multi_exec( + "DELETE FROM event" + " WHERE type='e'" + " AND tagid=%d" + " AND objid IN (SELECT rid FROM tagxref WHERE tagid=%d)", + tagid, tagid + ); + } } - if( nWiki>0 ){ - zComment = mprintf("Changes to wiki page [%h]", m.zWikiTitle); + if( subsequent ){ + content_deltify(rid, &subsequent, 1, 0); }else{ - zComment = mprintf("Deleted wiki page [%h]", m.zWikiTitle); - } - db_multi_exec( - "REPLACE INTO event(type,mtime,objid,user,comment," - " bgcolor,euser,ecomment)" - "VALUES('w',%.17g,%d,%Q,%Q," - " (SELECT value FROM tagxref WHERE tagid=%d AND rid=%d AND tagtype>1)," - " (SELECT value FROM tagxref WHERE tagid=%d AND rid=%d)," - " (SELECT value FROM tagxref WHERE tagid=%d AND rid=%d));", - m.rDate, rid, m.zUser, zComment, - TAG_BGCOLOR, rid, - TAG_BGCOLOR, rid, - TAG_USER, rid, - TAG_COMMENT, rid + search_doc_touch('e',rid,0); + assert( manifest_event_triggers_are_enabled ); + db_multi_exec( + "REPLACE INTO event(type,mtime,objid,tagid,user,comment,bgcolor)" + "VALUES('e',%.17g,%d,%d,%Q,%Q," + " (SELECT value FROM tagxref WHERE tagid=%d AND rid=%d));", + p->rEventDate, rid, tagid, p->zUser, p->zComment, + TAG_BGCOLOR, rid + ); + } + /* Locate and update comment for any attachments */ + db_prepare(&qatt, + "SELECT attachid, src, target, filename FROM attachment" + " WHERE target=%Q", + p->zEventId ); - free(zComment); + while( db_step(&qatt)==SQLITE_ROW ){ + const char *zAttachId = db_column_text(&qatt, 0); + const char *zSrc = db_column_text(&qatt, 1); + const char *zTarget = db_column_text(&qatt, 2); + const char *zName = db_column_text(&qatt, 3); + const char isAdd = (zSrc && zSrc[0]) ? 1 : 0; + char *zComment; + if( isAdd ){ + zComment = mprintf( + "Add attachment [/artifact/%!S|%h] to" + " tech note [/technote/%!S|%S]", + zSrc, zName, zTarget, zTarget); + }else{ + zComment = mprintf( + "Delete attachment \"%h\" from" + " tech note [/technote/%!S|%S]", + zName, zTarget, zTarget); + } + db_multi_exec("UPDATE event SET comment=%Q, type='e'" + " WHERE objid=%Q", + zComment, zAttachId); + fossil_free(zComment); + } + db_finalize(&qatt); } - if( m.type==CFTYPE_TICKET ){ + if( p->type==CFTYPE_TICKET ){ char *zTag; - + Stmt qatt; assert( manifest_crosslink_busy==1 ); - zTag = mprintf("tkt-%s", m.zTicketUuid); - tag_insert(zTag, 1, 0, rid, m.rDate, rid); - free(zTag); - db_multi_exec("INSERT OR IGNORE INTO pending_tkt VALUES(%Q)", - m.zTicketUuid); + zTag = mprintf("tkt-%s", p->zTicketUuid); + tag_insert(zTag, 1, 0, rid, p->rDate, rid); + fossil_free(zTag); + add_pending_crosslink('t',p->zTicketUuid); + /* Locate and update comment for any attachments */ + db_prepare(&qatt, + "SELECT attachid, src, target, filename FROM attachment" + " WHERE target=%Q", + p->zTicketUuid + ); + while( db_step(&qatt)==SQLITE_ROW ){ + const char *zAttachId = db_column_text(&qatt, 0); + const char *zSrc = db_column_text(&qatt, 1); + const char *zTarget = db_column_text(&qatt, 2); + const char *zName = db_column_text(&qatt, 3); + const char isAdd = (zSrc && zSrc[0]) ? 1 : 0; + char *zComment; + if( isAdd ){ + zComment = mprintf( + "Add attachment [/artifact/%!S|%h] to ticket [%!S|%S]", + zSrc, zName, zTarget, zTarget); + }else{ + zComment = mprintf("Delete attachment \"%h\" from ticket [%!S|%S]", + zName, zTarget, zTarget); + } + db_multi_exec("UPDATE event SET comment=%Q, type='t'" + " WHERE objid=%Q", + zComment, zAttachId); + fossil_free(zComment); + } + db_finalize(&qatt); } - if( m.type==CFTYPE_ATTACHMENT ){ + if( p->type==CFTYPE_ATTACHMENT ){ + char *zComment = 0; + const char isAdd = (p->zAttachSrc && p->zAttachSrc[0]) ? 1 : 0; + /* We assume that we're attaching to a wiki page until we + ** prove otherwise (which could on a later artifact if we + ** process the attachment artifact before the artifact to + ** which it is attached!) */ + char attachToType = 'w'; + if( fossil_is_artifact_hash(p->zAttachTarget) ){ + if( db_exists("SELECT 1 FROM tag WHERE tagname='tkt-%q'", + p->zAttachTarget) + ){ + attachToType = 't'; /* Attaching to known ticket */ + }else if( db_exists("SELECT 1 FROM tag WHERE tagname='event-%q'", + p->zAttachTarget) + ){ + attachToType = 'e'; /* Attaching to known tech note */ + } + } db_multi_exec( "INSERT INTO attachment(attachid, mtime, src, target," - "filename, comment, user)" + "filename, comment, user)" "VALUES(%d,%.17g,%Q,%Q,%Q,%Q,%Q);", - rid, m.rDate, m.zAttachSrc, m.zAttachTarget, m.zAttachName, - (m.zComment ? m.zComment : ""), m.zUser + rid, p->rDate, p->zAttachSrc, p->zAttachTarget, p->zAttachName, + (p->zComment ? p->zComment : ""), p->zUser ); db_multi_exec( "UPDATE attachment SET isLatest = (mtime==" "(SELECT max(mtime) FROM attachment" " WHERE target=%Q AND filename=%Q))" " WHERE target=%Q AND filename=%Q", - m.zAttachTarget, m.zAttachName, - m.zAttachTarget, m.zAttachName + p->zAttachTarget, p->zAttachName, + p->zAttachTarget, p->zAttachName ); - if( strlen(m.zAttachTarget)!=UUID_SIZE - || !validate16(m.zAttachTarget, UUID_SIZE) - ){ - char *zComment; - if( m.zAttachSrc && m.zAttachSrc[0] ){ - zComment = mprintf("Add attachment \"%h\" to wiki page [%h]", - m.zAttachName, m.zAttachTarget); + if( 'w' == attachToType ){ + if( isAdd ){ + zComment = mprintf( + "Add attachment [/artifact/%!S|%h] to wiki page [%h]", + p->zAttachSrc, p->zAttachName, p->zAttachTarget); }else{ zComment = mprintf("Delete attachment \"%h\" from wiki page [%h]", - m.zAttachName, m.zAttachTarget); - } - db_multi_exec( - "REPLACE INTO event(type,mtime,objid,user,comment)" - "VALUES('w',%.17g,%d,%Q,%Q)", - m.rDate, rid, m.zUser, zComment - ); - free(zComment); - }else{ - char *zComment; - if( m.zAttachSrc && m.zAttachSrc[0] ){ - zComment = mprintf("Add attachment \"%h\" to ticket [%.10s]", - m.zAttachName, m.zAttachTarget); - }else{ - zComment = mprintf("Delete attachment \"%h\" from ticket [%.10s]", - m.zAttachName, m.zAttachTarget); - } - db_multi_exec( - "REPLACE INTO event(type,mtime,objid,user,comment)" - "VALUES('t',%.17g,%d,%Q,%Q)", - m.rDate, rid, m.zUser, zComment - ); - free(zComment); - } - } - db_end_transaction(0); - manifest_clear(&m); - return 1; + p->zAttachName, p->zAttachTarget); + } + }else if( 'e' == attachToType ){ + if( isAdd ){ + zComment = mprintf( + "Add attachment [/artifact/%!S|%h] to tech note [/technote/%!S|%S]", + p->zAttachSrc, p->zAttachName, p->zAttachTarget, p->zAttachTarget); + }else{ + zComment = mprintf( + "Delete attachment \"/artifact/%!S|%h\" from" + " tech note [/technote/%!S|%S]", + p->zAttachName, p->zAttachName, + p->zAttachTarget,p->zAttachTarget); + } + }else{ + if( isAdd ){ + zComment = mprintf( + "Add attachment [/artifact/%!S|%h] to ticket [%!S|%S]", + p->zAttachSrc, p->zAttachName, p->zAttachTarget, p->zAttachTarget); + }else{ + zComment = mprintf("Delete attachment \"%h\" from ticket [%!S|%S]", + p->zAttachName, p->zAttachTarget, p->zAttachTarget); + } + } + assert( manifest_event_triggers_are_enabled ); + db_multi_exec( + "REPLACE INTO event(type,mtime,objid,user,comment)" + "VALUES('%c',%.17g,%d,%Q,%Q)", + attachToType, p->rDate, rid, p->zUser, zComment + ); + fossil_free(zComment); + } + if( p->type==CFTYPE_CONTROL ){ + Blob comment; + int i; + const char *zName; + const char *zValue; + const char *zTagUuid; + int branchMove = 0; + blob_zero(&comment); + if( p->zComment ){ + blob_appendf(&comment, " %s.", p->zComment); + } + /* Next loop expects tags to be sorted on hash, so sort it. */ + qsort(p->aTag, p->nTag, sizeof(p->aTag[0]), tag_compare); + for(i=0; inTag; i++){ + zTagUuid = p->aTag[i].zUuid; + if( !zTagUuid ) continue; + if( i==0 || fossil_strcmp(zTagUuid, p->aTag[i-1].zUuid)!=0 ){ + blob_appendf(&comment, + " Edit [%!S|%S]:", + zTagUuid, zTagUuid); + branchMove = 0; + if( permitHooks && db_exists("SELECT 1 FROM event, blob" + " WHERE event.type='ci' AND event.objid=blob.rid" + " AND blob.uuid=%Q", zTagUuid) ){ + zScript = xfer_commit_code(); + zUuid = zTagUuid; + } + } + zName = p->aTag[i].zName; + zValue = p->aTag[i].zValue; + if( strcmp(zName, "*branch")==0 ){ + blob_appendf(&comment, + " Move to branch [/timeline?r=%h&nd&dp=%!S&unhide | %h].", + zValue, zTagUuid, zValue); + branchMove = 1; + continue; + }else if( strcmp(zName, "*bgcolor")==0 ){ + blob_appendf(&comment, + " Change branch background color to \"%h\".", zValue); + continue; + }else if( strcmp(zName, "+bgcolor")==0 ){ + blob_appendf(&comment, + " Change background color to \"%h\".", zValue); + continue; + }else if( strcmp(zName, "-bgcolor")==0 ){ + blob_appendf(&comment, " Cancel background color"); + }else if( strcmp(zName, "+comment")==0 ){ + blob_appendf(&comment, " Edit check-in comment."); + continue; + }else if( strcmp(zName, "+user")==0 ){ + blob_appendf(&comment, " Change user to \"%h\".", zValue); + continue; + }else if( strcmp(zName, "+date")==0 ){ + blob_appendf(&comment, " Timestamp %h.", zValue); + continue; + }else if( memcmp(zName, "-sym-",5)==0 ){ + if( !branchMove ){ + blob_appendf(&comment, " Cancel tag \"%h\"", &zName[5]); + }else{ + continue; + } + }else if( memcmp(zName, "*sym-",5)==0 ){ + if( !branchMove ){ + blob_appendf(&comment, " Add propagating tag \"%h\"", &zName[5]); + }else{ + continue; + } + }else if( memcmp(zName, "+sym-",5)==0 ){ + blob_appendf(&comment, " Add tag \"%h\"", &zName[5]); + }else if( strcmp(zName, "+closed")==0 ){ + blob_append(&comment, " Mark \"Closed\"", -1); + }else if( strcmp(zName, "-closed")==0 ){ + blob_append(&comment, " Remove the \"Closed\" mark", -1); + }else { + if( zName[0]=='-' ){ + blob_appendf(&comment, " Cancel \"%h\"", &zName[1]); + }else if( zName[0]=='+' ){ + blob_appendf(&comment, " Add \"%h\"", &zName[1]); + }else{ + blob_appendf(&comment, " Add propagating \"%h\"", &zName[1]); + } + if( zValue && zValue[0] ){ + blob_appendf(&comment, " with value \"%h\".", zValue); + }else{ + blob_appendf(&comment, "."); + } + continue; + } + if( zValue && zValue[0] ){ + blob_appendf(&comment, " with note \"%h\".", zValue); + }else{ + blob_appendf(&comment, "."); + } + } + /*blob_appendf(&comment, " [[/info/%S | details]]");*/ + if( blob_size(&comment)==0 ) blob_append(&comment, " ", 1); + assert( manifest_event_triggers_are_enabled ); + db_multi_exec( + "REPLACE INTO event(type,mtime,objid,user,comment)" + "VALUES('g',%.17g,%d,%Q,%Q)", + p->rDate, rid, p->zUser, blob_str(&comment)+1 + ); + blob_reset(&comment); + } + if( p->type==CFTYPE_FORUM ){ + int froot, fprev, firt; + char *zFType; + char *zTitle; + schema_forum(); + search_doc_touch('f', rid, 0); + froot = p->zThreadRoot ? uuid_to_rid(p->zThreadRoot, 1) : rid; + fprev = p->nParent ? uuid_to_rid(p->azParent[0],1) : 0; + firt = p->zInReplyTo ? uuid_to_rid(p->zInReplyTo,1) : 0; + db_multi_exec( + "REPLACE INTO forumpost(fpid,froot,fprev,firt,fmtime)" + "VALUES(%d,%d,nullif(%d,0),nullif(%d,0),%.17g)", + p->rid, froot, fprev, firt, p->rDate + ); + if( firt==0 ){ + /* This is the start of a new thread, either the initial entry + ** or an edit of the initial entry. */ + zTitle = p->zThreadTitle; + if( zTitle==0 || zTitle[0]==0 ){ + zTitle = "(Deleted)"; + } + zFType = fprev ? "Edit" : "Post"; + assert( manifest_event_triggers_are_enabled ); + db_multi_exec( + "REPLACE INTO event(type,mtime,objid,user,comment)" + "VALUES('f',%.17g,%d,%Q,'%q: %q')", + p->rDate, rid, p->zUser, zFType, zTitle + ); + /* + ** If this edit is the most recent, then make it the title for + ** all other entries for the same thread + */ + if( !db_exists("SELECT 1 FROM forumpost WHERE froot=%d AND firt=0" + " AND fpid!=%d AND fmtime>%.17g", froot, rid, p->rDate) + ){ + /* This entry establishes a new title for all entries on the thread */ + db_multi_exec( + "UPDATE event" + " SET comment=substr(comment,1,instr(comment,':')) || ' %q'" + " WHERE objid IN (SELECT fpid FROM forumpost WHERE froot=%d)", + zTitle, froot + ); + } + }else{ + /* This is a reply to a prior post. Take the title from the root. */ + zTitle = db_text(0, "SELECT substr(comment,instr(comment,':')+2)" + " FROM event WHERE objid=%d", froot); + if( zTitle==0 ) zTitle = fossil_strdup("Unknown"); + if( p->zWiki[0]==0 ){ + zFType = "Delete reply"; + }else if( fprev ){ + zFType = "Edit reply"; + }else{ + zFType = "Reply"; + } + assert( manifest_event_triggers_are_enabled ); + db_multi_exec( + "REPLACE INTO event(type,mtime,objid,user,comment)" + "VALUES('f',%.17g,%d,%Q,'%q: %q')", + p->rDate, rid, p->zUser, zFType, zTitle + ); + fossil_free(zTitle); + } + if( p->zWiki[0] ){ + backlink_extract(p->zWiki, p->zMimetype, rid, BKLNK_FORUM, p->rDate, 1); + } + } + + db_end_transaction(0); + if( permitHooks ){ + rc = xfer_run_common_script(); + if( rc==TH_OK ){ + rc = xfer_run_script(zScript, zUuid, 0); + } + } + if( p->type==CFTYPE_MANIFEST ){ + manifest_cache_insert(p); + }else{ + manifest_destroy(p); + } + assert( blob_is_reset(pContent) ); + return ( rc!=TH_ERROR ); +} + +/* +** COMMAND: test-crosslink +** +** Usage: %fossil test-crosslink RECORDID +** +** Run the manifest_crosslink() routine on the artifact with the given +** record ID. This is typically done in the debugger. +*/ +void test_crosslink_cmd(void){ + int rid; + Blob content; + db_find_and_open_repository(0, 0); + if( g.argc!=3 ) usage("RECORDID"); + rid = name_to_rid(g.argv[2]); + content_get(rid, &content); + manifest_crosslink(rid, &content, MC_NONE); } ADDED src/markdown.c Index: src/markdown.c ================================================================== --- /dev/null +++ src/markdown.c @@ -0,0 +1,2311 @@ +/* +** Copyright (c) 2012 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 code to parse a blob containing markdown text, +** using an external renderer. +*/ + +#include "config.h" +#include "markdown.h" + +#include +#include +#include + +#define MKD_LI_END 8 /* internal list flag */ + +/******************** + * TYPE DEFINITIONS * + ********************/ + +#if INTERFACE + +/* mkd_autolink -- type of autolink */ +enum mkd_autolink { + MKDA_NOT_AUTOLINK, /* used internally when it is not an autolink*/ + MKDA_NORMAL, /* normal http/http/ftp link */ + MKDA_EXPLICIT_EMAIL, /* e-mail link with explicit mailto: */ + MKDA_IMPLICIT_EMAIL /* e-mail link without mailto: */ +}; + +/* mkd_renderer -- functions for rendering parsed data */ +struct mkd_renderer { + /* document level callbacks */ + void (*prolog)(struct Blob *ob, void *opaque); + void (*epilog)(struct Blob *ob, void *opaque); + + /* block level callbacks - NULL skips the block */ + void (*blockcode)(struct Blob *ob, struct Blob *text, void *opaque); + void (*blockquote)(struct Blob *ob, struct Blob *text, void *opaque); + void (*blockhtml)(struct Blob *ob, struct Blob *text, void *opaque); + void (*header)(struct Blob *ob, struct Blob *text, + int level, void *opaque); + void (*hrule)(struct Blob *ob, void *opaque); + void (*list)(struct Blob *ob, struct Blob *text, int flags, void *opaque); + void (*listitem)(struct Blob *ob, struct Blob *text, + int flags, void *opaque); + void (*paragraph)(struct Blob *ob, struct Blob *text, void *opaque); + void (*table)(struct Blob *ob, struct Blob *head_row, struct Blob *rows, + void *opaque); + void (*table_cell)(struct Blob *ob, struct Blob *text, int flags, + void *opaque); + void (*table_row)(struct Blob *ob, struct Blob *cells, int flags, + void *opaque); + + /* span level callbacks - NULL or return 0 prints the span verbatim */ + int (*autolink)(struct Blob *ob, struct Blob *link, + enum mkd_autolink type, void *opaque); + int (*codespan)(struct Blob *ob, struct Blob *text, int nSep, void *opaque); + int (*double_emphasis)(struct Blob *ob, struct Blob *text, + char c, void *opaque); + int (*emphasis)(struct Blob *ob, struct Blob *text, char c,void*opaque); + int (*image)(struct Blob *ob, struct Blob *link, struct Blob *title, + struct Blob *alt, void *opaque); + int (*linebreak)(struct Blob *ob, void *opaque); + int (*link)(struct Blob *ob, struct Blob *link, struct Blob *title, + struct Blob *content, void *opaque); + int (*raw_html_tag)(struct Blob *ob, struct Blob *tag, void *opaque); + int (*triple_emphasis)(struct Blob *ob, struct Blob *text, + char c, void *opaque); + + /* low level callbacks - NULL copies input directly into the output */ + void (*entity)(struct Blob *ob, struct Blob *entity, void *opaque); + void (*normal_text)(struct Blob *ob, struct Blob *text, void *opaque); + + /* renderer data */ + const char *emph_chars; /* chars that trigger emphasis rendering */ + void *opaque; /* opaque data send to every rendering callback */ +}; + + + +/********* + * FLAGS * + *********/ + +/* list/listitem flags */ +#define MKD_LIST_ORDERED 1 +#define MKD_LI_BLOCK 2 /*
  • containing block data */ + +/* table cell flags */ +#define MKD_CELL_ALIGN_DEFAULT 0 +#define MKD_CELL_ALIGN_LEFT 1 +#define MKD_CELL_ALIGN_RIGHT 2 +#define MKD_CELL_ALIGN_CENTER 3 /* LEFT | RIGHT */ +#define MKD_CELL_ALIGN_MASK 3 +#define MKD_CELL_HEAD 4 + + + +/********************** + * EXPORTED FUNCTIONS * + **********************/ + +/* markdown -- parses the input buffer and renders it into the output buffer */ +void markdown( + struct Blob *ob, + struct Blob *ib, + const struct mkd_renderer *rndr); + + +#endif /* INTERFACE */ + + +/*************** + * LOCAL TYPES * + ***************/ + +/* link_ref -- reference to a link */ +struct link_ref { + struct Blob id; + struct Blob link; + struct Blob title; +}; + + +/* char_trigger -- function pointer to render active chars */ +/* returns the number of chars taken care of */ +/* data is the pointer of the beginning of the span */ +/* offset is the number of valid chars before data */ +struct render; +typedef size_t (*char_trigger)( + struct Blob *ob, + struct render *rndr, + char *data, + size_t offset, + size_t size); + + +/* render -- structure containing one particular render */ +struct render { + struct mkd_renderer make; + struct Blob refs; + char_trigger active_char[256]; + int iDepth; /* Depth of recursion */ + int nBlobCache; /* Number of entries in aBlobCache */ + struct Blob *aBlobCache[20]; /* Cache of Blobs available for reuse */ +}; + + +/* html_tag -- structure for quick HTML tag search (inspired from discount) */ +struct html_tag { + const char *text; + int size; +}; + + + +/******************** + * GLOBAL VARIABLES * + ********************/ + +/* block_tags -- recognised block tags, sorted by cmp_html_tag */ +static const struct html_tag block_tags[] = { + { "p", 1 }, + { "dl", 2 }, + { "h1", 2 }, + { "h2", 2 }, + { "h3", 2 }, + { "h4", 2 }, + { "h5", 2 }, + { "h6", 2 }, + { "ol", 2 }, + { "ul", 2 }, + { "del", 3 }, + { "div", 3 }, + { "ins", 3 }, + { "pre", 3 }, + { "form", 4 }, + { "math", 4 }, + { "table", 5 }, + { "iframe", 6 }, + { "script", 6 }, + { "fieldset", 8 }, + { "noscript", 8 }, + { "blockquote", 10 } +}; + +#define INS_TAG (block_tags + 12) +#define DEL_TAG (block_tags + 10) + + + +/*************************** + * STATIC HELPER FUNCTIONS * + ***************************/ + +/* build_ref_id -- collapse whitespace from input text to make it a ref id */ +static int build_ref_id(struct Blob *id, const char *data, size_t size){ + size_t beg, i; + char *id_data; + + /* skip leading whitespace */ + while( size>0 && (data[0]==' ' || data[0]=='\t' || data[0]=='\n') ){ + data++; + size--; + } + + /* skip trailing whitespace */ + while( size>0 && (data[size-1]==' ' + || data[size-1]=='\t' + || data[size-1]=='\n') + ){ + size--; + } + if( size==0 ) return -1; + + /* making the ref id */ + i = 0; + blob_reset(id); + while( i='A' && id_data[i]<='Z' ) id_data[i] += 'a' - 'A'; + } + return 0; +} + + +/* cmp_link_ref -- comparison function for link_ref sorted arrays */ +static int cmp_link_ref(const void *key, const void *array_entry){ + struct link_ref *lr = (void *)array_entry; + return blob_compare((void *)key, &lr->id); +} + + +/* cmp_link_ref_sort -- comparison function for link_ref qsort */ +static int cmp_link_ref_sort(const void *a, const void *b){ + struct link_ref *lra = (void *)a; + struct link_ref *lrb = (void *)b; + return blob_compare(&lra->id, &lrb->id); +} + + +/* cmp_html_tag -- comparison function for bsearch() (stolen from discount) */ +static int cmp_html_tag(const void *a, const void *b){ + const struct html_tag *hta = a; + const struct html_tag *htb = b; + if( hta->size!=htb->size ) return hta->size-htb->size; + return fossil_strnicmp(hta->text, htb->text, hta->size); +} + + +/* find_block_tag -- returns the current block tag */ +static const struct html_tag *find_block_tag(const char *data, size_t size){ + size_t i = 0; + struct html_tag key; + + /* looking for the word end */ + while( i='0' && data[i]<='9') + || (data[i]>='A' && data[i]<='Z') + || (data[i]>='a' && data[i]<='z')) + ){ + i++; + } + if( i>=size ) return 0; + + /* binary search of the tag */ + key.text = data; + key.size = i; + return bsearch(&key, + block_tags, + count(block_tags), + sizeof block_tags[0], + cmp_html_tag); +} + +/* return true if recursion has gone too deep */ +static int too_deep(struct render *rndr){ + return rndr->iDepth>200; +} + +/* get a new working buffer from the cache or create one. return NULL +** if failIfDeep is true and the depth of recursion has gone too deep. */ +static struct Blob *new_work_buffer(struct render *rndr){ + struct Blob *ret; + rndr->iDepth++; + if( rndr->nBlobCache ){ + ret = rndr->aBlobCache[--rndr->nBlobCache]; + }else{ + ret = fossil_malloc(sizeof(*ret)); + } + *ret = empty_blob; + return ret; +} + + +/* release the given working buffer back to the cache */ +static void release_work_buffer(struct render *rndr, struct Blob *buf){ + if( !buf ) return; + rndr->iDepth--; + blob_reset(buf); + if( rndr->nBlobCache < sizeof(rndr->aBlobCache)/sizeof(rndr->aBlobCache[0]) ){ + rndr->aBlobCache[rndr->nBlobCache++] = buf; + }else{ + fossil_free(buf); + } +} + + + +/**************************** + * INLINE PARSING FUNCTIONS * + ****************************/ + +/* is_mail_autolink -- looks for the address part of a mail autolink and '>' */ +/* this is less strict than the original markdown e-mail address matching */ +static size_t is_mail_autolink(char *data, size_t size){ + size_t i = 0, nb = 0; + /* address is assumed to be: [-@._a-zA-Z0-9]+ with exactly one '@' */ + while( i='a' && data[i]<='z') + || (data[i]>='A' && data[i]<='Z') + || (data[i]>='0' && data[i]<='9')) + ){ + if( data[i]=='@' ) nb++; + i++; + } + if( i>=size || data[i]!='>' || nb!=1 ) return 0; + return i+1; +} + + +/* tag_length -- returns the length of the given tag, or 0 if it's not valid */ +static size_t tag_length(char *data, size_t size, enum mkd_autolink *autolink){ + size_t i, j; + + /* a valid tag can't be shorter than 3 chars */ + if( size<3 ) return 0; + + /* begins with a '<' optionally followed by '/', followed by letter */ + if( data[0]!='<' ) return 0; + i = (data[1]=='/') ? 2 : 1; + if( (data[i]<'a' || data[i]>'z') && (data[i]<'A' || data[i]>'Z') ){ + if( data[1]=='!' && size>=7 && data[2]=='-' && data[3]=='-' ){ + for(i=6; i6 + && fossil_strnicmp(data+1, "http", 4)==0 + && (data[5]==':' + || ((data[5]=='s' || data[5]=='S') && data[6]==':')) + ){ + i = (data[5]==':') ? 6 : 7; + *autolink = MKDA_NORMAL; + }else if( size>5 && fossil_strnicmp(data+1, "ftp:", 4)==0 ){ + i = 5; + *autolink = MKDA_NORMAL; + }else if( size>7 && fossil_strnicmp(data+1, "mailto:", 7)==0 ){ + i = 8; + /* not changing *autolink to go to the address test */ + } + + /* completing autolink test: no whitespace or ' or " */ + if( i>=size || i=='>' ){ + *autolink = MKDA_NOT_AUTOLINK; + }else if( *autolink ){ + j = i; + while( i=size ) return 0; + if( i>j && data[i]=='>' ) return i+1; + /* one of the forbidden chars has been found */ + *autolink = MKDA_NOT_AUTOLINK; + }else if( (j = is_mail_autolink(data+i, size-i))!=0 ){ + *autolink = (i==8) ? MKDA_EXPLICIT_EMAIL : MKDA_IMPLICIT_EMAIL; + return i+j; + } + + /* looking for something looking like a tag end */ + while( i=size ) return 0; + return i+1; +} + + +/* parse_inline -- parses inline markdown elements */ +static void parse_inline( + struct Blob *ob, + struct render *rndr, + char *data, + size_t size +){ + size_t i = 0, end = 0; + char_trigger action = 0; + struct Blob work = BLOB_INITIALIZER; + + if( too_deep(rndr) ){ + blob_append(ob, data, size); + return; + } + while( iactive_char[(unsigned char)data[end]])==0 + ){ + end++; + } + if( end>i ){ + if( rndr->make.normal_text ){ + blob_init(&work, data+i, end-i); + rndr->make.normal_text(ob, &work, rndr->make.opaque); + }else{ + blob_append(ob, data+i, end-i); + } + } + if( end>=size ) break; + i = end; + + /* calling the trigger */ + end = action(ob, rndr, data+i, i, size-i); + if( !end ){ + /* no action from the callback */ + end = i+1; + }else{ + i += end; + end = i; + } + } +} + +/* +** data[*pI] should be a "`" character that introduces a code-span. +** The code-span boundry mark can be any number of one or more "`" +** characters. We do not know the size of the boundry marker, only +** that there is at least one "`" at data[*pI]. +** +** This routine increases *pI to move it past the code-span, including +** the closing boundary mark. Or, if the code-span is unterminated, +** this routine moves *pI past the opening boundary mark only. +*/ +static void skip_codespan(const char *data, size_t size, size_t *pI){ + size_t i = *pI; + size_t span_nb; /* Number of "`" characters in the boundary mark */ + size_t bt; + + assert( i=size ){ + *pI += span_nb; + return; + } + + /* finding the matching closing sequence */ + bt = 0; + while( i=size ) return 0; + + /* not counting escaped chars */ + if( i && data[i-1]=='\\' ){ + i++; + continue; + } + + if( data[i]==c ) return i; + + if( data[i]=='`' ){ /* skip a code span */ + skip_codespan(data, size, &i); + }else if( data[i]=='[' ){ /* skip a link */ + size_t tmp_i = 0; + char cc; + i++; + while( i=size ) return tmp_i; + if( data[i]!='[' && data[i]!='(' ){ /* not a link*/ + if( tmp_i ) return tmp_i; else continue; + } + cc = data[i]; + i++; + while( i=size ) return tmp_i; + i++; + } + } + return 0; +} + +/* CommonMark defines separate "right-flanking" and "left-flanking" +** deliminators for emphasis. Whether a deliminator is left- or +** right-flanking, or both, or neither depends on the characters +** immediately before and after. +** +** before after example left-flanking right-flanking +** ------ ----- ------- ------------- -------------- +** space space * no no +** space punct *) yes no +** space alnum *x yes no +** punct space (* no yes +** punct punct (*) yes yes +** punct alnum (*x yes no +** alnum space a* no yes +** alnum punct a*( no yes +** alnum alnum a*x yes yes +** +** The following routines determine whether a delimitor is left +** or right flanking. +*/ +static int left_flanking(char before, char after){ + if( fossil_isspace(after) ) return 0; + if( fossil_isalnum(after) ) return 1; + if( fossil_isalnum(before) ) return 0; + return 1; +} +static int right_flanking(char before, char after){ + if( fossil_isspace(before) ) return 0; + if( fossil_isalnum(before) ) return 1; + if( fossil_isalnum(after) ) return 0; + return 1; +} + + +/* parse_emph1 -- parsing single emphasis */ +/* closed by a symbol not preceded by whitespace and not followed by symbol */ +static size_t parse_emph1( + struct Blob *ob, + struct render *rndr, + char *data, + size_t size, + char c +){ + size_t i = 0, len; + struct Blob *work = 0; + int r; + char after; + + if( !rndr->make.emphasis ) return 0; + + /* skipping one symbol if coming from emph3 */ + if( size>1 && data[0]==c && data[1]==c ) i = 1; + + while( i=size ) return 0; + + if( i+1make.emphasis(ob, work, c, rndr->make.opaque); + release_work_buffer(rndr, work); + return r ? i+1 : 0; + } + } + return 0; +} + + +/* parse_emph2 -- parsing single emphasis */ +static size_t parse_emph2( + struct Blob *ob, + struct render *rndr, + char *data, + size_t size, + char c +){ + size_t i = 0, len; + struct Blob *work = 0; + int r; + char after; + + if( !rndr->make.double_emphasis ) return 0; + + while( imake.double_emphasis(ob, work, c, rndr->make.opaque); + release_work_buffer(rndr, work); + return r ? i+2 : 0; + } + i++; + } + return 0; +} + + +/* parse_emph3 -- parsing single emphasis */ +/* finds the first closing tag, and delegates to the other emph */ +static size_t parse_emph3( + struct Blob *ob, + struct render *rndr, + char *data, + size_t size, + char c +){ + size_t i = 0, len; + int r; + + while( imake.triple_emphasis + && !too_deep(rndr) + ){ + /* triple symbol found */ + struct Blob *work = new_work_buffer(rndr); + parse_inline(work, rndr, data, i); + r = rndr->make.triple_emphasis(ob, work, c, rndr->make.opaque); + release_work_buffer(rndr, work); + return r ? i+3 : 0; + }else if( i+10 ? data[-1] : ' '; + size_t ret; + + if( size>2 && data[1]!=c ){ + if( !left_flanking(before, data[1]) + || (c=='_' && fossil_isalnum(before)) + || (ret = parse_emph1(ob, rndr, data+1, size-1, c))==0 + ){ + return 0; + } + return ret+1; + } + + if( size>3 && data[1]==c && data[2]!=c ){ + if( !left_flanking(before, data[2]) + || (c=='_' && fossil_isalnum(before)) + || (ret = parse_emph2(ob, rndr, data+2, size-2, c))==0 + ){ + return 0; + } + return ret+2; + } + + if( size>4 && data[1]==c && data[2]==c && data[3]!=c ){ + if( !left_flanking(before, data[3]) + || (c=='_' && fossil_isalnum(before)) + || (ret = parse_emph3(ob, rndr, data+3, size-3, c))==0 + ){ + return 0; + } + return ret+3; + } + return 0; +} + + +/* char_linebreak -- '\n' preceded by two spaces (assuming linebreak != 0) */ +static size_t char_linebreak( + struct Blob *ob, + struct render *rndr, + char *data, + size_t offset, + size_t size +){ + if( offset<2 || data[-1]!=' ' || data[-2]!=' ' ) return 0; + /* removing the last space from ob and rendering */ + if( blob_size(ob)>0 && blob_buffer(ob)[blob_size(ob)-1]==' ' ) ob->nUsed--; + return rndr->make.linebreak(ob, rndr->make.opaque) ? 1 : 0; +} + + +/* char_codespan -- '`' parsing a code span (assuming codespan != 0) */ +static size_t char_codespan( + struct Blob *ob, + struct render *rndr, + char *data, + size_t offset, + size_t size +){ + size_t end, nb = 0, i, f_begin, f_end; + char delim = data[0]; + + /* counting the number of backticks in the delimiter */ + while( nb=size ) return 0; /* no matching delimiter */ + + /* trimming outside whitespaces */ + f_begin = nb; + while( f_beginnb && (data[f_end-1]==' ' || data[f_end-1]=='\t') ){ f_end--; } + + /* real code span */ + if( f_beginmake.codespan(ob, &work, nb, rndr->make.opaque) ) end = 0; + }else{ + if( !rndr->make.codespan(ob, 0, nb, rndr->make.opaque) ) end = 0; + } + return end; +} + + +/* char_escape -- '\\' backslash escape */ +static size_t char_escape( + struct Blob *ob, + struct render *rndr, + char *data, + size_t offset, + size_t size +){ + struct Blob work = BLOB_INITIALIZER; + if( size>1 ){ + if( rndr->make.normal_text ){ + blob_init(&work, data+1,1); + rndr->make.normal_text(ob, &work, rndr->make.opaque); + }else{ + blob_append(ob, data+1, 1); + } + } + return 2; +} + + +/* char_entity -- '&' escaped when it doesn't belong to an entity */ +/* valid entities are assumed to be anything matching &#?[A-Za-z0-9]+; */ +static size_t char_entity( + struct Blob *ob, + struct render *rndr, + char *data, + size_t offset, + size_t size +){ + size_t end = 1; + struct Blob work = BLOB_INITIALIZER; + if( end='0' && data[end]<='9') + || (data[end]>='a' && data[end]<='z') + || (data[end]>='A' && data[end]<='Z')) + ){ + end++; + } + if( endmake.entity ){ + blob_init(&work, data, end); + rndr->make.entity(ob, &work, rndr->make.opaque); + }else{ + blob_append(ob, data, end); + } + return end; +} + + +/* char_langle_tag -- '<' when tags or autolinks are allowed */ +static size_t char_langle_tag( + struct Blob *ob, + struct render *rndr, + char *data, + size_t offset, + size_t size +){ + enum mkd_autolink altype = MKDA_NOT_AUTOLINK; + size_t end = tag_length(data, size, &altype); + struct Blob work = BLOB_INITIALIZER; + int ret = 0; + if( end ){ + if( rndr->make.autolink && altype!=MKDA_NOT_AUTOLINK ){ + blob_init(&work, data+1, end-2); + ret = rndr->make.autolink(ob, &work, altype, rndr->make.opaque); + }else if( rndr->make.raw_html_tag ){ + blob_init(&work, data, end); + ret = rndr->make.raw_html_tag(ob, &work, rndr->make.opaque); + } + } + + if( !ret ){ + return 0; + }else{ + return end; + } +} + + +/* get_link_inline -- extract inline-style link and title from +** parenthesed data +*/ +static int get_link_inline( + struct Blob *link, + struct Blob *title, + char *data, + size_t size +){ + size_t i = 0, mark; + size_t link_b, link_e; + size_t title_b = 0, title_e = 0; + + /* skipping initial whitespace */ + while( ititle_b + && (data[title_e]==' ' + || data[title_e]=='\t' + || data[title_e]=='\n') + ){ + title_e--; + } + + /* checking for closing quote presence */ + if (data[title_e] != '\'' && data[title_e] != '"') { + title_b = title_e = 0; + link_e = i; + } + } + + /* remove whitespace at the end of the link */ + while( link_e>link_b + && (data[link_e-1]==' ' + || data[link_e-1]=='\t' + || data[link_e-1]=='\n') + ){ + link_e--; + } + + /* remove optional angle brackets around the link */ + if( data[link_b]=='<' ) link_b += 1; + if( data[link_e-1]=='>' ) link_e -= 1; + + /* escape backslashed character from link */ + blob_reset(link); + i = link_b; + while( ititle_b ) blob_append(title, data+title_b, title_e-title_b); + + /* this function always succeed */ + return 0; +} + + +/* get_link_ref -- extract referenced link and title from id */ +static int get_link_ref( + struct render *rndr, + struct Blob *link, + struct Blob *title, + char *data, + size_t size +){ + struct link_ref *lr; + + /* find the link from its id (stored temporarily in link) */ + blob_reset(link); + if( build_ref_id(link, data, size)<0 ) return -1; + lr = bsearch(link, + blob_buffer(&rndr->refs), + blob_size(&rndr->refs)/sizeof(struct link_ref), + sizeof (struct link_ref), + cmp_link_ref); + if( !lr ) return -1; + + /* fill the output buffers */ + blob_reset(link); + blob_reset(title); + blob_append(link, blob_buffer(&lr->link), blob_size(&lr->link)); + blob_append(title, blob_buffer(&lr->title), blob_size(&lr->title)); + return 0; +} + + +/* char_link -- '[': parsing a link or an image */ +static size_t char_link( + struct Blob *ob, + struct render *rndr, + char *data, + size_t offset, + size_t size +){ + int is_img = (offset && data[-1] == '!'), level; + size_t i = 1, txt_e; + struct Blob *content = 0; + struct Blob *link = 0; + struct Blob *title = 0; + int ret; + + /* checking whether the correct renderer exists */ + if( (is_img && !rndr->make.image) || (!is_img && !rndr->make.link) ){ + return 0; + } + + /* looking for the matching closing bracket */ + for(level=1; i=size ) return 0; + txt_e = i; + i++; + + /* skip any amount of whitespace or newline */ + /* (this is much more laxist than original markdown syntax) */ + while( i=size + || get_link_inline(link, title, data+i+1, span_end-(i+1))<0 + ){ + goto char_link_cleanup; + } + + i = span_end+1; + + /* reference style link */ + }else if( i=size ) goto char_link_cleanup; + + if( i+1==id_end ){ + /* implicit id - use the contents */ + id_data = data+1; + id_size = txt_e-1; + }else{ + /* explicit id - between brackets */ + id_data = data+i+1; + id_size = id_end-(i+1); + } + + if( get_link_ref(rndr, link, title, id_data, id_size)<0 ){ + goto char_link_cleanup; + } + + i = id_end+1; + + /* shortcut reference style link */ + }else{ + if( get_link_ref(rndr, link, title, data+1, txt_e-1)<0 ){ + goto char_link_cleanup; + } + + /* rewinding the whitespace */ + i = txt_e+1; + } + + /* building content: img alt is escaped, link content is parsed */ + if( txt_e>1 ){ + if( is_img ) blob_append(content, data+1, txt_e-1); + else parse_inline(content, rndr, data+1, txt_e-1); + } + + /* calling the relevant rendering function */ + if( is_img ){ + if( blob_size(ob)>0 && blob_buffer(ob)[blob_size(ob)-1]=='!' ) ob->nUsed--; + ret = rndr->make.image(ob, link, title, content, rndr->make.opaque); + }else{ + ret = rndr->make.link(ob, link, title, content, rndr->make.opaque); + } + + /* cleanup */ +char_link_cleanup: + release_work_buffer(rndr, title); + release_work_buffer(rndr, link); + release_work_buffer(rndr, content); + return ret ? i : 0; +} + + + +/********************************* + * BLOCK-LEVEL PARSING FUNCTIONS * + *********************************/ + +/* is_empty -- returns the line length when it is empty, 0 otherwise */ +static size_t is_empty(const char *data, size_t size){ + size_t i; + for(i=0; i=size || (data[i]!='*' && data[i]!='-' && data[i]!='_') ) return 0; + c = data[i]; + + /* the whole line must be the char or whitespace */ + while (i < size && data[i] != '\n') { + if( data[i]==c ){ + n += 1; + }else if( data[i]!=' ' && data[i]!='\t' ){ + return 0; + } + i++; + } + + return n>=3; +} + + +/* is_headerline -- returns whether the line is a setext-style hdr underline */ +static int is_headerline(char *data, size_t size){ + size_t i = 0; + + /* test of level 1 header */ + if( data[i]=='=' ){ + for(i=1; i=size || data[i]=='\n') ? 1 : 0; + } + + /* test of level 2 header */ + if( data[i]=='-' ){ + for(i=1; i=size || data[i]=='\n') ? 2 : 0; + } + + return 0; +} + + +/* is_table_sep -- returns whether there is a table separator at pos */ +static int is_table_sep(char *data, size_t pos){ + return data[pos]=='|' && (pos==0 || data[pos-1]!='\\'); +} + + +/* is_tableline -- returns the number of column tables in the given line */ +static int is_tableline(char *data, size_t size){ + size_t i = 0; + int n_sep = 0, outer_sep = 0; + + /* skip initial blanks */ + while( i0) ? (n_sep-outer_sep+1) : 0; +} + + +/* prefix_quote -- returns blockquote prefix length */ +static size_t prefix_quote(char *data, size_t size){ + size_t i = 0; + if( i' ){ + if( i+10 && data[0]=='\t' ) return 1; + if( size>3 && data[0]==' ' && data[1]==' ' && data[2]==' ' && data[3]==' ' ){ + return 4; + } + return 0; +} + +/* Return the number of characters in the delimiter of a fenced code +** block. */ +static size_t prefix_fencedcode(char *data, size_t size){ + char c = data[0]; + int nb; + if( c!='`' && c!='~' ) return 0; + for(nb=1; nb=size-nb ) return 0; + return nb; +} + +/* prefix_oli -- returns ordered list item prefix */ +static size_t prefix_oli(char *data, size_t size){ + size_t i = 0; + if( i=size || data[i]<'0' || data[i]>'9' ) return 0; + while( i='0' && data[i]<='9' ){ i++; } + + if( i+1>=size + || (data[i]!='.' && data[i]!=')') + || (data[i+1]!=' ' && data[i+1]!='\t') + ){ + return 0; + } + i = i+2; + while( i=size + || (data[i]!='*' && data[i]!='+' && data[i]!='-') + || (data[i+1]!=' ' && data[i+1]!='\t') + ){ + return 0; + } + i = i+2; + while( i=size + || (prefix_quote(data+end, size-end)==0 + && !is_empty(data+end, size-end))) + ){ + /* empty line followed by non-quote line */ + break; + } + if( begmake.blockquote ){ + if( !too_deep(rndr) ){ + parse_block(out, rndr, work_data, work_size); + }else{ + blob_append(out, work_data, work_size); + } + rndr->make.blockquote(ob, out, rndr->make.opaque); + } + release_work_buffer(rndr, out); + return end; +} + + +/* parse_paragraph -- handles parsing of a regular paragraph */ +static size_t parse_paragraph( + struct Blob *ob, + struct render *rndr, + char *data, + size_t size +){ + size_t i = 0, end = 0; + int level = 0; + char *work_data = data; + size_t work_size = 0; + + while( imake.paragraph ){ + struct Blob *tmp = new_work_buffer(rndr); + parse_inline(tmp, rndr, work_data, work_size); + rndr->make.paragraph(ob, tmp, rndr->make.opaque); + release_work_buffer(rndr, tmp); + } + }else{ + if( work_size ){ + size_t beg; + i = work_size; + work_size -= 1; + while( work_size && data[work_size]!='\n' ){ work_size--; } + beg = work_size+1; + while( work_size && data[work_size-1]=='\n'){ work_size--; } + if( work_size ){ + struct Blob *tmp = new_work_buffer(rndr); + parse_inline(tmp, rndr, work_data, work_size); + if( rndr->make.paragraph ){ + rndr->make.paragraph(ob, tmp, rndr->make.opaque); + } + release_work_buffer(rndr, tmp); + work_data += beg; + work_size = i - beg; + }else{ + work_size = i; + } + } + + if( rndr->make.header ){ + struct Blob *span = new_work_buffer(rndr); + parse_inline(span, rndr, work_data, work_size); + rndr->make.header(ob, span, level, rndr->make.opaque); + release_work_buffer(rndr, span); + } + } + return end; +} + + +/* parse_blockcode -- handles parsing of a block-level code fragment */ +static size_t parse_blockcode( + struct Blob *ob, + struct render *rndr, + char *data, + size_t size +){ + size_t beg, end, pre; + struct Blob *work = new_work_buffer(rndr); + + beg = 0; + while( beg0 && blob_buffer(work)[end-1]=='\n' ){ end--; } + work->nUsed = end; + blob_append_char(work, '\n'); + + if( work!=ob ){ + if( rndr->make.blockcode ){ + rndr->make.blockcode(ob, work, rndr->make.opaque); + } + release_work_buffer(rndr, work); + } + return beg; +} + + +/* parse_listitem -- parsing of a single list item */ +/* assuming initial prefix is already removed */ +static size_t parse_listitem( + struct Blob *ob, + struct render *rndr, + char *data, + size_t size, + int *flags +){ + struct Blob *work = 0, *inter = 0; + size_t beg = 0, end, pre, sublist = 0, orgpre = 0, i; + int in_empty = 0, has_inside_empty = 0; + + /* keeping track of the first indentation prefix */ + if( size>1 && data[0]==' ' ){ + orgpre = 1; + if( size>2 && data[1]==' ' ){ + orgpre = 2; + if( size>3 && data[2]==' ' ){ + orgpre = 3; + } + } + } + beg = prefix_uli(data, size); + if( !beg ) beg = prefix_oli(data, size); + if( !beg ) return 0; + /* skipping to the beginning of the following line */ + end = beg; + while( end1 && data[beg]==' ' ){ + i = 1; + if( end-beg>2 && data[beg+1]==' ' ){ + i = 2; + if( end-beg>3 && data[beg+2]==' ' ){ + i = 3; + if( end-beg>3 && data[beg+3]==' ' ){ + i = 4; + } + } + } + } + pre = i; + if( data[beg]=='\t' ){ i = 1; pre = 8; } + + /* checking for a new item */ + if( (prefix_uli(data+beg+i, end-beg-i) && !is_hrule(data+beg+i, end-beg-i)) + || prefix_oli(data+beg+i, end-beg-i) + ){ + if( in_empty ) has_inside_empty = 1; + if( pre == orgpre ){ /* the following item must have */ + break; /* the same indentation */ + } + if( !sublist ) sublist = blob_size(work); + + /* joining only indented stuff after empty lines */ + }else if( in_empty && i<4 && data[beg]!='\t' ){ + *flags |= MKD_LI_END; + break; + }else if( in_empty ){ + blob_append_char(work, '\n'); + has_inside_empty = 1; + } + in_empty = 0; + + /* adding the line without prefix into the working buffer */ + blob_append(work, data+beg+i, end-beg-i); + beg = end; + } + + /* non-recursive fallback when working buffer stack is full */ + if( !inter ){ + if( rndr->make.listitem ){ + rndr->make.listitem(ob, work, *flags, rndr->make.opaque); + } + release_work_buffer(rndr, work); + return beg; + } + + /* render of li contents */ + if( has_inside_empty ) *flags |= MKD_LI_BLOCK; + if( *flags & MKD_LI_BLOCK ){ + /* intermediate render of block li */ + if( sublist && sublistmake.listitem ){ + rndr->make.listitem(ob, inter, *flags, rndr->make.opaque); + } + release_work_buffer(rndr, inter); + release_work_buffer(rndr, work); + return beg; +} + + +/* parse_list -- parsing ordered or unordered list block */ +static size_t parse_list( + struct Blob *ob, + struct render *rndr, + char *data, + size_t size, + int flags +){ + struct Blob *work = new_work_buffer(rndr); + size_t i = 0, j; + + while( imake.list ) rndr->make.list(ob, work, flags, rndr->make.opaque); + release_work_buffer(rndr, work); + return i; +} + + +/* parse_atxheader -- parsing of atx-style headers */ +static size_t parse_atxheader( + struct Blob *ob, + struct render *rndr, + char *data, + size_t size +){ + int level = 0; + size_t i, end, skip, span_beg, span_size; + + if( !size || data[0]!='#' ) return 0; + + while( levelmake.header ){ + struct Blob *span = new_work_buffer(rndr); + parse_inline(span, rndr, data+span_beg, span_size); + rndr->make.header(ob, span, level, rndr->make.opaque); + release_work_buffer(rndr, span); + } + return skip; +} + + +/* htmlblock_end -- checking end of HTML block : [ \t]*\n[ \t*]\n */ +/* returns the length on match, 0 otherwise */ +static size_t htmlblock_end( + const struct html_tag *tag, + const char *data, + size_t size +){ + size_t i, w; + + /* assuming data[0]=='<' && data[1]=='/' already tested */ + + /* checking tag is a match */ + if( (tag->size+3)>=size + || fossil_strnicmp(data+2, tag->text, tag->size) + || data[tag->size+2]!='>' + ){ + return 0; + } + + /* checking white lines */ + i = tag->size + 3; + w = 0; + if( i5 && data[1]=='!' && data[2]=='-' && data[3]=='-' ){ + i = 5; + while( i') ){ + i++; + } + i++; + if( imake.blockhtml ) return work_size; + blob_init(&work, data, work_size); + rndr->make.blockhtml(ob, &work, rndr->make.opaque); + return work_size; + } + } + } + + /* HR, which is the only self-closing block tag considered */ + if( size>4 + && (data[1]=='h' || data[1]=='H') + && (data[2]=='r' || data[2]=='R') + ){ + i = 3; + while( imake.blockhtml ) return work_size; + blob_init(&work, data, work_size); + rndr->make.blockhtml(ob, &work, rndr->make.opaque); + return work_size; + } + } + } + + /* no special case recognised */ + return 0; + } + + /* looking for an unindented matching closing tag */ + /* followed by a blank line */ + i = 1; + found = 0; +#if 0 + while( isize)>=size ) break; + j = htmlblock_end(curtag, data+i-1, size-i+1); + if (j) { + i += j-1; + found = 1; + break; + } + } +#endif + + /* if not found, trying a second pass looking for indented match */ + /* but not if tag is "ins" or "del" (following original Markdown.pl) */ + if( !found && curtag!=INS_TAG && curtag!=DEL_TAG ){ + i = 1; + while( isize)>=size ) break; + j = htmlblock_end(curtag, data+i-1, size-i+1); + if (j) { + i += j-1; + found = 1; + break; + } + } + } + + if( !found ) return 0; + + /* the end of the block has been found */ + blob_init(&work, data, i); + if( rndr->make.blockhtml ){ + rndr->make.blockhtml(ob, &work, rndr->make.opaque); + } + return i; +} + + +/* parse_table_cell -- parse a cell inside a table */ +static void parse_table_cell( + struct Blob *ob, /* output blob */ + struct render *rndr, /* renderer description */ + char *data, /* input text */ + size_t size, /* input text size */ + int flags /* table flags */ +){ + struct Blob *span = new_work_buffer(rndr); + parse_inline(span, rndr, data, size); + rndr->make.table_cell(ob, span, flags, rndr->make.opaque); + release_work_buffer(rndr, span); +} + + +/* parse_table_row -- parse an input line into a table row */ +static size_t parse_table_row( + struct Blob *ob, /* output blob for rendering */ + struct render *rndr, /* renderer description */ + char *data, /* input text */ + size_t size, /* input text size */ + int *aligns, /* array of default alignment for columns */ + size_t align_size, /* number of columns with default alignment */ + int flags /* table flags */ +){ + size_t i = 0, col = 0; + size_t beg, end, total = 0; + struct Blob *cells = new_work_buffer(rndr); + int align; + + /* skip leading blanks and separator */ + while( ibeg && data[end-1]==':' ){ + align |= MKD_CELL_ALIGN_RIGHT; + end--; + } + + /* remove trailing blanks */ + while( end>beg && (data[end-1]==' ' || data[end-1]=='\t') ){ end--; } + + /* skip the last cell if it was only blanks */ + /* (because it is only the optional end separator) */ + if( total && end<=beg ) continue; + + /* fallback on default alignment if not explicit */ + if( align==0 && aligns && col=beg ){ + parse_table_cell(cells, rndr, data+beg, end-beg, align|flags); + } + + col++; + } + + /* render the whole row and clean up */ + rndr->make.table_row(ob, cells, flags, rndr->make.opaque); + release_work_buffer(rndr, cells); + return total ? total : size; +} + + +/* parse_table -- parsing of a whole table */ +static size_t parse_table( + struct Blob *ob, + struct render *rndr, + char *data, + size_t size +){ + size_t i = 0, head_end, col; + size_t align_size = 0; + int *aligns = 0; + struct Blob *head = 0; + struct Blob *rows = new_work_buffer(rndr); + + /* skip the first (presumably header) line */ + while( i=size ){ + parse_table_row(rows, rndr, data, size, 0, 0, 0); + rndr->make.table(ob, 0, rows, rndr->make.opaque); + release_work_buffer(rndr, rows); + return i; + } + + /* attempt to parse a table rule, i.e. blanks, dash, colons and sep */ + i++; + col = 0; + while( imake.table(ob, head, rows, rndr->make.opaque); + + /* cleanup */ + release_work_buffer(rndr, head); + release_work_buffer(rndr, rows); + fossil_free(aligns); + return i; +} + + +/* parse_block -- parsing of one block, returning next char to parse */ +static void parse_block( + struct Blob *ob, /* output blob */ + struct render *rndr, /* renderer internal state */ + char *data, /* input text */ + size_t size /* input text size */ +){ + size_t beg, end, i; + char *txt_data; + int has_table = (rndr->make.table + && rndr->make.table_row + && rndr->make.table_cell + && memchr(data, '|', size)!=0); + + beg = 0; + while( begmake.blockhtml + && (i = parse_htmlblock(ob, rndr, txt_data, end))!=0 + ){ + beg += i; + }else if( (i=is_empty(txt_data, end))!=0 ){ + beg += i; + }else if( is_hrule(txt_data, end) ){ + if( rndr->make.hrule ) rndr->make.hrule(ob, rndr->make.opaque); + while( beg=end ) return 0; + if( data[beg]==' ' ){ + i = 1; + if( data[beg+1]==' ' ){ + i = 2; + if( data[beg+2]==' ' ){ + i = 3; + if( data[beg+3]==' ' ) return 0; + } + } + } + i += beg; + + /* id part: anything but a newline between brackets */ + if( data[i]!='[' ) return 0; + i++; + id_offset = i; + while( i=end || data[i]!=']' ) return 0; + id_end = i; + + /* spacer: colon (space | tab)* newline? (space | tab)* */ + i++; + if( i>=end || data[i]!=':' ) return 0; + i++; + while( i=end ) return 0; + + /* link: whitespace-free sequence, optionally between angle brackets */ + if( data[i]=='<' ) i++; + link_offset = i; + while( i' ) link_end = i-1; else link_end = i; + + /* optional spacer: (space | tab)* (newline | '\'' | '"' | '(' ) */ + while( i=end || data[i]=='\r' || data[i]=='\n' ) line_end = i; + if( i+1title_offset && (data[i]==' ' || data[i]=='\t') ){ i--; } + if( i>title_offset && (data[i]=='\'' || data[i]=='"' || data[i]==')') ){ + line_end = title_end; + title_end = i; + } + } + if( !line_end ) return 0; /* garbage after the link */ + + /* a valid ref has been found, filling-in return structures */ + if( last ) *last = line_end; + if( !refs ) return 1; + if( build_ref_id(&lr.id, data+id_offset, id_end-id_offset)<0 ) return 0; + blob_append(&lr.link, data+link_offset, link_end-link_offset); + if( title_end>title_offset ){ + blob_append(&lr.title, data+title_offset, title_end-title_offset); + } + blob_append(refs, (char *)&lr, sizeof lr); + return 1; +} + + + +/********************** + * EXPORTED FUNCTIONS * + **********************/ + +/* markdown -- parses the input buffer and renders it into the output buffer */ +void markdown( + struct Blob *ob, /* output blob for rendered text */ + struct Blob *ib, /* input blob in markdown */ + const struct mkd_renderer *rndrer /* renderer descriptor (callbacks) */ +){ + struct link_ref *lr; + size_t i, beg, end = 0; + struct render rndr; + char *ib_data; + Blob text = BLOB_INITIALIZER; + + /* filling the render structure */ + if( !rndrer ) return; + rndr.make = *rndrer; + rndr.nBlobCache = 0; + rndr.iDepth = 0; + rndr.refs = empty_blob; + for(i=0; i<256; i++) rndr.active_char[i] = 0; + if( (rndr.make.emphasis + || rndr.make.double_emphasis + || rndr.make.triple_emphasis) + && rndr.make.emph_chars + ){ + for(i=0; rndr.make.emph_chars[i]; i++){ + rndr.active_char[(unsigned char)rndr.make.emph_chars[i]] = char_emphasis; + } + } + if( rndr.make.codespan ) rndr.active_char['`'] = char_codespan; + if( rndr.make.linebreak ) rndr.active_char['\n'] = char_linebreak; + if( rndr.make.image || rndr.make.link ) rndr.active_char['['] = char_link; + rndr.active_char['<'] = char_langle_tag; + rndr.active_char['\\'] = char_escape; + rndr.active_char['&'] = char_entity; + + /* first pass: looking for references, copying everything else */ + beg = 0; + ib_data = blob_buffer(ib); + while( begbeg ) blob_append(&text, ib_data + beg, end - beg); + while( end Paragraphs are divided by blank lines. +> End a line with two or more spaces to force a mid-paragraph line break. + +## Headings ## + +> + # Top Level Heading Alternative Top Level Heading + # Top Level Heading Variant # ============================= +> + ## 2nd Level Heading Alternative 2nd Level Heading + ## 2nd Level Heading Variant ## ----------------------------- +> + ### 3rd Level Heading ### 3rd Level Heading Variant ### + #### 4th Level Heading #### 4th Level Heading Variant #### + ##### 5th Level Heading ##### 5th Level Heading Variant ##### + ###### 6th Level Heading ###### 6th Level Heading Variant ###### + +## Links ## + +> 1. **\[display text\]\(URL\)** +> 2. **\[display text\]\(URL "Title"\)** +> 3. **\[display text\]\(URL 'Title'\)** +> 4. **\** +> 5. **\[display text\]\[label\]** +> 6. **\[display text\]\[\]** +> 7. **\[display text\]** +> 8. **\[\]\(URL\)** + +> With link formats 5, 6, and 7 ("reference links"), the URL is supplied +> elsewhere in the document, as shown below. Link formats 6 and 7 reuse +> the display text as the label. Labels are case-insensitive. The title +> may be split onto the next line with optional indenting. + +> * **\[label\]: URL** +> * **\[label\]: URL "Title"** +> * **\[label\]: URL 'Title'** +> * **\[label\]: URL (Title)** + +> If **URL** begins with "http:", "https:', "ftp:' or "mailto:", +> it may optionally be written **\** (format 4). +> Other **URL** formats include: +>
      +>
    • A relative pathname. +>
    • A pathname starting with "/" in which case the Fossil server +> URL prefix is prepended +>
    • A wiki page name, or a wiki page name preceded by "wiki:" +>
    • An artifact or ticket hash or hash prefix +>
    • A date and time stamp: "YYYY-MM-DD HH:MM:SS" or a subset that +> includes at least the day of the month. +>
    • An [interwiki link](#intermap) of the form "Tag:PageName"
    + +> In format 8, then the URL becomes the display text. This is useful for +> hyperlinks that refer to wiki pages and check-in and ticket hashes. + +## Fonts ## + +> * _\*italic\*_ +> * *\_italic\_* +> * __\*\*bold\*\*__ +> * **\_\_bold\_\_** +> * ___\*\*\*italic+bold\*\*\*___ +> * ***\_\_\_italic+bold\_\_\_*** +> * \``code`\` + +> The **\`code\`** construct disables HTML markup, so one can write, for +> example, **\`\\`** to yield **``**. + +## Lists ## + +> + * bullet item + + bullet item + - bullet item + 1. numbered item + 2) numbered item + +> A two-level list is created by placing additional whitespace before the +> **\***/**+**/**-**/**1.** of the secondary items. + +> + * top-level item + * secondary item + +## Block Quotes ## + +> Begin each line of a paragraph with **>** to block quote that paragraph. + +> > + > This paragraph is indented +> > + > > Double-indented paragraph + +## Literal/Verbatim Text - Code Blocks ## + +> For inline text, you can either use \``backticks`\` or the HTML +> `` tag. +> +> For blocks of text or code: +> +> 1. Indent the text using a tab character or at least four spaces. +> 2. Precede the block with an HTML `
    ` tag and follow it with `
    `. +> 3. Surround the block by \`\`\` (three or more) or \~\~\~ either at the +> left margin or indented no more than three spaces. The first word +> on that same line (if any) is used in a “`language-WORD`” CSS style in +> the HTML rendering of that code block and is intended for use by +> code syntax highlighters. Thus \`\`\`c would mark a block of code +> in the C programming language. Text to be rendered inside the code block +> should therefore start on the next line, not be cuddled up with the +> backticks or tildes. See the "Diagrams" section below for the case where +> "`language-WORD`" is "pikchr". + +> With the standard skins, verbatim text is rendered in a fixed-width font, +> but that is purely a presentation matter, controlled by the skin’s CSS. + + +## Tables ## + +> + | Header 1 | Header 2 | Header 3 | + ---------------------------------------------- + | Row 1 Col 1 | Row 1 Col 2 | Row 1 Col 3 | + |:Left-aligned |:Centered :| Right-aligned:| + | | ← Blank → | | + | Row 4 Col 1 | Row 4 Col 2 | Row 4 Col 3 | + +> The first row is a header if followed by a horizontal rule or a blank line. + +> Placing **:** at the left, both, or right sides of a cell gives left-aligned, +> centered, or right-aligned text, respectively. By default, header cells are +> centered, and body cells are left-aligned. + +> The leftmost or rightmost **\|** is required only if the first or last column, +> respectively, contains at least one blank cell. + +## Diagrams ## + +> +~~~~~ +~~~ pikchr +oval "Start" fit; arrow; box "Hello, World!" fit; arrow; oval "Done" fit +~~~ +~~~~~ + +> Formatted using [Pikchr](https://pikchr.org/home), resulting in: + +> +~~~ pikchr +oval "Start" fit; arrow; box "Hello, World!" fit; arrow; oval "Done" fit +~~~ + +## Miscellaneous ## + +> * In-line images are made using **\!\[alt-text\]\(image-URL\)**. +> * Use HTML for advanced formatting such as forms. +> * **\** are supported. +> * Escape special characters (ex: **\[** **\(** **\|** **\***) +> using backslash (ex: **\\\[** **\\\(** **\\\|** **\\\***). +> * A line consisting of **---**, **\*\*\***, or **\_\_\_** is a horizontal +> rule. Spaces and extra **-**/**\***/**_** are allowed. +> * See [daringfireball.net][] for additional information. +> * See this page's [Markdown source](/md_rules?txt=1) for complex examples. + +## Special Features For Fossil ## + +> * In hyperlinks, if the URL begins with **/** then the root of the Fossil +> repository is prepended. This allows for repository-relative hyperlinks. +> * For documents that begin with a top-level heading (ex: **# heading #**), +> the heading is omitted from the body of the document and becomes the +> document title displayed at the top of the Fossil page. + +[daringfireball.net]: http://daringfireball.net/projects/markdown/syntax + + +## Interwiki Tag [Map](/intermap) ADDED src/markdown_html.c Index: src/markdown_html.c ================================================================== --- /dev/null +++ src/markdown_html.c @@ -0,0 +1,608 @@ +/* +** Copyright (c) 2012 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 callbacks for the markdown parser that generate +** XHTML output. +*/ + +#include "config.h" +#include "markdown_html.h" + +#if INTERFACE + +void markdown_to_html( + struct Blob *input_markdown, + struct Blob *output_title, + struct Blob *output_body); + +#endif /* INTERFACE */ + +/* +** An instance of the following structure is passed through the +** "opaque" pointer. +*/ +typedef struct MarkdownToHtml MarkdownToHtml; +struct MarkdownToHtml { + Blob *output_title; /* Store the title here */ +}; + + +/* INTER_BLOCK -- skip a line between block level elements */ +#define INTER_BLOCK(ob) \ + do { if( blob_size(ob)>0 ) blob_append_char(ob, '\n'); } while (0) + +/* BLOB_APPEND_LITERAL -- append a string literal to a blob */ +#define BLOB_APPEND_LITERAL(blob, literal) \ + blob_append((blob), "" literal, (sizeof literal)-1) + /* + * The empty string in the second argument leads to a syntax error + * when the macro is not used with a string literal. Unfortunately + * the error is not overly explicit. + */ + +/* BLOB_APPEND_BLOB -- append blob contents to another */ +#define BLOB_APPEND_BLOB(dest, src) \ + blob_append((dest), blob_buffer(src), blob_size(src)) + + +/* HTML escapes +** +** html_escape() converts < to <, > to >, and & to &. +** html_quote() goes further and converts " into " and ' in '. +*/ +static void html_quote(struct Blob *ob, const char *data, size_t size){ + size_t beg = 0, i = 0; + while( i' ){ + BLOB_APPEND_LITERAL(ob, ">"); + }else if( data[i]=='&' ){ + BLOB_APPEND_LITERAL(ob, "&"); + }else if( data[i]=='"' ){ + BLOB_APPEND_LITERAL(ob, """); + }else if( data[i]=='\'' ){ + BLOB_APPEND_LITERAL(ob, "'"); + }else{ + break; + } + i++; + } + } +} +static void html_escape(struct Blob *ob, const char *data, size_t size){ + size_t beg = 0, i = 0; + while( i' ){ + BLOB_APPEND_LITERAL(ob, ">"); + }else if( data[i]=='&' ){ + BLOB_APPEND_LITERAL(ob, "&"); + }else{ + break; + } + i++; + } + } +} + + +/* HTML block tags */ + +/* Size of the prolog: "
    \n" */ +#define PROLOG_SIZE 23 + +static void html_prolog(struct Blob *ob, void *opaque){ + INTER_BLOCK(ob); + BLOB_APPEND_LITERAL(ob, "
    \n"); + assert( blob_size(ob)==PROLOG_SIZE ); +} + +static void html_epilog(struct Blob *ob, void *opaque){ + INTER_BLOCK(ob); + BLOB_APPEND_LITERAL(ob, "
    \n"); +} + +static void html_blockhtml(struct Blob *ob, struct Blob *text, void *opaque){ + char *data = blob_buffer(text); + size_t size = blob_size(text); + Blob *title = ((MarkdownToHtml*)opaque)->output_title; + while( size>0 && fossil_isspace(data[0]) ){ data++; size--; } + while( size>0 && fossil_isspace(data[size-1]) ){ size--; } + /* If the first raw block is an

    element, then use it as the title. */ + if( blob_size(ob)<=PROLOG_SIZE + && size>9 + && title!=0 + && sqlite3_strnicmp("", &data[size-5],5)==0 + ){ + int nTag = html_tag_length(data); + blob_append(title, data+nTag, size - nTag - 5); + return; + } + INTER_BLOCK(ob); + blob_append(ob, data, size); + BLOB_APPEND_LITERAL(ob, "\n"); +} + +static void html_blockcode(struct Blob *ob, struct Blob *text, void *opaque){ + INTER_BLOCK(ob); + BLOB_APPEND_LITERAL(ob, "
    ");
    +  html_escape(ob, blob_buffer(text), blob_size(text));
    +  BLOB_APPEND_LITERAL(ob, "
    \n"); +} + +static void html_blockquote(struct Blob *ob, struct Blob *text, void *opaque){ + INTER_BLOCK(ob); + BLOB_APPEND_LITERAL(ob, "
    \n"); + BLOB_APPEND_BLOB(ob, text); + BLOB_APPEND_LITERAL(ob, "
    \n"); +} + +static void html_header( + struct Blob *ob, + struct Blob *text, + int level, + void *opaque +){ + struct Blob *title = ((MarkdownToHtml*)opaque)->output_title; + /* The first header at the beginning of a text is considered as + * a title and not output. */ + if( blob_size(ob)<=PROLOG_SIZE && title!=0 && blob_size(title)==0 ){ + BLOB_APPEND_BLOB(title, text); + return; + } + INTER_BLOCK(ob); + blob_appendf(ob, "", level); + BLOB_APPEND_BLOB(ob, text); + blob_appendf(ob, "", level); +} + +static void html_hrule(struct Blob *ob, void *opaque){ + INTER_BLOCK(ob); + BLOB_APPEND_LITERAL(ob, "
    \n"); +} + + +static void html_list( + struct Blob *ob, + struct Blob *text, + int flags, + void *opaque +){ + char ol[] = "ol"; + char ul[] = "ul"; + char *tag = (flags & MKD_LIST_ORDERED) ? ol : ul; + INTER_BLOCK(ob); + blob_appendf(ob, "<%s>\n", tag); + BLOB_APPEND_BLOB(ob, text); + blob_appendf(ob, "\n", tag); +} + +static void html_list_item( + struct Blob *ob, + struct Blob *text, + int flags, + void *opaque +){ + char *text_data = blob_buffer(text); + size_t text_size = blob_size(text); + while( text_size>0 && text_data[text_size-1]=='\n' ) text_size--; + BLOB_APPEND_LITERAL(ob, "
  • "); + blob_append(ob, text_data, text_size); + BLOB_APPEND_LITERAL(ob, "
  • \n"); +} + +static void html_paragraph(struct Blob *ob, struct Blob *text, void *opaque){ + INTER_BLOCK(ob); + BLOB_APPEND_LITERAL(ob, "

    "); + BLOB_APPEND_BLOB(ob, text); + BLOB_APPEND_LITERAL(ob, "

    \n"); +} + + +static void html_table( + struct Blob *ob, + struct Blob *head_row, + struct Blob *rows, + void *opaque +){ + INTER_BLOCK(ob); + BLOB_APPEND_LITERAL(ob, "\n"); + if( head_row && blob_size(head_row)>0 ){ + BLOB_APPEND_LITERAL(ob, "\n"); + BLOB_APPEND_BLOB(ob, head_row); + BLOB_APPEND_LITERAL(ob, "\n\n"); + } + if( rows ){ + BLOB_APPEND_BLOB(ob, rows); + } + if( head_row && blob_size(head_row)>0 ){ + BLOB_APPEND_LITERAL(ob, "\n"); + } + BLOB_APPEND_LITERAL(ob, "
    \n"); +} + +static void html_table_cell( + struct Blob *ob, + struct Blob *text, + int flags, + void *opaque +){ + if( flags & MKD_CELL_HEAD ){ + BLOB_APPEND_LITERAL(ob, " "); + BLOB_APPEND_BLOB(ob, text); + if( flags & MKD_CELL_HEAD ){ + BLOB_APPEND_LITERAL(ob, "\n"); + }else{ + BLOB_APPEND_LITERAL(ob, "\n"); + } +} + +static void html_table_row( + struct Blob *ob, + struct Blob *cells, + int flags, + void *opaque +){ + BLOB_APPEND_LITERAL(ob, " \n"); + BLOB_APPEND_BLOB(ob, cells); + BLOB_APPEND_LITERAL(ob, " \n"); +} + + + +/* HTML span tags */ + +static int html_raw_html_tag(struct Blob *ob, struct Blob *text, void *opaque){ + blob_append(ob, blob_buffer(text), blob_size(text)); + return 1; +} + +static int html_autolink( + struct Blob *ob, + struct Blob *link, + enum mkd_autolink type, + void *opaque +){ + if( !link || blob_size(link)<=0 ) return 0; + BLOB_APPEND_LITERAL(ob, ""); + if( type==MKDA_EXPLICIT_EMAIL && blob_size(link)>7 ){ + /* remove "mailto:" from displayed text */ + html_escape(ob, blob_buffer(link)+7, blob_size(link)-7); + }else{ + html_escape(ob, blob_buffer(link), blob_size(link)); + } + BLOB_APPEND_LITERAL(ob, ""); + return 1; +} + +/* +** The nSrc bytes at zSrc[] are Pikchr input text (allegedly). Process that +** text and insert the result in place of the original. +*/ +void pikchr_to_html( + Blob *ob, /* Write the generated SVG here */ + const char *zSrc, int nSrc, /* The Pikchr source text */ + const char *zArg, int nArg /* Addition arguments */ +){ + int pikFlags = PIKCHR_PROCESS_NONCE + | PIKCHR_PROCESS_DIV + | PIKCHR_PROCESS_SRC + | PIKCHR_PROCESS_ERR_PRE; + Blob bSrc = empty_blob; + const char *zPikVar; + double rPikVar; + + while( nArg>0 ){ + int i; + for(i=0; i=0.1 + && rPikVar<10.0 + ){ + blob_appendf(&bSrc, "scale = %.13g\n", rPikVar); + } + zPikVar = skin_detail("pikchr-fontscale"); + if( zPikVar + && (rPikVar = atof(zPikVar))>=0.1 + && rPikVar<10.0 + ){ + blob_appendf(&bSrc, "fontscale = %.13g\n", rPikVar); + } + blob_append(&bSrc, zSrc, nSrc) + /*have to dup input to ensure a NUL-terminated source string */; + pikchr_process(blob_str(&bSrc), pikFlags, 0, ob); + blob_reset(&bSrc); +} + +/* Invoked for `...` blocks where there are nSep grave accents in a +** row that serve as the delimiter. According to CommonMark: +** +** * https://spec.commonmark.org/0.29/#fenced-code-blocks +** * https://spec.commonmark.org/0.29/#code-spans +** +** If nSep is 1 or 2, then this is a code-span which is inline. +** If nSep is 3 or more, then this is a fenced code block +*/ +static int html_codespan( + struct Blob *ob, /* Write the output here */ + struct Blob *text, /* The stuff in between the code span marks */ + int nSep, /* Number of grave accents marks as delimiters */ + void *opaque +){ + if( text==0 ){ + /* no-op */ + }else if( nSep<=2 ){ + /* One or two graves: an in-line code span */ + BLOB_APPEND_LITERAL(ob, ""); + html_escape(ob, blob_buffer(text), blob_size(text)); + BLOB_APPEND_LITERAL(ob, ""); + }else{ + /* Three or more graves: a fenced code block */ + int n = blob_size(text); + const char *z = blob_buffer(text); + int i; + for(i=0; i=n ){ + blob_appendf(ob, "
    %#h
    ", n, z); + }else{ + int k, j; + i++; + for(k=0; k%#h

  • ", n-i, z+i); + }else{ + for(j=k+1; j%#h", + j-k, z+k, n-i, z+i); + } + } + } + } + return 1; +} + +static int html_double_emphasis( + struct Blob *ob, + struct Blob *text, + char c, + void *opaque +){ + BLOB_APPEND_LITERAL(ob, ""); + BLOB_APPEND_BLOB(ob, text); + BLOB_APPEND_LITERAL(ob, ""); + return 1; +} + +static int html_emphasis( + struct Blob *ob, + struct Blob *text, + char c, + void *opaque +){ + BLOB_APPEND_LITERAL(ob, ""); + BLOB_APPEND_BLOB(ob, text); + BLOB_APPEND_LITERAL(ob, ""); + return 1; +} + +static int html_image( + struct Blob *ob, + struct Blob *link, + struct Blob *title, + struct Blob *alt, + void *opaque +){ + BLOB_APPEND_LITERAL(ob, "\"");0 ){ + BLOB_APPEND_LITERAL(ob, "\" title=\""); + html_quote(ob, blob_buffer(title), blob_size(title)); + } + BLOB_APPEND_LITERAL(ob, "\" />"); + return 1; +} + +static int html_linebreak(struct Blob *ob, void *opaque){ + BLOB_APPEND_LITERAL(ob, "
    \n"); + return 1; +} + +static int html_link( + struct Blob *ob, + struct Blob *link, + struct Blob *title, + struct Blob *content, + void *opaque +){ + char *zLink = blob_buffer(link); + char *zTitle = title!=0 && blob_size(title)>0 ? blob_str(title) : 0; + char zClose[20]; + + if( zLink==0 || zLink[0]==0 ){ + zClose[0] = 0; + }else{ + static const int flags = + WIKI_NOBADLINKS | + WIKI_MARKDOWNLINKS + ; + wiki_resolve_hyperlink(ob, flags, zLink, zClose, sizeof(zClose), 0, zTitle); + } + if( blob_size(content)==0 ){ + if( link ) BLOB_APPEND_BLOB(ob, link); + }else{ + BLOB_APPEND_BLOB(ob, content); + } + blob_append(ob, zClose, -1); + return 1; +} + +static int html_triple_emphasis( + struct Blob *ob, + struct Blob *text, + char c, + void *opaque +){ + BLOB_APPEND_LITERAL(ob, ""); + BLOB_APPEND_BLOB(ob, text); + BLOB_APPEND_LITERAL(ob, ""); + return 1; +} + + +static void html_normal_text(struct Blob *ob, struct Blob *text, void *opaque){ + html_escape(ob, blob_buffer(text), blob_size(text)); +} + +/* +** Convert markdown into HTML. +** +** The document title is placed in output_title if not NULL. Or if +** output_title is NULL, the document title appears in the body. +*/ +void markdown_to_html( + struct Blob *input_markdown, /* Markdown content to be rendered */ + struct Blob *output_title, /* Put title here. May be NULL */ + struct Blob *output_body /* Put document body here. */ +){ + struct mkd_renderer html_renderer = { + /* prolog and epilog */ + html_prolog, + html_epilog, + + /* block level elements */ + html_blockcode, + html_blockquote, + html_blockhtml, + html_header, + html_hrule, + html_list, + html_list_item, + html_paragraph, + html_table, + html_table_cell, + html_table_row, + + /* span level elements */ + html_autolink, + html_codespan, + html_double_emphasis, + html_emphasis, + html_image, + html_linebreak, + html_link, + html_raw_html_tag, + html_triple_emphasis, + + /* low level elements */ + 0, /* entity */ + html_normal_text, + + /* misc. parameters */ + "*_", /* emph_chars */ + 0 /* opaque */ + }; + MarkdownToHtml context; + memset(&context, 0, sizeof(context)); + context.output_title = output_title; + html_renderer.opaque = &context; + if( output_title ) blob_reset(output_title); + blob_reset(output_body); + markdown(output_body, input_markdown, &html_renderer); +} Index: src/md5.c ================================================================== --- src/md5.c +++ src/md5.c @@ -16,14 +16,25 @@ * 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 "config.h" #include #include #include #include "md5.h" + +#ifdef FOSSIL_ENABLE_SSL + +# include +# define MD5Context MD5_CTX +# define MD5Init MD5_Init +# define MD5Update MD5_Update +# define MD5Final MD5_Final + +#else /* * If compiled on a machine that doesn't have a 32-bit integer, * you just set "uint32" to the appropriate datatype for an * unsigned 32-bit integer. For example: @@ -41,12 +52,19 @@ uint32 bits[2]; unsigned char in[64]; }; typedef struct Context MD5Context; +#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__) || defined(_WIN32) +# define byteReverse(A,B) +#else /* - * Note: this code is harmless on little-endian machines. + * 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; do { t = (uint32)((unsigned)buf[3]<<8 | buf[2]) << 16 | @@ -53,10 +71,12 @@ ((unsigned)buf[1]<<8 | buf[0]); *(uint32 *)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) @@ -157,11 +177,11 @@ /* * Start MD5 accumulation. Set bit count to 0 and buffer to mysterious * initialization constants. */ static void MD5Init(MD5Context *ctx){ - ctx->isInit = 1; + ctx->isInit = 1; ctx->buf[0] = 0x67452301; ctx->buf[1] = 0xefcdab89; ctx->buf[2] = 0x98badcfe; ctx->buf[3] = 0x10325476; ctx->bits[0] = 0; @@ -170,11 +190,11 @@ /* * Update context to reflect the concatenation of another buffer full * of bytes. */ -static +static void MD5Update(MD5Context *pCtx, const unsigned char *buf, unsigned int len){ struct Context *ctx = (struct Context *)pCtx; uint32 t; /* Update bitcount */ @@ -217,11 +237,11 @@ memcpy(ctx->in, buf, len); } /* - * Final wrapup - pad to 64-byte boundary with the bit pattern + * Final wrapup - pad to 64-byte boundary with the bit pattern * 1 0* (64-bit count of bits processed, MSB-first) */ static void MD5Final(unsigned char digest[16], MD5Context *pCtx){ struct Context *ctx = (struct Context *)pCtx; unsigned count; @@ -252,27 +272,27 @@ memset(p, 0, count-8); } byteReverse(ctx->in, 14); /* Append length in bits and transform */ - ((uint32 *)ctx->in)[ 14 ] = ctx->bits[0]; - ((uint32 *)ctx->in)[ 15 ] = ctx->bits[1]; + memcpy(&ctx->in[14*sizeof(uint32)], ctx->bits, 2*sizeof(uint32)); MD5Transform(ctx->buf, (uint32 *)ctx->in); byteReverse((unsigned char *)ctx->buf, 4); memcpy(digest, ctx->buf, 16); - memset(ctx, 0, sizeof(ctx)); /* In case it's sensitive */ + memset(ctx, 0, sizeof(*ctx)); /* In case it's sensitive */ } +#endif /* ** Convert a digest into base-16. digest should be declared as ** "unsigned char digest[16]" in the calling function. The MD5 ** digest is stored in the first 16 bytes. zBuf should ** be "char zBuf[33]". */ static void DigestToBase16(unsigned char *digest, char *zBuf){ - static char const zEncode[] = "0123456789abcdef"; + static const char zEncode[] = "0123456789abcdef"; int i, j; for(j=i=0; i<16; i++){ int a = digest[i]; zBuf[j++] = zEncode[(a>>4)&0xf]; @@ -314,14 +334,34 @@ ** 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)); } + +/* +** For trouble-shooting only: +** +** Report the current state of the incremental checksum. +*/ +const char *md5sum_current_state(void){ + unsigned int cksum = 0; + unsigned int *pFirst, *pLast; + static char zResult[12]; + + pFirst = (unsigned int*)&incrCtx; + pLast = (unsigned int*)((&incrCtx)+1); + while( pFirst +/* +** Print information about a particular check-in. +*/ +void print_checkin_description(int rid, int indent, const char *zLabel){ + Stmt q; + db_prepare(&q, + "SELECT datetime(mtime,toLocal())," + " coalesce(euser,user), coalesce(ecomment,comment)," + " (SELECT uuid FROM blob WHERE rid=%d)," + " (SELECT group_concat(substr(tagname,5), ', ') FROM tag, tagxref" + " WHERE tagname GLOB 'sym-*' AND tag.tagid=tagxref.tagid" + " AND tagxref.rid=%d AND tagxref.tagtype>0)" + " FROM event WHERE objid=%d", rid, rid, rid); + if( db_step(&q)==SQLITE_ROW ){ + const char *zTagList = db_column_text(&q, 4); + char *zCom; + if( zTagList && zTagList[0] ){ + zCom = mprintf("%s (%s)", db_column_text(&q, 2), zTagList); + }else{ + zCom = mprintf("%s", db_column_text(&q,2)); + } + fossil_print("%-*s [%S] by %s on %s\n%*s", + indent-1, zLabel, + db_column_text(&q, 3), + db_column_text(&q, 1), + db_column_text(&q, 0), + indent, ""); + comment_print(zCom, db_column_text(&q,2), indent, -1, get_comment_format()); + fossil_free(zCom); + } + db_finalize(&q); +} + + +/* Pick the most recent leaf that is (1) not equal to vid and (2) +** has not already been merged into vid and (3) the leaf is not +** closed and (4) the leaf is in the same branch as vid. +** +** Set vmergeFlag to control whether the vmerge table is checked. +*/ +int fossil_find_nearest_fork(int vid, int vmergeFlag){ + Blob sql; + Stmt q; + int rid = 0; + + blob_zero(&sql); + blob_append_sql(&sql, + "SELECT leaf.rid" + " FROM leaf, event" + " WHERE leaf.rid=event.objid" + " AND leaf.rid!=%d", /* Constraint (1) */ + vid + ); + if( vmergeFlag ){ + blob_append_sql(&sql, + " AND leaf.rid NOT IN (SELECT merge FROM vmerge)" /* Constraint (2) */ + ); + } + blob_append_sql(&sql, + " AND NOT EXISTS(SELECT 1 FROM tagxref" /* Constraint (3) */ + " WHERE rid=leaf.rid" + " AND tagid=%d" + " AND tagtype>0)" + " AND (SELECT value FROM tagxref" /* Constraint (4) */ + " WHERE tagid=%d AND rid=%d AND tagtype>0) =" + " (SELECT value FROM tagxref" + " WHERE tagid=%d AND rid=leaf.rid AND tagtype>0)" + " ORDER BY event.mtime DESC LIMIT 1", + TAG_CLOSED, TAG_BRANCH, vid, TAG_BRANCH + ); + db_prepare(&q, "%s", blob_sql_text(&sql)); + blob_reset(&sql); + if( db_step(&q)==SQLITE_ROW ){ + rid = db_column_int(&q, 0); + } + db_finalize(&q); + return rid; +} + +/* +** Check content that was received with rcvid and return true if any +** fork was created. +*/ +int fossil_any_has_fork(int rcvid){ + static Stmt q; + int fForkSeen = 0; + + if( rcvid==0 ) return 0; + db_static_prepare(&q, + " SELECT pid FROM plink WHERE pid>0 AND isprim" + " AND cid IN (SELECT blob.rid FROM blob" + " WHERE rcvid=:rcvid)"); + db_bind_int(&q, ":rcvid", rcvid); + while( !fForkSeen && db_step(&q)==SQLITE_ROW ){ + int pid = db_column_int(&q, 0); + if( count_nonbranch_children(pid)>1 ){ + compute_leaves(pid,1); + if( db_int(0, "SELECT count(*) FROM leaves")>1 ){ + int rid = db_int(0, "SELECT rid FROM leaves, event" + " WHERE event.objid=leaves.rid" + " ORDER BY event.mtime DESC LIMIT 1"); + fForkSeen = fossil_find_nearest_fork(rid, db_open_local(0))!=0; + } + } + } + db_finalize(&q); + return fForkSeen; +} + +/* +** Add an entry to the FV table for all files renamed between +** version N and the version specified by vid. +*/ +static void add_renames( + const char *zFnCol, /* The FV column for the filename in vid */ + int vid, /* The desired version's checkin RID */ + int nid, /* The checkin rid for the name pivot */ + int revOK, /* OK to move backwards (child->parent) if true */ + const char *zDebug /* Generate trace output if not NULL */ +){ + int nChng; /* Number of file name changes */ + int *aChng; /* An array of file name changes */ + int i; /* Loop counter */ + find_filename_changes(nid, vid, revOK, &nChng, &aChng, zDebug); + if( nChng==0 ) return; + for(i=0; i0", + TAG_BRANCH, vid) + ); + } + db_prepare(&q, + "SELECT blob.uuid," + " datetime(event.mtime,toLocal())," + " coalesce(ecomment, comment)," + " coalesce(euser, user)" + " FROM event, blob" + " WHERE event.objid=%d AND blob.rid=%d", + mid, mid + ); + if( db_step(&q)==SQLITE_ROW ){ + char *zCom = mprintf("Merging fork [%S] at %s by %s: \"%s\"", + db_column_text(&q, 0), db_column_text(&q, 1), + db_column_text(&q, 3), db_column_text(&q, 2)); + comment_print(zCom, db_column_text(&q,2), 0, -1, get_comment_format()); + fossil_free(zCom); + } + db_finalize(&q); + }else{ + usage("?OPTIONS? ?VERSION?"); + return; } - if( mid>1 && !db_exists("SELECT 1 FROM plink WHERE cid=%d", mid) ){ - fossil_fatal("not a version: %s", g.argv[2]); + + if( zPivot ){ + pid = name_to_typed_rid(zPivot, "ci"); + if( pid==0 || !is_a_version(pid) ){ + fossil_fatal("not a version: %s", zPivot); + } + if( pickFlag ){ + fossil_fatal("incompatible options: --cherrypick and --baseline"); + } } if( pickFlag || backoutFlag ){ + if( integrateFlag ){ + fossil_fatal("incompatible options: --integrate and --cherrypick " + "with --backout"); + } pid = db_int(0, "SELECT pid FROM plink WHERE cid=%d AND isprim", mid); if( pid<=0 ){ fossil_fatal("cannot find an ancestor for %s", g.argv[2]); } - if( backoutFlag ){ - int t = pid; - pid = mid; - mid = t; - } }else{ + if( !zPivot ){ + pivot_set_primary(mid); + pivot_set_secondary(vid); + db_prepare(&q, "SELECT merge FROM vmerge WHERE id=0"); + while( db_step(&q)==SQLITE_ROW ){ + pivot_set_secondary(db_column_int(&q,0)); + } + db_finalize(&q); + pid = pivot_find(0); + if( pid<=0 ){ + fossil_fatal("cannot find a common ancestor between the current " + "checkout and %s", g.argv[2]); + } + } pivot_set_primary(mid); pivot_set_secondary(vid); - db_prepare(&q, "SELECT merge FROM vmerge WHERE id=0"); - while( db_step(&q)==SQLITE_ROW ){ - pivot_set_secondary(db_column_int(&q,0)); - } - db_finalize(&q); - pid = pivot_find(); - if( pid<=0 ){ - fossil_fatal("cannot find a common ancestor between the current" - "checkout and %s", g.argv[2]); - } - } - if( pid>1 && !db_exists("SELECT 1 FROM plink WHERE cid=%d", pid) ){ - fossil_fatal("not a version: record #%d", mid); - } - vfile_check_signature(vid, 1); + nid = pivot_find(1); + if( nid!=pid ){ + pivot_set_primary(nid); + pivot_set_secondary(pid); + nid = pivot_find(1); + } + } + if( backoutFlag ){ + int t = pid; + pid = mid; + mid = t; + } + if( nid==0 ) nid = pid; + if( !is_a_version(pid) ){ + fossil_fatal("not a version: record #%d", pid); + } + if( !forceFlag && mid==pid ){ + fossil_print("Merge skipped because it is a no-op. " + " Use --force to override.\n"); + return; + } + if( integrateFlag && !is_a_leaf(mid)){ + fossil_warning("ignoring --integrate: %s is not a leaf", g.argv[2]); + integrateFlag = 0; + } + if( integrateFlag && content_is_private(mid) ){ + fossil_warning( + "ignoring --integrate: %s is on a private branch" + "\n Use \"fossil amend --close\" (after commit) to close the leaf.", + g.argv[2]); + integrateFlag = 0; + } + if( verboseFlag ){ + print_checkin_description(mid, 12, + integrateFlag ? "integrate:" : "merge-from:"); + print_checkin_description(pid, 12, "baseline:"); + } + vfile_check_signature(vid, CKSIG_ENOTFILE); db_begin_transaction(); - undo_begin(); - load_vfile_from_rid(mid); - load_vfile_from_rid(pid); + if( !dryRunFlag ) undo_begin(); + if( load_vfile_from_rid(mid) && !forceMissingFlag ){ + fossil_fatal("missing content, unable to merge"); + } + if( load_vfile_from_rid(pid) && !forceMissingFlag ){ + fossil_fatal("missing content, unable to merge"); + } + if( zPivot ){ + vAncestor = db_exists( + "WITH RECURSIVE ancestor(id) AS (" + " VALUES(%d)" + " UNION" + " SELECT pid FROM plink, ancestor" + " WHERE cid=ancestor.id AND pid!=%d AND cid!=%d)" + "SELECT 1 FROM ancestor WHERE id=%d LIMIT 1", + vid, nid, pid, pid + ) ? 'p' : 'n'; + } + if( debugFlag ){ + char *z; + z = db_text(0, "SELECT uuid FROM blob WHERE rid=%d", nid); + fossil_print("N=%-4d %z (file rename pivot)\n", nid, z); + z = db_text(0, "SELECT uuid FROM blob WHERE rid=%d", pid); + fossil_print("P=%-4d %z (file content pivot)\n", pid, z); + z = db_text(0, "SELECT uuid FROM blob WHERE rid=%d", mid); + fossil_print("M=%-4d %z (merged-in version)\n", mid, z); + z = db_text(0, "SELECT uuid FROM blob WHERE rid=%d", vid); + fossil_print("V=%-4d %z (current version)\n", vid, z); + fossil_print("vAncestor = '%c'\n", vAncestor); + } + if( showVfileFlag ) debug_show_vfile(); /* ** The vfile.pathname 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. */ db_multi_exec( "DROP TABLE IF EXISTS fv;" - "CREATE TEMP TABLE fv(" - " fn TEXT PRIMARY KEY," /* The filename */ - " idv INTEGER," /* VFILE entry for current version */ - " idp INTEGER," /* VFILE entry for the pivot */ - " idm INTEGER," /* VFILE entry for version merging in */ - " chnged BOOLEAN," /* True if current version has been edited */ - " ridv INTEGER," /* Record ID for current version */ - " ridp INTEGER," /* Record ID for pivot */ - " ridm INTEGER" /* Record ID for merge */ - ");" - "INSERT OR IGNORE INTO fv" - " SELECT pathname, 0, 0, 0, 0, 0, 0, 0 FROM vfile" - ); - db_prepare(&q, - "SELECT id, pathname, rid FROM vfile" - " WHERE vid=%d", pid - ); - while( db_step(&q)==SQLITE_ROW ){ - int id = db_column_int(&q, 0); - const char *fn = db_column_text(&q, 1); - int rid = db_column_int(&q, 2); - db_multi_exec( - "UPDATE fv SET idp=%d, ridp=%d WHERE fn=%Q", - id, rid, fn - ); - } - db_finalize(&q); - db_prepare(&q, - "SELECT id, pathname, rid FROM vfile" - " WHERE vid=%d", mid - ); - while( db_step(&q)==SQLITE_ROW ){ - int id = db_column_int(&q, 0); - const char *fn = db_column_text(&q, 1); - int rid = db_column_int(&q, 2); - db_multi_exec( - "UPDATE fv SET idm=%d, ridm=%d WHERE fn=%Q", - id, rid, fn - ); - } - db_finalize(&q); - db_prepare(&q, - "SELECT id, pathname, rid, chnged FROM vfile" - " WHERE vid=%d", vid - ); - while( db_step(&q)==SQLITE_ROW ){ - int id = db_column_int(&q, 0); - const char *fn = db_column_text(&q, 1); - int rid = db_column_int(&q, 2); - int chnged = db_column_int(&q, 3); - db_multi_exec( - "UPDATE fv SET idv=%d, ridv=%d, chnged=%d WHERE fn=%Q", - id, rid, chnged, fn - ); - } - db_finalize(&q); - - /* - ** Find files in mid and vid but not in pid and report conflicts. - ** The file in mid will be ignored. It will be treated as if it + "CREATE TEMP TABLE fv(\n" + " fn TEXT UNIQUE %s,\n" /* The filename */ + " idv INTEGER DEFAULT 0,\n" /* VFILE entry for current version */ + " idp INTEGER DEFAULT 0,\n" /* VFILE entry for the pivot */ + " idm INTEGER DEFAULT 0,\n" /* VFILE entry for version merging in */ + " chnged BOOLEAN,\n" /* True if current version has been edited */ + " ridv INTEGER DEFAULT 0,\n" /* Record ID for current version */ + " ridp INTEGER DEFAULT 0,\n" /* Record ID for pivot */ + " ridm INTEGER DEFAULT 0,\n" /* Record ID for merge */ + " isexe BOOLEAN,\n" /* Execute permission enabled */ + " fnp TEXT UNIQUE %s,\n" /* The filename in the pivot */ + " fnm TEXT UNIQUE %s,\n" /* The filename in the merged version */ + " fnn TEXT UNIQUE %s,\n" /* The filename in the name pivot */ + " islinkv BOOLEAN,\n" /* True if current version is a symlink */ + " islinkm BOOLEAN\n" /* True if merged version in is a symlink */ + ");", + filename_collation(), filename_collation(), filename_collation(), + filename_collation() + ); + + /* + ** Compute name changes from N to V, P, and M + */ + add_renames("fn", vid, nid, 0, debugFlag ? "N->V" : 0); + add_renames("fnp", pid, nid, 0, debugFlag ? "N->P" : 0); + add_renames("fnm", mid, nid, backoutFlag, debugFlag ? "N->M" : 0); + if( debugFlag ){ + fossil_print("******** FV after name change search *******\n"); + debug_fv_dump(1); + } + if( nid!=pid ){ + /* See forum thread https://fossil-scm.org/forum/forumpost/549700437b + ** + ** If a filename changes between nid and one of the other check-ins + ** pid, vid, or mid, then it might not have changed for all of them. + ** try to fill in the appropriate filename in all slots where the + ** name is missing. + ** + ** This does not work if + ** (1) The filename changes more than once in between nid and vid/mid + ** (2) Two or more filenames swap places - for example if A is renamed + ** to B and B is renamed to A. + ** The Fossil merge algorithm breaks down in those cases. It will need + ** to be completely rewritten to handle such complex cases. Such cases + ** appear to be rare, and also confusing to humans. + */ + db_multi_exec( + "UPDATE OR IGNORE fv SET fnp=vfile.pathname FROM vfile" + " WHERE fnp IS NULL" + " AND vfile.pathname = fv.fnn" + " AND vfile.vid=%d;", + pid + ); + db_multi_exec( + "UPDATE OR IGNORE fv SET fn=vfile.pathname FROM vfile" + " WHERE fn IS NULL" + " AND vfile.pathname = coalesce(fv.fnp,fv.fnn)" + " AND vfile.vid=%d;", + vid + ); + db_multi_exec( + "UPDATE OR IGNORE fv SET fnm=vfile.pathname FROM vfile" + " WHERE fnm IS NULL" + " AND vfile.pathname = coalesce(fv.fnp,fv.fnn)" + " AND vfile.vid=%d;", + mid + ); + db_multi_exec( + "UPDATE OR IGNORE fv SET fnp=vfile.pathname FROM vfile" + " WHERE fnp IS NULL" + " AND vfile.pathname IN (fv.fnm,fv.fn)" + " AND vfile.vid=%d;", + pid + ); + db_multi_exec( + "UPDATE OR IGNORE fv SET fn=vfile.pathname FROM vfile" + " WHERE fn IS NULL" + " AND vfile.pathname = fv.fnm" + " AND vfile.vid=%d;", + vid + ); + db_multi_exec( + "UPDATE OR IGNORE fv SET fnm=vfile.pathname FROM vfile" + " WHERE fnm IS NULL" + " AND vfile.pathname = fv.fn" + " AND vfile.vid=%d;", + mid + ); + } + if( debugFlag ){ + fossil_print("******** FV after name change fill-in *******\n"); + debug_fv_dump(1); + } + + /* + ** Add files found in V + */ + db_multi_exec( + "UPDATE OR IGNORE fv SET fn=coalesce(fn%c,fnn) WHERE fn IS NULL;" + "REPLACE INTO fv(fn,fnp,fnm,fnn,idv,ridv,islinkv,isexe,chnged)" + " SELECT pathname, fnp, fnm, fnn, id, rid, islink, vf.isexe, vf.chnged" + " FROM vfile vf" + " LEFT JOIN fv ON fn=coalesce(origname,pathname)" + " AND rid>0 AND vf.chnged NOT IN (3,5)" + " WHERE vid=%d;", + vAncestor, vid + ); + if( debugFlag>=2 ){ + fossil_print("******** FV after adding files in current version *******\n"); + debug_fv_dump(1); + } + + /* + ** Add files found in P + */ + db_multi_exec( + "UPDATE OR IGNORE fv SET fnp=coalesce(fnn," + " (SELECT coalesce(origname,pathname) FROM vfile WHERE id=idv))" + " WHERE fnp IS NULL;" + "INSERT OR IGNORE INTO fv(fnp)" + " SELECT coalesce(origname,pathname) FROM vfile WHERE vid=%d;", + pid + ); + if( debugFlag>=2 ){ + fossil_print("******** FV after adding pivot files *******\n"); + debug_fv_dump(1); + } + + /* + ** Add files found in M + */ + db_multi_exec( + "UPDATE OR IGNORE fv SET fnm=fnp WHERE fnm IS NULL;" + "INSERT OR IGNORE INTO fv(fnm)" + " SELECT pathname FROM vfile WHERE vid=%d;", + mid + ); + if( debugFlag>=2 ){ + fossil_print("******** FV after adding merge-in files *******\n"); + debug_fv_dump(1); + } + + /* + ** Compute the file version ids for P and M + */ + if( pid==vid ){ + db_multi_exec( + "UPDATE fv SET idp=idv, ridp=ridv WHERE ridv>0 AND chnged NOT IN (3,5)" + ); + }else{ + db_multi_exec( + "UPDATE fv SET idp=coalesce(vfile.id,0), ridp=coalesce(vfile.rid,0)" + " FROM vfile" + " WHERE vfile.vid=%d AND fv.fnp=vfile.pathname", + pid + ); + } + db_multi_exec( + "UPDATE fv SET" + " idm=coalesce(vfile.id,0)," + " ridm=coalesce(vfile.rid,0)," + " islinkm=coalesce(vfile.islink,0)," + " isexe=coalesce(vfile.isexe,fv.isexe)" + " FROM vfile" + " WHERE vid=%d AND fnm=pathname", + mid + ); + + /* + ** Update the execute bit on files where it's changed from P->M but not P->V + */ + db_prepare(&q, + "SELECT idv, fn, fv.isexe FROM fv, vfile p, vfile v" + " WHERE p.id=idp AND v.id=idv AND fv.isexe!=p.isexe AND v.isexe=p.isexe" + ); + while( db_step(&q)==SQLITE_ROW ){ + int idv = db_column_int(&q, 0); + const char *zName = db_column_text(&q, 1); + int isExe = db_column_int(&q, 2); + fossil_print("%s %s\n", isExe ? "EXECUTABLE" : "UNEXEC", zName); + if( !dryRunFlag ){ + char *zFullPath = mprintf("%s/%s", g.zLocalRoot, zName); + file_setexe(zFullPath, isExe); + free(zFullPath); + db_multi_exec("UPDATE vfile SET isexe=%d WHERE id=%d", isExe, idv); + } + } + db_finalize(&q); + if( debugFlag ){ + fossil_print("******** FV final *******\n"); + debug_fv_dump( debugFlag>=2 ); + } + + /************************************************************************ + ** All of the information needed to do the merge is now contained in the + ** FV table. Starting here, we begin to actually carry out the merge. + ** + ** First, find files in M and V but not in P and report conflicts. + ** The file in M will be ignored. It will be treated as if it ** does not exist. */ db_prepare(&q, "SELECT idm FROM fv WHERE idp=0 AND idv>0 AND idm>0" ); while( db_step(&q)==SQLITE_ROW ){ int idm = db_column_int(&q, 0); char *zName = db_text(0, "SELECT pathname FROM vfile WHERE id=%d", idm); - printf("WARNING: conflict on %s\n", zName); + fossil_warning("WARNING: no common ancestor for %s", zName); free(zName); db_multi_exec("UPDATE fv SET idm=0 WHERE idm=%d", idm); } db_finalize(&q); /* - ** Add to vid files that are not in pid but are in mid - */ - db_prepare(&q, - "SELECT idm, rowid, fn FROM fv WHERE idp=0 AND idv=0 AND idm>0" - ); - while( db_step(&q)==SQLITE_ROW ){ - int idm = db_column_int(&q, 0); - int rowid = db_column_int(&q, 1); - int idv; - const char *zName; - db_multi_exec( - "INSERT INTO vfile(vid,chnged,deleted,rid,mrid,pathname)" - " SELECT %d,3,0,rid,mrid,pathname FROM vfile WHERE id=%d", - vid, idm - ); - idv = db_last_insert_rowid(); - db_multi_exec("UPDATE fv SET idv=%d WHERE rowid=%d", idv, rowid); - zName = db_column_text(&q, 2); - printf("ADDED %s\n", zName); - undo_save(zName); - vfile_to_disk(0, idm, 0); - } - db_finalize(&q); - - /* - ** Find files that have changed from pid->mid but not pid->vid. - ** Copy the mid content over into vid. - */ - db_prepare(&q, - "SELECT idv, ridm FROM fv" + ** Find files that have changed from P->M but not P->V. + ** Copy the M content over into V. + */ + db_prepare(&q, + "SELECT idv, ridm, fn, islinkm FROM fv" " WHERE idp>0 AND idv>0 AND idm>0" " AND ridm!=ridp AND ridv=ridp AND NOT chnged" ); while( db_step(&q)==SQLITE_ROW ){ int idv = db_column_int(&q, 0); int ridm = db_column_int(&q, 1); - char *zName = db_text(0, "SELECT pathname FROM vfile WHERE id=%d", idv); + const char *zName = db_column_text(&q, 2); + int islinkm = db_column_int(&q, 3); /* Copy content from idm over into idv. Overwrite idv. */ - printf("UPDATE %s\n", zName); - undo_save(zName); - db_multi_exec( - "UPDATE vfile SET mrid=%d, chnged=2 WHERE id=%d", ridm, idv - ); - vfile_to_disk(0, idv, 0); - free(zName); + fossil_print("UPDATE %s\n", zName); + if( !dryRunFlag ){ + undo_save(zName); + db_multi_exec( + "UPDATE vfile SET mtime=0, mrid=%d, chnged=%d, islink=%d," + " mhash=CASE WHEN rid<>%d" + " THEN (SELECT uuid FROM blob WHERE blob.rid=%d) END" + " WHERE id=%d", ridm, integrateFlag?4:2, islinkm, ridm, ridm, idv + ); + vfile_to_disk(0, idv, 0, 0); + } } db_finalize(&q); /* - ** Do a three-way merge on files that have changes pid->mid and pid->vid + ** Do a three-way merge on files that have changes on both P->M and P->V. */ db_prepare(&q, - "SELECT ridm, idv, ridp, ridv, %s FROM fv" + "SELECT ridm, idv, ridp, ridv, %s, fn, isexe, islinkv, islinkm FROM fv" " WHERE idp>0 AND idv>0 AND idm>0" " AND ridm!=ridp AND (ridv!=ridp OR chnged)", glob_expr("fv.fn", zBinGlob) ); while( db_step(&q)==SQLITE_ROW ){ @@ -254,75 +832,238 @@ int ridm = db_column_int(&q, 0); int idv = db_column_int(&q, 1); int ridp = db_column_int(&q, 2); int ridv = db_column_int(&q, 3); int isBinary = db_column_int(&q, 4); - int rc; - char *zName = db_text(0, "SELECT pathname FROM vfile WHERE id=%d", idv); - char *zFullPath; - Blob m, p, v, r; - /* Do a 3-way merge of idp->idm into idp->idv. The results go into idv. */ - if( detailFlag ){ - printf("MERGE %s (pivot=%d v1=%d v2=%d)\n", zName, ridp, ridm, ridv); - }else{ - printf("MERGE %s\n", zName); - } - undo_save(zName); - zFullPath = mprintf("%s/%s", g.zLocalRoot, zName); - content_get(ridp, &p); - content_get(ridm, &m); - blob_zero(&v); - blob_read_from_file(&v, zFullPath); - if( isBinary ){ - rc = -1; - blob_zero(&r); - }else{ - rc = blob_merge(&p, &m, &v, &r); - } - if( rc>=0 ){ - blob_write_to_file(&r, zFullPath); - if( rc>0 ){ - printf("***** %d merge conflicts in %s\n", rc, zName); - } - }else{ - printf("***** Cannot merge binary file %s\n", zName); - } - free(zName); - blob_reset(&p); - blob_reset(&m); - blob_reset(&v); - blob_reset(&r); - db_multi_exec("INSERT OR IGNORE INTO vmerge(id,merge) VALUES(%d,%d)", - idv,ridm); + const char *zName = db_column_text(&q, 5); + int isExe = db_column_int(&q, 6); + int islinkv = db_column_int(&q, 7); + int islinkm = db_column_int(&q, 8); + int rc; + char *zFullPath; + Blob m, p, r; + /* Do a 3-way merge of idp->idm into idp->idv. The results go into idv. */ + if( verboseFlag ){ + fossil_print("MERGE %s (pivot=%d v1=%d v2=%d)\n", + zName, ridp, ridm, ridv); + }else{ + fossil_print("MERGE %s\n", zName); + } + if( islinkv || islinkm ){ + fossil_print("***** Cannot merge symlink %s\n", zName); + nConflict++; + }else{ + if( !dryRunFlag ) undo_save(zName); + zFullPath = mprintf("%s/%s", g.zLocalRoot, zName); + content_get(ridp, &p); + content_get(ridm, &m); + if( isBinary ){ + rc = -1; + blob_zero(&r); + }else{ + unsigned mergeFlags = dryRunFlag ? MERGE_DRYRUN : 0; + if(keepMergeFlag!=0) mergeFlags |= MERGE_KEEP_FILES; + rc = merge_3way(&p, zFullPath, &m, &r, mergeFlags); + } + if( rc>=0 ){ + if( !dryRunFlag ){ + blob_write_to_file(&r, zFullPath); + file_setexe(zFullPath, isExe); + } + db_multi_exec("UPDATE vfile SET mtime=0 WHERE id=%d", idv); + if( rc>0 ){ + fossil_print("***** %d merge conflict%s in %s\n", + rc, rc>1 ? "s" : "", zName); + nConflict++; + } + }else{ + fossil_print("***** Cannot merge binary file %s\n", zName); + nConflict++; + } + blob_reset(&p); + blob_reset(&m); + blob_reset(&r); + } + vmerge_insert(idv, ridm); } db_finalize(&q); /* - ** Drop files from vid that are in pid but not in mid + ** Drop files that are in P and V but not in M */ db_prepare(&q, - "SELECT idv FROM fv" + "SELECT idv, fn, chnged FROM fv" " WHERE idp>0 AND idv>0 AND idm=0" ); while( db_step(&q)==SQLITE_ROW ){ int idv = db_column_int(&q, 0); - char *zName = db_text(0, "SELECT pathname FROM vfile WHERE id=%d", idv); + const char *zName = db_column_text(&q, 1); + int chnged = db_column_int(&q, 2); /* Delete the file idv */ - printf("DELETE %s\n", zName); - undo_save(zName); + fossil_print("DELETE %s\n", zName); + if( chnged ){ + fossil_warning("WARNING: local edits lost for %s", zName); + nConflict++; + } + if( !dryRunFlag ) undo_save(zName); db_multi_exec( "UPDATE vfile SET deleted=1 WHERE id=%d", idv ); - free(zName); + if( !dryRunFlag ){ + char *zFullPath = mprintf("%s%s", g.zLocalRoot, zName); + file_delete(zFullPath); + free(zFullPath); + } + } + db_finalize(&q); + + /* For certain sets of renames (e.g. A -> B and B -> A), a file that is + ** being renamed must first be moved to a temporary location to avoid + ** being overwritten by another rename operation. A row is added to the + ** TMPRN table for each of these temporary renames. + */ + db_multi_exec( + "DROP TABLE IF EXISTS tmprn;" + "CREATE TEMP TABLE tmprn(fn UNIQUE, tmpfn);" + ); + + /* + ** Rename files that have taken a rename on P->M but which keep the same + ** name on P->V. If a file is renamed on P->V only or on both P->V and + ** P->M then we retain the V name of the file. + */ + db_prepare(&q, + "SELECT idv, fnp, fnm, isexe FROM fv" + " WHERE idv>0 AND idp>0 AND idm>0 AND fnp=fn AND fnm!=fnp" + ); + while( db_step(&q)==SQLITE_ROW ){ + int idv = db_column_int(&q, 0); + const char *zOldName = db_column_text(&q, 1); + const char *zNewName = db_column_text(&q, 2); + int isExe = db_column_int(&q, 3); + fossil_print("RENAME %s -> %s\n", zOldName, zNewName); + if( !dryRunFlag ) undo_save(zOldName); + if( !dryRunFlag ) undo_save(zNewName); + db_multi_exec( + "UPDATE vfile SET pathname=NULL, origname=pathname" + " WHERE vid=%d AND pathname=%Q;" + "UPDATE vfile SET pathname=%Q, origname=coalesce(origname,pathname)" + " WHERE id=%d;", + vid, zNewName, zNewName, idv + ); + if( !dryRunFlag ){ + char *zFullOldPath, *zFullNewPath; + zFullOldPath = db_text(0,"SELECT tmpfn FROM tmprn WHERE fn=%Q", zOldName); + if( !zFullOldPath ){ + zFullOldPath = mprintf("%s%s", g.zLocalRoot, zOldName); + } + zFullNewPath = mprintf("%s%s", g.zLocalRoot, zNewName); + if( file_size(zFullNewPath, RepoFILE)>=0 ){ + Blob tmpPath; + file_tempname(&tmpPath, "", 0); + db_multi_exec("INSERT INTO tmprn(fn,tmpfn) VALUES(%Q,%Q)", + zNewName, blob_str(&tmpPath)); + if( file_islink(zFullNewPath) ){ + symlink_copy(zFullNewPath, blob_str(&tmpPath)); + }else{ + file_copy(zFullNewPath, blob_str(&tmpPath)); + } + blob_reset(&tmpPath); + } + if( file_islink(zFullOldPath) ){ + symlink_copy(zFullOldPath, zFullNewPath); + }else{ + file_copy(zFullOldPath, zFullNewPath); + } + file_setexe(zFullNewPath, isExe); + file_delete(zFullOldPath); + free(zFullNewPath); + free(zFullOldPath); + } + } + db_finalize(&q); + + /* A file that has been deleted and replaced by a renamed file will have a + ** NULL pathname. Change it to something that makes the output of "status" + ** and similar commands make sense for such files and that will (most likely) + ** not be an actual existing pathname. + */ + db_multi_exec( + "UPDATE vfile SET pathname=origname || ' (overwritten by rename)'" + " WHERE pathname IS NULL" + ); + + /* + ** Insert into V any files that are not in V or P but are in M. + */ + db_prepare(&q, + "SELECT idm, fnm FROM fv" + " WHERE idp=0 AND idv=0 AND idm>0" + ); + while( db_step(&q)==SQLITE_ROW ){ + int idm = db_column_int(&q, 0); + const char *zName; + char *zFullName; + db_multi_exec( + "REPLACE INTO vfile(vid,chnged,deleted,rid,mrid," + "isexe,islink,pathname,mhash)" + " SELECT %d,%d,0,rid,mrid,isexe,islink,pathname," + "CASE WHEN rid<>mrid" + " THEN (SELECT uuid FROM blob WHERE blob.rid=vfile.mrid) END " + "FROM vfile WHERE id=%d", + vid, integrateFlag?5:3, idm + ); + zName = db_column_text(&q, 1); + zFullName = mprintf("%s%s", g.zLocalRoot, zName); + if( file_isfile_or_link(zFullName) + && !db_exists("SELECT 1 FROM fv WHERE fn=%Q", zName) ){ + fossil_print("ADDED %s (overwrites an unmanaged file)\n", zName); + nOverwrite++; + }else{ + fossil_print("ADDED %s\n", zName); + } + fossil_free(zFullName); + if( !dryRunFlag ){ + undo_save(zName); + vfile_to_disk(0, idm, 0, 0); + } } db_finalize(&q); - + + /* Report on conflicts + */ + if( nConflict ){ + fossil_warning("WARNING: %d merge conflicts", nConflict); + } + if( nOverwrite ){ + fossil_warning("WARNING: %d unmanaged files were overwritten", + nOverwrite); + } + if( dryRunFlag ){ + fossil_warning("REMINDER: this was a dry run -" + " no files were actually changed."); + } + /* ** Clean up the mid and pid VFILE entries. Then commit the changes. */ db_multi_exec("DELETE FROM vfile WHERE vid!=%d", vid); - if( !pickFlag ){ - db_multi_exec("INSERT OR IGNORE INTO vmerge(id,merge) VALUES(0,%d)", mid); + if( pickFlag ){ + vmerge_insert(-1, mid); + /* For a cherry-pick merge, make the default check-in comment the same + ** as the check-in comment on the check-in that is being merged in. */ + db_multi_exec( + "REPLACE INTO vvar(name,value)" + " SELECT 'ci-comment', coalesce(ecomment,comment) FROM event" + " WHERE type='ci' AND objid=%d", + mid + ); + }else if( backoutFlag ){ + vmerge_insert(-2, pid); + }else if( integrateFlag ){ + vmerge_insert(-4, mid); + }else{ + vmerge_insert(0, mid); } - undo_finish(); - db_end_transaction(0); + if( !dryRunFlag ) undo_finish(); + db_end_transaction(dryRunFlag); } Index: src/merge3.c ================================================================== --- src/merge3.c +++ src/merge3.c @@ -27,11 +27,13 @@ #define DEBUG(X) #define ISDEBUG 0 #endif /* The minimum of two integers */ -#define min(A,B) (A0 && (aC[0]>0 || aC[1]>0 || aC[2]>0) ){ @@ -130,11 +133,52 @@ i += 3; } return i; } +/* +** Text of boundary markers for merge conflicts. +*/ +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 >>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>" +}; + +/* +** 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. +*/ +int contains_crlf(Blob *p){ + int i; + int j = 0; + const int maxL = 10; /* Max lines to check */ + const char *z = blob_buffer(p); + int n = blob_size(p)+1; + for(i=1; imaxL ) return 0; + } + return 0; +} +/* +** Ensure that the text in pBlob ends with a new line. +** If useCrLf is true adds "\r\n" otherwise '\n'. +*/ +void ensure_line_end(Blob *pBlob, int useCrLf){ + if( pBlob->nUsed<=0 ) return; + if( pBlob->aData[pBlob->nUsed-1]!='\n' ){ + if( useCrLf ) blob_append_char(pBlob, '\r'); + blob_append_char(pBlob, '\n'); + } +} /* ** Do a three-way merge. Initialize pOut to contain the result. ** ** The merge is an edit against pV2. Both pV1 and pV2 have a @@ -141,36 +185,51 @@ ** common origin at pPivot. Apply the changes of pPivot ==> pV1 ** to pV2. ** ** The return is 0 upon complete success. If any input file is binary, ** -1 is returned and pOut is unmodified. If there are merge -** conflicts, the merge proceeds as best as it can and the number +** conflicts, the merge proceeds as best as it can and the number ** of conflicts is returns */ -int blob_merge(Blob *pPivot, Blob *pV1, Blob *pV2, Blob *pOut){ +static int blob_merge(Blob *pPivot, Blob *pV1, Blob *pV2, Blob *pOut){ int *aC1; /* Changes from pPivot to pV1 */ int *aC2; /* 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 nConflict = 0; /* Number of merge conflicts seen so far */ - static const char zBegin[] = ">>>>>>> BEGIN MERGE CONFLICT\n"; - static const char zMid[] = "============================\n"; - static const char zEnd[] = "<<<<<<< END MERGE CONFLICT\n"; + int useCrLf = 0; blob_zero(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) ){ + blob_append(pOut, (char*)get_utf8_bom(0), -1); + } + + /* 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 = 1; + } /* 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. */ - aC1 = text_diff(pPivot, pV1, 0, 0); - aC2 = text_diff(pPivot, pV2, 0, 0); + aC1 = text_diff(pPivot, pV1, 0, 0, 0); + aC2 = text_diff(pPivot, pV2, 0, 0, 0); if( aC1==0 || aC2==0 ){ free(aC1); free(aC2); return -1; } @@ -188,11 +247,11 @@ DEBUG( for(i1=0; i1VERSION1 with the change going -** from PIVOT->VERSION2 and write the combined changes into MERGED. +** fossil 3-way-merge Xbase.c Xlocal.c Xup.c Xlocal.c +** cp Xup.c Xbase.c +** # Verify that everything still works +** fossil commit +** */ void delta_3waymerge_cmd(void){ Blob pivot, v1, v2, merged; - if( g.argc!=6 ){ - fprintf(stderr,"Usage: %s %s PIVOT V1 V2 MERGED\n", g.argv[0], g.argv[1]); - exit(1); - } - if( blob_read_from_file(&pivot, g.argv[2])<0 ){ - fprintf(stderr,"cannot read %s\n", g.argv[2]); - exit(1); - } - if( blob_read_from_file(&v1, g.argv[3])<0 ){ - fprintf(stderr,"cannot read %s\n", g.argv[3]); - exit(1); - } - if( blob_read_from_file(&v2, g.argv[4])<0 ){ - fprintf(stderr,"cannot read %s\n", g.argv[4]); - exit(1); - } - blob_merge(&pivot, &v1, &v2, &merged); - if( blob_write_to_file(&merged, g.argv[5])0 ) fossil_warning("WARNING: %d merge conflicts", nConflict); +} + +/* +** aSubst is an array of string pairs. The first element of each pair is +** a string that begins with %. The second element is a replacement for that +** string. +** +** This routine makes a copy of zInput into memory obtained from malloc and +** performance all applicable substitutions on that string. +*/ +char *string_subst(const char *zInput, int nSubst, const char **azSubst){ + Blob x; + int i, j; + blob_zero(&x); + while( zInput[0] ){ + for(i=0; zInput[i] && zInput[i]!='%'; i++){} + if( i>0 ){ + blob_append(&x, zInput, i); + zInput += i; + } + if( zInput[0]==0 ) break; + for(j=0; j=nSubst ){ + blob_append(&x, "%", 1); + zInput++; + } + } + return blob_str(&x); +} + +#if INTERFACE +/* +** Flags to the 3-way merger +*/ +#define MERGE_DRYRUN 0x0001 +/* +** The MERGE_KEEP_FILES flag specifies that merge_3way() should retain +** its temporary files on error. By default they are removed after the +** merge, regardless of success or failure. +*/ +#define MERGE_KEEP_FILES 0x0002 +#endif + + +/* +** This routine is a wrapper around blob_merge() with the following +** enhancements: +** +** (1) If the merge-command is defined, then use the external merging +** program specified instead of the built-in blob-merge to do the +** merging. Panic if the external merger fails. +** ** Not currently implemented ** +** +** (2) If gmerge-command is defined and there are merge conflicts in +** blob_merge() then invoke the external graphical merger to resolve +** the conflicts. +** +** (3) If a merge conflict occurs and gmerge-command is not defined, +** then write the pivot, original, and merge-in files to the +** filesystem. +*/ +int merge_3way( + Blob *pPivot, /* Common ancestor (older) */ + const char *zV1, /* Name of file for version merging into (mine) */ + Blob *pV2, /* Version merging from (yours) */ + Blob *pOut, /* Output written here */ + unsigned mergeFlags /* Flags that control operation */ +){ + Blob v1; /* Content of zV1 */ + int rc; /* Return code of subroutines and this routine */ + const char *zGMerge; /* Name of the gmerge command */ + + blob_read_from_file(&v1, zV1, ExtFILE); + rc = blob_merge(pPivot, &v1, pV2, pOut); + zGMerge = rc<=0 ? 0 : db_get("gmerge-command", 0); + if( (mergeFlags & MERGE_DRYRUN)==0 + && ((zGMerge!=0 && zGMerge[0]!=0) + || (rc!=0 && (mergeFlags & MERGE_KEEP_FILES)!=0)) ){ + char *zPivot; /* Name of the pivot file */ + char *zOrig; /* Name of the original content file */ + char *zOther; /* Name of the merge file */ + + zPivot = file_newname(zV1, "baseline", 1); + blob_write_to_file(pPivot, zPivot); + zOrig = file_newname(zV1, "original", 1); + blob_write_to_file(&v1, zOrig); + zOther = file_newname(zV1, "merge", 1); + blob_write_to_file(pV2, zOther); + if( rc>0 ){ + if( zGMerge && zGMerge[0] ){ + char *zOut; /* Temporary output file */ + char *zCmd; /* Command to invoke */ + const char *azSubst[8]; /* Strings to be substituted */ + zOut = file_newname(zV1, "output", 1); + azSubst[0] = "%baseline"; azSubst[1] = zPivot; + azSubst[2] = "%original"; azSubst[3] = zOrig; + azSubst[4] = "%merge"; azSubst[5] = zOther; + azSubst[6] = "%output"; azSubst[7] = zOut; + zCmd = string_subst(zGMerge, 8, azSubst); + printf("%s\n", zCmd); fflush(stdout); + fossil_system(zCmd); + if( file_size(zOut, RepoFILE)>=0 ){ + blob_read_from_file(pOut, zOut, ExtFILE); + file_delete(zOut); + } + fossil_free(zCmd); + fossil_free(zOut); + } + } + if( (mergeFlags & MERGE_KEEP_FILES)==0 ){ + file_delete(zPivot); + file_delete(zOrig); + file_delete(zOther); + } + fossil_free(zPivot); + fossil_free(zOrig); + fossil_free(zOther); + } + blob_reset(&v1); + return rc; } ADDED src/miniz.c Index: src/miniz.c ================================================================== --- /dev/null +++ src/miniz.c @@ -0,0 +1,4916 @@ +/* miniz.c v1.15 - public domain deflate/inflate, zlib-subset, ZIP reading/writing/appending, PNG writing + See "unlicense" statement at the end of this file. + Rich Geldreich , last updated Oct. 13, 2013 + Implements RFC 1950: http://www.ietf.org/rfc/rfc1950.txt and RFC 1951: http://www.ietf.org/rfc/rfc1951.txt + + Most API's defined in miniz.c are optional. For example, to disable the archive related functions just define + MINIZ_NO_ARCHIVE_APIS, or to get rid of all stdio usage define MINIZ_NO_STDIO (see the list below for more macros). + + * Change History + 10/13/13 v1.15 r4 - Interim bugfix release while I work on the next major release with Zip64 support (almost there!): + - Critical fix for the MZ_ZIP_FLAG_DO_NOT_SORT_CENTRAL_DIRECTORY bug (thanks kahmyong.moon@hp.com) which could cause locate files to not find files. This bug + would only have occured in earlier versions if you explicitly used this flag, OR if you used mz_zip_extract_archive_file_to_heap() or mz_zip_add_mem_to_archive_file_in_place() + (which used this flag). If you can't switch to v1.15 but want to fix this bug, just remove the uses of this flag from both helper funcs (and of course don't use the flag). + - Bugfix in mz_zip_reader_extract_to_mem_no_alloc() from kymoon when pUser_read_buf is not NULL and compressed size is > uncompressed size + - Fixing mz_zip_reader_extract_*() funcs so they don't try to extract compressed data from directory entries, to account for weird zipfiles which contain zero-size compressed data on dir entries. + Hopefully this fix won't cause any issues on weird zip archives, because it assumes the low 16-bits of zip external attributes are DOS attributes (which I believe they always are in practice). + - Fixing mz_zip_reader_is_file_a_directory() so it doesn't check the internal attributes, just the filename and external attributes + - mz_zip_reader_init_file() - missing MZ_FCLOSE() call if the seek failed + - Added cmake support for Linux builds which builds all the examples, tested with clang v3.3 and gcc v4.6. + - Clang fix for tdefl_write_image_to_png_file_in_memory() from toffaletti + - Merged MZ_FORCEINLINE fix from hdeanclark + - Fix include before config #ifdef, thanks emil.brink + - Added tdefl_write_image_to_png_file_in_memory_ex(): supports Y flipping (super useful for OpenGL apps), and explicit control over the compression level (so you can + set it to 1 for real-time compression). + - Merged in some compiler fixes from paulharris's github repro. + - Retested this build under Windows (VS 2010, including static analysis), tcc 0.9.26, gcc v4.6 and clang v3.3. + - Added example6.c, which dumps an image of the mandelbrot set to a PNG file. + - Modified example2 to help test the MZ_ZIP_FLAG_DO_NOT_SORT_CENTRAL_DIRECTORY flag more. + - In r3: Bugfix to mz_zip_writer_add_file() found during merge: Fix possible src file fclose() leak if alignment bytes+local header file write faiiled + - In r4: Minor bugfix to mz_zip_writer_add_from_zip_reader(): Was pushing the wrong central dir header offset, appears harmless in this release, but it became a problem in the zip64 branch + 5/20/12 v1.14 - MinGW32/64 GCC 4.6.1 compiler fixes: added MZ_FORCEINLINE, #include (thanks fermtect). + 5/19/12 v1.13 - From jason@cornsyrup.org and kelwert@mtu.edu - Fix mz_crc32() so it doesn't compute the wrong CRC-32's when mz_ulong is 64-bit. + - Temporarily/locally slammed in "typedef unsigned long mz_ulong" and re-ran a randomized regression test on ~500k files. + - Eliminated a bunch of warnings when compiling with GCC 32-bit/64. + - Ran all examples, miniz.c, and tinfl.c through MSVC 2008's /analyze (static analysis) option and fixed all warnings (except for the silly + "Use of the comma-operator in a tested expression.." analysis warning, which I purposely use to work around a MSVC compiler warning). + - Created 32-bit and 64-bit Codeblocks projects/workspace. Built and tested Linux executables. The codeblocks workspace is compatible with Linux+Win32/x64. + - Added miniz_tester solution/project, which is a useful little app derived from LZHAM's tester app that I use as part of the regression test. + - Ran miniz.c and tinfl.c through another series of regression testing on ~500,000 files and archives. + - Modified example5.c so it purposely disables a bunch of high-level functionality (MINIZ_NO_STDIO, etc.). (Thanks to corysama for the MINIZ_NO_STDIO bug report.) + - Fix ftell() usage in examples so they exit with an error on files which are too large (a limitation of the examples, not miniz itself). + 4/12/12 v1.12 - More comments, added low-level example5.c, fixed a couple minor level_and_flags issues in the archive API's. + level_and_flags can now be set to MZ_DEFAULT_COMPRESSION. Thanks to Bruce Dawson for the feedback/bug report. + 5/28/11 v1.11 - Added statement from unlicense.org + 5/27/11 v1.10 - Substantial compressor optimizations: + - Level 1 is now ~4x faster than before. The L1 compressor's throughput now varies between 70-110MB/sec. on a + - Core i7 (actual throughput varies depending on the type of data, and x64 vs. x86). + - Improved baseline L2-L9 compression perf. Also, greatly improved compression perf. issues on some file types. + - Refactored the compression code for better readability and maintainability. + - Added level 10 compression level (L10 has slightly better ratio than level 9, but could have a potentially large + drop in throughput on some files). + 5/15/11 v1.09 - Initial stable release. + + * Low-level Deflate/Inflate implementation notes: + + Compression: Use the "tdefl" API's. The compressor supports raw, static, and dynamic blocks, lazy or + greedy parsing, match length filtering, RLE-only, and Huffman-only streams. It performs and compresses + approximately as well as zlib. + + Decompression: Use the "tinfl" API's. The entire decompressor is implemented as a single function + coroutine: see tinfl_decompress(). It supports decompression into a 32KB (or larger power of 2) wrapping buffer, or into a memory + block large enough to hold the entire file. + + The low-level tdefl/tinfl API's do not make any use of dynamic memory allocation. + + * zlib-style API notes: + + miniz.c implements a fairly large subset of zlib. There's enough functionality present for it to be a drop-in + zlib replacement in many apps: + The z_stream struct, optional memory allocation callbacks + deflateInit/deflateInit2/deflate/deflateReset/deflateEnd/deflateBound + inflateInit/inflateInit2/inflate/inflateEnd + compress, compress2, compressBound, uncompress + CRC-32, Adler-32 - Using modern, minimal code size, CPU cache friendly routines. + Supports raw deflate streams or standard zlib streams with adler-32 checking. + + Limitations: + The callback API's are not implemented yet. No support for gzip headers or zlib static dictionaries. + I've tried to closely emulate zlib's various flavors of stream flushing and return status codes, but + there are no guarantees that miniz.c pulls this off perfectly. + + * PNG writing: See the tdefl_write_image_to_png_file_in_memory() function, originally written by + Alex Evans. Supports 1-4 bytes/pixel images. + + * ZIP archive API notes: + + The ZIP archive API's where designed with simplicity and efficiency in mind, with just enough abstraction to + get the job done with minimal fuss. There are simple API's to retrieve file information, read files from + existing archives, create new archives, append new files to existing archives, or clone archive data from + one archive to another. It supports archives located in memory or the heap, on disk (using stdio.h), + or you can specify custom file read/write callbacks. + + - Archive reading: Just call this function to read a single file from a disk archive: + + void *mz_zip_extract_archive_file_to_heap(const char *pZip_filename, const char *pArchive_name, + size_t *pSize, mz_uint zip_flags); + + For more complex cases, use the "mz_zip_reader" functions. Upon opening an archive, the entire central + directory is located and read as-is into memory, and subsequent file access only occurs when reading individual files. + + - Archives file scanning: The simple way is to use this function to scan a loaded archive for a specific file: + + int mz_zip_reader_locate_file(mz_zip_archive *pZip, const char *pName, const char *pComment, mz_uint flags); + + The locate operation can optionally check file comments too, which (as one example) can be used to identify + multiple versions of the same file in an archive. This function uses a simple linear search through the central + directory, so it's not very fast. + + Alternately, you can iterate through all the files in an archive (using mz_zip_reader_get_num_files()) and + retrieve detailed info on each file by calling mz_zip_reader_file_stat(). + + - Archive creation: Use the "mz_zip_writer" functions. The ZIP writer immediately writes compressed file data + to disk and builds an exact image of the central directory in memory. The central directory image is written + all at once at the end of the archive file when the archive is finalized. + + The archive writer can optionally align each file's local header and file data to any power of 2 alignment, + which can be useful when the archive will be read from optical media. Also, the writer supports placing + arbitrary data blobs at the very beginning of ZIP archives. Archives written using either feature are still + readable by any ZIP tool. + + - Archive appending: The simple way to add a single file to an archive is to call this function: + + mz_bool mz_zip_add_mem_to_archive_file_in_place(const char *pZip_filename, const char *pArchive_name, + const void *pBuf, size_t buf_size, const void *pComment, mz_uint16 comment_size, mz_uint level_and_flags); + + The archive will be created if it doesn't already exist, otherwise it'll be appended to. + Note the appending is done in-place and is not an atomic operation, so if something goes wrong + during the operation it's possible the archive could be left without a central directory (although the local + file headers and file data will be fine, so the archive will be recoverable). + + For more complex archive modification scenarios: + 1. The safest way is to use a mz_zip_reader to read the existing archive, cloning only those bits you want to + preserve into a new archive using using the mz_zip_writer_add_from_zip_reader() function (which compiles the + compressed file data as-is). When you're done, delete the old archive and rename the newly written archive, and + you're done. This is safe but requires a bunch of temporary disk space or heap memory. + + 2. Or, you can convert an mz_zip_reader in-place to an mz_zip_writer using mz_zip_writer_init_from_reader(), + append new files as needed, then finalize the archive which will write an updated central directory to the + original archive. (This is basically what mz_zip_add_mem_to_archive_file_in_place() does.) There's a + possibility that the archive's central directory could be lost with this method if anything goes wrong, though. + + - ZIP archive support limitations: + No zip64 or spanning support. Extraction functions can only handle unencrypted, stored or deflated files. + Requires streams capable of seeking. + + * This is a header file library, like stb_image.c. To get only a header file, either cut and paste the + below header, or create miniz.h, #define MINIZ_HEADER_FILE_ONLY, and then include miniz.c from it. + + * Important: For best perf. be sure to customize the below macros for your target platform: + #define MINIZ_USE_UNALIGNED_LOADS_AND_STORES 1 + #define MINIZ_LITTLE_ENDIAN 1 + #define MINIZ_HAS_64BIT_REGISTERS 1 + + * On platforms using glibc, Be sure to "#define _LARGEFILE64_SOURCE 1" before including miniz.c to ensure miniz + uses the 64-bit variants: fopen64(), stat64(), etc. Otherwise you won't be able to process large files + (i.e. 32-bit stat() fails for me on files > 0x7FFFFFFF bytes). +*/ + +#ifndef MINIZ_HEADER_INCLUDED +#define MINIZ_HEADER_INCLUDED + +#include + +// Defines to completely disable specific portions of miniz.c: +// If all macros here are defined the only functionality remaining will be CRC-32, adler-32, tinfl, and tdefl. + +// Define MINIZ_NO_STDIO to disable all usage and any functions which rely on stdio for file I/O. +//#define MINIZ_NO_STDIO + +// If MINIZ_NO_TIME is specified then the ZIP archive functions will not be able to get the current time, or +// get/set file times, and the C run-time funcs that get/set times won't be called. +// The current downside is the times written to your archives will be from 1979. +//#define MINIZ_NO_TIME + +// Define MINIZ_NO_ARCHIVE_APIS to disable all ZIP archive API's. +//#define MINIZ_NO_ARCHIVE_APIS + +// Define MINIZ_NO_ARCHIVE_APIS to disable all writing related ZIP archive API's. +//#define MINIZ_NO_ARCHIVE_WRITING_APIS + +// Define MINIZ_NO_ZLIB_APIS to remove all ZLIB-style compression/decompression API's. +//#define MINIZ_NO_ZLIB_APIS + +// Define MINIZ_NO_ZLIB_COMPATIBLE_NAME to disable zlib names, to prevent conflicts against stock zlib. +//#define MINIZ_NO_ZLIB_COMPATIBLE_NAMES + +// Define MINIZ_NO_MALLOC to disable all calls to malloc, free, and realloc. +// Note if MINIZ_NO_MALLOC is defined then the user must always provide custom user alloc/free/realloc +// callbacks to the zlib and archive API's, and a few stand-alone helper API's which don't provide custom user +// functions (such as tdefl_compress_mem_to_heap() and tinfl_decompress_mem_to_heap()) won't work. +//#define MINIZ_NO_MALLOC + +#if defined(__TINYC__) && (defined(__linux) || defined(__linux__)) + // TODO: Work around "error: include file 'sys\utime.h' when compiling with tcc on Linux + #define MINIZ_NO_TIME +#endif + +#if !defined(MINIZ_NO_TIME) && !defined(MINIZ_NO_ARCHIVE_APIS) + #include +#endif + +#if defined(_M_IX86) || defined(_M_X64) || defined(__i386__) || defined(__i386) || defined(__i486__) || defined(__i486) || defined(i386) || defined(__ia64__) || defined(__x86_64__) +// MINIZ_X86_OR_X64_CPU is only used to help set the below macros. +#define MINIZ_X86_OR_X64_CPU 1 +#endif + +#if (__BYTE_ORDER__==__ORDER_LITTLE_ENDIAN__) || MINIZ_X86_OR_X64_CPU +// Set MINIZ_LITTLE_ENDIAN to 1 if the processor is little endian. +#define MINIZ_LITTLE_ENDIAN 1 +#endif + +#if MINIZ_X86_OR_X64_CPU +// Set MINIZ_USE_UNALIGNED_LOADS_AND_STORES to 1 on CPU's that permit efficient integer loads and stores from unaligned addresses. +#define MINIZ_USE_UNALIGNED_LOADS_AND_STORES 1 +#endif + +#if defined(_M_X64) || defined(_WIN64) || defined(__MINGW64__) || defined(_LP64) || defined(__LP64__) || defined(__ia64__) || defined(__x86_64__) +// Set MINIZ_HAS_64BIT_REGISTERS to 1 if operations on 64-bit integers are reasonably fast (and don't involve compiler generated calls to helper functions). +#define MINIZ_HAS_64BIT_REGISTERS 1 +#endif + +#ifdef __cplusplus +extern "C" { +#endif + +// ------------------- zlib-style API Definitions. + +// For more compatibility with zlib, miniz.c uses unsigned long for some parameters/struct members. Beware: mz_ulong can be either 32 or 64-bits! +typedef unsigned long mz_ulong; + +// mz_free() internally uses the MZ_FREE() macro (which by default calls free() unless you've modified the MZ_MALLOC macro) to release a block allocated from the heap. +void mz_free(void *p); + +#define MZ_ADLER32_INIT (1) +// mz_adler32() returns the initial adler-32 value to use when called with ptr==NULL. +mz_ulong mz_adler32(mz_ulong adler, const unsigned char *ptr, size_t buf_len); + +#define MZ_CRC32_INIT (0) +// mz_crc32() returns the initial CRC-32 value to use when called with ptr==NULL. +mz_ulong mz_crc32(mz_ulong crc, const unsigned char *ptr, size_t buf_len); + +// Compression strategies. +enum { MZ_DEFAULT_STRATEGY = 0, MZ_FILTERED = 1, MZ_HUFFMAN_ONLY = 2, MZ_RLE = 3, MZ_FIXED = 4 }; + +// Method +#define MZ_DEFLATED 8 + +#ifndef MINIZ_NO_ZLIB_APIS + +// Heap allocation callbacks. +// Note that mz_alloc_func parameter types purpsosely differ from zlib's: items/size is size_t, not unsigned long. +typedef void *(*mz_alloc_func)(void *opaque, size_t items, size_t size); +typedef void (*mz_free_func)(void *opaque, void *address); +typedef void *(*mz_realloc_func)(void *opaque, void *address, size_t items, size_t size); + +#define MZ_VERSION "9.1.15" +#define MZ_VERNUM 0x91F0 +#define MZ_VER_MAJOR 9 +#define MZ_VER_MINOR 1 +#define MZ_VER_REVISION 15 +#define MZ_VER_SUBREVISION 0 + +// Flush values. For typical usage you only need MZ_NO_FLUSH and MZ_FINISH. The other values are for advanced use (refer to the zlib docs). +enum { MZ_NO_FLUSH = 0, MZ_PARTIAL_FLUSH = 1, MZ_SYNC_FLUSH = 2, MZ_FULL_FLUSH = 3, MZ_FINISH = 4, MZ_BLOCK = 5 }; + +// Return status codes. MZ_PARAM_ERROR is non-standard. +enum { MZ_OK = 0, MZ_STREAM_END = 1, MZ_NEED_DICT = 2, MZ_ERRNO = -1, MZ_STREAM_ERROR = -2, MZ_DATA_ERROR = -3, MZ_MEM_ERROR = -4, MZ_BUF_ERROR = -5, MZ_VERSION_ERROR = -6, MZ_PARAM_ERROR = -10000 }; + +// Compression levels: 0-9 are the standard zlib-style levels, 10 is best possible compression (not zlib compatible, and may be very slow), MZ_DEFAULT_COMPRESSION=MZ_DEFAULT_LEVEL. +enum { MZ_NO_COMPRESSION = 0, MZ_BEST_SPEED = 1, MZ_BEST_COMPRESSION = 9, MZ_UBER_COMPRESSION = 10, MZ_DEFAULT_LEVEL = 6, MZ_DEFAULT_COMPRESSION = -1 }; + +// Window bits +#define MZ_DEFAULT_WINDOW_BITS 15 + +struct mz_internal_state; + +// Compression/decompression stream struct. +typedef struct mz_stream_s +{ + const unsigned char *next_in; // pointer to next byte to read + unsigned int avail_in; // number of bytes available at next_in + mz_ulong total_in; // total number of bytes consumed so far + + unsigned char *next_out; // pointer to next byte to write + unsigned int avail_out; // number of bytes that can be written to next_out + mz_ulong total_out; // total number of bytes produced so far + + char *msg; // error msg (unused) + struct mz_internal_state *state; // internal state, allocated by zalloc/zfree + + mz_alloc_func zalloc; // optional heap allocation function (defaults to malloc) + mz_free_func zfree; // optional heap free function (defaults to free) + void *opaque; // heap alloc function user pointer + + int data_type; // data_type (unused) + mz_ulong adler; // adler32 of the source or uncompressed data + mz_ulong reserved; // not used +} mz_stream; + +typedef mz_stream *mz_streamp; + +// Returns the version string of miniz.c. +const char *mz_version(void); + +// mz_deflateInit() initializes a compressor with default options: +// Parameters: +// pStream must point to an initialized mz_stream struct. +// level must be between [MZ_NO_COMPRESSION, MZ_BEST_COMPRESSION]. +// level 1 enables a specially optimized compression function that's been optimized purely for performance, not ratio. +// (This special func. is currently only enabled when MINIZ_USE_UNALIGNED_LOADS_AND_STORES and MINIZ_LITTLE_ENDIAN are defined.) +// Return values: +// MZ_OK on success. +// MZ_STREAM_ERROR if the stream is bogus. +// MZ_PARAM_ERROR if the input parameters are bogus. +// MZ_MEM_ERROR on out of memory. +int mz_deflateInit(mz_streamp pStream, int level); + +// mz_deflateInit2() is like mz_deflate(), except with more control: +// Additional parameters: +// method must be MZ_DEFLATED +// window_bits must be MZ_DEFAULT_WINDOW_BITS (to wrap the deflate stream with zlib header/adler-32 footer) or -MZ_DEFAULT_WINDOW_BITS (raw deflate/no header or footer) +// mem_level must be between [1, 9] (it's checked but ignored by miniz.c) +int mz_deflateInit2(mz_streamp pStream, int level, int method, int window_bits, int mem_level, int strategy); + +// Quickly resets a compressor without having to reallocate anything. Same as calling mz_deflateEnd() followed by mz_deflateInit()/mz_deflateInit2(). +int mz_deflateReset(mz_streamp pStream); + +// mz_deflate() compresses the input to output, consuming as much of the input and producing as much output as possible. +// Parameters: +// pStream is the stream to read from and write to. You must initialize/update the next_in, avail_in, next_out, and avail_out members. +// flush may be MZ_NO_FLUSH, MZ_PARTIAL_FLUSH/MZ_SYNC_FLUSH, MZ_FULL_FLUSH, or MZ_FINISH. +// Return values: +// MZ_OK on success (when flushing, or if more input is needed but not available, and/or there's more output to be written but the output buffer is full). +// MZ_STREAM_END if all input has been consumed and all output bytes have been written. Don't call mz_deflate() on the stream anymore. +// MZ_STREAM_ERROR if the stream is bogus. +// MZ_PARAM_ERROR if one of the parameters is invalid. +// MZ_BUF_ERROR if no forward progress is possible because the input and/or output buffers are empty. (Fill up the input buffer or free up some output space and try again.) +int mz_deflate(mz_streamp pStream, int flush); + +// mz_deflateEnd() deinitializes a compressor: +// Return values: +// MZ_OK on success. +// MZ_STREAM_ERROR if the stream is bogus. +int mz_deflateEnd(mz_streamp pStream); + +// mz_deflateBound() returns a (very) conservative upper bound on the amount of data that could be generated by deflate(), assuming flush is set to only MZ_NO_FLUSH or MZ_FINISH. +mz_ulong mz_deflateBound(mz_streamp pStream, mz_ulong source_len); + +// Single-call compression functions mz_compress() and mz_compress2(): +// Returns MZ_OK on success, or one of the error codes from mz_deflate() on failure. +int mz_compress(unsigned char *pDest, mz_ulong *pDest_len, const unsigned char *pSource, mz_ulong source_len); +int mz_compress2(unsigned char *pDest, mz_ulong *pDest_len, const unsigned char *pSource, mz_ulong source_len, int level); + +// mz_compressBound() returns a (very) conservative upper bound on the amount of data that could be generated by calling mz_compress(). +mz_ulong mz_compressBound(mz_ulong source_len); + +// Initializes a decompressor. +int mz_inflateInit(mz_streamp pStream); + +// mz_inflateInit2() is like mz_inflateInit() with an additional option that controls the window size and whether or not the stream has been wrapped with a zlib header/footer: +// window_bits must be MZ_DEFAULT_WINDOW_BITS (to parse zlib header/footer) or -MZ_DEFAULT_WINDOW_BITS (raw deflate). +int mz_inflateInit2(mz_streamp pStream, int window_bits); + +// Decompresses the input stream to the output, consuming only as much of the input as needed, and writing as much to the output as possible. +// Parameters: +// pStream is the stream to read from and write to. You must initialize/update the next_in, avail_in, next_out, and avail_out members. +// flush may be MZ_NO_FLUSH, MZ_SYNC_FLUSH, or MZ_FINISH. +// On the first call, if flush is MZ_FINISH it's assumed the input and output buffers are both sized large enough to decompress the entire stream in a single call (this is slightly faster). +// MZ_FINISH implies that there are no more source bytes available beside what's already in the input buffer, and that the output buffer is large enough to hold the rest of the decompressed data. +// Return values: +// MZ_OK on success. Either more input is needed but not available, and/or there's more output to be written but the output buffer is full. +// MZ_STREAM_END if all needed input has been consumed and all output bytes have been written. For zlib streams, the adler-32 of the decompressed data has also been verified. +// MZ_STREAM_ERROR if the stream is bogus. +// MZ_DATA_ERROR if the deflate stream is invalid. +// MZ_PARAM_ERROR if one of the parameters is invalid. +// MZ_BUF_ERROR if no forward progress is possible because the input buffer is empty but the inflater needs more input to continue, or if the output buffer is not large enough. Call mz_inflate() again +// with more input data, or with more room in the output buffer (except when using single call decompression, described above). +int mz_inflate(mz_streamp pStream, int flush); + +// Deinitializes a decompressor. +int mz_inflateEnd(mz_streamp pStream); + +// Single-call decompression. +// Returns MZ_OK on success, or one of the error codes from mz_inflate() on failure. +int mz_uncompress(unsigned char *pDest, mz_ulong *pDest_len, const unsigned char *pSource, mz_ulong source_len); + +// Returns a string description of the specified error code, or NULL if the error code is invalid. +const char *mz_error(int err); + +// Redefine zlib-compatible names to miniz equivalents, so miniz.c can be used as a drop-in replacement for the subset of zlib that miniz.c supports. +// Define MINIZ_NO_ZLIB_COMPATIBLE_NAMES to disable zlib-compatibility if you use zlib in the same project. +#ifndef MINIZ_NO_ZLIB_COMPATIBLE_NAMES + typedef unsigned char Byte; + typedef unsigned int uInt; + typedef mz_ulong uLong; + typedef Byte Bytef; + typedef uInt uIntf; + typedef char charf; + typedef int intf; + typedef void *voidpf; + typedef uLong uLongf; + typedef void *voidp; + typedef void *const voidpc; + #define Z_NULL 0 + #define Z_NO_FLUSH MZ_NO_FLUSH + #define Z_PARTIAL_FLUSH MZ_PARTIAL_FLUSH + #define Z_SYNC_FLUSH MZ_SYNC_FLUSH + #define Z_FULL_FLUSH MZ_FULL_FLUSH + #define Z_FINISH MZ_FINISH + #define Z_BLOCK MZ_BLOCK + #define Z_OK MZ_OK + #define Z_STREAM_END MZ_STREAM_END + #define Z_NEED_DICT MZ_NEED_DICT + #define Z_ERRNO MZ_ERRNO + #define Z_STREAM_ERROR MZ_STREAM_ERROR + #define Z_DATA_ERROR MZ_DATA_ERROR + #define Z_MEM_ERROR MZ_MEM_ERROR + #define Z_BUF_ERROR MZ_BUF_ERROR + #define Z_VERSION_ERROR MZ_VERSION_ERROR + #define Z_PARAM_ERROR MZ_PARAM_ERROR + #define Z_NO_COMPRESSION MZ_NO_COMPRESSION + #define Z_BEST_SPEED MZ_BEST_SPEED + #define Z_BEST_COMPRESSION MZ_BEST_COMPRESSION + #define Z_DEFAULT_COMPRESSION MZ_DEFAULT_COMPRESSION + #define Z_DEFAULT_STRATEGY MZ_DEFAULT_STRATEGY + #define Z_FILTERED MZ_FILTERED + #define Z_HUFFMAN_ONLY MZ_HUFFMAN_ONLY + #define Z_RLE MZ_RLE + #define Z_FIXED MZ_FIXED + #define Z_DEFLATED MZ_DEFLATED + #define Z_DEFAULT_WINDOW_BITS MZ_DEFAULT_WINDOW_BITS + #define alloc_func mz_alloc_func + #define free_func mz_free_func + #define internal_state mz_internal_state + #define z_stream mz_stream + #define deflateInit mz_deflateInit + #define deflateInit2 mz_deflateInit2 + #define deflateReset mz_deflateReset + #define deflate mz_deflate + #define deflateEnd mz_deflateEnd + #define deflateBound mz_deflateBound + #define compress mz_compress + #define compress2 mz_compress2 + #define compressBound mz_compressBound + #define inflateInit mz_inflateInit + #define inflateInit2 mz_inflateInit2 + #define inflate mz_inflate + #define inflateEnd mz_inflateEnd + #define uncompress mz_uncompress + #define crc32 mz_crc32 + #define adler32 mz_adler32 + #define MAX_WBITS 15 + #define MAX_MEM_LEVEL 9 + #define zError mz_error + #define ZLIB_VERSION MZ_VERSION + #define ZLIB_VERNUM MZ_VERNUM + #define ZLIB_VER_MAJOR MZ_VER_MAJOR + #define ZLIB_VER_MINOR MZ_VER_MINOR + #define ZLIB_VER_REVISION MZ_VER_REVISION + #define ZLIB_VER_SUBREVISION MZ_VER_SUBREVISION + #define zlibVersion mz_version + #define zlib_version mz_version() +#endif // #ifndef MINIZ_NO_ZLIB_COMPATIBLE_NAMES + +#endif // MINIZ_NO_ZLIB_APIS + +// ------------------- Types and macros + +typedef unsigned char mz_uint8; +typedef signed short mz_int16; +typedef unsigned short mz_uint16; +typedef unsigned int mz_uint32; +typedef unsigned int mz_uint; +typedef long long mz_int64; +typedef unsigned long long mz_uint64; +typedef int mz_bool; + +#define MZ_FALSE (0) +#define MZ_TRUE (1) + +// An attempt to work around MSVC's spammy "warning C4127: conditional expression is constant" message. +#ifdef _MSC_VER + #define MZ_MACRO_END while (0, 0) +#else + #define MZ_MACRO_END while (0) +#endif + +// ------------------- ZIP archive reading/writing + +#ifndef MINIZ_NO_ARCHIVE_APIS + +enum +{ + MZ_ZIP_MAX_IO_BUF_SIZE = 64*1024, + MZ_ZIP_MAX_ARCHIVE_FILENAME_SIZE = 260, + MZ_ZIP_MAX_ARCHIVE_FILE_COMMENT_SIZE = 256 +}; + +typedef struct +{ + mz_uint32 m_file_index; + mz_uint32 m_central_dir_ofs; + mz_uint16 m_version_made_by; + mz_uint16 m_version_needed; + mz_uint16 m_bit_flag; + mz_uint16 m_method; +#ifndef MINIZ_NO_TIME + time_t m_time; +#endif + mz_uint32 m_crc32; + mz_uint64 m_comp_size; + mz_uint64 m_uncomp_size; + mz_uint16 m_internal_attr; + mz_uint32 m_external_attr; + mz_uint64 m_local_header_ofs; + mz_uint32 m_comment_size; + char m_filename[MZ_ZIP_MAX_ARCHIVE_FILENAME_SIZE]; + char m_comment[MZ_ZIP_MAX_ARCHIVE_FILE_COMMENT_SIZE]; +} mz_zip_archive_file_stat; + +typedef size_t (*mz_file_read_func)(void *pOpaque, mz_uint64 file_ofs, void *pBuf, size_t n); +typedef size_t (*mz_file_write_func)(void *pOpaque, mz_uint64 file_ofs, const void *pBuf, size_t n); + +struct mz_zip_internal_state_tag; +typedef struct mz_zip_internal_state_tag mz_zip_internal_state; + +typedef enum +{ + MZ_ZIP_MODE_INVALID = 0, + MZ_ZIP_MODE_READING = 1, + MZ_ZIP_MODE_WRITING = 2, + MZ_ZIP_MODE_WRITING_HAS_BEEN_FINALIZED = 3 +} mz_zip_mode; + +typedef struct mz_zip_archive_tag +{ + mz_uint64 m_archive_size; + mz_uint64 m_central_directory_file_ofs; + mz_uint m_total_files; + mz_zip_mode m_zip_mode; + + mz_uint m_file_offset_alignment; + + mz_alloc_func m_pAlloc; + mz_free_func m_pFree; + mz_realloc_func m_pRealloc; + void *m_pAlloc_opaque; + + mz_file_read_func m_pRead; + mz_file_write_func m_pWrite; + void *m_pIO_opaque; + + mz_zip_internal_state *m_pState; + +} mz_zip_archive; + +typedef enum +{ + MZ_ZIP_FLAG_CASE_SENSITIVE = 0x0100, + MZ_ZIP_FLAG_IGNORE_PATH = 0x0200, + MZ_ZIP_FLAG_COMPRESSED_DATA = 0x0400, + MZ_ZIP_FLAG_DO_NOT_SORT_CENTRAL_DIRECTORY = 0x0800 +} mz_zip_flags; + +// ZIP archive reading + +// Inits a ZIP archive reader. +// These functions read and validate the archive's central directory. +mz_bool mz_zip_reader_init(mz_zip_archive *pZip, mz_uint64 size, mz_uint32 flags); +mz_bool mz_zip_reader_init_mem(mz_zip_archive *pZip, const void *pMem, size_t size, mz_uint32 flags); + +#ifndef MINIZ_NO_STDIO +mz_bool mz_zip_reader_init_file(mz_zip_archive *pZip, const char *pFilename, mz_uint32 flags); +#endif + +// Returns the total number of files in the archive. +mz_uint mz_zip_reader_get_num_files(mz_zip_archive *pZip); + +// Returns detailed information about an archive file entry. +mz_bool mz_zip_reader_file_stat(mz_zip_archive *pZip, mz_uint file_index, mz_zip_archive_file_stat *pStat); + +// Determines if an archive file entry is a directory entry. +mz_bool mz_zip_reader_is_file_a_directory(mz_zip_archive *pZip, mz_uint file_index); +mz_bool mz_zip_reader_is_file_encrypted(mz_zip_archive *pZip, mz_uint file_index); + +// Retrieves the filename of an archive file entry. +// Returns the number of bytes written to pFilename, or if filename_buf_size is 0 this function returns the number of bytes needed to fully store the filename. +mz_uint mz_zip_reader_get_filename(mz_zip_archive *pZip, mz_uint file_index, char *pFilename, mz_uint filename_buf_size); + +// Attempts to locates a file in the archive's central directory. +// Valid flags: MZ_ZIP_FLAG_CASE_SENSITIVE, MZ_ZIP_FLAG_IGNORE_PATH +// Returns -1 if the file cannot be found. +int mz_zip_reader_locate_file(mz_zip_archive *pZip, const char *pName, const char *pComment, mz_uint flags); + +// Extracts a archive file to a memory buffer using no memory allocation. +mz_bool mz_zip_reader_extract_to_mem_no_alloc(mz_zip_archive *pZip, mz_uint file_index, void *pBuf, size_t buf_size, mz_uint flags, void *pUser_read_buf, size_t user_read_buf_size); +mz_bool mz_zip_reader_extract_file_to_mem_no_alloc(mz_zip_archive *pZip, const char *pFilename, void *pBuf, size_t buf_size, mz_uint flags, void *pUser_read_buf, size_t user_read_buf_size); + +// Extracts a archive file to a memory buffer. +mz_bool mz_zip_reader_extract_to_mem(mz_zip_archive *pZip, mz_uint file_index, void *pBuf, size_t buf_size, mz_uint flags); +mz_bool mz_zip_reader_extract_file_to_mem(mz_zip_archive *pZip, const char *pFilename, void *pBuf, size_t buf_size, mz_uint flags); + +// Extracts a archive file to a dynamically allocated heap buffer. +void *mz_zip_reader_extract_to_heap(mz_zip_archive *pZip, mz_uint file_index, size_t *pSize, mz_uint flags); +void *mz_zip_reader_extract_file_to_heap(mz_zip_archive *pZip, const char *pFilename, size_t *pSize, mz_uint flags); + +// Extracts a archive file using a callback function to output the file's data. +mz_bool mz_zip_reader_extract_to_callback(mz_zip_archive *pZip, mz_uint file_index, mz_file_write_func pCallback, void *pOpaque, mz_uint flags); +mz_bool mz_zip_reader_extract_file_to_callback(mz_zip_archive *pZip, const char *pFilename, mz_file_write_func pCallback, void *pOpaque, mz_uint flags); + +#ifndef MINIZ_NO_STDIO +// Extracts a archive file to a disk file and sets its last accessed and modified times. +// This function only extracts files, not archive directory records. +mz_bool mz_zip_reader_extract_to_file(mz_zip_archive *pZip, mz_uint file_index, const char *pDst_filename, mz_uint flags); +mz_bool mz_zip_reader_extract_file_to_file(mz_zip_archive *pZip, const char *pArchive_filename, const char *pDst_filename, mz_uint flags); +#endif + +// Ends archive reading, freeing all allocations, and closing the input archive file if mz_zip_reader_init_file() was used. +mz_bool mz_zip_reader_end(mz_zip_archive *pZip); + +// ZIP archive writing + +#ifndef MINIZ_NO_ARCHIVE_WRITING_APIS + +// Inits a ZIP archive writer. +mz_bool mz_zip_writer_init(mz_zip_archive *pZip, mz_uint64 existing_size); +mz_bool mz_zip_writer_init_heap(mz_zip_archive *pZip, size_t size_to_reserve_at_beginning, size_t initial_allocation_size); + +#ifndef MINIZ_NO_STDIO +mz_bool mz_zip_writer_init_file(mz_zip_archive *pZip, const char *pFilename, mz_uint64 size_to_reserve_at_beginning); +#endif + +// Converts a ZIP archive reader object into a writer object, to allow efficient in-place file appends to occur on an existing archive. +// For archives opened using mz_zip_reader_init_file, pFilename must be the archive's filename so it can be reopened for writing. If the file can't be reopened, mz_zip_reader_end() will be called. +// For archives opened using mz_zip_reader_init_mem, the memory block must be growable using the realloc callback (which defaults to realloc unless you've overridden it). +// Finally, for archives opened using mz_zip_reader_init, the mz_zip_archive's user provided m_pWrite function cannot be NULL. +// Note: In-place archive modification is not recommended unless you know what you're doing, because if execution stops or something goes wrong before +// the archive is finalized the file's central directory will be hosed. +mz_bool mz_zip_writer_init_from_reader(mz_zip_archive *pZip, const char *pFilename); + +// Adds the contents of a memory buffer to an archive. These functions record the current local time into the archive. +// To add a directory entry, call this method with an archive name ending in a forwardslash with empty buffer. +// level_and_flags - compression level (0-10, see MZ_BEST_SPEED, MZ_BEST_COMPRESSION, etc.) logically OR'd with zero or more mz_zip_flags, or just set to MZ_DEFAULT_COMPRESSION. +mz_bool mz_zip_writer_add_mem(mz_zip_archive *pZip, const char *pArchive_name, const void *pBuf, size_t buf_size, mz_uint level_and_flags); +mz_bool mz_zip_writer_add_mem_ex(mz_zip_archive *pZip, const char *pArchive_name, const void *pBuf, size_t buf_size, const void *pComment, mz_uint16 comment_size, mz_uint level_and_flags, mz_uint64 uncomp_size, mz_uint32 uncomp_crc32); + +#ifndef MINIZ_NO_STDIO +// Adds the contents of a disk file to an archive. This function also records the disk file's modified time into the archive. +// level_and_flags - compression level (0-10, see MZ_BEST_SPEED, MZ_BEST_COMPRESSION, etc.) logically OR'd with zero or more mz_zip_flags, or just set to MZ_DEFAULT_COMPRESSION. +mz_bool mz_zip_writer_add_file(mz_zip_archive *pZip, const char *pArchive_name, const char *pSrc_filename, const void *pComment, mz_uint16 comment_size, mz_uint level_and_flags); +#endif + +// Adds a file to an archive by fully cloning the data from another archive. +// This function fully clones the source file's compressed data (no recompression), along with its full filename, extra data, and comment fields. +mz_bool mz_zip_writer_add_from_zip_reader(mz_zip_archive *pZip, mz_zip_archive *pSource_zip, mz_uint file_index); + +// Finalizes the archive by writing the central directory records followed by the end of central directory record. +// After an archive is finalized, the only valid call on the mz_zip_archive struct is mz_zip_writer_end(). +// An archive must be manually finalized by calling this function for it to be valid. +mz_bool mz_zip_writer_finalize_archive(mz_zip_archive *pZip); +mz_bool mz_zip_writer_finalize_heap_archive(mz_zip_archive *pZip, void **pBuf, size_t *pSize); + +// Ends archive writing, freeing all allocations, and closing the output file if mz_zip_writer_init_file() was used. +// Note for the archive to be valid, it must have been finalized before ending. +mz_bool mz_zip_writer_end(mz_zip_archive *pZip); + +// Misc. high-level helper functions: + +// mz_zip_add_mem_to_archive_file_in_place() efficiently (but not atomically) appends a memory blob to a ZIP archive. +// level_and_flags - compression level (0-10, see MZ_BEST_SPEED, MZ_BEST_COMPRESSION, etc.) logically OR'd with zero or more mz_zip_flags, or just set to MZ_DEFAULT_COMPRESSION. +mz_bool mz_zip_add_mem_to_archive_file_in_place(const char *pZip_filename, const char *pArchive_name, const void *pBuf, size_t buf_size, const void *pComment, mz_uint16 comment_size, mz_uint level_and_flags); + +// Reads a single file from an archive into a heap block. +// Returns NULL on failure. +void *mz_zip_extract_archive_file_to_heap(const char *pZip_filename, const char *pArchive_name, size_t *pSize, mz_uint zip_flags); + +#endif // #ifndef MINIZ_NO_ARCHIVE_WRITING_APIS + +#endif // #ifndef MINIZ_NO_ARCHIVE_APIS + +// ------------------- Low-level Decompression API Definitions + +// Decompression flags used by tinfl_decompress(). +// TINFL_FLAG_PARSE_ZLIB_HEADER: If set, the input has a valid zlib header and ends with an adler32 checksum (it's a valid zlib stream). Otherwise, the input is a raw deflate stream. +// TINFL_FLAG_HAS_MORE_INPUT: If set, there are more input bytes available beyond the end of the supplied input buffer. If clear, the input buffer contains all remaining input. +// TINFL_FLAG_USING_NON_WRAPPING_OUTPUT_BUF: If set, the output buffer is large enough to hold the entire decompressed stream. If clear, the output buffer is at least the size of the dictionary (typically 32KB). +// TINFL_FLAG_COMPUTE_ADLER32: Force adler-32 checksum computation of the decompressed bytes. +enum +{ + TINFL_FLAG_PARSE_ZLIB_HEADER = 1, + TINFL_FLAG_HAS_MORE_INPUT = 2, + TINFL_FLAG_USING_NON_WRAPPING_OUTPUT_BUF = 4, + TINFL_FLAG_COMPUTE_ADLER32 = 8 +}; + +// High level decompression functions: +// tinfl_decompress_mem_to_heap() decompresses a block in memory to a heap block allocated via malloc(). +// On entry: +// pSrc_buf, src_buf_len: Pointer and size of the Deflate or zlib source data to decompress. +// On return: +// Function returns a pointer to the decompressed data, or NULL on failure. +// *pOut_len will be set to the decompressed data's size, which could be larger than src_buf_len on uncompressible data. +// The caller must call mz_free() on the returned block when it's no longer needed. +void *tinfl_decompress_mem_to_heap(const void *pSrc_buf, size_t src_buf_len, size_t *pOut_len, int flags); + +// tinfl_decompress_mem_to_mem() decompresses a block in memory to another block in memory. +// Returns TINFL_DECOMPRESS_MEM_TO_MEM_FAILED on failure, or the number of bytes written on success. +#define TINFL_DECOMPRESS_MEM_TO_MEM_FAILED ((size_t)(-1)) +size_t tinfl_decompress_mem_to_mem(void *pOut_buf, size_t out_buf_len, const void *pSrc_buf, size_t src_buf_len, int flags); + +// tinfl_decompress_mem_to_callback() decompresses a block in memory to an internal 32KB buffer, and a user provided callback function will be called to flush the buffer. +// Returns 1 on success or 0 on failure. +typedef int (*tinfl_put_buf_func_ptr)(const void* pBuf, int len, void *pUser); +int tinfl_decompress_mem_to_callback(const void *pIn_buf, size_t *pIn_buf_size, tinfl_put_buf_func_ptr pPut_buf_func, void *pPut_buf_user, int flags); + +struct tinfl_decompressor_tag; typedef struct tinfl_decompressor_tag tinfl_decompressor; + +// Max size of LZ dictionary. +#define TINFL_LZ_DICT_SIZE 32768 + +// Return status. +typedef enum +{ + TINFL_STATUS_BAD_PARAM = -3, + TINFL_STATUS_ADLER32_MISMATCH = -2, + TINFL_STATUS_FAILED = -1, + TINFL_STATUS_DONE = 0, + TINFL_STATUS_NEEDS_MORE_INPUT = 1, + TINFL_STATUS_HAS_MORE_OUTPUT = 2 +} tinfl_status; + +// Initializes the decompressor to its initial state. +#define tinfl_init(r) do { (r)->m_state = 0; } MZ_MACRO_END +#define tinfl_get_adler32(r) (r)->m_check_adler32 + +// Main low-level decompressor coroutine function. This is the only function actually needed for decompression. All the other functions are just high-level helpers for improved usability. +// This is a universal API, i.e. it can be used as a building block to build any desired higher level decompression API. In the limit case, it can be called once per every byte input or output. +tinfl_status tinfl_decompress(tinfl_decompressor *r, const mz_uint8 *pIn_buf_next, size_t *pIn_buf_size, mz_uint8 *pOut_buf_start, mz_uint8 *pOut_buf_next, size_t *pOut_buf_size, const mz_uint32 decomp_flags); + +// Internal/private bits follow. +enum +{ + TINFL_MAX_HUFF_TABLES = 3, TINFL_MAX_HUFF_SYMBOLS_0 = 288, TINFL_MAX_HUFF_SYMBOLS_1 = 32, TINFL_MAX_HUFF_SYMBOLS_2 = 19, + TINFL_FAST_LOOKUP_BITS = 10, TINFL_FAST_LOOKUP_SIZE = 1 << TINFL_FAST_LOOKUP_BITS +}; + +typedef struct +{ + mz_uint8 m_code_size[TINFL_MAX_HUFF_SYMBOLS_0]; + mz_int16 m_look_up[TINFL_FAST_LOOKUP_SIZE], m_tree[TINFL_MAX_HUFF_SYMBOLS_0 * 2]; +} tinfl_huff_table; + +#if MINIZ_HAS_64BIT_REGISTERS + #define TINFL_USE_64BIT_BITBUF 1 +#endif + +#if TINFL_USE_64BIT_BITBUF + typedef mz_uint64 tinfl_bit_buf_t; + #define TINFL_BITBUF_SIZE (64) +#else + typedef mz_uint32 tinfl_bit_buf_t; + #define TINFL_BITBUF_SIZE (32) +#endif + +struct tinfl_decompressor_tag +{ + mz_uint32 m_state, m_num_bits, m_zhdr0, m_zhdr1, m_z_adler32, m_final, m_type, m_check_adler32, m_dist, m_counter, m_num_extra, m_table_sizes[TINFL_MAX_HUFF_TABLES]; + tinfl_bit_buf_t m_bit_buf; + size_t m_dist_from_out_buf_start; + tinfl_huff_table m_tables[TINFL_MAX_HUFF_TABLES]; + mz_uint8 m_raw_header[4], m_len_codes[TINFL_MAX_HUFF_SYMBOLS_0 + TINFL_MAX_HUFF_SYMBOLS_1 + 137]; +}; + +// ------------------- Low-level Compression API Definitions + +// Set TDEFL_LESS_MEMORY to 1 to use less memory (compression will be slightly slower, and raw/dynamic blocks will be output more frequently). +#define TDEFL_LESS_MEMORY 0 + +// tdefl_init() compression flags logically OR'd together (low 12 bits contain the max. number of probes per dictionary search): +// TDEFL_DEFAULT_MAX_PROBES: The compressor defaults to 128 dictionary probes per dictionary search. 0=Huffman only, 1=Huffman+LZ (fastest/crap compression), 4095=Huffman+LZ (slowest/best compression). +enum +{ + TDEFL_HUFFMAN_ONLY = 0, TDEFL_DEFAULT_MAX_PROBES = 128, TDEFL_MAX_PROBES_MASK = 0xFFF +}; + +// TDEFL_WRITE_ZLIB_HEADER: If set, the compressor outputs a zlib header before the deflate data, and the Adler-32 of the source data at the end. Otherwise, you'll get raw deflate data. +// TDEFL_COMPUTE_ADLER32: Always compute the adler-32 of the input data (even when not writing zlib headers). +// TDEFL_GREEDY_PARSING_FLAG: Set to use faster greedy parsing, instead of more efficient lazy parsing. +// TDEFL_NONDETERMINISTIC_PARSING_FLAG: Enable to decrease the compressor's initialization time to the minimum, but the output may vary from run to run given the same input (depending on the contents of memory). +// TDEFL_RLE_MATCHES: Only look for RLE matches (matches with a distance of 1) +// TDEFL_FILTER_MATCHES: Discards matches <= 5 chars if enabled. +// TDEFL_FORCE_ALL_STATIC_BLOCKS: Disable usage of optimized Huffman tables. +// TDEFL_FORCE_ALL_RAW_BLOCKS: Only use raw (uncompressed) deflate blocks. +// The low 12 bits are reserved to control the max # of hash probes per dictionary lookup (see TDEFL_MAX_PROBES_MASK). +enum +{ + TDEFL_WRITE_ZLIB_HEADER = 0x01000, + TDEFL_COMPUTE_ADLER32 = 0x02000, + TDEFL_GREEDY_PARSING_FLAG = 0x04000, + TDEFL_NONDETERMINISTIC_PARSING_FLAG = 0x08000, + TDEFL_RLE_MATCHES = 0x10000, + TDEFL_FILTER_MATCHES = 0x20000, + TDEFL_FORCE_ALL_STATIC_BLOCKS = 0x40000, + TDEFL_FORCE_ALL_RAW_BLOCKS = 0x80000 +}; + +// High level compression functions: +// tdefl_compress_mem_to_heap() compresses a block in memory to a heap block allocated via malloc(). +// On entry: +// pSrc_buf, src_buf_len: Pointer and size of source block to compress. +// flags: The max match finder probes (default is 128) logically OR'd against the above flags. Higher probes are slower but improve compression. +// On return: +// Function returns a pointer to the compressed data, or NULL on failure. +// *pOut_len will be set to the compressed data's size, which could be larger than src_buf_len on uncompressible data. +// The caller must free() the returned block when it's no longer needed. +void *tdefl_compress_mem_to_heap(const void *pSrc_buf, size_t src_buf_len, size_t *pOut_len, int flags); + +// tdefl_compress_mem_to_mem() compresses a block in memory to another block in memory. +// Returns 0 on failure. +size_t tdefl_compress_mem_to_mem(void *pOut_buf, size_t out_buf_len, const void *pSrc_buf, size_t src_buf_len, int flags); + +// Compresses an image to a compressed PNG file in memory. +// On entry: +// pImage, w, h, and num_chans describe the image to compress. num_chans may be 1, 2, 3, or 4. +// The image pitch in bytes per scanline will be w*num_chans. The leftmost pixel on the top scanline is stored first in memory. +// level may range from [0,10], use MZ_NO_COMPRESSION, MZ_BEST_SPEED, MZ_BEST_COMPRESSION, etc. or a decent default is MZ_DEFAULT_LEVEL +// If flip is true, the image will be flipped on the Y axis (useful for OpenGL apps). +// On return: +// Function returns a pointer to the compressed data, or NULL on failure. +// *pLen_out will be set to the size of the PNG image file. +// The caller must mz_free() the returned heap block (which will typically be larger than *pLen_out) when it's no longer needed. +void *tdefl_write_image_to_png_file_in_memory_ex(const void *pImage, int w, int h, int num_chans, size_t *pLen_out, mz_uint level, mz_bool flip); +void *tdefl_write_image_to_png_file_in_memory(const void *pImage, int w, int h, int num_chans, size_t *pLen_out); + +// Output stream interface. The compressor uses this interface to write compressed data. It'll typically be called TDEFL_OUT_BUF_SIZE at a time. +typedef mz_bool (*tdefl_put_buf_func_ptr)(const void* pBuf, int len, void *pUser); + +// tdefl_compress_mem_to_output() compresses a block to an output stream. The above helpers use this function internally. +mz_bool tdefl_compress_mem_to_output(const void *pBuf, size_t buf_len, tdefl_put_buf_func_ptr pPut_buf_func, void *pPut_buf_user, int flags); + +enum { TDEFL_MAX_HUFF_TABLES = 3, TDEFL_MAX_HUFF_SYMBOLS_0 = 288, TDEFL_MAX_HUFF_SYMBOLS_1 = 32, TDEFL_MAX_HUFF_SYMBOLS_2 = 19, TDEFL_LZ_DICT_SIZE = 32768, TDEFL_LZ_DICT_SIZE_MASK = TDEFL_LZ_DICT_SIZE - 1, TDEFL_MIN_MATCH_LEN = 3, TDEFL_MAX_MATCH_LEN = 258 }; + +// TDEFL_OUT_BUF_SIZE MUST be large enough to hold a single entire compressed output block (using static/fixed Huffman codes). +#if TDEFL_LESS_MEMORY +enum { TDEFL_LZ_CODE_BUF_SIZE = 24 * 1024, TDEFL_OUT_BUF_SIZE = (TDEFL_LZ_CODE_BUF_SIZE * 13 ) / 10, TDEFL_MAX_HUFF_SYMBOLS = 288, TDEFL_LZ_HASH_BITS = 12, TDEFL_LEVEL1_HASH_SIZE_MASK = 4095, TDEFL_LZ_HASH_SHIFT = (TDEFL_LZ_HASH_BITS + 2) / 3, TDEFL_LZ_HASH_SIZE = 1 << TDEFL_LZ_HASH_BITS }; +#else +enum { TDEFL_LZ_CODE_BUF_SIZE = 64 * 1024, TDEFL_OUT_BUF_SIZE = (TDEFL_LZ_CODE_BUF_SIZE * 13 ) / 10, TDEFL_MAX_HUFF_SYMBOLS = 288, TDEFL_LZ_HASH_BITS = 15, TDEFL_LEVEL1_HASH_SIZE_MASK = 4095, TDEFL_LZ_HASH_SHIFT = (TDEFL_LZ_HASH_BITS + 2) / 3, TDEFL_LZ_HASH_SIZE = 1 << TDEFL_LZ_HASH_BITS }; +#endif + +// The low-level tdefl functions below may be used directly if the above helper functions aren't flexible enough. The low-level functions don't make any heap allocations, unlike the above helper functions. +typedef enum +{ + TDEFL_STATUS_BAD_PARAM = -2, + TDEFL_STATUS_PUT_BUF_FAILED = -1, + TDEFL_STATUS_OKAY = 0, + TDEFL_STATUS_DONE = 1, +} tdefl_status; + +// Must map to MZ_NO_FLUSH, MZ_SYNC_FLUSH, etc. enums +typedef enum +{ + TDEFL_NO_FLUSH = 0, + TDEFL_SYNC_FLUSH = 2, + TDEFL_FULL_FLUSH = 3, + TDEFL_FINISH = 4 +} tdefl_flush; + +// tdefl's compression state structure. +typedef struct +{ + tdefl_put_buf_func_ptr m_pPut_buf_func; + void *m_pPut_buf_user; + mz_uint m_flags, m_max_probes[2]; + int m_greedy_parsing; + mz_uint m_adler32, m_lookahead_pos, m_lookahead_size, m_dict_size; + mz_uint8 *m_pLZ_code_buf, *m_pLZ_flags, *m_pOutput_buf, *m_pOutput_buf_end; + mz_uint m_num_flags_left, m_total_lz_bytes, m_lz_code_buf_dict_pos, m_bits_in, m_bit_buffer; + mz_uint m_saved_match_dist, m_saved_match_len, m_saved_lit, m_output_flush_ofs, m_output_flush_remaining, m_finished, m_block_index, m_wants_to_finish; + tdefl_status m_prev_return_status; + const void *m_pIn_buf; + void *m_pOut_buf; + size_t *m_pIn_buf_size, *m_pOut_buf_size; + tdefl_flush m_flush; + const mz_uint8 *m_pSrc; + size_t m_src_buf_left, m_out_buf_ofs; + mz_uint8 m_dict[TDEFL_LZ_DICT_SIZE + TDEFL_MAX_MATCH_LEN - 1]; + mz_uint16 m_huff_count[TDEFL_MAX_HUFF_TABLES][TDEFL_MAX_HUFF_SYMBOLS]; + mz_uint16 m_huff_codes[TDEFL_MAX_HUFF_TABLES][TDEFL_MAX_HUFF_SYMBOLS]; + mz_uint8 m_huff_code_sizes[TDEFL_MAX_HUFF_TABLES][TDEFL_MAX_HUFF_SYMBOLS]; + mz_uint8 m_lz_code_buf[TDEFL_LZ_CODE_BUF_SIZE]; + mz_uint16 m_next[TDEFL_LZ_DICT_SIZE]; + mz_uint16 m_hash[TDEFL_LZ_HASH_SIZE]; + mz_uint8 m_output_buf[TDEFL_OUT_BUF_SIZE]; +} tdefl_compressor; + +// Initializes the compressor. +// There is no corresponding deinit() function because the tdefl API's do not dynamically allocate memory. +// pBut_buf_func: If NULL, output data will be supplied to the specified callback. In this case, the user should call the tdefl_compress_buffer() API for compression. +// If pBut_buf_func is NULL the user should always call the tdefl_compress() API. +// flags: See the above enums (TDEFL_HUFFMAN_ONLY, TDEFL_WRITE_ZLIB_HEADER, etc.) +tdefl_status tdefl_init(tdefl_compressor *d, tdefl_put_buf_func_ptr pPut_buf_func, void *pPut_buf_user, int flags); + +// Compresses a block of data, consuming as much of the specified input buffer as possible, and writing as much compressed data to the specified output buffer as possible. +tdefl_status tdefl_compress(tdefl_compressor *d, const void *pIn_buf, size_t *pIn_buf_size, void *pOut_buf, size_t *pOut_buf_size, tdefl_flush flush); + +// tdefl_compress_buffer() is only usable when the tdefl_init() is called with a non-NULL tdefl_put_buf_func_ptr. +// tdefl_compress_buffer() always consumes the entire input buffer. +tdefl_status tdefl_compress_buffer(tdefl_compressor *d, const void *pIn_buf, size_t in_buf_size, tdefl_flush flush); + +tdefl_status tdefl_get_prev_return_status(tdefl_compressor *d); +mz_uint32 tdefl_get_adler32(tdefl_compressor *d); + +// Can't use tdefl_create_comp_flags_from_zip_params if MINIZ_NO_ZLIB_APIS isn't defined, because it uses some of its macros. +#ifndef MINIZ_NO_ZLIB_APIS +// Create tdefl_compress() flags given zlib-style compression parameters. +// level may range from [0,10] (where 10 is absolute max compression, but may be much slower on some files) +// window_bits may be -15 (raw deflate) or 15 (zlib) +// strategy may be either MZ_DEFAULT_STRATEGY, MZ_FILTERED, MZ_HUFFMAN_ONLY, MZ_RLE, or MZ_FIXED +mz_uint tdefl_create_comp_flags_from_zip_params(int level, int window_bits, int strategy); +#endif // #ifndef MINIZ_NO_ZLIB_APIS + +#ifdef __cplusplus +} +#endif + +#endif // MINIZ_HEADER_INCLUDED + +// ------------------- End of Header: Implementation follows. (If you only want the header, define MINIZ_HEADER_FILE_ONLY.) + +#ifndef MINIZ_HEADER_FILE_ONLY + +typedef unsigned char mz_validate_uint16[sizeof(mz_uint16)==2 ? 1 : -1]; +typedef unsigned char mz_validate_uint32[sizeof(mz_uint32)==4 ? 1 : -1]; +typedef unsigned char mz_validate_uint64[sizeof(mz_uint64)==8 ? 1 : -1]; + +#include +#include + +#define MZ_ASSERT(x) assert(x) + +#ifdef MINIZ_NO_MALLOC + #define MZ_MALLOC(x) NULL + #define MZ_FREE(x) (void)x, ((void)0) + #define MZ_REALLOC(p, x) NULL +#else + #define MZ_MALLOC(x) malloc(x) + #define MZ_FREE(x) free(x) + #define MZ_REALLOC(p, x) realloc(p, x) +#endif + +#define MZ_MAX(a,b) (((a)>(b))?(a):(b)) +#define MZ_MIN(a,b) (((a)<(b))?(a):(b)) +#define MZ_CLEAR_OBJ(obj) memset(&(obj), 0, sizeof(obj)) + +#if MINIZ_USE_UNALIGNED_LOADS_AND_STORES && MINIZ_LITTLE_ENDIAN + #define MZ_READ_LE16(p) *((const mz_uint16 *)(p)) + #define MZ_READ_LE32(p) *((const mz_uint32 *)(p)) +#else + #define MZ_READ_LE16(p) ((mz_uint32)(((const mz_uint8 *)(p))[0]) | ((mz_uint32)(((const mz_uint8 *)(p))[1]) << 8U)) + #define MZ_READ_LE32(p) ((mz_uint32)(((const mz_uint8 *)(p))[0]) | ((mz_uint32)(((const mz_uint8 *)(p))[1]) << 8U) | ((mz_uint32)(((const mz_uint8 *)(p))[2]) << 16U) | ((mz_uint32)(((const mz_uint8 *)(p))[3]) << 24U)) +#endif + +#ifdef _MSC_VER + #define MZ_FORCEINLINE __forceinline +#elif defined(__GNUC__) + #define MZ_FORCEINLINE inline __attribute__((__always_inline__)) +#else + #define MZ_FORCEINLINE inline +#endif + +#ifdef __cplusplus + extern "C" { +#endif + +// ------------------- zlib-style API's + +mz_ulong mz_adler32(mz_ulong adler, const unsigned char *ptr, size_t buf_len) +{ + mz_uint32 i, s1 = (mz_uint32)(adler & 0xffff), s2 = (mz_uint32)(adler >> 16); size_t block_len = buf_len % 5552; + if (!ptr) return MZ_ADLER32_INIT; + while (buf_len) { + for (i = 0; i + 7 < block_len; i += 8, ptr += 8) { + s1 += ptr[0], s2 += s1; s1 += ptr[1], s2 += s1; s1 += ptr[2], s2 += s1; s1 += ptr[3], s2 += s1; + s1 += ptr[4], s2 += s1; s1 += ptr[5], s2 += s1; s1 += ptr[6], s2 += s1; s1 += ptr[7], s2 += s1; + } + for ( ; i < block_len; ++i) s1 += *ptr++, s2 += s1; + s1 %= 65521U, s2 %= 65521U; buf_len -= block_len; block_len = 5552; + } + return (s2 << 16) + s1; +} + +// Karl Malbrain's compact CRC-32. See "A compact CCITT crc16 and crc32 C implementation that balances processor cache usage against speed": http://www.geocities.com/malbrain/ +mz_ulong mz_crc32(mz_ulong crc, const mz_uint8 *ptr, size_t buf_len) +{ + static const mz_uint32 s_crc32[16] = { 0, 0x1db71064, 0x3b6e20c8, 0x26d930ac, 0x76dc4190, 0x6b6b51f4, 0x4db26158, 0x5005713c, + 0xedb88320, 0xf00f9344, 0xd6d6a3e8, 0xcb61b38c, 0x9b64c2b0, 0x86d3d2d4, 0xa00ae278, 0xbdbdf21c }; + mz_uint32 crcu32 = (mz_uint32)crc; + if (!ptr) return MZ_CRC32_INIT; + crcu32 = ~crcu32; while (buf_len--) { mz_uint8 b = *ptr++; crcu32 = (crcu32 >> 4) ^ s_crc32[(crcu32 & 0xF) ^ (b & 0xF)]; crcu32 = (crcu32 >> 4) ^ s_crc32[(crcu32 & 0xF) ^ (b >> 4)]; } + return ~crcu32; +} + +void mz_free(void *p) +{ + MZ_FREE(p); +} + +#ifndef MINIZ_NO_ZLIB_APIS + +static void *def_alloc_func(void *opaque, size_t items, size_t size) { (void)opaque, (void)items, (void)size; return MZ_MALLOC(items * size); } +static void def_free_func(void *opaque, void *address) { (void)opaque, (void)address; MZ_FREE(address); } +static void *def_realloc_func(void *opaque, void *address, size_t items, size_t size) { (void)opaque, (void)address, (void)items, (void)size; return MZ_REALLOC(address, items * size); } + +const char *mz_version(void) +{ + return MZ_VERSION; +} + +int mz_deflateInit(mz_streamp pStream, int level) +{ + return mz_deflateInit2(pStream, level, MZ_DEFLATED, MZ_DEFAULT_WINDOW_BITS, 9, MZ_DEFAULT_STRATEGY); +} + +int mz_deflateInit2(mz_streamp pStream, int level, int method, int window_bits, int mem_level, int strategy) +{ + tdefl_compressor *pComp; + mz_uint comp_flags = TDEFL_COMPUTE_ADLER32 | tdefl_create_comp_flags_from_zip_params(level, window_bits, strategy); + + if (!pStream) return MZ_STREAM_ERROR; + if ((method != MZ_DEFLATED) || ((mem_level < 1) || (mem_level > 9)) || ((window_bits != MZ_DEFAULT_WINDOW_BITS) && (-window_bits != MZ_DEFAULT_WINDOW_BITS))) return MZ_PARAM_ERROR; + + pStream->data_type = 0; + pStream->adler = MZ_ADLER32_INIT; + pStream->msg = NULL; + pStream->reserved = 0; + pStream->total_in = 0; + pStream->total_out = 0; + if (!pStream->zalloc) pStream->zalloc = def_alloc_func; + if (!pStream->zfree) pStream->zfree = def_free_func; + + pComp = (tdefl_compressor *)pStream->zalloc(pStream->opaque, 1, sizeof(tdefl_compressor)); + if (!pComp) + return MZ_MEM_ERROR; + + pStream->state = (struct mz_internal_state *)pComp; + + if (tdefl_init(pComp, NULL, NULL, comp_flags) != TDEFL_STATUS_OKAY) + { + mz_deflateEnd(pStream); + return MZ_PARAM_ERROR; + } + + return MZ_OK; +} + +int mz_deflateReset(mz_streamp pStream) +{ + if ((!pStream) || (!pStream->state) || (!pStream->zalloc) || (!pStream->zfree)) return MZ_STREAM_ERROR; + pStream->total_in = pStream->total_out = 0; + tdefl_init((tdefl_compressor*)pStream->state, NULL, NULL, ((tdefl_compressor*)pStream->state)->m_flags); + return MZ_OK; +} + +int mz_deflate(mz_streamp pStream, int flush) +{ + size_t in_bytes, out_bytes; + mz_ulong orig_total_in, orig_total_out; + int mz_status = MZ_OK; + + if ((!pStream) || (!pStream->state) || (flush < 0) || (flush > MZ_FINISH) || (!pStream->next_out)) return MZ_STREAM_ERROR; + if (!pStream->avail_out) return MZ_BUF_ERROR; + + if (flush == MZ_PARTIAL_FLUSH) flush = MZ_SYNC_FLUSH; + + if (((tdefl_compressor*)pStream->state)->m_prev_return_status == TDEFL_STATUS_DONE) + return (flush == MZ_FINISH) ? MZ_STREAM_END : MZ_BUF_ERROR; + + orig_total_in = pStream->total_in; orig_total_out = pStream->total_out; + for ( ; ; ) + { + tdefl_status defl_status; + in_bytes = pStream->avail_in; out_bytes = pStream->avail_out; + + defl_status = tdefl_compress((tdefl_compressor*)pStream->state, pStream->next_in, &in_bytes, pStream->next_out, &out_bytes, (tdefl_flush)flush); + pStream->next_in += (mz_uint)in_bytes; pStream->avail_in -= (mz_uint)in_bytes; + pStream->total_in += (mz_uint)in_bytes; pStream->adler = tdefl_get_adler32((tdefl_compressor*)pStream->state); + + pStream->next_out += (mz_uint)out_bytes; pStream->avail_out -= (mz_uint)out_bytes; + pStream->total_out += (mz_uint)out_bytes; + + if (defl_status < 0) + { + mz_status = MZ_STREAM_ERROR; + break; + } + else if (defl_status == TDEFL_STATUS_DONE) + { + mz_status = MZ_STREAM_END; + break; + } + else if (!pStream->avail_out) + break; + else if ((!pStream->avail_in) && (flush != MZ_FINISH)) + { + if ((flush) || (pStream->total_in != orig_total_in) || (pStream->total_out != orig_total_out)) + break; + return MZ_BUF_ERROR; // Can't make forward progress without some input. + } + } + return mz_status; +} + +int mz_deflateEnd(mz_streamp pStream) +{ + if (!pStream) return MZ_STREAM_ERROR; + if (pStream->state) + { + pStream->zfree(pStream->opaque, pStream->state); + pStream->state = NULL; + } + return MZ_OK; +} + +mz_ulong mz_deflateBound(mz_streamp pStream, mz_ulong source_len) +{ + (void)pStream; + // This is really over conservative. (And lame, but it's actually pretty tricky to compute a true upper bound given the way tdefl's blocking works.) + return MZ_MAX(128 + (source_len * 110) / 100, 128 + source_len + ((source_len / (31 * 1024)) + 1) * 5); +} + +int mz_compress2(unsigned char *pDest, mz_ulong *pDest_len, const unsigned char *pSource, mz_ulong source_len, int level) +{ + int status; + mz_stream stream; + memset(&stream, 0, sizeof(stream)); + + // In case mz_ulong is 64-bits (argh I hate longs). + if ((source_len | *pDest_len) > 0xFFFFFFFFU) return MZ_PARAM_ERROR; + + stream.next_in = pSource; + stream.avail_in = (mz_uint32)source_len; + stream.next_out = pDest; + stream.avail_out = (mz_uint32)*pDest_len; + + status = mz_deflateInit(&stream, level); + if (status != MZ_OK) return status; + + status = mz_deflate(&stream, MZ_FINISH); + if (status != MZ_STREAM_END) + { + mz_deflateEnd(&stream); + return (status == MZ_OK) ? MZ_BUF_ERROR : status; + } + + *pDest_len = stream.total_out; + return mz_deflateEnd(&stream); +} + +int mz_compress(unsigned char *pDest, mz_ulong *pDest_len, const unsigned char *pSource, mz_ulong source_len) +{ + return mz_compress2(pDest, pDest_len, pSource, source_len, MZ_DEFAULT_COMPRESSION); +} + +mz_ulong mz_compressBound(mz_ulong source_len) +{ + return mz_deflateBound(NULL, source_len); +} + +typedef struct +{ + tinfl_decompressor m_decomp; + mz_uint m_dict_ofs, m_dict_avail, m_first_call, m_has_flushed; int m_window_bits; + mz_uint8 m_dict[TINFL_LZ_DICT_SIZE]; + tinfl_status m_last_status; +} inflate_state; + +int mz_inflateInit2(mz_streamp pStream, int window_bits) +{ + inflate_state *pDecomp; + if (!pStream) return MZ_STREAM_ERROR; + if ((window_bits != MZ_DEFAULT_WINDOW_BITS) && (-window_bits != MZ_DEFAULT_WINDOW_BITS)) return MZ_PARAM_ERROR; + + pStream->data_type = 0; + pStream->adler = 0; + pStream->msg = NULL; + pStream->total_in = 0; + pStream->total_out = 0; + pStream->reserved = 0; + if (!pStream->zalloc) pStream->zalloc = def_alloc_func; + if (!pStream->zfree) pStream->zfree = def_free_func; + + pDecomp = (inflate_state*)pStream->zalloc(pStream->opaque, 1, sizeof(inflate_state)); + if (!pDecomp) return MZ_MEM_ERROR; + + pStream->state = (struct mz_internal_state *)pDecomp; + + tinfl_init(&pDecomp->m_decomp); + pDecomp->m_dict_ofs = 0; + pDecomp->m_dict_avail = 0; + pDecomp->m_last_status = TINFL_STATUS_NEEDS_MORE_INPUT; + pDecomp->m_first_call = 1; + pDecomp->m_has_flushed = 0; + pDecomp->m_window_bits = window_bits; + + return MZ_OK; +} + +int mz_inflateInit(mz_streamp pStream) +{ + return mz_inflateInit2(pStream, MZ_DEFAULT_WINDOW_BITS); +} + +int mz_inflate(mz_streamp pStream, int flush) +{ + inflate_state* pState; + mz_uint n, first_call, decomp_flags = TINFL_FLAG_COMPUTE_ADLER32; + size_t in_bytes, out_bytes, orig_avail_in; + tinfl_status status; + + if ((!pStream) || (!pStream->state)) return MZ_STREAM_ERROR; + if (flush == MZ_PARTIAL_FLUSH) flush = MZ_SYNC_FLUSH; + if ((flush) && (flush != MZ_SYNC_FLUSH) && (flush != MZ_FINISH)) return MZ_STREAM_ERROR; + + pState = (inflate_state*)pStream->state; + if (pState->m_window_bits > 0) decomp_flags |= TINFL_FLAG_PARSE_ZLIB_HEADER; + orig_avail_in = pStream->avail_in; + + first_call = pState->m_first_call; pState->m_first_call = 0; + if (pState->m_last_status < 0) return MZ_DATA_ERROR; + + if (pState->m_has_flushed && (flush != MZ_FINISH)) return MZ_STREAM_ERROR; + pState->m_has_flushed |= (flush == MZ_FINISH); + + if ((flush == MZ_FINISH) && (first_call)) + { + // MZ_FINISH on the first call implies that the input and output buffers are large enough to hold the entire compressed/decompressed file. + decomp_flags |= TINFL_FLAG_USING_NON_WRAPPING_OUTPUT_BUF; + in_bytes = pStream->avail_in; out_bytes = pStream->avail_out; + status = tinfl_decompress(&pState->m_decomp, pStream->next_in, &in_bytes, pStream->next_out, pStream->next_out, &out_bytes, decomp_flags); + pState->m_last_status = status; + pStream->next_in += (mz_uint)in_bytes; pStream->avail_in -= (mz_uint)in_bytes; pStream->total_in += (mz_uint)in_bytes; + pStream->adler = tinfl_get_adler32(&pState->m_decomp); + pStream->next_out += (mz_uint)out_bytes; pStream->avail_out -= (mz_uint)out_bytes; pStream->total_out += (mz_uint)out_bytes; + + if (status < 0) + return MZ_DATA_ERROR; + else if (status != TINFL_STATUS_DONE) + { + pState->m_last_status = TINFL_STATUS_FAILED; + return MZ_BUF_ERROR; + } + return MZ_STREAM_END; + } + // flush != MZ_FINISH then we must assume there's more input. + if (flush != MZ_FINISH) decomp_flags |= TINFL_FLAG_HAS_MORE_INPUT; + + if (pState->m_dict_avail) + { + n = MZ_MIN(pState->m_dict_avail, pStream->avail_out); + memcpy(pStream->next_out, pState->m_dict + pState->m_dict_ofs, n); + pStream->next_out += n; pStream->avail_out -= n; pStream->total_out += n; + pState->m_dict_avail -= n; pState->m_dict_ofs = (pState->m_dict_ofs + n) & (TINFL_LZ_DICT_SIZE - 1); + return ((pState->m_last_status == TINFL_STATUS_DONE) && (!pState->m_dict_avail)) ? MZ_STREAM_END : MZ_OK; + } + + for ( ; ; ) + { + in_bytes = pStream->avail_in; + out_bytes = TINFL_LZ_DICT_SIZE - pState->m_dict_ofs; + + status = tinfl_decompress(&pState->m_decomp, pStream->next_in, &in_bytes, pState->m_dict, pState->m_dict + pState->m_dict_ofs, &out_bytes, decomp_flags); + pState->m_last_status = status; + + pStream->next_in += (mz_uint)in_bytes; pStream->avail_in -= (mz_uint)in_bytes; + pStream->total_in += (mz_uint)in_bytes; pStream->adler = tinfl_get_adler32(&pState->m_decomp); + + pState->m_dict_avail = (mz_uint)out_bytes; + + n = MZ_MIN(pState->m_dict_avail, pStream->avail_out); + memcpy(pStream->next_out, pState->m_dict + pState->m_dict_ofs, n); + pStream->next_out += n; pStream->avail_out -= n; pStream->total_out += n; + pState->m_dict_avail -= n; pState->m_dict_ofs = (pState->m_dict_ofs + n) & (TINFL_LZ_DICT_SIZE - 1); + + if (status < 0) + return MZ_DATA_ERROR; // Stream is corrupted (there could be some uncompressed data left in the output dictionary - oh well). + else if ((status == TINFL_STATUS_NEEDS_MORE_INPUT) && (!orig_avail_in)) + return MZ_BUF_ERROR; // Signal caller that we can't make forward progress without supplying more input or by setting flush to MZ_FINISH. + else if (flush == MZ_FINISH) + { + // The output buffer MUST be large to hold the remaining uncompressed data when flush==MZ_FINISH. + if (status == TINFL_STATUS_DONE) + return pState->m_dict_avail ? MZ_BUF_ERROR : MZ_STREAM_END; + // status here must be TINFL_STATUS_HAS_MORE_OUTPUT, which means there's at least 1 more byte on the way. If there's no more room left in the output buffer then something is wrong. + else if (!pStream->avail_out) + return MZ_BUF_ERROR; + } + else if ((status == TINFL_STATUS_DONE) || (!pStream->avail_in) || (!pStream->avail_out) || (pState->m_dict_avail)) + break; + } + + return ((status == TINFL_STATUS_DONE) && (!pState->m_dict_avail)) ? MZ_STREAM_END : MZ_OK; +} + +int mz_inflateEnd(mz_streamp pStream) +{ + if (!pStream) + return MZ_STREAM_ERROR; + if (pStream->state) + { + pStream->zfree(pStream->opaque, pStream->state); + pStream->state = NULL; + } + return MZ_OK; +} + +int mz_uncompress(unsigned char *pDest, mz_ulong *pDest_len, const unsigned char *pSource, mz_ulong source_len) +{ + mz_stream stream; + int status; + memset(&stream, 0, sizeof(stream)); + + // In case mz_ulong is 64-bits (argh I hate longs). + if ((source_len | *pDest_len) > 0xFFFFFFFFU) return MZ_PARAM_ERROR; + + stream.next_in = pSource; + stream.avail_in = (mz_uint32)source_len; + stream.next_out = pDest; + stream.avail_out = (mz_uint32)*pDest_len; + + status = mz_inflateInit(&stream); + if (status != MZ_OK) + return status; + + status = mz_inflate(&stream, MZ_FINISH); + if (status != MZ_STREAM_END) + { + mz_inflateEnd(&stream); + return ((status == MZ_BUF_ERROR) && (!stream.avail_in)) ? MZ_DATA_ERROR : status; + } + *pDest_len = stream.total_out; + + return mz_inflateEnd(&stream); +} + +const char *mz_error(int err) +{ + static const struct { int m_err; const char *m_pDesc; } s_error_descs[] = + { + { MZ_OK, "" }, { MZ_STREAM_END, "stream end" }, { MZ_NEED_DICT, "need dictionary" }, { MZ_ERRNO, "file error" }, { MZ_STREAM_ERROR, "stream error" }, + { MZ_DATA_ERROR, "data error" }, { MZ_MEM_ERROR, "out of memory" }, { MZ_BUF_ERROR, "buf error" }, { MZ_VERSION_ERROR, "version error" }, { MZ_PARAM_ERROR, "parameter error" } + }; + mz_uint i; for (i = 0; i < sizeof(s_error_descs) / sizeof(s_error_descs[0]); ++i) if (s_error_descs[i].m_err == err) return s_error_descs[i].m_pDesc; + return NULL; +} + +#endif //MINIZ_NO_ZLIB_APIS + +// ------------------- Low-level Decompression (completely independent from all compression API's) + +#define TINFL_MEMCPY(d, s, l) memcpy(d, s, l) +#define TINFL_MEMSET(p, c, l) memset(p, c, l) + +#define TINFL_CR_BEGIN switch(r->m_state) { case 0: +#define TINFL_CR_RETURN(state_index, result) do { status = result; r->m_state = state_index; goto common_exit; case state_index:; } MZ_MACRO_END +#define TINFL_CR_RETURN_FOREVER(state_index, result) do { for ( ; ; ) { TINFL_CR_RETURN(state_index, result); } } MZ_MACRO_END +#define TINFL_CR_FINISH } + +// TODO: If the caller has indicated that there's no more input, and we attempt to read beyond the input buf, then something is wrong with the input because the inflator never +// reads ahead more than it needs to. Currently TINFL_GET_BYTE() pads the end of the stream with 0's in this scenario. +#define TINFL_GET_BYTE(state_index, c) do { \ + if (pIn_buf_cur >= pIn_buf_end) { \ + for ( ; ; ) { \ + if (decomp_flags & TINFL_FLAG_HAS_MORE_INPUT) { \ + TINFL_CR_RETURN(state_index, TINFL_STATUS_NEEDS_MORE_INPUT); \ + if (pIn_buf_cur < pIn_buf_end) { \ + c = *pIn_buf_cur++; \ + break; \ + } \ + } else { \ + c = 0; \ + break; \ + } \ + } \ + } else c = *pIn_buf_cur++; } MZ_MACRO_END + +#define TINFL_NEED_BITS(state_index, n) do { mz_uint c; TINFL_GET_BYTE(state_index, c); bit_buf |= (((tinfl_bit_buf_t)c) << num_bits); num_bits += 8; } while (num_bits < (mz_uint)(n)) +#define TINFL_SKIP_BITS(state_index, n) do { if (num_bits < (mz_uint)(n)) { TINFL_NEED_BITS(state_index, n); } bit_buf >>= (n); num_bits -= (n); } MZ_MACRO_END +#define TINFL_GET_BITS(state_index, b, n) do { if (num_bits < (mz_uint)(n)) { TINFL_NEED_BITS(state_index, n); } b = bit_buf & ((1 << (n)) - 1); bit_buf >>= (n); num_bits -= (n); } MZ_MACRO_END + +// TINFL_HUFF_BITBUF_FILL() is only used rarely, when the number of bytes remaining in the input buffer falls below 2. +// It reads just enough bytes from the input stream that are needed to decode the next Huffman code (and absolutely no more). It works by trying to fully decode a +// Huffman code by using whatever bits are currently present in the bit buffer. If this fails, it reads another byte, and tries again until it succeeds or until the +// bit buffer contains >=15 bits (deflate's max. Huffman code size). +#define TINFL_HUFF_BITBUF_FILL(state_index, pHuff) \ + do { \ + temp = (pHuff)->m_look_up[bit_buf & (TINFL_FAST_LOOKUP_SIZE - 1)]; \ + if (temp >= 0) { \ + code_len = temp >> 9; \ + if ((code_len) && (num_bits >= code_len)) \ + break; \ + } else if (num_bits > TINFL_FAST_LOOKUP_BITS) { \ + code_len = TINFL_FAST_LOOKUP_BITS; \ + do { \ + temp = (pHuff)->m_tree[~temp + ((bit_buf >> code_len++) & 1)]; \ + } while ((temp < 0) && (num_bits >= (code_len + 1))); if (temp >= 0) break; \ + } TINFL_GET_BYTE(state_index, c); bit_buf |= (((tinfl_bit_buf_t)c) << num_bits); num_bits += 8; \ + } while (num_bits < 15); + +// TINFL_HUFF_DECODE() decodes the next Huffman coded symbol. It's more complex than you would initially expect because the zlib API expects the decompressor to never read +// beyond the final byte of the deflate stream. (In other words, when this macro wants to read another byte from the input, it REALLY needs another byte in order to fully +// decode the next Huffman code.) Handling this properly is particularly important on raw deflate (non-zlib) streams, which aren't followed by a byte aligned adler-32. +// The slow path is only executed at the very end of the input buffer. +#define TINFL_HUFF_DECODE(state_index, sym, pHuff) do { \ + int temp; mz_uint code_len, c; \ + if (num_bits < 15) { \ + if ((pIn_buf_end - pIn_buf_cur) < 2) { \ + TINFL_HUFF_BITBUF_FILL(state_index, pHuff); \ + } else { \ + bit_buf |= (((tinfl_bit_buf_t)pIn_buf_cur[0]) << num_bits) | (((tinfl_bit_buf_t)pIn_buf_cur[1]) << (num_bits + 8)); pIn_buf_cur += 2; num_bits += 16; \ + } \ + } \ + if ((temp = (pHuff)->m_look_up[bit_buf & (TINFL_FAST_LOOKUP_SIZE - 1)]) >= 0) \ + code_len = temp >> 9, temp &= 511; \ + else { \ + code_len = TINFL_FAST_LOOKUP_BITS; do { temp = (pHuff)->m_tree[~temp + ((bit_buf >> code_len++) & 1)]; } while (temp < 0); \ + } sym = temp; bit_buf >>= code_len; num_bits -= code_len; } MZ_MACRO_END + +tinfl_status tinfl_decompress(tinfl_decompressor *r, const mz_uint8 *pIn_buf_next, size_t *pIn_buf_size, mz_uint8 *pOut_buf_start, mz_uint8 *pOut_buf_next, size_t *pOut_buf_size, const mz_uint32 decomp_flags) +{ + static const int s_length_base[31] = { 3,4,5,6,7,8,9,10,11,13, 15,17,19,23,27,31,35,43,51,59, 67,83,99,115,131,163,195,227,258,0,0 }; + static const int s_length_extra[31]= { 0,0,0,0,0,0,0,0,1,1,1,1,2,2,2,2,3,3,3,3,4,4,4,4,5,5,5,5,0,0,0 }; + static const int s_dist_base[32] = { 1,2,3,4,5,7,9,13,17,25,33,49,65,97,129,193, 257,385,513,769,1025,1537,2049,3073,4097,6145,8193,12289,16385,24577,0,0}; + static const int s_dist_extra[32] = { 0,0,0,0,1,1,2,2,3,3,4,4,5,5,6,6,7,7,8,8,9,9,10,10,11,11,12,12,13,13}; + static const mz_uint8 s_length_dezigzag[19] = { 16,17,18,0,8,7,9,6,10,5,11,4,12,3,13,2,14,1,15 }; + static const int s_min_table_sizes[3] = { 257, 1, 4 }; + + tinfl_status status = TINFL_STATUS_FAILED; mz_uint32 num_bits, dist, counter, num_extra; tinfl_bit_buf_t bit_buf; + const mz_uint8 *pIn_buf_cur = pIn_buf_next, *const pIn_buf_end = pIn_buf_next + *pIn_buf_size; + mz_uint8 *pOut_buf_cur = pOut_buf_next, *const pOut_buf_end = pOut_buf_next + *pOut_buf_size; + size_t out_buf_size_mask = (decomp_flags & TINFL_FLAG_USING_NON_WRAPPING_OUTPUT_BUF) ? (size_t)-1 : ((pOut_buf_next - pOut_buf_start) + *pOut_buf_size) - 1, dist_from_out_buf_start; + + // Ensure the output buffer's size is a power of 2, unless the output buffer is large enough to hold the entire output file (in which case it doesn't matter). + if (((out_buf_size_mask + 1) & out_buf_size_mask) || (pOut_buf_next < pOut_buf_start)) { *pIn_buf_size = *pOut_buf_size = 0; return TINFL_STATUS_BAD_PARAM; } + + num_bits = r->m_num_bits; bit_buf = r->m_bit_buf; dist = r->m_dist; counter = r->m_counter; num_extra = r->m_num_extra; dist_from_out_buf_start = r->m_dist_from_out_buf_start; + TINFL_CR_BEGIN + + bit_buf = num_bits = dist = counter = num_extra = r->m_zhdr0 = r->m_zhdr1 = 0; r->m_z_adler32 = r->m_check_adler32 = 1; + if (decomp_flags & TINFL_FLAG_PARSE_ZLIB_HEADER) + { + TINFL_GET_BYTE(1, r->m_zhdr0); TINFL_GET_BYTE(2, r->m_zhdr1); + counter = (((r->m_zhdr0 * 256 + r->m_zhdr1) % 31 != 0) || (r->m_zhdr1 & 32) || ((r->m_zhdr0 & 15) != 8)); + if (!(decomp_flags & TINFL_FLAG_USING_NON_WRAPPING_OUTPUT_BUF)) counter |= (((1U << (8U + (r->m_zhdr0 >> 4))) > 32768U) || ((out_buf_size_mask + 1) < (size_t)(1U << (8U + (r->m_zhdr0 >> 4))))); + if (counter) { TINFL_CR_RETURN_FOREVER(36, TINFL_STATUS_FAILED); } + } + + do + { + TINFL_GET_BITS(3, r->m_final, 3); r->m_type = r->m_final >> 1; + if (r->m_type == 0) + { + TINFL_SKIP_BITS(5, num_bits & 7); + for (counter = 0; counter < 4; ++counter) { if (num_bits) TINFL_GET_BITS(6, r->m_raw_header[counter], 8); else TINFL_GET_BYTE(7, r->m_raw_header[counter]); } + if ((counter = (r->m_raw_header[0] | (r->m_raw_header[1] << 8))) != (mz_uint)(0xFFFF ^ (r->m_raw_header[2] | (r->m_raw_header[3] << 8)))) { TINFL_CR_RETURN_FOREVER(39, TINFL_STATUS_FAILED); } + while ((counter) && (num_bits)) + { + TINFL_GET_BITS(51, dist, 8); + while (pOut_buf_cur >= pOut_buf_end) { TINFL_CR_RETURN(52, TINFL_STATUS_HAS_MORE_OUTPUT); } + *pOut_buf_cur++ = (mz_uint8)dist; + counter--; + } + while (counter) + { + size_t n; while (pOut_buf_cur >= pOut_buf_end) { TINFL_CR_RETURN(9, TINFL_STATUS_HAS_MORE_OUTPUT); } + while (pIn_buf_cur >= pIn_buf_end) + { + if (decomp_flags & TINFL_FLAG_HAS_MORE_INPUT) + { + TINFL_CR_RETURN(38, TINFL_STATUS_NEEDS_MORE_INPUT); + } + else + { + TINFL_CR_RETURN_FOREVER(40, TINFL_STATUS_FAILED); + } + } + n = MZ_MIN(MZ_MIN((size_t)(pOut_buf_end - pOut_buf_cur), (size_t)(pIn_buf_end - pIn_buf_cur)), counter); + TINFL_MEMCPY(pOut_buf_cur, pIn_buf_cur, n); pIn_buf_cur += n; pOut_buf_cur += n; counter -= (mz_uint)n; + } + } + else if (r->m_type == 3) + { + TINFL_CR_RETURN_FOREVER(10, TINFL_STATUS_FAILED); + } + else + { + if (r->m_type == 1) + { + mz_uint8 *p = r->m_tables[0].m_code_size; mz_uint i; + r->m_table_sizes[0] = 288; r->m_table_sizes[1] = 32; TINFL_MEMSET(r->m_tables[1].m_code_size, 5, 32); + for ( i = 0; i <= 143; ++i) *p++ = 8; for ( ; i <= 255; ++i) *p++ = 9; for ( ; i <= 279; ++i) *p++ = 7; for ( ; i <= 287; ++i) *p++ = 8; + } + else + { + for (counter = 0; counter < 3; counter++) { TINFL_GET_BITS(11, r->m_table_sizes[counter], "\05\05\04"[counter]); r->m_table_sizes[counter] += s_min_table_sizes[counter]; } + MZ_CLEAR_OBJ(r->m_tables[2].m_code_size); for (counter = 0; counter < r->m_table_sizes[2]; counter++) { mz_uint s; TINFL_GET_BITS(14, s, 3); r->m_tables[2].m_code_size[s_length_dezigzag[counter]] = (mz_uint8)s; } + r->m_table_sizes[2] = 19; + } + for ( ; (int)r->m_type >= 0; r->m_type--) + { + int tree_next, tree_cur; tinfl_huff_table *pTable; + mz_uint i, j, used_syms, total, sym_index, next_code[17], total_syms[16]; pTable = &r->m_tables[r->m_type]; MZ_CLEAR_OBJ(total_syms); MZ_CLEAR_OBJ(pTable->m_look_up); MZ_CLEAR_OBJ(pTable->m_tree); + for (i = 0; i < r->m_table_sizes[r->m_type]; ++i) total_syms[pTable->m_code_size[i]]++; + used_syms = 0, total = 0; next_code[0] = next_code[1] = 0; + for (i = 1; i <= 15; ++i) { used_syms += total_syms[i]; next_code[i + 1] = (total = ((total + total_syms[i]) << 1)); } + if ((65536 != total) && (used_syms > 1)) + { + TINFL_CR_RETURN_FOREVER(35, TINFL_STATUS_FAILED); + } + for (tree_next = -1, sym_index = 0; sym_index < r->m_table_sizes[r->m_type]; ++sym_index) + { + mz_uint rev_code = 0, l, cur_code, code_size = pTable->m_code_size[sym_index]; if (!code_size) continue; + cur_code = next_code[code_size]++; for (l = code_size; l > 0; l--, cur_code >>= 1) rev_code = (rev_code << 1) | (cur_code & 1); + if (code_size <= TINFL_FAST_LOOKUP_BITS) { mz_int16 k = (mz_int16)((code_size << 9) | sym_index); while (rev_code < TINFL_FAST_LOOKUP_SIZE) { pTable->m_look_up[rev_code] = k; rev_code += (1 << code_size); } continue; } + if (0 == (tree_cur = pTable->m_look_up[rev_code & (TINFL_FAST_LOOKUP_SIZE - 1)])) { pTable->m_look_up[rev_code & (TINFL_FAST_LOOKUP_SIZE - 1)] = (mz_int16)tree_next; tree_cur = tree_next; tree_next -= 2; } + rev_code >>= (TINFL_FAST_LOOKUP_BITS - 1); + for (j = code_size; j > (TINFL_FAST_LOOKUP_BITS + 1); j--) + { + tree_cur -= ((rev_code >>= 1) & 1); + if (!pTable->m_tree[-tree_cur - 1]) { pTable->m_tree[-tree_cur - 1] = (mz_int16)tree_next; tree_cur = tree_next; tree_next -= 2; } else tree_cur = pTable->m_tree[-tree_cur - 1]; + } + tree_cur -= ((rev_code >>= 1) & 1); pTable->m_tree[-tree_cur - 1] = (mz_int16)sym_index; + } + if (r->m_type == 2) + { + for (counter = 0; counter < (r->m_table_sizes[0] + r->m_table_sizes[1]); ) + { + mz_uint s; TINFL_HUFF_DECODE(16, dist, &r->m_tables[2]); if (dist < 16) { r->m_len_codes[counter++] = (mz_uint8)dist; continue; } + if ((dist == 16) && (!counter)) + { + TINFL_CR_RETURN_FOREVER(17, TINFL_STATUS_FAILED); + } + num_extra = "\02\03\07"[dist - 16]; TINFL_GET_BITS(18, s, num_extra); s += "\03\03\013"[dist - 16]; + TINFL_MEMSET(r->m_len_codes + counter, (dist == 16) ? r->m_len_codes[counter - 1] : 0, s); counter += s; + } + if ((r->m_table_sizes[0] + r->m_table_sizes[1]) != counter) + { + TINFL_CR_RETURN_FOREVER(21, TINFL_STATUS_FAILED); + } + TINFL_MEMCPY(r->m_tables[0].m_code_size, r->m_len_codes, r->m_table_sizes[0]); TINFL_MEMCPY(r->m_tables[1].m_code_size, r->m_len_codes + r->m_table_sizes[0], r->m_table_sizes[1]); + } + } + for ( ; ; ) + { + mz_uint8 *pSrc; + for ( ; ; ) + { + if (((pIn_buf_end - pIn_buf_cur) < 4) || ((pOut_buf_end - pOut_buf_cur) < 2)) + { + TINFL_HUFF_DECODE(23, counter, &r->m_tables[0]); + if (counter >= 256) + break; + while (pOut_buf_cur >= pOut_buf_end) { TINFL_CR_RETURN(24, TINFL_STATUS_HAS_MORE_OUTPUT); } + *pOut_buf_cur++ = (mz_uint8)counter; + } + else + { + int sym2; mz_uint code_len; +#if TINFL_USE_64BIT_BITBUF + if (num_bits < 30) { bit_buf |= (((tinfl_bit_buf_t)MZ_READ_LE32(pIn_buf_cur)) << num_bits); pIn_buf_cur += 4; num_bits += 32; } +#else + if (num_bits < 15) { bit_buf |= (((tinfl_bit_buf_t)MZ_READ_LE16(pIn_buf_cur)) << num_bits); pIn_buf_cur += 2; num_bits += 16; } +#endif + if ((sym2 = r->m_tables[0].m_look_up[bit_buf & (TINFL_FAST_LOOKUP_SIZE - 1)]) >= 0) + code_len = sym2 >> 9; + else + { + code_len = TINFL_FAST_LOOKUP_BITS; do { sym2 = r->m_tables[0].m_tree[~sym2 + ((bit_buf >> code_len++) & 1)]; } while (sym2 < 0); + } + counter = sym2; bit_buf >>= code_len; num_bits -= code_len; + if (counter & 256) + break; + +#if !TINFL_USE_64BIT_BITBUF + if (num_bits < 15) { bit_buf |= (((tinfl_bit_buf_t)MZ_READ_LE16(pIn_buf_cur)) << num_bits); pIn_buf_cur += 2; num_bits += 16; } +#endif + if ((sym2 = r->m_tables[0].m_look_up[bit_buf & (TINFL_FAST_LOOKUP_SIZE - 1)]) >= 0) + code_len = sym2 >> 9; + else + { + code_len = TINFL_FAST_LOOKUP_BITS; do { sym2 = r->m_tables[0].m_tree[~sym2 + ((bit_buf >> code_len++) & 1)]; } while (sym2 < 0); + } + bit_buf >>= code_len; num_bits -= code_len; + + pOut_buf_cur[0] = (mz_uint8)counter; + if (sym2 & 256) + { + pOut_buf_cur++; + counter = sym2; + break; + } + pOut_buf_cur[1] = (mz_uint8)sym2; + pOut_buf_cur += 2; + } + } + if ((counter &= 511) == 256) break; + + num_extra = s_length_extra[counter - 257]; counter = s_length_base[counter - 257]; + if (num_extra) { mz_uint extra_bits; TINFL_GET_BITS(25, extra_bits, num_extra); counter += extra_bits; } + + TINFL_HUFF_DECODE(26, dist, &r->m_tables[1]); + num_extra = s_dist_extra[dist]; dist = s_dist_base[dist]; + if (num_extra) { mz_uint extra_bits; TINFL_GET_BITS(27, extra_bits, num_extra); dist += extra_bits; } + + dist_from_out_buf_start = pOut_buf_cur - pOut_buf_start; + if ((dist > dist_from_out_buf_start) && (decomp_flags & TINFL_FLAG_USING_NON_WRAPPING_OUTPUT_BUF)) + { + TINFL_CR_RETURN_FOREVER(37, TINFL_STATUS_FAILED); + } + + pSrc = pOut_buf_start + ((dist_from_out_buf_start - dist) & out_buf_size_mask); + + if ((MZ_MAX(pOut_buf_cur, pSrc) + counter) > pOut_buf_end) + { + while (counter--) + { + while (pOut_buf_cur >= pOut_buf_end) { TINFL_CR_RETURN(53, TINFL_STATUS_HAS_MORE_OUTPUT); } + *pOut_buf_cur++ = pOut_buf_start[(dist_from_out_buf_start++ - dist) & out_buf_size_mask]; + } + continue; + } +#if MINIZ_USE_UNALIGNED_LOADS_AND_STORES + else if ((counter >= 9) && (counter <= dist)) + { + const mz_uint8 *pSrc_end = pSrc + (counter & ~7); + do + { + ((mz_uint32 *)pOut_buf_cur)[0] = ((const mz_uint32 *)pSrc)[0]; + ((mz_uint32 *)pOut_buf_cur)[1] = ((const mz_uint32 *)pSrc)[1]; + pOut_buf_cur += 8; + } while ((pSrc += 8) < pSrc_end); + if ((counter &= 7) < 3) + { + if (counter) + { + pOut_buf_cur[0] = pSrc[0]; + if (counter > 1) + pOut_buf_cur[1] = pSrc[1]; + pOut_buf_cur += counter; + } + continue; + } + } +#endif + do + { + pOut_buf_cur[0] = pSrc[0]; + pOut_buf_cur[1] = pSrc[1]; + pOut_buf_cur[2] = pSrc[2]; + pOut_buf_cur += 3; pSrc += 3; + } while ((int)(counter -= 3) > 2); + if ((int)counter > 0) + { + pOut_buf_cur[0] = pSrc[0]; + if ((int)counter > 1) + pOut_buf_cur[1] = pSrc[1]; + pOut_buf_cur += counter; + } + } + } + } while (!(r->m_final & 1)); + if (decomp_flags & TINFL_FLAG_PARSE_ZLIB_HEADER) + { + TINFL_SKIP_BITS(32, num_bits & 7); for (counter = 0; counter < 4; ++counter) { mz_uint s; if (num_bits) TINFL_GET_BITS(41, s, 8); else TINFL_GET_BYTE(42, s); r->m_z_adler32 = (r->m_z_adler32 << 8) | s; } + } + TINFL_CR_RETURN_FOREVER(34, TINFL_STATUS_DONE); + TINFL_CR_FINISH + +common_exit: + r->m_num_bits = num_bits; r->m_bit_buf = bit_buf; r->m_dist = dist; r->m_counter = counter; r->m_num_extra = num_extra; r->m_dist_from_out_buf_start = dist_from_out_buf_start; + *pIn_buf_size = pIn_buf_cur - pIn_buf_next; *pOut_buf_size = pOut_buf_cur - pOut_buf_next; + if ((decomp_flags & (TINFL_FLAG_PARSE_ZLIB_HEADER | TINFL_FLAG_COMPUTE_ADLER32)) && (status >= 0)) + { + const mz_uint8 *ptr = pOut_buf_next; size_t buf_len = *pOut_buf_size; + mz_uint32 i, s1 = r->m_check_adler32 & 0xffff, s2 = r->m_check_adler32 >> 16; size_t block_len = buf_len % 5552; + while (buf_len) + { + for (i = 0; i + 7 < block_len; i += 8, ptr += 8) + { + s1 += ptr[0], s2 += s1; s1 += ptr[1], s2 += s1; s1 += ptr[2], s2 += s1; s1 += ptr[3], s2 += s1; + s1 += ptr[4], s2 += s1; s1 += ptr[5], s2 += s1; s1 += ptr[6], s2 += s1; s1 += ptr[7], s2 += s1; + } + for ( ; i < block_len; ++i) s1 += *ptr++, s2 += s1; + s1 %= 65521U, s2 %= 65521U; buf_len -= block_len; block_len = 5552; + } + r->m_check_adler32 = (s2 << 16) + s1; if ((status == TINFL_STATUS_DONE) && (decomp_flags & TINFL_FLAG_PARSE_ZLIB_HEADER) && (r->m_check_adler32 != r->m_z_adler32)) status = TINFL_STATUS_ADLER32_MISMATCH; + } + return status; +} + +// Higher level helper functions. +void *tinfl_decompress_mem_to_heap(const void *pSrc_buf, size_t src_buf_len, size_t *pOut_len, int flags) +{ + tinfl_decompressor decomp; void *pBuf = NULL, *pNew_buf; size_t src_buf_ofs = 0, out_buf_capacity = 0; + *pOut_len = 0; + tinfl_init(&decomp); + for ( ; ; ) + { + size_t src_buf_size = src_buf_len - src_buf_ofs, dst_buf_size = out_buf_capacity - *pOut_len, new_out_buf_capacity; + tinfl_status status = tinfl_decompress(&decomp, (const mz_uint8*)pSrc_buf + src_buf_ofs, &src_buf_size, (mz_uint8*)pBuf, pBuf ? (mz_uint8*)pBuf + *pOut_len : NULL, &dst_buf_size, + (flags & ~TINFL_FLAG_HAS_MORE_INPUT) | TINFL_FLAG_USING_NON_WRAPPING_OUTPUT_BUF); + if ((status < 0) || (status == TINFL_STATUS_NEEDS_MORE_INPUT)) + { + MZ_FREE(pBuf); *pOut_len = 0; return NULL; + } + src_buf_ofs += src_buf_size; + *pOut_len += dst_buf_size; + if (status == TINFL_STATUS_DONE) break; + new_out_buf_capacity = out_buf_capacity * 2; if (new_out_buf_capacity < 128) new_out_buf_capacity = 128; + pNew_buf = MZ_REALLOC(pBuf, new_out_buf_capacity); + if (!pNew_buf) + { + MZ_FREE(pBuf); *pOut_len = 0; return NULL; + } + pBuf = pNew_buf; out_buf_capacity = new_out_buf_capacity; + } + return pBuf; +} + +size_t tinfl_decompress_mem_to_mem(void *pOut_buf, size_t out_buf_len, const void *pSrc_buf, size_t src_buf_len, int flags) +{ + tinfl_decompressor decomp; tinfl_status status; tinfl_init(&decomp); + status = tinfl_decompress(&decomp, (const mz_uint8*)pSrc_buf, &src_buf_len, (mz_uint8*)pOut_buf, (mz_uint8*)pOut_buf, &out_buf_len, (flags & ~TINFL_FLAG_HAS_MORE_INPUT) | TINFL_FLAG_USING_NON_WRAPPING_OUTPUT_BUF); + return (status != TINFL_STATUS_DONE) ? TINFL_DECOMPRESS_MEM_TO_MEM_FAILED : out_buf_len; +} + +int tinfl_decompress_mem_to_callback(const void *pIn_buf, size_t *pIn_buf_size, tinfl_put_buf_func_ptr pPut_buf_func, void *pPut_buf_user, int flags) +{ + int result = 0; + tinfl_decompressor decomp; + mz_uint8 *pDict = (mz_uint8*)MZ_MALLOC(TINFL_LZ_DICT_SIZE); size_t in_buf_ofs = 0, dict_ofs = 0; + if (!pDict) + return TINFL_STATUS_FAILED; + tinfl_init(&decomp); + for ( ; ; ) + { + size_t in_buf_size = *pIn_buf_size - in_buf_ofs, dst_buf_size = TINFL_LZ_DICT_SIZE - dict_ofs; + tinfl_status status = tinfl_decompress(&decomp, (const mz_uint8*)pIn_buf + in_buf_ofs, &in_buf_size, pDict, pDict + dict_ofs, &dst_buf_size, + (flags & ~(TINFL_FLAG_HAS_MORE_INPUT | TINFL_FLAG_USING_NON_WRAPPING_OUTPUT_BUF))); + in_buf_ofs += in_buf_size; + if ((dst_buf_size) && (!(*pPut_buf_func)(pDict + dict_ofs, (int)dst_buf_size, pPut_buf_user))) + break; + if (status != TINFL_STATUS_HAS_MORE_OUTPUT) + { + result = (status == TINFL_STATUS_DONE); + break; + } + dict_ofs = (dict_ofs + dst_buf_size) & (TINFL_LZ_DICT_SIZE - 1); + } + MZ_FREE(pDict); + *pIn_buf_size = in_buf_ofs; + return result; +} + +// ------------------- Low-level Compression (independent from all decompression API's) + +// Purposely making these tables static for faster init and thread safety. +static const mz_uint16 s_tdefl_len_sym[256] = { + 257,258,259,260,261,262,263,264,265,265,266,266,267,267,268,268,269,269,269,269,270,270,270,270,271,271,271,271,272,272,272,272, + 273,273,273,273,273,273,273,273,274,274,274,274,274,274,274,274,275,275,275,275,275,275,275,275,276,276,276,276,276,276,276,276, + 277,277,277,277,277,277,277,277,277,277,277,277,277,277,277,277,278,278,278,278,278,278,278,278,278,278,278,278,278,278,278,278, + 279,279,279,279,279,279,279,279,279,279,279,279,279,279,279,279,280,280,280,280,280,280,280,280,280,280,280,280,280,280,280,280, + 281,281,281,281,281,281,281,281,281,281,281,281,281,281,281,281,281,281,281,281,281,281,281,281,281,281,281,281,281,281,281,281, + 282,282,282,282,282,282,282,282,282,282,282,282,282,282,282,282,282,282,282,282,282,282,282,282,282,282,282,282,282,282,282,282, + 283,283,283,283,283,283,283,283,283,283,283,283,283,283,283,283,283,283,283,283,283,283,283,283,283,283,283,283,283,283,283,283, + 284,284,284,284,284,284,284,284,284,284,284,284,284,284,284,284,284,284,284,284,284,284,284,284,284,284,284,284,284,284,284,285 }; + +static const mz_uint8 s_tdefl_len_extra[256] = { + 0,0,0,0,0,0,0,0,1,1,1,1,1,1,1,1,2,2,2,2,2,2,2,2,2,2,2,2,2,2,2,2,3,3,3,3,3,3,3,3,3,3,3,3,3,3,3,3,3,3,3,3,3,3,3,3,3,3,3,3,3,3,3,3, + 4,4,4,4,4,4,4,4,4,4,4,4,4,4,4,4,4,4,4,4,4,4,4,4,4,4,4,4,4,4,4,4,4,4,4,4,4,4,4,4,4,4,4,4,4,4,4,4,4,4,4,4,4,4,4,4,4,4,4,4,4,4,4,4, + 5,5,5,5,5,5,5,5,5,5,5,5,5,5,5,5,5,5,5,5,5,5,5,5,5,5,5,5,5,5,5,5,5,5,5,5,5,5,5,5,5,5,5,5,5,5,5,5,5,5,5,5,5,5,5,5,5,5,5,5,5,5,5,5, + 5,5,5,5,5,5,5,5,5,5,5,5,5,5,5,5,5,5,5,5,5,5,5,5,5,5,5,5,5,5,5,5,5,5,5,5,5,5,5,5,5,5,5,5,5,5,5,5,5,5,5,5,5,5,5,5,5,5,5,5,5,5,5,0 }; + +static const mz_uint8 s_tdefl_small_dist_sym[512] = { + 0,1,2,3,4,4,5,5,6,6,6,6,7,7,7,7,8,8,8,8,8,8,8,8,9,9,9,9,9,9,9,9,10,10,10,10,10,10,10,10,10,10,10,10,10,10,10,10,11,11,11,11,11,11, + 11,11,11,11,11,11,11,11,11,11,12,12,12,12,12,12,12,12,12,12,12,12,12,12,12,12,12,12,12,12,12,12,12,12,12,12,12,12,12,12,12,12,13, + 13,13,13,13,13,13,13,13,13,13,13,13,13,13,13,13,13,13,13,13,13,13,13,13,13,13,13,13,13,13,13,14,14,14,14,14,14,14,14,14,14,14,14, + 14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14, + 14,14,14,14,14,14,14,14,14,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15, + 15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,16,16,16,16,16,16,16,16,16,16,16,16,16, + 16,16,16,16,16,16,16,16,16,16,16,16,16,16,16,16,16,16,16,16,16,16,16,16,16,16,16,16,16,16,16,16,16,16,16,16,16,16,16,16,16,16,16, + 16,16,16,16,16,16,16,16,16,16,16,16,16,16,16,16,16,16,16,16,16,16,16,16,16,16,16,16,16,16,16,16,16,16,16,16,16,16,16,16,16,16,16, + 16,16,16,16,16,16,16,16,16,16,16,16,16,16,16,16,16,16,16,16,16,16,16,16,16,16,16,16,16,17,17,17,17,17,17,17,17,17,17,17,17,17,17, + 17,17,17,17,17,17,17,17,17,17,17,17,17,17,17,17,17,17,17,17,17,17,17,17,17,17,17,17,17,17,17,17,17,17,17,17,17,17,17,17,17,17,17, + 17,17,17,17,17,17,17,17,17,17,17,17,17,17,17,17,17,17,17,17,17,17,17,17,17,17,17,17,17,17,17,17,17,17,17,17,17,17,17,17,17,17,17, + 17,17,17,17,17,17,17,17,17,17,17,17,17,17,17,17,17,17,17,17,17,17,17,17,17,17,17,17 }; + +static const mz_uint8 s_tdefl_small_dist_extra[512] = { + 0,0,0,0,1,1,1,1,2,2,2,2,2,2,2,2,3,3,3,3,3,3,3,3,3,3,3,3,3,3,3,3,4,4,4,4,4,4,4,4,4,4,4,4,4,4,4,4,4,4,4,4,4,4,4,4,4,4,4,4,4,4,4,4,5,5,5,5,5,5,5,5, + 5,5,5,5,5,5,5,5,5,5,5,5,5,5,5,5,5,5,5,5,5,5,5,5,5,5,5,5,5,5,5,5,5,5,5,5,5,5,5,5,5,5,5,5,5,5,5,5,5,5,5,5,5,5,5,5,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6, + 6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6, + 6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,7,7,7,7,7,7,7,7,7,7,7,7,7,7,7,7,7,7,7,7,7,7,7,7,7,7,7,7,7,7,7,7, + 7,7,7,7,7,7,7,7,7,7,7,7,7,7,7,7,7,7,7,7,7,7,7,7,7,7,7,7,7,7,7,7,7,7,7,7,7,7,7,7,7,7,7,7,7,7,7,7,7,7,7,7,7,7,7,7,7,7,7,7,7,7,7,7,7,7,7,7,7,7,7,7, + 7,7,7,7,7,7,7,7,7,7,7,7,7,7,7,7,7,7,7,7,7,7,7,7,7,7,7,7,7,7,7,7,7,7,7,7,7,7,7,7,7,7,7,7,7,7,7,7,7,7,7,7,7,7,7,7,7,7,7,7,7,7,7,7,7,7,7,7,7,7,7,7, + 7,7,7,7,7,7,7,7,7,7,7,7,7,7,7,7,7,7,7,7,7,7,7,7,7,7,7,7,7,7,7,7,7,7,7,7,7,7,7,7,7,7,7,7,7,7,7,7,7,7,7,7,7,7,7,7,7,7,7,7,7,7,7,7,7,7,7,7,7,7,7,7, + 7,7,7,7,7,7,7,7 }; + +static const mz_uint8 s_tdefl_large_dist_sym[128] = { + 0,0,18,19,20,20,21,21,22,22,22,22,23,23,23,23,24,24,24,24,24,24,24,24,25,25,25,25,25,25,25,25,26,26,26,26,26,26,26,26,26,26,26,26, + 26,26,26,26,27,27,27,27,27,27,27,27,27,27,27,27,27,27,27,27,28,28,28,28,28,28,28,28,28,28,28,28,28,28,28,28,28,28,28,28,28,28,28,28, + 28,28,28,28,28,28,28,28,29,29,29,29,29,29,29,29,29,29,29,29,29,29,29,29,29,29,29,29,29,29,29,29,29,29,29,29,29,29,29,29 }; + +static const mz_uint8 s_tdefl_large_dist_extra[128] = { + 0,0,8,8,9,9,9,9,10,10,10,10,10,10,10,10,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,12,12,12,12,12,12,12,12,12,12,12,12,12,12,12,12, + 12,12,12,12,12,12,12,12,12,12,12,12,12,12,12,12,13,13,13,13,13,13,13,13,13,13,13,13,13,13,13,13,13,13,13,13,13,13,13,13,13,13,13,13,13,13,13,13, + 13,13,13,13,13,13,13,13,13,13,13,13,13,13,13,13,13,13,13,13,13,13,13,13,13,13,13,13,13,13,13,13 }; + +// Radix sorts tdefl_sym_freq[] array by 16-bit key m_key. Returns ptr to sorted values. +typedef struct { mz_uint16 m_key, m_sym_index; } tdefl_sym_freq; +static tdefl_sym_freq* tdefl_radix_sort_syms(mz_uint num_syms, tdefl_sym_freq* pSyms0, tdefl_sym_freq* pSyms1) +{ + mz_uint32 total_passes = 2, pass_shift, pass, i, hist[256 * 2]; tdefl_sym_freq* pCur_syms = pSyms0, *pNew_syms = pSyms1; MZ_CLEAR_OBJ(hist); + for (i = 0; i < num_syms; i++) { mz_uint freq = pSyms0[i].m_key; hist[freq & 0xFF]++; hist[256 + ((freq >> 8) & 0xFF)]++; } + while ((total_passes > 1) && (num_syms == hist[(total_passes - 1) * 256])) total_passes--; + for (pass_shift = 0, pass = 0; pass < total_passes; pass++, pass_shift += 8) + { + const mz_uint32* pHist = &hist[pass << 8]; + mz_uint offsets[256], cur_ofs = 0; + for (i = 0; i < 256; i++) { offsets[i] = cur_ofs; cur_ofs += pHist[i]; } + for (i = 0; i < num_syms; i++) pNew_syms[offsets[(pCur_syms[i].m_key >> pass_shift) & 0xFF]++] = pCur_syms[i]; + { tdefl_sym_freq* t = pCur_syms; pCur_syms = pNew_syms; pNew_syms = t; } + } + return pCur_syms; +} + +// tdefl_calculate_minimum_redundancy() originally written by: Alistair Moffat, alistair@cs.mu.oz.au, Jyrki Katajainen, jyrki@diku.dk, November 1996. +static void tdefl_calculate_minimum_redundancy(tdefl_sym_freq *A, int n) +{ + int root, leaf, next, avbl, used, dpth; + if (n==0) return; else if (n==1) { A[0].m_key = 1; return; } + A[0].m_key += A[1].m_key; root = 0; leaf = 2; + for (next=1; next < n-1; next++) + { + if (leaf>=n || A[root].m_key=n || (root=0; next--) A[next].m_key = A[A[next].m_key].m_key+1; + avbl = 1; used = dpth = 0; root = n-2; next = n-1; + while (avbl>0) + { + while (root>=0 && (int)A[root].m_key==dpth) { used++; root--; } + while (avbl>used) { A[next--].m_key = (mz_uint16)(dpth); avbl--; } + avbl = 2*used; dpth++; used = 0; + } +} + +// Limits canonical Huffman code table's max code size. +enum { TDEFL_MAX_SUPPORTED_HUFF_CODESIZE = 32 }; +static void tdefl_huffman_enforce_max_code_size(int *pNum_codes, int code_list_len, int max_code_size) +{ + int i; mz_uint32 total = 0; if (code_list_len <= 1) return; + for (i = max_code_size + 1; i <= TDEFL_MAX_SUPPORTED_HUFF_CODESIZE; i++) pNum_codes[max_code_size] += pNum_codes[i]; + for (i = max_code_size; i > 0; i--) total += (((mz_uint32)pNum_codes[i]) << (max_code_size - i)); + while (total != (1UL << max_code_size)) + { + pNum_codes[max_code_size]--; + for (i = max_code_size - 1; i > 0; i--) if (pNum_codes[i]) { pNum_codes[i]--; pNum_codes[i + 1] += 2; break; } + total--; + } +} + +static void tdefl_optimize_huffman_table(tdefl_compressor *d, int table_num, int table_len, int code_size_limit, int static_table) +{ + int i, j, l, num_codes[1 + TDEFL_MAX_SUPPORTED_HUFF_CODESIZE]; mz_uint next_code[TDEFL_MAX_SUPPORTED_HUFF_CODESIZE + 1]; MZ_CLEAR_OBJ(num_codes); + if (static_table) + { + for (i = 0; i < table_len; i++) num_codes[d->m_huff_code_sizes[table_num][i]]++; + } + else + { + tdefl_sym_freq syms0[TDEFL_MAX_HUFF_SYMBOLS], syms1[TDEFL_MAX_HUFF_SYMBOLS], *pSyms; + int num_used_syms = 0; + const mz_uint16 *pSym_count = &d->m_huff_count[table_num][0]; + for (i = 0; i < table_len; i++) if (pSym_count[i]) { syms0[num_used_syms].m_key = (mz_uint16)pSym_count[i]; syms0[num_used_syms++].m_sym_index = (mz_uint16)i; } + + pSyms = tdefl_radix_sort_syms(num_used_syms, syms0, syms1); tdefl_calculate_minimum_redundancy(pSyms, num_used_syms); + + for (i = 0; i < num_used_syms; i++) num_codes[pSyms[i].m_key]++; + + tdefl_huffman_enforce_max_code_size(num_codes, num_used_syms, code_size_limit); + + MZ_CLEAR_OBJ(d->m_huff_code_sizes[table_num]); MZ_CLEAR_OBJ(d->m_huff_codes[table_num]); + for (i = 1, j = num_used_syms; i <= code_size_limit; i++) + for (l = num_codes[i]; l > 0; l--) d->m_huff_code_sizes[table_num][pSyms[--j].m_sym_index] = (mz_uint8)(i); + } + + next_code[1] = 0; for (j = 0, i = 2; i <= code_size_limit; i++) next_code[i] = j = ((j + num_codes[i - 1]) << 1); + + for (i = 0; i < table_len; i++) + { + mz_uint rev_code = 0, code, code_size; if ((code_size = d->m_huff_code_sizes[table_num][i]) == 0) continue; + code = next_code[code_size]++; for (l = code_size; l > 0; l--, code >>= 1) rev_code = (rev_code << 1) | (code & 1); + d->m_huff_codes[table_num][i] = (mz_uint16)rev_code; + } +} + +#define TDEFL_PUT_BITS(b, l) do { \ + mz_uint bits = b; mz_uint len = l; MZ_ASSERT(bits <= ((1U << len) - 1U)); \ + d->m_bit_buffer |= (bits << d->m_bits_in); d->m_bits_in += len; \ + while (d->m_bits_in >= 8) { \ + if (d->m_pOutput_buf < d->m_pOutput_buf_end) \ + *d->m_pOutput_buf++ = (mz_uint8)(d->m_bit_buffer); \ + d->m_bit_buffer >>= 8; \ + d->m_bits_in -= 8; \ + } \ +} MZ_MACRO_END + +#define TDEFL_RLE_PREV_CODE_SIZE() { if (rle_repeat_count) { \ + if (rle_repeat_count < 3) { \ + d->m_huff_count[2][prev_code_size] = (mz_uint16)(d->m_huff_count[2][prev_code_size] + rle_repeat_count); \ + while (rle_repeat_count--) packed_code_sizes[num_packed_code_sizes++] = prev_code_size; \ + } else { \ + d->m_huff_count[2][16] = (mz_uint16)(d->m_huff_count[2][16] + 1); packed_code_sizes[num_packed_code_sizes++] = 16; packed_code_sizes[num_packed_code_sizes++] = (mz_uint8)(rle_repeat_count - 3); \ +} rle_repeat_count = 0; } } + +#define TDEFL_RLE_ZERO_CODE_SIZE() { if (rle_z_count) { \ + if (rle_z_count < 3) { \ + d->m_huff_count[2][0] = (mz_uint16)(d->m_huff_count[2][0] + rle_z_count); while (rle_z_count--) packed_code_sizes[num_packed_code_sizes++] = 0; \ + } else if (rle_z_count <= 10) { \ + d->m_huff_count[2][17] = (mz_uint16)(d->m_huff_count[2][17] + 1); packed_code_sizes[num_packed_code_sizes++] = 17; packed_code_sizes[num_packed_code_sizes++] = (mz_uint8)(rle_z_count - 3); \ + } else { \ + d->m_huff_count[2][18] = (mz_uint16)(d->m_huff_count[2][18] + 1); packed_code_sizes[num_packed_code_sizes++] = 18; packed_code_sizes[num_packed_code_sizes++] = (mz_uint8)(rle_z_count - 11); \ +} rle_z_count = 0; } } + +static mz_uint8 s_tdefl_packed_code_size_syms_swizzle[] = { 16, 17, 18, 0, 8, 7, 9, 6, 10, 5, 11, 4, 12, 3, 13, 2, 14, 1, 15 }; + +static void tdefl_start_dynamic_block(tdefl_compressor *d) +{ + int num_lit_codes, num_dist_codes, num_bit_lengths; mz_uint i, total_code_sizes_to_pack, num_packed_code_sizes, rle_z_count, rle_repeat_count, packed_code_sizes_index; + mz_uint8 code_sizes_to_pack[TDEFL_MAX_HUFF_SYMBOLS_0 + TDEFL_MAX_HUFF_SYMBOLS_1], packed_code_sizes[TDEFL_MAX_HUFF_SYMBOLS_0 + TDEFL_MAX_HUFF_SYMBOLS_1], prev_code_size = 0xFF; + + d->m_huff_count[0][256] = 1; + + tdefl_optimize_huffman_table(d, 0, TDEFL_MAX_HUFF_SYMBOLS_0, 15, MZ_FALSE); + tdefl_optimize_huffman_table(d, 1, TDEFL_MAX_HUFF_SYMBOLS_1, 15, MZ_FALSE); + + for (num_lit_codes = 286; num_lit_codes > 257; num_lit_codes--) if (d->m_huff_code_sizes[0][num_lit_codes - 1]) break; + for (num_dist_codes = 30; num_dist_codes > 1; num_dist_codes--) if (d->m_huff_code_sizes[1][num_dist_codes - 1]) break; + + memcpy(code_sizes_to_pack, &d->m_huff_code_sizes[0][0], num_lit_codes); + memcpy(code_sizes_to_pack + num_lit_codes, &d->m_huff_code_sizes[1][0], num_dist_codes); + total_code_sizes_to_pack = num_lit_codes + num_dist_codes; num_packed_code_sizes = 0; rle_z_count = 0; rle_repeat_count = 0; + + memset(&d->m_huff_count[2][0], 0, sizeof(d->m_huff_count[2][0]) * TDEFL_MAX_HUFF_SYMBOLS_2); + for (i = 0; i < total_code_sizes_to_pack; i++) + { + mz_uint8 code_size = code_sizes_to_pack[i]; + if (!code_size) + { + TDEFL_RLE_PREV_CODE_SIZE(); + if (++rle_z_count == 138) { TDEFL_RLE_ZERO_CODE_SIZE(); } + } + else + { + TDEFL_RLE_ZERO_CODE_SIZE(); + if (code_size != prev_code_size) + { + TDEFL_RLE_PREV_CODE_SIZE(); + d->m_huff_count[2][code_size] = (mz_uint16)(d->m_huff_count[2][code_size] + 1); packed_code_sizes[num_packed_code_sizes++] = code_size; + } + else if (++rle_repeat_count == 6) + { + TDEFL_RLE_PREV_CODE_SIZE(); + } + } + prev_code_size = code_size; + } + if (rle_repeat_count) { TDEFL_RLE_PREV_CODE_SIZE(); } else { TDEFL_RLE_ZERO_CODE_SIZE(); } + + tdefl_optimize_huffman_table(d, 2, TDEFL_MAX_HUFF_SYMBOLS_2, 7, MZ_FALSE); + + TDEFL_PUT_BITS(2, 2); + + TDEFL_PUT_BITS(num_lit_codes - 257, 5); + TDEFL_PUT_BITS(num_dist_codes - 1, 5); + + for (num_bit_lengths = 18; num_bit_lengths >= 0; num_bit_lengths--) if (d->m_huff_code_sizes[2][s_tdefl_packed_code_size_syms_swizzle[num_bit_lengths]]) break; + num_bit_lengths = MZ_MAX(4, (num_bit_lengths + 1)); TDEFL_PUT_BITS(num_bit_lengths - 4, 4); + for (i = 0; (int)i < num_bit_lengths; i++) TDEFL_PUT_BITS(d->m_huff_code_sizes[2][s_tdefl_packed_code_size_syms_swizzle[i]], 3); + + for (packed_code_sizes_index = 0; packed_code_sizes_index < num_packed_code_sizes; ) + { + mz_uint code = packed_code_sizes[packed_code_sizes_index++]; MZ_ASSERT(code < TDEFL_MAX_HUFF_SYMBOLS_2); + TDEFL_PUT_BITS(d->m_huff_codes[2][code], d->m_huff_code_sizes[2][code]); + if (code >= 16) TDEFL_PUT_BITS(packed_code_sizes[packed_code_sizes_index++], "\02\03\07"[code - 16]); + } +} + +static void tdefl_start_static_block(tdefl_compressor *d) +{ + mz_uint i; + mz_uint8 *p = &d->m_huff_code_sizes[0][0]; + + for (i = 0; i <= 143; ++i) *p++ = 8; + for ( ; i <= 255; ++i) *p++ = 9; + for ( ; i <= 279; ++i) *p++ = 7; + for ( ; i <= 287; ++i) *p++ = 8; + + memset(d->m_huff_code_sizes[1], 5, 32); + + tdefl_optimize_huffman_table(d, 0, 288, 15, MZ_TRUE); + tdefl_optimize_huffman_table(d, 1, 32, 15, MZ_TRUE); + + TDEFL_PUT_BITS(1, 2); +} + +static const mz_uint mz_bitmasks[17] = { 0x0000, 0x0001, 0x0003, 0x0007, 0x000F, 0x001F, 0x003F, 0x007F, 0x00FF, 0x01FF, 0x03FF, 0x07FF, 0x0FFF, 0x1FFF, 0x3FFF, 0x7FFF, 0xFFFF }; + +#if MINIZ_USE_UNALIGNED_LOADS_AND_STORES && MINIZ_LITTLE_ENDIAN && MINIZ_HAS_64BIT_REGISTERS +static mz_bool tdefl_compress_lz_codes(tdefl_compressor *d) +{ + mz_uint flags; + mz_uint8 *pLZ_codes; + mz_uint8 *pOutput_buf = d->m_pOutput_buf; + mz_uint8 *pLZ_code_buf_end = d->m_pLZ_code_buf; + mz_uint64 bit_buffer = d->m_bit_buffer; + mz_uint bits_in = d->m_bits_in; + +#define TDEFL_PUT_BITS_FAST(b, l) { bit_buffer |= (((mz_uint64)(b)) << bits_in); bits_in += (l); } + + flags = 1; + for (pLZ_codes = d->m_lz_code_buf; pLZ_codes < pLZ_code_buf_end; flags >>= 1) + { + if (flags == 1) + flags = *pLZ_codes++ | 0x100; + + if (flags & 1) + { + mz_uint s0, s1, n0, n1, sym, num_extra_bits; + mz_uint match_len = pLZ_codes[0], match_dist = *(const mz_uint16 *)(pLZ_codes + 1); pLZ_codes += 3; + + MZ_ASSERT(d->m_huff_code_sizes[0][s_tdefl_len_sym[match_len]]); + TDEFL_PUT_BITS_FAST(d->m_huff_codes[0][s_tdefl_len_sym[match_len]], d->m_huff_code_sizes[0][s_tdefl_len_sym[match_len]]); + TDEFL_PUT_BITS_FAST(match_len & mz_bitmasks[s_tdefl_len_extra[match_len]], s_tdefl_len_extra[match_len]); + + // This sequence coaxes MSVC into using cmov's vs. jmp's. + s0 = s_tdefl_small_dist_sym[match_dist & 511]; + n0 = s_tdefl_small_dist_extra[match_dist & 511]; + s1 = s_tdefl_large_dist_sym[match_dist >> 8]; + n1 = s_tdefl_large_dist_extra[match_dist >> 8]; + sym = (match_dist < 512) ? s0 : s1; + num_extra_bits = (match_dist < 512) ? n0 : n1; + + MZ_ASSERT(d->m_huff_code_sizes[1][sym]); + TDEFL_PUT_BITS_FAST(d->m_huff_codes[1][sym], d->m_huff_code_sizes[1][sym]); + TDEFL_PUT_BITS_FAST(match_dist & mz_bitmasks[num_extra_bits], num_extra_bits); + } + else + { + mz_uint lit = *pLZ_codes++; + MZ_ASSERT(d->m_huff_code_sizes[0][lit]); + TDEFL_PUT_BITS_FAST(d->m_huff_codes[0][lit], d->m_huff_code_sizes[0][lit]); + + if (((flags & 2) == 0) && (pLZ_codes < pLZ_code_buf_end)) + { + flags >>= 1; + lit = *pLZ_codes++; + MZ_ASSERT(d->m_huff_code_sizes[0][lit]); + TDEFL_PUT_BITS_FAST(d->m_huff_codes[0][lit], d->m_huff_code_sizes[0][lit]); + + if (((flags & 2) == 0) && (pLZ_codes < pLZ_code_buf_end)) + { + flags >>= 1; + lit = *pLZ_codes++; + MZ_ASSERT(d->m_huff_code_sizes[0][lit]); + TDEFL_PUT_BITS_FAST(d->m_huff_codes[0][lit], d->m_huff_code_sizes[0][lit]); + } + } + } + + if (pOutput_buf >= d->m_pOutput_buf_end) + return MZ_FALSE; + + *(mz_uint64*)pOutput_buf = bit_buffer; + pOutput_buf += (bits_in >> 3); + bit_buffer >>= (bits_in & ~7); + bits_in &= 7; + } + +#undef TDEFL_PUT_BITS_FAST + + d->m_pOutput_buf = pOutput_buf; + d->m_bits_in = 0; + d->m_bit_buffer = 0; + + while (bits_in) + { + mz_uint32 n = MZ_MIN(bits_in, 16); + TDEFL_PUT_BITS((mz_uint)bit_buffer & mz_bitmasks[n], n); + bit_buffer >>= n; + bits_in -= n; + } + + TDEFL_PUT_BITS(d->m_huff_codes[0][256], d->m_huff_code_sizes[0][256]); + + return (d->m_pOutput_buf < d->m_pOutput_buf_end); +} +#else +static mz_bool tdefl_compress_lz_codes(tdefl_compressor *d) +{ + mz_uint flags; + mz_uint8 *pLZ_codes; + + flags = 1; + for (pLZ_codes = d->m_lz_code_buf; pLZ_codes < d->m_pLZ_code_buf; flags >>= 1) + { + if (flags == 1) + flags = *pLZ_codes++ | 0x100; + if (flags & 1) + { + mz_uint sym, num_extra_bits; + mz_uint match_len = pLZ_codes[0], match_dist = (pLZ_codes[1] | (pLZ_codes[2] << 8)); pLZ_codes += 3; + + MZ_ASSERT(d->m_huff_code_sizes[0][s_tdefl_len_sym[match_len]]); + TDEFL_PUT_BITS(d->m_huff_codes[0][s_tdefl_len_sym[match_len]], d->m_huff_code_sizes[0][s_tdefl_len_sym[match_len]]); + TDEFL_PUT_BITS(match_len & mz_bitmasks[s_tdefl_len_extra[match_len]], s_tdefl_len_extra[match_len]); + + if (match_dist < 512) + { + sym = s_tdefl_small_dist_sym[match_dist]; num_extra_bits = s_tdefl_small_dist_extra[match_dist]; + } + else + { + sym = s_tdefl_large_dist_sym[match_dist >> 8]; num_extra_bits = s_tdefl_large_dist_extra[match_dist >> 8]; + } + MZ_ASSERT(d->m_huff_code_sizes[1][sym]); + TDEFL_PUT_BITS(d->m_huff_codes[1][sym], d->m_huff_code_sizes[1][sym]); + TDEFL_PUT_BITS(match_dist & mz_bitmasks[num_extra_bits], num_extra_bits); + } + else + { + mz_uint lit = *pLZ_codes++; + MZ_ASSERT(d->m_huff_code_sizes[0][lit]); + TDEFL_PUT_BITS(d->m_huff_codes[0][lit], d->m_huff_code_sizes[0][lit]); + } + } + + TDEFL_PUT_BITS(d->m_huff_codes[0][256], d->m_huff_code_sizes[0][256]); + + return (d->m_pOutput_buf < d->m_pOutput_buf_end); +} +#endif // MINIZ_USE_UNALIGNED_LOADS_AND_STORES && MINIZ_LITTLE_ENDIAN && MINIZ_HAS_64BIT_REGISTERS + +static mz_bool tdefl_compress_block(tdefl_compressor *d, mz_bool static_block) +{ + if (static_block) + tdefl_start_static_block(d); + else + tdefl_start_dynamic_block(d); + return tdefl_compress_lz_codes(d); +} + +static int tdefl_flush_block(tdefl_compressor *d, int flush) +{ + mz_uint saved_bit_buf, saved_bits_in; + mz_uint8 *pSaved_output_buf; + mz_bool comp_block_succeeded = MZ_FALSE; + int n, use_raw_block = ((d->m_flags & TDEFL_FORCE_ALL_RAW_BLOCKS) != 0) && (d->m_lookahead_pos - d->m_lz_code_buf_dict_pos) <= d->m_dict_size; + mz_uint8 *pOutput_buf_start = ((d->m_pPut_buf_func == NULL) && ((*d->m_pOut_buf_size - d->m_out_buf_ofs) >= TDEFL_OUT_BUF_SIZE)) ? ((mz_uint8 *)d->m_pOut_buf + d->m_out_buf_ofs) : d->m_output_buf; + + d->m_pOutput_buf = pOutput_buf_start; + d->m_pOutput_buf_end = d->m_pOutput_buf + TDEFL_OUT_BUF_SIZE - 16; + + MZ_ASSERT(!d->m_output_flush_remaining); + d->m_output_flush_ofs = 0; + d->m_output_flush_remaining = 0; + + *d->m_pLZ_flags = (mz_uint8)(*d->m_pLZ_flags >> d->m_num_flags_left); + d->m_pLZ_code_buf -= (d->m_num_flags_left == 8); + + if ((d->m_flags & TDEFL_WRITE_ZLIB_HEADER) && (!d->m_block_index)) + { + TDEFL_PUT_BITS(0x78, 8); TDEFL_PUT_BITS(0x01, 8); + } + + TDEFL_PUT_BITS(flush == TDEFL_FINISH, 1); + + pSaved_output_buf = d->m_pOutput_buf; saved_bit_buf = d->m_bit_buffer; saved_bits_in = d->m_bits_in; + + if (!use_raw_block) + comp_block_succeeded = tdefl_compress_block(d, (d->m_flags & TDEFL_FORCE_ALL_STATIC_BLOCKS) || (d->m_total_lz_bytes < 48)); + + // If the block gets expanded, forget the current contents of the output buffer and send a raw block instead. + if ( ((use_raw_block) || ((d->m_total_lz_bytes) && ((d->m_pOutput_buf - pSaved_output_buf + 1U) >= d->m_total_lz_bytes))) && + ((d->m_lookahead_pos - d->m_lz_code_buf_dict_pos) <= d->m_dict_size) ) + { + mz_uint i; d->m_pOutput_buf = pSaved_output_buf; d->m_bit_buffer = saved_bit_buf, d->m_bits_in = saved_bits_in; + TDEFL_PUT_BITS(0, 2); + if (d->m_bits_in) { TDEFL_PUT_BITS(0, 8 - d->m_bits_in); } + for (i = 2; i; --i, d->m_total_lz_bytes ^= 0xFFFF) + { + TDEFL_PUT_BITS(d->m_total_lz_bytes & 0xFFFF, 16); + } + for (i = 0; i < d->m_total_lz_bytes; ++i) + { + TDEFL_PUT_BITS(d->m_dict[(d->m_lz_code_buf_dict_pos + i) & TDEFL_LZ_DICT_SIZE_MASK], 8); + } + } + // Check for the extremely unlikely (if not impossible) case of the compressed block not fitting into the output buffer when using dynamic codes. + else if (!comp_block_succeeded) + { + d->m_pOutput_buf = pSaved_output_buf; d->m_bit_buffer = saved_bit_buf, d->m_bits_in = saved_bits_in; + tdefl_compress_block(d, MZ_TRUE); + } + + if (flush) + { + if (flush == TDEFL_FINISH) + { + if (d->m_bits_in) { TDEFL_PUT_BITS(0, 8 - d->m_bits_in); } + if (d->m_flags & TDEFL_WRITE_ZLIB_HEADER) { mz_uint i, a = d->m_adler32; for (i = 0; i < 4; i++) { TDEFL_PUT_BITS((a >> 24) & 0xFF, 8); a <<= 8; } } + } + else + { + mz_uint i, z = 0; TDEFL_PUT_BITS(0, 3); if (d->m_bits_in) { TDEFL_PUT_BITS(0, 8 - d->m_bits_in); } for (i = 2; i; --i, z ^= 0xFFFF) { TDEFL_PUT_BITS(z & 0xFFFF, 16); } + } + } + + MZ_ASSERT(d->m_pOutput_buf < d->m_pOutput_buf_end); + + memset(&d->m_huff_count[0][0], 0, sizeof(d->m_huff_count[0][0]) * TDEFL_MAX_HUFF_SYMBOLS_0); + memset(&d->m_huff_count[1][0], 0, sizeof(d->m_huff_count[1][0]) * TDEFL_MAX_HUFF_SYMBOLS_1); + + d->m_pLZ_code_buf = d->m_lz_code_buf + 1; d->m_pLZ_flags = d->m_lz_code_buf; d->m_num_flags_left = 8; d->m_lz_code_buf_dict_pos += d->m_total_lz_bytes; d->m_total_lz_bytes = 0; d->m_block_index++; + + if ((n = (int)(d->m_pOutput_buf - pOutput_buf_start)) != 0) + { + if (d->m_pPut_buf_func) + { + *d->m_pIn_buf_size = d->m_pSrc - (const mz_uint8 *)d->m_pIn_buf; + if (!(*d->m_pPut_buf_func)(d->m_output_buf, n, d->m_pPut_buf_user)) + return (d->m_prev_return_status = TDEFL_STATUS_PUT_BUF_FAILED); + } + else if (pOutput_buf_start == d->m_output_buf) + { + int bytes_to_copy = (int)MZ_MIN((size_t)n, (size_t)(*d->m_pOut_buf_size - d->m_out_buf_ofs)); + memcpy((mz_uint8 *)d->m_pOut_buf + d->m_out_buf_ofs, d->m_output_buf, bytes_to_copy); + d->m_out_buf_ofs += bytes_to_copy; + if ((n -= bytes_to_copy) != 0) + { + d->m_output_flush_ofs = bytes_to_copy; + d->m_output_flush_remaining = n; + } + } + else + { + d->m_out_buf_ofs += n; + } + } + + return d->m_output_flush_remaining; +} + +#if MINIZ_USE_UNALIGNED_LOADS_AND_STORES +#define TDEFL_READ_UNALIGNED_WORD(p) *(const mz_uint16*)(p) +static MZ_FORCEINLINE void tdefl_find_match(tdefl_compressor *d, mz_uint lookahead_pos, mz_uint max_dist, mz_uint max_match_len, mz_uint *pMatch_dist, mz_uint *pMatch_len) +{ + mz_uint dist, pos = lookahead_pos & TDEFL_LZ_DICT_SIZE_MASK, match_len = *pMatch_len, probe_pos = pos, next_probe_pos, probe_len; + mz_uint num_probes_left = d->m_max_probes[match_len >= 32]; + const mz_uint16 *s = (const mz_uint16*)(d->m_dict + pos), *p, *q; + mz_uint16 c01 = TDEFL_READ_UNALIGNED_WORD(&d->m_dict[pos + match_len - 1]), s01 = TDEFL_READ_UNALIGNED_WORD(s); + MZ_ASSERT(max_match_len <= TDEFL_MAX_MATCH_LEN); if (max_match_len <= match_len) return; + for ( ; ; ) + { + for ( ; ; ) + { + if (--num_probes_left == 0) return; + #define TDEFL_PROBE \ + next_probe_pos = d->m_next[probe_pos]; \ + if ((!next_probe_pos) || ((dist = (mz_uint16)(lookahead_pos - next_probe_pos)) > max_dist)) return; \ + probe_pos = next_probe_pos & TDEFL_LZ_DICT_SIZE_MASK; \ + if (TDEFL_READ_UNALIGNED_WORD(&d->m_dict[probe_pos + match_len - 1]) == c01) break; + TDEFL_PROBE; TDEFL_PROBE; TDEFL_PROBE; + } + if (!dist) break; q = (const mz_uint16*)(d->m_dict + probe_pos); if (TDEFL_READ_UNALIGNED_WORD(q) != s01) continue; p = s; probe_len = 32; + do { } while ( (TDEFL_READ_UNALIGNED_WORD(++p) == TDEFL_READ_UNALIGNED_WORD(++q)) && (TDEFL_READ_UNALIGNED_WORD(++p) == TDEFL_READ_UNALIGNED_WORD(++q)) && + (TDEFL_READ_UNALIGNED_WORD(++p) == TDEFL_READ_UNALIGNED_WORD(++q)) && (TDEFL_READ_UNALIGNED_WORD(++p) == TDEFL_READ_UNALIGNED_WORD(++q)) && (--probe_len > 0) ); + if (!probe_len) + { + *pMatch_dist = dist; *pMatch_len = MZ_MIN(max_match_len, TDEFL_MAX_MATCH_LEN); break; + } + else if ((probe_len = ((mz_uint)(p - s) * 2) + (mz_uint)(*(const mz_uint8*)p == *(const mz_uint8*)q)) > match_len) + { + *pMatch_dist = dist; if ((*pMatch_len = match_len = MZ_MIN(max_match_len, probe_len)) == max_match_len) break; + c01 = TDEFL_READ_UNALIGNED_WORD(&d->m_dict[pos + match_len - 1]); + } + } +} +#else +static MZ_FORCEINLINE void tdefl_find_match(tdefl_compressor *d, mz_uint lookahead_pos, mz_uint max_dist, mz_uint max_match_len, mz_uint *pMatch_dist, mz_uint *pMatch_len) +{ + mz_uint dist, pos = lookahead_pos & TDEFL_LZ_DICT_SIZE_MASK, match_len = *pMatch_len, probe_pos = pos, next_probe_pos, probe_len; + mz_uint num_probes_left = d->m_max_probes[match_len >= 32]; + const mz_uint8 *s = d->m_dict + pos, *p, *q; + mz_uint8 c0 = d->m_dict[pos + match_len], c1 = d->m_dict[pos + match_len - 1]; + MZ_ASSERT(max_match_len <= TDEFL_MAX_MATCH_LEN); if (max_match_len <= match_len) return; + for ( ; ; ) + { + for ( ; ; ) + { + if (--num_probes_left == 0) return; + #define TDEFL_PROBE \ + next_probe_pos = d->m_next[probe_pos]; \ + if ((!next_probe_pos) || ((dist = (mz_uint16)(lookahead_pos - next_probe_pos)) > max_dist)) return; \ + probe_pos = next_probe_pos & TDEFL_LZ_DICT_SIZE_MASK; \ + if ((d->m_dict[probe_pos + match_len] == c0) && (d->m_dict[probe_pos + match_len - 1] == c1)) break; + TDEFL_PROBE; TDEFL_PROBE; TDEFL_PROBE; + } + if (!dist) break; p = s; q = d->m_dict + probe_pos; for (probe_len = 0; probe_len < max_match_len; probe_len++) if (*p++ != *q++) break; + if (probe_len > match_len) + { + *pMatch_dist = dist; if ((*pMatch_len = match_len = probe_len) == max_match_len) return; + c0 = d->m_dict[pos + match_len]; c1 = d->m_dict[pos + match_len - 1]; + } + } +} +#endif // #if MINIZ_USE_UNALIGNED_LOADS_AND_STORES + +#if MINIZ_USE_UNALIGNED_LOADS_AND_STORES && MINIZ_LITTLE_ENDIAN +static mz_bool tdefl_compress_fast(tdefl_compressor *d) +{ + // Faster, minimally featured LZRW1-style match+parse loop with better register utilization. Intended for applications where raw throughput is valued more highly than ratio. + mz_uint lookahead_pos = d->m_lookahead_pos, lookahead_size = d->m_lookahead_size, dict_size = d->m_dict_size, total_lz_bytes = d->m_total_lz_bytes, num_flags_left = d->m_num_flags_left; + mz_uint8 *pLZ_code_buf = d->m_pLZ_code_buf, *pLZ_flags = d->m_pLZ_flags; + mz_uint cur_pos = lookahead_pos & TDEFL_LZ_DICT_SIZE_MASK; + + while ((d->m_src_buf_left) || ((d->m_flush) && (lookahead_size))) + { + const mz_uint TDEFL_COMP_FAST_LOOKAHEAD_SIZE = 4096; + mz_uint dst_pos = (lookahead_pos + lookahead_size) & TDEFL_LZ_DICT_SIZE_MASK; + mz_uint num_bytes_to_process = (mz_uint)MZ_MIN(d->m_src_buf_left, TDEFL_COMP_FAST_LOOKAHEAD_SIZE - lookahead_size); + d->m_src_buf_left -= num_bytes_to_process; + lookahead_size += num_bytes_to_process; + + while (num_bytes_to_process) + { + mz_uint32 n = MZ_MIN(TDEFL_LZ_DICT_SIZE - dst_pos, num_bytes_to_process); + memcpy(d->m_dict + dst_pos, d->m_pSrc, n); + if (dst_pos < (TDEFL_MAX_MATCH_LEN - 1)) + memcpy(d->m_dict + TDEFL_LZ_DICT_SIZE + dst_pos, d->m_pSrc, MZ_MIN(n, (TDEFL_MAX_MATCH_LEN - 1) - dst_pos)); + d->m_pSrc += n; + dst_pos = (dst_pos + n) & TDEFL_LZ_DICT_SIZE_MASK; + num_bytes_to_process -= n; + } + + dict_size = MZ_MIN(TDEFL_LZ_DICT_SIZE - lookahead_size, dict_size); + if ((!d->m_flush) && (lookahead_size < TDEFL_COMP_FAST_LOOKAHEAD_SIZE)) break; + + while (lookahead_size >= 4) + { + mz_uint cur_match_dist, cur_match_len = 1; + mz_uint8 *pCur_dict = d->m_dict + cur_pos; + mz_uint first_trigram = (*(const mz_uint32 *)pCur_dict) & 0xFFFFFF; + mz_uint hash = (first_trigram ^ (first_trigram >> (24 - (TDEFL_LZ_HASH_BITS - 8)))) & TDEFL_LEVEL1_HASH_SIZE_MASK; + mz_uint probe_pos = d->m_hash[hash]; + d->m_hash[hash] = (mz_uint16)lookahead_pos; + + if (((cur_match_dist = (mz_uint16)(lookahead_pos - probe_pos)) <= dict_size) && ((*(const mz_uint32 *)(d->m_dict + (probe_pos &= TDEFL_LZ_DICT_SIZE_MASK)) & 0xFFFFFF) == first_trigram)) + { + const mz_uint16 *p = (const mz_uint16 *)pCur_dict; + const mz_uint16 *q = (const mz_uint16 *)(d->m_dict + probe_pos); + mz_uint32 probe_len = 32; + do { } while ( (TDEFL_READ_UNALIGNED_WORD(++p) == TDEFL_READ_UNALIGNED_WORD(++q)) && (TDEFL_READ_UNALIGNED_WORD(++p) == TDEFL_READ_UNALIGNED_WORD(++q)) && + (TDEFL_READ_UNALIGNED_WORD(++p) == TDEFL_READ_UNALIGNED_WORD(++q)) && (TDEFL_READ_UNALIGNED_WORD(++p) == TDEFL_READ_UNALIGNED_WORD(++q)) && (--probe_len > 0) ); + cur_match_len = ((mz_uint)(p - (const mz_uint16 *)pCur_dict) * 2) + (mz_uint)(*(const mz_uint8 *)p == *(const mz_uint8 *)q); + if (!probe_len) + cur_match_len = cur_match_dist ? TDEFL_MAX_MATCH_LEN : 0; + + if ((cur_match_len < TDEFL_MIN_MATCH_LEN) || ((cur_match_len == TDEFL_MIN_MATCH_LEN) && (cur_match_dist >= 8U*1024U))) + { + cur_match_len = 1; + *pLZ_code_buf++ = (mz_uint8)first_trigram; + *pLZ_flags = (mz_uint8)(*pLZ_flags >> 1); + d->m_huff_count[0][(mz_uint8)first_trigram]++; + } + else + { + mz_uint32 s0, s1; + cur_match_len = MZ_MIN(cur_match_len, lookahead_size); + + MZ_ASSERT((cur_match_len >= TDEFL_MIN_MATCH_LEN) && (cur_match_dist >= 1) && (cur_match_dist <= TDEFL_LZ_DICT_SIZE)); + + cur_match_dist--; + + pLZ_code_buf[0] = (mz_uint8)(cur_match_len - TDEFL_MIN_MATCH_LEN); + *(mz_uint16 *)(&pLZ_code_buf[1]) = (mz_uint16)cur_match_dist; + pLZ_code_buf += 3; + *pLZ_flags = (mz_uint8)((*pLZ_flags >> 1) | 0x80); + + s0 = s_tdefl_small_dist_sym[cur_match_dist & 511]; + s1 = s_tdefl_large_dist_sym[cur_match_dist >> 8]; + d->m_huff_count[1][(cur_match_dist < 512) ? s0 : s1]++; + + d->m_huff_count[0][s_tdefl_len_sym[cur_match_len - TDEFL_MIN_MATCH_LEN]]++; + } + } + else + { + *pLZ_code_buf++ = (mz_uint8)first_trigram; + *pLZ_flags = (mz_uint8)(*pLZ_flags >> 1); + d->m_huff_count[0][(mz_uint8)first_trigram]++; + } + + if (--num_flags_left == 0) { num_flags_left = 8; pLZ_flags = pLZ_code_buf++; } + + total_lz_bytes += cur_match_len; + lookahead_pos += cur_match_len; + dict_size = MZ_MIN(dict_size + cur_match_len, TDEFL_LZ_DICT_SIZE); + cur_pos = (cur_pos + cur_match_len) & TDEFL_LZ_DICT_SIZE_MASK; + MZ_ASSERT(lookahead_size >= cur_match_len); + lookahead_size -= cur_match_len; + + if (pLZ_code_buf > &d->m_lz_code_buf[TDEFL_LZ_CODE_BUF_SIZE - 8]) + { + int n; + d->m_lookahead_pos = lookahead_pos; d->m_lookahead_size = lookahead_size; d->m_dict_size = dict_size; + d->m_total_lz_bytes = total_lz_bytes; d->m_pLZ_code_buf = pLZ_code_buf; d->m_pLZ_flags = pLZ_flags; d->m_num_flags_left = num_flags_left; + if ((n = tdefl_flush_block(d, 0)) != 0) + return (n < 0) ? MZ_FALSE : MZ_TRUE; + total_lz_bytes = d->m_total_lz_bytes; pLZ_code_buf = d->m_pLZ_code_buf; pLZ_flags = d->m_pLZ_flags; num_flags_left = d->m_num_flags_left; + } + } + + while (lookahead_size) + { + mz_uint8 lit = d->m_dict[cur_pos]; + + total_lz_bytes++; + *pLZ_code_buf++ = lit; + *pLZ_flags = (mz_uint8)(*pLZ_flags >> 1); + if (--num_flags_left == 0) { num_flags_left = 8; pLZ_flags = pLZ_code_buf++; } + + d->m_huff_count[0][lit]++; + + lookahead_pos++; + dict_size = MZ_MIN(dict_size + 1, TDEFL_LZ_DICT_SIZE); + cur_pos = (cur_pos + 1) & TDEFL_LZ_DICT_SIZE_MASK; + lookahead_size--; + + if (pLZ_code_buf > &d->m_lz_code_buf[TDEFL_LZ_CODE_BUF_SIZE - 8]) + { + int n; + d->m_lookahead_pos = lookahead_pos; d->m_lookahead_size = lookahead_size; d->m_dict_size = dict_size; + d->m_total_lz_bytes = total_lz_bytes; d->m_pLZ_code_buf = pLZ_code_buf; d->m_pLZ_flags = pLZ_flags; d->m_num_flags_left = num_flags_left; + if ((n = tdefl_flush_block(d, 0)) != 0) + return (n < 0) ? MZ_FALSE : MZ_TRUE; + total_lz_bytes = d->m_total_lz_bytes; pLZ_code_buf = d->m_pLZ_code_buf; pLZ_flags = d->m_pLZ_flags; num_flags_left = d->m_num_flags_left; + } + } + } + + d->m_lookahead_pos = lookahead_pos; d->m_lookahead_size = lookahead_size; d->m_dict_size = dict_size; + d->m_total_lz_bytes = total_lz_bytes; d->m_pLZ_code_buf = pLZ_code_buf; d->m_pLZ_flags = pLZ_flags; d->m_num_flags_left = num_flags_left; + return MZ_TRUE; +} +#endif // MINIZ_USE_UNALIGNED_LOADS_AND_STORES && MINIZ_LITTLE_ENDIAN + +static MZ_FORCEINLINE void tdefl_record_literal(tdefl_compressor *d, mz_uint8 lit) +{ + d->m_total_lz_bytes++; + *d->m_pLZ_code_buf++ = lit; + *d->m_pLZ_flags = (mz_uint8)(*d->m_pLZ_flags >> 1); if (--d->m_num_flags_left == 0) { d->m_num_flags_left = 8; d->m_pLZ_flags = d->m_pLZ_code_buf++; } + d->m_huff_count[0][lit]++; +} + +static MZ_FORCEINLINE void tdefl_record_match(tdefl_compressor *d, mz_uint match_len, mz_uint match_dist) +{ + mz_uint32 s0, s1; + + MZ_ASSERT((match_len >= TDEFL_MIN_MATCH_LEN) && (match_dist >= 1) && (match_dist <= TDEFL_LZ_DICT_SIZE)); + + d->m_total_lz_bytes += match_len; + + d->m_pLZ_code_buf[0] = (mz_uint8)(match_len - TDEFL_MIN_MATCH_LEN); + + match_dist -= 1; + d->m_pLZ_code_buf[1] = (mz_uint8)(match_dist & 0xFF); + d->m_pLZ_code_buf[2] = (mz_uint8)(match_dist >> 8); d->m_pLZ_code_buf += 3; + + *d->m_pLZ_flags = (mz_uint8)((*d->m_pLZ_flags >> 1) | 0x80); if (--d->m_num_flags_left == 0) { d->m_num_flags_left = 8; d->m_pLZ_flags = d->m_pLZ_code_buf++; } + + s0 = s_tdefl_small_dist_sym[match_dist & 511]; s1 = s_tdefl_large_dist_sym[(match_dist >> 8) & 127]; + d->m_huff_count[1][(match_dist < 512) ? s0 : s1]++; + + if (match_len >= TDEFL_MIN_MATCH_LEN) d->m_huff_count[0][s_tdefl_len_sym[match_len - TDEFL_MIN_MATCH_LEN]]++; +} + +static mz_bool tdefl_compress_normal(tdefl_compressor *d) +{ + const mz_uint8 *pSrc = d->m_pSrc; size_t src_buf_left = d->m_src_buf_left; + tdefl_flush flush = d->m_flush; + + while ((src_buf_left) || ((flush) && (d->m_lookahead_size))) + { + mz_uint len_to_move, cur_match_dist, cur_match_len, cur_pos; + // Update dictionary and hash chains. Keeps the lookahead size equal to TDEFL_MAX_MATCH_LEN. + if ((d->m_lookahead_size + d->m_dict_size) >= (TDEFL_MIN_MATCH_LEN - 1)) + { + mz_uint dst_pos = (d->m_lookahead_pos + d->m_lookahead_size) & TDEFL_LZ_DICT_SIZE_MASK, ins_pos = d->m_lookahead_pos + d->m_lookahead_size - 2; + mz_uint hash = (d->m_dict[ins_pos & TDEFL_LZ_DICT_SIZE_MASK] << TDEFL_LZ_HASH_SHIFT) ^ d->m_dict[(ins_pos + 1) & TDEFL_LZ_DICT_SIZE_MASK]; + mz_uint num_bytes_to_process = (mz_uint)MZ_MIN(src_buf_left, TDEFL_MAX_MATCH_LEN - d->m_lookahead_size); + const mz_uint8 *pSrc_end = pSrc + num_bytes_to_process; + src_buf_left -= num_bytes_to_process; + d->m_lookahead_size += num_bytes_to_process; + while (pSrc != pSrc_end) + { + mz_uint8 c = *pSrc++; d->m_dict[dst_pos] = c; if (dst_pos < (TDEFL_MAX_MATCH_LEN - 1)) d->m_dict[TDEFL_LZ_DICT_SIZE + dst_pos] = c; + hash = ((hash << TDEFL_LZ_HASH_SHIFT) ^ c) & (TDEFL_LZ_HASH_SIZE - 1); + d->m_next[ins_pos & TDEFL_LZ_DICT_SIZE_MASK] = d->m_hash[hash]; d->m_hash[hash] = (mz_uint16)(ins_pos); + dst_pos = (dst_pos + 1) & TDEFL_LZ_DICT_SIZE_MASK; ins_pos++; + } + } + else + { + while ((src_buf_left) && (d->m_lookahead_size < TDEFL_MAX_MATCH_LEN)) + { + mz_uint8 c = *pSrc++; + mz_uint dst_pos = (d->m_lookahead_pos + d->m_lookahead_size) & TDEFL_LZ_DICT_SIZE_MASK; + src_buf_left--; + d->m_dict[dst_pos] = c; + if (dst_pos < (TDEFL_MAX_MATCH_LEN - 1)) + d->m_dict[TDEFL_LZ_DICT_SIZE + dst_pos] = c; + if ((++d->m_lookahead_size + d->m_dict_size) >= TDEFL_MIN_MATCH_LEN) + { + mz_uint ins_pos = d->m_lookahead_pos + (d->m_lookahead_size - 1) - 2; + mz_uint hash = ((d->m_dict[ins_pos & TDEFL_LZ_DICT_SIZE_MASK] << (TDEFL_LZ_HASH_SHIFT * 2)) ^ (d->m_dict[(ins_pos + 1) & TDEFL_LZ_DICT_SIZE_MASK] << TDEFL_LZ_HASH_SHIFT) ^ c) & (TDEFL_LZ_HASH_SIZE - 1); + d->m_next[ins_pos & TDEFL_LZ_DICT_SIZE_MASK] = d->m_hash[hash]; d->m_hash[hash] = (mz_uint16)(ins_pos); + } + } + } + d->m_dict_size = MZ_MIN(TDEFL_LZ_DICT_SIZE - d->m_lookahead_size, d->m_dict_size); + if ((!flush) && (d->m_lookahead_size < TDEFL_MAX_MATCH_LEN)) + break; + + // Simple lazy/greedy parsing state machine. + len_to_move = 1; cur_match_dist = 0; cur_match_len = d->m_saved_match_len ? d->m_saved_match_len : (TDEFL_MIN_MATCH_LEN - 1); cur_pos = d->m_lookahead_pos & TDEFL_LZ_DICT_SIZE_MASK; + if (d->m_flags & (TDEFL_RLE_MATCHES | TDEFL_FORCE_ALL_RAW_BLOCKS)) + { + if ((d->m_dict_size) && (!(d->m_flags & TDEFL_FORCE_ALL_RAW_BLOCKS))) + { + mz_uint8 c = d->m_dict[(cur_pos - 1) & TDEFL_LZ_DICT_SIZE_MASK]; + cur_match_len = 0; while (cur_match_len < d->m_lookahead_size) { if (d->m_dict[cur_pos + cur_match_len] != c) break; cur_match_len++; } + if (cur_match_len < TDEFL_MIN_MATCH_LEN) cur_match_len = 0; else cur_match_dist = 1; + } + } + else + { + tdefl_find_match(d, d->m_lookahead_pos, d->m_dict_size, d->m_lookahead_size, &cur_match_dist, &cur_match_len); + } + if (((cur_match_len == TDEFL_MIN_MATCH_LEN) && (cur_match_dist >= 8U*1024U)) || (cur_pos == cur_match_dist) || ((d->m_flags & TDEFL_FILTER_MATCHES) && (cur_match_len <= 5))) + { + cur_match_dist = cur_match_len = 0; + } + if (d->m_saved_match_len) + { + if (cur_match_len > d->m_saved_match_len) + { + tdefl_record_literal(d, (mz_uint8)d->m_saved_lit); + if (cur_match_len >= 128) + { + tdefl_record_match(d, cur_match_len, cur_match_dist); + d->m_saved_match_len = 0; len_to_move = cur_match_len; + } + else + { + d->m_saved_lit = d->m_dict[cur_pos]; d->m_saved_match_dist = cur_match_dist; d->m_saved_match_len = cur_match_len; + } + } + else + { + tdefl_record_match(d, d->m_saved_match_len, d->m_saved_match_dist); + len_to_move = d->m_saved_match_len - 1; d->m_saved_match_len = 0; + } + } + else if (!cur_match_dist) + tdefl_record_literal(d, d->m_dict[MZ_MIN(cur_pos, sizeof(d->m_dict) - 1)]); + else if ((d->m_greedy_parsing) || (d->m_flags & TDEFL_RLE_MATCHES) || (cur_match_len >= 128)) + { + tdefl_record_match(d, cur_match_len, cur_match_dist); + len_to_move = cur_match_len; + } + else + { + d->m_saved_lit = d->m_dict[MZ_MIN(cur_pos, sizeof(d->m_dict) - 1)]; d->m_saved_match_dist = cur_match_dist; d->m_saved_match_len = cur_match_len; + } + // Move the lookahead forward by len_to_move bytes. + d->m_lookahead_pos += len_to_move; + MZ_ASSERT(d->m_lookahead_size >= len_to_move); + d->m_lookahead_size -= len_to_move; + d->m_dict_size = MZ_MIN(d->m_dict_size + len_to_move, TDEFL_LZ_DICT_SIZE); + // Check if it's time to flush the current LZ codes to the internal output buffer. + if ( (d->m_pLZ_code_buf > &d->m_lz_code_buf[TDEFL_LZ_CODE_BUF_SIZE - 8]) || + ( (d->m_total_lz_bytes > 31*1024) && (((((mz_uint)(d->m_pLZ_code_buf - d->m_lz_code_buf) * 115) >> 7) >= d->m_total_lz_bytes) || (d->m_flags & TDEFL_FORCE_ALL_RAW_BLOCKS))) ) + { + int n; + d->m_pSrc = pSrc; d->m_src_buf_left = src_buf_left; + if ((n = tdefl_flush_block(d, 0)) != 0) + return (n < 0) ? MZ_FALSE : MZ_TRUE; + } + } + + d->m_pSrc = pSrc; d->m_src_buf_left = src_buf_left; + return MZ_TRUE; +} + +static tdefl_status tdefl_flush_output_buffer(tdefl_compressor *d) +{ + if (d->m_pIn_buf_size) + { + *d->m_pIn_buf_size = d->m_pSrc - (const mz_uint8 *)d->m_pIn_buf; + } + + if (d->m_pOut_buf_size) + { + size_t n = MZ_MIN(*d->m_pOut_buf_size - d->m_out_buf_ofs, d->m_output_flush_remaining); + memcpy((mz_uint8 *)d->m_pOut_buf + d->m_out_buf_ofs, d->m_output_buf + d->m_output_flush_ofs, n); + d->m_output_flush_ofs += (mz_uint)n; + d->m_output_flush_remaining -= (mz_uint)n; + d->m_out_buf_ofs += n; + + *d->m_pOut_buf_size = d->m_out_buf_ofs; + } + + return (d->m_finished && !d->m_output_flush_remaining) ? TDEFL_STATUS_DONE : TDEFL_STATUS_OKAY; +} + +tdefl_status tdefl_compress(tdefl_compressor *d, const void *pIn_buf, size_t *pIn_buf_size, void *pOut_buf, size_t *pOut_buf_size, tdefl_flush flush) +{ + if (!d) + { + if (pIn_buf_size) *pIn_buf_size = 0; + if (pOut_buf_size) *pOut_buf_size = 0; + return TDEFL_STATUS_BAD_PARAM; + } + + d->m_pIn_buf = pIn_buf; d->m_pIn_buf_size = pIn_buf_size; + d->m_pOut_buf = pOut_buf; d->m_pOut_buf_size = pOut_buf_size; + d->m_pSrc = (const mz_uint8 *)(pIn_buf); d->m_src_buf_left = pIn_buf_size ? *pIn_buf_size : 0; + d->m_out_buf_ofs = 0; + d->m_flush = flush; + + if ( ((d->m_pPut_buf_func != NULL) == ((pOut_buf != NULL) || (pOut_buf_size != NULL))) || (d->m_prev_return_status != TDEFL_STATUS_OKAY) || + (d->m_wants_to_finish && (flush != TDEFL_FINISH)) || (pIn_buf_size && *pIn_buf_size && !pIn_buf) || (pOut_buf_size && *pOut_buf_size && !pOut_buf) ) + { + if (pIn_buf_size) *pIn_buf_size = 0; + if (pOut_buf_size) *pOut_buf_size = 0; + return (d->m_prev_return_status = TDEFL_STATUS_BAD_PARAM); + } + d->m_wants_to_finish |= (flush == TDEFL_FINISH); + + if ((d->m_output_flush_remaining) || (d->m_finished)) + return (d->m_prev_return_status = tdefl_flush_output_buffer(d)); + +#if MINIZ_USE_UNALIGNED_LOADS_AND_STORES && MINIZ_LITTLE_ENDIAN + if (((d->m_flags & TDEFL_MAX_PROBES_MASK) == 1) && + ((d->m_flags & TDEFL_GREEDY_PARSING_FLAG) != 0) && + ((d->m_flags & (TDEFL_FILTER_MATCHES | TDEFL_FORCE_ALL_RAW_BLOCKS | TDEFL_RLE_MATCHES)) == 0)) + { + if (!tdefl_compress_fast(d)) + return d->m_prev_return_status; + } + else +#endif // #if MINIZ_USE_UNALIGNED_LOADS_AND_STORES && MINIZ_LITTLE_ENDIAN + { + if (!tdefl_compress_normal(d)) + return d->m_prev_return_status; + } + + if ((d->m_flags & (TDEFL_WRITE_ZLIB_HEADER | TDEFL_COMPUTE_ADLER32)) && (pIn_buf)) + d->m_adler32 = (mz_uint32)mz_adler32(d->m_adler32, (const mz_uint8 *)pIn_buf, d->m_pSrc - (const mz_uint8 *)pIn_buf); + + if ((flush) && (!d->m_lookahead_size) && (!d->m_src_buf_left) && (!d->m_output_flush_remaining)) + { + if (tdefl_flush_block(d, flush) < 0) + return d->m_prev_return_status; + d->m_finished = (flush == TDEFL_FINISH); + if (flush == TDEFL_FULL_FLUSH) { MZ_CLEAR_OBJ(d->m_hash); MZ_CLEAR_OBJ(d->m_next); d->m_dict_size = 0; } + } + + return (d->m_prev_return_status = tdefl_flush_output_buffer(d)); +} + +tdefl_status tdefl_compress_buffer(tdefl_compressor *d, const void *pIn_buf, size_t in_buf_size, tdefl_flush flush) +{ + MZ_ASSERT(d->m_pPut_buf_func); return tdefl_compress(d, pIn_buf, &in_buf_size, NULL, NULL, flush); +} + +tdefl_status tdefl_init(tdefl_compressor *d, tdefl_put_buf_func_ptr pPut_buf_func, void *pPut_buf_user, int flags) +{ + d->m_pPut_buf_func = pPut_buf_func; d->m_pPut_buf_user = pPut_buf_user; + d->m_flags = (mz_uint)(flags); d->m_max_probes[0] = 1 + ((flags & 0xFFF) + 2) / 3; d->m_greedy_parsing = (flags & TDEFL_GREEDY_PARSING_FLAG) != 0; + d->m_max_probes[1] = 1 + (((flags & 0xFFF) >> 2) + 2) / 3; + if (!(flags & TDEFL_NONDETERMINISTIC_PARSING_FLAG)) MZ_CLEAR_OBJ(d->m_hash); + d->m_lookahead_pos = d->m_lookahead_size = d->m_dict_size = d->m_total_lz_bytes = d->m_lz_code_buf_dict_pos = d->m_bits_in = 0; + d->m_output_flush_ofs = d->m_output_flush_remaining = d->m_finished = d->m_block_index = d->m_bit_buffer = d->m_wants_to_finish = 0; + d->m_pLZ_code_buf = d->m_lz_code_buf + 1; d->m_pLZ_flags = d->m_lz_code_buf; d->m_num_flags_left = 8; + d->m_pOutput_buf = d->m_output_buf; d->m_pOutput_buf_end = d->m_output_buf; d->m_prev_return_status = TDEFL_STATUS_OKAY; + d->m_saved_match_dist = d->m_saved_match_len = d->m_saved_lit = 0; d->m_adler32 = 1; + d->m_pIn_buf = NULL; d->m_pOut_buf = NULL; + d->m_pIn_buf_size = NULL; d->m_pOut_buf_size = NULL; + d->m_flush = TDEFL_NO_FLUSH; d->m_pSrc = NULL; d->m_src_buf_left = 0; d->m_out_buf_ofs = 0; + memset(&d->m_huff_count[0][0], 0, sizeof(d->m_huff_count[0][0]) * TDEFL_MAX_HUFF_SYMBOLS_0); + memset(&d->m_huff_count[1][0], 0, sizeof(d->m_huff_count[1][0]) * TDEFL_MAX_HUFF_SYMBOLS_1); + return TDEFL_STATUS_OKAY; +} + +tdefl_status tdefl_get_prev_return_status(tdefl_compressor *d) +{ + return d->m_prev_return_status; +} + +mz_uint32 tdefl_get_adler32(tdefl_compressor *d) +{ + return d->m_adler32; +} + +mz_bool tdefl_compress_mem_to_output(const void *pBuf, size_t buf_len, tdefl_put_buf_func_ptr pPut_buf_func, void *pPut_buf_user, int flags) +{ + tdefl_compressor *pComp; mz_bool succeeded; if (((buf_len) && (!pBuf)) || (!pPut_buf_func)) return MZ_FALSE; + pComp = (tdefl_compressor*)MZ_MALLOC(sizeof(tdefl_compressor)); if (!pComp) return MZ_FALSE; + succeeded = (tdefl_init(pComp, pPut_buf_func, pPut_buf_user, flags) == TDEFL_STATUS_OKAY); + succeeded = succeeded && (tdefl_compress_buffer(pComp, pBuf, buf_len, TDEFL_FINISH) == TDEFL_STATUS_DONE); + MZ_FREE(pComp); return succeeded; +} + +typedef struct +{ + size_t m_size, m_capacity; + mz_uint8 *m_pBuf; + mz_bool m_expandable; +} tdefl_output_buffer; + +static mz_bool tdefl_output_buffer_putter(const void *pBuf, int len, void *pUser) +{ + tdefl_output_buffer *p = (tdefl_output_buffer *)pUser; + size_t new_size = p->m_size + len; + if (new_size > p->m_capacity) + { + size_t new_capacity = p->m_capacity; mz_uint8 *pNew_buf; if (!p->m_expandable) return MZ_FALSE; + do { new_capacity = MZ_MAX(128U, new_capacity << 1U); } while (new_size > new_capacity); + pNew_buf = (mz_uint8*)MZ_REALLOC(p->m_pBuf, new_capacity); if (!pNew_buf) return MZ_FALSE; + p->m_pBuf = pNew_buf; p->m_capacity = new_capacity; + } + memcpy((mz_uint8*)p->m_pBuf + p->m_size, pBuf, len); p->m_size = new_size; + return MZ_TRUE; +} + +void *tdefl_compress_mem_to_heap(const void *pSrc_buf, size_t src_buf_len, size_t *pOut_len, int flags) +{ + tdefl_output_buffer out_buf; MZ_CLEAR_OBJ(out_buf); + if (!pOut_len) return MZ_FALSE; else *pOut_len = 0; + out_buf.m_expandable = MZ_TRUE; + if (!tdefl_compress_mem_to_output(pSrc_buf, src_buf_len, tdefl_output_buffer_putter, &out_buf, flags)) return NULL; + *pOut_len = out_buf.m_size; return out_buf.m_pBuf; +} + +size_t tdefl_compress_mem_to_mem(void *pOut_buf, size_t out_buf_len, const void *pSrc_buf, size_t src_buf_len, int flags) +{ + tdefl_output_buffer out_buf; MZ_CLEAR_OBJ(out_buf); + if (!pOut_buf) return 0; + out_buf.m_pBuf = (mz_uint8*)pOut_buf; out_buf.m_capacity = out_buf_len; + if (!tdefl_compress_mem_to_output(pSrc_buf, src_buf_len, tdefl_output_buffer_putter, &out_buf, flags)) return 0; + return out_buf.m_size; +} + +#ifndef MINIZ_NO_ZLIB_APIS +static const mz_uint s_tdefl_num_probes[11] = { 0, 1, 6, 32, 16, 32, 128, 256, 512, 768, 1500 }; + +// level may actually range from [0,10] (10 is a "hidden" max level, where we want a bit more compression and it's fine if throughput to fall off a cliff on some files). +mz_uint tdefl_create_comp_flags_from_zip_params(int level, int window_bits, int strategy) +{ + mz_uint comp_flags = s_tdefl_num_probes[(level >= 0) ? MZ_MIN(10, level) : MZ_DEFAULT_LEVEL] | ((level <= 3) ? TDEFL_GREEDY_PARSING_FLAG : 0); + if (window_bits > 0) comp_flags |= TDEFL_WRITE_ZLIB_HEADER; + + if (!level) comp_flags |= TDEFL_FORCE_ALL_RAW_BLOCKS; + else if (strategy == MZ_FILTERED) comp_flags |= TDEFL_FILTER_MATCHES; + else if (strategy == MZ_HUFFMAN_ONLY) comp_flags &= ~TDEFL_MAX_PROBES_MASK; + else if (strategy == MZ_FIXED) comp_flags |= TDEFL_FORCE_ALL_STATIC_BLOCKS; + else if (strategy == MZ_RLE) comp_flags |= TDEFL_RLE_MATCHES; + + return comp_flags; +} +#endif //MINIZ_NO_ZLIB_APIS + +#ifdef _MSC_VER +#pragma warning (push) +#pragma warning (disable:4204) // nonstandard extension used : non-constant aggregate initializer (also supported by GNU C and C99, so no big deal) +#endif + +// Simple PNG writer function by Alex Evans, 2011. Released into the public domain: https://gist.github.com/908299, more context at +// http://altdevblogaday.org/2011/04/06/a-smaller-jpg-encoder/. +// This is actually a modification of Alex's original code so PNG files generated by this function pass pngcheck. +void *tdefl_write_image_to_png_file_in_memory_ex(const void *pImage, int w, int h, int num_chans, size_t *pLen_out, mz_uint level, mz_bool flip) +{ + // Using a local copy of this array here in case MINIZ_NO_ZLIB_APIS was defined. + static const mz_uint s_tdefl_png_num_probes[11] = { 0, 1, 6, 32, 16, 32, 128, 256, 512, 768, 1500 }; + tdefl_compressor *pComp = (tdefl_compressor *)MZ_MALLOC(sizeof(tdefl_compressor)); tdefl_output_buffer out_buf; int i, bpl = w * num_chans, y, z; mz_uint32 c; *pLen_out = 0; + if (!pComp) return NULL; + MZ_CLEAR_OBJ(out_buf); out_buf.m_expandable = MZ_TRUE; out_buf.m_capacity = 57+MZ_MAX(64, (1+bpl)*h); if (NULL == (out_buf.m_pBuf = (mz_uint8*)MZ_MALLOC(out_buf.m_capacity))) { MZ_FREE(pComp); return NULL; } + // write dummy header + for (z = 41; z; --z) tdefl_output_buffer_putter(&z, 1, &out_buf); + // compress image data + tdefl_init(pComp, tdefl_output_buffer_putter, &out_buf, s_tdefl_png_num_probes[MZ_MIN(10, level)] | TDEFL_WRITE_ZLIB_HEADER); + for (y = 0; y < h; ++y) { tdefl_compress_buffer(pComp, &z, 1, TDEFL_NO_FLUSH); tdefl_compress_buffer(pComp, (mz_uint8*)pImage + (flip ? (h - 1 - y) : y) * bpl, bpl, TDEFL_NO_FLUSH); } + if (tdefl_compress_buffer(pComp, NULL, 0, TDEFL_FINISH) != TDEFL_STATUS_DONE) { MZ_FREE(pComp); MZ_FREE(out_buf.m_pBuf); return NULL; } + // write real header + *pLen_out = out_buf.m_size-41; + { + static const mz_uint8 chans[] = {0x00, 0x00, 0x04, 0x02, 0x06}; + mz_uint8 pnghdr[41]={0x89,0x50,0x4e,0x47,0x0d,0x0a,0x1a,0x0a,0x00,0x00,0x00,0x0d,0x49,0x48,0x44,0x52, + 0,0,(mz_uint8)(w>>8),(mz_uint8)w,0,0,(mz_uint8)(h>>8),(mz_uint8)h,8,chans[num_chans],0,0,0,0,0,0,0, + (mz_uint8)(*pLen_out>>24),(mz_uint8)(*pLen_out>>16),(mz_uint8)(*pLen_out>>8),(mz_uint8)*pLen_out,0x49,0x44,0x41,0x54}; + c=(mz_uint32)mz_crc32(MZ_CRC32_INIT,pnghdr+12,17); for (i=0; i<4; ++i, c<<=8) ((mz_uint8*)(pnghdr+29))[i]=(mz_uint8)(c>>24); + memcpy(out_buf.m_pBuf, pnghdr, 41); + } + // write footer (IDAT CRC-32, followed by IEND chunk) + if (!tdefl_output_buffer_putter("\0\0\0\0\0\0\0\0\x49\x45\x4e\x44\xae\x42\x60\x82", 16, &out_buf)) { *pLen_out = 0; MZ_FREE(pComp); MZ_FREE(out_buf.m_pBuf); return NULL; } + c = (mz_uint32)mz_crc32(MZ_CRC32_INIT,out_buf.m_pBuf+41-4, *pLen_out+4); for (i=0; i<4; ++i, c<<=8) (out_buf.m_pBuf+out_buf.m_size-16)[i] = (mz_uint8)(c >> 24); + // compute final size of file, grab compressed data buffer and return + *pLen_out += 57; MZ_FREE(pComp); return out_buf.m_pBuf; +} +void *tdefl_write_image_to_png_file_in_memory(const void *pImage, int w, int h, int num_chans, size_t *pLen_out) +{ + // Level 6 corresponds to TDEFL_DEFAULT_MAX_PROBES or MZ_DEFAULT_LEVEL (but we can't depend on MZ_DEFAULT_LEVEL being available in case the zlib API's where #defined out) + return tdefl_write_image_to_png_file_in_memory_ex(pImage, w, h, num_chans, pLen_out, 6, MZ_FALSE); +} + +#ifdef _MSC_VER +#pragma warning (pop) +#endif + +// ------------------- .ZIP archive reading + +#ifndef MINIZ_NO_ARCHIVE_APIS + +#ifdef MINIZ_NO_STDIO + #define MZ_FILE void * +#else + #include + #include + + #if defined(_MSC_VER) || defined(__MINGW64__) + static FILE *mz_fopen(const char *pFilename, const char *pMode) + { + FILE* pFile = NULL; + fopen_s(&pFile, pFilename, pMode); + return pFile; + } + static FILE *mz_freopen(const char *pPath, const char *pMode, FILE *pStream) + { + FILE* pFile = NULL; + if (freopen_s(&pFile, pPath, pMode, pStream)) + return NULL; + return pFile; + } + #ifndef MINIZ_NO_TIME + #include + #endif + #define MZ_FILE FILE + #define MZ_FOPEN mz_fopen + #define MZ_FCLOSE fclose + #define MZ_FREAD fread + #define MZ_FWRITE fwrite + #define MZ_FTELL64 _ftelli64 + #define MZ_FSEEK64 _fseeki64 + #define MZ_FILE_STAT_STRUCT _stat + #define MZ_FILE_STAT _stat + #define MZ_FFLUSH fflush + #define MZ_FREOPEN mz_freopen + #define MZ_DELETE_FILE remove + #elif defined(__MINGW32__) + #ifndef MINIZ_NO_TIME + #include + #endif + #define MZ_FILE FILE + #define MZ_FOPEN(f, m) fopen(f, m) + #define MZ_FCLOSE fclose + #define MZ_FREAD fread + #define MZ_FWRITE fwrite + #define MZ_FTELL64 ftello64 + #define MZ_FSEEK64 fseeko64 + #define MZ_FILE_STAT_STRUCT _stat + #define MZ_FILE_STAT _stat + #define MZ_FFLUSH fflush + #define MZ_FREOPEN(f, m, s) freopen(f, m, s) + #define MZ_DELETE_FILE remove + #elif defined(__TINYC__) + #ifndef MINIZ_NO_TIME + #include + #endif + #define MZ_FILE FILE + #define MZ_FOPEN(f, m) fopen(f, m) + #define MZ_FCLOSE fclose + #define MZ_FREAD fread + #define MZ_FWRITE fwrite + #define MZ_FTELL64 ftell + #define MZ_FSEEK64 fseek + #define MZ_FILE_STAT_STRUCT stat + #define MZ_FILE_STAT stat + #define MZ_FFLUSH fflush + #define MZ_FREOPEN(f, m, s) freopen(f, m, s) + #define MZ_DELETE_FILE remove + #elif defined(__GNUC__) && _LARGEFILE64_SOURCE + #ifndef MINIZ_NO_TIME + #include + #endif + #define MZ_FILE FILE + #define MZ_FOPEN(f, m) fopen64(f, m) + #define MZ_FCLOSE fclose + #define MZ_FREAD fread + #define MZ_FWRITE fwrite + #define MZ_FTELL64 ftello64 + #define MZ_FSEEK64 fseeko64 + #define MZ_FILE_STAT_STRUCT stat64 + #define MZ_FILE_STAT stat64 + #define MZ_FFLUSH fflush + #define MZ_FREOPEN(p, m, s) freopen64(p, m, s) + #define MZ_DELETE_FILE remove + #else + #ifndef MINIZ_NO_TIME + #include + #endif + #define MZ_FILE FILE + #define MZ_FOPEN(f, m) fopen(f, m) + #define MZ_FCLOSE fclose + #define MZ_FREAD fread + #define MZ_FWRITE fwrite + #define MZ_FTELL64 ftello + #define MZ_FSEEK64 fseeko + #define MZ_FILE_STAT_STRUCT stat + #define MZ_FILE_STAT stat + #define MZ_FFLUSH fflush + #define MZ_FREOPEN(f, m, s) freopen(f, m, s) + #define MZ_DELETE_FILE remove + #endif // #ifdef _MSC_VER +#endif // #ifdef MINIZ_NO_STDIO + +#define MZ_TOLOWER(c) ((((c) >= 'A') && ((c) <= 'Z')) ? ((c) - 'A' + 'a') : (c)) + +// Various ZIP archive enums. To completely avoid cross platform compiler alignment and platform endian issues, miniz.c doesn't use structs for any of this stuff. +enum +{ + // ZIP archive identifiers and record sizes + MZ_ZIP_END_OF_CENTRAL_DIR_HEADER_SIG = 0x06054b50, MZ_ZIP_CENTRAL_DIR_HEADER_SIG = 0x02014b50, MZ_ZIP_LOCAL_DIR_HEADER_SIG = 0x04034b50, + MZ_ZIP_LOCAL_DIR_HEADER_SIZE = 30, MZ_ZIP_CENTRAL_DIR_HEADER_SIZE = 46, MZ_ZIP_END_OF_CENTRAL_DIR_HEADER_SIZE = 22, + // Central directory header record offsets + MZ_ZIP_CDH_SIG_OFS = 0, MZ_ZIP_CDH_VERSION_MADE_BY_OFS = 4, MZ_ZIP_CDH_VERSION_NEEDED_OFS = 6, MZ_ZIP_CDH_BIT_FLAG_OFS = 8, + MZ_ZIP_CDH_METHOD_OFS = 10, MZ_ZIP_CDH_FILE_TIME_OFS = 12, MZ_ZIP_CDH_FILE_DATE_OFS = 14, MZ_ZIP_CDH_CRC32_OFS = 16, + MZ_ZIP_CDH_COMPRESSED_SIZE_OFS = 20, MZ_ZIP_CDH_DECOMPRESSED_SIZE_OFS = 24, MZ_ZIP_CDH_FILENAME_LEN_OFS = 28, MZ_ZIP_CDH_EXTRA_LEN_OFS = 30, + MZ_ZIP_CDH_COMMENT_LEN_OFS = 32, MZ_ZIP_CDH_DISK_START_OFS = 34, MZ_ZIP_CDH_INTERNAL_ATTR_OFS = 36, MZ_ZIP_CDH_EXTERNAL_ATTR_OFS = 38, MZ_ZIP_CDH_LOCAL_HEADER_OFS = 42, + // Local directory header offsets + MZ_ZIP_LDH_SIG_OFS = 0, MZ_ZIP_LDH_VERSION_NEEDED_OFS = 4, MZ_ZIP_LDH_BIT_FLAG_OFS = 6, MZ_ZIP_LDH_METHOD_OFS = 8, MZ_ZIP_LDH_FILE_TIME_OFS = 10, + MZ_ZIP_LDH_FILE_DATE_OFS = 12, MZ_ZIP_LDH_CRC32_OFS = 14, MZ_ZIP_LDH_COMPRESSED_SIZE_OFS = 18, MZ_ZIP_LDH_DECOMPRESSED_SIZE_OFS = 22, + MZ_ZIP_LDH_FILENAME_LEN_OFS = 26, MZ_ZIP_LDH_EXTRA_LEN_OFS = 28, + // End of central directory offsets + MZ_ZIP_ECDH_SIG_OFS = 0, MZ_ZIP_ECDH_NUM_THIS_DISK_OFS = 4, MZ_ZIP_ECDH_NUM_DISK_CDIR_OFS = 6, MZ_ZIP_ECDH_CDIR_NUM_ENTRIES_ON_DISK_OFS = 8, + MZ_ZIP_ECDH_CDIR_TOTAL_ENTRIES_OFS = 10, MZ_ZIP_ECDH_CDIR_SIZE_OFS = 12, MZ_ZIP_ECDH_CDIR_OFS_OFS = 16, MZ_ZIP_ECDH_COMMENT_SIZE_OFS = 20, +}; + +typedef struct +{ + void *m_p; + size_t m_size, m_capacity; + mz_uint m_element_size; +} mz_zip_array; + +struct mz_zip_internal_state_tag +{ + mz_zip_array m_central_dir; + mz_zip_array m_central_dir_offsets; + mz_zip_array m_sorted_central_dir_offsets; + MZ_FILE *m_pFile; + void *m_pMem; + size_t m_mem_size; + size_t m_mem_capacity; +}; + +#define MZ_ZIP_ARRAY_SET_ELEMENT_SIZE(array_ptr, element_size) (array_ptr)->m_element_size = element_size +#define MZ_ZIP_ARRAY_ELEMENT(array_ptr, element_type, index) ((element_type *)((array_ptr)->m_p))[index] + +static MZ_FORCEINLINE void mz_zip_array_clear(mz_zip_archive *pZip, mz_zip_array *pArray) +{ + pZip->m_pFree(pZip->m_pAlloc_opaque, pArray->m_p); + memset(pArray, 0, sizeof(mz_zip_array)); +} + +static mz_bool mz_zip_array_ensure_capacity(mz_zip_archive *pZip, mz_zip_array *pArray, size_t min_new_capacity, mz_uint growing) +{ + void *pNew_p; size_t new_capacity = min_new_capacity; MZ_ASSERT(pArray->m_element_size); if (pArray->m_capacity >= min_new_capacity) return MZ_TRUE; + if (growing) { new_capacity = MZ_MAX(1, pArray->m_capacity); while (new_capacity < min_new_capacity) new_capacity *= 2; } + if (NULL == (pNew_p = pZip->m_pRealloc(pZip->m_pAlloc_opaque, pArray->m_p, pArray->m_element_size, new_capacity))) return MZ_FALSE; + pArray->m_p = pNew_p; pArray->m_capacity = new_capacity; + return MZ_TRUE; +} + +static MZ_FORCEINLINE mz_bool mz_zip_array_reserve(mz_zip_archive *pZip, mz_zip_array *pArray, size_t new_capacity, mz_uint growing) +{ + if (new_capacity > pArray->m_capacity) { if (!mz_zip_array_ensure_capacity(pZip, pArray, new_capacity, growing)) return MZ_FALSE; } + return MZ_TRUE; +} + +static MZ_FORCEINLINE mz_bool mz_zip_array_resize(mz_zip_archive *pZip, mz_zip_array *pArray, size_t new_size, mz_uint growing) +{ + if (new_size > pArray->m_capacity) { if (!mz_zip_array_ensure_capacity(pZip, pArray, new_size, growing)) return MZ_FALSE; } + pArray->m_size = new_size; + return MZ_TRUE; +} + +static MZ_FORCEINLINE mz_bool mz_zip_array_ensure_room(mz_zip_archive *pZip, mz_zip_array *pArray, size_t n) +{ + return mz_zip_array_reserve(pZip, pArray, pArray->m_size + n, MZ_TRUE); +} + +static MZ_FORCEINLINE mz_bool mz_zip_array_push_back(mz_zip_archive *pZip, mz_zip_array *pArray, const void *pElements, size_t n) +{ + size_t orig_size = pArray->m_size; if (!mz_zip_array_resize(pZip, pArray, orig_size + n, MZ_TRUE)) return MZ_FALSE; + memcpy((mz_uint8*)pArray->m_p + orig_size * pArray->m_element_size, pElements, n * pArray->m_element_size); + return MZ_TRUE; +} + +#ifndef MINIZ_NO_TIME +static time_t mz_zip_dos_to_time_t(int dos_time, int dos_date) +{ + struct tm tm; + memset(&tm, 0, sizeof(tm)); tm.tm_isdst = -1; + tm.tm_year = ((dos_date >> 9) & 127) + 1980 - 1900; tm.tm_mon = ((dos_date >> 5) & 15) - 1; tm.tm_mday = dos_date & 31; + tm.tm_hour = (dos_time >> 11) & 31; tm.tm_min = (dos_time >> 5) & 63; tm.tm_sec = (dos_time << 1) & 62; + return mktime(&tm); +} + +static void mz_zip_time_to_dos_time(time_t time, mz_uint16 *pDOS_time, mz_uint16 *pDOS_date) +{ +#ifdef _MSC_VER + struct tm tm_struct; + struct tm *tm = &tm_struct; + errno_t err = localtime_s(tm, &time); + if (err) + { + *pDOS_date = 0; *pDOS_time = 0; + return; + } +#else + struct tm *tm = localtime(&time); +#endif + *pDOS_time = (mz_uint16)(((tm->tm_hour) << 11) + ((tm->tm_min) << 5) + ((tm->tm_sec) >> 1)); + *pDOS_date = (mz_uint16)(((tm->tm_year + 1900 - 1980) << 9) + ((tm->tm_mon + 1) << 5) + tm->tm_mday); +} +#endif + +#ifndef MINIZ_NO_STDIO +static mz_bool mz_zip_get_file_modified_time(const char *pFilename, mz_uint16 *pDOS_time, mz_uint16 *pDOS_date) +{ +#ifdef MINIZ_NO_TIME + (void)pFilename; *pDOS_date = *pDOS_time = 0; +#else + struct MZ_FILE_STAT_STRUCT file_stat; + // On Linux with x86 glibc, this call will fail on large files (>= 0x80000000 bytes) unless you compiled with _LARGEFILE64_SOURCE. Argh. + if (MZ_FILE_STAT(pFilename, &file_stat) != 0) + return MZ_FALSE; + mz_zip_time_to_dos_time(file_stat.st_mtime, pDOS_time, pDOS_date); +#endif // #ifdef MINIZ_NO_TIME + return MZ_TRUE; +} + +#ifndef MINIZ_NO_TIME +static mz_bool mz_zip_set_file_times(const char *pFilename, time_t access_time, time_t modified_time) +{ + struct utimbuf t; t.actime = access_time; t.modtime = modified_time; + return !utime(pFilename, &t); +} +#endif // #ifndef MINIZ_NO_TIME +#endif // #ifndef MINIZ_NO_STDIO + +static mz_bool mz_zip_reader_init_internal(mz_zip_archive *pZip, mz_uint32 flags) +{ + (void)flags; + if ((!pZip) || (pZip->m_pState) || (pZip->m_zip_mode != MZ_ZIP_MODE_INVALID)) + return MZ_FALSE; + + if (!pZip->m_pAlloc) pZip->m_pAlloc = def_alloc_func; + if (!pZip->m_pFree) pZip->m_pFree = def_free_func; + if (!pZip->m_pRealloc) pZip->m_pRealloc = def_realloc_func; + + pZip->m_zip_mode = MZ_ZIP_MODE_READING; + pZip->m_archive_size = 0; + pZip->m_central_directory_file_ofs = 0; + pZip->m_total_files = 0; + + if (NULL == (pZip->m_pState = (mz_zip_internal_state *)pZip->m_pAlloc(pZip->m_pAlloc_opaque, 1, sizeof(mz_zip_internal_state)))) + return MZ_FALSE; + memset(pZip->m_pState, 0, sizeof(mz_zip_internal_state)); + MZ_ZIP_ARRAY_SET_ELEMENT_SIZE(&pZip->m_pState->m_central_dir, sizeof(mz_uint8)); + MZ_ZIP_ARRAY_SET_ELEMENT_SIZE(&pZip->m_pState->m_central_dir_offsets, sizeof(mz_uint32)); + MZ_ZIP_ARRAY_SET_ELEMENT_SIZE(&pZip->m_pState->m_sorted_central_dir_offsets, sizeof(mz_uint32)); + return MZ_TRUE; +} + +static MZ_FORCEINLINE mz_bool mz_zip_reader_filename_less(const mz_zip_array *pCentral_dir_array, const mz_zip_array *pCentral_dir_offsets, mz_uint l_index, mz_uint r_index) +{ + const mz_uint8 *pL = &MZ_ZIP_ARRAY_ELEMENT(pCentral_dir_array, mz_uint8, MZ_ZIP_ARRAY_ELEMENT(pCentral_dir_offsets, mz_uint32, l_index)), *pE; + const mz_uint8 *pR = &MZ_ZIP_ARRAY_ELEMENT(pCentral_dir_array, mz_uint8, MZ_ZIP_ARRAY_ELEMENT(pCentral_dir_offsets, mz_uint32, r_index)); + mz_uint l_len = MZ_READ_LE16(pL + MZ_ZIP_CDH_FILENAME_LEN_OFS), r_len = MZ_READ_LE16(pR + MZ_ZIP_CDH_FILENAME_LEN_OFS); + mz_uint8 l = 0, r = 0; + pL += MZ_ZIP_CENTRAL_DIR_HEADER_SIZE; pR += MZ_ZIP_CENTRAL_DIR_HEADER_SIZE; + pE = pL + MZ_MIN(l_len, r_len); + while (pL < pE) + { + if ((l = MZ_TOLOWER(*pL)) != (r = MZ_TOLOWER(*pR))) + break; + pL++; pR++; + } + return (pL == pE) ? (l_len < r_len) : (l < r); +} + +#define MZ_SWAP_UINT32(a, b) do { mz_uint32 t = a; a = b; b = t; } MZ_MACRO_END + +// Heap sort of lowercased filenames, used to help accelerate plain central directory searches by mz_zip_reader_locate_file(). (Could also use qsort(), but it could allocate memory.) +static void mz_zip_reader_sort_central_dir_offsets_by_filename(mz_zip_archive *pZip) +{ + mz_zip_internal_state *pState = pZip->m_pState; + const mz_zip_array *pCentral_dir_offsets = &pState->m_central_dir_offsets; + const mz_zip_array *pCentral_dir = &pState->m_central_dir; + mz_uint32 *pIndices = &MZ_ZIP_ARRAY_ELEMENT(&pState->m_sorted_central_dir_offsets, mz_uint32, 0); + const int size = pZip->m_total_files; + int start = (size - 2) >> 1, end; + while (start >= 0) + { + int child, root = start; + for ( ; ; ) + { + if ((child = (root << 1) + 1) >= size) + break; + child += (((child + 1) < size) && (mz_zip_reader_filename_less(pCentral_dir, pCentral_dir_offsets, pIndices[child], pIndices[child + 1]))); + if (!mz_zip_reader_filename_less(pCentral_dir, pCentral_dir_offsets, pIndices[root], pIndices[child])) + break; + MZ_SWAP_UINT32(pIndices[root], pIndices[child]); root = child; + } + start--; + } + + end = size - 1; + while (end > 0) + { + int child, root = 0; + MZ_SWAP_UINT32(pIndices[end], pIndices[0]); + for ( ; ; ) + { + if ((child = (root << 1) + 1) >= end) + break; + child += (((child + 1) < end) && mz_zip_reader_filename_less(pCentral_dir, pCentral_dir_offsets, pIndices[child], pIndices[child + 1])); + if (!mz_zip_reader_filename_less(pCentral_dir, pCentral_dir_offsets, pIndices[root], pIndices[child])) + break; + MZ_SWAP_UINT32(pIndices[root], pIndices[child]); root = child; + } + end--; + } +} + +static mz_bool mz_zip_reader_read_central_dir(mz_zip_archive *pZip, mz_uint32 flags) +{ + mz_uint cdir_size, num_this_disk, cdir_disk_index; + mz_uint64 cdir_ofs; + mz_int64 cur_file_ofs; + const mz_uint8 *p; + mz_uint32 buf_u32[4096 / sizeof(mz_uint32)]; mz_uint8 *pBuf = (mz_uint8 *)buf_u32; + mz_bool sort_central_dir = ((flags & MZ_ZIP_FLAG_DO_NOT_SORT_CENTRAL_DIRECTORY) == 0); + // Basic sanity checks - reject files which are too small, and check the first 4 bytes of the file to make sure a local header is there. + if (pZip->m_archive_size < MZ_ZIP_END_OF_CENTRAL_DIR_HEADER_SIZE) + return MZ_FALSE; + // Find the end of central directory record by scanning the file from the end towards the beginning. + cur_file_ofs = MZ_MAX((mz_int64)pZip->m_archive_size - (mz_int64)sizeof(buf_u32), 0); + for ( ; ; ) + { + int i, n = (int)MZ_MIN(sizeof(buf_u32), pZip->m_archive_size - cur_file_ofs); + if (pZip->m_pRead(pZip->m_pIO_opaque, cur_file_ofs, pBuf, n) != (mz_uint)n) + return MZ_FALSE; + for (i = n - 4; i >= 0; --i) + if (MZ_READ_LE32(pBuf + i) == MZ_ZIP_END_OF_CENTRAL_DIR_HEADER_SIG) + break; + if (i >= 0) + { + cur_file_ofs += i; + break; + } + if ((!cur_file_ofs) || ((pZip->m_archive_size - cur_file_ofs) >= (0xFFFF + MZ_ZIP_END_OF_CENTRAL_DIR_HEADER_SIZE))) + return MZ_FALSE; + cur_file_ofs = MZ_MAX(cur_file_ofs - (sizeof(buf_u32) - 3), 0); + } + // Read and verify the end of central directory record. + if (pZip->m_pRead(pZip->m_pIO_opaque, cur_file_ofs, pBuf, MZ_ZIP_END_OF_CENTRAL_DIR_HEADER_SIZE) != MZ_ZIP_END_OF_CENTRAL_DIR_HEADER_SIZE) + return MZ_FALSE; + if ((MZ_READ_LE32(pBuf + MZ_ZIP_ECDH_SIG_OFS) != MZ_ZIP_END_OF_CENTRAL_DIR_HEADER_SIG) || + ((pZip->m_total_files = MZ_READ_LE16(pBuf + MZ_ZIP_ECDH_CDIR_TOTAL_ENTRIES_OFS)) != MZ_READ_LE16(pBuf + MZ_ZIP_ECDH_CDIR_NUM_ENTRIES_ON_DISK_OFS))) + return MZ_FALSE; + + num_this_disk = MZ_READ_LE16(pBuf + MZ_ZIP_ECDH_NUM_THIS_DISK_OFS); + cdir_disk_index = MZ_READ_LE16(pBuf + MZ_ZIP_ECDH_NUM_DISK_CDIR_OFS); + if (((num_this_disk | cdir_disk_index) != 0) && ((num_this_disk != 1) || (cdir_disk_index != 1))) + return MZ_FALSE; + + if ((cdir_size = MZ_READ_LE32(pBuf + MZ_ZIP_ECDH_CDIR_SIZE_OFS)) < pZip->m_total_files * MZ_ZIP_CENTRAL_DIR_HEADER_SIZE) + return MZ_FALSE; + + cdir_ofs = MZ_READ_LE32(pBuf + MZ_ZIP_ECDH_CDIR_OFS_OFS); + if ((cdir_ofs + (mz_uint64)cdir_size) > pZip->m_archive_size) + return MZ_FALSE; + + pZip->m_central_directory_file_ofs = cdir_ofs; + + if (pZip->m_total_files) + { + mz_uint i, n; + + // Read the entire central directory into a heap block, and allocate another heap block to hold the unsorted central dir file record offsets, and another to hold the sorted indices. + if ((!mz_zip_array_resize(pZip, &pZip->m_pState->m_central_dir, cdir_size, MZ_FALSE)) || + (!mz_zip_array_resize(pZip, &pZip->m_pState->m_central_dir_offsets, pZip->m_total_files, MZ_FALSE))) + return MZ_FALSE; + + if (sort_central_dir) + { + if (!mz_zip_array_resize(pZip, &pZip->m_pState->m_sorted_central_dir_offsets, pZip->m_total_files, MZ_FALSE)) + return MZ_FALSE; + } + + if (pZip->m_pRead(pZip->m_pIO_opaque, cdir_ofs, pZip->m_pState->m_central_dir.m_p, cdir_size) != cdir_size) + return MZ_FALSE; + + // Now create an index into the central directory file records, do some basic sanity checking on each record, and check for zip64 entries (which are not yet supported). + p = (const mz_uint8 *)pZip->m_pState->m_central_dir.m_p; + for (n = cdir_size, i = 0; i < pZip->m_total_files; ++i) + { + mz_uint total_header_size, comp_size, decomp_size, disk_index; + if ((n < MZ_ZIP_CENTRAL_DIR_HEADER_SIZE) || (MZ_READ_LE32(p) != MZ_ZIP_CENTRAL_DIR_HEADER_SIG)) + return MZ_FALSE; + MZ_ZIP_ARRAY_ELEMENT(&pZip->m_pState->m_central_dir_offsets, mz_uint32, i) = (mz_uint32)(p - (const mz_uint8 *)pZip->m_pState->m_central_dir.m_p); + if (sort_central_dir) + MZ_ZIP_ARRAY_ELEMENT(&pZip->m_pState->m_sorted_central_dir_offsets, mz_uint32, i) = i; + comp_size = MZ_READ_LE32(p + MZ_ZIP_CDH_COMPRESSED_SIZE_OFS); + decomp_size = MZ_READ_LE32(p + MZ_ZIP_CDH_DECOMPRESSED_SIZE_OFS); + if (((!MZ_READ_LE32(p + MZ_ZIP_CDH_METHOD_OFS)) && (decomp_size != comp_size)) || (decomp_size && !comp_size) || (decomp_size == 0xFFFFFFFF) || (comp_size == 0xFFFFFFFF)) + return MZ_FALSE; + disk_index = MZ_READ_LE16(p + MZ_ZIP_CDH_DISK_START_OFS); + if ((disk_index != num_this_disk) && (disk_index != 1)) + return MZ_FALSE; + if (((mz_uint64)MZ_READ_LE32(p + MZ_ZIP_CDH_LOCAL_HEADER_OFS) + MZ_ZIP_LOCAL_DIR_HEADER_SIZE + comp_size) > pZip->m_archive_size) + return MZ_FALSE; + if ((total_header_size = MZ_ZIP_CENTRAL_DIR_HEADER_SIZE + MZ_READ_LE16(p + MZ_ZIP_CDH_FILENAME_LEN_OFS) + MZ_READ_LE16(p + MZ_ZIP_CDH_EXTRA_LEN_OFS) + MZ_READ_LE16(p + MZ_ZIP_CDH_COMMENT_LEN_OFS)) > n) + return MZ_FALSE; + n -= total_header_size; p += total_header_size; + } + } + + if (sort_central_dir) + mz_zip_reader_sort_central_dir_offsets_by_filename(pZip); + + return MZ_TRUE; +} + +mz_bool mz_zip_reader_init(mz_zip_archive *pZip, mz_uint64 size, mz_uint32 flags) +{ + if ((!pZip) || (!pZip->m_pRead)) + return MZ_FALSE; + if (!mz_zip_reader_init_internal(pZip, flags)) + return MZ_FALSE; + pZip->m_archive_size = size; + if (!mz_zip_reader_read_central_dir(pZip, flags)) + { + mz_zip_reader_end(pZip); + return MZ_FALSE; + } + return MZ_TRUE; +} + +static size_t mz_zip_mem_read_func(void *pOpaque, mz_uint64 file_ofs, void *pBuf, size_t n) +{ + mz_zip_archive *pZip = (mz_zip_archive *)pOpaque; + size_t s = (file_ofs >= pZip->m_archive_size) ? 0 : (size_t)MZ_MIN(pZip->m_archive_size - file_ofs, n); + memcpy(pBuf, (const mz_uint8 *)pZip->m_pState->m_pMem + file_ofs, s); + return s; +} + +mz_bool mz_zip_reader_init_mem(mz_zip_archive *pZip, const void *pMem, size_t size, mz_uint32 flags) +{ + if (!mz_zip_reader_init_internal(pZip, flags)) + return MZ_FALSE; + pZip->m_archive_size = size; + pZip->m_pRead = mz_zip_mem_read_func; + pZip->m_pIO_opaque = pZip; +#ifdef __cplusplus + pZip->m_pState->m_pMem = const_cast(pMem); +#else + pZip->m_pState->m_pMem = (void *)pMem; +#endif + pZip->m_pState->m_mem_size = size; + if (!mz_zip_reader_read_central_dir(pZip, flags)) + { + mz_zip_reader_end(pZip); + return MZ_FALSE; + } + return MZ_TRUE; +} + +#ifndef MINIZ_NO_STDIO +static size_t mz_zip_file_read_func(void *pOpaque, mz_uint64 file_ofs, void *pBuf, size_t n) +{ + mz_zip_archive *pZip = (mz_zip_archive *)pOpaque; + mz_int64 cur_ofs = MZ_FTELL64(pZip->m_pState->m_pFile); + if (((mz_int64)file_ofs < 0) || (((cur_ofs != (mz_int64)file_ofs)) && (MZ_FSEEK64(pZip->m_pState->m_pFile, (mz_int64)file_ofs, SEEK_SET)))) + return 0; + return MZ_FREAD(pBuf, 1, n, pZip->m_pState->m_pFile); +} + +mz_bool mz_zip_reader_init_file(mz_zip_archive *pZip, const char *pFilename, mz_uint32 flags) +{ + mz_uint64 file_size; + MZ_FILE *pFile = MZ_FOPEN(pFilename, "rb"); + if (!pFile) + return MZ_FALSE; + if (MZ_FSEEK64(pFile, 0, SEEK_END)) + { + MZ_FCLOSE(pFile); + return MZ_FALSE; + } + file_size = MZ_FTELL64(pFile); + if (!mz_zip_reader_init_internal(pZip, flags)) + { + MZ_FCLOSE(pFile); + return MZ_FALSE; + } + pZip->m_pRead = mz_zip_file_read_func; + pZip->m_pIO_opaque = pZip; + pZip->m_pState->m_pFile = pFile; + pZip->m_archive_size = file_size; + if (!mz_zip_reader_read_central_dir(pZip, flags)) + { + mz_zip_reader_end(pZip); + return MZ_FALSE; + } + return MZ_TRUE; +} +#endif // #ifndef MINIZ_NO_STDIO + +mz_uint mz_zip_reader_get_num_files(mz_zip_archive *pZip) +{ + return pZip ? pZip->m_total_files : 0; +} + +static MZ_FORCEINLINE const mz_uint8 *mz_zip_reader_get_cdh(mz_zip_archive *pZip, mz_uint file_index) +{ + if ((!pZip) || (!pZip->m_pState) || (file_index >= pZip->m_total_files) || (pZip->m_zip_mode != MZ_ZIP_MODE_READING)) + return NULL; + return &MZ_ZIP_ARRAY_ELEMENT(&pZip->m_pState->m_central_dir, mz_uint8, MZ_ZIP_ARRAY_ELEMENT(&pZip->m_pState->m_central_dir_offsets, mz_uint32, file_index)); +} + +mz_bool mz_zip_reader_is_file_encrypted(mz_zip_archive *pZip, mz_uint file_index) +{ + mz_uint m_bit_flag; + const mz_uint8 *p = mz_zip_reader_get_cdh(pZip, file_index); + if (!p) + return MZ_FALSE; + m_bit_flag = MZ_READ_LE16(p + MZ_ZIP_CDH_BIT_FLAG_OFS); + return (m_bit_flag & 1); +} + +mz_bool mz_zip_reader_is_file_a_directory(mz_zip_archive *pZip, mz_uint file_index) +{ + mz_uint filename_len, external_attr; + const mz_uint8 *p = mz_zip_reader_get_cdh(pZip, file_index); + if (!p) + return MZ_FALSE; + + // First see if the filename ends with a '/' character. + filename_len = MZ_READ_LE16(p + MZ_ZIP_CDH_FILENAME_LEN_OFS); + if (filename_len) + { + if (*(p + MZ_ZIP_CENTRAL_DIR_HEADER_SIZE + filename_len - 1) == '/') + return MZ_TRUE; + } + + // Bugfix: This code was also checking if the internal attribute was non-zero, which wasn't correct. + // Most/all zip writers (hopefully) set DOS file/directory attributes in the low 16-bits, so check for the DOS directory flag and ignore the source OS ID in the created by field. + // FIXME: Remove this check? Is it necessary - we already check the filename. + external_attr = MZ_READ_LE32(p + MZ_ZIP_CDH_EXTERNAL_ATTR_OFS); + if ((external_attr & 0x10) != 0) + return MZ_TRUE; + + return MZ_FALSE; +} + +mz_bool mz_zip_reader_file_stat(mz_zip_archive *pZip, mz_uint file_index, mz_zip_archive_file_stat *pStat) +{ + mz_uint n; + const mz_uint8 *p = mz_zip_reader_get_cdh(pZip, file_index); + if ((!p) || (!pStat)) + return MZ_FALSE; + + // Unpack the central directory record. + pStat->m_file_index = file_index; + pStat->m_central_dir_ofs = MZ_ZIP_ARRAY_ELEMENT(&pZip->m_pState->m_central_dir_offsets, mz_uint32, file_index); + pStat->m_version_made_by = MZ_READ_LE16(p + MZ_ZIP_CDH_VERSION_MADE_BY_OFS); + pStat->m_version_needed = MZ_READ_LE16(p + MZ_ZIP_CDH_VERSION_NEEDED_OFS); + pStat->m_bit_flag = MZ_READ_LE16(p + MZ_ZIP_CDH_BIT_FLAG_OFS); + pStat->m_method = MZ_READ_LE16(p + MZ_ZIP_CDH_METHOD_OFS); +#ifndef MINIZ_NO_TIME + pStat->m_time = mz_zip_dos_to_time_t(MZ_READ_LE16(p + MZ_ZIP_CDH_FILE_TIME_OFS), MZ_READ_LE16(p + MZ_ZIP_CDH_FILE_DATE_OFS)); +#endif + pStat->m_crc32 = MZ_READ_LE32(p + MZ_ZIP_CDH_CRC32_OFS); + pStat->m_comp_size = MZ_READ_LE32(p + MZ_ZIP_CDH_COMPRESSED_SIZE_OFS); + pStat->m_uncomp_size = MZ_READ_LE32(p + MZ_ZIP_CDH_DECOMPRESSED_SIZE_OFS); + pStat->m_internal_attr = MZ_READ_LE16(p + MZ_ZIP_CDH_INTERNAL_ATTR_OFS); + pStat->m_external_attr = MZ_READ_LE32(p + MZ_ZIP_CDH_EXTERNAL_ATTR_OFS); + pStat->m_local_header_ofs = MZ_READ_LE32(p + MZ_ZIP_CDH_LOCAL_HEADER_OFS); + + // Copy as much of the filename and comment as possible. + n = MZ_READ_LE16(p + MZ_ZIP_CDH_FILENAME_LEN_OFS); n = MZ_MIN(n, MZ_ZIP_MAX_ARCHIVE_FILENAME_SIZE - 1); + memcpy(pStat->m_filename, p + MZ_ZIP_CENTRAL_DIR_HEADER_SIZE, n); pStat->m_filename[n] = '\0'; + + n = MZ_READ_LE16(p + MZ_ZIP_CDH_COMMENT_LEN_OFS); n = MZ_MIN(n, MZ_ZIP_MAX_ARCHIVE_FILE_COMMENT_SIZE - 1); + pStat->m_comment_size = n; + memcpy(pStat->m_comment, p + MZ_ZIP_CENTRAL_DIR_HEADER_SIZE + MZ_READ_LE16(p + MZ_ZIP_CDH_FILENAME_LEN_OFS) + MZ_READ_LE16(p + MZ_ZIP_CDH_EXTRA_LEN_OFS), n); pStat->m_comment[n] = '\0'; + + return MZ_TRUE; +} + +mz_uint mz_zip_reader_get_filename(mz_zip_archive *pZip, mz_uint file_index, char *pFilename, mz_uint filename_buf_size) +{ + mz_uint n; + const mz_uint8 *p = mz_zip_reader_get_cdh(pZip, file_index); + if (!p) { if (filename_buf_size) pFilename[0] = '\0'; return 0; } + n = MZ_READ_LE16(p + MZ_ZIP_CDH_FILENAME_LEN_OFS); + if (filename_buf_size) + { + n = MZ_MIN(n, filename_buf_size - 1); + memcpy(pFilename, p + MZ_ZIP_CENTRAL_DIR_HEADER_SIZE, n); + pFilename[n] = '\0'; + } + return n + 1; +} + +static MZ_FORCEINLINE mz_bool mz_zip_reader_string_equal(const char *pA, const char *pB, mz_uint len, mz_uint flags) +{ + mz_uint i; + if (flags & MZ_ZIP_FLAG_CASE_SENSITIVE) + return 0 == memcmp(pA, pB, len); + for (i = 0; i < len; ++i) + if (MZ_TOLOWER(pA[i]) != MZ_TOLOWER(pB[i])) + return MZ_FALSE; + return MZ_TRUE; +} + +static MZ_FORCEINLINE int mz_zip_reader_filename_compare(const mz_zip_array *pCentral_dir_array, const mz_zip_array *pCentral_dir_offsets, mz_uint l_index, const char *pR, mz_uint r_len) +{ + const mz_uint8 *pL = &MZ_ZIP_ARRAY_ELEMENT(pCentral_dir_array, mz_uint8, MZ_ZIP_ARRAY_ELEMENT(pCentral_dir_offsets, mz_uint32, l_index)), *pE; + mz_uint l_len = MZ_READ_LE16(pL + MZ_ZIP_CDH_FILENAME_LEN_OFS); + mz_uint8 l = 0, r = 0; + pL += MZ_ZIP_CENTRAL_DIR_HEADER_SIZE; + pE = pL + MZ_MIN(l_len, r_len); + while (pL < pE) + { + if ((l = MZ_TOLOWER(*pL)) != (r = MZ_TOLOWER(*pR))) + break; + pL++; pR++; + } + return (pL == pE) ? (int)(l_len - r_len) : (l - r); +} + +static int mz_zip_reader_locate_file_binary_search(mz_zip_archive *pZip, const char *pFilename) +{ + mz_zip_internal_state *pState = pZip->m_pState; + const mz_zip_array *pCentral_dir_offsets = &pState->m_central_dir_offsets; + const mz_zip_array *pCentral_dir = &pState->m_central_dir; + mz_uint32 *pIndices = &MZ_ZIP_ARRAY_ELEMENT(&pState->m_sorted_central_dir_offsets, mz_uint32, 0); + const int size = pZip->m_total_files; + const mz_uint filename_len = (mz_uint)strlen(pFilename); + int l = 0, h = size - 1; + while (l <= h) + { + int m = (l + h) >> 1, file_index = pIndices[m], comp = mz_zip_reader_filename_compare(pCentral_dir, pCentral_dir_offsets, file_index, pFilename, filename_len); + if (!comp) + return file_index; + else if (comp < 0) + l = m + 1; + else + h = m - 1; + } + return -1; +} + +int mz_zip_reader_locate_file(mz_zip_archive *pZip, const char *pName, const char *pComment, mz_uint flags) +{ + mz_uint file_index; size_t name_len, comment_len; + if ((!pZip) || (!pZip->m_pState) || (!pName) || (pZip->m_zip_mode != MZ_ZIP_MODE_READING)) + return -1; + if (((flags & (MZ_ZIP_FLAG_IGNORE_PATH | MZ_ZIP_FLAG_CASE_SENSITIVE)) == 0) && (!pComment) && (pZip->m_pState->m_sorted_central_dir_offsets.m_size)) + return mz_zip_reader_locate_file_binary_search(pZip, pName); + name_len = strlen(pName); if (name_len > 0xFFFF) return -1; + comment_len = pComment ? strlen(pComment) : 0; if (comment_len > 0xFFFF) return -1; + for (file_index = 0; file_index < pZip->m_total_files; file_index++) + { + const mz_uint8 *pHeader = &MZ_ZIP_ARRAY_ELEMENT(&pZip->m_pState->m_central_dir, mz_uint8, MZ_ZIP_ARRAY_ELEMENT(&pZip->m_pState->m_central_dir_offsets, mz_uint32, file_index)); + mz_uint filename_len = MZ_READ_LE16(pHeader + MZ_ZIP_CDH_FILENAME_LEN_OFS); + const char *pFilename = (const char *)pHeader + MZ_ZIP_CENTRAL_DIR_HEADER_SIZE; + if (filename_len < name_len) + continue; + if (comment_len) + { + mz_uint file_extra_len = MZ_READ_LE16(pHeader + MZ_ZIP_CDH_EXTRA_LEN_OFS), file_comment_len = MZ_READ_LE16(pHeader + MZ_ZIP_CDH_COMMENT_LEN_OFS); + const char *pFile_comment = pFilename + filename_len + file_extra_len; + if ((file_comment_len != comment_len) || (!mz_zip_reader_string_equal(pComment, pFile_comment, file_comment_len, flags))) + continue; + } + if ((flags & MZ_ZIP_FLAG_IGNORE_PATH) && (filename_len)) + { + int ofs = filename_len - 1; + do + { + if ((pFilename[ofs] == '/') || (pFilename[ofs] == '\\') || (pFilename[ofs] == ':')) + break; + } while (--ofs >= 0); + ofs++; + pFilename += ofs; filename_len -= ofs; + } + if ((filename_len == name_len) && (mz_zip_reader_string_equal(pName, pFilename, filename_len, flags))) + return file_index; + } + return -1; +} + +mz_bool mz_zip_reader_extract_to_mem_no_alloc(mz_zip_archive *pZip, mz_uint file_index, void *pBuf, size_t buf_size, mz_uint flags, void *pUser_read_buf, size_t user_read_buf_size) +{ + int status = TINFL_STATUS_DONE; + mz_uint64 needed_size, cur_file_ofs, comp_remaining, out_buf_ofs = 0, read_buf_size, read_buf_ofs = 0, read_buf_avail; + mz_zip_archive_file_stat file_stat; + void *pRead_buf; + mz_uint32 local_header_u32[(MZ_ZIP_LOCAL_DIR_HEADER_SIZE + sizeof(mz_uint32) - 1) / sizeof(mz_uint32)]; mz_uint8 *pLocal_header = (mz_uint8 *)local_header_u32; + tinfl_decompressor inflator; + + if ((buf_size) && (!pBuf)) + return MZ_FALSE; + + if (!mz_zip_reader_file_stat(pZip, file_index, &file_stat)) + return MZ_FALSE; + + // Empty file, or a directory (but not always a directory - I've seen odd zips with directories that have compressed data which inflates to 0 bytes) + if (!file_stat.m_comp_size) + return MZ_TRUE; + + // Entry is a subdirectory (I've seen old zips with dir entries which have compressed deflate data which inflates to 0 bytes, but these entries claim to uncompress to 512 bytes in the headers). + // I'm torn how to handle this case - should it fail instead? + if (mz_zip_reader_is_file_a_directory(pZip, file_index)) + return MZ_TRUE; + + // Encryption and patch files are not supported. + if (file_stat.m_bit_flag & (1 | 32)) + return MZ_FALSE; + + // This function only supports stored and deflate. + if ((!(flags & MZ_ZIP_FLAG_COMPRESSED_DATA)) && (file_stat.m_method != 0) && (file_stat.m_method != MZ_DEFLATED)) + return MZ_FALSE; + + // Ensure supplied output buffer is large enough. + needed_size = (flags & MZ_ZIP_FLAG_COMPRESSED_DATA) ? file_stat.m_comp_size : file_stat.m_uncomp_size; + if (buf_size < needed_size) + return MZ_FALSE; + + // Read and parse the local directory entry. + cur_file_ofs = file_stat.m_local_header_ofs; + if (pZip->m_pRead(pZip->m_pIO_opaque, cur_file_ofs, pLocal_header, MZ_ZIP_LOCAL_DIR_HEADER_SIZE) != MZ_ZIP_LOCAL_DIR_HEADER_SIZE) + return MZ_FALSE; + if (MZ_READ_LE32(pLocal_header) != MZ_ZIP_LOCAL_DIR_HEADER_SIG) + return MZ_FALSE; + + cur_file_ofs += MZ_ZIP_LOCAL_DIR_HEADER_SIZE + MZ_READ_LE16(pLocal_header + MZ_ZIP_LDH_FILENAME_LEN_OFS) + MZ_READ_LE16(pLocal_header + MZ_ZIP_LDH_EXTRA_LEN_OFS); + if ((cur_file_ofs + file_stat.m_comp_size) > pZip->m_archive_size) + return MZ_FALSE; + + if ((flags & MZ_ZIP_FLAG_COMPRESSED_DATA) || (!file_stat.m_method)) + { + // The file is stored or the caller has requested the compressed data. + if (pZip->m_pRead(pZip->m_pIO_opaque, cur_file_ofs, pBuf, (size_t)needed_size) != needed_size) + return MZ_FALSE; + return ((flags & MZ_ZIP_FLAG_COMPRESSED_DATA) != 0) || (mz_crc32(MZ_CRC32_INIT, (const mz_uint8 *)pBuf, (size_t)file_stat.m_uncomp_size) == file_stat.m_crc32); + } + + // Decompress the file either directly from memory or from a file input buffer. + tinfl_init(&inflator); + + if (pZip->m_pState->m_pMem) + { + // Read directly from the archive in memory. + pRead_buf = (mz_uint8 *)pZip->m_pState->m_pMem + cur_file_ofs; + read_buf_size = read_buf_avail = file_stat.m_comp_size; + comp_remaining = 0; + } + else if (pUser_read_buf) + { + // Use a user provided read buffer. + if (!user_read_buf_size) + return MZ_FALSE; + pRead_buf = (mz_uint8 *)pUser_read_buf; + read_buf_size = user_read_buf_size; + read_buf_avail = 0; + comp_remaining = file_stat.m_comp_size; + } + else + { + // Temporarily allocate a read buffer. + read_buf_size = MZ_MIN(file_stat.m_comp_size, MZ_ZIP_MAX_IO_BUF_SIZE); +#ifdef _MSC_VER + if (((0, sizeof(size_t) == sizeof(mz_uint32))) && (read_buf_size > 0x7FFFFFFF)) +#else + if (((sizeof(size_t) == sizeof(mz_uint32))) && (read_buf_size > 0x7FFFFFFF)) +#endif + return MZ_FALSE; + if (NULL == (pRead_buf = pZip->m_pAlloc(pZip->m_pAlloc_opaque, 1, (size_t)read_buf_size))) + return MZ_FALSE; + read_buf_avail = 0; + comp_remaining = file_stat.m_comp_size; + } + + do + { + size_t in_buf_size, out_buf_size = (size_t)(file_stat.m_uncomp_size - out_buf_ofs); + if ((!read_buf_avail) && (!pZip->m_pState->m_pMem)) + { + read_buf_avail = MZ_MIN(read_buf_size, comp_remaining); + if (pZip->m_pRead(pZip->m_pIO_opaque, cur_file_ofs, pRead_buf, (size_t)read_buf_avail) != read_buf_avail) + { + status = TINFL_STATUS_FAILED; + break; + } + cur_file_ofs += read_buf_avail; + comp_remaining -= read_buf_avail; + read_buf_ofs = 0; + } + in_buf_size = (size_t)read_buf_avail; + status = tinfl_decompress(&inflator, (mz_uint8 *)pRead_buf + read_buf_ofs, &in_buf_size, (mz_uint8 *)pBuf, (mz_uint8 *)pBuf + out_buf_ofs, &out_buf_size, TINFL_FLAG_USING_NON_WRAPPING_OUTPUT_BUF | (comp_remaining ? TINFL_FLAG_HAS_MORE_INPUT : 0)); + read_buf_avail -= in_buf_size; + read_buf_ofs += in_buf_size; + out_buf_ofs += out_buf_size; + } while (status == TINFL_STATUS_NEEDS_MORE_INPUT); + + if (status == TINFL_STATUS_DONE) + { + // Make sure the entire file was decompressed, and check its CRC. + if ((out_buf_ofs != file_stat.m_uncomp_size) || (mz_crc32(MZ_CRC32_INIT, (const mz_uint8 *)pBuf, (size_t)file_stat.m_uncomp_size) != file_stat.m_crc32)) + status = TINFL_STATUS_FAILED; + } + + if ((!pZip->m_pState->m_pMem) && (!pUser_read_buf)) + pZip->m_pFree(pZip->m_pAlloc_opaque, pRead_buf); + + return status == TINFL_STATUS_DONE; +} + +mz_bool mz_zip_reader_extract_file_to_mem_no_alloc(mz_zip_archive *pZip, const char *pFilename, void *pBuf, size_t buf_size, mz_uint flags, void *pUser_read_buf, size_t user_read_buf_size) +{ + int file_index = mz_zip_reader_locate_file(pZip, pFilename, NULL, flags); + if (file_index < 0) + return MZ_FALSE; + return mz_zip_reader_extract_to_mem_no_alloc(pZip, file_index, pBuf, buf_size, flags, pUser_read_buf, user_read_buf_size); +} + +mz_bool mz_zip_reader_extract_to_mem(mz_zip_archive *pZip, mz_uint file_index, void *pBuf, size_t buf_size, mz_uint flags) +{ + return mz_zip_reader_extract_to_mem_no_alloc(pZip, file_index, pBuf, buf_size, flags, NULL, 0); +} + +mz_bool mz_zip_reader_extract_file_to_mem(mz_zip_archive *pZip, const char *pFilename, void *pBuf, size_t buf_size, mz_uint flags) +{ + return mz_zip_reader_extract_file_to_mem_no_alloc(pZip, pFilename, pBuf, buf_size, flags, NULL, 0); +} + +void *mz_zip_reader_extract_to_heap(mz_zip_archive *pZip, mz_uint file_index, size_t *pSize, mz_uint flags) +{ + mz_uint64 comp_size, uncomp_size, alloc_size; + const mz_uint8 *p = mz_zip_reader_get_cdh(pZip, file_index); + void *pBuf; + + if (pSize) + *pSize = 0; + if (!p) + return NULL; + + comp_size = MZ_READ_LE32(p + MZ_ZIP_CDH_COMPRESSED_SIZE_OFS); + uncomp_size = MZ_READ_LE32(p + MZ_ZIP_CDH_DECOMPRESSED_SIZE_OFS); + + alloc_size = (flags & MZ_ZIP_FLAG_COMPRESSED_DATA) ? comp_size : uncomp_size; +#ifdef _MSC_VER + if (((0, sizeof(size_t) == sizeof(mz_uint32))) && (alloc_size > 0x7FFFFFFF)) +#else + if (((sizeof(size_t) == sizeof(mz_uint32))) && (alloc_size > 0x7FFFFFFF)) +#endif + return NULL; + if (NULL == (pBuf = pZip->m_pAlloc(pZip->m_pAlloc_opaque, 1, (size_t)alloc_size))) + return NULL; + + if (!mz_zip_reader_extract_to_mem(pZip, file_index, pBuf, (size_t)alloc_size, flags)) + { + pZip->m_pFree(pZip->m_pAlloc_opaque, pBuf); + return NULL; + } + + if (pSize) *pSize = (size_t)alloc_size; + return pBuf; +} + +void *mz_zip_reader_extract_file_to_heap(mz_zip_archive *pZip, const char *pFilename, size_t *pSize, mz_uint flags) +{ + int file_index = mz_zip_reader_locate_file(pZip, pFilename, NULL, flags); + if (file_index < 0) + { + if (pSize) *pSize = 0; + return MZ_FALSE; + } + return mz_zip_reader_extract_to_heap(pZip, file_index, pSize, flags); +} + +mz_bool mz_zip_reader_extract_to_callback(mz_zip_archive *pZip, mz_uint file_index, mz_file_write_func pCallback, void *pOpaque, mz_uint flags) +{ + int status = TINFL_STATUS_DONE; mz_uint file_crc32 = MZ_CRC32_INIT; + mz_uint64 read_buf_size, read_buf_ofs = 0, read_buf_avail, comp_remaining, out_buf_ofs = 0, cur_file_ofs; + mz_zip_archive_file_stat file_stat; + void *pRead_buf = NULL; void *pWrite_buf = NULL; + mz_uint32 local_header_u32[(MZ_ZIP_LOCAL_DIR_HEADER_SIZE + sizeof(mz_uint32) - 1) / sizeof(mz_uint32)]; mz_uint8 *pLocal_header = (mz_uint8 *)local_header_u32; + + if (!mz_zip_reader_file_stat(pZip, file_index, &file_stat)) + return MZ_FALSE; + + // Empty file, or a directory (but not always a directory - I've seen odd zips with directories that have compressed data which inflates to 0 bytes) + if (!file_stat.m_comp_size) + return MZ_TRUE; + + // Entry is a subdirectory (I've seen old zips with dir entries which have compressed deflate data which inflates to 0 bytes, but these entries claim to uncompress to 512 bytes in the headers). + // I'm torn how to handle this case - should it fail instead? + if (mz_zip_reader_is_file_a_directory(pZip, file_index)) + return MZ_TRUE; + + // Encryption and patch files are not supported. + if (file_stat.m_bit_flag & (1 | 32)) + return MZ_FALSE; + + // This function only supports stored and deflate. + if ((!(flags & MZ_ZIP_FLAG_COMPRESSED_DATA)) && (file_stat.m_method != 0) && (file_stat.m_method != MZ_DEFLATED)) + return MZ_FALSE; + + // Read and parse the local directory entry. + cur_file_ofs = file_stat.m_local_header_ofs; + if (pZip->m_pRead(pZip->m_pIO_opaque, cur_file_ofs, pLocal_header, MZ_ZIP_LOCAL_DIR_HEADER_SIZE) != MZ_ZIP_LOCAL_DIR_HEADER_SIZE) + return MZ_FALSE; + if (MZ_READ_LE32(pLocal_header) != MZ_ZIP_LOCAL_DIR_HEADER_SIG) + return MZ_FALSE; + + cur_file_ofs += MZ_ZIP_LOCAL_DIR_HEADER_SIZE + MZ_READ_LE16(pLocal_header + MZ_ZIP_LDH_FILENAME_LEN_OFS) + MZ_READ_LE16(pLocal_header + MZ_ZIP_LDH_EXTRA_LEN_OFS); + if ((cur_file_ofs + file_stat.m_comp_size) > pZip->m_archive_size) + return MZ_FALSE; + + // Decompress the file either directly from memory or from a file input buffer. + if (pZip->m_pState->m_pMem) + { + pRead_buf = (mz_uint8 *)pZip->m_pState->m_pMem + cur_file_ofs; + read_buf_size = read_buf_avail = file_stat.m_comp_size; + comp_remaining = 0; + } + else + { + read_buf_size = MZ_MIN(file_stat.m_comp_size, MZ_ZIP_MAX_IO_BUF_SIZE); + if (NULL == (pRead_buf = pZip->m_pAlloc(pZip->m_pAlloc_opaque, 1, (size_t)read_buf_size))) + return MZ_FALSE; + read_buf_avail = 0; + comp_remaining = file_stat.m_comp_size; + } + + if ((flags & MZ_ZIP_FLAG_COMPRESSED_DATA) || (!file_stat.m_method)) + { + // The file is stored or the caller has requested the compressed data. + if (pZip->m_pState->m_pMem) + { +#ifdef _MSC_VER + if (((0, sizeof(size_t) == sizeof(mz_uint32))) && (file_stat.m_comp_size > 0xFFFFFFFF)) +#else + if (((sizeof(size_t) == sizeof(mz_uint32))) && (file_stat.m_comp_size > 0xFFFFFFFF)) +#endif + return MZ_FALSE; + if (pCallback(pOpaque, out_buf_ofs, pRead_buf, (size_t)file_stat.m_comp_size) != file_stat.m_comp_size) + status = TINFL_STATUS_FAILED; + else if (!(flags & MZ_ZIP_FLAG_COMPRESSED_DATA)) + file_crc32 = (mz_uint32)mz_crc32(file_crc32, (const mz_uint8 *)pRead_buf, (size_t)file_stat.m_comp_size); + cur_file_ofs += file_stat.m_comp_size; + out_buf_ofs += file_stat.m_comp_size; + comp_remaining = 0; + } + else + { + while (comp_remaining) + { + read_buf_avail = MZ_MIN(read_buf_size, comp_remaining); + if (pZip->m_pRead(pZip->m_pIO_opaque, cur_file_ofs, pRead_buf, (size_t)read_buf_avail) != read_buf_avail) + { + status = TINFL_STATUS_FAILED; + break; + } + + if (!(flags & MZ_ZIP_FLAG_COMPRESSED_DATA)) + file_crc32 = (mz_uint32)mz_crc32(file_crc32, (const mz_uint8 *)pRead_buf, (size_t)read_buf_avail); + + if (pCallback(pOpaque, out_buf_ofs, pRead_buf, (size_t)read_buf_avail) != read_buf_avail) + { + status = TINFL_STATUS_FAILED; + break; + } + cur_file_ofs += read_buf_avail; + out_buf_ofs += read_buf_avail; + comp_remaining -= read_buf_avail; + } + } + } + else + { + tinfl_decompressor inflator; + tinfl_init(&inflator); + + if (NULL == (pWrite_buf = pZip->m_pAlloc(pZip->m_pAlloc_opaque, 1, TINFL_LZ_DICT_SIZE))) + status = TINFL_STATUS_FAILED; + else + { + do + { + mz_uint8 *pWrite_buf_cur = (mz_uint8 *)pWrite_buf + (out_buf_ofs & (TINFL_LZ_DICT_SIZE - 1)); + size_t in_buf_size, out_buf_size = TINFL_LZ_DICT_SIZE - (out_buf_ofs & (TINFL_LZ_DICT_SIZE - 1)); + if ((!read_buf_avail) && (!pZip->m_pState->m_pMem)) + { + read_buf_avail = MZ_MIN(read_buf_size, comp_remaining); + if (pZip->m_pRead(pZip->m_pIO_opaque, cur_file_ofs, pRead_buf, (size_t)read_buf_avail) != read_buf_avail) + { + status = TINFL_STATUS_FAILED; + break; + } + cur_file_ofs += read_buf_avail; + comp_remaining -= read_buf_avail; + read_buf_ofs = 0; + } + + in_buf_size = (size_t)read_buf_avail; + status = tinfl_decompress(&inflator, (const mz_uint8 *)pRead_buf + read_buf_ofs, &in_buf_size, (mz_uint8 *)pWrite_buf, pWrite_buf_cur, &out_buf_size, comp_remaining ? TINFL_FLAG_HAS_MORE_INPUT : 0); + read_buf_avail -= in_buf_size; + read_buf_ofs += in_buf_size; + + if (out_buf_size) + { + if (pCallback(pOpaque, out_buf_ofs, pWrite_buf_cur, out_buf_size) != out_buf_size) + { + status = TINFL_STATUS_FAILED; + break; + } + file_crc32 = (mz_uint32)mz_crc32(file_crc32, pWrite_buf_cur, out_buf_size); + if ((out_buf_ofs += out_buf_size) > file_stat.m_uncomp_size) + { + status = TINFL_STATUS_FAILED; + break; + } + } + } while ((status == TINFL_STATUS_NEEDS_MORE_INPUT) || (status == TINFL_STATUS_HAS_MORE_OUTPUT)); + } + } + + if ((status == TINFL_STATUS_DONE) && (!(flags & MZ_ZIP_FLAG_COMPRESSED_DATA))) + { + // Make sure the entire file was decompressed, and check its CRC. + if ((out_buf_ofs != file_stat.m_uncomp_size) || (file_crc32 != file_stat.m_crc32)) + status = TINFL_STATUS_FAILED; + } + + if (!pZip->m_pState->m_pMem) + pZip->m_pFree(pZip->m_pAlloc_opaque, pRead_buf); + if (pWrite_buf) + pZip->m_pFree(pZip->m_pAlloc_opaque, pWrite_buf); + + return status == TINFL_STATUS_DONE; +} + +mz_bool mz_zip_reader_extract_file_to_callback(mz_zip_archive *pZip, const char *pFilename, mz_file_write_func pCallback, void *pOpaque, mz_uint flags) +{ + int file_index = mz_zip_reader_locate_file(pZip, pFilename, NULL, flags); + if (file_index < 0) + return MZ_FALSE; + return mz_zip_reader_extract_to_callback(pZip, file_index, pCallback, pOpaque, flags); +} + +#ifndef MINIZ_NO_STDIO +static size_t mz_zip_file_write_callback(void *pOpaque, mz_uint64 ofs, const void *pBuf, size_t n) +{ + (void)ofs; return MZ_FWRITE(pBuf, 1, n, (MZ_FILE*)pOpaque); +} + +mz_bool mz_zip_reader_extract_to_file(mz_zip_archive *pZip, mz_uint file_index, const char *pDst_filename, mz_uint flags) +{ + mz_bool status; + mz_zip_archive_file_stat file_stat; + MZ_FILE *pFile; + if (!mz_zip_reader_file_stat(pZip, file_index, &file_stat)) + return MZ_FALSE; + pFile = MZ_FOPEN(pDst_filename, "wb"); + if (!pFile) + return MZ_FALSE; + status = mz_zip_reader_extract_to_callback(pZip, file_index, mz_zip_file_write_callback, pFile, flags); + if (MZ_FCLOSE(pFile) == EOF) + return MZ_FALSE; +#ifndef MINIZ_NO_TIME + if (status) + mz_zip_set_file_times(pDst_filename, file_stat.m_time, file_stat.m_time); +#endif + return status; +} +#endif // #ifndef MINIZ_NO_STDIO + +mz_bool mz_zip_reader_end(mz_zip_archive *pZip) +{ + if ((!pZip) || (!pZip->m_pState) || (!pZip->m_pAlloc) || (!pZip->m_pFree) || (pZip->m_zip_mode != MZ_ZIP_MODE_READING)) + return MZ_FALSE; + + if (pZip->m_pState) + { + mz_zip_internal_state *pState = pZip->m_pState; pZip->m_pState = NULL; + mz_zip_array_clear(pZip, &pState->m_central_dir); + mz_zip_array_clear(pZip, &pState->m_central_dir_offsets); + mz_zip_array_clear(pZip, &pState->m_sorted_central_dir_offsets); + +#ifndef MINIZ_NO_STDIO + if (pState->m_pFile) + { + MZ_FCLOSE(pState->m_pFile); + pState->m_pFile = NULL; + } +#endif // #ifndef MINIZ_NO_STDIO + + pZip->m_pFree(pZip->m_pAlloc_opaque, pState); + } + pZip->m_zip_mode = MZ_ZIP_MODE_INVALID; + + return MZ_TRUE; +} + +#ifndef MINIZ_NO_STDIO +mz_bool mz_zip_reader_extract_file_to_file(mz_zip_archive *pZip, const char *pArchive_filename, const char *pDst_filename, mz_uint flags) +{ + int file_index = mz_zip_reader_locate_file(pZip, pArchive_filename, NULL, flags); + if (file_index < 0) + return MZ_FALSE; + return mz_zip_reader_extract_to_file(pZip, file_index, pDst_filename, flags); +} +#endif + +// ------------------- .ZIP archive writing + +#ifndef MINIZ_NO_ARCHIVE_WRITING_APIS + +static void mz_write_le16(mz_uint8 *p, mz_uint16 v) { p[0] = (mz_uint8)v; p[1] = (mz_uint8)(v >> 8); } +static void mz_write_le32(mz_uint8 *p, mz_uint32 v) { p[0] = (mz_uint8)v; p[1] = (mz_uint8)(v >> 8); p[2] = (mz_uint8)(v >> 16); p[3] = (mz_uint8)(v >> 24); } +#define MZ_WRITE_LE16(p, v) mz_write_le16((mz_uint8 *)(p), (mz_uint16)(v)) +#define MZ_WRITE_LE32(p, v) mz_write_le32((mz_uint8 *)(p), (mz_uint32)(v)) + +mz_bool mz_zip_writer_init(mz_zip_archive *pZip, mz_uint64 existing_size) +{ + if ((!pZip) || (pZip->m_pState) || (!pZip->m_pWrite) || (pZip->m_zip_mode != MZ_ZIP_MODE_INVALID)) + return MZ_FALSE; + + if (pZip->m_file_offset_alignment) + { + // Ensure user specified file offset alignment is a power of 2. + if (pZip->m_file_offset_alignment & (pZip->m_file_offset_alignment - 1)) + return MZ_FALSE; + } + + if (!pZip->m_pAlloc) pZip->m_pAlloc = def_alloc_func; + if (!pZip->m_pFree) pZip->m_pFree = def_free_func; + if (!pZip->m_pRealloc) pZip->m_pRealloc = def_realloc_func; + + pZip->m_zip_mode = MZ_ZIP_MODE_WRITING; + pZip->m_archive_size = existing_size; + pZip->m_central_directory_file_ofs = 0; + pZip->m_total_files = 0; + + if (NULL == (pZip->m_pState = (mz_zip_internal_state *)pZip->m_pAlloc(pZip->m_pAlloc_opaque, 1, sizeof(mz_zip_internal_state)))) + return MZ_FALSE; + memset(pZip->m_pState, 0, sizeof(mz_zip_internal_state)); + MZ_ZIP_ARRAY_SET_ELEMENT_SIZE(&pZip->m_pState->m_central_dir, sizeof(mz_uint8)); + MZ_ZIP_ARRAY_SET_ELEMENT_SIZE(&pZip->m_pState->m_central_dir_offsets, sizeof(mz_uint32)); + MZ_ZIP_ARRAY_SET_ELEMENT_SIZE(&pZip->m_pState->m_sorted_central_dir_offsets, sizeof(mz_uint32)); + return MZ_TRUE; +} + +static size_t mz_zip_heap_write_func(void *pOpaque, mz_uint64 file_ofs, const void *pBuf, size_t n) +{ + mz_zip_archive *pZip = (mz_zip_archive *)pOpaque; + mz_zip_internal_state *pState = pZip->m_pState; + mz_uint64 new_size = MZ_MAX(file_ofs + n, pState->m_mem_size); +#ifdef _MSC_VER + if ((!n) || ((0, sizeof(size_t) == sizeof(mz_uint32)) && (new_size > 0x7FFFFFFF))) +#else + if ((!n) || ((sizeof(size_t) == sizeof(mz_uint32)) && (new_size > 0x7FFFFFFF))) +#endif + return 0; + if (new_size > pState->m_mem_capacity) + { + void *pNew_block; + size_t new_capacity = MZ_MAX(64, pState->m_mem_capacity); while (new_capacity < new_size) new_capacity *= 2; + if (NULL == (pNew_block = pZip->m_pRealloc(pZip->m_pAlloc_opaque, pState->m_pMem, 1, new_capacity))) + return 0; + pState->m_pMem = pNew_block; pState->m_mem_capacity = new_capacity; + } + memcpy((mz_uint8 *)pState->m_pMem + file_ofs, pBuf, n); + pState->m_mem_size = (size_t)new_size; + return n; +} + +mz_bool mz_zip_writer_init_heap(mz_zip_archive *pZip, size_t size_to_reserve_at_beginning, size_t initial_allocation_size) +{ + pZip->m_pWrite = mz_zip_heap_write_func; + pZip->m_pIO_opaque = pZip; + if (!mz_zip_writer_init(pZip, size_to_reserve_at_beginning)) + return MZ_FALSE; + if (0 != (initial_allocation_size = MZ_MAX(initial_allocation_size, size_to_reserve_at_beginning))) + { + if (NULL == (pZip->m_pState->m_pMem = pZip->m_pAlloc(pZip->m_pAlloc_opaque, 1, initial_allocation_size))) + { + mz_zip_writer_end(pZip); + return MZ_FALSE; + } + pZip->m_pState->m_mem_capacity = initial_allocation_size; + } + return MZ_TRUE; +} + +#ifndef MINIZ_NO_STDIO +static size_t mz_zip_file_write_func(void *pOpaque, mz_uint64 file_ofs, const void *pBuf, size_t n) +{ + mz_zip_archive *pZip = (mz_zip_archive *)pOpaque; + mz_int64 cur_ofs = MZ_FTELL64(pZip->m_pState->m_pFile); + if (((mz_int64)file_ofs < 0) || (((cur_ofs != (mz_int64)file_ofs)) && (MZ_FSEEK64(pZip->m_pState->m_pFile, (mz_int64)file_ofs, SEEK_SET)))) + return 0; + return MZ_FWRITE(pBuf, 1, n, pZip->m_pState->m_pFile); +} + +mz_bool mz_zip_writer_init_file(mz_zip_archive *pZip, const char *pFilename, mz_uint64 size_to_reserve_at_beginning) +{ + MZ_FILE *pFile; + pZip->m_pWrite = mz_zip_file_write_func; + pZip->m_pIO_opaque = pZip; + if (!mz_zip_writer_init(pZip, size_to_reserve_at_beginning)) + return MZ_FALSE; + if (NULL == (pFile = MZ_FOPEN(pFilename, "wb"))) + { + mz_zip_writer_end(pZip); + return MZ_FALSE; + } + pZip->m_pState->m_pFile = pFile; + if (size_to_reserve_at_beginning) + { + mz_uint64 cur_ofs = 0; char buf[4096]; MZ_CLEAR_OBJ(buf); + do + { + size_t n = (size_t)MZ_MIN(sizeof(buf), size_to_reserve_at_beginning); + if (pZip->m_pWrite(pZip->m_pIO_opaque, cur_ofs, buf, n) != n) + { + mz_zip_writer_end(pZip); + return MZ_FALSE; + } + cur_ofs += n; size_to_reserve_at_beginning -= n; + } while (size_to_reserve_at_beginning); + } + return MZ_TRUE; +} +#endif // #ifndef MINIZ_NO_STDIO + +mz_bool mz_zip_writer_init_from_reader(mz_zip_archive *pZip, const char *pFilename) +{ + mz_zip_internal_state *pState; + if ((!pZip) || (!pZip->m_pState) || (pZip->m_zip_mode != MZ_ZIP_MODE_READING)) + return MZ_FALSE; + // No sense in trying to write to an archive that's already at the support max size + if ((pZip->m_total_files == 0xFFFF) || ((pZip->m_archive_size + MZ_ZIP_CENTRAL_DIR_HEADER_SIZE + MZ_ZIP_LOCAL_DIR_HEADER_SIZE) > 0xFFFFFFFF)) + return MZ_FALSE; + + pState = pZip->m_pState; + + if (pState->m_pFile) + { +#ifdef MINIZ_NO_STDIO + pFilename; return MZ_FALSE; +#else + // Archive is being read from stdio - try to reopen as writable. + if (pZip->m_pIO_opaque != pZip) + return MZ_FALSE; + if (!pFilename) + return MZ_FALSE; + pZip->m_pWrite = mz_zip_file_write_func; + if (NULL == (pState->m_pFile = MZ_FREOPEN(pFilename, "r+b", pState->m_pFile))) + { + // The mz_zip_archive is now in a bogus state because pState->m_pFile is NULL, so just close it. + mz_zip_reader_end(pZip); + return MZ_FALSE; + } +#endif // #ifdef MINIZ_NO_STDIO + } + else if (pState->m_pMem) + { + // Archive lives in a memory block. Assume it's from the heap that we can resize using the realloc callback. + if (pZip->m_pIO_opaque != pZip) + return MZ_FALSE; + pState->m_mem_capacity = pState->m_mem_size; + pZip->m_pWrite = mz_zip_heap_write_func; + } + // Archive is being read via a user provided read function - make sure the user has specified a write function too. + else if (!pZip->m_pWrite) + return MZ_FALSE; + + // Start writing new files at the archive's current central directory location. + pZip->m_archive_size = pZip->m_central_directory_file_ofs; + pZip->m_zip_mode = MZ_ZIP_MODE_WRITING; + pZip->m_central_directory_file_ofs = 0; + + return MZ_TRUE; +} + +mz_bool mz_zip_writer_add_mem(mz_zip_archive *pZip, const char *pArchive_name, const void *pBuf, size_t buf_size, mz_uint level_and_flags) +{ + return mz_zip_writer_add_mem_ex(pZip, pArchive_name, pBuf, buf_size, NULL, 0, level_and_flags, 0, 0); +} + +typedef struct +{ + mz_zip_archive *m_pZip; + mz_uint64 m_cur_archive_file_ofs; + mz_uint64 m_comp_size; +} mz_zip_writer_add_state; + +static mz_bool mz_zip_writer_add_put_buf_callback(const void* pBuf, int len, void *pUser) +{ + mz_zip_writer_add_state *pState = (mz_zip_writer_add_state *)pUser; + if ((int)pState->m_pZip->m_pWrite(pState->m_pZip->m_pIO_opaque, pState->m_cur_archive_file_ofs, pBuf, len) != len) + return MZ_FALSE; + pState->m_cur_archive_file_ofs += len; + pState->m_comp_size += len; + return MZ_TRUE; +} + +static mz_bool mz_zip_writer_create_local_dir_header(mz_zip_archive *pZip, mz_uint8 *pDst, mz_uint16 filename_size, mz_uint16 extra_size, mz_uint64 uncomp_size, mz_uint64 comp_size, mz_uint32 uncomp_crc32, mz_uint16 method, mz_uint16 bit_flags, mz_uint16 dos_time, mz_uint16 dos_date) +{ + (void)pZip; + memset(pDst, 0, MZ_ZIP_LOCAL_DIR_HEADER_SIZE); + MZ_WRITE_LE32(pDst + MZ_ZIP_LDH_SIG_OFS, MZ_ZIP_LOCAL_DIR_HEADER_SIG); + MZ_WRITE_LE16(pDst + MZ_ZIP_LDH_VERSION_NEEDED_OFS, method ? 20 : 0); + MZ_WRITE_LE16(pDst + MZ_ZIP_LDH_BIT_FLAG_OFS, bit_flags); + MZ_WRITE_LE16(pDst + MZ_ZIP_LDH_METHOD_OFS, method); + MZ_WRITE_LE16(pDst + MZ_ZIP_LDH_FILE_TIME_OFS, dos_time); + MZ_WRITE_LE16(pDst + MZ_ZIP_LDH_FILE_DATE_OFS, dos_date); + MZ_WRITE_LE32(pDst + MZ_ZIP_LDH_CRC32_OFS, uncomp_crc32); + MZ_WRITE_LE32(pDst + MZ_ZIP_LDH_COMPRESSED_SIZE_OFS, comp_size); + MZ_WRITE_LE32(pDst + MZ_ZIP_LDH_DECOMPRESSED_SIZE_OFS, uncomp_size); + MZ_WRITE_LE16(pDst + MZ_ZIP_LDH_FILENAME_LEN_OFS, filename_size); + MZ_WRITE_LE16(pDst + MZ_ZIP_LDH_EXTRA_LEN_OFS, extra_size); + return MZ_TRUE; +} + +static mz_bool mz_zip_writer_create_central_dir_header(mz_zip_archive *pZip, mz_uint8 *pDst, mz_uint16 filename_size, mz_uint16 extra_size, mz_uint16 comment_size, mz_uint64 uncomp_size, mz_uint64 comp_size, mz_uint32 uncomp_crc32, mz_uint16 method, mz_uint16 bit_flags, mz_uint16 dos_time, mz_uint16 dos_date, mz_uint64 local_header_ofs, mz_uint32 ext_attributes) +{ + (void)pZip; + memset(pDst, 0, MZ_ZIP_CENTRAL_DIR_HEADER_SIZE); + MZ_WRITE_LE32(pDst + MZ_ZIP_CDH_SIG_OFS, MZ_ZIP_CENTRAL_DIR_HEADER_SIG); + MZ_WRITE_LE16(pDst + MZ_ZIP_CDH_VERSION_NEEDED_OFS, method ? 20 : 0); + MZ_WRITE_LE16(pDst + MZ_ZIP_CDH_BIT_FLAG_OFS, bit_flags); + MZ_WRITE_LE16(pDst + MZ_ZIP_CDH_METHOD_OFS, method); + MZ_WRITE_LE16(pDst + MZ_ZIP_CDH_FILE_TIME_OFS, dos_time); + MZ_WRITE_LE16(pDst + MZ_ZIP_CDH_FILE_DATE_OFS, dos_date); + MZ_WRITE_LE32(pDst + MZ_ZIP_CDH_CRC32_OFS, uncomp_crc32); + MZ_WRITE_LE32(pDst + MZ_ZIP_CDH_COMPRESSED_SIZE_OFS, comp_size); + MZ_WRITE_LE32(pDst + MZ_ZIP_CDH_DECOMPRESSED_SIZE_OFS, uncomp_size); + MZ_WRITE_LE16(pDst + MZ_ZIP_CDH_FILENAME_LEN_OFS, filename_size); + MZ_WRITE_LE16(pDst + MZ_ZIP_CDH_EXTRA_LEN_OFS, extra_size); + MZ_WRITE_LE16(pDst + MZ_ZIP_CDH_COMMENT_LEN_OFS, comment_size); + MZ_WRITE_LE32(pDst + MZ_ZIP_CDH_EXTERNAL_ATTR_OFS, ext_attributes); + MZ_WRITE_LE32(pDst + MZ_ZIP_CDH_LOCAL_HEADER_OFS, local_header_ofs); + return MZ_TRUE; +} + +static mz_bool mz_zip_writer_add_to_central_dir(mz_zip_archive *pZip, const char *pFilename, mz_uint16 filename_size, const void *pExtra, mz_uint16 extra_size, const void *pComment, mz_uint16 comment_size, mz_uint64 uncomp_size, mz_uint64 comp_size, mz_uint32 uncomp_crc32, mz_uint16 method, mz_uint16 bit_flags, mz_uint16 dos_time, mz_uint16 dos_date, mz_uint64 local_header_ofs, mz_uint32 ext_attributes) +{ + mz_zip_internal_state *pState = pZip->m_pState; + mz_uint32 central_dir_ofs = (mz_uint32)pState->m_central_dir.m_size; + size_t orig_central_dir_size = pState->m_central_dir.m_size; + mz_uint8 central_dir_header[MZ_ZIP_CENTRAL_DIR_HEADER_SIZE]; + + // No zip64 support yet + if ((local_header_ofs > 0xFFFFFFFF) || (((mz_uint64)pState->m_central_dir.m_size + MZ_ZIP_CENTRAL_DIR_HEADER_SIZE + filename_size + extra_size + comment_size) > 0xFFFFFFFF)) + return MZ_FALSE; + + if (!mz_zip_writer_create_central_dir_header(pZip, central_dir_header, filename_size, extra_size, comment_size, uncomp_size, comp_size, uncomp_crc32, method, bit_flags, dos_time, dos_date, local_header_ofs, ext_attributes)) + return MZ_FALSE; + + if ((!mz_zip_array_push_back(pZip, &pState->m_central_dir, central_dir_header, MZ_ZIP_CENTRAL_DIR_HEADER_SIZE)) || + (!mz_zip_array_push_back(pZip, &pState->m_central_dir, pFilename, filename_size)) || + (!mz_zip_array_push_back(pZip, &pState->m_central_dir, pExtra, extra_size)) || + (!mz_zip_array_push_back(pZip, &pState->m_central_dir, pComment, comment_size)) || + (!mz_zip_array_push_back(pZip, &pState->m_central_dir_offsets, ¢ral_dir_ofs, 1))) + { + // Try to push the central directory array back into its original state. + mz_zip_array_resize(pZip, &pState->m_central_dir, orig_central_dir_size, MZ_FALSE); + return MZ_FALSE; + } + + return MZ_TRUE; +} + +static mz_bool mz_zip_writer_validate_archive_name(const char *pArchive_name) +{ + // Basic ZIP archive filename validity checks: Valid filenames cannot start with a forward slash, cannot contain a drive letter, and cannot use DOS-style backward slashes. + if (*pArchive_name == '/') + return MZ_FALSE; + while (*pArchive_name) + { + if ((*pArchive_name == '\\') || (*pArchive_name == ':')) + return MZ_FALSE; + pArchive_name++; + } + return MZ_TRUE; +} + +static mz_uint mz_zip_writer_compute_padding_needed_for_file_alignment(mz_zip_archive *pZip) +{ + mz_uint32 n; + if (!pZip->m_file_offset_alignment) + return 0; + n = (mz_uint32)(pZip->m_archive_size & (pZip->m_file_offset_alignment - 1)); + return (pZip->m_file_offset_alignment - n) & (pZip->m_file_offset_alignment - 1); +} + +static mz_bool mz_zip_writer_write_zeros(mz_zip_archive *pZip, mz_uint64 cur_file_ofs, mz_uint32 n) +{ + char buf[4096]; + memset(buf, 0, MZ_MIN(sizeof(buf), n)); + while (n) + { + mz_uint32 s = MZ_MIN(sizeof(buf), n); + if (pZip->m_pWrite(pZip->m_pIO_opaque, cur_file_ofs, buf, s) != s) + return MZ_FALSE; + cur_file_ofs += s; n -= s; + } + return MZ_TRUE; +} + +mz_bool mz_zip_writer_add_mem_ex(mz_zip_archive *pZip, const char *pArchive_name, const void *pBuf, size_t buf_size, const void *pComment, mz_uint16 comment_size, mz_uint level_and_flags, mz_uint64 uncomp_size, mz_uint32 uncomp_crc32) +{ + mz_uint16 method = 0, dos_time = 0, dos_date = 0; + mz_uint level, ext_attributes = 0, num_alignment_padding_bytes; + mz_uint64 local_dir_header_ofs = pZip->m_archive_size, cur_archive_file_ofs = pZip->m_archive_size, comp_size = 0; + size_t archive_name_size; + mz_uint8 local_dir_header[MZ_ZIP_LOCAL_DIR_HEADER_SIZE]; + tdefl_compressor *pComp = NULL; + mz_bool store_data_uncompressed; + mz_zip_internal_state *pState; + + if ((int)level_and_flags < 0) + level_and_flags = MZ_DEFAULT_LEVEL; + level = level_and_flags & 0xF; + store_data_uncompressed = ((!level) || (level_and_flags & MZ_ZIP_FLAG_COMPRESSED_DATA)); + + if ((!pZip) || (!pZip->m_pState) || (pZip->m_zip_mode != MZ_ZIP_MODE_WRITING) || ((buf_size) && (!pBuf)) || (!pArchive_name) || ((comment_size) && (!pComment)) || (pZip->m_total_files == 0xFFFF) || (level > MZ_UBER_COMPRESSION)) + return MZ_FALSE; + + pState = pZip->m_pState; + + if ((!(level_and_flags & MZ_ZIP_FLAG_COMPRESSED_DATA)) && (uncomp_size)) + return MZ_FALSE; + // No zip64 support yet + if ((buf_size > 0xFFFFFFFF) || (uncomp_size > 0xFFFFFFFF)) + return MZ_FALSE; + if (!mz_zip_writer_validate_archive_name(pArchive_name)) + return MZ_FALSE; + +#ifndef MINIZ_NO_TIME + { + time_t cur_time; time(&cur_time); + mz_zip_time_to_dos_time(cur_time, &dos_time, &dos_date); + } +#endif // #ifndef MINIZ_NO_TIME + + archive_name_size = strlen(pArchive_name); + if (archive_name_size > 0xFFFF) + return MZ_FALSE; + + num_alignment_padding_bytes = mz_zip_writer_compute_padding_needed_for_file_alignment(pZip); + + // no zip64 support yet + if ((pZip->m_total_files == 0xFFFF) || ((pZip->m_archive_size + num_alignment_padding_bytes + MZ_ZIP_LOCAL_DIR_HEADER_SIZE + MZ_ZIP_CENTRAL_DIR_HEADER_SIZE + comment_size + archive_name_size) > 0xFFFFFFFF)) + return MZ_FALSE; + + if ((archive_name_size) && (pArchive_name[archive_name_size - 1] == '/')) + { + // Set DOS Subdirectory attribute bit. + ext_attributes |= 0x10; + // Subdirectories cannot contain data. + if ((buf_size) || (uncomp_size)) + return MZ_FALSE; + } + + // Try to do any allocations before writing to the archive, so if an allocation fails the file remains unmodified. (A good idea if we're doing an in-place modification.) + if ((!mz_zip_array_ensure_room(pZip, &pState->m_central_dir, MZ_ZIP_CENTRAL_DIR_HEADER_SIZE + archive_name_size + comment_size)) || (!mz_zip_array_ensure_room(pZip, &pState->m_central_dir_offsets, 1))) + return MZ_FALSE; + + if ((!store_data_uncompressed) && (buf_size)) + { + if (NULL == (pComp = (tdefl_compressor *)pZip->m_pAlloc(pZip->m_pAlloc_opaque, 1, sizeof(tdefl_compressor)))) + return MZ_FALSE; + } + + if (!mz_zip_writer_write_zeros(pZip, cur_archive_file_ofs, num_alignment_padding_bytes + sizeof(local_dir_header))) + { + pZip->m_pFree(pZip->m_pAlloc_opaque, pComp); + return MZ_FALSE; + } + local_dir_header_ofs += num_alignment_padding_bytes; + if (pZip->m_file_offset_alignment) { MZ_ASSERT((local_dir_header_ofs & (pZip->m_file_offset_alignment - 1)) == 0); } + cur_archive_file_ofs += num_alignment_padding_bytes + sizeof(local_dir_header); + + MZ_CLEAR_OBJ(local_dir_header); + if (pZip->m_pWrite(pZip->m_pIO_opaque, cur_archive_file_ofs, pArchive_name, archive_name_size) != archive_name_size) + { + pZip->m_pFree(pZip->m_pAlloc_opaque, pComp); + return MZ_FALSE; + } + cur_archive_file_ofs += archive_name_size; + + if (!(level_and_flags & MZ_ZIP_FLAG_COMPRESSED_DATA)) + { + uncomp_crc32 = (mz_uint32)mz_crc32(MZ_CRC32_INIT, (const mz_uint8*)pBuf, buf_size); + uncomp_size = buf_size; + if (uncomp_size <= 3) + { + level = 0; + store_data_uncompressed = MZ_TRUE; + } + } + + if (store_data_uncompressed) + { + if (pZip->m_pWrite(pZip->m_pIO_opaque, cur_archive_file_ofs, pBuf, buf_size) != buf_size) + { + pZip->m_pFree(pZip->m_pAlloc_opaque, pComp); + return MZ_FALSE; + } + + cur_archive_file_ofs += buf_size; + comp_size = buf_size; + + if (level_and_flags & MZ_ZIP_FLAG_COMPRESSED_DATA) + method = MZ_DEFLATED; + } + else if (buf_size) + { + mz_zip_writer_add_state state; + + state.m_pZip = pZip; + state.m_cur_archive_file_ofs = cur_archive_file_ofs; + state.m_comp_size = 0; + + if ((tdefl_init(pComp, mz_zip_writer_add_put_buf_callback, &state, tdefl_create_comp_flags_from_zip_params(level, -15, MZ_DEFAULT_STRATEGY)) != TDEFL_STATUS_OKAY) || + (tdefl_compress_buffer(pComp, pBuf, buf_size, TDEFL_FINISH) != TDEFL_STATUS_DONE)) + { + pZip->m_pFree(pZip->m_pAlloc_opaque, pComp); + return MZ_FALSE; + } + + comp_size = state.m_comp_size; + cur_archive_file_ofs = state.m_cur_archive_file_ofs; + + method = MZ_DEFLATED; + } + + pZip->m_pFree(pZip->m_pAlloc_opaque, pComp); + pComp = NULL; + + // no zip64 support yet + if ((comp_size > 0xFFFFFFFF) || (cur_archive_file_ofs > 0xFFFFFFFF)) + return MZ_FALSE; + + if (!mz_zip_writer_create_local_dir_header(pZip, local_dir_header, (mz_uint16)archive_name_size, 0, uncomp_size, comp_size, uncomp_crc32, method, 0, dos_time, dos_date)) + return MZ_FALSE; + + if (pZip->m_pWrite(pZip->m_pIO_opaque, local_dir_header_ofs, local_dir_header, sizeof(local_dir_header)) != sizeof(local_dir_header)) + return MZ_FALSE; + + if (!mz_zip_writer_add_to_central_dir(pZip, pArchive_name, (mz_uint16)archive_name_size, NULL, 0, pComment, comment_size, uncomp_size, comp_size, uncomp_crc32, method, 0, dos_time, dos_date, local_dir_header_ofs, ext_attributes)) + return MZ_FALSE; + + pZip->m_total_files++; + pZip->m_archive_size = cur_archive_file_ofs; + + return MZ_TRUE; +} + +#ifndef MINIZ_NO_STDIO +mz_bool mz_zip_writer_add_file(mz_zip_archive *pZip, const char *pArchive_name, const char *pSrc_filename, const void *pComment, mz_uint16 comment_size, mz_uint level_and_flags) +{ + mz_uint uncomp_crc32 = MZ_CRC32_INIT, level, num_alignment_padding_bytes; + mz_uint16 method = 0, dos_time = 0, dos_date = 0, ext_attributes = 0; + mz_uint64 local_dir_header_ofs = pZip->m_archive_size, cur_archive_file_ofs = pZip->m_archive_size, uncomp_size = 0, comp_size = 0; + size_t archive_name_size; + mz_uint8 local_dir_header[MZ_ZIP_LOCAL_DIR_HEADER_SIZE]; + MZ_FILE *pSrc_file = NULL; + + if ((int)level_and_flags < 0) + level_and_flags = MZ_DEFAULT_LEVEL; + level = level_and_flags & 0xF; + + if ((!pZip) || (!pZip->m_pState) || (pZip->m_zip_mode != MZ_ZIP_MODE_WRITING) || (!pArchive_name) || ((comment_size) && (!pComment)) || (level > MZ_UBER_COMPRESSION)) + return MZ_FALSE; + if (level_and_flags & MZ_ZIP_FLAG_COMPRESSED_DATA) + return MZ_FALSE; + if (!mz_zip_writer_validate_archive_name(pArchive_name)) + return MZ_FALSE; + + archive_name_size = strlen(pArchive_name); + if (archive_name_size > 0xFFFF) + return MZ_FALSE; + + num_alignment_padding_bytes = mz_zip_writer_compute_padding_needed_for_file_alignment(pZip); + + // no zip64 support yet + if ((pZip->m_total_files == 0xFFFF) || ((pZip->m_archive_size + num_alignment_padding_bytes + MZ_ZIP_LOCAL_DIR_HEADER_SIZE + MZ_ZIP_CENTRAL_DIR_HEADER_SIZE + comment_size + archive_name_size) > 0xFFFFFFFF)) + return MZ_FALSE; + + if (!mz_zip_get_file_modified_time(pSrc_filename, &dos_time, &dos_date)) + return MZ_FALSE; + + pSrc_file = MZ_FOPEN(pSrc_filename, "rb"); + if (!pSrc_file) + return MZ_FALSE; + MZ_FSEEK64(pSrc_file, 0, SEEK_END); + uncomp_size = MZ_FTELL64(pSrc_file); + MZ_FSEEK64(pSrc_file, 0, SEEK_SET); + + if (uncomp_size > 0xFFFFFFFF) + { + // No zip64 support yet + MZ_FCLOSE(pSrc_file); + return MZ_FALSE; + } + if (uncomp_size <= 3) + level = 0; + + if (!mz_zip_writer_write_zeros(pZip, cur_archive_file_ofs, num_alignment_padding_bytes + sizeof(local_dir_header))) + { + MZ_FCLOSE(pSrc_file); + return MZ_FALSE; + } + local_dir_header_ofs += num_alignment_padding_bytes; + if (pZip->m_file_offset_alignment) { MZ_ASSERT((local_dir_header_ofs & (pZip->m_file_offset_alignment - 1)) == 0); } + cur_archive_file_ofs += num_alignment_padding_bytes + sizeof(local_dir_header); + + MZ_CLEAR_OBJ(local_dir_header); + if (pZip->m_pWrite(pZip->m_pIO_opaque, cur_archive_file_ofs, pArchive_name, archive_name_size) != archive_name_size) + { + MZ_FCLOSE(pSrc_file); + return MZ_FALSE; + } + cur_archive_file_ofs += archive_name_size; + + if (uncomp_size) + { + mz_uint64 uncomp_remaining = uncomp_size; + void *pRead_buf = pZip->m_pAlloc(pZip->m_pAlloc_opaque, 1, MZ_ZIP_MAX_IO_BUF_SIZE); + if (!pRead_buf) + { + MZ_FCLOSE(pSrc_file); + return MZ_FALSE; + } + + if (!level) + { + while (uncomp_remaining) + { + mz_uint n = (mz_uint)MZ_MIN(MZ_ZIP_MAX_IO_BUF_SIZE, uncomp_remaining); + if ((MZ_FREAD(pRead_buf, 1, n, pSrc_file) != n) || (pZip->m_pWrite(pZip->m_pIO_opaque, cur_archive_file_ofs, pRead_buf, n) != n)) + { + pZip->m_pFree(pZip->m_pAlloc_opaque, pRead_buf); + MZ_FCLOSE(pSrc_file); + return MZ_FALSE; + } + uncomp_crc32 = (mz_uint32)mz_crc32(uncomp_crc32, (const mz_uint8 *)pRead_buf, n); + uncomp_remaining -= n; + cur_archive_file_ofs += n; + } + comp_size = uncomp_size; + } + else + { + mz_bool result = MZ_FALSE; + mz_zip_writer_add_state state; + tdefl_compressor *pComp = (tdefl_compressor *)pZip->m_pAlloc(pZip->m_pAlloc_opaque, 1, sizeof(tdefl_compressor)); + if (!pComp) + { + pZip->m_pFree(pZip->m_pAlloc_opaque, pRead_buf); + MZ_FCLOSE(pSrc_file); + return MZ_FALSE; + } + + state.m_pZip = pZip; + state.m_cur_archive_file_ofs = cur_archive_file_ofs; + state.m_comp_size = 0; + + if (tdefl_init(pComp, mz_zip_writer_add_put_buf_callback, &state, tdefl_create_comp_flags_from_zip_params(level, -15, MZ_DEFAULT_STRATEGY)) != TDEFL_STATUS_OKAY) + { + pZip->m_pFree(pZip->m_pAlloc_opaque, pComp); + pZip->m_pFree(pZip->m_pAlloc_opaque, pRead_buf); + MZ_FCLOSE(pSrc_file); + return MZ_FALSE; + } + + for ( ; ; ) + { + size_t in_buf_size = (mz_uint32)MZ_MIN(uncomp_remaining, MZ_ZIP_MAX_IO_BUF_SIZE); + tdefl_status status; + + if (MZ_FREAD(pRead_buf, 1, in_buf_size, pSrc_file) != in_buf_size) + break; + + uncomp_crc32 = (mz_uint32)mz_crc32(uncomp_crc32, (const mz_uint8 *)pRead_buf, in_buf_size); + uncomp_remaining -= in_buf_size; + + status = tdefl_compress_buffer(pComp, pRead_buf, in_buf_size, uncomp_remaining ? TDEFL_NO_FLUSH : TDEFL_FINISH); + if (status == TDEFL_STATUS_DONE) + { + result = MZ_TRUE; + break; + } + else if (status != TDEFL_STATUS_OKAY) + break; + } + + pZip->m_pFree(pZip->m_pAlloc_opaque, pComp); + + if (!result) + { + pZip->m_pFree(pZip->m_pAlloc_opaque, pRead_buf); + MZ_FCLOSE(pSrc_file); + return MZ_FALSE; + } + + comp_size = state.m_comp_size; + cur_archive_file_ofs = state.m_cur_archive_file_ofs; + + method = MZ_DEFLATED; + } + + pZip->m_pFree(pZip->m_pAlloc_opaque, pRead_buf); + } + + MZ_FCLOSE(pSrc_file); pSrc_file = NULL; + + // no zip64 support yet + if ((comp_size > 0xFFFFFFFF) || (cur_archive_file_ofs > 0xFFFFFFFF)) + return MZ_FALSE; + + if (!mz_zip_writer_create_local_dir_header(pZip, local_dir_header, (mz_uint16)archive_name_size, 0, uncomp_size, comp_size, uncomp_crc32, method, 0, dos_time, dos_date)) + return MZ_FALSE; + + if (pZip->m_pWrite(pZip->m_pIO_opaque, local_dir_header_ofs, local_dir_header, sizeof(local_dir_header)) != sizeof(local_dir_header)) + return MZ_FALSE; + + if (!mz_zip_writer_add_to_central_dir(pZip, pArchive_name, (mz_uint16)archive_name_size, NULL, 0, pComment, comment_size, uncomp_size, comp_size, uncomp_crc32, method, 0, dos_time, dos_date, local_dir_header_ofs, ext_attributes)) + return MZ_FALSE; + + pZip->m_total_files++; + pZip->m_archive_size = cur_archive_file_ofs; + + return MZ_TRUE; +} +#endif // #ifndef MINIZ_NO_STDIO + +mz_bool mz_zip_writer_add_from_zip_reader(mz_zip_archive *pZip, mz_zip_archive *pSource_zip, mz_uint file_index) +{ + mz_uint n, bit_flags, num_alignment_padding_bytes; + mz_uint64 comp_bytes_remaining, local_dir_header_ofs; + mz_uint64 cur_src_file_ofs, cur_dst_file_ofs; + mz_uint32 local_header_u32[(MZ_ZIP_LOCAL_DIR_HEADER_SIZE + sizeof(mz_uint32) - 1) / sizeof(mz_uint32)]; mz_uint8 *pLocal_header = (mz_uint8 *)local_header_u32; + mz_uint8 central_header[MZ_ZIP_CENTRAL_DIR_HEADER_SIZE]; + size_t orig_central_dir_size; + mz_zip_internal_state *pState; + void *pBuf; const mz_uint8 *pSrc_central_header; + + if ((!pZip) || (!pZip->m_pState) || (pZip->m_zip_mode != MZ_ZIP_MODE_WRITING)) + return MZ_FALSE; + if (NULL == (pSrc_central_header = mz_zip_reader_get_cdh(pSource_zip, file_index))) + return MZ_FALSE; + pState = pZip->m_pState; + + num_alignment_padding_bytes = mz_zip_writer_compute_padding_needed_for_file_alignment(pZip); + + // no zip64 support yet + if ((pZip->m_total_files == 0xFFFF) || ((pZip->m_archive_size + num_alignment_padding_bytes + MZ_ZIP_LOCAL_DIR_HEADER_SIZE + MZ_ZIP_CENTRAL_DIR_HEADER_SIZE) > 0xFFFFFFFF)) + return MZ_FALSE; + + cur_src_file_ofs = MZ_READ_LE32(pSrc_central_header + MZ_ZIP_CDH_LOCAL_HEADER_OFS); + cur_dst_file_ofs = pZip->m_archive_size; + + if (pSource_zip->m_pRead(pSource_zip->m_pIO_opaque, cur_src_file_ofs, pLocal_header, MZ_ZIP_LOCAL_DIR_HEADER_SIZE) != MZ_ZIP_LOCAL_DIR_HEADER_SIZE) + return MZ_FALSE; + if (MZ_READ_LE32(pLocal_header) != MZ_ZIP_LOCAL_DIR_HEADER_SIG) + return MZ_FALSE; + cur_src_file_ofs += MZ_ZIP_LOCAL_DIR_HEADER_SIZE; + + if (!mz_zip_writer_write_zeros(pZip, cur_dst_file_ofs, num_alignment_padding_bytes)) + return MZ_FALSE; + cur_dst_file_ofs += num_alignment_padding_bytes; + local_dir_header_ofs = cur_dst_file_ofs; + if (pZip->m_file_offset_alignment) { MZ_ASSERT((local_dir_header_ofs & (pZip->m_file_offset_alignment - 1)) == 0); } + + if (pZip->m_pWrite(pZip->m_pIO_opaque, cur_dst_file_ofs, pLocal_header, MZ_ZIP_LOCAL_DIR_HEADER_SIZE) != MZ_ZIP_LOCAL_DIR_HEADER_SIZE) + return MZ_FALSE; + cur_dst_file_ofs += MZ_ZIP_LOCAL_DIR_HEADER_SIZE; + + n = MZ_READ_LE16(pLocal_header + MZ_ZIP_LDH_FILENAME_LEN_OFS) + MZ_READ_LE16(pLocal_header + MZ_ZIP_LDH_EXTRA_LEN_OFS); + comp_bytes_remaining = n + MZ_READ_LE32(pSrc_central_header + MZ_ZIP_CDH_COMPRESSED_SIZE_OFS); + + if (NULL == (pBuf = pZip->m_pAlloc(pZip->m_pAlloc_opaque, 1, (size_t)MZ_MAX(sizeof(mz_uint32) * 4, MZ_MIN(MZ_ZIP_MAX_IO_BUF_SIZE, comp_bytes_remaining))))) + return MZ_FALSE; + + while (comp_bytes_remaining) + { + n = (mz_uint)MZ_MIN(MZ_ZIP_MAX_IO_BUF_SIZE, comp_bytes_remaining); + if (pSource_zip->m_pRead(pSource_zip->m_pIO_opaque, cur_src_file_ofs, pBuf, n) != n) + { + pZip->m_pFree(pZip->m_pAlloc_opaque, pBuf); + return MZ_FALSE; + } + cur_src_file_ofs += n; + + if (pZip->m_pWrite(pZip->m_pIO_opaque, cur_dst_file_ofs, pBuf, n) != n) + { + pZip->m_pFree(pZip->m_pAlloc_opaque, pBuf); + return MZ_FALSE; + } + cur_dst_file_ofs += n; + + comp_bytes_remaining -= n; + } + + bit_flags = MZ_READ_LE16(pLocal_header + MZ_ZIP_LDH_BIT_FLAG_OFS); + if (bit_flags & 8) + { + // Copy data descriptor + if (pSource_zip->m_pRead(pSource_zip->m_pIO_opaque, cur_src_file_ofs, pBuf, sizeof(mz_uint32) * 4) != sizeof(mz_uint32) * 4) + { + pZip->m_pFree(pZip->m_pAlloc_opaque, pBuf); + return MZ_FALSE; + } + + n = sizeof(mz_uint32) * ((MZ_READ_LE32(pBuf) == 0x08074b50) ? 4 : 3); + if (pZip->m_pWrite(pZip->m_pIO_opaque, cur_dst_file_ofs, pBuf, n) != n) + { + pZip->m_pFree(pZip->m_pAlloc_opaque, pBuf); + return MZ_FALSE; + } + + cur_src_file_ofs += n; + cur_dst_file_ofs += n; + } + pZip->m_pFree(pZip->m_pAlloc_opaque, pBuf); + + // no zip64 support yet + if (cur_dst_file_ofs > 0xFFFFFFFF) + return MZ_FALSE; + + orig_central_dir_size = pState->m_central_dir.m_size; + + memcpy(central_header, pSrc_central_header, MZ_ZIP_CENTRAL_DIR_HEADER_SIZE); + MZ_WRITE_LE32(central_header + MZ_ZIP_CDH_LOCAL_HEADER_OFS, local_dir_header_ofs); + if (!mz_zip_array_push_back(pZip, &pState->m_central_dir, central_header, MZ_ZIP_CENTRAL_DIR_HEADER_SIZE)) + return MZ_FALSE; + + n = MZ_READ_LE16(pSrc_central_header + MZ_ZIP_CDH_FILENAME_LEN_OFS) + MZ_READ_LE16(pSrc_central_header + MZ_ZIP_CDH_EXTRA_LEN_OFS) + MZ_READ_LE16(pSrc_central_header + MZ_ZIP_CDH_COMMENT_LEN_OFS); + if (!mz_zip_array_push_back(pZip, &pState->m_central_dir, pSrc_central_header + MZ_ZIP_CENTRAL_DIR_HEADER_SIZE, n)) + { + mz_zip_array_resize(pZip, &pState->m_central_dir, orig_central_dir_size, MZ_FALSE); + return MZ_FALSE; + } + + if (pState->m_central_dir.m_size > 0xFFFFFFFF) + return MZ_FALSE; + n = (mz_uint32)orig_central_dir_size; + if (!mz_zip_array_push_back(pZip, &pState->m_central_dir_offsets, &n, 1)) + { + mz_zip_array_resize(pZip, &pState->m_central_dir, orig_central_dir_size, MZ_FALSE); + return MZ_FALSE; + } + + pZip->m_total_files++; + pZip->m_archive_size = cur_dst_file_ofs; + + return MZ_TRUE; +} + +mz_bool mz_zip_writer_finalize_archive(mz_zip_archive *pZip) +{ + mz_zip_internal_state *pState; + mz_uint64 central_dir_ofs, central_dir_size; + mz_uint8 hdr[MZ_ZIP_END_OF_CENTRAL_DIR_HEADER_SIZE]; + + if ((!pZip) || (!pZip->m_pState) || (pZip->m_zip_mode != MZ_ZIP_MODE_WRITING)) + return MZ_FALSE; + + pState = pZip->m_pState; + + // no zip64 support yet + if ((pZip->m_total_files > 0xFFFF) || ((pZip->m_archive_size + pState->m_central_dir.m_size + MZ_ZIP_END_OF_CENTRAL_DIR_HEADER_SIZE) > 0xFFFFFFFF)) + return MZ_FALSE; + + central_dir_ofs = 0; + central_dir_size = 0; + if (pZip->m_total_files) + { + // Write central directory + central_dir_ofs = pZip->m_archive_size; + central_dir_size = pState->m_central_dir.m_size; + pZip->m_central_directory_file_ofs = central_dir_ofs; + if (pZip->m_pWrite(pZip->m_pIO_opaque, central_dir_ofs, pState->m_central_dir.m_p, (size_t)central_dir_size) != central_dir_size) + return MZ_FALSE; + pZip->m_archive_size += central_dir_size; + } + + // Write end of central directory record + MZ_CLEAR_OBJ(hdr); + MZ_WRITE_LE32(hdr + MZ_ZIP_ECDH_SIG_OFS, MZ_ZIP_END_OF_CENTRAL_DIR_HEADER_SIG); + MZ_WRITE_LE16(hdr + MZ_ZIP_ECDH_CDIR_NUM_ENTRIES_ON_DISK_OFS, pZip->m_total_files); + MZ_WRITE_LE16(hdr + MZ_ZIP_ECDH_CDIR_TOTAL_ENTRIES_OFS, pZip->m_total_files); + MZ_WRITE_LE32(hdr + MZ_ZIP_ECDH_CDIR_SIZE_OFS, central_dir_size); + MZ_WRITE_LE32(hdr + MZ_ZIP_ECDH_CDIR_OFS_OFS, central_dir_ofs); + + if (pZip->m_pWrite(pZip->m_pIO_opaque, pZip->m_archive_size, hdr, sizeof(hdr)) != sizeof(hdr)) + return MZ_FALSE; +#ifndef MINIZ_NO_STDIO + if ((pState->m_pFile) && (MZ_FFLUSH(pState->m_pFile) == EOF)) + return MZ_FALSE; +#endif // #ifndef MINIZ_NO_STDIO + + pZip->m_archive_size += sizeof(hdr); + + pZip->m_zip_mode = MZ_ZIP_MODE_WRITING_HAS_BEEN_FINALIZED; + return MZ_TRUE; +} + +mz_bool mz_zip_writer_finalize_heap_archive(mz_zip_archive *pZip, void **pBuf, size_t *pSize) +{ + if ((!pZip) || (!pZip->m_pState) || (!pBuf) || (!pSize)) + return MZ_FALSE; + if (pZip->m_pWrite != mz_zip_heap_write_func) + return MZ_FALSE; + if (!mz_zip_writer_finalize_archive(pZip)) + return MZ_FALSE; + + *pBuf = pZip->m_pState->m_pMem; + *pSize = pZip->m_pState->m_mem_size; + pZip->m_pState->m_pMem = NULL; + pZip->m_pState->m_mem_size = pZip->m_pState->m_mem_capacity = 0; + return MZ_TRUE; +} + +mz_bool mz_zip_writer_end(mz_zip_archive *pZip) +{ + mz_zip_internal_state *pState; + mz_bool status = MZ_TRUE; + if ((!pZip) || (!pZip->m_pState) || (!pZip->m_pAlloc) || (!pZip->m_pFree) || ((pZip->m_zip_mode != MZ_ZIP_MODE_WRITING) && (pZip->m_zip_mode != MZ_ZIP_MODE_WRITING_HAS_BEEN_FINALIZED))) + return MZ_FALSE; + + pState = pZip->m_pState; + pZip->m_pState = NULL; + mz_zip_array_clear(pZip, &pState->m_central_dir); + mz_zip_array_clear(pZip, &pState->m_central_dir_offsets); + mz_zip_array_clear(pZip, &pState->m_sorted_central_dir_offsets); + +#ifndef MINIZ_NO_STDIO + if (pState->m_pFile) + { + MZ_FCLOSE(pState->m_pFile); + pState->m_pFile = NULL; + } +#endif // #ifndef MINIZ_NO_STDIO + + if ((pZip->m_pWrite == mz_zip_heap_write_func) && (pState->m_pMem)) + { + pZip->m_pFree(pZip->m_pAlloc_opaque, pState->m_pMem); + pState->m_pMem = NULL; + } + + pZip->m_pFree(pZip->m_pAlloc_opaque, pState); + pZip->m_zip_mode = MZ_ZIP_MODE_INVALID; + return status; +} + +#ifndef MINIZ_NO_STDIO +mz_bool mz_zip_add_mem_to_archive_file_in_place(const char *pZip_filename, const char *pArchive_name, const void *pBuf, size_t buf_size, const void *pComment, mz_uint16 comment_size, mz_uint level_and_flags) +{ + mz_bool status, created_new_archive = MZ_FALSE; + mz_zip_archive zip_archive; + struct MZ_FILE_STAT_STRUCT file_stat; + MZ_CLEAR_OBJ(zip_archive); + if ((int)level_and_flags < 0) + level_and_flags = MZ_DEFAULT_LEVEL; + if ((!pZip_filename) || (!pArchive_name) || ((buf_size) && (!pBuf)) || ((comment_size) && (!pComment)) || ((level_and_flags & 0xF) > MZ_UBER_COMPRESSION)) + return MZ_FALSE; + if (!mz_zip_writer_validate_archive_name(pArchive_name)) + return MZ_FALSE; + if (MZ_FILE_STAT(pZip_filename, &file_stat) != 0) + { + // Create a new archive. + if (!mz_zip_writer_init_file(&zip_archive, pZip_filename, 0)) + return MZ_FALSE; + created_new_archive = MZ_TRUE; + } + else + { + // Append to an existing archive. + if (!mz_zip_reader_init_file(&zip_archive, pZip_filename, level_and_flags | MZ_ZIP_FLAG_DO_NOT_SORT_CENTRAL_DIRECTORY)) + return MZ_FALSE; + if (!mz_zip_writer_init_from_reader(&zip_archive, pZip_filename)) + { + mz_zip_reader_end(&zip_archive); + return MZ_FALSE; + } + } + status = mz_zip_writer_add_mem_ex(&zip_archive, pArchive_name, pBuf, buf_size, pComment, comment_size, level_and_flags, 0, 0); + // Always finalize, even if adding failed for some reason, so we have a valid central directory. (This may not always succeed, but we can try.) + if (!mz_zip_writer_finalize_archive(&zip_archive)) + status = MZ_FALSE; + if (!mz_zip_writer_end(&zip_archive)) + status = MZ_FALSE; + if ((!status) && (created_new_archive)) + { + // It's a new archive and something went wrong, so just delete it. + int ignoredStatus = MZ_DELETE_FILE(pZip_filename); + (void)ignoredStatus; + } + return status; +} + +void *mz_zip_extract_archive_file_to_heap(const char *pZip_filename, const char *pArchive_name, size_t *pSize, mz_uint flags) +{ + int file_index; + mz_zip_archive zip_archive; + void *p = NULL; + + if (pSize) + *pSize = 0; + + if ((!pZip_filename) || (!pArchive_name)) + return NULL; + + MZ_CLEAR_OBJ(zip_archive); + if (!mz_zip_reader_init_file(&zip_archive, pZip_filename, flags | MZ_ZIP_FLAG_DO_NOT_SORT_CENTRAL_DIRECTORY)) + return NULL; + + if ((file_index = mz_zip_reader_locate_file(&zip_archive, pArchive_name, NULL, flags)) >= 0) + p = mz_zip_reader_extract_to_heap(&zip_archive, file_index, pSize, flags); + + mz_zip_reader_end(&zip_archive); + return p; +} + +#endif // #ifndef MINIZ_NO_STDIO + +#endif // #ifndef MINIZ_NO_ARCHIVE_WRITING_APIS + +#endif // #ifndef MINIZ_NO_ARCHIVE_APIS + +#ifdef __cplusplus +} +#endif + +#endif // MINIZ_HEADER_FILE_ONLY + +/* + This is free and unencumbered software released into the public domain. + + Anyone is free to copy, modify, publish, use, compile, sell, or + distribute this software, either in source code form or as a compiled + binary, for any purpose, commercial or non-commercial, and by any + means. + + In jurisdictions that recognize copyright laws, the author or authors + of this software dedicate any and all copyright interest in the + software to the public domain. We make this dedication for the benefit + of the public at large and to the detriment of our heirs and + successors. We intend this dedication to be an overt act of + relinquishment in perpetuity of all present and future rights to this + software under copyright law. + + 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 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. + + For more information, please refer to +*/ ADDED src/mkbuiltin.c Index: src/mkbuiltin.c ================================================================== --- /dev/null +++ src/mkbuiltin.c @@ -0,0 +1,415 @@ +/* +** 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 is a stand-alone utility program that is part of the Fossil build +** process. This program reads files named on the command line and converts +** them into ANSI-C static char array variables. Output is written onto +** standard output. +** +** Additionally, the input files may be listed in a separate list file (one +** resource name per line, optionally enclosed in double quotes). Pass the list +** via '--reslist ' option. Both lists, from the command line and +** the list file, are merged; duplicate file names skipped from processing. +** This option is useful to get around the command line length limitations +** under some OS, like Windows. +** +** The makefiles use this utility to package various resources (large scripts, +** GIF images, etc) that are separate files in the source code as byte +** arrays in the resulting executable. +*/ +#include +#include +#include +#include + +/* +** Read the entire content of the file named zFilename into memory obtained +** from malloc() and return a pointer to that memory. Write the size of the +** file into *pnByte. +*/ +static unsigned char *read_file(const char *zFilename, int *pnByte){ + FILE *in; + unsigned char *z; + int nByte; + int got; + in = fopen(zFilename, "rb"); + if( in==0 ){ + return 0; + } + fseek(in, 0, SEEK_END); + *pnByte = nByte = ftell(in); + fseek(in, 0, SEEK_SET); + z = malloc( nByte+1 ); + if( z==0 ){ + fprintf(stderr, "failed to allocate %d bytes\n", nByte+1); + exit(1); + } + got = fread(z, 1, nByte, in); + fclose(in); + z[got] = 0; + return z; +} + +#ifndef FOSSIL_DEBUG +/* +** Try to compress a javascript file by removing unnecessary whitespace. +** +** Warning: This compression routine does not necessarily work for any +** arbitrary Javascript source file. But it should work ok for the +** well-behaved source files in this project. +*/ +static void compressJavascript(unsigned char *z, int *pn){ + int n = *pn; + int i, j, k; + for(i=j=0; i0 && (z[j-1]==' ' || z[j-1]=='\t') ){ j--; } + for(k=i+3; k0 && (z[j-1]==' ' || z[j-1]=='\t') ){ j--; } + for(k=i+2; k0 && isspace(z[j-1]) ) j--; + z[j++] = '\n'; + while( i+1aRes; + } + fseek(in, 0L, SEEK_END); + filesize = ftell(in); + rewind(in); + + if( filesize > RESLIST_BUF_MAXBYTES ){ + fprintf(stderr, "List file [%s] must be smaller than %ld bytes\n", name, + RESLIST_BUF_MAXBYTES); + return list->aRes; + } + list->bufsize = filesize; + list->buf = (char *)calloc((list->bufsize + 2), sizeof(list->buf[0])); + if( list->buf==0 ){ + fprintf(stderr, "failed to allocated %ld bytes\n", list->bufsize + 1); + list->bufsize = 0L; + return list->aRes; + } + filesize = fread(list->buf, sizeof(list->buf[0]),list->bufsize, in); + if ( filesize!=list->bufsize ){ + fprintf(stderr, "failed to read [%s]\n", name); + return list->aRes; + } + fclose(in); + + /* + ** append an extra newline (if missing) for a correct line count + */ + if( list->buf[list->bufsize-1]!='\n' ) list->buf[list->bufsize]='\n'; + + linecount = 0L; + for( p = strchr(list->buf, '\n'); + p && p <= &list->buf[list->bufsize-1]; + p = strchr(++p, '\n') ){ + ++linecount; + } + + list->aRes = (Resource *)calloc(linecount+1, sizeof(list->aRes[0])); + for( pb = list->buf, p = strchr(pb, '\n'); + p && p <= &list->buf[list->bufsize-1]; + pb = ++p, p = strchr(pb, '\n') ){ + + char *path = pb; + char *pe = p - 1; + + /* strip leading and trailing whitespace */ + while( path < p && isspace(*path) ) ++path; + while( pe > path && isspace(*pe) ){ + *pe = '\0'; + --pe; + } + + /* strip outer quotes */ + while( path < p && *path=='\"') ++path; + while( pe > path && *pe=='\"' ){ + *pe = '\0'; + --pe; + } + *p = '\0'; + + /* skip empty path */ + if( *path ){ + list->aRes[list->nRes].zName = path; + ++(list->nRes); + } + } + return list->aRes; +} + +void free_reslist(ResourceList *list){ + if( list ){ + if( list->buf ) free(list->buf); + if( list->aRes) free(list->aRes); + memset(list, 0, sizeof(*list)); + } +} + +/* +** Compare two Resource objects for sorting purposes. They sort +** in zName order so that Fossil can search for resources using +** a binary search. +*/ +typedef int (*QsortCompareFunc)(const void *, const void*); + +static int compareResource(const Resource *a, const Resource *b){ + return strcmp(a->zName, b->zName); +} + +int remove_duplicates(ResourceList *list){ + char dupNameAsc[64] = "\255"; + char dupNameDesc[64] = ""; + Resource dupResAsc; + Resource dupResDesc; + Resource *pDupRes; + int dupcount = 0; + int i; + + if( list->nRes==0 ){ + return list->nRes; + } + + /* + ** scan for duplicates and assign their names to a string that would sort to + ** the bottom, then re-sort and truncate the duplicates + */ + memset(dupNameAsc, dupNameAsc[0], sizeof(dupNameAsc)-2); + memset(dupNameDesc, dupNameDesc[0], sizeof(dupNameDesc)-2); + memset(&dupResAsc, 0, sizeof(dupResAsc)); + dupResAsc.zName = dupNameAsc; + memset(&dupResDesc, 0, sizeof(dupResDesc)); + dupResDesc.zName = dupNameDesc; + pDupRes = (compareResource(&dupResAsc, &dupResDesc) > 0 + ? &dupResAsc : &dupResDesc); + + qsort(list->aRes, list->nRes, sizeof(list->aRes[0]), + (QsortCompareFunc)compareResource); + for( i=0; inRes-1 ; ++i){ + Resource *res = &list->aRes[i]; + + while( inRes-1 + && compareResource(res, &list->aRes[i+1])==0 ){ + fprintf(stderr, "Skipped a duplicate file [%s]\n", list->aRes[i+1].zName); + memcpy(&list->aRes[i+1], pDupRes, sizeof(list->aRes[0])); + ++dupcount; + + ++i; + } + } + if( dupcount == 0){ + return list->nRes; + } + qsort(list->aRes, list->nRes, sizeof(list->aRes[0]), + (QsortCompareFunc)compareResource); + list->nRes -= dupcount; + memset(&list->aRes[list->nRes], 0, sizeof(list->aRes[0])); + + return list->nRes; +} + +int main(int argc, char **argv){ + int i, sz; + int j, n; + ResourceList resList; + Resource *aRes; + int nRes; + unsigned char *pData; + int nErr = 0; + int nSkip; + int nPrefix = 0; +#ifndef FOSSIL_DEBUG + int nName; +#endif + + if( argc==1 ){ + fprintf(stderr, "usage\t:%s " + "[--prefix path] [--reslist file] [resource-file1 ...]\n", + argv[0] + ); + return 1; + } + if( argc>3 && strcmp(argv[1],"--prefix")==0 ){ + nPrefix = (int)strlen(argv[2]); + argc -= 2; + argv += 2; + } + + memset(&resList, 0, sizeof(resList)); + if( argc>2 && strcmp(argv[1],"--reslist")==0 ){ + if( read_reslist(argv[2], &resList)==0 ){ + fprintf(stderr, "Failed to load resource list from [%s]", argv[2]); + free_reslist(&resList); + return 1; + } + argc -= 2; + argv += 2; + } + + if( argc>1 ){ + aRes = realloc(resList.aRes, (resList.nRes+argc-1)*sizeof(resList.aRes[0])); + if( aRes==0 || aRes==resList.aRes ){ + fprintf(stderr, "realloc failed\n"); + free_reslist(&resList); + return 1; + } + resList.aRes = aRes; + + for(i=0; i3 && strcmp(&aRes[i].zName[nName-3],".js")==0) + || (nName>7 && strcmp(&aRes[i].zName[nName-7], "/js.txt")==0) + ){ + int x = sz-nSkip; + compressJavascript(pData+nSkip, &x); + sz = x + nSkip; + } +#endif + + aRes[i].nByte = sz - nSkip; + aRes[i].idx = i; + printf("/* Content of file %s */\n", aRes[i].zName); + printf("static const unsigned char bidata%d[%d] = {\n ", + i, sz+1-nSkip); + for(j=nSkip, n=0; j<=sz; j++){ + printf("%3d", pData[j]); + if( j==sz ){ + printf(" };\n"); + }else if( n==14 ){ + printf(",\n "); + n = 0; + }else{ + printf(", "); + n++; + } + } + free(pData); + } + printf("typedef struct BuiltinFileTable BuiltinFileTable;\n"); + printf("struct BuiltinFileTable {\n"); + printf(" const char *zName;\n"); + printf(" const unsigned char *pData;\n"); + printf(" int nByte;\n"); + printf("};\n"); + printf("static const BuiltinFileTable aBuiltinFiles[] = {\n"); + for(i=0; i=nPrefix ) z += nPrefix; + while( z[0]=='.' || z[0]=='/' || z[0]=='\\' ){ z++; } + aRes[i].zName = z; + while( z[0] ){ + if( z[0]=='\\' ) z[0] = '/'; + z++; + } + } + qsort(aRes, nRes, sizeof(aRes[0]), (QsortCompareFunc)compareResource); + for(i=0; i #include -#include #include #include + +/*************************************************************************** +** These macros must match similar macros in dispatch.c. +** +** Allowed values for CmdOrPage.eCmdFlags. */ +#define CMDFLAG_1ST_TIER 0x0001 /* Most important commands */ +#define CMDFLAG_2ND_TIER 0x0002 /* Obscure and seldom used commands */ +#define CMDFLAG_TEST 0x0004 /* Commands for testing only */ +#define CMDFLAG_WEBPAGE 0x0008 /* Web pages */ +#define CMDFLAG_COMMAND 0x0010 /* A command */ +#define CMDFLAG_SETTING 0x0020 /* A setting */ +#define CMDFLAG_VERSIONABLE 0x0040 /* A versionable setting */ +#define CMDFLAG_BLOCKTEXT 0x0080 /* Multi-line text setting */ +#define CMDFLAG_BOOLEAN 0x0100 /* A boolean setting */ +#define CMDFLAG_RAWCONTENT 0x0200 /* Do not interpret webpage content */ +#define CMDFLAG_SENSITIVE 0x0400 /* Security-sensitive setting */ +/**************************************************************************/ /* ** Each entry looks like this: */ typedef struct Entry { - int eType; - char *zFunc; - char *zPath; - char *zHelp; + int eType; /* CMDFLAG_* values */ + char *zIf; /* Enclose in #if */ + char *zFunc; /* Name of implementation */ + char *zPath; /* Webpage or command name */ + char *zHelp; /* Help text */ + char *zDflt; /* Default value for settings */ + char *zVar; /* config.name for settings, if different from zPath */ + int iHelp; /* Index of Help text */ + int iWidth; /* Display width for SETTING: values */ } Entry; /* ** Maximum number of entries */ -#define N_ENTRY 500 +#define N_ENTRY 5000 /* ** Maximum size of a help message */ -#define MX_HELP 10000 +#define MX_HELP 250000 /* ** Table of entries */ Entry aEntry[N_ENTRY]; @@ -72,10 +129,15 @@ ** Current help message accumulator */ char zHelp[MX_HELP]; int nHelp; +/* +** Most recently encountered #if +*/ +char zIf[2000]; + /* ** How many entries are used */ int nUsed; int nFixed; @@ -84,10 +146,15 @@ ** Current filename and line number */ char *zFile; int nLine; +/* +** Number of errors +*/ +int nErr = 0; + /* ** Duplicate N characters of a string. */ char *string_dup(const char *zSrc, int n){ char *z; @@ -96,85 +163,220 @@ if( z==0 ){ fprintf(stderr,"Out of memory!\n"); exit(1); } strncpy(z, zSrc, n); z[n] = 0; return z; } + +/* +** Safe isspace macro. Works with signed characters. +*/ +int fossil_isspace(char c){ + return c==' ' || (c<='\r' && c>='\t'); +} + +/* +** Safe isident macro. Works with signed characters. +*/ +int fossil_isident(char c){ + if( c>='a' && c<='z' ) return 1; + if( c>='A' && c<='Z' ) return 1; + if( c>='0' && c<='9' ) return 1; + if( c=='_' ) return 1; + return 0; +} /* ** Scan a line looking for comments containing zLabel. Make ** new entries if found. */ void scan_for_label(const char *zLabel, char *zLine, int eType){ int i, j; int len = strlen(zLabel); if( nUsed>=N_ENTRY ) return; - for(i=0; isspace(zLine[i]) || zLine[i]=='*'; i++){} + for(i=0; fossil_isspace(zLine[i]) || zLine[i]=='*'; i++){} if( zLine[i]!=zLabel[0] ) return; if( strncmp(&zLine[i],zLabel, len)==0 ){ i += len; }else{ return; } - while( isspace(zLine[i]) ){ i++; } + while( fossil_isspace(zLine[i]) ){ i++; } if( zLine[i]=='/' ) i++; - for(j=0; zLine[i+j] && !isspace(zLine[i+j]); j++){} + for(j=0; zLine[i+j] && !fossil_isspace(zLine[i+j]); j++){} aEntry[nUsed].eType = eType; - aEntry[nUsed].zPath = string_dup(&zLine[i], j); + if( eType & CMDFLAG_WEBPAGE ){ + aEntry[nUsed].zPath = string_dup(&zLine[i-1], j+1); + aEntry[nUsed].zPath[0] = '/'; + }else{ + aEntry[nUsed].zPath = string_dup(&zLine[i], j); + } aEntry[nUsed].zFunc = 0; + if( (eType & CMDFLAG_COMMAND)!=0 ){ + if( strncmp(&zLine[i], "test-", 5)==0 ){ + /* Commands that start with "test-" are test-commands */ + aEntry[nUsed].eType |= CMDFLAG_TEST; + }else if( zLine[i+j-1]=='*' ){ + /* If the command name ends in '*', remove the '*' from the name + ** but move the command into the second tier */ + aEntry[nUsed].zPath[j-1] = 0; + aEntry[nUsed].eType |= CMDFLAG_2ND_TIER; + }else{ + /* Otherwise, this is a first-tier command */ + aEntry[nUsed].eType |= CMDFLAG_1ST_TIER; + } + } + + /* Process additional flags that might follow the command name */ + while( zLine[i+j]!=0 ){ + i += j; + while( fossil_isspace(zLine[i]) ){ i++; } + if( zLine[i]==0 ) break; + for(j=0; zLine[i+j] && !fossil_isspace(zLine[i+j]); j++){} + if( j==8 && strncmp(&zLine[i], "1st-tier", j)==0 ){ + aEntry[nUsed].eType &= ~(CMDFLAG_2ND_TIER|CMDFLAG_TEST); + aEntry[nUsed].eType |= CMDFLAG_1ST_TIER; + }else if( j==8 && strncmp(&zLine[i], "2nd-tier", j)==0 ){ + aEntry[nUsed].eType &= ~(CMDFLAG_1ST_TIER|CMDFLAG_TEST); + aEntry[nUsed].eType |= CMDFLAG_2ND_TIER; + }else if( j==4 && strncmp(&zLine[i], "test", j)==0 ){ + aEntry[nUsed].eType &= ~(CMDFLAG_1ST_TIER|CMDFLAG_2ND_TIER); + aEntry[nUsed].eType |= CMDFLAG_TEST; + }else if( j==11 && strncmp(&zLine[i], "raw-content", j)==0 ){ + aEntry[nUsed].eType |= CMDFLAG_RAWCONTENT; + }else if( j==7 && strncmp(&zLine[i], "boolean", j)==0 ){ + aEntry[nUsed].eType &= ~(CMDFLAG_BLOCKTEXT); + aEntry[nUsed].iWidth = 0; + aEntry[nUsed].eType |= CMDFLAG_BOOLEAN; + }else if( j==10 && strncmp(&zLine[i], "block-text", j)==0 ){ + aEntry[nUsed].eType &= ~(CMDFLAG_BOOLEAN); + aEntry[nUsed].eType |= CMDFLAG_BLOCKTEXT; + }else if( j==11 && strncmp(&zLine[i], "versionable", j)==0 ){ + aEntry[nUsed].eType |= CMDFLAG_VERSIONABLE; + }else if( j==9 && strncmp(&zLine[i], "sensitive", j)==0 ){ + aEntry[nUsed].eType |= CMDFLAG_SENSITIVE; + }else if( j>6 && strncmp(&zLine[i], "width=", 6)==0 ){ + aEntry[nUsed].iWidth = atoi(&zLine[i+6]); + }else if( j>8 && strncmp(&zLine[i], "default=", 8)==0 ){ + aEntry[nUsed].zDflt = string_dup(&zLine[i+8], j-8); + }else if( j>9 && strncmp(&zLine[i], "variable=", 9)==0 ){ + aEntry[nUsed].zVar = string_dup(&zLine[i+9], j-9); + }else{ + fprintf(stderr, "%s:%d: unknown option: '%.*s'\n", + zFile, nLine, j, &zLine[i]); + nErr++; + } + } + nUsed++; + return; +} + +/* +** Check to see if the current line is an #if and if it is, add it to +** the zIf[] string. If the current line is an #endif or #else or #elif +** then cancel the current zIf[] string. +*/ +void scan_for_if(const char *zLine){ + int i; + int len; + if( zLine[0]!='#' ) return; + for(i=1; fossil_isspace(zLine[i]); i++){} + if( zLine[i]==0 ) return; + len = strlen(&zLine[i]); + if( strncmp(&zLine[i],"if",2)==0 ){ + zIf[0] = '#'; + memcpy(&zIf[1], &zLine[i], len+1); + }else if( zLine[i]=='e' ){ + zIf[0] = 0; + } +} + +/* +** Check to see if the current line is a "** DEFAULT: ..." line for a +** SETTING definition. If so, remember the default value. +*/ +void scan_for_default(const char *zLine){ + int len; + const char *z; + if( nUsed<1 ) return; + if( (aEntry[nUsed-1].eType & CMDFLAG_SETTING)==0 ) return; + if( strncmp(zLine, "** DEFAULT: ", 12)!=0 ) return; + z = zLine + 12; + while( fossil_isspace(z[0]) ) z++; + len = (int)strlen(z); + while( len>0 && fossil_isspace(z[len-1]) ){ len--; } + aEntry[nUsed-1].zDflt = string_dup(z,len); } /* ** Scan a line for a function that implements a web page or command. */ void scan_for_func(char *zLine){ int i,j,k; char *z; + int isSetting; if( nUsed<=nFixed ) return; - if( strncmp(zLine, "**", 2)==0 && isspace(zLine[2]) - && strlen(zLine)nFixed ){ + if( strncmp(zLine, "**", 2)==0 + && fossil_isspace(zLine[2]) + && strlen(zLine)nFixed + && strncmp(zLine,"** COMMAND:",11)!=0 + && strncmp(zLine,"** WEBPAGE:",11)!=0 + && strncmp(zLine,"** SETTING:",11)!=0 + && strncmp(zLine,"** DEFAULT:",11)!=0 + ){ if( zLine[2]=='\n' ){ zHelp[nHelp++] = '\n'; }else{ if( strncmp(&zLine[3], "Usage: ", 6)==0 ) nHelp = 0; strcpy(&zHelp[nHelp], &zLine[3]); nHelp += strlen(&zHelp[nHelp]); } return; } - for(i=0; isspace(zLine[i]); i++){} + for(i=0; fossil_isspace(zLine[i]); i++){} if( zLine[i]==0 ) return; - if( strncmp(&zLine[i],"void",4)!=0 ){ - if( zLine[i]!='*' ) goto page_skip; - return; - } - i += 4; - if( !isspace(zLine[i]) ) goto page_skip; - while( isspace(zLine[i]) ){ i++; } - for(j=0; isalnum(zLine[i+j]) || zLine[i+j]=='_'; j++){} - if( j==0 ) goto page_skip; - for(k=nHelp-1; k>=0 && isspace(zHelp[k]); k--){} + isSetting = (aEntry[nFixed].eType & CMDFLAG_SETTING)!=0; + if( !isSetting ){ + if( strncmp(&zLine[i],"void",4)!=0 ){ + if( zLine[i]!='*' ) goto page_skip; + return; + } + i += 4; + if( !fossil_isspace(zLine[i]) ) goto page_skip; + while( fossil_isspace(zLine[i]) ){ i++; } + for(j=0; fossil_isident(zLine[i+j]); j++){} + if( j==0 ) goto page_skip; + }else{ + j = 0; + } + for(k=nHelp-1; k>=0 && fossil_isspace(zHelp[k]); k--){} nHelp = k+1; zHelp[nHelp] = 0; - for(k=0; keType - pB->eType; - if( x==0 ){ - x = strcmp(pA->zPath, pB->zPath); - } - return x; + return strcmp(pA->zPath, pB->zPath); } /* ** Build the binary search table. */ void build_table(void){ int i; - int nType0; + int nWeb = 0; + int mxLen = 0; qsort(aEntry, nFixed, sizeof(aEntry[0]), e_compare); - for(i=0; imxLen ) mxLen = n; + if( aEntry[i].zIf ){ + printf("%s", aEntry[i].zIf); + }else if( (aEntry[i].eType & CMDFLAG_WEBPAGE)!=0 ){ + nWeb++; + } + printf(" { \"%.*s\",%*s%s,%*szHelp%03d, 0x%03x },\n", + n, z, + 25-n, "", + aEntry[i].zFunc, + (int)(29-strlen(aEntry[i].zFunc)), "", + aEntry[i].iHelp, + aEntry[i].eType + ); + if( aEntry[i].zIf ) printf("#endif\n"); + } + printf("};\n"); + printf("#define FOSSIL_FIRST_CMD %d\n", nWeb); + printf("#define FOSSIL_MX_CMDNAME %d /* max length of any command name */\n", + mxLen); + + /* Generate the aSetting[] table */ + printf("const Setting aSetting[] = {\n"); + for(i=0; i +#include +#include +#include +#include + +#if defined(_MSC_VER) && (_MSC_VER < 1800) /* MSVS 2013 */ +# define strtoll _strtoi64 +#endif + +static FILE *open_for_reading(const char *zFilename){ + FILE *f = fopen(zFilename, "r"); + if( f==0 ){ + fprintf(stderr, "cannot open \"%s\" for reading\n", zFilename); + exit(1); + } + return f; +} + +/* +** Given an arbitrary-length input string key zIn, generate +** an N-byte hexadecimal hash of that string into zOut. +*/ +static void hash(const char *zIn, int N, char *zOut){ + unsigned char i, j, t; + int m, n; + unsigned char s[256]; + for(m=0; m<256; m++){ s[m] = m; } + for(j=0, m=n=0; m<256; m++, n++){ + j += s[m] + zIn[n]; + if( zIn[n]==0 ){ n = -1; } + t = s[j]; + s[j] = s[m]; + s[m] = t; + } + i = j = 0; + for(n=0; n>4)&0xf]; + zOut[n+1] = "0123456789abcdef"[t&0xf]; + } + zOut[n] = 0; +} + +int main(int argc, char *argv[]){ + FILE *m,*u,*v; + char *z; +#if defined(__DMC__) /* e.g. 0x857 */ + int i = 0; +#endif + int j = 0, x = 0, d = 0; + size_t n; + int vn[3]; + char b[1000]; + char vx[1000]; + if( argc!=4 ){ + fprintf(stderr, "Usage: %s manifest.uuid manifest VERSION\n", argv[0]); + exit(1); + } + memset(b,0,sizeof(b)); + memset(vx,0,sizeof(vx)); + u = open_for_reading(argv[1]); + if( fgets(b, sizeof(b)-1,u)==0 ){ + fprintf(stderr, "malformed manifest.uuid file: %s\n", argv[1]); + exit(1); + } + fclose(u); + for(z=b; z[0] && z[0]!='\r' && z[0]!='\n'; z++){} + *z = 0; + printf("#define MANIFEST_UUID \"%s\"\n",b); + printf("#define MANIFEST_VERSION \"[%10.10s]\"\n",b); + n = strlen(b); + if( n + 50 < sizeof(b) ){ +#ifdef FOSSIL_BUILD_EPOCH +#define str(s) #s + sprintf(b+n, "%d", (int)strtoll(str(FOSSIL_BUILD_EPOCH), 0, 10)); +#else + const char *zEpoch = getenv("SOURCE_DATE_EPOCH"); + if( zEpoch && isdigit(zEpoch[0]) ){ + sprintf(b+n, "%d", (int)strtoll(zEpoch, 0, 10)); + }else{ + sprintf(b+n, "%d", (int)time(0)); + } +#endif + hash(b,33,vx); + printf("#define FOSSIL_BUILD_HASH \"%s\"\n", vx); + } + m = open_for_reading(argv[2]); + while(b == fgets(b, sizeof(b)-1,m)){ + if(0 == strncmp("D ",b,2)){ + int k, n; + char zDateNum[30]; + printf("#define MANIFEST_DATE \"%.10s %.8s\"\n",b+2,b+13); + printf("#define MANIFEST_YEAR \"%.4s\"\n",b+2); + n = 0; + for(k=0; k<10; k++){ + if( isdigit(b[k+2]) ) zDateNum[n++] = b[k+2]; + } + zDateNum[n] = 0; + printf("#define MANIFEST_NUMERIC_DATE %s\n", zDateNum); + n = 0; + for(k=0; k<8; k++){ + if( isdigit(b[k+13]) ) zDateNum[n++] = b[k+13]; + } + zDateNum[n] = 0; + for(k=0; zDateNum[k]=='0'; k++){} + printf("#define MANIFEST_NUMERIC_TIME %s\n", zDateNum+k); + } + } + fclose(m); + v = open_for_reading(argv[3]); + if( fgets(b, sizeof(b)-1,v)==0 ){ + fprintf(stderr, "malformed VERSION file: %s\n", argv[3]); + exit(1); + } + fclose(v); + for(z=b; z[0] && z[0]!='\r' && z[0]!='\n'; z++){} + *z = 0; + printf("#define RELEASE_VERSION \"%s\"\n", b); + z=b; + vn[0] = vn[1] = vn[2] = 0; + while(1){ + if( z[0]>='0' && z[0]<='9' ){ + x = x*10 + z[0] - '0'; + }else{ + if( j<3 ) vn[j++] = x; + x = 0; + if( z[0]==0 ) break; + } + z++; + } + for(z=vx; z[0]=='0'; z++){} + printf("#define RELEASE_VERSION_NUMBER %d%02d%02d\n", vn[0], vn[1], vn[2]); + memset(vx,0,sizeof(vx)); + strcpy(vx,b); + for(z=vx; z[0]; z++){ + if( z[0]=='-' ){ + z[0] = 0; + break; + } + if( z[0]!='.' ) continue; + if ( d<3 ){ + z[0] = ','; + d++; + }else{ + z[0] = '\0'; + break; + } + } + printf("#define RELEASE_RESOURCE_VERSION %s", vx); + while( d<3 ){ printf(",0"); d++; } + printf("\n"); +#if defined(__DMC__) /* e.g. 0x857 */ + d = (__DMC__ & 0xF00) >> 8; /* major */ + x = (__DMC__ & 0x0F0) >> 4; /* minor */ + i = (__DMC__ & 0x00F); /* revision */ + printf("#define COMPILER_VERSION \"%d.%d.%d\"\n", d, x, i); +#elif defined(__POCC__) /* e.g. 700 */ + d = (__POCC__ / 100); /* major */ + x = (__POCC__ % 100); /* minor */ + printf("#define COMPILER_VERSION \"%d.%02d\"\n", d, x); +#elif defined(_MSC_VER) /* e.g. 1800 */ + d = (_MSC_VER / 100); /* major */ + x = (_MSC_VER % 100); /* minor */ + printf("#define COMPILER_VERSION \"%d.%02d\"\n", d, x); +#endif + return 0; +} ADDED src/moderate.c Index: src/moderate.c ================================================================== --- /dev/null +++ src/moderate.c @@ -0,0 +1,229 @@ +/* +** Copyright (c) 2012 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 code used to deal with moderator actions for +** Wiki and Tickets. +*/ +#include "config.h" +#include "moderate.h" +#include + +/* +** Create a table to represent pending moderation requests, if the +** table does not already exist. +*/ +void moderation_table_create(void){ + db_multi_exec( + "CREATE TABLE IF NOT EXISTS repository.modreq(\n" + " objid INTEGER PRIMARY KEY,\n" /* Record pending approval */ + " attachRid INT,\n" /* Object attached */ + " tktid TEXT\n" /* Associated ticket id */ + ");\n" + ); +} + +/* +** Return TRUE if the modreq table exists +*/ +int moderation_table_exists(void){ + return db_table_exists("repository", "modreq"); +} + +/* +** Return TRUE if the object specified is being held for moderation. +*/ +int moderation_pending(int rid){ + static Stmt q; + int rc; + if( rid==0 || !moderation_table_exists() ) return 0; + db_static_prepare(&q, "SELECT 1 FROM modreq WHERE objid=:objid"); + db_bind_int(&q, ":objid", rid); + rc = db_step(&q)==SQLITE_ROW; + db_reset(&q); + return rc; +} + +/* +** If the rid object is being held for moderation, write out +** an "awaiting moderation" message and return true. +** +** If the object is not being held for moderation, simply return +** false without generating any output. +*/ +int moderation_pending_www(int rid){ + int pending = moderation_pending(rid); + if( pending ){ + @ (Awaiting Moderator Approval) + } + return pending; +} + + +/* +** Return TRUE if there any pending moderation requests. +*/ +int moderation_needed(void){ + if( !moderation_table_exists() ) return 0; + return db_exists("SELECT 1 FROM modreq"); +} + +/* +** Check to see if the object identified by RID is used for anything. +*/ +static int object_used(int rid){ + static const char *const aTabField[] = { + "modreq", "attachRid", + "mlink", "mid", + "mlink", "fid", + "tagxref", "srcid", + "tagxref", "rid", + }; + int i; + for(i=0; iAll Pending Moderation Requests + if( moderation_table_exists() ){ + blob_init(&sql, timeline_query_for_www(), -1); + blob_append_sql(&sql, + " AND event.objid IN (SELECT objid FROM modreq)" + " ORDER BY event.mtime DESC" + ); + db_prepare(&q, "%s", blob_sql_text(&sql)); + www_print_timeline(&q, 0, 0, 0, 0, 0, 0, 0); + db_finalize(&q); + } + style_finish_page(); +} + +/* +** Disapproves any entries in the modreq table which belong to any +** user whose name is no longer found in the user table. This is only +** intended to be called after user deletion via /setup_uedit. +** +** To figure out whether a name exists it cross-references +** coalesce(event.euser, event.user) with user.login, limiting the +** selection to event entries where objid matches an entry in the +** modreq table. +** +** This is a no-op if called without g.perm.Admin permissions or if +** moderation_table_exists() returns false. +*/ +void moderation_disapprove_for_missing_users(){ + Stmt q; + if( !g.perm.Admin || !moderation_table_exists() ){ + return; + } + db_begin_transaction(); + db_prepare(&q, + "SELECT objid FROM event WHERE objid IN " + "(SELECT objid FROM modreq) " + "AND coalesce(euser,user) NOT IN " + "(SELECT login FROM user)" + ); + while( db_step(&q)==SQLITE_ROW ){ + int const objid = db_column_int(&q, 0); + moderation_disapprove(objid); + } + db_finalize(&q); + setup_incr_cfgcnt(); + db_end_transaction(0); +} Index: src/name.c ================================================================== --- src/name.c +++ src/name.c @@ -2,11 +2,11 @@ ** Copyright (c) 2006 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: @@ -13,358 +13,1775 @@ ** drh@hwaci.com ** http://www.hwaci.com/drh/ ** ******************************************************************************* ** -** This file contains code used to convert user-supplied object names into -** canonical UUIDs. -** -** A user-supplied object name is any unique prefix of a valid UUID but -** not necessarily in canonical form. +** This file contains code used to resolved user-supplied object names. */ #include "config.h" #include "name.h" #include /* -** This routine takes a user-entered UUID which might be in mixed -** case and might only be a prefix of the full UUID and converts it -** into the full-length UUID in canonical form. -** -** If the input is not a UUID or a UUID prefix, then try to resolve -** the name as a tag. If multiple tags match, pick the latest. -** If the input name matches "tag:*" then always resolve as a tag. -** -** If the input is not a tag, then try to match it as an ISO-8601 date -** string YYYY-MM-DD HH:MM:SS and pick the nearest check-in to that date. -** If the input is of the form "date:*" or "localtime:*" or "utc:*" then -** always resolve the name as a date. -** -** Return 0 on success. Return 1 if the name cannot be resolved. -** Return 2 name is ambiguous. -*/ -int name_to_uuid(Blob *pName, int iErrPriority){ - int rc; - int sz; - sz = blob_size(pName); - if( sz>UUID_SIZE || sz<4 || !validate16(blob_buffer(pName), sz) ){ - char *zUuid; - const char *zName = blob_str(pName); - if( memcmp(zName, "tag:", 4)==0 ){ - zName += 4; - zUuid = tag_to_uuid(zName); - }else{ - zUuid = tag_to_uuid(zName); - if( zUuid==0 ){ - zUuid = date_to_uuid(zName); - } - } - if( zUuid ){ - blob_reset(pName); - blob_append(pName, zUuid, -1); - free(zUuid); - return 0; - } - fossil_error(iErrPriority, "not a valid object name: %s", zName); - return 1; - } - blob_materialize(pName); - canonical16(blob_buffer(pName), sz); - if( sz==UUID_SIZE ){ - rc = db_int(1, "SELECT 0 FROM blob WHERE uuid=%B", pName); - if( rc ){ - fossil_error(iErrPriority, "no such artifact: %b", pName); - blob_reset(pName); - } - }else if( sz=4 ){ - Stmt q; - db_prepare(&q, "SELECT uuid FROM blob WHERE uuid GLOB '%b*'", pName); - if( db_step(&q)!=SQLITE_ROW ){ - char *zUuid; - db_finalize(&q); - zUuid = tag_to_uuid(blob_str(pName)); - if( zUuid ){ - blob_reset(pName); - blob_append(pName, zUuid, -1); - free(zUuid); - return 0; - } - fossil_error(iErrPriority, "no artifacts match the prefix \"%b\"", pName); - return 1; - } - blob_reset(pName); - blob_append(pName, db_column_text(&q, 0), db_column_bytes(&q, 0)); - if( db_step(&q)==SQLITE_ROW ){ - fossil_error(iErrPriority, - "multiple artifacts match" - ); - blob_reset(pName); - db_finalize(&q); - return 2; - } - db_finalize(&q); - rc = 0; - }else{ - rc = 0; - } - return rc; -} - -/* -** Return TRUE if the string begins with an ISO8601 date: YYYY-MM-DD. -*/ -static int is_date(const char *z){ - if( !isdigit(z[0]) ) return 0; - if( !isdigit(z[1]) ) return 0; - if( !isdigit(z[2]) ) return 0; - if( !isdigit(z[3]) ) return 0; - if( z[4]!='-') return 0; - if( !isdigit(z[5]) ) return 0; - if( !isdigit(z[6]) ) return 0; - if( z[7]!='-') return 0; - if( !isdigit(z[8]) ) return 0; - if( !isdigit(z[9]) ) return 0; - return 1; -} - -/* -** Convert a symbolic tag name into the UUID of a check-in that contains -** that tag. If the tag appears on multiple check-ins, return the UUID -** of the most recent check-in with the tag. -** -** If the input string is of the form: -** -** tag:date -** -** Then return the UUID of the oldest check-in with that tag that is -** not older than 'date'. -** -** An input of "tip" returns the most recent check-in. -** -** Memory to hold the returned string comes from malloc() and needs to -** be freed by the caller. -*/ -char *tag_to_uuid(const char *zTag){ - char *zUuid = - db_text(0, - "SELECT blob.uuid" - " FROM tag, tagxref, event, blob" - " WHERE tag.tagname='sym-'||%Q " - " AND tagxref.tagid=tag.tagid AND tagxref.tagtype>0 " - " AND event.objid=tagxref.rid " - " AND blob.rid=event.objid " - " ORDER BY event.mtime DESC ", - zTag - ); - if( zUuid==0 ){ - int nTag = strlen(zTag); - int i; - for(i=0; i0 " - " AND event.objid=tagxref.rid " - " AND blob.rid=event.objid " - " AND event.mtime<=julianday(%Q %s)" - " ORDER BY event.mtime DESC ", - zTagBase, zDate, (useUtc ? "" : ",'utc'") - ); - break; - } - } - if( zUuid==0 && strcmp(zTag, "tip")==0 ){ - zUuid = db_text(0, - "SELECT blob.uuid" - " FROM event, blob" - " WHERE event.type='ci'" - " AND blob.rid=event.objid" - " ORDER BY event.mtime DESC" - ); - } - } - return zUuid; -} - -/* -** Convert a date/time string into a UUID. -** -** Input forms accepted: -** -** date:DATE -** local:DATE -** utc:DATE -** -** The DATE is interpreted as localtime unless the "utc:" prefix is used -** or a "utc" string appears at the end of the DATE string. -*/ -char *date_to_uuid(const char *zDate){ - int useUtc = 0; - int n; - char *zCopy = 0; - char *zUuid; - - if( memcmp(zDate, "date:", 5)==0 ){ - zDate += 5; - }else if( memcmp(zDate, "local:", 6)==0 ){ - zDate += 6; - }else if( memcmp(zDate, "utc:", 4)==0 ){ - zDate += 4; - useUtc = 1; - } - n = strlen(zDate); - if( n<10 || !is_date(zDate) ) return 0; - if( n>4 && sqlite3_strnicmp(&zDate[n-3], "utc", 3)==0 ){ - zCopy = mprintf("%s", zDate); - zCopy[n-3] = 0; - zDate = zCopy; - n -= 3; - useUtc = 1; - } - zUuid = db_text(0, - "SELECT (SELECT uuid FROM blob WHERE rid=event.objid)" - " FROM event" - " WHERE mtime<=julianday(%Q %s) AND type='ci'" - " ORDER BY mtime DESC LIMIT 1", - zDate, useUtc ? "" : ",'utc'" - ); - free(zCopy); - return zUuid; -} - -/* -** COMMAND: test-name-to-id -** -** Convert a name to a full artifact ID. -*/ -void test_name_to_id(void){ - int i; - Blob name; - db_must_be_within_tree(); - for(i=2; i ", g.argv[i]); - if( name_to_uuid(&name, 1) ){ - printf("ERROR: %s\n", g.zErrMsg); - fossil_error_reset(); - }else{ - printf("%s\n", blob_buffer(&name)); - } - blob_reset(&name); - } -} - -/* -** Convert a name to a rid. If the name is a small integer value then -** just use atoi() to do the conversion. If the name contains alphabetic -** characters or is not an existing rid, then use name_to_uuid then -** convert the uuid to a rid. -** -** This routine is used by command-line routines to resolve command-line inputs -** into a rid. -*/ -int name_to_rid(const char *zName){ - int i; - int rid; - Blob name; - - if( zName==0 || zName[0]==0 ) return 0; - blob_init(&name, zName, -1); - if( name_to_uuid(&name, -1) ){ - blob_reset(&name); - for(i=0; zName[i] && isdigit(zName[i]); i++){} - if( zName[i]==0 ){ - rid = atoi(zName); - if( db_exists("SELECT 1 FROM blob WHERE rid=%d", rid) ){ - return rid; - } - } - fossil_error(1, "no such artifact: %s", zName); - return 0; - }else{ - rid = db_int(0, "SELECT rid FROM blob WHERE uuid=%B", &name); - blob_reset(&name); - } - return rid; -} - -/* -** WEBPAGE: ambiguous -** URL: /ambiguous?name=UUID&src=WEBPAGE -** -** The UUID given by the name paramager is ambiguous. Display a page -** that shows all possible choices and let the user select between them. -*/ -void ambiguous_page(void){ - Stmt q; - const char *zName = P("name"); - const char *zSrc = P("src"); - char *z; - - if( zName==0 || zName[0]==0 || zSrc==0 || zSrc[0]==0 ){ - fossil_redirect_home(); - } - style_header("Ambiguous Artifact ID"); - @

    The artifact id %h(zName) is ambiguous and might +** 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. +*/ +int fossil_isdate(const char *z){ + if( !fossil_isdigit(z[0]) ) return 0; + if( !fossil_isdigit(z[1]) ) return 0; + if( !fossil_isdigit(z[2]) ) return 0; + if( !fossil_isdigit(z[3]) ) return 0; + if( z[4]!='-') return 0; + if( !fossil_isdigit(z[5]) ) return 0; + if( !fossil_isdigit(z[6]) ) return 0; + if( z[7]!='-') return 0; + if( !fossil_isdigit(z[8]) ) return 0; + if( !fossil_isdigit(z[9]) ) return 0; + return 1; +} + +/* +** Check to see if the string might be a compact date/time that omits +** the punctuation. Example: "20190327084549" instead of +** "2019-03-27 08:45:49". If the string is of the appropriate form, +** then return an alternative string (in static space) that is the same +** string with punctuation inserted. +** +** If the bVerifyNotAHash flag is true, then a check is made to see if +** the string is a hash prefix and NULL is returned if it is. If the +** bVerifyNotAHash flag is false, then the result is determined by syntax +** of the input string only, without reference to the artifact table. +*/ +char *fossil_expand_datetime(const char *zIn, int bVerifyNotAHash){ + static char zEDate[20]; + static const char aPunct[] = { 0, 0, '-', '-', ' ', ':', ':' }; + int n = (int)strlen(zIn); + int i, j; + + /* Only three forms allowed: + ** (1) YYYYMMDD + ** (2) YYYYMMDDHHMM + ** (3) YYYYMMDDHHMMSS + */ + if( n!=8 && n!=12 && n!=14 ) return 0; + + /* Every character must be a digit */ + for(i=0; fossil_isdigit(zIn[i]); i++){} + if( i!=n ) return 0; + + /* Expand the date */ + for(i=j=0; zIn[i]; i++){ + if( i>=4 && (i%2)==0 ){ + zEDate[j++] = aPunct[i/2]; + } + zEDate[j++] = zIn[i]; + } + zEDate[j] = 0; + + /* Check for reasonable date values. + ** Offset references: + ** YYYY-MM-DD HH:MM:SS + ** 0123456789 12345678 + */ + + i = atoi(zEDate); + if( i<1970 || i>2100 ) return 0; + i = atoi(zEDate+5); + if( i<1 || i>12 ) return 0; + i = atoi(zEDate+8); + if( i<1 || i>31 ) return 0; + if( n>8 ){ + i = atoi(zEDate+11); + if( i>24 ) return 0; + i = atoi(zEDate+14); + if( i>60 ) return 0; + if( n==14 && atoi(zEDate+17)>60 ) return 0; + } + + /* The string is not also a hash prefix */ + if( bVerifyNotAHash ){ + if( db_exists("SELECT 1 FROM blob WHERE uuid GLOB '%q*'",zIn) ) return 0; + } + + /* It looks like this may be a date. Return it with punctuation added. */ + return zEDate; +} + +/* +** The data-time string in the argument is going to be used as an +** upper bound like this: mtime<=julianday(zDate,'localtime'). +** But if the zDate parameter omits the fractional seconds or the +** seconds, or the time, that might mess up the == part of the +** comparison. So add in missing factional seconds or seconds or time. +** +** The returned string is held in a static buffer that is overwritten +** with each call, or else is just a copy of its input if there are +** no changes. +*/ +const char *fossil_roundup_date(const char *zDate){ + static char zUp[24]; + int n = (int)strlen(zDate); + if( n==19 ){ /* YYYY-MM-DD HH:MM:SS */ + memcpy(zUp, zDate, 19); + memcpy(zUp+19, ".999", 5); + return zUp; + } + if( n==16 ){ /* YYYY-MM-DD HH:MM */ + memcpy(zUp, zDate, 16); + memcpy(zUp+16, ":59.999", 8); + return zUp; + } + if( n==10 ){ /* YYYY-MM-DD */ + memcpy(zUp, zDate, 10); + memcpy(zUp+10, " 23:59:59.999", 14); + return zUp; + } + return zDate; +} + + +/* +** Return the RID that is the "root" of the branch that contains +** check-in "rid". Details depending on eType: +** +** eType==0 The check-in of the parent branch off of which +** the branch containing RID originally diverged. +** +** eType==1 The first check-in of the branch that contains RID. +** +** eType==2 The youngest ancestor of RID that is on the branch +** from which the branch containing RID diverged. +*/ +int start_of_branch(int rid, int eType){ + Stmt q; + int rc; + int ans = rid; + char *zBr = branch_of_rid(rid); + db_prepare(&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=:cid AND isprim", + TAG_BRANCH, zBr + ); + fossil_free(zBr); + do{ + db_reset(&q); + db_bind_int(&q, ":cid", ans); + rc = db_step(&q); + if( rc!=SQLITE_ROW ) break; + if( eType==1 && db_column_int(&q,1)==0 ) break; + ans = db_column_int(&q, 0); + }while( db_column_int(&q, 1)==1 && ans>0 ); + db_finalize(&q); + if( eType==2 && ans>0 ){ + zBr = branch_of_rid(ans); + ans = compute_youngest_ancestor_in_branch(rid, zBr); + fossil_free(zBr); + } + return ans; +} + +/* +** 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 int most_recent_event_with_tag(const char *zTag, const char *zType){ + return db_int(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 + ); +} + + +/* +** Convert a symbolic name into a RID. Acceptable forms: +** +** * artifact hash (optionally enclosed in [...]) +** * 4-character or larger prefix of a artifact +** * Symbolic Name +** * "tag:" + symbolic name +** * Date or date-time +** * "date:" + Date or date-time +** * symbolic-name ":" date-time +** * "tip" +** +** The following additional forms are available in local checkouts: +** +** * "current" +** * "prev" or "previous" +** * "next" +** +** The following modifier prefixes may be applied to the above forms: +** +** * "root:BR" = The origin of the branch named BR. +** * "merge-in:BR" = The most recent merge-in for the branch named BR. +** +** In those forms, BR may be any symbolic form but is assumed to be a +** checkin. Thus root:2021-02-01 would resolve to a checkin, possibly +** in a branch and possibly in the trunk, but never a wiki edit or +** forum post. +** +** Return the RID of the matching artifact. Or return 0 if the name does not +** match any known object. Or return -1 if the name is ambiguous. +** +** The zType parameter specifies the type of artifact: ci, t, w, e, g, f. +** If zType is NULL or "" or "*" then any type of artifact will serve. +** If zType is "br" then find the first check-in of the named branch +** rather than the last. +** +** zType is "ci" in most use cases since we are usually searching for +** a check-in. +** +** Note that the input zTag for types "t" and "e" is the artifact hash of +** the ticket-change or technote-change artifact, not the randomly generated +** hexadecimal identifier assigned to tickets and events. Those identifiers +** live in a separate namespace. +*/ +int symbolic_name_to_rid(const char *zTag, const char *zType){ + int vid; + int rid = 0; + int nTag; + int i; + int startOfBranch = 0; + const char *zXTag; /* zTag with optional [...] removed */ + int nXTag; /* Size of zXTag */ + const char *zDate; /* Expanded date-time string */ + + if( zType==0 || zType[0]==0 ){ + zType = "*"; + }else if( zType[0]=='b' ){ + zType = "ci"; + startOfBranch = 1; + } + if( zTag==0 || zTag[0]==0 ) return 0; + + /* special keyword: "tip" */ + if( fossil_strcmp(zTag, "tip")==0 && (zType[0]=='*' || zType[0]=='c') ){ + rid = db_int(0, + "SELECT objid" + " FROM event" + " WHERE type='ci'" + " ORDER BY event.mtime DESC" + ); + if( rid ) return rid; + } + + /* special keywords: "prev", "previous", "current", and "next" */ + if( g.localOpen && (vid=db_lget_int("checkout",0))!=0 ){ + if( fossil_strcmp(zTag, "current")==0 ){ + rid = vid; + }else if( fossil_strcmp(zTag, "prev")==0 + || fossil_strcmp(zTag, "previous")==0 ){ + rid = db_int(0, "SELECT pid FROM plink WHERE cid=%d AND isprim", vid); + }else if( fossil_strcmp(zTag, "next")==0 ){ + rid = db_int(0, "SELECT cid FROM plink WHERE pid=%d" + " ORDER BY isprim DESC, mtime DESC", vid); + } + if( rid ) return rid; + } + + /* Date and times */ + if( memcmp(zTag, "date:", 5)==0 ){ + zDate = fossil_expand_datetime(&zTag[5],0); + if( zDate==0 ) zDate = &zTag[5]; + rid = db_int(0, + "SELECT objid FROM event" + " WHERE mtime<=julianday(%Q,fromLocal()) AND type GLOB '%q'" + " ORDER BY mtime DESC LIMIT 1", + fossil_roundup_date(zDate), zType); + return rid; + } + if( fossil_isdate(zTag) ){ + rid = db_int(0, + "SELECT objid FROM event" + " WHERE mtime<=julianday(%Q,fromLocal()) AND type GLOB '%q'" + " ORDER BY mtime DESC LIMIT 1", + fossil_roundup_date(zTag), zType); + if( rid) return rid; + } + + /* Deprecated date & time formats: "local:" + date-time and + ** "utc:" + date-time */ + if( memcmp(zTag, "local:", 6)==0 ){ + rid = db_int(0, + "SELECT objid FROM event" + " WHERE mtime<=julianday(%Q) AND type GLOB '%q'" + " ORDER BY mtime DESC LIMIT 1", + &zTag[6], zType); + return rid; + } + if( memcmp(zTag, "utc:", 4)==0 ){ + rid = db_int(0, + "SELECT objid FROM event" + " WHERE mtime<=julianday('%qz') AND type GLOB '%q'" + " ORDER BY mtime DESC LIMIT 1", + fossil_roundup_date(&zTag[4]), zType); + return rid; + } + + /* "tag:" + symbolic-name */ + if( memcmp(zTag, "tag:", 4)==0 ){ + rid = most_recent_event_with_tag(&zTag[4], zType); + if( startOfBranch ) rid = start_of_branch(rid,1); + return rid; + } + + /* root:BR -> The origin of the branch named BR */ + if( strncmp(zTag, "root:", 5)==0 ){ + rid = symbolic_name_to_rid(zTag+5, zType); + return start_of_branch(rid, 0); + } + /* merge-in:BR -> Most recent merge-in for the branch name BR */ + if( strncmp(zTag, "merge-in:", 9)==0 ){ + rid = symbolic_name_to_rid(zTag+9, zType); + return start_of_branch(rid, 2); + } + + /* symbolic-name ":" date-time */ + nTag = strlen(zTag); + for(i=0; i0 " + " AND event.objid=tagxref.rid " + " AND event.mtime<=julianday(%Q,fromLocal())" + " AND event.type GLOB '%q'", + zTagBase, fossil_roundup_date(zXDate), zType + ); + fossil_free(zDate); + fossil_free(zTagBase); + return rid; + } + + /* Remove optional [...] */ + zXTag = zTag; + nXTag = nTag; + if( zXTag[0]=='[' ){ + zXTag++; + nXTag--; + } + if( nXTag>0 && zXTag[nXTag-1]==']' ){ + nXTag--; + } + + /* artifact hash or prefix */ + if( nXTag>=4 && nXTag<=HNAME_MAX && validate16(zXTag, nXTag) ){ + Stmt q; + char zUuid[HNAME_MAX+1]; + memcpy(zUuid, zXTag, nXTag); + zUuid[nXTag] = 0; + canonical16(zUuid, nXTag); + rid = 0; + if( zType[0]=='*' ){ + db_prepare(&q, "SELECT rid FROM blob WHERE uuid GLOB '%q*'", zUuid); + }else{ + db_prepare(&q, + "SELECT blob.rid" + " FROM blob CROSS JOIN event" + " WHERE blob.uuid GLOB '%q*'" + " AND event.objid=blob.rid" + " AND event.type GLOB '%q'", + zUuid, zType + ); + } + if( db_step(&q)==SQLITE_ROW ){ + rid = db_column_int(&q, 0); + if( db_step(&q)==SQLITE_ROW ) rid = -1; + } + db_finalize(&q); + if( rid ) return rid; + } + + if( zType[0]=='w' ){ + rid = db_int(0, + "SELECT event.objid, max(event.mtime)" + " FROM tag, tagxref, event" + " WHERE tag.tagname='wiki-%q' " + " AND tagxref.tagid=tag.tagid AND tagxref.tagtype>0 " + " AND event.objid=tagxref.rid " + " AND event.type GLOB '%q'", + zTag, zType + ); + }else{ + rid = most_recent_event_with_tag(zTag, zType); + } + + if( rid>0 ){ + if( startOfBranch ) rid = start_of_branch(rid,1); + return rid; + } + + /* Pure numeric date/time */ + zDate = fossil_expand_datetime(zTag, 0); + if( zDate ){ + rid = db_int(0, + "SELECT objid FROM event" + " WHERE mtime<=julianday(%Q,fromLocal()) AND type GLOB '%q'" + " ORDER BY mtime DESC LIMIT 1", + fossil_roundup_date(zDate), zType); + if( rid) return rid; + } + + + /* Undocumented: numeric tags get translated directly into the RID */ + if( memcmp(zTag, "rid:", 4)==0 ){ + zTag += 4; + for(i=0; fossil_isdigit(zTag[i]); i++){} + if( zTag[i]==0 ){ + if( strcmp(zType,"*")==0 ){ + rid = atoi(zTag); + }else{ + rid = db_int(0, + "SELECT event.objid" + " FROM event" + " WHERE event.objid=%s" + " AND event.type GLOB '%q'", zTag /*safe-for-%s*/, zType); + } + } + } + return rid; +} + +/* +** This routine takes a user-entered string and tries to convert it to +** an artifact hash. +** +** We first try to treat the string as an artifact hash, or at least a +** unique prefix of an artifact hash. The input may be in mixed case. +** If we are passed such a string, this routine has the effect of +** converting the hash [prefix] to canonical form. +** +** If the input is not a hash or a hash prefix, then try to resolve +** the name as a tag. If multiple tags match, pick the latest. +** A caller can force this routine to skip the hash case above by +** prefixing the string with "tag:", a useful property when the tag +** may be misinterpreted as a hex ASCII string. (e.g. "decade" or "facade") +** +** If the input is not a tag, then try to match it as an ISO-8601 date +** string YYYY-MM-DD HH:MM:SS and pick the nearest check-in to that date. +** If the input is of the form "date:*" then always resolve the name as +** a date. The forms "utc:*" and "local:" are deprecated. +** +** Return 0 on success. Return 1 if the name cannot be resolved. +** Return 2 name is ambiguous. +*/ +int name_to_uuid(Blob *pName, int iErrPriority, const char *zType){ + char *zName = blob_str(pName); + int rid = symbolic_name_to_rid(zName, zType); + if( rid<0 ){ + fossil_error(iErrPriority, "ambiguous name: %s", zName); + return 2; + }else if( rid==0 ){ + fossil_error(iErrPriority, "not found: %s", zName); + return 1; + }else{ + blob_reset(pName); + db_blob(pName, "SELECT uuid FROM blob WHERE rid=%d", rid); + return 0; + } +} + +/* +** This routine is similar to name_to_uuid() except in the form it +** takes its parameters and returns its value, and in that it does not +** treat errors as fatal. zName must be an artifact hash or prefix of +** a hash. zType is also as described for name_to_uuid(). If +** zName does not resolve, 0 is returned. If it is ambiguous, a +** negative value is returned. On success the rid is returned and +** pUuid (if it is not NULL) is set to a newly-allocated string, +** the full hash, which must eventually be free()d by the caller. +*/ +int name_to_uuid2(const char *zName, const char *zType, char **pUuid){ + int rid = symbolic_name_to_rid(zName, zType); + if((rid>0) && pUuid){ + *pUuid = db_text(NULL, "SELECT uuid FROM blob WHERE rid=%d", rid); + } + return rid; +} + + +/* +** name_collisions searches through events, blobs, and tickets for +** collisions of a given hash based on its length, counting only +** hashes greater than or equal to 4 hex ASCII characters (16 bits) +** in length. +*/ +int name_collisions(const char *zName){ + int c = 0; /* count of collisions for zName */ + int nLen; /* length of zName */ + nLen = strlen(zName); + if( nLen>=4 && nLen<=HNAME_MAX && validate16(zName, nLen) ){ + c = db_int(0, + "SELECT" + " (SELECT count(*) FROM ticket" + " WHERE tkt_uuid GLOB '%q*') +" + " (SELECT count(*) FROM tag" + " WHERE tagname GLOB 'event-%q*') +" + " (SELECT count(*) FROM blob" + " WHERE uuid GLOB '%q*');", + zName, zName, zName + ); + if( c<2 ) c = 0; + } + return c; +} + +/* +** COMMAND: test-name-to-id +** +** Usage: %fossil test-name-to-id [--count N] NAME +** +** Convert a NAME to a full artifact ID. Repeat the conversion N +** times (for timing purposes) if the --count option is given. +*/ +void test_name_to_id(void){ + int i; + int n = 0; + Blob name; + db_must_be_within_tree(); + for(i=2; i ", g.argv[i]); + if( name_to_uuid(&name, 1, "*") ){ + fossil_print("ERROR: %s\n", g.zErrMsg); + fossil_error_reset(); + }else{ + fossil_print("%s\n", blob_buffer(&name)); + } + blob_reset(&name); + }while( n-- > 0 ); + } +} + +/* +** Convert a name to a rid. If the name can be any of the various forms +** accepted: +** +** * artifact hash or prefix thereof +** * symbolic name +** * date +** * label:date +** * prev, previous +** * next +** * tip +** +** This routine is used by command-line routines to resolve command-line inputs +** into a rid. +*/ +int name_to_typed_rid(const char *zName, const char *zType){ + int rid; + + if( zName==0 || zName[0]==0 ) return 0; + rid = symbolic_name_to_rid(zName, zType); + if( rid<0 ){ + fossil_fatal("ambiguous name: %s", zName); + }else if( rid==0 ){ + fossil_fatal("not found: %s", zName); + } + return rid; +} +int name_to_rid(const char *zName){ + return name_to_typed_rid(zName, "*"); +} + +/* +** WEBPAGE: ambiguous +** URL: /ambiguous?name=NAME&src=WEBPAGE +** +** The NAME given by the name parameter is ambiguous. Display a page +** that shows all possible choices and let the user select between them. +** +** The src= query parameter is optional. If omitted it defaults +** to "info". +*/ +void ambiguous_page(void){ + Stmt q; + const char *zName = P("name"); + const char *zSrc = PD("src","info"); + char *z; + + if( zName==0 || zName[0]==0 || zSrc==0 || zSrc[0]==0 ){ + fossil_redirect_home(); + } + style_header("Ambiguous Artifact ID"); + @

    The artifact hash prefix %h(zName) is ambiguous and might @ mean any of the following: @

      z = mprintf("%s", zName); canonical16(z, strlen(z)); db_prepare(&q, "SELECT uuid, rid FROM blob WHERE uuid GLOB '%q*'", z); while( db_step(&q)==SQLITE_ROW ){ const char *zUuid = db_column_text(&q, 0); int rid = db_column_int(&q, 1); - @
    1. - @ %S(zUuid) - - object_description(rid, 0, 0); + @

    2. + @ %s(zUuid) - + object_description(rid, 0, 0, 0); + @

    3. + } + db_finalize(&q); + db_prepare(&q, + " SELECT tkt_rid, tkt_uuid, title" + " FROM ticket, ticketchng" + " WHERE ticket.tkt_id = ticketchng.tkt_id" + " AND tkt_uuid GLOB '%q*'" + " GROUP BY tkt_uuid" + " ORDER BY tkt_ctime DESC", z); + while( db_step(&q)==SQLITE_ROW ){ + int rid = db_column_int(&q, 0); + const char *zUuid = db_column_text(&q, 1); + const char *zTitle = db_column_text(&q, 2); + @
    4. + @ %s(zUuid) - + @

        + @ Ticket + hyperlink_to_version(zUuid); + @ - %h(zTitle). + @
        • + object_description(rid, 0, 0, 0); + @
        + @

      • + } + db_finalize(&q); + db_prepare(&q, + "SELECT rid, uuid FROM" + " (SELECT tagxref.rid AS rid, substr(tagname, 7) AS uuid" + " FROM tagxref, tag WHERE tagxref.tagid = tag.tagid" + " AND tagname GLOB 'event-%q*') GROUP BY uuid", z); + while( db_step(&q)==SQLITE_ROW ){ + int rid = db_column_int(&q, 0); + const char* zUuid = db_column_text(&q, 1); + @
      • + @ %s(zUuid) - + @

        • + object_description(rid, 0, 0, 0); + @
        @

      • } @
      - style_footer(); + db_finalize(&q); + style_finish_page(); } /* ** Convert the name in CGI parameter zParamName into a rid and return that ** rid. If the CGI parameter is missing or is not a valid artifact tag, ** return 0. If the CGI parameter is ambiguous, redirect to a page that ** shows all possibilities and do not return. */ int name_to_rid_www(const char *zParamName){ - int i, rc; int rid; const char *zName = P(zParamName); - Blob name; - +#ifdef FOSSIL_ENABLE_JSON + if(!zName && fossil_has_json()){ + zName = json_find_option_cstr(zParamName,NULL,NULL); + } +#endif if( zName==0 || zName[0]==0 ) return 0; - blob_init(&name, zName, -1); - rc = name_to_uuid(&name, -1); - if( rc==1 ){ - blob_reset(&name); - for(i=0; zName[i] && isdigit(zName[i]); i++){} - if( zName[i]==0 ){ - rid = atoi(zName); - if( db_exists("SELECT 1 FROM blob WHERE rid=%d", rid) ){ - return rid; - } - } - return 0; - }else if( rc==2 ){ - cgi_redirectf("%s/ambiguous/%T?src=%t", g.zTop, zName, g.zPath); - return 0; - }else{ - rid = db_int(0, "SELECT rid FROM blob WHERE uuid=%B", &name); - blob_reset(&name); + rid = symbolic_name_to_rid(zName, "*"); + if( rid<0 ){ + cgi_redirectf("%R/ambiguous/%T?src=%t", zName, g.zPath); + rid = 0; } return rid; } +/* +** Given an RID of a structural artifact, which is assumed to be +** valid, this function returns one of the CFTYPE_xxx values +** describing the record type, or 0 if the RID does not refer to an +** artifact record (as determined by reading the event table). +** +** Note that this function never returns CFTYPE_ATTACHMENT or +** CFTYPE_CLUSTER because those are not used in the event table. Thus +** it cannot be used to distinguish those artifact types from +** non-artifact file content. +** +** Potential TODO: if the rid is not found in the timeline, try to +** match it to a client file and return a hypothetical new +** CFTYPE_OPAQUE or CFTYPE_NONARTIFACT if a match is found. +*/ +int whatis_rid_type(int rid){ + Stmt q = empty_Stmt; + int type = 0; + /* Check for entries on the timeline that reference this object */ + db_prepare(&q, + "SELECT type FROM event WHERE objid=%d", rid); + if( db_step(&q)==SQLITE_ROW ){ + switch( db_column_text(&q,0)[0] ){ + case 'c': type = CFTYPE_MANIFEST; break; + case 'w': type = CFTYPE_WIKI; break; + case 'e': type = CFTYPE_EVENT; break; + case 'f': type = CFTYPE_FORUM; break; + case 't': type = CFTYPE_TICKET; break; + case 'g': type = CFTYPE_CONTROL; break; + default: break; + } + } + db_finalize(&q); + return type; +} + +/* +** A proxy for whatis_rid_type() which returns a brief string (in +** static memory) describing the record type. Returns NULL if rid does +** not refer to an artifact record (as determined by reading the event +** table). The returned string is intended to be used in headers which +** can refer to different artifact types. It is not "definitive," in +** that it does not distinguish between closely-related types like +** wiki creation, edit, and removal. +*/ +char const * whatis_rid_type_label(int rid){ + char const * zType = 0; + switch( whatis_rid_type(rid) ){ + case CFTYPE_MANIFEST: zType = "Check-in"; break; + case CFTYPE_WIKI: zType = "Wiki-edit"; break; + case CFTYPE_EVENT: zType = "Technote"; break; + case CFTYPE_FORUM: zType = "Forum-post"; break; + case CFTYPE_TICKET: zType = "Ticket-change"; break; + case CFTYPE_CONTROL: zType = "Tag-change"; break; + default: break; + } + return zType; +} + +/* +** Generate a description of artifact "rid" +*/ +void whatis_rid(int rid, int verboseFlag){ + Stmt q; + int cnt; + + /* Basic information about the object. */ + db_prepare(&q, + "SELECT uuid, size, datetime(mtime,toLocal()), ipaddr" + " FROM blob, rcvfrom" + " WHERE rid=%d" + " AND rcvfrom.rcvid=blob.rcvid", + rid); + if( db_step(&q)==SQLITE_ROW ){ + if( verboseFlag ){ + fossil_print("artifact: %s (%d)\n", db_column_text(&q,0), rid); + fossil_print("size: %d bytes\n", db_column_int(&q,1)); + fossil_print("received: %s from %s\n", + db_column_text(&q, 2), + db_column_text(&q, 3)); + }else{ + fossil_print("artifact: %s\n", db_column_text(&q,0)); + fossil_print("size: %d bytes\n", db_column_int(&q,1)); + } + } + db_finalize(&q); + + /* Report any symbolic tags on this artifact */ + db_prepare(&q, + "SELECT substr(tagname,5)" + " FROM tag JOIN tagxref ON tag.tagid=tagxref.tagid" + " WHERE tagxref.rid=%d" + " AND tagxref.tagtype<>0" + " AND tagname GLOB 'sym-*'" + " ORDER BY 1", + rid + ); + cnt = 0; + while( db_step(&q)==SQLITE_ROW ){ + const char *zPrefix = cnt++ ? ", " : "tags: "; + fossil_print("%s%s", zPrefix, db_column_text(&q,0)); + } + if( cnt ) fossil_print("\n"); + db_finalize(&q); + + /* Report any HIDDEN, PRIVATE, CLUSTER, or CLOSED tags on this artifact */ + db_prepare(&q, + "SELECT tagname" + " FROM tag JOIN tagxref ON tag.tagid=tagxref.tagid" + " WHERE tagxref.rid=%d" + " AND tag.tagid IN (5,6,7,9)" + " ORDER BY 1", + rid + ); + cnt = 0; + while( db_step(&q)==SQLITE_ROW ){ + const char *zPrefix = cnt++ ? ", " : "raw-tags: "; + fossil_print("%s%s", zPrefix, db_column_text(&q,0)); + } + if( cnt ) fossil_print("\n"); + db_finalize(&q); + + /* Check for entries on the timeline that reference this object */ + db_prepare(&q, + "SELECT type, datetime(mtime,toLocal())," + " coalesce(euser,user), coalesce(ecomment,comment)" + " FROM event WHERE objid=%d", rid); + if( db_step(&q)==SQLITE_ROW ){ + const char *zType; + switch( db_column_text(&q,0)[0] ){ + case 'c': zType = "Check-in"; break; + case 'w': zType = "Wiki-edit"; break; + case 'e': zType = "Technote"; break; + case 'f': zType = "Forum-post"; break; + case 't': zType = "Ticket-change"; break; + case 'g': zType = "Tag-change"; break; + default: zType = "Unknown"; break; + } + fossil_print("type: %s by %s on %s\n", zType, db_column_text(&q,2), + db_column_text(&q, 1)); + fossil_print("comment: "); + comment_print(db_column_text(&q,3), 0, 12, -1, get_comment_format()); + cnt++; + } + db_finalize(&q); + + /* Check to see if this object is used as a file in a check-in */ + db_prepare(&q, + "SELECT filename.name, blob.uuid, datetime(event.mtime,toLocal())," + " coalesce(euser,user), coalesce(ecomment,comment)" + " FROM mlink, filename, blob, event" + " WHERE mlink.fid=%d" + " AND filename.fnid=mlink.fnid" + " AND event.objid=mlink.mid" + " AND blob.rid=mlink.mid" + " ORDER BY event.mtime DESC /*sort*/", + rid); + while( db_step(&q)==SQLITE_ROW ){ + fossil_print("file: %s\n", db_column_text(&q,0)); + fossil_print(" part of [%S] by %s on %s\n", + db_column_text(&q, 1), + db_column_text(&q, 3), + db_column_text(&q, 2)); + fossil_print(" "); + comment_print(db_column_text(&q,4), 0, 12, -1, get_comment_format()); + cnt++; + } + db_finalize(&q); + + /* Check to see if this object is used as an attachment */ + db_prepare(&q, + "SELECT attachment.filename," + " attachment.comment," + " attachment.user," + " datetime(attachment.mtime,toLocal())," + " attachment.target," + " CASE WHEN EXISTS(SELECT 1 FROM tag WHERE tagname=('tkt-'||target))" + " THEN 'ticket'" + " WHEN EXISTS(SELECT 1 FROM tag WHERE tagname=('wiki-'||target))" + " THEN 'wiki' END," + " attachment.attachid," + " (SELECT uuid FROM blob WHERE rid=attachid)" + " FROM attachment JOIN blob ON attachment.src=blob.uuid" + " WHERE blob.rid=%d", + rid + ); + while( db_step(&q)==SQLITE_ROW ){ + fossil_print("attachment: %s\n", db_column_text(&q,0)); + fossil_print(" attached to %s %s\n", + db_column_text(&q,5), db_column_text(&q,4)); + if( verboseFlag ){ + fossil_print(" via %s (%d)\n", + db_column_text(&q,7), db_column_int(&q,6)); + }else{ + fossil_print(" via %s\n", + db_column_text(&q,7)); + } + fossil_print(" by user %s on %s\n", + db_column_text(&q,2), db_column_text(&q,3)); + fossil_print(" "); + comment_print(db_column_text(&q,1), 0, 12, -1, get_comment_format()); + cnt++; + } + db_finalize(&q); + + /* If other information available, try to describe the object */ + if( cnt==0 ){ + char *zWhere = mprintf("=%d", rid); + char *zDesc; + describe_artifacts(zWhere); + free(zWhere); + zDesc = db_text(0, + "SELECT printf('%%-12s%%s %%s',type||':',summary,substr(ref,1,16))" + " FROM description WHERE rid=%d", rid); + fossil_print("%s\n", zDesc); + fossil_free(zDesc); + } +} + +/* +** COMMAND: whatis* +** +** Usage: %fossil whatis NAME +** +** Resolve the symbol NAME into its canonical artifact hash +** artifact name and provide a description of what role that artifact +** plays. +** +** Options: +** +** --type TYPE Only find artifacts of TYPE (one of: 'ci', 't', +** 'w', 'g', or 'e') +** -v|--verbose Provide extra information (such as the RID) +*/ +void whatis_cmd(void){ + int rid; + const char *zName; + int verboseFlag; + int i; + const char *zType = 0; + db_find_and_open_repository(0,0); + verboseFlag = find_option("verbose","v",0)!=0; + zType = find_option("type",0,1); + + /* We should be done with options.. */ + verify_all_options(); + + if( g.argc<3 ) usage("NAME ..."); + for(i=2; i2 ) fossil_print("%.79c\n",'-'); + rid = symbolic_name_to_rid(zName, zType); + if( rid<0 ){ + Stmt q; + int cnt = 0; + fossil_print("name: %s (ambiguous)\n", zName); + db_prepare(&q, + "SELECT rid FROM blob WHERE uuid>=lower(%Q) AND uuid<(lower(%Q)||'z')", + zName, zName + ); + while( db_step(&q)==SQLITE_ROW ){ + if( cnt++ ) fossil_print("%12s---- meaning #%d ----\n", " ", cnt); + whatis_rid(db_column_int(&q, 0), verboseFlag); + } + db_finalize(&q); + }else if( rid==0 ){ + /* 0123456789 12 */ + fossil_print("unknown: %s\n", zName); + }else{ + fossil_print("name: %s\n", zName); + whatis_rid(rid, verboseFlag); + } + } +} + +/* +** COMMAND: test-whatis-all +** +** Usage: %fossil test-whatis-all +** +** Show "whatis" information about every artifact in the repository +*/ +void test_whatis_all_cmd(void){ + Stmt q; + int cnt = 0; + db_find_and_open_repository(0,0); + db_prepare(&q, "SELECT rid FROM blob ORDER BY rid"); + while( db_step(&q)==SQLITE_ROW ){ + if( cnt++ ) fossil_print("%.79c\n", '-'); + whatis_rid(db_column_int(&q,0), 1); + } + db_finalize(&q); +} + + +/* +** COMMAND: test-ambiguous +** +** Usage: %fossil test-ambiguous [--minsize N] +** +** Show a list of ambiguous artifact hash abbreviations of N characters or +** more where N defaults to 4. Change N to a different value using +** the "--minsize N" command-line option. +*/ +void test_ambiguous_cmd(void){ + Stmt q, ins; + int i; + int minSize = 4; + const char *zMinsize; + char zPrev[100]; + db_find_and_open_repository(0,0); + zMinsize = find_option("minsize",0,1); + if( zMinsize && atoi(zMinsize)>0 ) minSize = atoi(zMinsize); + db_multi_exec("CREATE TEMP TABLE dups(uuid, cnt)"); + db_prepare(&ins,"INSERT INTO dups(uuid) VALUES(substr(:uuid,1,:cnt))"); + db_prepare(&q, + "SELECT uuid FROM blob " + "UNION " + "SELECT substr(tagname,7) FROM tag WHERE tagname GLOB 'event-*' " + "UNION " + "SELECT tkt_uuid FROM ticket " + "ORDER BY 1" + ); + zPrev[0] = 0; + while( db_step(&q)==SQLITE_ROW ){ + const char *zUuid = db_column_text(&q, 0); + for(i=0; zUuid[i]==zPrev[i] && zUuid[i]!=0; i++){} + if( i>=minSize ){ + db_bind_int(&ins, ":cnt", i); + db_bind_text(&ins, ":uuid", zUuid); + db_step(&ins); + db_reset(&ins); + } + sqlite3_snprintf(sizeof(zPrev), zPrev, "%s", zUuid); + } + db_finalize(&ins); + db_finalize(&q); + db_prepare(&q, "SELECT uuid FROM dups ORDER BY length(uuid) DESC, uuid"); + while( db_step(&q)==SQLITE_ROW ){ + fossil_print("%s\n", db_column_text(&q, 0)); + } + db_finalize(&q); +} + +/* +** Schema for the description table +*/ +static const char zDescTab[] = +@ CREATE TEMP TABLE IF NOT EXISTS description( +@ rid INTEGER PRIMARY KEY, -- RID of the object +@ uuid TEXT, -- hash of the object +@ ctime DATETIME, -- Time of creation +@ isPrivate BOOLEAN DEFAULT 0, -- True for unpublished artifacts +@ type TEXT, -- file, checkin, wiki, ticket, etc. +@ rcvid INT, -- When the artifact was received +@ summary TEXT, -- Summary comment for the object +@ ref TEXT -- hash of an object to link against +@ ); +@ CREATE INDEX IF NOT EXISTS desctype +@ ON description(summary) WHERE summary='unknown'; +; + +/* +** Attempt to describe all phantom artifacts. The artifacts are +** already loaded into the description table and have summary='unknown'. +** This routine attempts to generate a better summary, and possibly +** fill in the ref field. +*/ +static void describe_unknown_artifacts(){ + /* Try to figure out the origin of unknown artifacts */ + db_multi_exec( + "REPLACE INTO description(rid,uuid,isPrivate,type,summary,ref)\n" + " SELECT description.rid, description.uuid, isPrivate, type,\n" + " CASE WHEN plink.isprim THEN '' ELSE 'merge ' END ||\n" + " 'parent of check-in', blob.uuid\n" + " FROM description, plink, blob\n" + " WHERE description.summary='unknown'\n" + " AND plink.pid=description.rid\n" + " AND blob.rid=plink.cid;" + ); + db_multi_exec( + "REPLACE INTO description(rid,uuid,isPrivate,type,summary,ref)\n" + " SELECT description.rid, description.uuid, isPrivate, type,\n" + " 'child of check-in', blob.uuid\n" + " FROM description, plink, blob\n" + " WHERE description.summary='unknown'\n" + " AND plink.cid=description.rid\n" + " AND blob.rid=plink.pid;" + ); + db_multi_exec( + "REPLACE INTO description(rid,uuid,isPrivate,type,summary,ref)\n" + " SELECT description.rid, description.uuid, isPrivate, type,\n" + " 'check-in referenced by \"'||tag.tagname ||'\" tag',\n" + " blob.uuid\n" + " FROM description, tagxref, tag, blob\n" + " WHERE description.summary='unknown'\n" + " AND tagxref.origid=description.rid\n" + " AND tag.tagid=tagxref.tagid\n" + " AND blob.rid=tagxref.srcid;" + ); + db_multi_exec( + "REPLACE INTO description(rid,uuid,isPrivate,type,summary,ref)\n" + " SELECT description.rid, description.uuid, isPrivate, type,\n" + " 'file \"'||filename.name||'\"',\n" + " blob.uuid\n" + " FROM description, mlink, filename, blob\n" + " WHERE description.summary='unknown'\n" + " AND mlink.fid=description.rid\n" + " AND blob.rid=mlink.mid\n" + " AND filename.fnid=mlink.fnid;" + ); + if( !db_exists("SELECT 1 FROM description WHERE summary='unknown'") ){ + return; + } + add_content_sql_commands(g.db); + db_multi_exec( + "REPLACE INTO description(rid,uuid,isPrivate,type,summary,ref)\n" + " SELECT description.rid, description.uuid, isPrivate, type,\n" + " 'referenced by cluster', blob.uuid\n" + " FROM description, tagxref, blob\n" + " WHERE description.summary='unknown'\n" + " AND tagxref.tagid=(SELECT tagid FROM tag WHERE tagname='cluster')\n" + " AND blob.rid=tagxref.rid\n" + " AND CAST(content(blob.uuid) AS text)" + " GLOB ('*M '||description.uuid||'*');" + ); +} + +/* +** Create the description table if it does not already exists. +** Populate fields of this table with descriptions for all artifacts +** whose RID matches the SQL expression in zWhere. +*/ +void describe_artifacts(const char *zWhere){ + db_multi_exec("%s", zDescTab/*safe-for-%s*/); + + /* Describe check-ins */ + db_multi_exec( + "INSERT OR IGNORE INTO description(rid,uuid,rcvid,ctime,type,summary)\n" + "SELECT blob.rid, blob.uuid, blob.rcvid, event.mtime, 'checkin',\n" + " 'check-in to '\n" + " || coalesce((SELECT value FROM tagxref WHERE tagid=%d" + " AND tagtype>0 AND tagxref.rid=blob.rid),'trunk')\n" + " || ' by ' || coalesce(event.euser,event.user)\n" + " || ' on ' || strftime('%%Y-%%m-%%d %%H:%%M',event.mtime)\n" + " FROM event, blob\n" + " WHERE (event.objid %s) AND event.type='ci'\n" + " AND event.objid=blob.rid;", + TAG_BRANCH, zWhere /*safe-for-%s*/ + ); + + /* Describe files */ + db_multi_exec( + "INSERT OR IGNORE INTO description(rid,uuid,rcvid,ctime,type,summary)\n" + "SELECT blob.rid, blob.uuid, blob.rcvid, event.mtime," + " 'file', 'file '||filename.name\n" + " FROM mlink, blob, event, filename\n" + " WHERE (mlink.fid %s)\n" + " AND mlink.mid=event.objid\n" + " AND filename.fnid=mlink.fnid\n" + " AND mlink.fid=blob.rid;", + zWhere /*safe-for-%s*/ + ); + + /* Describe tags */ + db_multi_exec( + "INSERT OR IGNORE INTO description(rid,uuid,rcvid,ctime,type,summary)\n" + "SELECT blob.rid, blob.uuid, blob.rcvid, tagxref.mtime, 'tag',\n" + " 'tag '||substr((SELECT uuid FROM blob WHERE rid=tagxref.rid),1,16)\n" + " FROM tagxref, blob\n" + " WHERE (tagxref.srcid %s) AND tagxref.srcid!=tagxref.rid\n" + " AND tagxref.srcid=blob.rid;", + zWhere /*safe-for-%s*/ + ); + + /* Cluster artifacts */ + db_multi_exec( + "INSERT OR IGNORE INTO description(rid,uuid,rcvid,ctime,type,summary)\n" + "SELECT blob.rid, blob.uuid, blob.rcvid, rcvfrom.mtime," + " 'cluster', 'cluster'\n" + " FROM tagxref, blob, rcvfrom\n" + " WHERE (tagxref.rid %s)\n" + " AND tagxref.tagid=(SELECT tagid FROM tag WHERE tagname='cluster')\n" + " AND blob.rid=tagxref.rid" + " AND rcvfrom.rcvid=blob.rcvid;", + zWhere /*safe-for-%s*/ + ); + + /* Ticket change artifacts */ + db_multi_exec( + "INSERT OR IGNORE INTO description(rid,uuid,rcvid,ctime,type,summary)\n" + "SELECT blob.rid, blob.uuid, blob.rcvid, tagxref.mtime, 'ticket',\n" + " 'ticket '||substr(tag.tagname,5,21)\n" + " FROM tagxref, tag, blob\n" + " WHERE (tagxref.rid %s)\n" + " AND tag.tagid=tagxref.tagid\n" + " AND tag.tagname GLOB 'tkt-*'" + " AND blob.rid=tagxref.rid;", + zWhere /*safe-for-%s*/ + ); + + /* Wiki edit artifacts */ + db_multi_exec( + "INSERT OR IGNORE INTO description(rid,uuid,rcvid,ctime,type,summary)\n" + "SELECT blob.rid, blob.uuid, blob.rcvid, tagxref.mtime, 'wiki',\n" + " printf('wiki \"%%s\"',substr(tag.tagname,6))\n" + " FROM tagxref, tag, blob\n" + " WHERE (tagxref.rid %s)\n" + " AND tag.tagid=tagxref.tagid\n" + " AND tag.tagname GLOB 'wiki-*'" + " AND blob.rid=tagxref.rid;", + zWhere /*safe-for-%s*/ + ); + + /* Event edit artifacts */ + db_multi_exec( + "INSERT OR IGNORE INTO description(rid,uuid,rcvid,ctime,type,summary)\n" + "SELECT blob.rid, blob.uuid, blob.rcvid, tagxref.mtime, 'event',\n" + " 'event '||substr(tag.tagname,7)\n" + " FROM tagxref, tag, blob\n" + " WHERE (tagxref.rid %s)\n" + " AND tag.tagid=tagxref.tagid\n" + " AND tag.tagname GLOB 'event-*'" + " AND blob.rid=tagxref.rid;", + zWhere /*safe-for-%s*/ + ); + + /* Attachments */ + db_multi_exec( + "INSERT OR IGNORE INTO description(rid,uuid,rcvid,ctime,type,summary)\n" + "SELECT blob.rid, blob.uuid, blob.rcvid, attachment.mtime," + " 'attach-control',\n" + " 'attachment-control for '||attachment.filename\n" + " FROM attachment, blob\n" + " WHERE (attachment.attachid %s)\n" + " AND blob.rid=attachment.attachid", + zWhere /*safe-for-%s*/ + ); + db_multi_exec( + "INSERT OR IGNORE INTO description(rid,uuid,rcvid,ctime,type,summary)\n" + "SELECT blob.rid, blob.uuid, blob.rcvid, attachment.mtime, 'attachment',\n" + " 'attachment '||attachment.filename\n" + " FROM attachment, blob\n" + " WHERE (blob.rid %s)\n" + " AND blob.rid NOT IN (SELECT rid FROM description)\n" + " AND blob.uuid=attachment.src", + zWhere /*safe-for-%s*/ + ); + + /* Forum posts */ + if( db_table_exists("repository","forumpost") ){ + db_multi_exec( + "INSERT OR IGNORE INTO description(rid,uuid,rcvid,ctime,type,summary)\n" + "SELECT postblob.rid, postblob.uuid, postblob.rcvid," + " forumpost.fmtime, 'forumpost',\n" + " CASE WHEN fpid=froot THEN 'forum-post '\n" + " ELSE 'forum-reply-to ' END || substr(rootblob.uuid,1,14)\n" + " FROM forumpost, blob AS postblob, blob AS rootblob\n" + " WHERE (forumpost.fpid %s)\n" + " AND postblob.rid=forumpost.fpid" + " AND rootblob.rid=forumpost.froot", + zWhere /*safe-for-%s*/ + ); + } + + /* Mark all other artifacts as "unknown" for now */ + db_multi_exec( + "INSERT OR IGNORE INTO description(rid,uuid,rcvid,type,summary)\n" + "SELECT blob.rid, blob.uuid,blob.rcvid,\n" + " CASE WHEN EXISTS(SELECT 1 FROM phantom WHERE rid=blob.rid)\n" + " THEN 'phantom' ELSE '' END,\n" + " 'unknown'\n" + " FROM blob\n" + " WHERE (blob.rid %s)\n" + " AND (blob.rid NOT IN (SELECT rid FROM description));", + zWhere /*safe-for-%s*/ + ); + + /* Mark private elements */ + db_multi_exec( + "UPDATE description SET isPrivate=1 WHERE rid IN private" + ); + + if( db_exists("SELECT 1 FROM description WHERE summary='unknown'") ){ + describe_unknown_artifacts(); + } +} + +/* +** Print the content of the description table on stdout. +** +** The description table is computed using the WHERE clause zWhere if +** the zWhere parameter is not NULL. If zWhere is NULL, then this +** routine assumes that the description table already exists and is +** populated and merely prints the contents. +*/ +int describe_artifacts_to_stdout(const char *zWhere, const char *zLabel){ + Stmt q; + int cnt = 0; + if( zWhere!=0 ) describe_artifacts(zWhere); + db_prepare(&q, + "SELECT uuid, summary, coalesce(ref,''), isPrivate\n" + " FROM description\n" + " ORDER BY ctime, type;" + ); + while( db_step(&q)==SQLITE_ROW ){ + if( zLabel ){ + fossil_print("%s\n", zLabel); + zLabel = 0; + } + fossil_print(" %.16s %s %s", db_column_text(&q,0), + db_column_text(&q,1), db_column_text(&q,2)); + if( db_column_int(&q,3) ) fossil_print(" (private)"); + fossil_print("\n"); + cnt++; + } + db_finalize(&q); + if( zWhere!=0 ) db_multi_exec("DELETE FROM description;"); + return cnt; +} + +/* +** COMMAND: test-describe-artifacts +** +** Usage: %fossil test-describe-artifacts [--from S] [--count N] +** +** Display a one-line description of every artifact. +*/ +void test_describe_artifacts_cmd(void){ + int iFrom = 0; + int iCnt = 1000000; + const char *z; + char *zRange; + db_find_and_open_repository(0,0); + z = find_option("from",0,1); + if( z ) iFrom = atoi(z); + z = find_option("count",0,1); + if( z ) iCnt = atoi(z); + zRange = mprintf("BETWEEN %d AND %d", iFrom, iFrom+iCnt-1); + describe_artifacts_to_stdout(zRange, 0); +} + +/* +** WEBPAGE: bloblist +** +** Return a page showing all artifacts in the repository. Query parameters: +** +** n=N Show N artifacts +** s=S Start with artifact number S +** priv Show only unpublished or private artifacts +** phan Show only phantom artifacts +** hclr Color code hash types (SHA1 vs SHA3) +*/ +void bloblist_page(void){ + Stmt q; + int s = atoi(PD("s","0")); + int n = atoi(PD("n","5000")); + int mx = db_int(0, "SELECT max(rid) FROM blob"); + int privOnly = PB("priv"); + int phantomOnly = PB("phan"); + int hashClr = PB("hclr"); + char *zRange; + char *zSha1Bg; + char *zSha3Bg; + + login_check_credentials(); + if( !g.perm.Read ){ login_needed(g.anon.Read); return; } + style_header("List Of Artifacts"); + style_submenu_element("250 Largest", "bigbloblist"); + if( g.perm.Admin ){ + style_submenu_element("Artifact Log", "rcvfromlist"); + } + if( !phantomOnly ){ + style_submenu_element("Phantoms", "bloblist?phan"); + } + if( g.perm.Private || g.perm.Admin ){ + if( !privOnly ){ + style_submenu_element("Private", "bloblist?priv"); + } + }else{ + privOnly = 0; + } + if( g.perm.Write ){ + style_submenu_element("Artifact Stats", "artifact_stats"); + } + if( !privOnly && !phantomOnly && mx>n && P("s")==0 ){ + int i; + @

      Select a range of artifacts to view:

      + @
        + for(i=1; i<=mx; i+=n){ + @
      • %z(href("%R/bloblist?s=%d&n=%d",i,n)) + @ %d(i)..%d(i+n-1 + } + @
      + style_finish_page(); + return; + } + if( phantomOnly || privOnly || mx>n ){ + style_submenu_element("Index", "bloblist"); + } + if( privOnly ){ + zRange = mprintf("IN private"); + }else if( phantomOnly ){ + zRange = mprintf("IN phantom"); + }else{ + zRange = mprintf("BETWEEN %d AND %d", s, s+n-1); + } + describe_artifacts(zRange); + fossil_free(zRange); + db_prepare(&q, + "SELECT rid, uuid, summary, isPrivate, type='phantom', rcvid, ref" + " FROM description ORDER BY rid" + ); + if( skin_detail_boolean("white-foreground") ){ + zSha1Bg = "#714417"; + zSha3Bg = "#177117"; + }else{ + zSha1Bg = "#ebffb0"; + zSha3Bg = "#b0ffb0"; + } + @ + if( g.perm.Admin ){ + @ + }else{ + @ + } + @ + if( g.perm.Admin ){ + int rcvid = db_column_int(&q,5); + if( rcvid<=0 ){ + @ + if( zRef && zRef[0] ){ + @ + }else if( isPhantom==0 ){ + @ + }else{ + @ + } + }else{ + @ + } + @
      RIDHashRcvidDescriptionRefRemarks + }else{ + @
      RIDHashDescriptionRefRemarks + } + while( db_step(&q)==SQLITE_ROW ){ + int rid = db_column_int(&q,0); + const char *zUuid = db_column_text(&q, 1); + const char *zDesc = db_column_text(&q, 2); + int isPriv = db_column_int(&q,3); + int isPhantom = db_column_int(&q,4); + const char *zRef = db_column_text(&q,6); + if( isPriv && !isPhantom && !g.perm.Private && !g.perm.Admin ){ + /* Don't show private artifacts to users without Private (x) permission */ + continue; + } + if( hashClr ){ + const char *zClr = db_column_bytes(&q,1)>40 ? zSha3Bg : zSha1Bg; + @
      %d(rid)
      %d(rid) %z(href("%R/info/%!S",zUuid))%S(zUuid)   + }else{ + @ %d(rcvid) + } + } + @ %h(zDesc)%z(href("%R/info/%!S",zRef))%S(zRef) + }else{ + @   + } + if( isPriv || isPhantom ){ + if( isPriv==0 ){ + @ phantomprivateprivate,phantom  + } + @
      + db_finalize(&q); + style_finish_page(); +} + +/* +** Output HTML that shows a table of all public phantoms. +*/ +void table_of_public_phantoms(void){ + Stmt q; + char *zRange; + double rNow; + zRange = mprintf("IN (SELECT rid FROM phantom EXCEPT" + " SELECT rid FROM private)"); + describe_artifacts(zRange); + fossil_free(zRange); + db_prepare(&q, + "SELECT rid, uuid, summary, ref," + " (SELECT mtime FROM blob, rcvfrom" + " WHERE blob.uuid=ref AND rcvfrom.rcvid=blob.rcvid)" + " FROM description ORDER BY rid" + ); + rNow = db_double(0.0, "SELECT julianday('now')"); + @ + @ + @ + if( zRef && zRef[0] ){ + @ + } + @
      RIDDescriptionSourceAge + while( db_step(&q)==SQLITE_ROW ){ + int rid = db_column_int(&q,0); + const char *zUuid = db_column_text(&q, 1); + const char *zDesc = db_column_text(&q, 2); + const char *zRef = db_column_text(&q,3); + double mtime = db_column_double(&q,4); + @
      %d(rid)%h(zUuid)
      %h(zDesc)
      %z(href("%R/info/%!S",zRef))%!S(zRef) + if( mtime>0 ){ + char *zAge = human_readable_age(rNow - mtime); + @ %h(zAge) + fossil_free(zAge); + }else{ + @   + } + }else{ + @    + } + @
      + db_finalize(&q); +} + +/* +** WEBPAGE: phantoms +** +** Show a list of all "phantom" artifacts that are not marked as "private". +** +** A "phantom" artifact is an artifact whose hash named appears in some +** artifact but whose content is unknown. For example, if a manifest +** references a particular SHA3 hash of a file, but that SHA3 hash is +** not on the shunning list and is not in the database, then the file +** is a phantom. We know it exists, but we do not know its content. +** +** Whenever a sync occurs, both each party looks at its phantom list +** and for every phantom that is not also marked private, it asks the +** other party to send it the content. This mechanism helps keep all +** repositories synced up. +** +** This page is similar to the /bloblist page in that it lists artifacts. +** But this page is a special case in that it only shows phantoms that +** are not private. In other words, this page shows all phantoms that +** generate extra network traffic on every sync request. +*/ +void phantom_list_page(void){ + login_check_credentials(); + if( !g.perm.Read ){ login_needed(g.anon.Read); return; } + style_header("Public Phantom Artifacts"); + if( g.perm.Admin ){ + style_submenu_element("Artifact Log", "rcvfromlist"); + style_submenu_element("Artifact List", "bloblist"); + } + if( g.perm.Write ){ + style_submenu_element("Artifact Stats", "artifact_stats"); + } + table_of_public_phantoms(); + style_finish_page(); +} + +/* +** WEBPAGE: bigbloblist +** +** Return a page showing the largest artifacts in the repository in order +** of decreasing size. +** +** n=N Show the top N artifacts +*/ +void bigbloblist_page(void){ + Stmt q; + int n = atoi(PD("n","250")); + + login_check_credentials(); + if( !g.perm.Read ){ login_needed(g.anon.Read); return; } + if( g.perm.Admin ){ + style_submenu_element("Artifact Log", "rcvfromlist"); + } + if( g.perm.Write ){ + style_submenu_element("Artifact Stats", "artifact_stats"); + } + style_submenu_element("All Artifacts", "bloblist"); + style_header("%d Largest Artifacts", n); + db_multi_exec( + "CREATE TEMP TABLE toshow(rid INTEGER PRIMARY KEY);" + "INSERT INTO toshow(rid)" + " SELECT rid FROM blob" + " ORDER BY length(content) DESC" + " LIMIT %d;", n + ); + describe_artifacts("IN toshow"); + db_prepare(&q, + "SELECT description.rid, description.uuid, description.summary," + " length(blob.content), coalesce(delta.srcid,'')," + " datetime(description.ctime)" + " FROM description, blob LEFT JOIN delta ON delta.rid=blob.rid" + " WHERE description.rid=blob.rid" + " ORDER BY length(content) DESC" + ); + @ + @ + @ + while( db_step(&q)==SQLITE_ROW ){ + int rid = db_column_int(&q,0); + const char *zUuid = db_column_text(&q, 1); + const char *zDesc = db_column_text(&q, 2); + int sz = db_column_int(&q,3); + const char *zSrcId = db_column_text(&q,4); + const char *zDate = db_column_text(&q,5); + @ + @ + @ + @ + @ + @ + @ + } + @
      SizeRID + @ Delta FromHashDescriptionDate
      %d(sz)%d(rid)%s(zSrcId) %z(href("%R/info/%!S",zUuid))%S(zUuid) %h(zDesc)%z(href("%R/timeline?c=%T",zDate))%s(zDate)
      + db_finalize(&q); + style_table_sorter(); + style_finish_page(); +} + +/* +** COMMAND: test-unsent +** +** Usage: %fossil test-unsent +** +** Show all artifacts in the unsent table +*/ +void test_unsent_cmd(void){ + db_find_and_open_repository(0,0); + describe_artifacts_to_stdout("IN unsent", 0); +} + +/* +** COMMAND: test-unclustered +** +** Usage: %fossil test-unclustered +** +** Show all artifacts in the unclustered table +*/ +void test_unclusterd_cmd(void){ + db_find_and_open_repository(0,0); + describe_artifacts_to_stdout("IN unclustered", 0); +} + +/* +** COMMAND: test-phantoms +** +** Usage: %fossil test-phantoms +** +** Show all phantom artifacts +*/ +void test_phatoms_cmd(void){ + db_find_and_open_repository(0,0); + describe_artifacts_to_stdout("IN (SELECT rid FROM blob WHERE size<0)", 0); +} + +/* Maximum number of collision examples to remember */ +#define MAX_COLLIDE 25 + +/* +** Generate a report on the number of collisions in artifact hashes +** generated by the SQL given in the argument. +*/ +static void collision_report(const char *zSql){ + int i, j, kk; + int nHash = 0; + Stmt q; + char zPrev[HNAME_MAX+1]; + struct { + int cnt; + char *azHit[MAX_COLLIDE]; + char z[HNAME_MAX+1]; + } aCollide[HNAME_MAX+1]; + memset(aCollide, 0, sizeof(aCollide)); + memset(zPrev, 0, sizeof(zPrev)); + db_prepare(&q,"%s",zSql/*safe-for-%s*/); + while( db_step(&q)==SQLITE_ROW ){ + const char *zUuid = db_column_text(&q,0); + int n = db_column_bytes(&q,0); + int i; + nHash++; + for(i=0; zPrev[i] && zPrev[i]==zUuid[i]; i++){} + if( i>0 && i<=HNAME_MAX ){ + if( i>=4 && aCollide[i].cnt + @ LengthInstancesFirst Instance + @ + for(i=1; i<=HNAME_MAX; i++){ + if( aCollide[i].cnt==0 ) continue; + @ %d(i)%d(aCollide[i].cnt)%h(aCollide[i].z) + } + @ + @

      Total number of hashes: %d(nHash)

      + kk = 0; + for(i=HNAME_MAX; i>=4; i--){ + if( aCollide[i].cnt==0 ) continue; + if( aCollide[i].cnt>200 ) break; + kk += aCollide[i].cnt; + if( aCollide[i].cnt<25 ){ + @

      Collisions of length %d(i): + }else{ + @

      First 25 collisions of length %d(i): + } + for(j=0; j + } + } + for(i=4; iHash Prefix Collisions on Check-ins + collision_report("SELECT (SELECT uuid FROM blob WHERE rid=objid)" + " FROM event WHERE event.type='ci'" + " ORDER BY 1"); + @

      Hash Prefix Collisions on All Artifacts

      + collision_report("SELECT uuid FROM blob ORDER BY 1"); + style_finish_page(); +} ADDED src/patch.c Index: src/patch.c ================================================================== --- /dev/null +++ src/patch.c @@ -0,0 +1,994 @@ +/* +** Copyright (c) 2021 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 code used to implement the "diff" command +*/ +#include "config.h" +#include "patch.h" +#include + +/* +** Try to compute the name of the computer on which this process +** is running. +*/ +char *fossil_hostname(void){ + FILE *in; + char zBuf[200]; + in = popen("hostname","r"); + if( in ){ + size_t n = fread(zBuf, 1, sizeof(zBuf)-1, in); + while( n>0 && fossil_isspace(zBuf[n-1]) ){ n--; } + if( n<0 ) n = 0; + zBuf[n] = 0; + pclose(in); + return fossil_strdup(zBuf); + } + return 0; +} + +/* +** Flags passed from the main patch_cmd() routine into subfunctions used +** to implement the various subcommands. +*/ +#define PATCH_DRYRUN 0x0001 +#define PATCH_VERBOSE 0x0002 +#define PATCH_FORCE 0x0004 + +/* +** Implementation of the "readfile(X)" SQL function. The entire content +** of the checkout file named X is read and returned as a BLOB. +*/ +static void readfileFunc( + sqlite3_context *context, + int argc, + sqlite3_value **argv +){ + const char *zName; + Blob x; + sqlite3_int64 sz; + (void)(argc); /* Unused parameter */ + zName = (const char*)sqlite3_value_text(argv[0]); + if( zName==0 || (zName[0]=='-' && zName[1]==0) ) return; + sz = blob_read_from_file(&x, zName, RepoFILE); + sqlite3_result_blob64(context, x.aData, sz, SQLITE_TRANSIENT); + blob_reset(&x); +} + +/* +** mkdelta(X,Y) +** +** X is an numeric artifact id. Y is a filename. +** +** Compute a compressed delta that carries X into Y. Or return +** and zero-length blob if X is equal to Y. +*/ +static void mkdeltaFunc( + sqlite3_context *context, + int argc, + sqlite3_value **argv +){ + const char *zFile; + Blob x, y; + int rid; + char *aOut; + int nOut; + sqlite3_int64 sz; + + rid = sqlite3_value_int(argv[0]); + if( !content_get(rid, &x) ){ + sqlite3_result_error(context, "mkdelta(X,Y): no content for X", -1); + return; + } + zFile = (const char*)sqlite3_value_text(argv[1]); + if( zFile==0 ){ + sqlite3_result_error(context, "mkdelta(X,Y): NULL Y argument", -1); + blob_reset(&x); + return; + } + sz = blob_read_from_file(&y, zFile, RepoFILE); + if( sz<0 ){ + sqlite3_result_error(context, "mkdelta(X,Y): cannot read file Y", -1); + blob_reset(&x); + return; + } + aOut = sqlite3_malloc64(sz+70); + if( aOut==0 ){ + sqlite3_result_error_nomem(context); + blob_reset(&y); + blob_reset(&x); + return; + } + if( blob_size(&x)==blob_size(&y) + && memcmp(blob_buffer(&x), blob_buffer(&y), blob_size(&x))==0 + ){ + blob_reset(&y); + blob_reset(&x); + sqlite3_result_blob64(context, "", 0, SQLITE_STATIC); + return; + } + nOut = delta_create(blob_buffer(&x),blob_size(&x), + blob_buffer(&y),blob_size(&y), aOut); + blob_reset(&x); + blob_reset(&y); + blob_init(&x, aOut, nOut); + blob_compress(&x, &x); + sqlite3_result_blob64(context, blob_buffer(&x), blob_size(&x), + SQLITE_TRANSIENT); + blob_reset(&x); +} + + +/* +** Generate a binary patch file and store it into the file +** named zOut. +*/ +void patch_create(unsigned mFlags, const char *zOut, FILE *out){ + int vid; + char *z; + + if( zOut && file_isdir(zOut, ExtFILE)!=0 ){ + if( mFlags & PATCH_FORCE ){ + file_delete(zOut); + } + if( file_isdir(zOut, ExtFILE)!=0 ){ + fossil_fatal("patch file already exists: %s", zOut); + } + } + add_content_sql_commands(g.db); + deltafunc_init(g.db); + sqlite3_create_function(g.db, "read_co_file", 1, SQLITE_UTF8, 0, + readfileFunc, 0, 0); + sqlite3_create_function(g.db, "mkdelta", 2, SQLITE_UTF8, 0, + mkdeltaFunc, 0, 0); + db_multi_exec("ATTACH %Q AS patch;", zOut ? zOut : ":memory:"); + db_multi_exec( + "PRAGMA patch.journal_mode=OFF;\n" + "PRAGMA patch.page_size=512;\n" + "CREATE TABLE patch.chng(\n" + " pathname TEXT,\n" /* Filename */ + " origname TEXT,\n" /* Name before rename. NULL if not renamed */ + " hash TEXT,\n" /* Baseline hash. NULL for new files. */ + " isexe BOOL,\n" /* True if executable */ + " islink BOOL,\n" /* True if is a symbolic link */ + " delta BLOB\n" /* compressed delta. NULL if deleted. + ** length 0 if unchanged */ + ");" + "CREATE TABLE patch.cfg(\n" + " key TEXT,\n" + " value ANY\n" + ");" + ); + vid = db_lget_int("checkout", 0); + vfile_check_signature(vid, CKSIG_ENOTFILE); + user_select(); + db_multi_exec( + "INSERT INTO patch.cfg(key,value)" + "SELECT 'baseline',uuid FROM blob WHERE rid=%d " + "UNION ALL" + " SELECT 'ckout',rtrim(%Q,'/')" + "UNION ALL" + " SELECT 'repo',%Q " + "UNION ALL" + " SELECT 'user',%Q " + "UNION ALL" + " SELECT 'date',julianday('now')" + "UNION ALL" + " SELECT name,value FROM repository.config" + " WHERE name IN ('project-code','project-name') " + "UNION ALL" + " SELECT 'fossil-date',julianday('" MANIFEST_DATE "')" + ";", vid, g.zLocalRoot, g.zRepositoryName, g.zLogin); + z = fossil_hostname(); + if( z ){ + db_multi_exec( + "INSERT INTO patch.cfg(key,value)VALUES('hostname',%Q)", z); + fossil_free(z); + } + + /* New files */ + db_multi_exec( + "INSERT INTO patch.chng(pathname,hash,isexe,islink,delta)" + " SELECT pathname, NULL, isexe, islink," + " compress(read_co_file(%Q||pathname))" + " FROM vfile WHERE rid==0;", + g.zLocalRoot + ); + + /* Deleted files */ + db_multi_exec( + "INSERT INTO patch.chng(pathname,hash,isexe,islink,delta)" + " SELECT pathname, NULL, 0, 0, NULL" + " FROM vfile WHERE deleted;" + ); + + /* Changed files */ + db_multi_exec( + "INSERT INTO patch.chng(pathname,origname,hash,isexe,islink,delta)" + " SELECT pathname, nullif(origname,pathname), blob.uuid, isexe, islink," + " mkdelta(blob.rid, %Q||pathname)" + " FROM vfile, blob" + " WHERE blob.rid=vfile.rid" + " AND NOT deleted AND (chnged OR origname<>pathname);", + g.zLocalRoot + ); + + /* Merges */ + if( db_exists("SELECT 1 FROM localdb.vmerge WHERE id<=0") ){ + db_multi_exec( + "CREATE TABLE patch.patchmerge(type TEXT,mhash TEXT);\n" + "WITH tmap(id,type) AS (VALUES(0,'merge'),(-1,'cherrypick')," + "(-2,'backout'),(-4,'integrate'))" + "INSERT INTO patch.patchmerge(type,mhash)" + " SELECT tmap.type,vmerge.mhash FROM vmerge, tmap" + " WHERE tmap.id=vmerge.id;" + ); + } + + /* Write the database to standard output if zOut==0 */ + if( zOut==0 ){ + sqlite3_int64 sz; + unsigned char *pData; + pData = sqlite3_serialize(g.db, "patch", &sz, 0); + if( pData==0 ){ + fossil_fatal("out of memory"); + } +#ifdef _WIN32 + fflush(out); + _setmode(_fileno(out), _O_BINARY); +#endif + fwrite(pData, sz, 1, out); + sqlite3_free(pData); + fflush(out); + } +} + +/* +** Attempt to load and validate a patchfile identified by the first +** argument. +*/ +void patch_attach(const char *zIn, FILE *in){ + Stmt q; + if( g.db==0 ){ + sqlite3_open(":memory:", &g.db); + } + if( zIn==0 ){ + Blob buf; + int rc; + int sz; + unsigned char *pData; + blob_init(&buf, 0, 0); +#ifdef _WIN32 + _setmode(_fileno(in), _O_BINARY); +#endif + sz = blob_read_from_channel(&buf, in, -1); + pData = (unsigned char*)blob_buffer(&buf); + db_multi_exec("ATTACH ':memory:' AS patch"); + if( g.fSqlTrace ){ + fossil_trace("-- deserialize(\"patch\", pData, %lld);\n", sz); + } + rc = sqlite3_deserialize(g.db, "patch", pData, sz, sz, 0); + if( rc ){ + fossil_fatal("cannot open patch database: %s", sqlite3_errmsg(g.db)); + } + }else if( !file_isfile(zIn, ExtFILE) ){ + fossil_fatal("no such file: %s", zIn); + }else{ + db_multi_exec("ATTACH %Q AS patch", zIn); + } + db_prepare(&q, "PRAGMA patch.quick_check"); + while( db_step(&q)==SQLITE_ROW ){ + if( fossil_strcmp(db_column_text(&q,0),"ok")!=0 ){ + fossil_fatal("file %s is not a well-formed Fossil patchfile", zIn); + } + } + db_finalize(&q); +} + +/* +** Show a summary of the content of a patch on standard output +*/ +void patch_view(unsigned mFlags){ + Stmt q; + db_prepare(&q, + "WITH nmap(nkey,nm) AS (VALUES" + "('baseline','BASELINE')," + "('project-name','PROJECT-NAME'))" + "SELECT nm, value FROM nmap, patch.cfg WHERE nkey=key;" + ); + while( db_step(&q)==SQLITE_ROW ){ + fossil_print("%-12s %s\n", db_column_text(&q,0), db_column_text(&q,1)); + } + db_finalize(&q); + if( mFlags & PATCH_VERBOSE ){ + db_prepare(&q, + "WITH nmap(nkey,nm,isDate) AS (VALUES" + "('project-code','PROJECT-CODE',0)," + "('date','TIMESTAMP',1)," + "('user','USER',0)," + "('hostname','HOSTNAME',0)," + "('ckout','CHECKOUT',0)," + "('repo','REPOSITORY',0))" + "SELECT nm, CASE WHEN isDate THEN datetime(value) ELSE value END" + " FROM nmap, patch.cfg WHERE nkey=key;" + ); + while( db_step(&q)==SQLITE_ROW ){ + fossil_print("%-12s %s\n", db_column_text(&q,0), db_column_text(&q,1)); + } + db_finalize(&q); + } + if( db_table_exists("patch","patchmerge") ){ + db_prepare(&q, "SELECT upper(type),mhash FROM patchmerge"); + while( db_step(&q)==SQLITE_ROW ){ + fossil_print("%-12s %s\n", + db_column_text(&q,0), + db_column_text(&q,1)); + } + db_finalize(&q); + } + db_prepare(&q, + "SELECT pathname," /* 0: new name */ + " hash IS NULL AND delta IS NOT NULL," /* 1: isNew */ + " delta IS NULL," /* 2: isDeleted */ + " origname" /* 3: old name or NULL */ + " FROM patch.chng ORDER BY 1"); + while( db_step(&q)==SQLITE_ROW ){ + const char *zClass = "EDIT"; + const char *zName = db_column_text(&q,0); + const char *zOrigName = db_column_text(&q, 3); + if( db_column_int(&q, 1) && zOrigName==0 ){ + zClass = "NEW"; + }else if( db_column_int(&q, 2) ){ + zClass = zOrigName==0 ? "DELETE" : 0; + } + if( zOrigName!=0 && zOrigName[0]!=0 ){ + fossil_print("%-12s %s -> %s\n", "RENAME",zOrigName,zName); + } + if( zClass ){ + fossil_print("%-12s %s\n", zClass, zName); + } + } + db_finalize(&q); +} + +/* +** Apply the patch currently attached as database "patch". +** +** First update the check-out to be at "baseline". Then loop through +** and update all files. +*/ +void patch_apply(unsigned mFlags){ + Stmt q; + Blob cmd; + + if( (mFlags & PATCH_FORCE)==0 && unsaved_changes(0) ){ + fossil_fatal("there are unsaved changes in the current checkout"); + } + blob_init(&cmd, 0, 0); + file_chdir(g.zLocalRoot, 0); + db_prepare(&q, + "SELECT patch.cfg.value" + " FROM patch.cfg, localdb.vvar" + " WHERE patch.cfg.key='baseline'" + " AND localdb.vvar.name='checkout-hash'" + " AND patch.cfg.key<>localdb.vvar.name" + ); + if( db_step(&q)==SQLITE_ROW ){ + blob_append_escaped_arg(&cmd, g.nameOfExe, 1); + blob_appendf(&cmd, " update %s", db_column_text(&q, 0)); + if( mFlags & PATCH_VERBOSE ){ + fossil_print("%-10s %s\n", "BASELINE", db_column_text(&q,0)); + } + } + db_finalize(&q); + if( blob_size(&cmd)>0 ){ + if( mFlags & PATCH_DRYRUN ){ + fossil_print("%s\n", blob_str(&cmd)); + }else{ + int rc = fossil_system(blob_str(&cmd)); + if( rc ){ + fossil_fatal("unable to update to the baseline check-out: %s", + blob_str(&cmd)); + } + } + } + blob_reset(&cmd); + if( db_table_exists("patch","patchmerge") ){ + db_prepare(&q, + "SELECT type, mhash, upper(type) FROM patch.patchmerge" + " WHERE type IN ('merge','cherrypick','backout','integrate')" + " AND mhash NOT GLOB '*[^a-fA-F0-9]*';" + ); + while( db_step(&q)==SQLITE_ROW ){ + const char *zType = db_column_text(&q,0); + blob_append_escaped_arg(&cmd, g.nameOfExe, 1); + if( strcmp(zType,"merge")==0 ){ + blob_appendf(&cmd, " merge %s\n", db_column_text(&q,1)); + }else{ + blob_appendf(&cmd, " merge --%s %s\n", zType, db_column_text(&q,1)); + } + if( mFlags & PATCH_VERBOSE ){ + fossil_print("%-10s %s\n", db_column_text(&q,2), + db_column_text(&q,0)); + } + } + db_finalize(&q); + if( mFlags & PATCH_DRYRUN ){ + fossil_print("%s", blob_str(&cmd)); + }else{ + int rc = fossil_unsafe_system(blob_str(&cmd)); + if( rc ){ + fossil_fatal("unable to do merges:\n%s", + blob_str(&cmd)); + } + } + blob_reset(&cmd); + } + + /* Deletions */ + db_prepare(&q, "SELECT pathname FROM patch.chng" + " WHERE origname IS NULL AND delta IS NULL"); + while( db_step(&q)==SQLITE_ROW ){ + blob_append_escaped_arg(&cmd, g.nameOfExe, 1); + blob_appendf(&cmd, " rm --hard %$\n", db_column_text(&q,0)); + if( mFlags & PATCH_VERBOSE ){ + fossil_print("%-10s %s\n", "DELETE", db_column_text(&q,0)); + } + } + db_finalize(&q); + if( blob_size(&cmd)>0 ){ + if( mFlags & PATCH_DRYRUN ){ + fossil_print("%s", blob_str(&cmd)); + }else{ + int rc = fossil_unsafe_system(blob_str(&cmd)); + if( rc ){ + fossil_fatal("unable to do merges:\n%s", + blob_str(&cmd)); + } + } + blob_reset(&cmd); + } + + /* Renames */ + db_prepare(&q, + "SELECT origname, pathname FROM patch.chng" + " WHERE origname IS NOT NULL" + " AND origname<>pathname" + ); + while( db_step(&q)==SQLITE_ROW ){ + blob_append_escaped_arg(&cmd, g.nameOfExe, 1); + blob_appendf(&cmd, " mv --hard %$ %$\n", + db_column_text(&q,0), db_column_text(&q,1)); + if( mFlags & PATCH_VERBOSE ){ + fossil_print("%-10s %s -> %s\n", "RENAME", + db_column_text(&q,0), db_column_text(&q,1)); + } + } + db_finalize(&q); + if( blob_size(&cmd)>0 ){ + if( mFlags & PATCH_DRYRUN ){ + fossil_print("%s", blob_str(&cmd)); + }else{ + int rc = fossil_unsafe_system(blob_str(&cmd)); + if( rc ){ + fossil_fatal("unable to rename files:\n%s", + blob_str(&cmd)); + } + } + blob_reset(&cmd); + } + + /* Edits and new files */ + db_prepare(&q, + "SELECT pathname, hash, isexe, islink, delta FROM patch.chng" + " WHERE delta IS NOT NULL" + ); + while( db_step(&q)==SQLITE_ROW ){ + const char *zPathname = db_column_text(&q,0); + const char *zHash = db_column_text(&q,1); + int isExe = db_column_int(&q,2); + int isLink = db_column_int(&q,3); + Blob data; + + blob_init(&data, 0, 0); + db_ephemeral_blob(&q, 4, &data); + if( blob_size(&data) ){ + blob_uncompress(&data, &data); + } + if( blob_size(&data)==0 ){ + /* No changes to the file */ + }else if( zHash ){ + Blob basis; + int rid = fast_uuid_to_rid(zHash); + int outSize, sz; + char *aOut; + if( rid==0 ){ + fossil_fatal("cannot locate basis artifact %s for %s", + zHash, zPathname); + } + if( !content_get(rid, &basis) ){ + fossil_fatal("cannot load basis artifact %d for %s", rid, zPathname); + } + outSize = delta_output_size(blob_buffer(&data),blob_size(&data)); + if( outSize<=0 ){ + fossil_fatal("malformed delta for %s", zPathname); + } + aOut = sqlite3_malloc64( outSize+1 ); + if( aOut==0 ){ + fossil_fatal("out of memory"); + } + sz = delta_apply(blob_buffer(&basis), blob_size(&basis), + blob_buffer(&data), blob_size(&data), aOut); + if( sz<0 ){ + fossil_fatal("malformed delta for %s", zPathname); + } + blob_reset(&basis); + blob_reset(&data); + blob_append(&data, aOut, sz); + sqlite3_free(aOut); + if( mFlags & PATCH_VERBOSE ){ + fossil_print("%-10s %s\n", "EDIT", zPathname); + } + }else{ + blob_append_escaped_arg(&cmd, g.nameOfExe, 1); + blob_appendf(&cmd, " add %$\n", zPathname); + if( mFlags & PATCH_VERBOSE ){ + fossil_print("%-10s %s\n", "NEW", zPathname); + } + } + if( (mFlags & PATCH_DRYRUN)==0 ){ + if( isLink ){ + symlink_create(blob_str(&data), zPathname); + }else{ + blob_write_to_file(&data, zPathname); + } + file_setexe(zPathname, isExe); + blob_reset(&data); + } + } + db_finalize(&q); + if( blob_size(&cmd)>0 ){ + if( mFlags & PATCH_DRYRUN ){ + fossil_print("%s", blob_str(&cmd)); + }else{ + int rc = fossil_system(blob_str(&cmd)); + if( rc ){ + fossil_fatal("unable to add new files:\n%s", + blob_str(&cmd)); + } + } + blob_reset(&cmd); + } +} + +/* +** This routine processes the +** +** ... [--dir64 DIR64] [DIRECTORY] FILENAME +** +** part of various "fossil patch" subcommands. +** +** Find and return the filename of the patch file to be used by +** "fossil patch apply" or "fossil patch create". Space to hold +** the returned name is obtained from fossil_malloc() and should +** be freed by the caller. +** +** If the name is "-" return NULL. The caller will interpret this +** to mean the patch is coming in over stdin or going out over +** stdout. +** +** If there is a prior DIRECTORY argument, or if +** the --dir64 option is present, first chdir to the specified +** directory, and adjust the path of FILENAME as appropriate so +** that it still points to the same file. +** +** The --dir64 option is undocumented. The argument to --dir64 +** is a base64-encoded directory name. The --dir64 option is used +** to transmit the directory as part of the command argument to +** a "ssh" command without having to worry about quoting +** any special characters in the filename. +** +** The returned name is obtained from fossil_malloc() and should +** be freed by the caller. +*/ +static char *patch_find_patch_filename(const char *zCmdName){ + const char *zDir64 = find_option("dir64",0,1); + const char *zDir = 0; + const char *zBaseName; + char *zToFree = 0; + char *zPatchFile = 0; + if( zDir64 ){ + int n = 0; + zToFree = decode64(zDir64, &n); + zDir = zToFree; + } + verify_all_options(); + if( g.argc!=4 && g.argc!=5 ){ + usage(mprintf("%s [DIRECTORY] FILENAME", zCmdName)); + } + if( g.argc==5 ){ + zDir = g.argv[3]; + zBaseName = g.argv[4]; + }else{ + zBaseName = g.argv[3]; + } + if( fossil_strcmp(zBaseName, "-")==0 ){ + zPatchFile = 0; + }else if( zDir ){ + zPatchFile = file_canonical_name_dup(g.argv[4]); + }else{ + zPatchFile = fossil_strdup(g.argv[3]); + } + if( zDir && file_chdir(zDir,0) ){ + fossil_fatal("cannot change to directory \"%s\"", zDir); + } + fossil_free(zToFree); + return zPatchFile; +} + +/* +** Create a FILE* that will execute the remote side of a push or pull +** using ssh (probably) or fossil for local pushes and pulls. Return +** a FILE* obtained from popen() into which we write the patch, or from +** which we read the patch, depending on whether this is a push or pull. +*/ +static FILE *patch_remote_command( + unsigned mFlags, /* flags */ + const char *zThisCmd, /* "push" or "pull" */ + const char *zRemoteCmd, /* "apply" or "create" */ + const char *zRW /* "w" or "r" */ +){ + char *zRemote; + char *zDir; + Blob cmd; + FILE *f; + const char *zForce = (mFlags & PATCH_FORCE)!=0 ? " -f" : ""; + if( g.argc!=4 ){ + usage(mprintf("%s [USER@]HOST:DIRECTORY", zThisCmd)); + } + zRemote = fossil_strdup(g.argv[3]); + zDir = (char*)file_skip_userhost(zRemote); + if( zDir==0 ){ + zDir = zRemote; + blob_init(&cmd, 0, 0); + blob_append_escaped_arg(&cmd, g.nameOfExe, 1); + blob_appendf(&cmd, " patch %s%s %$ -", zRemoteCmd, zForce, zDir); + }else{ + Blob remote; + *(char*)(zDir-1) = 0; + transport_ssh_command(&cmd); + blob_appendf(&cmd, " -T"); + blob_append_escaped_arg(&cmd, zRemote, 0); + blob_init(&remote, 0, 0); + blob_appendf(&remote, "fossil patch %s%s --dir64 %z -", + zRemoteCmd, zForce, encode64(zDir, -1)); + blob_append_escaped_arg(&cmd, blob_str(&remote), 0); + blob_reset(&remote); + } + if( mFlags & PATCH_VERBOSE ){ + fossil_print("# %s\n", blob_str(&cmd)); + fflush(stdout); + } + f = popen(blob_str(&cmd), zRW); + if( f==0 ){ + fossil_fatal("cannot run command: %s", blob_str(&cmd)); + } + blob_reset(&cmd); + return f; +} + +/* +** Show a diff for the patch currently loaded into database "patch". +*/ +static void patch_diff( + unsigned mFlags, /* Patch flags. only -f is allowed */ + const char *zDiffCmd, /* Command used for diffing */ + const char *zBinGlob, /* GLOB pattern to determine binary files */ + int fIncludeBinary, /* Do diffs against binary files */ + u64 diffFlags /* Other diff flags */ +){ + int nErr = 0; + Stmt q; + Blob empty; + blob_zero(&empty); + + if( (mFlags & PATCH_FORCE)==0 ){ + /* Check to ensure that the patch is against the repository that + ** we have opened. + ** + ** To do: If there is a mismatch, should we scan all of the repositories + ** listed in the global_config table looking for a match? + */ + if( db_exists( + "SELECT 1 FROM patch.cfg" + " WHERE cfg.key='baseline'" + " AND NOT EXISTS(SELECT 1 FROM blob WHERE uuid=cfg.value)" + )){ + char *zBaseline; + db_prepare(&q, + "SELECT config.value, cfg.value FROM config, cfg" + " WHERE config.name='project-name'" + " AND cfg.key='project-name'" + " AND config.value<>cfg.value" + ); + if( db_step(&q)==SQLITE_ROW ){ + char *zRepo = fossil_strdup(db_column_text(&q,0)); + char *zPatch = fossil_strdup(db_column_text(&q,1)); + db_finalize(&q); + fossil_fatal("the patch is against project \"%z\" but you are using " + "project \"%z\"", zPatch, zRepo); + } + db_finalize(&q); + zBaseline = db_text(0, "SELECT value FROM patch.cfg" + " WHERE key='baseline'"); + if( zBaseline ){ + fossil_fatal("the baseline of the patch (check-in %S) is not found " + "in the %s repository", zBaseline, g.zRepositoryName); + } + } + } + + db_prepare(&q, + "SELECT" + " (SELECT blob.rid FROM blob WHERE blob.uuid=chng.hash)," + " pathname," /* 1: new pathname */ + " origname," /* 2: original pathname. Null if not renamed */ + " delta," /* 3: delta. NULL if deleted. empty is no change */ + " hash" /* 4: baseline hash */ + " FROM patch.chng" + " ORDER BY pathname" + ); + while( db_step(&q)==SQLITE_ROW ){ + int rid; + const char *zName; + int isBin1, isBin2; + Blob a, b; + + if( db_column_type(&q,0)!=SQLITE_INTEGER + && db_column_type(&q,4)==SQLITE_TEXT + ){ + char *zUuid = fossil_strdup(db_column_text(&q,4)); + char *zName = fossil_strdup(db_column_text(&q,1)); + if( mFlags & PATCH_FORCE ){ + fossil_print("ERROR cannot find base artifact %S for file \"%s\"\n", + zUuid, zName); + nErr++; + fossil_free(zUuid); + fossil_free(zName); + continue; + }else{ + db_finalize(&q); + fossil_fatal("base artifact %S for file \"%s\" not found", + zUuid, zName); + } + } + zName = db_column_text(&q, 1); + rid = db_column_int(&q, 0); + + if( db_column_type(&q,3)==SQLITE_NULL ){ + fossil_print("DELETE %s\n", zName); + diff_print_index(zName, diffFlags, 0); + isBin2 = 0; + content_get(rid, &a); + isBin1 = fIncludeBinary ? 0 : looks_like_binary(&a); + diff_file_mem(&a, &empty, isBin1, isBin2, zName, zDiffCmd, + zBinGlob, fIncludeBinary, diffFlags); + }else if( rid==0 ){ + db_ephemeral_blob(&q, 3, &a); + blob_uncompress(&a, &a); + fossil_print("ADDED %s\n", zName); + diff_print_index(zName, diffFlags, 0); + isBin1 = 0; + isBin2 = fIncludeBinary ? 0 : looks_like_binary(&a); + diff_file_mem(&empty, &a, isBin1, isBin2, zName, zDiffCmd, + zBinGlob, fIncludeBinary, diffFlags); + blob_reset(&a); + }else if( db_column_bytes(&q, 3)>0 ){ + Blob delta; + db_ephemeral_blob(&q, 3, &delta); + blob_uncompress(&delta, &delta); + content_get(rid, &a); + blob_delta_apply(&a, &delta, &b); + isBin1 = fIncludeBinary ? 0 : looks_like_binary(&a); + isBin2 = fIncludeBinary ? 0 : looks_like_binary(&b); + diff_file_mem(&a, &b, isBin1, isBin2, zName, + zDiffCmd, zBinGlob, fIncludeBinary, diffFlags); + blob_reset(&a); + blob_reset(&b); + blob_reset(&delta); + } + } + db_finalize(&q); + if( nErr ) fossil_fatal("abort due to prior errors"); +} + + +/* +** COMMAND: patch +** +** Usage: %fossil patch SUBCOMMAND ?ARGS ..? +** +** This command is used to create, view, and apply Fossil binary patches. +** A Fossil binary patch is a single (binary) file that captures all of the +** uncommitted changes of a check-out. Use Fossil binary patches to transfer +** proposed or incomplete changes between machines for testing or analysis. +** +** > fossil patch create [DIRECTORY] FILENAME +** +** Create a new binary patch in FILENAME that captures all uncommitted +** changes in the check-out at DIRECTORY, or the current directory if +** DIRECTORY is omitted. If FILENAME is "-" then the binary patch +** is written to standard output. +** +** -f|--force Overwrite an existing patch with the same name. +** +** > fossil patch apply [DIRECTORY] FILENAME +** +** Apply the changes in FILENAME to the check-out at DIRECTORY, or +** in the current directory if DIRECTORY is omitted. Options: +** +** -f|--force Apply the patch even though there are unsaved +** changes in the current check-out. +** -n|--dryrun Do nothing, but print what would have happened. +** -v|--verbose Extra output explaining what happens. +** +** > fossil patch diff [DIRECTORY] FILENAME +** +** Show a human-readable diff for the patch. All the usual +** diff flags described at "fossil help diff" apply. In addition: +** +** -f|--force Continue trying to perform the diff even if +** baseline information is missing from the current +** repository +** +** > fossil patch push REMOTE-CHECKOUT +** +** Create a patch for the current check-out, transfer that patch to +** a remote machine (using ssh) and apply the patch there. The +** REMOTE-CHECKOUT is in one of the following formats: +** +** * DIRECTORY +** * HOST:DIRECTORY +** * USER@HOST:DIRECTORY +** +** This command will only work if "fossil" is on the default PATH +** of the remote machine. +** +** > fossil patch pull REMOTE-CHECKOUT +** +** Create a patch on a remote check-out, transfer that patch to the +** local machine (using ssh) and apply the patch in the local checkout. +** +** -f|--force Apply the patch even though there are unsaved +** changes in the current check-out. +** -n|--dryrun Do nothing, but print what would have happened. +** -v|--verbose Extra output explaining what happens. +** +** > fossil patch view FILENAME +** +** View a summary of the changes in the binary patch FILENAME. +** Use "fossil patch diff" for detailed patch content. +** +** -v|--verbose Show extra detail about the patch. +** +*/ +void patch_cmd(void){ + const char *zCmd; + size_t n; + if( g.argc<3 ){ + patch_usage: + usage("apply|create|diff|pull|push|view"); + } + zCmd = g.argv[2]; + n = strlen(zCmd); + if( strncmp(zCmd, "apply", n)==0 ){ + char *zIn; + unsigned flags = 0; + if( find_option("dryrun","n",0) ) flags |= PATCH_DRYRUN; + if( find_option("verbose","v",0) ) flags |= PATCH_VERBOSE; + if( find_option("force","f",0) ) flags |= PATCH_FORCE; + zIn = patch_find_patch_filename("apply"); + db_must_be_within_tree(); + patch_attach(zIn, stdin); + patch_apply(flags); + fossil_free(zIn); + }else + if( strncmp(zCmd, "create", n)==0 ){ + char *zOut; + unsigned flags = 0; + if( find_option("force","f",0) ) flags |= PATCH_FORCE; + zOut = patch_find_patch_filename("create"); + verify_all_options(); + db_must_be_within_tree(); + patch_create(flags, zOut, stdout); + fossil_free(zOut); + }else + if( strncmp(zCmd, "diff", n)==0 ){ + const char *zDiffCmd = 0; + const char *zBinGlob = 0; + int fIncludeBinary = 0; + u64 diffFlags; + char *zIn; + unsigned flags = 0; + + if( find_option("tk",0,0)!=0 ){ + db_close(0); + diff_tk("patch diff", 3); + return; + } + if( find_option("internal","i",0)==0 ){ + zDiffCmd = diff_command_external(zCmd[0]=='g'); + } + diffFlags = diff_options(); + if( find_option("verbose","v",0)!=0 ) diffFlags |= DIFF_VERBOSE; + if( zDiffCmd ){ + zBinGlob = diff_get_binary_glob(); + fIncludeBinary = diff_include_binary_files(); + } + db_find_and_open_repository(0, 0); + if( find_option("force","f",0) ) flags |= PATCH_FORCE; + verify_all_options(); + zIn = patch_find_patch_filename("apply"); + patch_attach(zIn, stdin); + patch_diff(flags, zDiffCmd, zBinGlob, fIncludeBinary, diffFlags); + fossil_free(zIn); + }else + if( strncmp(zCmd, "pull", n)==0 ){ + FILE *pIn = 0; + unsigned flags = 0; + if( find_option("dryrun","n",0) ) flags |= PATCH_DRYRUN; + if( find_option("verbose","v",0) ) flags |= PATCH_VERBOSE; + if( find_option("force","f",0) ) flags |= PATCH_FORCE; + db_must_be_within_tree(); + verify_all_options(); + pIn = patch_remote_command(flags & (~PATCH_FORCE), "pull", "create", "r"); + if( pIn ){ + patch_attach(0, pIn); + pclose(pIn); + patch_apply(flags); + } + }else + if( strncmp(zCmd, "push", n)==0 ){ + FILE *pOut = 0; + unsigned flags = 0; + if( find_option("dryrun","n",0) ) flags |= PATCH_DRYRUN; + if( find_option("verbose","v",0) ) flags |= PATCH_VERBOSE; + if( find_option("force","f",0) ) flags |= PATCH_FORCE; + db_must_be_within_tree(); + verify_all_options(); + pOut = patch_remote_command(flags, "push", "apply", "w"); + if( pOut ){ + patch_create(0, 0, pOut); + pclose(pOut); + } + }else + if( strncmp(zCmd, "view", n)==0 ){ + const char *zIn; + unsigned int flags = 0; + if( find_option("verbose","v",0) ) flags |= PATCH_VERBOSE; + verify_all_options(); + if( g.argc!=4 ){ + usage("view FILENAME"); + } + zIn = g.argv[3]; + if( fossil_strcmp(zIn, "-")==0 ) zIn = 0; + patch_attach(zIn, stdin); + patch_view(flags); + }else + { + goto patch_usage; + } +} ADDED src/path.c Index: src/path.c ================================================================== --- /dev/null +++ src/path.c @@ -0,0 +1,658 @@ +/* +** Copyright (c) 2011 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@sqlite.org +** +******************************************************************************* +** +** This file contains code used to trace paths of through the +** directed acyclic graph (DAG) of check-ins. +*/ +#include "config.h" +#include "path.h" +#include + +#if INTERFACE +/* Nodes for the paths through the DAG. +*/ +struct PathNode { + int rid; /* ID for this node */ + u8 fromIsParent; /* True if pFrom is the parent of rid */ + u8 isPrim; /* True if primary side of common ancestor */ + u8 isHidden; /* Abbreviate output in "fossil bisect ls" */ + PathNode *pFrom; /* Node we came from */ + union { + PathNode *pPeer; /* List of nodes of the same generation */ + PathNode *pTo; /* Next on path from beginning to end */ + } u; + PathNode *pAll; /* List of all nodes */ +}; +#endif + +/* +** Local variables for this module +*/ +static struct { + PathNode *pCurrent; /* Current generation of nodes */ + PathNode *pAll; /* All nodes */ + Bag seen; /* Nodes seen before */ + int nStep; /* Number of steps from first to last */ + int nNotHidden; /* Number of steps not counting hidden nodes */ + PathNode *pStart; /* Earliest node */ + PathNode *pEnd; /* Most recent */ +} path; + +/* +** Return the first (last) element of the computed path. +*/ +PathNode *path_first(void){ return path.pStart; } +PathNode *path_last(void){ return path.pEnd; } + +/* +** Return the number of steps in the computed path. +*/ +int path_length(void){ return path.nStep; } + +/* +** Return the number of non-hidden steps in the computed path. +*/ +int path_length_not_hidden(void){ return path.nNotHidden; } + +/* +** Create a new node +*/ +static PathNode *path_new_node(int rid, PathNode *pFrom, int isParent){ + PathNode *p; + + p = fossil_malloc( sizeof(*p) ); + memset(p, 0, sizeof(*p)); + p->rid = rid; + p->fromIsParent = isParent; + p->pFrom = pFrom; + p->u.pPeer = path.pCurrent; + path.pCurrent = p; + p->pAll = path.pAll; + path.pAll = p; + bag_insert(&path.seen, rid); + return p; +} + +/* +** Reset memory used by the shortest path algorithm. +*/ +void path_reset(void){ + PathNode *p; + while( path.pAll ){ + p = path.pAll; + path.pAll = p->pAll; + fossil_free(p); + } + bag_clear(&path.seen); + memset(&path, 0, sizeof(path)); +} + +/* +** Construct the path from path.pStart to path.pEnd in the u.pTo fields. +*/ +static void path_reverse_path(void){ + PathNode *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 ); +} + +/* +** Compute the shortest path from iFrom to iTo +** +** If directOnly is true, then use only the "primary" links from parent to +** child. In other words, ignore merges. +** +** Return a pointer to the beginning of the path (the iFrom node). +** Elements of the path can be traversed by following the PathNode.u.pTo +** pointer chain. +** +** Return NULL if no path is found. +*/ +PathNode *path_shortest( + int iFrom, /* Path starts here */ + int iTo, /* Path ends here */ + int directOnly, /* No merge links if true */ + int oneWayOnly, /* Parent->child only if true */ + Bag *pHidden /* Hidden nodes */ +){ + Stmt s; + PathNode *pPrev; + PathNode *p; + + path_reset(); + path.pStart = path_new_node(iFrom, 0, 0); + if( iTo==iFrom ){ + path.pEnd = path.pStart; + return path.pStart; + } + if( oneWayOnly && directOnly ){ + db_prepare(&s, + "SELECT cid, 1 FROM plink WHERE pid=:pid AND isprim" + ); + }else if( oneWayOnly ){ + db_prepare(&s, + "SELECT cid, 1 FROM plink WHERE pid=:pid " + ); + }else if( directOnly ){ + db_prepare(&s, + "SELECT cid, 1 FROM plink WHERE pid=:pid AND isprim " + "UNION ALL " + "SELECT pid, 0 FROM plink WHERE cid=:pid AND isprim" + ); + }else{ + db_prepare(&s, + "SELECT cid, 1 FROM plink WHERE pid=:pid " + "UNION ALL " + "SELECT pid, 0 FROM plink WHERE cid=:pid" + ); + } + while( path.pCurrent ){ + path.nStep++; + pPrev = path.pCurrent; + path.pCurrent = 0; + while( pPrev ){ + db_bind_int(&s, ":pid", pPrev->rid); + while( db_step(&s)==SQLITE_ROW ){ + int cid = db_column_int(&s, 0); + int isParent = db_column_int(&s, 1); + if( bag_find(&path.seen, cid) ) continue; + p = path_new_node(cid, pPrev, isParent); + if( pHidden && bag_find(pHidden,cid) ) p->isHidden = 1; + if( cid==iTo ){ + db_finalize(&s); + path.pEnd = p; + path_reverse_path(); + for(p=path.pStart->u.pTo; p; p=p->u.pTo ){ + if( !p->isHidden ) path.nNotHidden++; + } + return path.pStart; + } + } + db_reset(&s); + pPrev = pPrev->u.pPeer; + } + } + db_finalize(&s); + path_reset(); + return 0; +} + +/* +** Find the mid-point of the path. If the path contains fewer than +** 2 steps, return 0. +*/ +PathNode *path_midpoint(void){ + PathNode *p; + int i; + if( path.nNotHidden<2 ) return 0; + for(p=path.pEnd, i=0; p && (p->isHidden || ipFrom){ + if( !p->isHidden ) i++; + } + return p; +} + +/* +** Return an estimate of the number of comparisons remaining in order +** to bisect path. This is based on the log2() of path.nStep. +*/ +int path_search_depth(void){ + int i, j; + for(i=0, j=1; jrid); + db_bind_int(&ins, ":gen", ++gen); + db_step(&ins); + db_reset(&ins); + pPath = pPath->u.pTo; + } + db_finalize(&ins); + path_reset(); +} + +/* +** COMMAND: test-shortest-path +** +** Usage: %fossil test-shortest-path ?--no-merge? VERSION1 VERSION2 +** +** Report the shortest path between two check-ins. If the --no-merge flag +** is used, follow only direct parent-child paths and omit merge links. +*/ +void shortest_path_test_cmd(void){ + int iFrom; + int iTo; + PathNode *p; + int n; + int directOnly; + int oneWay; + + db_find_and_open_repository(0,0); + directOnly = find_option("no-merge",0,0)!=0; + oneWay = find_option("one-way",0,0)!=0; + if( g.argc!=4 ) usage("VERSION1 VERSION2"); + iFrom = name_to_rid(g.argv[2]); + iTo = name_to_rid(g.argv[3]); + p = path_shortest(iFrom, iTo, directOnly, oneWay, 0); + if( p==0 ){ + fossil_fatal("no path from %s to %s", g.argv[1], g.argv[2]); + } + for(n=1, p=path.pStart; p; p=p->u.pTo, n++){ + char *z; + z = db_text(0, + "SELECT substr(uuid,1,12) || ' ' || datetime(mtime)" + " FROM blob, event" + " WHERE blob.rid=%d AND event.objid=%d AND event.type='ci'", + p->rid, p->rid); + fossil_print("%4d: %5d %s", n, p->rid, z); + fossil_free(z); + if( p->u.pTo ){ + fossil_print(" is a %s of\n", + p->u.pTo->fromIsParent ? "parent" : "child"); + }else{ + fossil_print("\n"); + } + } +} + +/* +** Find the closest common ancestor of two nodes. "Closest" means the +** fewest number of arcs. +*/ +int path_common_ancestor(int iMe, int iYou){ + Stmt s; + PathNode *pPrev; + PathNode *p; + Bag me, you; + + if( iMe==iYou ) return iMe; + if( iMe==0 || iYou==0 ) return 0; + path_reset(); + path.pStart = path_new_node(iMe, 0, 0); + path.pStart->isPrim = 1; + path.pEnd = path_new_node(iYou, 0, 0); + db_prepare(&s, "SELECT pid FROM plink WHERE cid=:cid"); + bag_init(&me); + bag_insert(&me, iMe); + bag_init(&you); + bag_insert(&you, iYou); + while( path.pCurrent ){ + pPrev = path.pCurrent; + path.pCurrent = 0; + while( pPrev ){ + db_bind_int(&s, ":cid", pPrev->rid); + while( db_step(&s)==SQLITE_ROW ){ + int pid = db_column_int(&s, 0); + if( bag_find(pPrev->isPrim ? &you : &me, pid) ){ + /* pid is the common ancestor */ + PathNode *pNext; + for(p=path.pAll; p && p->rid!=pid; p=p->pAll){} + assert( p!=0 ); + pNext = p; + while( pNext ){ + pNext = p->pFrom; + p->pFrom = pPrev; + pPrev = p; + p = pNext; + } + if( pPrev==path.pStart ) path.pStart = path.pEnd; + path.pEnd = pPrev; + path_reverse_path(); + db_finalize(&s); + return pid; + }else if( bag_find(&path.seen, pid) ){ + /* pid is just an alternative path on one of the legs */ + continue; + } + p = path_new_node(pid, pPrev, 0); + p->isPrim = pPrev->isPrim; + bag_insert(pPrev->isPrim ? &me : &you, pid); + } + db_reset(&s); + pPrev = pPrev->u.pPeer; + } + } + db_finalize(&s); + path_reset(); + return 0; +} + +/* +** COMMAND: test-ancestor-path +** +** Usage: %fossil test-ancestor-path VERSION1 VERSION2 +** +** Report the path from VERSION1 to VERSION2 through their most recent +** common ancestor. +*/ +void ancestor_path_test_cmd(void){ + int iFrom; + int iTo; + int iPivot; + PathNode *p; + int n; + + db_find_and_open_repository(0,0); + if( g.argc!=4 ) usage("VERSION1 VERSION2"); + iFrom = name_to_rid(g.argv[2]); + iTo = name_to_rid(g.argv[3]); + iPivot = path_common_ancestor(iFrom, iTo); + for(n=1, p=path.pStart; p; p=p->u.pTo, n++){ + char *z; + z = db_text(0, + "SELECT substr(uuid,1,12) || ' ' || datetime(mtime)" + " FROM blob, event" + " WHERE blob.rid=%d AND event.objid=%d AND event.type='ci'", + p->rid, p->rid); + fossil_print("%4d: %5d %s", n, p->rid, z); + fossil_free(z); + if( p->rid==iFrom ) fossil_print(" VERSION1"); + if( p->rid==iTo ) fossil_print(" VERSION2"); + if( p->rid==iPivot ) fossil_print(" PIVOT"); + fossil_print("\n"); + } +} + + +/* +** A record of a file rename operation. +*/ +typedef struct NameChange NameChange; +struct NameChange { + int origName; /* Original name of file */ + int curName; /* Current name of the file */ + int newName; /* Name of file in next version */ + NameChange *pNext; /* List of all name changes */ +}; + +/* +** Compute all file name changes that occur going from check-in iFrom +** to check-in iTo. +** +** 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. +** +** Space to hold *piChng is obtained from fossil_malloc() and should +** be released by the caller. +** +** This routine really has nothing to do with path. It is located +** in this path.c module in order to leverage some of the path +** infrastructure. +*/ +void find_filename_changes( + int iFrom, /* Ancestor check-in */ + int iTo, /* Recent check-in */ + int revOK, /* OK to move backwards (child->parent) if true */ + int *pnChng, /* Number of name changes along the path */ + int **aiChng, /* Name changes */ + const char *zDebug /* Generate trace output if no NULL */ +){ + PathNode *p; /* For looping over path from iFrom to iTo */ + NameChange *pAll = 0; /* List of all name changes seen so far */ + NameChange *pChng; /* For looping through the name change list */ + int nChng = 0; /* Number of files whose names have changed */ + int *aChng; /* Two integers per name change */ + int i; /* Loop counter */ + Stmt q1; /* Query of name changes */ + + *pnChng = 0; + *aiChng = 0; + if(0==iFrom){ + fossil_fatal("Invalid 'from' RID: 0"); + }else if(0==iTo){ + fossil_fatal("Invalid 'to' RID: 0"); + } + if( iFrom==iTo ) return; + path_reset(); + p = path_shortest(iFrom, iTo, 1, revOK==0, 0); + if( p==0 ) return; + path_reverse_path(); + db_prepare(&q1, + "SELECT pfnid, fnid FROM mlink" + " WHERE mid=:mid AND (pfnid>0 OR fid==0)" + " ORDER BY pfnid" + ); + for(p=path.pStart; p; p=p->u.pTo){ + int fnid, pfnid; + if( !p->fromIsParent && (p->u.pTo==0 || p->u.pTo->fromIsParent) ){ + /* Skip nodes where the parent is not on the path */ + continue; + } + db_bind_int(&q1, ":mid", p->rid); + while( db_step(&q1)==SQLITE_ROW ){ + fnid = db_column_int(&q1, 1); + pfnid = db_column_int(&q1, 0); + if( pfnid==0 ){ + pfnid = fnid; + fnid = 0; + } + if( !p->fromIsParent ){ + int t = fnid; + fnid = pfnid; + pfnid = t; + } + 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)); + } + for(pChng=pAll; pChng; pChng=pChng->pNext){ + if( pChng->curName==pfnid ){ + pChng->newName = fnid; + break; + } + } + if( pChng==0 && fnid>0 ){ + pChng = fossil_malloc( sizeof(*pChng) ); + 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; + } + db_reset(&q1); + } + db_finalize(&q1); + if( nChng ){ + aChng = *aiChng = fossil_malloc( nChng*2*sizeof(int) ); + 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( 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])); + } + i += 2; + } + *pnChng = i/2; + while( pAll ){ + pChng = pAll; + pAll = pAll->pNext; + fossil_free(pChng); + } + } +} + +/* +** COMMAND: test-name-changes +** +** Usage: %fossil test-name-changes [--debug] VERSION1 VERSION2 +** +** Show all filename changes that occur going from VERSION1 to VERSION2 +*/ +void test_name_change(void){ + int iFrom; + int iTo; + int *aChng; + int nChng; + int i; + const char *zDebug = 0; + int revOK = 0; + + db_find_and_open_repository(0,0); + zDebug = find_option("debug",0,0)!=0 ? "debug" : 0; + revOK = find_option("bidirectional",0,0)!=0; + if( g.argc<4 ) usage("VERSION1 VERSION2"); + while( g.argc>=4 ){ + iFrom = name_to_rid(g.argv[2]); + iTo = name_to_rid(g.argv[3]); + find_filename_changes(iFrom, iTo, revOK, &nChng, &aChng, zDebug); + fossil_print("------ Changes for (%d) %s -> (%d) %s\n", + iFrom, g.argv[2], iTo, g.argv[3]); + for(i=0; i [%s]\n", zFrom, zTo); + fossil_free(zFrom); + fossil_free(zTo); + } + fossil_free(aChng); + g.argv += 2; + g.argc -= 2; + } +} + +/* Query to extract all rename operations */ +static const char zRenameQuery[] = +@ CREATE TEMP TABLE renames AS +@ SELECT +@ datetime(event.mtime) AS date, +@ F.name AS old_name, +@ T.name AS new_name, +@ blob.uuid AS checkin +@ FROM mlink, filename F, filename T, event, blob +@ WHERE coalesce(mlink.pfnid,0)!=0 AND mlink.pfnid!=mlink.fnid +@ AND F.fnid=mlink.pfnid +@ AND T.fnid=mlink.fnid +@ AND event.objid=mlink.mid +@ AND event.type='ci' +@ AND blob.rid=mlink.mid; +; + +/* Query to extract distinct rename operations */ +static const char zDistinctRenameQuery[] = +@ CREATE TEMP TABLE renames AS +@ SELECT +@ min(datetime(event.mtime)) AS date, +@ F.name AS old_name, +@ T.name AS new_name, +@ blob.uuid AS checkin +@ FROM mlink, filename F, filename T, event, blob +@ WHERE coalesce(mlink.pfnid,0)!=0 AND mlink.pfnid!=mlink.fnid +@ AND F.fnid=mlink.pfnid +@ AND T.fnid=mlink.fnid +@ AND event.objid=mlink.mid +@ AND event.type='ci' +@ AND blob.rid=mlink.mid +@ GROUP BY 2, 3; +; + +/* +** WEBPAGE: test-rename-list +** +** Print a list of all file rename operations throughout history. +** This page is intended for testing purposes only and may change +** or be discontinued without notice. +*/ +void test_rename_list_page(void){ + Stmt q; + int nRename; + int nCheckin; + + login_check_credentials(); + if( !g.perm.Read ){ login_needed(g.anon.Read); return; } + style_set_current_feature("test"); + if( P("all")!=0 ){ + style_header("List Of All Filename Changes"); + db_multi_exec("%s", zRenameQuery/*safe-for-%s*/); + style_submenu_element("Distinct", "%R/test-rename-list"); + }else{ + style_header("List Of Distinct Filename Changes"); + db_multi_exec("%s", zDistinctRenameQuery/*safe-for-%s*/); + style_submenu_element("All", "%R/test-rename-list?all"); + } + nRename = db_int(0, "SELECT count(*) FROM renames;"); + nCheckin = db_int(0, "SELECT count(DISTINCT checkin) FROM renames;"); + db_prepare(&q, "SELECT date, old_name, new_name, checkin FROM renames" + " ORDER BY date DESC, old_name ASC"); + @

      %d(nRename) filename changes in %d(nCheckin) check-ins

      + @ + @ + @ + @ + @ + while( db_step(&q)==SQLITE_ROW ){ + const char *zDate = db_column_text(&q, 0); + const char *zOld = db_column_text(&q, 1); + const char *zNew = db_column_text(&q, 2); + const char *zUuid = db_column_text(&q, 3); + @ + @ + @ + @ + @ + } + @
      Date & TimeOld NameNew NameCheck-in
      %z(href("%R/timeline?c=%t",zDate))%s(zDate)%z(href("%R/finfo?name=%t",zOld))%h(zOld)%z(href("%R/finfo?name=%t",zNew))%h(zNew)%z(href("%R/info/%!S",zUuid))%S(zUuid)
      + db_finalize(&q); + style_table_sorter(); + style_finish_page(); +} ADDED src/piechart.c Index: src/piechart.c ================================================================== --- /dev/null +++ src/piechart.c @@ -0,0 +1,335 @@ +/* +** Copyright (c) 2015 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 code for generating pie charts on web pages. +** +*/ +#include "config.h" +#include "piechart.h" +#include + +#ifndef M_PI +# define M_PI 3.1415926535897932385 +#endif + +/* +** Return an RGB color name given HSV values. The HSV values +** must each be between between 0 and 255. The string +** returned is held in a static buffer and is overwritten +** on each call. +*/ +const char *rgbName(unsigned char h, unsigned char s, unsigned char v){ + static char zColor[8]; + unsigned char A, B, C, r, g, b; + unsigned int i, m; + if( s==0 ){ + r = g = b = v; + }else{ + i = (h*6)/256; + m = (h*6)&0xff; + A = v*(256-s)/256; + B = v*(65536-s*m)/65536; + C = v*(65536-s*(256-m))/65536; + @ + switch( i ){ + case 0: r=v; g=C; b=A; break; + case 1: r=B; g=v; b=A; break; + case 2: r=A; g=v; b=C; break; + case 3: r=A; g=B; b=v; break; + case 4: r=C; g=A; b=v; break; + default: r=v; g=A; b=B; break; + } + } + sqlite3_snprintf(sizeof(zColor),zColor,"#%02x%02x%02x",r,g,b); + return zColor; +} + +/* +** Flags that can be passed into the pie-chart generator +*/ +#if INTERFACE +#define PIE_OTHER 0x0001 /* No wedge less than 1/60th of the circle */ +#define PIE_CHROMATIC 0x0002 /* Wedge colors are in chromatic order */ +#define PIE_PERCENT 0x0004 /* Add "(XX%)" marks on each label */ +#endif + +/* +** A pie-chart wedge label +*/ +struct WedgeLabel { + double rCos, rSin; /* Sine and Cosine of center angle of wedge */ + char *z; /* Label to draw on this wedge */ +}; +typedef struct WedgeLabel WedgeLabel; + +/* +** Comparison callback for qsort() to sort labels in order of increasing +** distance above and below the horizontal centerline. +*/ +static int wedgeCompare(const void *a, const void *b){ + const WedgeLabel *pA = (const WedgeLabel*)a; + const WedgeLabel *pB = (const WedgeLabel*)b; + double rA = fabs(pA->rCos); + double rB = fabs(pB->rCos); + if( rArB ) return +1; + return 0; +} + +/* +** Output HTML that will render a pie chart using data from +** the PIECHART temporary table. +** +** The schema for the PIECHART table should be: +** +** CREATE TEMP TABLE piechart(amt REAL, label TEXT); +*/ +void piechart_render(int width, int height, unsigned int pieFlags){ + Stmt q; + double cx, cy; /* center of the pie */ + double r, r2; /* Radius of the pie */ + double x1,y1; /* Start of the slice */ + double x2,y2; /* End of the slice */ + double x3,y3; /* Middle point of the slice */ + double x4,y4; /* End of line extending from x3,y3 */ + double x5,y5; /* Text anchor */ + double d1; /* radius to x4,y4 */ + const char *zAnc; /* Anchor point for text */ + double a1 = 0.0; /* Angle for first edge of slice */ + double a2; /* Angle for second edge */ + double a3; /* Angle at middle of slice */ + unsigned char h; /* Hue */ + const char *zClr; /* Color */ + int l; /* Large arc flag */ + int j; /* Wedge number */ + double rTotal; /* Total piechart.amt */ + double rTooSmall; /* Sum of pieChart.amt entries less than 1/60th */ + int nTotal; /* Total number of entries in piechart */ + int nTooSmall; /* Number of pieChart.amt entries less than 1/60th */ + const char *zFg; /* foreground color for lines and text */ + int nWedgeAlloc = 0; /* Slots allocated for aWedge[] */ + int nWedge = 0; /* Slots used for aWedge[] */ + WedgeLabel *aWedge = 0; /* Labels */ + double rUprRight; /* Floor for next label in the upper right quadrant */ + double rUprLeft; /* Floor for next label in the upper left quadrant */ + double rLwrRight; /* Ceiling for label in the lower right quadrant */ + double rLwrLeft; /* Ceiling for label in the lower left quadrant */ + int i; /* Loop counter looping over wedge labels */ + +# define SATURATION 128 +# define VALUE 192 +# define OTHER_CUTOFF 90.0 +# define TEXT_HEIGHT 15.0 + + cx = 0.5*width; + cy = 0.5*height; + r2 = cx1 ){ + db_prepare(&q, "SELECT sum(amt), count(*) FROM piechart WHERE amt<:amt"); + db_bind_double(&q, ":amt", rTotal/OTHER_CUTOFF); + if( db_step(&q)==SQLITE_ROW ){ + rTooSmall = db_column_double(&q, 0); + nTooSmall = db_column_double(&q, 1); + } + db_finalize(&q); + } + if( nTooSmall>1 ){ + db_prepare(&q, "SELECT amt, label FROM piechart WHERE amt>=:limit" + " UNION ALL SELECT %.17g, '%d others';", + rTooSmall, nTooSmall); + db_bind_double(&q, ":limit", rTotal/OTHER_CUTOFF); + nTotal += 1 - nTooSmall; + }else{ + db_prepare(&q, "SELECT amt, label FROM piechart"); + } + if( nTotal<=10 ) pieFlags |= PIE_CHROMATIC; + for(j=0; db_step(&q)==SQLITE_ROW; j++){ + double x = db_column_double(&q,0)/rTotal; + const char *zLbl = db_column_text(&q,1); + /* @ */ + if( x<=0.0 ) continue; + x1 = cx + sin(a1)*r; + y1 = cy - cos(a1)*r; + a2 = a1 + x*2.0*M_PI; + x2 = cx + sin(a2)*r; + y2 = cy - cos(a2)*r; + a3 = 0.5*(a1+a2); + if( nWedge+1>nWedgeAlloc ){ + nWedgeAlloc = nWedgeAlloc*2 + 40; + aWedge = fossil_realloc(aWedge, sizeof(aWedge[0])*nWedgeAlloc); + } + if( pieFlags & PIE_PERCENT ){ + int pct = (int)(x*100.0 + 0.5); + aWedge[nWedge].z = mprintf("%s (%d%%)", zLbl, pct); + }else{ + aWedge[nWedge].z = fossil_strdup(zLbl); + } + aWedge[nWedge].rSin = sin(a3); + aWedge[nWedge].rCos = cos(a3); + nWedge++; + if( (j&1)==0 || (pieFlags & PIE_CHROMATIC)!=0 ){ + h = 256*j/nTotal; + }else if( j+2=0.5; + a1 = a2; + @ + } + qsort(aWedge, nWedge, sizeof(aWedge[0]), wedgeCompare); + rUprLeft = height; + rLwrLeft = 0; + rUprRight = height; + rLwrRight = 0; + d1 = r*1.1; + for(i=0; irSin*r; + y3 = cy - p->rCos*r; + x4 = cx + p->rSin*d1; + y4 = cy - p->rCos*d1; + if( y4<=cy ){ + if( x4>=cx ){ + if( y4>rUprRight ){ + y4 = rUprRight; + } + rUprRight = y4 - TEXT_HEIGHT; + }else{ + if( y4>rUprLeft ){ + y4 = rUprLeft; + } + rUprLeft = y4 - TEXT_HEIGHT; + } + }else{ + if( x4>=cx ){ + if( y4rCos); + @ + @ %h(p->z) + fossil_free(p->z); + } + db_finalize(&q); + fossil_free(aWedge); +} + +/* +** WEBPAGE: test-piechart +** +** Generate a pie-chart based on data input from a form. +*/ +void piechart_test_page(void){ + const char *zData; + Stmt ins; + int n = 0; + int width; + int height; + int i, j; + + login_check_credentials(); + style_set_current_feature("test"); + style_header("Pie Chart Test"); + db_multi_exec("CREATE TEMP TABLE piechart(amt REAL, label TEXT);"); + db_prepare(&ins, "INSERT INTO piechart(amt,label) VALUES(:amt,:label)"); + zData = PD("data",""); + width = atoi(PD("width","800")); + height = atoi(PD("height","400")); + i = 0; + while( zData[i] ){ + double rAmt; + char *zLabel; + while( fossil_isspace(zData[i]) ){ i++; } + j = i; + while( fossil_isdigit(zData[j]) ){ j++; } + if( zData[j]=='.' ){ + j++; + while( fossil_isdigit(zData[j]) ){ j++; } + } + if( i==j ) break; + rAmt = atof(&zData[i]); + i = j; + while( zData[i]==',' || fossil_isspace(zData[i]) ){ i++; } + n++; + zLabel = mprintf("label%02d-%g", n, rAmt); + db_bind_double(&ins, ":amt", rAmt); + db_bind_text(&ins, ":label", zLabel); + db_step(&ins); + db_reset(&ins); + fossil_free(zLabel); + } + db_finalize(&ins); + if( n>1 ){ + @ + piechart_render(width,height, PIE_OTHER|PIE_PERCENT); + @ + @
      + } + @
      + @

      Comma-separated list of slice widths:
      + @
      + @ Width: + @ Height:
      + @ + @

      + @

      Interesting test cases: + @

      + style_finish_page(); +} ADDED src/pikchr.c Index: src/pikchr.c ================================================================== --- /dev/null +++ src/pikchr.c @@ -0,0 +1,8006 @@ +/* This file is automatically generated by Lemon from input grammar +** source file "pikchr.y". */ +/* +** Zero-Clause BSD license: +** +** Copyright (C) 2020-09-01 by D. Richard Hipp +** +** Permission to use, copy, modify, and/or distribute this software for +** any purpose with or without fee is hereby granted. +** +**************************************************************************** +** +** This software translates a PIC-inspired diagram language into SVG. +** +** PIKCHR (pronounced like "picture") is *mostly* backwards compatible +** with legacy PIC, though some features of legacy PIC are removed +** (for example, the "sh" command is removed for security) and +** many enhancements are added. +** +** PIKCHR is designed for use in an internet facing web environment. +** In particular, PIKCHR is designed to safely generate benign SVG from +** source text that provided by a hostile agent. +** +** This code was originally written by D. Richard Hipp using documentation +** from prior PIC implementations but without reference to prior code. +** All of the code in this project is original. +** +** This file implements a C-language subroutine that accepts a string +** of PIKCHR language text and generates a second string of SVG output that +** renders the drawing defined by the input. Space to hold the returned +** string is obtained from malloc() and should be freed by the caller. +** NULL might be returned if there is a memory allocation error. +** +** If there are errors in the PIKCHR input, the output will consist of an +** error message and the original PIKCHR input text (inside of
      ...
      ). +** +** The subroutine implemented by this file is intended to be stand-alone. +** It uses no external routines other than routines commonly found in +** the standard C library. +** +**************************************************************************** +** COMPILING: +** +** The original source text is a mixture of C99 and "Lemon" +** (See https://sqlite.org/src/file/doc/lemon.html). Lemon is an LALR(1) +** parser generator program, similar to Yacc. The grammar of the +** input language is specified in Lemon. C-code is attached. Lemon +** runs to generate a single output file ("pikchr.c") which is then +** compiled to generate the Pikchr library. This header comment is +** preserved in the Lemon output, so you might be reading this in either +** the generated "pikchr.c" file that is output by Lemon, or in the +** "pikchr.y" source file that is input into Lemon. If you make changes, +** you should change the input source file "pikchr.y", not the +** Lemon-generated output file. +** +** Basic compilation steps: +** +** lemon pikchr.y +** cc pikchr.c -o pikchr.o +** +** Add -DPIKCHR_SHELL to add a main() routine that reads input files +** and sends them through Pikchr, for testing. Add -DPIKCHR_FUZZ for +** -fsanitizer=fuzzer testing. +** +**************************************************************************** +** IMPLEMENTATION NOTES (for people who want to understand the internal +** operation of this software, perhaps to extend the code or to fix bugs): +** +** Each call to pikchr() uses a single instance of the Pik structure to +** track its internal state. The Pik structure lives for the duration +** of the pikchr() call. +** +** The input is a sequence of objects or "statements". Each statement is +** parsed into a PObj object. These are stored on an extensible array +** called PList. All parameters to each PObj are computed as the +** object is parsed. (Hence, the parameters to a PObj may only refer +** to prior statements.) Once the PObj is completely assembled, it is +** added to the end of a PList and never changes thereafter - except, +** PObj objects that are part of a "[...]" block might have their +** absolute position shifted when the outer [...] block is positioned. +** But apart from this repositioning, PObj objects are unchanged once +** they are added to the list. The order of statements on a PList does +** not change. +** +** After all input has been parsed, the top-level PList is walked to +** generate output. Sub-lists resulting from [...] blocks are scanned +** as they are encountered. All input must be collected and parsed ahead +** of output generation because the size and position of statements must be +** known in order to compute a bounding box on the output. +** +** Each PObj is on a "layer". (The common case is that all PObj's are +** on a single layer, but multiple layers are possible.) A separate pass +** is made through the list for each layer. +** +** After all output is generated, the Pik object and all the PList +** and PObj objects are deallocated and the generated output string is +** returned. Upon any error, the Pik.nErr flag is set, processing quickly +** stops, and the stack unwinds. No attempt is made to continue reading +** input after an error. +** +** Most statements begin with a class name like "box" or "arrow" or "move". +** There is a class named "text" which is used for statements that begin +** with a string literal. You can also specify the "text" class. +** A Sublist ("[...]") is a single object that contains a pointer to +** its substatements, all gathered onto a separate PList object. +** +** Variables go into PVar objects that form a linked list. +** +** Each PObj has zero or one names. Input constructs that attempt +** to assign a new name from an older name, for example: +** +** Abc: Abc + (0.5cm, 0) +** +** Statements like these generate a new "noop" object at the specified +** place and with the given name. As place-names are searched by scanning +** the list in reverse order, this has the effect of overriding the "Abc" +** name when referenced by subsequent objects. +*/ +#include +#include +#include +#include +#include +#include +#define count(X) (sizeof(X)/sizeof(X[0])) +#ifndef M_PI +# define M_PI 3.1415926535897932385 +#endif + +/* Tag intentionally unused parameters with this macro to prevent +** compiler warnings with -Wextra */ +#define UNUSED_PARAMETER(X) (void)(X) + +typedef struct Pik Pik; /* Complete parsing context */ +typedef struct PToken PToken; /* A single token */ +typedef struct PObj PObj; /* A single diagram object */ +typedef struct PList PList; /* A list of diagram objects */ +typedef struct PClass PClass; /* Description of statements types */ +typedef double PNum; /* Numeric value */ +typedef struct PRel PRel; /* Absolute or percentage value */ +typedef struct PPoint PPoint; /* A position in 2-D space */ +typedef struct PVar PVar; /* script-defined variable */ +typedef struct PBox PBox; /* A bounding box */ +typedef struct PMacro PMacro; /* A "define" macro */ + +/* Compass points */ +#define CP_N 1 +#define CP_NE 2 +#define CP_E 3 +#define CP_SE 4 +#define CP_S 5 +#define CP_SW 6 +#define CP_W 7 +#define CP_NW 8 +#define CP_C 9 /* .center or .c */ +#define CP_END 10 /* .end */ +#define CP_START 11 /* .start */ + +/* Heading angles corresponding to compass points */ +static const PNum pik_hdg_angle[] = { +/* none */ 0.0, + /* N */ 0.0, + /* NE */ 45.0, + /* E */ 90.0, + /* SE */ 135.0, + /* S */ 180.0, + /* SW */ 225.0, + /* W */ 270.0, + /* NW */ 315.0, + /* C */ 0.0, +}; + +/* Built-in functions */ +#define FN_ABS 0 +#define FN_COS 1 +#define FN_INT 2 +#define FN_MAX 3 +#define FN_MIN 4 +#define FN_SIN 5 +#define FN_SQRT 6 + +/* Text position and style flags. Stored in PToken.eCode so limited +** to 15 bits. */ +#define TP_LJUST 0x0001 /* left justify...... */ +#define TP_RJUST 0x0002 /* ...Right justify */ +#define TP_JMASK 0x0003 /* Mask for justification bits */ +#define TP_ABOVE2 0x0004 /* Position text way above PObj.ptAt */ +#define TP_ABOVE 0x0008 /* Position text above PObj.ptAt */ +#define TP_CENTER 0x0010 /* On the line */ +#define TP_BELOW 0x0020 /* Position text below PObj.ptAt */ +#define TP_BELOW2 0x0040 /* Position text way below PObj.ptAt */ +#define TP_VMASK 0x007c /* Mask for text positioning flags */ +#define TP_BIG 0x0100 /* Larger font */ +#define TP_SMALL 0x0200 /* Smaller font */ +#define TP_XTRA 0x0400 /* Amplify TP_BIG or TP_SMALL */ +#define TP_SZMASK 0x0700 /* Font size mask */ +#define TP_ITALIC 0x1000 /* Italic font */ +#define TP_BOLD 0x2000 /* Bold font */ +#define TP_FMASK 0x3000 /* Mask for font style */ +#define TP_ALIGN 0x4000 /* Rotate to align with the line */ + +/* An object to hold a position in 2-D space */ +struct PPoint { + PNum x, y; /* X and Y coordinates */ +}; +static const PPoint cZeroPoint = {0.0,0.0}; + +/* A bounding box */ +struct PBox { + PPoint sw, ne; /* Lower-left and top-right corners */ +}; + +/* An Absolute or a relative distance. The absolute distance +** is stored in rAbs and the relative distance is stored in rRel. +** Usually, one or the other will be 0.0. When using a PRel to +** update an existing value, the computation is usually something +** like this: +** +** value = PRel.rAbs + value*PRel.rRel +** +*/ +struct PRel { + PNum rAbs; /* Absolute value */ + PNum rRel; /* Value relative to current value */ +}; + +/* A variable created by the ID = EXPR construct of the PIKCHR script +** +** PIKCHR (and PIC) scripts do not use many varaibles, so it is reasonable +** to store them all on a linked list. +*/ +struct PVar { + const char *zName; /* Name of the variable */ + PNum val; /* Value of the variable */ + PVar *pNext; /* Next variable in a list of them all */ +}; + +/* A single token in the parser input stream +*/ +struct PToken { + const char *z; /* Pointer to the token text */ + unsigned int n; /* Length of the token in bytes */ + short int eCode; /* Auxiliary code */ + unsigned char eType; /* The numeric parser code */ + unsigned char eEdge; /* Corner value for corner keywords */ +}; + +/* Return negative, zero, or positive if pToken is less than, equal to +** or greater than the zero-terminated string z[] +*/ +static int pik_token_eq(PToken *pToken, const char *z){ + int c = strncmp(pToken->z,z,pToken->n); + if( c==0 && z[pToken->n]!=0 ) c = -1; + return c; +} + +/* Extra token types not generated by LEMON but needed by the +** tokenizer +*/ +#define T_PARAMETER 253 /* $1, $2, ..., $9 */ +#define T_WHITESPACE 254 /* Whitespace of comments */ +#define T_ERROR 255 /* Any text that is not a valid token */ + +/* Directions of movement */ +#define DIR_RIGHT 0 +#define DIR_DOWN 1 +#define DIR_LEFT 2 +#define DIR_UP 3 +#define ValidDir(X) ((X)>=0 && (X)<=3) +#define IsUpDown(X) (((X)&1)==1) +#define IsLeftRight(X) (((X)&1)==0) + +/* Bitmask for the various attributes for PObj. These bits are +** collected in PObj.mProp and PObj.mCalc to check for constraint +** errors. */ +#define A_WIDTH 0x0001 +#define A_HEIGHT 0x0002 +#define A_RADIUS 0x0004 +#define A_THICKNESS 0x0008 +#define A_DASHED 0x0010 /* Includes "dotted" */ +#define A_FILL 0x0020 +#define A_COLOR 0x0040 +#define A_ARROW 0x0080 +#define A_FROM 0x0100 +#define A_CW 0x0200 +#define A_AT 0x0400 +#define A_TO 0x0800 /* one or more movement attributes */ +#define A_FIT 0x1000 + + +/* A single graphics object */ +struct PObj { + const PClass *type; /* Object type or class */ + PToken errTok; /* Reference token for error messages */ + PPoint ptAt; /* Reference point for the object */ + PPoint ptEnter, ptExit; /* Entry and exit points */ + PList *pSublist; /* Substructure for [...] objects */ + char *zName; /* Name assigned to this statement */ + PNum w; /* "width" property */ + PNum h; /* "height" property */ + PNum rad; /* "radius" property */ + PNum sw; /* "thickness" property. (Mnemonic: "stroke width")*/ + PNum dotted; /* "dotted" property. <=0.0 for off */ + PNum dashed; /* "dashed" property. <=0.0 for off */ + PNum fill; /* "fill" property. Negative for off */ + PNum color; /* "color" property */ + PPoint with; /* Position constraint from WITH clause */ + char eWith; /* Type of heading point on WITH clause */ + char cw; /* True for clockwise arc */ + char larrow; /* Arrow at beginning (<- or <->) */ + char rarrow; /* Arrow at end (-> or <->) */ + char bClose; /* True if "close" is seen */ + char bChop; /* True if "chop" is seen */ + unsigned char nTxt; /* Number of text values */ + unsigned mProp; /* Masks of properties set so far */ + unsigned mCalc; /* Values computed from other constraints */ + PToken aTxt[5]; /* Text with .eCode holding TP flags */ + int iLayer; /* Rendering order */ + int inDir, outDir; /* Entry and exit directions */ + int nPath; /* Number of path points */ + PPoint *aPath; /* Array of path points */ + PBox bbox; /* Bounding box */ +}; + +/* A list of graphics objects */ +struct PList { + int n; /* Number of statements in the list */ + int nAlloc; /* Allocated slots in a[] */ + PObj **a; /* Pointers to individual objects */ +}; + +/* A macro definition */ +struct PMacro { + PMacro *pNext; /* Next in the list */ + PToken macroName; /* Name of the macro */ + PToken macroBody; /* Body of the macro */ + int inUse; /* Do not allow recursion */ +}; + +/* Each call to the pikchr() subroutine uses an instance of the following +** object to pass around context to all of its subroutines. +*/ +struct Pik { + unsigned nErr; /* Number of errors seen */ + PToken sIn; /* Input Pikchr-language text */ + char *zOut; /* Result accumulates here */ + unsigned int nOut; /* Bytes written to zOut[] so far */ + unsigned int nOutAlloc; /* Space allocated to zOut[] */ + unsigned char eDir; /* Current direction */ + unsigned int mFlags; /* Flags passed to pikchr() */ + PObj *cur; /* Object under construction */ + PList *list; /* Object list under construction */ + PMacro *pMacros; /* List of all defined macros */ + PVar *pVar; /* Application-defined variables */ + PBox bbox; /* Bounding box around all statements */ + /* Cache of layout values. <=0.0 for unknown... */ + PNum rScale; /* Multiply to convert inches to pixels */ + PNum fontScale; /* Scale fonts by this percent */ + PNum charWidth; /* Character width */ + PNum charHeight; /* Character height */ + PNum wArrow; /* Width of arrowhead at the fat end */ + PNum hArrow; /* Ht of arrowhead - dist from tip to fat end */ + char bLayoutVars; /* True if cache is valid */ + char thenFlag; /* True if "then" seen */ + char samePath; /* aTPath copied by "same" */ + const char *zClass; /* Class name for the */ + int wSVG, hSVG; /* Width and height of the */ + int fgcolor; /* foreground color value, or -1 for none */ + int bgcolor; /* background color value, or -1 for none */ + /* Paths for lines are constructed here first, then transferred into + ** the PObj object at the end: */ + int nTPath; /* Number of entries on aTPath[] */ + int mTPath; /* For last entry, 1: x set, 2: y set */ + PPoint aTPath[1000]; /* Path under construction */ + /* Error contexts */ + unsigned int nCtx; /* Number of error contexts */ + PToken aCtx[10]; /* Nested error contexts */ +}; + +/* Include PIKCHR_PLAINTEXT_ERRORS among the bits of mFlags on the 3rd +** argument to pikchr() in order to cause error message text to come out +** as text/plain instead of as text/html +*/ +#define PIKCHR_PLAINTEXT_ERRORS 0x0001 + +/* Include PIKCHR_DARK_MODE among the mFlag bits to invert colors. +*/ +#define PIKCHR_DARK_MODE 0x0002 + +/* +** The behavior of an object class is defined by an instance of +** this structure. This is the "virtual method" table. +*/ +struct PClass { + const char *zName; /* Name of class */ + char isLine; /* True if a line class */ + char eJust; /* Use box-style text justification */ + void (*xInit)(Pik*,PObj*); /* Initializer */ + void (*xNumProp)(Pik*,PObj*,PToken*); /* Value change notification */ + void (*xCheck)(Pik*,PObj*); /* Checks to do after parsing */ + PPoint (*xChop)(Pik*,PObj*,PPoint*); /* Chopper */ + PPoint (*xOffset)(Pik*,PObj*,int); /* Offset from .c to edge point */ + void (*xFit)(Pik*,PObj*,PNum w,PNum h); /* Size to fit text */ + void (*xRender)(Pik*,PObj*); /* Render */ +}; + + +/* Forward declarations */ +static void pik_append(Pik*, const char*,int); +static void pik_append_text(Pik*,const char*,int,int); +static void pik_append_num(Pik*,const char*,PNum); +static void pik_append_point(Pik*,const char*,PPoint*); +static void pik_append_x(Pik*,const char*,PNum,const char*); +static void pik_append_y(Pik*,const char*,PNum,const char*); +static void pik_append_xy(Pik*,const char*,PNum,PNum); +static void pik_append_dis(Pik*,const char*,PNum,const char*); +static void pik_append_arc(Pik*,PNum,PNum,PNum,PNum); +static void pik_append_clr(Pik*,const char*,PNum,const char*,int); +static void pik_append_style(Pik*,PObj*,int); +static void pik_append_txt(Pik*,PObj*, PBox*); +static void pik_draw_arrowhead(Pik*,PPoint*pFrom,PPoint*pTo,PObj*); +static void pik_chop(PPoint*pFrom,PPoint*pTo,PNum); +static void pik_error(Pik*,PToken*,const char*); +static void pik_elist_free(Pik*,PList*); +static void pik_elem_free(Pik*,PObj*); +static void pik_render(Pik*,PList*); +static PList *pik_elist_append(Pik*,PList*,PObj*); +static PObj *pik_elem_new(Pik*,PToken*,PToken*,PList*); +static void pik_set_direction(Pik*,int); +static void pik_elem_setname(Pik*,PObj*,PToken*); +static void pik_set_var(Pik*,PToken*,PNum,PToken*); +static PNum pik_value(Pik*,const char*,int,int*); +static PNum pik_lookup_color(Pik*,PToken*); +static PNum pik_get_var(Pik*,PToken*); +static PNum pik_atof(PToken*); +static void pik_after_adding_attributes(Pik*,PObj*); +static void pik_elem_move(PObj*,PNum dx, PNum dy); +static void pik_elist_move(PList*,PNum dx, PNum dy); +static void pik_set_numprop(Pik*,PToken*,PRel*); +static void pik_set_clrprop(Pik*,PToken*,PNum); +static void pik_set_dashed(Pik*,PToken*,PNum*); +static void pik_then(Pik*,PToken*,PObj*); +static void pik_add_direction(Pik*,PToken*,PRel*); +static void pik_move_hdg(Pik*,PRel*,PToken*,PNum,PToken*,PToken*); +static void pik_evenwith(Pik*,PToken*,PPoint*); +static void pik_set_from(Pik*,PObj*,PToken*,PPoint*); +static void pik_add_to(Pik*,PObj*,PToken*,PPoint*); +static void pik_close_path(Pik*,PToken*); +static void pik_set_at(Pik*,PToken*,PPoint*,PToken*); +static short int pik_nth_value(Pik*,PToken*); +static PObj *pik_find_nth(Pik*,PObj*,PToken*); +static PObj *pik_find_byname(Pik*,PObj*,PToken*); +static PPoint pik_place_of_elem(Pik*,PObj*,PToken*); +static int pik_bbox_isempty(PBox*); +static void pik_bbox_init(PBox*); +static void pik_bbox_addbox(PBox*,PBox*); +static void pik_bbox_add_xy(PBox*,PNum,PNum); +static void pik_bbox_addellipse(PBox*,PNum x,PNum y,PNum rx,PNum ry); +static void pik_add_txt(Pik*,PToken*,int); +static int pik_text_length(const PToken *pToken); +static void pik_size_to_fit(Pik*,PToken*,int); +static int pik_text_position(int,PToken*); +static PNum pik_property_of(PObj*,PToken*); +static PNum pik_func(Pik*,PToken*,PNum,PNum); +static PPoint pik_position_between(PNum x, PPoint p1, PPoint p2); +static PPoint pik_position_at_angle(PNum dist, PNum r, PPoint pt); +static PPoint pik_position_at_hdg(PNum dist, PToken *pD, PPoint pt); +static void pik_same(Pik *p, PObj*, PToken*); +static PPoint pik_nth_vertex(Pik *p, PToken *pNth, PToken *pErr, PObj *pObj); +static PToken pik_next_semantic_token(PToken *pThis); +static void pik_compute_layout_settings(Pik*); +static void pik_behind(Pik*,PObj*); +static PObj *pik_assert(Pik*,PNum,PToken*,PNum); +static PObj *pik_position_assert(Pik*,PPoint*,PToken*,PPoint*); +static PNum pik_dist(PPoint*,PPoint*); +static void pik_add_macro(Pik*,PToken *pId,PToken *pCode); + + +#line 505 "pikchr.c" +/**************** End of %include directives **********************************/ +/* These constants specify the various numeric values for terminal symbols. +***************** Begin token definitions *************************************/ +#ifndef T_ID +#define T_ID 1 +#define T_EDGEPT 2 +#define T_OF 3 +#define T_PLUS 4 +#define T_MINUS 5 +#define T_STAR 6 +#define T_SLASH 7 +#define T_PERCENT 8 +#define T_UMINUS 9 +#define T_EOL 10 +#define T_ASSIGN 11 +#define T_PLACENAME 12 +#define T_COLON 13 +#define T_ASSERT 14 +#define T_LP 15 +#define T_EQ 16 +#define T_RP 17 +#define T_DEFINE 18 +#define T_CODEBLOCK 19 +#define T_FILL 20 +#define T_COLOR 21 +#define T_THICKNESS 22 +#define T_PRINT 23 +#define T_STRING 24 +#define T_COMMA 25 +#define T_CLASSNAME 26 +#define T_LB 27 +#define T_RB 28 +#define T_UP 29 +#define T_DOWN 30 +#define T_LEFT 31 +#define T_RIGHT 32 +#define T_CLOSE 33 +#define T_CHOP 34 +#define T_FROM 35 +#define T_TO 36 +#define T_THEN 37 +#define T_HEADING 38 +#define T_GO 39 +#define T_AT 40 +#define T_WITH 41 +#define T_SAME 42 +#define T_AS 43 +#define T_FIT 44 +#define T_BEHIND 45 +#define T_UNTIL 46 +#define T_EVEN 47 +#define T_DOT_E 48 +#define T_HEIGHT 49 +#define T_WIDTH 50 +#define T_RADIUS 51 +#define T_DIAMETER 52 +#define T_DOTTED 53 +#define T_DASHED 54 +#define T_CW 55 +#define T_CCW 56 +#define T_LARROW 57 +#define T_RARROW 58 +#define T_LRARROW 59 +#define T_INVIS 60 +#define T_THICK 61 +#define T_THIN 62 +#define T_SOLID 63 +#define T_CENTER 64 +#define T_LJUST 65 +#define T_RJUST 66 +#define T_ABOVE 67 +#define T_BELOW 68 +#define T_ITALIC 69 +#define T_BOLD 70 +#define T_ALIGNED 71 +#define T_BIG 72 +#define T_SMALL 73 +#define T_AND 74 +#define T_LT 75 +#define T_GT 76 +#define T_ON 77 +#define T_WAY 78 +#define T_BETWEEN 79 +#define T_THE 80 +#define T_NTH 81 +#define T_VERTEX 82 +#define T_TOP 83 +#define T_BOTTOM 84 +#define T_START 85 +#define T_END 86 +#define T_IN 87 +#define T_THIS 88 +#define T_DOT_U 89 +#define T_LAST 90 +#define T_NUMBER 91 +#define T_FUNC1 92 +#define T_FUNC2 93 +#define T_DIST 94 +#define T_DOT_XY 95 +#define T_X 96 +#define T_Y 97 +#define T_DOT_L 98 +#endif +/**************** End token definitions ***************************************/ + +/* The next sections is a series of control #defines. +** various aspects of the generated parser. +** YYCODETYPE is the data type used to store the integer codes +** that represent terminal and non-terminal symbols. +** "unsigned char" is used if there are fewer than +** 256 symbols. Larger types otherwise. +** YYNOCODE is a number of type YYCODETYPE that is not used for +** any terminal or nonterminal symbol. +** YYFALLBACK If defined, this indicates that one or more tokens +** (also known as: "terminal symbols") have fall-back +** values which should be used if the original symbol +** would not parse. This permits keywords to sometimes +** be used as identifiers, for example. +** YYACTIONTYPE is the data type used for "action codes" - numbers +** that indicate what to do in response to the next +** token. +** pik_parserTOKENTYPE is the data type used for minor type for terminal +** symbols. Background: A "minor type" is a semantic +** value associated with a terminal or non-terminal +** symbols. For example, for an "ID" terminal symbol, +** the minor type might be the name of the identifier. +** Each non-terminal can have a different minor type. +** Terminal symbols all have the same minor type, though. +** This macros defines the minor type for terminal +** symbols. +** YYMINORTYPE is the data type used for all minor types. +** This is typically a union of many types, one of +** which is pik_parserTOKENTYPE. The entry in the union +** for terminal symbols is called "yy0". +** YYSTACKDEPTH is the maximum depth of the parser's stack. If +** zero the stack is dynamically sized using realloc() +** pik_parserARG_SDECL A static variable declaration for the %extra_argument +** pik_parserARG_PDECL A parameter declaration for the %extra_argument +** pik_parserARG_PARAM Code to pass %extra_argument as a subroutine parameter +** pik_parserARG_STORE Code to store %extra_argument into yypParser +** pik_parserARG_FETCH Code to extract %extra_argument from yypParser +** pik_parserCTX_* As pik_parserARG_ except for %extra_context +** YYERRORSYMBOL is the code number of the error symbol. If not +** defined, then do no error processing. +** YYNSTATE the combined number of states. +** YYNRULE the number of rules in the grammar +** YYNTOKEN Number of terminal symbols +** YY_MAX_SHIFT Maximum value for shift actions +** YY_MIN_SHIFTREDUCE Minimum value for shift-reduce actions +** YY_MAX_SHIFTREDUCE Maximum value for shift-reduce actions +** YY_ERROR_ACTION The yy_action[] code for syntax error +** YY_ACCEPT_ACTION The yy_action[] code for accept +** YY_NO_ACTION The yy_action[] code for no-op +** YY_MIN_REDUCE Minimum value for reduce actions +** YY_MAX_REDUCE Maximum value for reduce actions +*/ +#ifndef INTERFACE +# define INTERFACE 1 +#endif +/************* Begin control #defines *****************************************/ +#define YYCODETYPE unsigned char +#define YYNOCODE 135 +#define YYACTIONTYPE unsigned short int +#define pik_parserTOKENTYPE PToken +typedef union { + int yyinit; + pik_parserTOKENTYPE yy0; + PRel yy10; + PObj* yy36; + PPoint yy79; + PNum yy153; + short int yy164; + PList* yy227; +} YYMINORTYPE; +#ifndef YYSTACKDEPTH +#define YYSTACKDEPTH 100 +#endif +#define pik_parserARG_SDECL +#define pik_parserARG_PDECL +#define pik_parserARG_PARAM +#define pik_parserARG_FETCH +#define pik_parserARG_STORE +#define pik_parserCTX_SDECL Pik *p; +#define pik_parserCTX_PDECL ,Pik *p +#define pik_parserCTX_PARAM ,p +#define pik_parserCTX_FETCH Pik *p=yypParser->p; +#define pik_parserCTX_STORE yypParser->p=p; +#define YYFALLBACK 1 +#define YYNSTATE 164 +#define YYNRULE 156 +#define YYNRULE_WITH_ACTION 116 +#define YYNTOKEN 99 +#define YY_MAX_SHIFT 163 +#define YY_MIN_SHIFTREDUCE 287 +#define YY_MAX_SHIFTREDUCE 442 +#define YY_ERROR_ACTION 443 +#define YY_ACCEPT_ACTION 444 +#define YY_NO_ACTION 445 +#define YY_MIN_REDUCE 446 +#define YY_MAX_REDUCE 601 +/************* End control #defines *******************************************/ +#define YY_NLOOKAHEAD ((int)(sizeof(yy_lookahead)/sizeof(yy_lookahead[0]))) + +/* Define the yytestcase() macro to be a no-op if is not already defined +** otherwise. +** +** Applications can choose to define yytestcase() in the %include section +** to a macro that can assist in verifying code coverage. For production +** code the yytestcase() macro should be turned off. But it is useful +** for testing. +*/ +#ifndef yytestcase +# define yytestcase(X) +#endif + + +/* Next are the tables used to determine what action to take based on the +** current state and lookahead token. These tables are used to implement +** functions that take a state number and lookahead value and return an +** action integer. +** +** Suppose the action integer is N. Then the action is determined as +** follows +** +** 0 <= N <= YY_MAX_SHIFT Shift N. That is, push the lookahead +** token onto the stack and goto state N. +** +** N between YY_MIN_SHIFTREDUCE Shift to an arbitrary state then +** and YY_MAX_SHIFTREDUCE reduce by rule N-YY_MIN_SHIFTREDUCE. +** +** N == YY_ERROR_ACTION A syntax error has occurred. +** +** N == YY_ACCEPT_ACTION The parser accepts its input. +** +** N == YY_NO_ACTION No such action. Denotes unused +** slots in the yy_action[] table. +** +** N between YY_MIN_REDUCE Reduce by rule N-YY_MIN_REDUCE +** and YY_MAX_REDUCE +** +** The action table is constructed as a single large table named yy_action[]. +** Given state S and lookahead X, the action is computed as either: +** +** (A) N = yy_action[ yy_shift_ofst[S] + X ] +** (B) N = yy_default[S] +** +** The (A) formula is preferred. The B formula is used instead if +** yy_lookahead[yy_shift_ofst[S]+X] is not equal to X. +** +** The formulas above are for computing the action when the lookahead is +** a terminal symbol. If the lookahead is a non-terminal (as occurs after +** a reduce action) then the yy_reduce_ofst[] array is used in place of +** the yy_shift_ofst[] array. +** +** The following are the tables generated in this section: +** +** yy_action[] A single table containing all actions. +** yy_lookahead[] A table containing the lookahead for each entry in +** yy_action. Used to detect hash collisions. +** yy_shift_ofst[] For each state, the offset into yy_action for +** shifting terminals. +** yy_reduce_ofst[] For each state, the offset into yy_action for +** shifting non-terminals after a reduce. +** yy_default[] Default action for each state. +** +*********** Begin parsing tables **********************************************/ +#define YY_ACTTAB_COUNT (1303) +static const YYACTIONTYPE yy_action[] = { + /* 0 */ 575, 495, 161, 119, 25, 452, 29, 74, 129, 148, + /* 10 */ 575, 492, 161, 119, 453, 113, 120, 161, 119, 530, + /* 20 */ 427, 428, 339, 559, 81, 30, 560, 561, 575, 64, + /* 30 */ 63, 62, 61, 322, 323, 9, 8, 33, 149, 32, + /* 40 */ 7, 71, 127, 38, 335, 66, 48, 37, 28, 339, + /* 50 */ 339, 339, 339, 425, 426, 340, 341, 342, 343, 344, + /* 60 */ 345, 346, 347, 348, 474, 528, 161, 119, 577, 77, + /* 70 */ 577, 73, 376, 148, 474, 533, 161, 119, 112, 113, + /* 80 */ 120, 161, 119, 128, 427, 428, 339, 357, 81, 531, + /* 90 */ 161, 119, 474, 36, 330, 13, 306, 322, 323, 9, + /* 100 */ 8, 33, 149, 32, 7, 71, 127, 328, 335, 66, + /* 110 */ 579, 310, 31, 339, 339, 339, 339, 425, 426, 340, + /* 120 */ 341, 342, 343, 344, 345, 346, 347, 348, 394, 435, + /* 130 */ 46, 59, 60, 64, 63, 62, 61, 54, 51, 376, + /* 140 */ 69, 108, 2, 47, 403, 83, 297, 435, 375, 84, + /* 150 */ 117, 80, 35, 308, 79, 133, 122, 126, 441, 440, + /* 160 */ 299, 123, 3, 404, 405, 406, 408, 80, 298, 308, + /* 170 */ 79, 4, 411, 412, 413, 414, 441, 440, 350, 350, + /* 180 */ 350, 350, 350, 350, 350, 350, 350, 350, 62, 61, + /* 190 */ 67, 434, 1, 75, 378, 158, 74, 76, 148, 411, + /* 200 */ 412, 413, 414, 124, 113, 120, 161, 119, 106, 434, + /* 210 */ 436, 437, 438, 439, 5, 375, 6, 117, 393, 155, + /* 220 */ 154, 153, 394, 435, 69, 59, 60, 149, 436, 437, + /* 230 */ 438, 439, 535, 376, 398, 399, 2, 424, 427, 428, + /* 240 */ 339, 156, 156, 156, 423, 394, 435, 65, 59, 60, + /* 250 */ 162, 131, 441, 440, 397, 72, 376, 148, 118, 2, + /* 260 */ 380, 157, 125, 113, 120, 161, 119, 339, 339, 339, + /* 270 */ 339, 425, 426, 535, 11, 441, 440, 394, 356, 535, + /* 280 */ 59, 60, 535, 379, 159, 434, 149, 12, 102, 446, + /* 290 */ 432, 42, 138, 14, 435, 139, 301, 302, 303, 36, + /* 300 */ 305, 430, 106, 16, 436, 437, 438, 439, 434, 375, + /* 310 */ 18, 117, 393, 155, 154, 153, 44, 142, 140, 64, + /* 320 */ 63, 62, 61, 441, 440, 106, 19, 436, 437, 438, + /* 330 */ 439, 45, 375, 20, 117, 393, 155, 154, 153, 68, + /* 340 */ 55, 114, 64, 63, 62, 61, 147, 146, 394, 473, + /* 350 */ 359, 59, 60, 43, 23, 391, 434, 106, 26, 376, + /* 360 */ 57, 58, 42, 49, 375, 392, 117, 393, 155, 154, + /* 370 */ 153, 64, 63, 62, 61, 436, 437, 438, 439, 384, + /* 380 */ 382, 383, 22, 21, 377, 473, 160, 70, 39, 445, + /* 390 */ 24, 445, 145, 141, 431, 142, 140, 64, 63, 62, + /* 400 */ 61, 394, 15, 445, 59, 60, 64, 63, 62, 61, + /* 410 */ 391, 445, 376, 445, 445, 42, 445, 445, 55, 391, + /* 420 */ 156, 156, 156, 445, 147, 146, 445, 52, 106, 445, + /* 430 */ 445, 43, 445, 445, 445, 375, 445, 117, 393, 155, + /* 440 */ 154, 153, 445, 394, 143, 445, 59, 60, 64, 63, + /* 450 */ 62, 61, 313, 445, 376, 378, 158, 42, 445, 445, + /* 460 */ 22, 21, 121, 447, 454, 29, 445, 445, 24, 450, + /* 470 */ 145, 141, 431, 142, 140, 64, 63, 62, 61, 445, + /* 480 */ 163, 106, 445, 445, 444, 27, 445, 445, 375, 445, + /* 490 */ 117, 393, 155, 154, 153, 445, 55, 74, 445, 148, + /* 500 */ 445, 445, 147, 146, 497, 113, 120, 161, 119, 43, + /* 510 */ 445, 394, 445, 445, 59, 60, 445, 445, 445, 118, + /* 520 */ 445, 445, 376, 106, 445, 42, 445, 445, 149, 445, + /* 530 */ 375, 445, 117, 393, 155, 154, 153, 445, 22, 21, + /* 540 */ 394, 144, 445, 59, 60, 445, 24, 445, 145, 141, + /* 550 */ 431, 376, 445, 445, 42, 445, 132, 130, 394, 445, + /* 560 */ 445, 59, 60, 109, 447, 454, 29, 445, 445, 376, + /* 570 */ 450, 445, 42, 445, 394, 445, 445, 59, 60, 445, + /* 580 */ 445, 163, 445, 445, 445, 102, 27, 445, 42, 445, + /* 590 */ 445, 106, 445, 64, 63, 62, 61, 445, 375, 445, + /* 600 */ 117, 393, 155, 154, 153, 394, 355, 445, 59, 60, + /* 610 */ 445, 445, 445, 445, 445, 74, 376, 148, 445, 40, + /* 620 */ 106, 445, 496, 113, 120, 161, 119, 375, 445, 117, + /* 630 */ 393, 155, 154, 153, 445, 448, 454, 29, 106, 445, + /* 640 */ 445, 450, 445, 445, 445, 375, 149, 117, 393, 155, + /* 650 */ 154, 153, 163, 445, 106, 445, 445, 27, 445, 445, + /* 660 */ 445, 375, 445, 117, 393, 155, 154, 153, 394, 445, + /* 670 */ 445, 59, 60, 64, 63, 62, 61, 445, 445, 376, + /* 680 */ 445, 445, 41, 445, 445, 106, 354, 64, 63, 62, + /* 690 */ 61, 445, 375, 445, 117, 393, 155, 154, 153, 445, + /* 700 */ 445, 445, 74, 445, 148, 445, 88, 445, 445, 490, + /* 710 */ 113, 120, 161, 119, 445, 120, 161, 119, 17, 74, + /* 720 */ 445, 148, 110, 110, 445, 445, 484, 113, 120, 161, + /* 730 */ 119, 445, 445, 149, 74, 445, 148, 152, 445, 445, + /* 740 */ 445, 483, 113, 120, 161, 119, 445, 445, 106, 445, + /* 750 */ 149, 445, 445, 107, 445, 375, 445, 117, 393, 155, + /* 760 */ 154, 153, 120, 161, 119, 149, 478, 74, 445, 148, + /* 770 */ 445, 88, 445, 445, 480, 113, 120, 161, 119, 445, + /* 780 */ 120, 161, 119, 74, 152, 148, 10, 479, 479, 445, + /* 790 */ 134, 113, 120, 161, 119, 445, 445, 445, 149, 74, + /* 800 */ 445, 148, 152, 445, 445, 445, 517, 113, 120, 161, + /* 810 */ 119, 445, 445, 74, 149, 148, 445, 445, 445, 445, + /* 820 */ 137, 113, 120, 161, 119, 74, 445, 148, 445, 445, + /* 830 */ 149, 445, 525, 113, 120, 161, 119, 445, 74, 445, + /* 840 */ 148, 445, 445, 445, 149, 527, 113, 120, 161, 119, + /* 850 */ 445, 445, 74, 445, 148, 445, 149, 445, 445, 524, + /* 860 */ 113, 120, 161, 119, 74, 445, 148, 445, 445, 149, + /* 870 */ 445, 526, 113, 120, 161, 119, 445, 445, 74, 445, + /* 880 */ 148, 445, 88, 149, 445, 523, 113, 120, 161, 119, + /* 890 */ 445, 120, 161, 119, 74, 149, 148, 85, 111, 111, + /* 900 */ 445, 522, 113, 120, 161, 119, 120, 161, 119, 149, + /* 910 */ 74, 445, 148, 152, 445, 445, 445, 521, 113, 120, + /* 920 */ 161, 119, 445, 445, 74, 149, 148, 445, 152, 445, + /* 930 */ 445, 520, 113, 120, 161, 119, 74, 445, 148, 445, + /* 940 */ 445, 149, 445, 519, 113, 120, 161, 119, 445, 74, + /* 950 */ 445, 148, 445, 445, 445, 149, 150, 113, 120, 161, + /* 960 */ 119, 445, 445, 74, 445, 148, 445, 149, 445, 445, + /* 970 */ 151, 113, 120, 161, 119, 74, 445, 148, 445, 445, + /* 980 */ 149, 445, 136, 113, 120, 161, 119, 445, 445, 74, + /* 990 */ 445, 148, 107, 445, 149, 445, 135, 113, 120, 161, + /* 1000 */ 119, 120, 161, 119, 445, 463, 149, 445, 88, 445, + /* 1010 */ 445, 445, 78, 78, 445, 445, 107, 120, 161, 119, + /* 1020 */ 149, 445, 445, 152, 82, 120, 161, 119, 445, 463, + /* 1030 */ 445, 466, 86, 34, 445, 88, 445, 569, 445, 152, + /* 1040 */ 445, 120, 161, 119, 120, 161, 119, 152, 107, 445, + /* 1050 */ 445, 475, 64, 63, 62, 61, 445, 120, 161, 119, + /* 1060 */ 98, 451, 445, 152, 89, 396, 152, 90, 445, 120, + /* 1070 */ 161, 119, 445, 120, 161, 119, 120, 161, 119, 152, + /* 1080 */ 445, 64, 63, 62, 61, 445, 445, 445, 445, 445, + /* 1090 */ 87, 152, 445, 99, 395, 152, 100, 445, 152, 120, + /* 1100 */ 161, 119, 120, 161, 119, 120, 161, 119, 445, 101, + /* 1110 */ 64, 63, 62, 61, 445, 445, 445, 445, 120, 161, + /* 1120 */ 119, 152, 91, 391, 152, 445, 445, 152, 103, 445, + /* 1130 */ 445, 120, 161, 119, 445, 92, 445, 120, 161, 119, + /* 1140 */ 152, 93, 445, 445, 120, 161, 119, 104, 445, 445, + /* 1150 */ 120, 161, 119, 152, 445, 445, 120, 161, 119, 152, + /* 1160 */ 445, 445, 445, 445, 94, 445, 152, 445, 445, 445, + /* 1170 */ 105, 445, 152, 120, 161, 119, 445, 95, 152, 120, + /* 1180 */ 161, 119, 96, 445, 445, 445, 120, 161, 119, 445, + /* 1190 */ 445, 120, 161, 119, 97, 152, 445, 445, 445, 445, + /* 1200 */ 549, 152, 445, 120, 161, 119, 548, 445, 152, 120, + /* 1210 */ 161, 119, 445, 152, 445, 120, 161, 119, 445, 445, + /* 1220 */ 445, 445, 445, 547, 445, 152, 445, 445, 445, 445, + /* 1230 */ 445, 152, 120, 161, 119, 546, 445, 152, 445, 115, + /* 1240 */ 445, 445, 116, 445, 120, 161, 119, 445, 120, 161, + /* 1250 */ 119, 120, 161, 119, 152, 64, 63, 62, 61, 64, + /* 1260 */ 63, 62, 61, 445, 445, 445, 152, 445, 445, 445, + /* 1270 */ 152, 445, 445, 152, 445, 445, 50, 445, 445, 445, + /* 1280 */ 53, 64, 63, 62, 61, 445, 445, 445, 445, 445, + /* 1290 */ 445, 445, 445, 445, 445, 445, 445, 445, 445, 445, + /* 1300 */ 445, 445, 56, +}; +static const YYCODETYPE yy_lookahead[] = { + /* 0 */ 0, 112, 113, 114, 133, 101, 102, 103, 105, 105, + /* 10 */ 10, 112, 113, 114, 110, 111, 112, 113, 114, 105, + /* 20 */ 20, 21, 22, 104, 24, 125, 107, 108, 28, 4, + /* 30 */ 5, 6, 7, 33, 34, 35, 36, 37, 134, 39, + /* 40 */ 40, 41, 42, 104, 44, 45, 107, 108, 106, 49, + /* 50 */ 50, 51, 52, 53, 54, 55, 56, 57, 58, 59, + /* 60 */ 60, 61, 62, 63, 0, 112, 113, 114, 129, 130, + /* 70 */ 131, 103, 12, 105, 10, 112, 113, 114, 110, 111, + /* 80 */ 112, 113, 114, 105, 20, 21, 22, 17, 24, 112, + /* 90 */ 113, 114, 28, 10, 2, 25, 25, 33, 34, 35, + /* 100 */ 36, 37, 134, 39, 40, 41, 42, 2, 44, 45, + /* 110 */ 132, 28, 127, 49, 50, 51, 52, 53, 54, 55, + /* 120 */ 56, 57, 58, 59, 60, 61, 62, 63, 1, 2, + /* 130 */ 38, 4, 5, 4, 5, 6, 7, 4, 5, 12, + /* 140 */ 3, 81, 15, 38, 1, 115, 17, 2, 88, 115, + /* 150 */ 90, 24, 128, 26, 27, 12, 1, 14, 31, 32, + /* 160 */ 19, 18, 16, 20, 21, 22, 23, 24, 17, 26, + /* 170 */ 27, 15, 29, 30, 31, 32, 31, 32, 64, 65, + /* 180 */ 66, 67, 68, 69, 70, 71, 72, 73, 6, 7, + /* 190 */ 43, 64, 13, 48, 26, 27, 103, 48, 105, 29, + /* 200 */ 30, 31, 32, 110, 111, 112, 113, 114, 81, 64, + /* 210 */ 83, 84, 85, 86, 40, 88, 40, 90, 91, 92, + /* 220 */ 93, 94, 1, 2, 87, 4, 5, 134, 83, 84, + /* 230 */ 85, 86, 48, 12, 96, 97, 15, 41, 20, 21, + /* 240 */ 22, 20, 21, 22, 41, 1, 2, 98, 4, 5, + /* 250 */ 82, 47, 31, 32, 17, 103, 12, 105, 90, 15, + /* 260 */ 26, 27, 110, 111, 112, 113, 114, 49, 50, 51, + /* 270 */ 52, 53, 54, 89, 25, 31, 32, 1, 17, 95, + /* 280 */ 4, 5, 98, 26, 27, 64, 134, 74, 12, 0, + /* 290 */ 79, 15, 78, 3, 2, 80, 20, 21, 22, 10, + /* 300 */ 24, 79, 81, 3, 83, 84, 85, 86, 64, 88, + /* 310 */ 3, 90, 91, 92, 93, 94, 38, 2, 3, 4, + /* 320 */ 5, 6, 7, 31, 32, 81, 3, 83, 84, 85, + /* 330 */ 86, 16, 88, 3, 90, 91, 92, 93, 94, 3, + /* 340 */ 25, 95, 4, 5, 6, 7, 31, 32, 1, 2, + /* 350 */ 76, 4, 5, 38, 25, 17, 64, 81, 15, 12, + /* 360 */ 15, 15, 15, 25, 88, 17, 90, 91, 92, 93, + /* 370 */ 94, 4, 5, 6, 7, 83, 84, 85, 86, 28, + /* 380 */ 28, 28, 67, 68, 12, 38, 89, 3, 11, 135, + /* 390 */ 75, 135, 77, 78, 79, 2, 3, 4, 5, 6, + /* 400 */ 7, 1, 35, 135, 4, 5, 4, 5, 6, 7, + /* 410 */ 17, 135, 12, 135, 135, 15, 135, 135, 25, 17, + /* 420 */ 20, 21, 22, 135, 31, 32, 135, 25, 81, 135, + /* 430 */ 135, 38, 135, 135, 135, 88, 135, 90, 91, 92, + /* 440 */ 93, 94, 135, 1, 2, 135, 4, 5, 4, 5, + /* 450 */ 6, 7, 8, 135, 12, 26, 27, 15, 135, 135, + /* 460 */ 67, 68, 99, 100, 101, 102, 135, 135, 75, 106, + /* 470 */ 77, 78, 79, 2, 3, 4, 5, 6, 7, 135, + /* 480 */ 117, 81, 135, 135, 121, 122, 135, 135, 88, 135, + /* 490 */ 90, 91, 92, 93, 94, 135, 25, 103, 135, 105, + /* 500 */ 135, 135, 31, 32, 110, 111, 112, 113, 114, 38, + /* 510 */ 135, 1, 135, 135, 4, 5, 135, 135, 135, 90, + /* 520 */ 135, 135, 12, 81, 135, 15, 135, 135, 134, 135, + /* 530 */ 88, 135, 90, 91, 92, 93, 94, 135, 67, 68, + /* 540 */ 1, 2, 135, 4, 5, 135, 75, 135, 77, 78, + /* 550 */ 79, 12, 135, 135, 15, 135, 46, 47, 1, 135, + /* 560 */ 135, 4, 5, 99, 100, 101, 102, 135, 135, 12, + /* 570 */ 106, 135, 15, 135, 1, 135, 135, 4, 5, 135, + /* 580 */ 135, 117, 135, 135, 135, 12, 122, 135, 15, 135, + /* 590 */ 135, 81, 135, 4, 5, 6, 7, 135, 88, 135, + /* 600 */ 90, 91, 92, 93, 94, 1, 17, 135, 4, 5, + /* 610 */ 135, 135, 135, 135, 135, 103, 12, 105, 135, 15, + /* 620 */ 81, 135, 110, 111, 112, 113, 114, 88, 135, 90, + /* 630 */ 91, 92, 93, 94, 135, 100, 101, 102, 81, 135, + /* 640 */ 135, 106, 135, 135, 135, 88, 134, 90, 91, 92, + /* 650 */ 93, 94, 117, 135, 81, 135, 135, 122, 135, 135, + /* 660 */ 135, 88, 135, 90, 91, 92, 93, 94, 1, 135, + /* 670 */ 135, 4, 5, 4, 5, 6, 7, 135, 135, 12, + /* 680 */ 135, 135, 15, 135, 135, 81, 17, 4, 5, 6, + /* 690 */ 7, 135, 88, 135, 90, 91, 92, 93, 94, 135, + /* 700 */ 135, 135, 103, 135, 105, 135, 103, 135, 135, 110, + /* 710 */ 111, 112, 113, 114, 135, 112, 113, 114, 35, 103, + /* 720 */ 135, 105, 119, 120, 135, 135, 110, 111, 112, 113, + /* 730 */ 114, 135, 135, 134, 103, 135, 105, 134, 135, 135, + /* 740 */ 135, 110, 111, 112, 113, 114, 135, 135, 81, 135, + /* 750 */ 134, 135, 135, 103, 135, 88, 135, 90, 91, 92, + /* 760 */ 93, 94, 112, 113, 114, 134, 116, 103, 135, 105, + /* 770 */ 135, 103, 135, 135, 110, 111, 112, 113, 114, 135, + /* 780 */ 112, 113, 114, 103, 134, 105, 118, 119, 120, 135, + /* 790 */ 110, 111, 112, 113, 114, 135, 135, 135, 134, 103, + /* 800 */ 135, 105, 134, 135, 135, 135, 110, 111, 112, 113, + /* 810 */ 114, 135, 135, 103, 134, 105, 135, 135, 135, 135, + /* 820 */ 110, 111, 112, 113, 114, 103, 135, 105, 135, 135, + /* 830 */ 134, 135, 110, 111, 112, 113, 114, 135, 103, 135, + /* 840 */ 105, 135, 135, 135, 134, 110, 111, 112, 113, 114, + /* 850 */ 135, 135, 103, 135, 105, 135, 134, 135, 135, 110, + /* 860 */ 111, 112, 113, 114, 103, 135, 105, 135, 135, 134, + /* 870 */ 135, 110, 111, 112, 113, 114, 135, 135, 103, 135, + /* 880 */ 105, 135, 103, 134, 135, 110, 111, 112, 113, 114, + /* 890 */ 135, 112, 113, 114, 103, 134, 105, 103, 119, 120, + /* 900 */ 135, 110, 111, 112, 113, 114, 112, 113, 114, 134, + /* 910 */ 103, 135, 105, 134, 135, 135, 135, 110, 111, 112, + /* 920 */ 113, 114, 135, 135, 103, 134, 105, 135, 134, 135, + /* 930 */ 135, 110, 111, 112, 113, 114, 103, 135, 105, 135, + /* 940 */ 135, 134, 135, 110, 111, 112, 113, 114, 135, 103, + /* 950 */ 135, 105, 135, 135, 135, 134, 110, 111, 112, 113, + /* 960 */ 114, 135, 135, 103, 135, 105, 135, 134, 135, 135, + /* 970 */ 110, 111, 112, 113, 114, 103, 135, 105, 135, 135, + /* 980 */ 134, 135, 110, 111, 112, 113, 114, 135, 135, 103, + /* 990 */ 135, 105, 103, 135, 134, 135, 110, 111, 112, 113, + /* 1000 */ 114, 112, 113, 114, 135, 116, 134, 135, 103, 135, + /* 1010 */ 135, 135, 123, 124, 135, 135, 103, 112, 113, 114, + /* 1020 */ 134, 135, 135, 134, 119, 112, 113, 114, 135, 116, + /* 1030 */ 135, 126, 103, 128, 135, 103, 135, 124, 135, 134, + /* 1040 */ 135, 112, 113, 114, 112, 113, 114, 134, 103, 135, + /* 1050 */ 135, 119, 4, 5, 6, 7, 135, 112, 113, 114, + /* 1060 */ 103, 116, 135, 134, 103, 17, 134, 103, 135, 112, + /* 1070 */ 113, 114, 135, 112, 113, 114, 112, 113, 114, 134, + /* 1080 */ 135, 4, 5, 6, 7, 135, 135, 135, 135, 135, + /* 1090 */ 103, 134, 135, 103, 17, 134, 103, 135, 134, 112, + /* 1100 */ 113, 114, 112, 113, 114, 112, 113, 114, 135, 103, + /* 1110 */ 4, 5, 6, 7, 135, 135, 135, 135, 112, 113, + /* 1120 */ 114, 134, 103, 17, 134, 135, 135, 134, 103, 135, + /* 1130 */ 135, 112, 113, 114, 135, 103, 135, 112, 113, 114, + /* 1140 */ 134, 103, 135, 135, 112, 113, 114, 103, 135, 135, + /* 1150 */ 112, 113, 114, 134, 135, 135, 112, 113, 114, 134, + /* 1160 */ 135, 135, 135, 135, 103, 135, 134, 135, 135, 135, + /* 1170 */ 103, 135, 134, 112, 113, 114, 135, 103, 134, 112, + /* 1180 */ 113, 114, 103, 135, 135, 135, 112, 113, 114, 135, + /* 1190 */ 135, 112, 113, 114, 103, 134, 135, 135, 135, 135, + /* 1200 */ 103, 134, 135, 112, 113, 114, 103, 135, 134, 112, + /* 1210 */ 113, 114, 135, 134, 135, 112, 113, 114, 135, 135, + /* 1220 */ 135, 135, 135, 103, 135, 134, 135, 135, 135, 135, + /* 1230 */ 135, 134, 112, 113, 114, 103, 135, 134, 135, 103, + /* 1240 */ 135, 135, 103, 135, 112, 113, 114, 135, 112, 113, + /* 1250 */ 114, 112, 113, 114, 134, 4, 5, 6, 7, 4, + /* 1260 */ 5, 6, 7, 135, 135, 135, 134, 135, 135, 135, + /* 1270 */ 134, 135, 135, 134, 135, 135, 25, 135, 135, 135, + /* 1280 */ 25, 4, 5, 6, 7, 135, 135, 135, 135, 135, + /* 1290 */ 135, 135, 135, 135, 135, 135, 135, 135, 135, 135, + /* 1300 */ 135, 135, 25, 135, 135, 135, 135, 135, 135, 135, + /* 1310 */ 135, 135, 135, 135, 135, 135, 135, 135, 135, 135, + /* 1320 */ 135, 135, 135, 135, 135, 135, 135, 135, 135, 135, + /* 1330 */ 135, 135, 135, 135, 135, 135, 135, 135, 135, 135, + /* 1340 */ 135, 135, 135, 135, 135, 135, 135, 135, 135, 135, + /* 1350 */ 135, 135, 135, 135, 135, 135, 135, 135, 135, 135, + /* 1360 */ 135, 135, 135, 135, 135, 135, 135, 135, 135, 135, + /* 1370 */ 135, 135, 135, 135, 135, 135, 135, 135, 135, 135, + /* 1380 */ 135, 99, 99, 99, 99, 99, 99, 99, 99, 99, + /* 1390 */ 99, 99, 99, 99, 99, 99, 99, 99, 99, 99, + /* 1400 */ 99, 99, +}; +#define YY_SHIFT_COUNT (163) +#define YY_SHIFT_MIN (0) +#define YY_SHIFT_MAX (1277) +static const unsigned short int yy_shift_ofst[] = { + /* 0 */ 143, 127, 221, 244, 244, 244, 244, 244, 244, 244, + /* 10 */ 244, 244, 244, 244, 244, 244, 244, 244, 244, 244, + /* 20 */ 244, 244, 244, 244, 244, 244, 244, 276, 510, 557, + /* 30 */ 276, 143, 347, 347, 0, 64, 143, 573, 557, 573, + /* 40 */ 400, 400, 400, 442, 539, 557, 557, 557, 557, 557, + /* 50 */ 557, 604, 557, 557, 667, 557, 557, 557, 557, 557, + /* 60 */ 557, 557, 557, 557, 557, 218, 60, 60, 60, 60, + /* 70 */ 60, 145, 315, 393, 471, 292, 292, 170, 71, 1303, + /* 80 */ 1303, 1303, 1303, 114, 114, 338, 402, 129, 444, 367, + /* 90 */ 683, 589, 1251, 669, 1255, 1048, 1277, 1077, 1106, 25, + /* 100 */ 25, 25, 184, 25, 25, 25, 168, 25, 429, 83, + /* 110 */ 92, 105, 70, 133, 138, 182, 182, 234, 257, 137, + /* 120 */ 149, 289, 141, 155, 151, 146, 156, 147, 174, 176, + /* 130 */ 196, 203, 204, 179, 237, 249, 213, 261, 211, 214, + /* 140 */ 215, 222, 290, 300, 307, 278, 323, 330, 336, 246, + /* 150 */ 274, 329, 246, 343, 345, 346, 348, 351, 352, 353, + /* 160 */ 372, 297, 384, 377, +}; +#define YY_REDUCE_COUNT (82) +#define YY_REDUCE_MIN (-129) +#define YY_REDUCE_MAX (1139) +static const short yy_reduce_ofst[] = { + /* 0 */ 363, -96, -32, 93, 152, 394, 512, 599, 616, 631, + /* 10 */ 664, 680, 696, 710, 722, 735, 749, 761, 775, 791, + /* 20 */ 807, 821, 833, 846, 860, 872, 886, 889, 668, 905, + /* 30 */ 913, 464, 603, 779, -61, -61, 535, 650, 932, 945, + /* 40 */ 794, 929, 957, 961, 964, 987, 990, 993, 1006, 1019, + /* 50 */ 1025, 1032, 1038, 1044, 1061, 1067, 1074, 1079, 1091, 1097, + /* 60 */ 1103, 1120, 1132, 1136, 1139, -81, -111, -101, -47, -37, + /* 70 */ -23, -22, -129, -129, -129, -97, -86, -58, -100, -15, + /* 80 */ 30, 34, 24, +}; +static const YYACTIONTYPE yy_default[] = { + /* 0 */ 449, 443, 443, 443, 443, 443, 443, 443, 443, 443, + /* 10 */ 443, 443, 443, 443, 443, 443, 443, 443, 443, 443, + /* 20 */ 443, 443, 443, 443, 443, 443, 443, 443, 473, 576, + /* 30 */ 443, 449, 580, 485, 581, 581, 449, 443, 443, 443, + /* 40 */ 443, 443, 443, 443, 443, 443, 443, 443, 477, 443, + /* 50 */ 443, 443, 443, 443, 443, 443, 443, 443, 443, 443, + /* 60 */ 443, 443, 443, 443, 443, 443, 443, 443, 443, 443, + /* 70 */ 443, 443, 443, 443, 443, 443, 443, 443, 455, 470, + /* 80 */ 508, 508, 576, 468, 493, 443, 443, 443, 471, 443, + /* 90 */ 443, 443, 443, 443, 443, 443, 443, 443, 443, 488, + /* 100 */ 486, 476, 459, 512, 511, 510, 443, 566, 443, 443, + /* 110 */ 443, 443, 443, 588, 443, 545, 544, 540, 443, 532, + /* 120 */ 529, 443, 443, 443, 443, 443, 443, 491, 443, 443, + /* 130 */ 443, 443, 443, 443, 443, 443, 443, 443, 443, 443, + /* 140 */ 443, 443, 443, 443, 443, 443, 443, 443, 443, 592, + /* 150 */ 443, 443, 443, 443, 443, 443, 443, 443, 443, 443, + /* 160 */ 443, 601, 443, 443, +}; +/********** End of lemon-generated parsing tables *****************************/ + +/* The next table maps tokens (terminal symbols) into fallback tokens. +** If a construct like the following: +** +** %fallback ID X Y Z. +** +** appears in the grammar, then ID becomes a fallback token for X, Y, +** and Z. Whenever one of the tokens X, Y, or Z is input to the parser +** but it does not parse, the type of the token is changed to ID and +** the parse is retried before an error is thrown. +** +** This feature can be used, for example, to cause some keywords in a language +** to revert to identifiers if they keyword does not apply in the context where +** it appears. +*/ +#ifdef YYFALLBACK +static const YYCODETYPE yyFallback[] = { + 0, /* $ => nothing */ + 0, /* ID => nothing */ + 1, /* EDGEPT => ID */ + 0, /* OF => nothing */ + 0, /* PLUS => nothing */ + 0, /* MINUS => nothing */ + 0, /* STAR => nothing */ + 0, /* SLASH => nothing */ + 0, /* PERCENT => nothing */ + 0, /* UMINUS => nothing */ + 0, /* EOL => nothing */ + 0, /* ASSIGN => nothing */ + 0, /* PLACENAME => nothing */ + 0, /* COLON => nothing */ + 0, /* ASSERT => nothing */ + 0, /* LP => nothing */ + 0, /* EQ => nothing */ + 0, /* RP => nothing */ + 0, /* DEFINE => nothing */ + 0, /* CODEBLOCK => nothing */ + 0, /* FILL => nothing */ + 0, /* COLOR => nothing */ + 0, /* THICKNESS => nothing */ + 0, /* PRINT => nothing */ + 0, /* STRING => nothing */ + 0, /* COMMA => nothing */ + 0, /* CLASSNAME => nothing */ + 0, /* LB => nothing */ + 0, /* RB => nothing */ + 0, /* UP => nothing */ + 0, /* DOWN => nothing */ + 0, /* LEFT => nothing */ + 0, /* RIGHT => nothing */ + 0, /* CLOSE => nothing */ + 0, /* CHOP => nothing */ + 0, /* FROM => nothing */ + 0, /* TO => nothing */ + 0, /* THEN => nothing */ + 0, /* HEADING => nothing */ + 0, /* GO => nothing */ + 0, /* AT => nothing */ + 0, /* WITH => nothing */ + 0, /* SAME => nothing */ + 0, /* AS => nothing */ + 0, /* FIT => nothing */ + 0, /* BEHIND => nothing */ + 0, /* UNTIL => nothing */ + 0, /* EVEN => nothing */ + 0, /* DOT_E => nothing */ + 0, /* HEIGHT => nothing */ + 0, /* WIDTH => nothing */ + 0, /* RADIUS => nothing */ + 0, /* DIAMETER => nothing */ + 0, /* DOTTED => nothing */ + 0, /* DASHED => nothing */ + 0, /* CW => nothing */ + 0, /* CCW => nothing */ + 0, /* LARROW => nothing */ + 0, /* RARROW => nothing */ + 0, /* LRARROW => nothing */ + 0, /* INVIS => nothing */ + 0, /* THICK => nothing */ + 0, /* THIN => nothing */ + 0, /* SOLID => nothing */ + 0, /* CENTER => nothing */ + 0, /* LJUST => nothing */ + 0, /* RJUST => nothing */ + 0, /* ABOVE => nothing */ + 0, /* BELOW => nothing */ + 0, /* ITALIC => nothing */ + 0, /* BOLD => nothing */ + 0, /* ALIGNED => nothing */ + 0, /* BIG => nothing */ + 0, /* SMALL => nothing */ + 0, /* AND => nothing */ + 0, /* LT => nothing */ + 0, /* GT => nothing */ + 0, /* ON => nothing */ + 0, /* WAY => nothing */ + 0, /* BETWEEN => nothing */ + 0, /* THE => nothing */ + 0, /* NTH => nothing */ + 0, /* VERTEX => nothing */ + 0, /* TOP => nothing */ + 0, /* BOTTOM => nothing */ + 0, /* START => nothing */ + 0, /* END => nothing */ + 0, /* IN => nothing */ + 0, /* THIS => nothing */ + 0, /* DOT_U => nothing */ + 0, /* LAST => nothing */ + 0, /* NUMBER => nothing */ + 0, /* FUNC1 => nothing */ + 0, /* FUNC2 => nothing */ + 0, /* DIST => nothing */ + 0, /* DOT_XY => nothing */ + 0, /* X => nothing */ + 0, /* Y => nothing */ + 0, /* DOT_L => nothing */ +}; +#endif /* YYFALLBACK */ + +/* The following structure represents a single element of the +** parser's stack. Information stored includes: +** +** + The state number for the parser at this level of the stack. +** +** + The value of the token stored at this level of the stack. +** (In other words, the "major" token.) +** +** + The semantic value stored at this level of the stack. This is +** the information used by the action routines in the grammar. +** It is sometimes called the "minor" token. +** +** After the "shift" half of a SHIFTREDUCE action, the stateno field +** actually contains the reduce action for the second half of the +** SHIFTREDUCE. +*/ +struct yyStackEntry { + YYACTIONTYPE stateno; /* The state-number, or reduce action in SHIFTREDUCE */ + YYCODETYPE major; /* The major token value. This is the code + ** number for the token at this stack level */ + YYMINORTYPE minor; /* The user-supplied minor token value. This + ** is the value of the token */ +}; +typedef struct yyStackEntry yyStackEntry; + +/* The state of the parser is completely contained in an instance of +** the following structure */ +struct yyParser { + yyStackEntry *yytos; /* Pointer to top element of the stack */ +#ifdef YYTRACKMAXSTACKDEPTH + int yyhwm; /* High-water mark of the stack */ +#endif +#ifndef YYNOERRORRECOVERY + int yyerrcnt; /* Shifts left before out of the error */ +#endif + pik_parserARG_SDECL /* A place to hold %extra_argument */ + pik_parserCTX_SDECL /* A place to hold %extra_context */ +#if YYSTACKDEPTH<=0 + int yystksz; /* Current side of the stack */ + yyStackEntry *yystack; /* The parser's stack */ + yyStackEntry yystk0; /* First stack entry */ +#else + yyStackEntry yystack[YYSTACKDEPTH]; /* The parser's stack */ + yyStackEntry *yystackEnd; /* Last entry in the stack */ +#endif +}; +typedef struct yyParser yyParser; + +#ifndef NDEBUG +#include +#include +static FILE *yyTraceFILE = 0; +static char *yyTracePrompt = 0; +#endif /* NDEBUG */ + +#ifndef NDEBUG +/* +** Turn parser tracing on by giving a stream to which to write the trace +** and a prompt to preface each trace message. Tracing is turned off +** by making either argument NULL +** +** Inputs: +**
        +**
      • A FILE* to which trace output should be written. +** If NULL, then tracing is turned off. +**
      • A prefix string written at the beginning of every +** line of trace output. If NULL, then tracing is +** turned off. +**
      +** +** Outputs: +** None. +*/ +void pik_parserTrace(FILE *TraceFILE, char *zTracePrompt){ + yyTraceFILE = TraceFILE; + yyTracePrompt = zTracePrompt; + if( yyTraceFILE==0 ) yyTracePrompt = 0; + else if( yyTracePrompt==0 ) yyTraceFILE = 0; +} +#endif /* NDEBUG */ + +#if defined(YYCOVERAGE) || !defined(NDEBUG) +/* For tracing shifts, the names of all terminals and nonterminals +** are required. The following table supplies these names */ +static const char *const yyTokenName[] = { + /* 0 */ "$", + /* 1 */ "ID", + /* 2 */ "EDGEPT", + /* 3 */ "OF", + /* 4 */ "PLUS", + /* 5 */ "MINUS", + /* 6 */ "STAR", + /* 7 */ "SLASH", + /* 8 */ "PERCENT", + /* 9 */ "UMINUS", + /* 10 */ "EOL", + /* 11 */ "ASSIGN", + /* 12 */ "PLACENAME", + /* 13 */ "COLON", + /* 14 */ "ASSERT", + /* 15 */ "LP", + /* 16 */ "EQ", + /* 17 */ "RP", + /* 18 */ "DEFINE", + /* 19 */ "CODEBLOCK", + /* 20 */ "FILL", + /* 21 */ "COLOR", + /* 22 */ "THICKNESS", + /* 23 */ "PRINT", + /* 24 */ "STRING", + /* 25 */ "COMMA", + /* 26 */ "CLASSNAME", + /* 27 */ "LB", + /* 28 */ "RB", + /* 29 */ "UP", + /* 30 */ "DOWN", + /* 31 */ "LEFT", + /* 32 */ "RIGHT", + /* 33 */ "CLOSE", + /* 34 */ "CHOP", + /* 35 */ "FROM", + /* 36 */ "TO", + /* 37 */ "THEN", + /* 38 */ "HEADING", + /* 39 */ "GO", + /* 40 */ "AT", + /* 41 */ "WITH", + /* 42 */ "SAME", + /* 43 */ "AS", + /* 44 */ "FIT", + /* 45 */ "BEHIND", + /* 46 */ "UNTIL", + /* 47 */ "EVEN", + /* 48 */ "DOT_E", + /* 49 */ "HEIGHT", + /* 50 */ "WIDTH", + /* 51 */ "RADIUS", + /* 52 */ "DIAMETER", + /* 53 */ "DOTTED", + /* 54 */ "DASHED", + /* 55 */ "CW", + /* 56 */ "CCW", + /* 57 */ "LARROW", + /* 58 */ "RARROW", + /* 59 */ "LRARROW", + /* 60 */ "INVIS", + /* 61 */ "THICK", + /* 62 */ "THIN", + /* 63 */ "SOLID", + /* 64 */ "CENTER", + /* 65 */ "LJUST", + /* 66 */ "RJUST", + /* 67 */ "ABOVE", + /* 68 */ "BELOW", + /* 69 */ "ITALIC", + /* 70 */ "BOLD", + /* 71 */ "ALIGNED", + /* 72 */ "BIG", + /* 73 */ "SMALL", + /* 74 */ "AND", + /* 75 */ "LT", + /* 76 */ "GT", + /* 77 */ "ON", + /* 78 */ "WAY", + /* 79 */ "BETWEEN", + /* 80 */ "THE", + /* 81 */ "NTH", + /* 82 */ "VERTEX", + /* 83 */ "TOP", + /* 84 */ "BOTTOM", + /* 85 */ "START", + /* 86 */ "END", + /* 87 */ "IN", + /* 88 */ "THIS", + /* 89 */ "DOT_U", + /* 90 */ "LAST", + /* 91 */ "NUMBER", + /* 92 */ "FUNC1", + /* 93 */ "FUNC2", + /* 94 */ "DIST", + /* 95 */ "DOT_XY", + /* 96 */ "X", + /* 97 */ "Y", + /* 98 */ "DOT_L", + /* 99 */ "statement_list", + /* 100 */ "statement", + /* 101 */ "unnamed_statement", + /* 102 */ "basetype", + /* 103 */ "expr", + /* 104 */ "numproperty", + /* 105 */ "edge", + /* 106 */ "direction", + /* 107 */ "dashproperty", + /* 108 */ "colorproperty", + /* 109 */ "locproperty", + /* 110 */ "position", + /* 111 */ "place", + /* 112 */ "object", + /* 113 */ "objectname", + /* 114 */ "nth", + /* 115 */ "textposition", + /* 116 */ "rvalue", + /* 117 */ "lvalue", + /* 118 */ "even", + /* 119 */ "relexpr", + /* 120 */ "optrelexpr", + /* 121 */ "document", + /* 122 */ "print", + /* 123 */ "prlist", + /* 124 */ "pritem", + /* 125 */ "prsep", + /* 126 */ "attribute_list", + /* 127 */ "savelist", + /* 128 */ "alist", + /* 129 */ "attribute", + /* 130 */ "go", + /* 131 */ "boolproperty", + /* 132 */ "withclause", + /* 133 */ "between", + /* 134 */ "place2", +}; +#endif /* defined(YYCOVERAGE) || !defined(NDEBUG) */ + +#ifndef NDEBUG +/* For tracing reduce actions, the names of all rules are required. +*/ +static const char *const yyRuleName[] = { + /* 0 */ "document ::= statement_list", + /* 1 */ "statement_list ::= statement", + /* 2 */ "statement_list ::= statement_list EOL statement", + /* 3 */ "statement ::=", + /* 4 */ "statement ::= direction", + /* 5 */ "statement ::= lvalue ASSIGN rvalue", + /* 6 */ "statement ::= PLACENAME COLON unnamed_statement", + /* 7 */ "statement ::= PLACENAME COLON position", + /* 8 */ "statement ::= unnamed_statement", + /* 9 */ "statement ::= print prlist", + /* 10 */ "statement ::= ASSERT LP expr EQ expr RP", + /* 11 */ "statement ::= ASSERT LP position EQ position RP", + /* 12 */ "statement ::= DEFINE ID CODEBLOCK", + /* 13 */ "rvalue ::= PLACENAME", + /* 14 */ "pritem ::= FILL", + /* 15 */ "pritem ::= COLOR", + /* 16 */ "pritem ::= THICKNESS", + /* 17 */ "pritem ::= rvalue", + /* 18 */ "pritem ::= STRING", + /* 19 */ "prsep ::= COMMA", + /* 20 */ "unnamed_statement ::= basetype attribute_list", + /* 21 */ "basetype ::= CLASSNAME", + /* 22 */ "basetype ::= STRING textposition", + /* 23 */ "basetype ::= LB savelist statement_list RB", + /* 24 */ "savelist ::=", + /* 25 */ "relexpr ::= expr", + /* 26 */ "relexpr ::= expr PERCENT", + /* 27 */ "optrelexpr ::=", + /* 28 */ "attribute_list ::= relexpr alist", + /* 29 */ "attribute ::= numproperty relexpr", + /* 30 */ "attribute ::= dashproperty expr", + /* 31 */ "attribute ::= dashproperty", + /* 32 */ "attribute ::= colorproperty rvalue", + /* 33 */ "attribute ::= go direction optrelexpr", + /* 34 */ "attribute ::= go direction even position", + /* 35 */ "attribute ::= CLOSE", + /* 36 */ "attribute ::= CHOP", + /* 37 */ "attribute ::= FROM position", + /* 38 */ "attribute ::= TO position", + /* 39 */ "attribute ::= THEN", + /* 40 */ "attribute ::= THEN optrelexpr HEADING expr", + /* 41 */ "attribute ::= THEN optrelexpr EDGEPT", + /* 42 */ "attribute ::= GO optrelexpr HEADING expr", + /* 43 */ "attribute ::= GO optrelexpr EDGEPT", + /* 44 */ "attribute ::= AT position", + /* 45 */ "attribute ::= SAME", + /* 46 */ "attribute ::= SAME AS object", + /* 47 */ "attribute ::= STRING textposition", + /* 48 */ "attribute ::= FIT", + /* 49 */ "attribute ::= BEHIND object", + /* 50 */ "withclause ::= DOT_E edge AT position", + /* 51 */ "withclause ::= edge AT position", + /* 52 */ "numproperty ::= HEIGHT|WIDTH|RADIUS|DIAMETER|THICKNESS", + /* 53 */ "boolproperty ::= CW", + /* 54 */ "boolproperty ::= CCW", + /* 55 */ "boolproperty ::= LARROW", + /* 56 */ "boolproperty ::= RARROW", + /* 57 */ "boolproperty ::= LRARROW", + /* 58 */ "boolproperty ::= INVIS", + /* 59 */ "boolproperty ::= THICK", + /* 60 */ "boolproperty ::= THIN", + /* 61 */ "boolproperty ::= SOLID", + /* 62 */ "textposition ::=", + /* 63 */ "textposition ::= textposition CENTER|LJUST|RJUST|ABOVE|BELOW|ITALIC|BOLD|ALIGNED|BIG|SMALL", + /* 64 */ "position ::= expr COMMA expr", + /* 65 */ "position ::= place PLUS expr COMMA expr", + /* 66 */ "position ::= place MINUS expr COMMA expr", + /* 67 */ "position ::= place PLUS LP expr COMMA expr RP", + /* 68 */ "position ::= place MINUS LP expr COMMA expr RP", + /* 69 */ "position ::= LP position COMMA position RP", + /* 70 */ "position ::= LP position RP", + /* 71 */ "position ::= expr between position AND position", + /* 72 */ "position ::= expr LT position COMMA position GT", + /* 73 */ "position ::= expr ABOVE position", + /* 74 */ "position ::= expr BELOW position", + /* 75 */ "position ::= expr LEFT OF position", + /* 76 */ "position ::= expr RIGHT OF position", + /* 77 */ "position ::= expr ON HEADING EDGEPT OF position", + /* 78 */ "position ::= expr HEADING EDGEPT OF position", + /* 79 */ "position ::= expr EDGEPT OF position", + /* 80 */ "position ::= expr ON HEADING expr FROM position", + /* 81 */ "position ::= expr HEADING expr FROM position", + /* 82 */ "place ::= edge OF object", + /* 83 */ "place2 ::= object", + /* 84 */ "place2 ::= object DOT_E edge", + /* 85 */ "place2 ::= NTH VERTEX OF object", + /* 86 */ "object ::= nth", + /* 87 */ "object ::= nth OF|IN object", + /* 88 */ "objectname ::= THIS", + /* 89 */ "objectname ::= PLACENAME", + /* 90 */ "objectname ::= objectname DOT_U PLACENAME", + /* 91 */ "nth ::= NTH CLASSNAME", + /* 92 */ "nth ::= NTH LAST CLASSNAME", + /* 93 */ "nth ::= LAST CLASSNAME", + /* 94 */ "nth ::= LAST", + /* 95 */ "nth ::= NTH LB RB", + /* 96 */ "nth ::= NTH LAST LB RB", + /* 97 */ "nth ::= LAST LB RB", + /* 98 */ "expr ::= expr PLUS expr", + /* 99 */ "expr ::= expr MINUS expr", + /* 100 */ "expr ::= expr STAR expr", + /* 101 */ "expr ::= expr SLASH expr", + /* 102 */ "expr ::= MINUS expr", + /* 103 */ "expr ::= PLUS expr", + /* 104 */ "expr ::= LP expr RP", + /* 105 */ "expr ::= LP FILL|COLOR|THICKNESS RP", + /* 106 */ "expr ::= NUMBER", + /* 107 */ "expr ::= ID", + /* 108 */ "expr ::= FUNC1 LP expr RP", + /* 109 */ "expr ::= FUNC2 LP expr COMMA expr RP", + /* 110 */ "expr ::= DIST LP position COMMA position RP", + /* 111 */ "expr ::= place2 DOT_XY X", + /* 112 */ "expr ::= place2 DOT_XY Y", + /* 113 */ "expr ::= object DOT_L numproperty", + /* 114 */ "expr ::= object DOT_L dashproperty", + /* 115 */ "expr ::= object DOT_L colorproperty", + /* 116 */ "lvalue ::= ID", + /* 117 */ "lvalue ::= FILL", + /* 118 */ "lvalue ::= COLOR", + /* 119 */ "lvalue ::= THICKNESS", + /* 120 */ "rvalue ::= expr", + /* 121 */ "print ::= PRINT", + /* 122 */ "prlist ::= pritem", + /* 123 */ "prlist ::= prlist prsep pritem", + /* 124 */ "direction ::= UP", + /* 125 */ "direction ::= DOWN", + /* 126 */ "direction ::= LEFT", + /* 127 */ "direction ::= RIGHT", + /* 128 */ "optrelexpr ::= relexpr", + /* 129 */ "attribute_list ::= alist", + /* 130 */ "alist ::=", + /* 131 */ "alist ::= alist attribute", + /* 132 */ "attribute ::= boolproperty", + /* 133 */ "attribute ::= WITH withclause", + /* 134 */ "go ::= GO", + /* 135 */ "go ::=", + /* 136 */ "even ::= UNTIL EVEN WITH", + /* 137 */ "even ::= EVEN WITH", + /* 138 */ "dashproperty ::= DOTTED", + /* 139 */ "dashproperty ::= DASHED", + /* 140 */ "colorproperty ::= FILL", + /* 141 */ "colorproperty ::= COLOR", + /* 142 */ "position ::= place", + /* 143 */ "between ::= WAY BETWEEN", + /* 144 */ "between ::= BETWEEN", + /* 145 */ "between ::= OF THE WAY BETWEEN", + /* 146 */ "place ::= place2", + /* 147 */ "edge ::= CENTER", + /* 148 */ "edge ::= EDGEPT", + /* 149 */ "edge ::= TOP", + /* 150 */ "edge ::= BOTTOM", + /* 151 */ "edge ::= START", + /* 152 */ "edge ::= END", + /* 153 */ "edge ::= RIGHT", + /* 154 */ "edge ::= LEFT", + /* 155 */ "object ::= objectname", +}; +#endif /* NDEBUG */ + + +#if YYSTACKDEPTH<=0 +/* +** Try to increase the size of the parser stack. Return the number +** of errors. Return 0 on success. +*/ +static int yyGrowStack(yyParser *p){ + int newSize; + int idx; + yyStackEntry *pNew; + + newSize = p->yystksz*2 + 100; + idx = p->yytos ? (int)(p->yytos - p->yystack) : 0; + if( p->yystack==&p->yystk0 ){ + pNew = malloc(newSize*sizeof(pNew[0])); + if( pNew ) pNew[0] = p->yystk0; + }else{ + pNew = realloc(p->yystack, newSize*sizeof(pNew[0])); + } + if( pNew ){ + p->yystack = pNew; + p->yytos = &p->yystack[idx]; +#ifndef NDEBUG + if( yyTraceFILE ){ + fprintf(yyTraceFILE,"%sStack grows from %d to %d entries.\n", + yyTracePrompt, p->yystksz, newSize); + } +#endif + p->yystksz = newSize; + } + return pNew==0; +} +#endif + +/* Datatype of the argument to the memory allocated passed as the +** second argument to pik_parserAlloc() below. This can be changed by +** putting an appropriate #define in the %include section of the input +** grammar. +*/ +#ifndef YYMALLOCARGTYPE +# define YYMALLOCARGTYPE size_t +#endif + +/* Initialize a new parser that has already been allocated. +*/ +void pik_parserInit(void *yypRawParser pik_parserCTX_PDECL){ + yyParser *yypParser = (yyParser*)yypRawParser; + pik_parserCTX_STORE +#ifdef YYTRACKMAXSTACKDEPTH + yypParser->yyhwm = 0; +#endif +#if YYSTACKDEPTH<=0 + yypParser->yytos = NULL; + yypParser->yystack = NULL; + yypParser->yystksz = 0; + if( yyGrowStack(yypParser) ){ + yypParser->yystack = &yypParser->yystk0; + yypParser->yystksz = 1; + } +#endif +#ifndef YYNOERRORRECOVERY + yypParser->yyerrcnt = -1; +#endif + yypParser->yytos = yypParser->yystack; + yypParser->yystack[0].stateno = 0; + yypParser->yystack[0].major = 0; +#if YYSTACKDEPTH>0 + yypParser->yystackEnd = &yypParser->yystack[YYSTACKDEPTH-1]; +#endif +} + +#ifndef pik_parser_ENGINEALWAYSONSTACK +/* +** This function allocates a new parser. +** The only argument is a pointer to a function which works like +** malloc. +** +** Inputs: +** A pointer to the function used to allocate memory. +** +** Outputs: +** A pointer to a parser. This pointer is used in subsequent calls +** to pik_parser and pik_parserFree. +*/ +void *pik_parserAlloc(void *(*mallocProc)(YYMALLOCARGTYPE) pik_parserCTX_PDECL){ + yyParser *yypParser; + yypParser = (yyParser*)(*mallocProc)( (YYMALLOCARGTYPE)sizeof(yyParser) ); + if( yypParser ){ + pik_parserCTX_STORE + pik_parserInit(yypParser pik_parserCTX_PARAM); + } + return (void*)yypParser; +} +#endif /* pik_parser_ENGINEALWAYSONSTACK */ + + +/* The following function deletes the "minor type" or semantic value +** associated with a symbol. The symbol can be either a terminal +** or nonterminal. "yymajor" is the symbol code, and "yypminor" is +** a pointer to the value to be deleted. The code used to do the +** deletions is derived from the %destructor and/or %token_destructor +** directives of the input grammar. +*/ +static void yy_destructor( + yyParser *yypParser, /* The parser */ + YYCODETYPE yymajor, /* Type code for object to destroy */ + YYMINORTYPE *yypminor /* The object to be destroyed */ +){ + pik_parserARG_FETCH + pik_parserCTX_FETCH + switch( yymajor ){ + /* Here is inserted the actions which take place when a + ** terminal or non-terminal is destroyed. This can happen + ** when the symbol is popped from the stack during a + ** reduce or during error processing or when a parser is + ** being destroyed before it is finished parsing. + ** + ** Note: during a reduce, the only symbols destroyed are those + ** which appear on the RHS of the rule, but which are *not* used + ** inside the C code. + */ +/********* Begin destructor definitions ***************************************/ + case 99: /* statement_list */ +{ +#line 494 "pikchr.y" +pik_elist_free(p,(yypminor->yy227)); +#line 1735 "pikchr.c" +} + break; + case 100: /* statement */ + case 101: /* unnamed_statement */ + case 102: /* basetype */ +{ +#line 496 "pikchr.y" +pik_elem_free(p,(yypminor->yy36)); +#line 1744 "pikchr.c" +} + break; +/********* End destructor definitions *****************************************/ + default: break; /* If no destructor action specified: do nothing */ + } +} + +/* +** Pop the parser's stack once. +** +** If there is a destructor routine associated with the token which +** is popped from the stack, then call it. +*/ +static void yy_pop_parser_stack(yyParser *pParser){ + yyStackEntry *yytos; + assert( pParser->yytos!=0 ); + assert( pParser->yytos > pParser->yystack ); + yytos = pParser->yytos--; +#ifndef NDEBUG + if( yyTraceFILE ){ + fprintf(yyTraceFILE,"%sPopping %s\n", + yyTracePrompt, + yyTokenName[yytos->major]); + } +#endif + yy_destructor(pParser, yytos->major, &yytos->minor); +} + +/* +** Clear all secondary memory allocations from the parser +*/ +void pik_parserFinalize(void *p){ + yyParser *pParser = (yyParser*)p; + while( pParser->yytos>pParser->yystack ) yy_pop_parser_stack(pParser); +#if YYSTACKDEPTH<=0 + if( pParser->yystack!=&pParser->yystk0 ) free(pParser->yystack); +#endif +} + +#ifndef pik_parser_ENGINEALWAYSONSTACK +/* +** Deallocate and destroy a parser. Destructors are called for +** all stack elements before shutting the parser down. +** +** If the YYPARSEFREENEVERNULL macro exists (for example because it +** is defined in a %include section of the input grammar) then it is +** assumed that the input pointer is never NULL. +*/ +void pik_parserFree( + void *p, /* The parser to be deleted */ + void (*freeProc)(void*) /* Function used to reclaim memory */ +){ +#ifndef YYPARSEFREENEVERNULL + if( p==0 ) return; +#endif + pik_parserFinalize(p); + (*freeProc)(p); +} +#endif /* pik_parser_ENGINEALWAYSONSTACK */ + +/* +** Return the peak depth of the stack for a parser. +*/ +#ifdef YYTRACKMAXSTACKDEPTH +int pik_parserStackPeak(void *p){ + yyParser *pParser = (yyParser*)p; + return pParser->yyhwm; +} +#endif + +/* This array of booleans keeps track of the parser statement +** coverage. The element yycoverage[X][Y] is set when the parser +** is in state X and has a lookahead token Y. In a well-tested +** systems, every element of this matrix should end up being set. +*/ +#if defined(YYCOVERAGE) +static unsigned char yycoverage[YYNSTATE][YYNTOKEN]; +#endif + +/* +** Write into out a description of every state/lookahead combination that +** +** (1) has not been used by the parser, and +** (2) is not a syntax error. +** +** Return the number of missed state/lookahead combinations. +*/ +#if defined(YYCOVERAGE) +int pik_parserCoverage(FILE *out){ + int stateno, iLookAhead, i; + int nMissed = 0; + for(stateno=0; statenoYY_MAX_SHIFT ) return stateno; + assert( stateno <= YY_SHIFT_COUNT ); +#if defined(YYCOVERAGE) + yycoverage[stateno][iLookAhead] = 1; +#endif + do{ + i = yy_shift_ofst[stateno]; + assert( i>=0 ); + assert( i<=YY_ACTTAB_COUNT ); + assert( i+YYNTOKEN<=(int)YY_NLOOKAHEAD ); + assert( iLookAhead!=YYNOCODE ); + assert( iLookAhead < YYNTOKEN ); + i += iLookAhead; + assert( i<(int)YY_NLOOKAHEAD ); + if( yy_lookahead[i]!=iLookAhead ){ +#ifdef YYFALLBACK + YYCODETYPE iFallback; /* Fallback token */ + assert( iLookAhead %s\n", + yyTracePrompt, yyTokenName[iLookAhead], yyTokenName[iFallback]); + } +#endif + assert( yyFallback[iFallback]==0 ); /* Fallback loop must terminate */ + iLookAhead = iFallback; + continue; + } +#endif +#ifdef YYWILDCARD + { + int j = i - iLookAhead + YYWILDCARD; + assert( j<(int)(sizeof(yy_lookahead)/sizeof(yy_lookahead[0])) ); + if( yy_lookahead[j]==YYWILDCARD && iLookAhead>0 ){ +#ifndef NDEBUG + if( yyTraceFILE ){ + fprintf(yyTraceFILE, "%sWILDCARD %s => %s\n", + yyTracePrompt, yyTokenName[iLookAhead], + yyTokenName[YYWILDCARD]); + } +#endif /* NDEBUG */ + return yy_action[j]; + } + } +#endif /* YYWILDCARD */ + return yy_default[stateno]; + }else{ + assert( i>=0 && i<(int)(sizeof(yy_action)/sizeof(yy_action[0])) ); + return yy_action[i]; + } + }while(1); +} + +/* +** Find the appropriate action for a parser given the non-terminal +** look-ahead token iLookAhead. +*/ +static YYACTIONTYPE yy_find_reduce_action( + YYACTIONTYPE stateno, /* Current state number */ + YYCODETYPE iLookAhead /* The look-ahead token */ +){ + int i; +#ifdef YYERRORSYMBOL + if( stateno>YY_REDUCE_COUNT ){ + return yy_default[stateno]; + } +#else + assert( stateno<=YY_REDUCE_COUNT ); +#endif + i = yy_reduce_ofst[stateno]; + assert( iLookAhead!=YYNOCODE ); + i += iLookAhead; +#ifdef YYERRORSYMBOL + if( i<0 || i>=YY_ACTTAB_COUNT || yy_lookahead[i]!=iLookAhead ){ + return yy_default[stateno]; + } +#else + assert( i>=0 && iyytos>yypParser->yystack ) yy_pop_parser_stack(yypParser); + /* Here code is inserted which will execute if the parser + ** stack every overflows */ +/******** Begin %stack_overflow code ******************************************/ +#line 528 "pikchr.y" + + pik_error(p, 0, "parser stack overflow"); +#line 1965 "pikchr.c" +/******** End %stack_overflow code ********************************************/ + pik_parserARG_STORE /* Suppress warning about unused %extra_argument var */ + pik_parserCTX_STORE +} + +/* +** Print tracing information for a SHIFT action +*/ +#ifndef NDEBUG +static void yyTraceShift(yyParser *yypParser, int yyNewState, const char *zTag){ + if( yyTraceFILE ){ + if( yyNewStateyytos->major], + yyNewState); + }else{ + fprintf(yyTraceFILE,"%s%s '%s', pending reduce %d\n", + yyTracePrompt, zTag, yyTokenName[yypParser->yytos->major], + yyNewState - YY_MIN_REDUCE); + } + } +} +#else +# define yyTraceShift(X,Y,Z) +#endif + +/* +** Perform a shift action. +*/ +static void yy_shift( + yyParser *yypParser, /* The parser to be shifted */ + YYACTIONTYPE yyNewState, /* The new state to shift in */ + YYCODETYPE yyMajor, /* The major token to shift in */ + pik_parserTOKENTYPE yyMinor /* The minor token to shift in */ +){ + yyStackEntry *yytos; + yypParser->yytos++; +#ifdef YYTRACKMAXSTACKDEPTH + if( (int)(yypParser->yytos - yypParser->yystack)>yypParser->yyhwm ){ + yypParser->yyhwm++; + assert( yypParser->yyhwm == (int)(yypParser->yytos - yypParser->yystack) ); + } +#endif +#if YYSTACKDEPTH>0 + if( yypParser->yytos>yypParser->yystackEnd ){ + yypParser->yytos--; + yyStackOverflow(yypParser); + return; + } +#else + if( yypParser->yytos>=&yypParser->yystack[yypParser->yystksz] ){ + if( yyGrowStack(yypParser) ){ + yypParser->yytos--; + yyStackOverflow(yypParser); + return; + } + } +#endif + if( yyNewState > YY_MAX_SHIFT ){ + yyNewState += YY_MIN_REDUCE - YY_MIN_SHIFTREDUCE; + } + yytos = yypParser->yytos; + yytos->stateno = yyNewState; + yytos->major = yyMajor; + yytos->minor.yy0 = yyMinor; + yyTraceShift(yypParser, yyNewState, "Shift"); +} + +/* For rule J, yyRuleInfoLhs[J] contains the symbol on the left-hand side +** of that rule */ +static const YYCODETYPE yyRuleInfoLhs[] = { + 121, /* (0) document ::= statement_list */ + 99, /* (1) statement_list ::= statement */ + 99, /* (2) statement_list ::= statement_list EOL statement */ + 100, /* (3) statement ::= */ + 100, /* (4) statement ::= direction */ + 100, /* (5) statement ::= lvalue ASSIGN rvalue */ + 100, /* (6) statement ::= PLACENAME COLON unnamed_statement */ + 100, /* (7) statement ::= PLACENAME COLON position */ + 100, /* (8) statement ::= unnamed_statement */ + 100, /* (9) statement ::= print prlist */ + 100, /* (10) statement ::= ASSERT LP expr EQ expr RP */ + 100, /* (11) statement ::= ASSERT LP position EQ position RP */ + 100, /* (12) statement ::= DEFINE ID CODEBLOCK */ + 116, /* (13) rvalue ::= PLACENAME */ + 124, /* (14) pritem ::= FILL */ + 124, /* (15) pritem ::= COLOR */ + 124, /* (16) pritem ::= THICKNESS */ + 124, /* (17) pritem ::= rvalue */ + 124, /* (18) pritem ::= STRING */ + 125, /* (19) prsep ::= COMMA */ + 101, /* (20) unnamed_statement ::= basetype attribute_list */ + 102, /* (21) basetype ::= CLASSNAME */ + 102, /* (22) basetype ::= STRING textposition */ + 102, /* (23) basetype ::= LB savelist statement_list RB */ + 127, /* (24) savelist ::= */ + 119, /* (25) relexpr ::= expr */ + 119, /* (26) relexpr ::= expr PERCENT */ + 120, /* (27) optrelexpr ::= */ + 126, /* (28) attribute_list ::= relexpr alist */ + 129, /* (29) attribute ::= numproperty relexpr */ + 129, /* (30) attribute ::= dashproperty expr */ + 129, /* (31) attribute ::= dashproperty */ + 129, /* (32) attribute ::= colorproperty rvalue */ + 129, /* (33) attribute ::= go direction optrelexpr */ + 129, /* (34) attribute ::= go direction even position */ + 129, /* (35) attribute ::= CLOSE */ + 129, /* (36) attribute ::= CHOP */ + 129, /* (37) attribute ::= FROM position */ + 129, /* (38) attribute ::= TO position */ + 129, /* (39) attribute ::= THEN */ + 129, /* (40) attribute ::= THEN optrelexpr HEADING expr */ + 129, /* (41) attribute ::= THEN optrelexpr EDGEPT */ + 129, /* (42) attribute ::= GO optrelexpr HEADING expr */ + 129, /* (43) attribute ::= GO optrelexpr EDGEPT */ + 129, /* (44) attribute ::= AT position */ + 129, /* (45) attribute ::= SAME */ + 129, /* (46) attribute ::= SAME AS object */ + 129, /* (47) attribute ::= STRING textposition */ + 129, /* (48) attribute ::= FIT */ + 129, /* (49) attribute ::= BEHIND object */ + 132, /* (50) withclause ::= DOT_E edge AT position */ + 132, /* (51) withclause ::= edge AT position */ + 104, /* (52) numproperty ::= HEIGHT|WIDTH|RADIUS|DIAMETER|THICKNESS */ + 131, /* (53) boolproperty ::= CW */ + 131, /* (54) boolproperty ::= CCW */ + 131, /* (55) boolproperty ::= LARROW */ + 131, /* (56) boolproperty ::= RARROW */ + 131, /* (57) boolproperty ::= LRARROW */ + 131, /* (58) boolproperty ::= INVIS */ + 131, /* (59) boolproperty ::= THICK */ + 131, /* (60) boolproperty ::= THIN */ + 131, /* (61) boolproperty ::= SOLID */ + 115, /* (62) textposition ::= */ + 115, /* (63) textposition ::= textposition CENTER|LJUST|RJUST|ABOVE|BELOW|ITALIC|BOLD|ALIGNED|BIG|SMALL */ + 110, /* (64) position ::= expr COMMA expr */ + 110, /* (65) position ::= place PLUS expr COMMA expr */ + 110, /* (66) position ::= place MINUS expr COMMA expr */ + 110, /* (67) position ::= place PLUS LP expr COMMA expr RP */ + 110, /* (68) position ::= place MINUS LP expr COMMA expr RP */ + 110, /* (69) position ::= LP position COMMA position RP */ + 110, /* (70) position ::= LP position RP */ + 110, /* (71) position ::= expr between position AND position */ + 110, /* (72) position ::= expr LT position COMMA position GT */ + 110, /* (73) position ::= expr ABOVE position */ + 110, /* (74) position ::= expr BELOW position */ + 110, /* (75) position ::= expr LEFT OF position */ + 110, /* (76) position ::= expr RIGHT OF position */ + 110, /* (77) position ::= expr ON HEADING EDGEPT OF position */ + 110, /* (78) position ::= expr HEADING EDGEPT OF position */ + 110, /* (79) position ::= expr EDGEPT OF position */ + 110, /* (80) position ::= expr ON HEADING expr FROM position */ + 110, /* (81) position ::= expr HEADING expr FROM position */ + 111, /* (82) place ::= edge OF object */ + 134, /* (83) place2 ::= object */ + 134, /* (84) place2 ::= object DOT_E edge */ + 134, /* (85) place2 ::= NTH VERTEX OF object */ + 112, /* (86) object ::= nth */ + 112, /* (87) object ::= nth OF|IN object */ + 113, /* (88) objectname ::= THIS */ + 113, /* (89) objectname ::= PLACENAME */ + 113, /* (90) objectname ::= objectname DOT_U PLACENAME */ + 114, /* (91) nth ::= NTH CLASSNAME */ + 114, /* (92) nth ::= NTH LAST CLASSNAME */ + 114, /* (93) nth ::= LAST CLASSNAME */ + 114, /* (94) nth ::= LAST */ + 114, /* (95) nth ::= NTH LB RB */ + 114, /* (96) nth ::= NTH LAST LB RB */ + 114, /* (97) nth ::= LAST LB RB */ + 103, /* (98) expr ::= expr PLUS expr */ + 103, /* (99) expr ::= expr MINUS expr */ + 103, /* (100) expr ::= expr STAR expr */ + 103, /* (101) expr ::= expr SLASH expr */ + 103, /* (102) expr ::= MINUS expr */ + 103, /* (103) expr ::= PLUS expr */ + 103, /* (104) expr ::= LP expr RP */ + 103, /* (105) expr ::= LP FILL|COLOR|THICKNESS RP */ + 103, /* (106) expr ::= NUMBER */ + 103, /* (107) expr ::= ID */ + 103, /* (108) expr ::= FUNC1 LP expr RP */ + 103, /* (109) expr ::= FUNC2 LP expr COMMA expr RP */ + 103, /* (110) expr ::= DIST LP position COMMA position RP */ + 103, /* (111) expr ::= place2 DOT_XY X */ + 103, /* (112) expr ::= place2 DOT_XY Y */ + 103, /* (113) expr ::= object DOT_L numproperty */ + 103, /* (114) expr ::= object DOT_L dashproperty */ + 103, /* (115) expr ::= object DOT_L colorproperty */ + 117, /* (116) lvalue ::= ID */ + 117, /* (117) lvalue ::= FILL */ + 117, /* (118) lvalue ::= COLOR */ + 117, /* (119) lvalue ::= THICKNESS */ + 116, /* (120) rvalue ::= expr */ + 122, /* (121) print ::= PRINT */ + 123, /* (122) prlist ::= pritem */ + 123, /* (123) prlist ::= prlist prsep pritem */ + 106, /* (124) direction ::= UP */ + 106, /* (125) direction ::= DOWN */ + 106, /* (126) direction ::= LEFT */ + 106, /* (127) direction ::= RIGHT */ + 120, /* (128) optrelexpr ::= relexpr */ + 126, /* (129) attribute_list ::= alist */ + 128, /* (130) alist ::= */ + 128, /* (131) alist ::= alist attribute */ + 129, /* (132) attribute ::= boolproperty */ + 129, /* (133) attribute ::= WITH withclause */ + 130, /* (134) go ::= GO */ + 130, /* (135) go ::= */ + 118, /* (136) even ::= UNTIL EVEN WITH */ + 118, /* (137) even ::= EVEN WITH */ + 107, /* (138) dashproperty ::= DOTTED */ + 107, /* (139) dashproperty ::= DASHED */ + 108, /* (140) colorproperty ::= FILL */ + 108, /* (141) colorproperty ::= COLOR */ + 110, /* (142) position ::= place */ + 133, /* (143) between ::= WAY BETWEEN */ + 133, /* (144) between ::= BETWEEN */ + 133, /* (145) between ::= OF THE WAY BETWEEN */ + 111, /* (146) place ::= place2 */ + 105, /* (147) edge ::= CENTER */ + 105, /* (148) edge ::= EDGEPT */ + 105, /* (149) edge ::= TOP */ + 105, /* (150) edge ::= BOTTOM */ + 105, /* (151) edge ::= START */ + 105, /* (152) edge ::= END */ + 105, /* (153) edge ::= RIGHT */ + 105, /* (154) edge ::= LEFT */ + 112, /* (155) object ::= objectname */ +}; + +/* For rule J, yyRuleInfoNRhs[J] contains the negative of the number +** of symbols on the right-hand side of that rule. */ +static const signed char yyRuleInfoNRhs[] = { + -1, /* (0) document ::= statement_list */ + -1, /* (1) statement_list ::= statement */ + -3, /* (2) statement_list ::= statement_list EOL statement */ + 0, /* (3) statement ::= */ + -1, /* (4) statement ::= direction */ + -3, /* (5) statement ::= lvalue ASSIGN rvalue */ + -3, /* (6) statement ::= PLACENAME COLON unnamed_statement */ + -3, /* (7) statement ::= PLACENAME COLON position */ + -1, /* (8) statement ::= unnamed_statement */ + -2, /* (9) statement ::= print prlist */ + -6, /* (10) statement ::= ASSERT LP expr EQ expr RP */ + -6, /* (11) statement ::= ASSERT LP position EQ position RP */ + -3, /* (12) statement ::= DEFINE ID CODEBLOCK */ + -1, /* (13) rvalue ::= PLACENAME */ + -1, /* (14) pritem ::= FILL */ + -1, /* (15) pritem ::= COLOR */ + -1, /* (16) pritem ::= THICKNESS */ + -1, /* (17) pritem ::= rvalue */ + -1, /* (18) pritem ::= STRING */ + -1, /* (19) prsep ::= COMMA */ + -2, /* (20) unnamed_statement ::= basetype attribute_list */ + -1, /* (21) basetype ::= CLASSNAME */ + -2, /* (22) basetype ::= STRING textposition */ + -4, /* (23) basetype ::= LB savelist statement_list RB */ + 0, /* (24) savelist ::= */ + -1, /* (25) relexpr ::= expr */ + -2, /* (26) relexpr ::= expr PERCENT */ + 0, /* (27) optrelexpr ::= */ + -2, /* (28) attribute_list ::= relexpr alist */ + -2, /* (29) attribute ::= numproperty relexpr */ + -2, /* (30) attribute ::= dashproperty expr */ + -1, /* (31) attribute ::= dashproperty */ + -2, /* (32) attribute ::= colorproperty rvalue */ + -3, /* (33) attribute ::= go direction optrelexpr */ + -4, /* (34) attribute ::= go direction even position */ + -1, /* (35) attribute ::= CLOSE */ + -1, /* (36) attribute ::= CHOP */ + -2, /* (37) attribute ::= FROM position */ + -2, /* (38) attribute ::= TO position */ + -1, /* (39) attribute ::= THEN */ + -4, /* (40) attribute ::= THEN optrelexpr HEADING expr */ + -3, /* (41) attribute ::= THEN optrelexpr EDGEPT */ + -4, /* (42) attribute ::= GO optrelexpr HEADING expr */ + -3, /* (43) attribute ::= GO optrelexpr EDGEPT */ + -2, /* (44) attribute ::= AT position */ + -1, /* (45) attribute ::= SAME */ + -3, /* (46) attribute ::= SAME AS object */ + -2, /* (47) attribute ::= STRING textposition */ + -1, /* (48) attribute ::= FIT */ + -2, /* (49) attribute ::= BEHIND object */ + -4, /* (50) withclause ::= DOT_E edge AT position */ + -3, /* (51) withclause ::= edge AT position */ + -1, /* (52) numproperty ::= HEIGHT|WIDTH|RADIUS|DIAMETER|THICKNESS */ + -1, /* (53) boolproperty ::= CW */ + -1, /* (54) boolproperty ::= CCW */ + -1, /* (55) boolproperty ::= LARROW */ + -1, /* (56) boolproperty ::= RARROW */ + -1, /* (57) boolproperty ::= LRARROW */ + -1, /* (58) boolproperty ::= INVIS */ + -1, /* (59) boolproperty ::= THICK */ + -1, /* (60) boolproperty ::= THIN */ + -1, /* (61) boolproperty ::= SOLID */ + 0, /* (62) textposition ::= */ + -2, /* (63) textposition ::= textposition CENTER|LJUST|RJUST|ABOVE|BELOW|ITALIC|BOLD|ALIGNED|BIG|SMALL */ + -3, /* (64) position ::= expr COMMA expr */ + -5, /* (65) position ::= place PLUS expr COMMA expr */ + -5, /* (66) position ::= place MINUS expr COMMA expr */ + -7, /* (67) position ::= place PLUS LP expr COMMA expr RP */ + -7, /* (68) position ::= place MINUS LP expr COMMA expr RP */ + -5, /* (69) position ::= LP position COMMA position RP */ + -3, /* (70) position ::= LP position RP */ + -5, /* (71) position ::= expr between position AND position */ + -6, /* (72) position ::= expr LT position COMMA position GT */ + -3, /* (73) position ::= expr ABOVE position */ + -3, /* (74) position ::= expr BELOW position */ + -4, /* (75) position ::= expr LEFT OF position */ + -4, /* (76) position ::= expr RIGHT OF position */ + -6, /* (77) position ::= expr ON HEADING EDGEPT OF position */ + -5, /* (78) position ::= expr HEADING EDGEPT OF position */ + -4, /* (79) position ::= expr EDGEPT OF position */ + -6, /* (80) position ::= expr ON HEADING expr FROM position */ + -5, /* (81) position ::= expr HEADING expr FROM position */ + -3, /* (82) place ::= edge OF object */ + -1, /* (83) place2 ::= object */ + -3, /* (84) place2 ::= object DOT_E edge */ + -4, /* (85) place2 ::= NTH VERTEX OF object */ + -1, /* (86) object ::= nth */ + -3, /* (87) object ::= nth OF|IN object */ + -1, /* (88) objectname ::= THIS */ + -1, /* (89) objectname ::= PLACENAME */ + -3, /* (90) objectname ::= objectname DOT_U PLACENAME */ + -2, /* (91) nth ::= NTH CLASSNAME */ + -3, /* (92) nth ::= NTH LAST CLASSNAME */ + -2, /* (93) nth ::= LAST CLASSNAME */ + -1, /* (94) nth ::= LAST */ + -3, /* (95) nth ::= NTH LB RB */ + -4, /* (96) nth ::= NTH LAST LB RB */ + -3, /* (97) nth ::= LAST LB RB */ + -3, /* (98) expr ::= expr PLUS expr */ + -3, /* (99) expr ::= expr MINUS expr */ + -3, /* (100) expr ::= expr STAR expr */ + -3, /* (101) expr ::= expr SLASH expr */ + -2, /* (102) expr ::= MINUS expr */ + -2, /* (103) expr ::= PLUS expr */ + -3, /* (104) expr ::= LP expr RP */ + -3, /* (105) expr ::= LP FILL|COLOR|THICKNESS RP */ + -1, /* (106) expr ::= NUMBER */ + -1, /* (107) expr ::= ID */ + -4, /* (108) expr ::= FUNC1 LP expr RP */ + -6, /* (109) expr ::= FUNC2 LP expr COMMA expr RP */ + -6, /* (110) expr ::= DIST LP position COMMA position RP */ + -3, /* (111) expr ::= place2 DOT_XY X */ + -3, /* (112) expr ::= place2 DOT_XY Y */ + -3, /* (113) expr ::= object DOT_L numproperty */ + -3, /* (114) expr ::= object DOT_L dashproperty */ + -3, /* (115) expr ::= object DOT_L colorproperty */ + -1, /* (116) lvalue ::= ID */ + -1, /* (117) lvalue ::= FILL */ + -1, /* (118) lvalue ::= COLOR */ + -1, /* (119) lvalue ::= THICKNESS */ + -1, /* (120) rvalue ::= expr */ + -1, /* (121) print ::= PRINT */ + -1, /* (122) prlist ::= pritem */ + -3, /* (123) prlist ::= prlist prsep pritem */ + -1, /* (124) direction ::= UP */ + -1, /* (125) direction ::= DOWN */ + -1, /* (126) direction ::= LEFT */ + -1, /* (127) direction ::= RIGHT */ + -1, /* (128) optrelexpr ::= relexpr */ + -1, /* (129) attribute_list ::= alist */ + 0, /* (130) alist ::= */ + -2, /* (131) alist ::= alist attribute */ + -1, /* (132) attribute ::= boolproperty */ + -2, /* (133) attribute ::= WITH withclause */ + -1, /* (134) go ::= GO */ + 0, /* (135) go ::= */ + -3, /* (136) even ::= UNTIL EVEN WITH */ + -2, /* (137) even ::= EVEN WITH */ + -1, /* (138) dashproperty ::= DOTTED */ + -1, /* (139) dashproperty ::= DASHED */ + -1, /* (140) colorproperty ::= FILL */ + -1, /* (141) colorproperty ::= COLOR */ + -1, /* (142) position ::= place */ + -2, /* (143) between ::= WAY BETWEEN */ + -1, /* (144) between ::= BETWEEN */ + -4, /* (145) between ::= OF THE WAY BETWEEN */ + -1, /* (146) place ::= place2 */ + -1, /* (147) edge ::= CENTER */ + -1, /* (148) edge ::= EDGEPT */ + -1, /* (149) edge ::= TOP */ + -1, /* (150) edge ::= BOTTOM */ + -1, /* (151) edge ::= START */ + -1, /* (152) edge ::= END */ + -1, /* (153) edge ::= RIGHT */ + -1, /* (154) edge ::= LEFT */ + -1, /* (155) object ::= objectname */ +}; + +static void yy_accept(yyParser*); /* Forward Declaration */ + +/* +** Perform a reduce action and the shift that must immediately +** follow the reduce. +** +** The yyLookahead and yyLookaheadToken parameters provide reduce actions +** access to the lookahead token (if any). The yyLookahead will be YYNOCODE +** if the lookahead token has already been consumed. As this procedure is +** only called from one place, optimizing compilers will in-line it, which +** means that the extra parameters have no performance impact. +*/ +static YYACTIONTYPE yy_reduce( + yyParser *yypParser, /* The parser */ + unsigned int yyruleno, /* Number of the rule by which to reduce */ + int yyLookahead, /* Lookahead token, or YYNOCODE if none */ + pik_parserTOKENTYPE yyLookaheadToken /* Value of the lookahead token */ + pik_parserCTX_PDECL /* %extra_context */ +){ + int yygoto; /* The next state */ + YYACTIONTYPE yyact; /* The next action */ + yyStackEntry *yymsp; /* The top of the parser's stack */ + int yysize; /* Amount to pop the stack */ + pik_parserARG_FETCH + (void)yyLookahead; + (void)yyLookaheadToken; + yymsp = yypParser->yytos; + assert( yyruleno<(int)(sizeof(yyRuleName)/sizeof(yyRuleName[0])) ); +#ifndef NDEBUG + if( yyTraceFILE ){ + yysize = yyRuleInfoNRhs[yyruleno]; + if( yysize ){ + fprintf(yyTraceFILE, "%sReduce %d [%s]%s, pop back to state %d.\n", + yyTracePrompt, + yyruleno, yyRuleName[yyruleno], + yyrulenoyytos - yypParser->yystack)>yypParser->yyhwm ){ + yypParser->yyhwm++; + assert( yypParser->yyhwm == (int)(yypParser->yytos - yypParser->yystack)); + } +#endif +#if YYSTACKDEPTH>0 + if( yypParser->yytos>=yypParser->yystackEnd ){ + yyStackOverflow(yypParser); + /* The call to yyStackOverflow() above pops the stack until it is + ** empty, causing the main parser loop to exit. So the return value + ** is never used and does not matter. */ + return 0; + } +#else + if( yypParser->yytos>=&yypParser->yystack[yypParser->yystksz-1] ){ + if( yyGrowStack(yypParser) ){ + yyStackOverflow(yypParser); + /* The call to yyStackOverflow() above pops the stack until it is + ** empty, causing the main parser loop to exit. So the return value + ** is never used and does not matter. */ + return 0; + } + yymsp = yypParser->yytos; + } +#endif + } + + switch( yyruleno ){ + /* Beginning here are the reduction cases. A typical example + ** follows: + ** case 0: + ** #line + ** { ... } // User supplied code + ** #line + ** break; + */ +/********** Begin reduce actions **********************************************/ + YYMINORTYPE yylhsminor; + case 0: /* document ::= statement_list */ +#line 532 "pikchr.y" +{pik_render(p,yymsp[0].minor.yy227);} +#line 2447 "pikchr.c" + break; + case 1: /* statement_list ::= statement */ +#line 535 "pikchr.y" +{ yylhsminor.yy227 = pik_elist_append(p,0,yymsp[0].minor.yy36); } +#line 2452 "pikchr.c" + yymsp[0].minor.yy227 = yylhsminor.yy227; + break; + case 2: /* statement_list ::= statement_list EOL statement */ +#line 537 "pikchr.y" +{ yylhsminor.yy227 = pik_elist_append(p,yymsp[-2].minor.yy227,yymsp[0].minor.yy36); } +#line 2458 "pikchr.c" + yymsp[-2].minor.yy227 = yylhsminor.yy227; + break; + case 3: /* statement ::= */ +#line 540 "pikchr.y" +{ yymsp[1].minor.yy36 = 0; } +#line 2464 "pikchr.c" + break; + case 4: /* statement ::= direction */ +#line 541 "pikchr.y" +{ pik_set_direction(p,yymsp[0].minor.yy0.eCode); yylhsminor.yy36=0; } +#line 2469 "pikchr.c" + yymsp[0].minor.yy36 = yylhsminor.yy36; + break; + case 5: /* statement ::= lvalue ASSIGN rvalue */ +#line 542 "pikchr.y" +{pik_set_var(p,&yymsp[-2].minor.yy0,yymsp[0].minor.yy153,&yymsp[-1].minor.yy0); yylhsminor.yy36=0;} +#line 2475 "pikchr.c" + yymsp[-2].minor.yy36 = yylhsminor.yy36; + break; + case 6: /* statement ::= PLACENAME COLON unnamed_statement */ +#line 544 "pikchr.y" +{ yylhsminor.yy36 = yymsp[0].minor.yy36; pik_elem_setname(p,yymsp[0].minor.yy36,&yymsp[-2].minor.yy0); } +#line 2481 "pikchr.c" + yymsp[-2].minor.yy36 = yylhsminor.yy36; + break; + case 7: /* statement ::= PLACENAME COLON position */ +#line 546 "pikchr.y" +{ yylhsminor.yy36 = pik_elem_new(p,0,0,0); + if(yylhsminor.yy36){ yylhsminor.yy36->ptAt = yymsp[0].minor.yy79; pik_elem_setname(p,yylhsminor.yy36,&yymsp[-2].minor.yy0); }} +#line 2488 "pikchr.c" + yymsp[-2].minor.yy36 = yylhsminor.yy36; + break; + case 8: /* statement ::= unnamed_statement */ +#line 548 "pikchr.y" +{yylhsminor.yy36 = yymsp[0].minor.yy36;} +#line 2494 "pikchr.c" + yymsp[0].minor.yy36 = yylhsminor.yy36; + break; + case 9: /* statement ::= print prlist */ +#line 549 "pikchr.y" +{pik_append(p,"
      \n",5); yymsp[-1].minor.yy36=0;} +#line 2500 "pikchr.c" + break; + case 10: /* statement ::= ASSERT LP expr EQ expr RP */ +#line 554 "pikchr.y" +{yymsp[-5].minor.yy36=pik_assert(p,yymsp[-3].minor.yy153,&yymsp[-2].minor.yy0,yymsp[-1].minor.yy153);} +#line 2505 "pikchr.c" + break; + case 11: /* statement ::= ASSERT LP position EQ position RP */ +#line 556 "pikchr.y" +{yymsp[-5].minor.yy36=pik_position_assert(p,&yymsp[-3].minor.yy79,&yymsp[-2].minor.yy0,&yymsp[-1].minor.yy79);} +#line 2510 "pikchr.c" + break; + case 12: /* statement ::= DEFINE ID CODEBLOCK */ +#line 557 "pikchr.y" +{yymsp[-2].minor.yy36=0; pik_add_macro(p,&yymsp[-1].minor.yy0,&yymsp[0].minor.yy0);} +#line 2515 "pikchr.c" + break; + case 13: /* rvalue ::= PLACENAME */ +#line 568 "pikchr.y" +{yylhsminor.yy153 = pik_lookup_color(p,&yymsp[0].minor.yy0);} +#line 2520 "pikchr.c" + yymsp[0].minor.yy153 = yylhsminor.yy153; + break; + case 14: /* pritem ::= FILL */ + case 15: /* pritem ::= COLOR */ yytestcase(yyruleno==15); + case 16: /* pritem ::= THICKNESS */ yytestcase(yyruleno==16); +#line 573 "pikchr.y" +{pik_append_num(p,"",pik_value(p,yymsp[0].minor.yy0.z,yymsp[0].minor.yy0.n,0));} +#line 2528 "pikchr.c" + break; + case 17: /* pritem ::= rvalue */ +#line 576 "pikchr.y" +{pik_append_num(p,"",yymsp[0].minor.yy153);} +#line 2533 "pikchr.c" + break; + case 18: /* pritem ::= STRING */ +#line 577 "pikchr.y" +{pik_append_text(p,yymsp[0].minor.yy0.z+1,yymsp[0].minor.yy0.n-2,0);} +#line 2538 "pikchr.c" + break; + case 19: /* prsep ::= COMMA */ +#line 578 "pikchr.y" +{pik_append(p, " ", 1);} +#line 2543 "pikchr.c" + break; + case 20: /* unnamed_statement ::= basetype attribute_list */ +#line 581 "pikchr.y" +{yylhsminor.yy36 = yymsp[-1].minor.yy36; pik_after_adding_attributes(p,yylhsminor.yy36);} +#line 2548 "pikchr.c" + yymsp[-1].minor.yy36 = yylhsminor.yy36; + break; + case 21: /* basetype ::= CLASSNAME */ +#line 583 "pikchr.y" +{yylhsminor.yy36 = pik_elem_new(p,&yymsp[0].minor.yy0,0,0); } +#line 2554 "pikchr.c" + yymsp[0].minor.yy36 = yylhsminor.yy36; + break; + case 22: /* basetype ::= STRING textposition */ +#line 585 "pikchr.y" +{yymsp[-1].minor.yy0.eCode = yymsp[0].minor.yy164; yylhsminor.yy36 = pik_elem_new(p,0,&yymsp[-1].minor.yy0,0); } +#line 2560 "pikchr.c" + yymsp[-1].minor.yy36 = yylhsminor.yy36; + break; + case 23: /* basetype ::= LB savelist statement_list RB */ +#line 587 "pikchr.y" +{ p->list = yymsp[-2].minor.yy227; yymsp[-3].minor.yy36 = pik_elem_new(p,0,0,yymsp[-1].minor.yy227); if(yymsp[-3].minor.yy36) yymsp[-3].minor.yy36->errTok = yymsp[0].minor.yy0; } +#line 2566 "pikchr.c" + break; + case 24: /* savelist ::= */ +#line 592 "pikchr.y" +{yymsp[1].minor.yy227 = p->list; p->list = 0;} +#line 2571 "pikchr.c" + break; + case 25: /* relexpr ::= expr */ +#line 599 "pikchr.y" +{yylhsminor.yy10.rAbs = yymsp[0].minor.yy153; yylhsminor.yy10.rRel = 0;} +#line 2576 "pikchr.c" + yymsp[0].minor.yy10 = yylhsminor.yy10; + break; + case 26: /* relexpr ::= expr PERCENT */ +#line 600 "pikchr.y" +{yylhsminor.yy10.rAbs = 0; yylhsminor.yy10.rRel = yymsp[-1].minor.yy153/100;} +#line 2582 "pikchr.c" + yymsp[-1].minor.yy10 = yylhsminor.yy10; + break; + case 27: /* optrelexpr ::= */ +#line 602 "pikchr.y" +{yymsp[1].minor.yy10.rAbs = 0; yymsp[1].minor.yy10.rRel = 1.0;} +#line 2588 "pikchr.c" + break; + case 28: /* attribute_list ::= relexpr alist */ +#line 604 "pikchr.y" +{pik_add_direction(p,0,&yymsp[-1].minor.yy10);} +#line 2593 "pikchr.c" + break; + case 29: /* attribute ::= numproperty relexpr */ +#line 608 "pikchr.y" +{ pik_set_numprop(p,&yymsp[-1].minor.yy0,&yymsp[0].minor.yy10); } +#line 2598 "pikchr.c" + break; + case 30: /* attribute ::= dashproperty expr */ +#line 609 "pikchr.y" +{ pik_set_dashed(p,&yymsp[-1].minor.yy0,&yymsp[0].minor.yy153); } +#line 2603 "pikchr.c" + break; + case 31: /* attribute ::= dashproperty */ +#line 610 "pikchr.y" +{ pik_set_dashed(p,&yymsp[0].minor.yy0,0); } +#line 2608 "pikchr.c" + break; + case 32: /* attribute ::= colorproperty rvalue */ +#line 611 "pikchr.y" +{ pik_set_clrprop(p,&yymsp[-1].minor.yy0,yymsp[0].minor.yy153); } +#line 2613 "pikchr.c" + break; + case 33: /* attribute ::= go direction optrelexpr */ +#line 612 "pikchr.y" +{ pik_add_direction(p,&yymsp[-1].minor.yy0,&yymsp[0].minor.yy10);} +#line 2618 "pikchr.c" + break; + case 34: /* attribute ::= go direction even position */ +#line 613 "pikchr.y" +{pik_evenwith(p,&yymsp[-2].minor.yy0,&yymsp[0].minor.yy79);} +#line 2623 "pikchr.c" + break; + case 35: /* attribute ::= CLOSE */ +#line 614 "pikchr.y" +{ pik_close_path(p,&yymsp[0].minor.yy0); } +#line 2628 "pikchr.c" + break; + case 36: /* attribute ::= CHOP */ +#line 615 "pikchr.y" +{ p->cur->bChop = 1; } +#line 2633 "pikchr.c" + break; + case 37: /* attribute ::= FROM position */ +#line 616 "pikchr.y" +{ pik_set_from(p,p->cur,&yymsp[-1].minor.yy0,&yymsp[0].minor.yy79); } +#line 2638 "pikchr.c" + break; + case 38: /* attribute ::= TO position */ +#line 617 "pikchr.y" +{ pik_add_to(p,p->cur,&yymsp[-1].minor.yy0,&yymsp[0].minor.yy79); } +#line 2643 "pikchr.c" + break; + case 39: /* attribute ::= THEN */ +#line 618 "pikchr.y" +{ pik_then(p, &yymsp[0].minor.yy0, p->cur); } +#line 2648 "pikchr.c" + break; + case 40: /* attribute ::= THEN optrelexpr HEADING expr */ + case 42: /* attribute ::= GO optrelexpr HEADING expr */ yytestcase(yyruleno==42); +#line 620 "pikchr.y" +{pik_move_hdg(p,&yymsp[-2].minor.yy10,&yymsp[-1].minor.yy0,yymsp[0].minor.yy153,0,&yymsp[-3].minor.yy0);} +#line 2654 "pikchr.c" + break; + case 41: /* attribute ::= THEN optrelexpr EDGEPT */ + case 43: /* attribute ::= GO optrelexpr EDGEPT */ yytestcase(yyruleno==43); +#line 621 "pikchr.y" +{pik_move_hdg(p,&yymsp[-1].minor.yy10,0,0,&yymsp[0].minor.yy0,&yymsp[-2].minor.yy0);} +#line 2660 "pikchr.c" + break; + case 44: /* attribute ::= AT position */ +#line 626 "pikchr.y" +{ pik_set_at(p,0,&yymsp[0].minor.yy79,&yymsp[-1].minor.yy0); } +#line 2665 "pikchr.c" + break; + case 45: /* attribute ::= SAME */ +#line 628 "pikchr.y" +{pik_same(p,0,&yymsp[0].minor.yy0);} +#line 2670 "pikchr.c" + break; + case 46: /* attribute ::= SAME AS object */ +#line 629 "pikchr.y" +{pik_same(p,yymsp[0].minor.yy36,&yymsp[-2].minor.yy0);} +#line 2675 "pikchr.c" + break; + case 47: /* attribute ::= STRING textposition */ +#line 630 "pikchr.y" +{pik_add_txt(p,&yymsp[-1].minor.yy0,yymsp[0].minor.yy164);} +#line 2680 "pikchr.c" + break; + case 48: /* attribute ::= FIT */ +#line 631 "pikchr.y" +{pik_size_to_fit(p,&yymsp[0].minor.yy0,3); } +#line 2685 "pikchr.c" + break; + case 49: /* attribute ::= BEHIND object */ +#line 632 "pikchr.y" +{pik_behind(p,yymsp[0].minor.yy36);} +#line 2690 "pikchr.c" + break; + case 50: /* withclause ::= DOT_E edge AT position */ + case 51: /* withclause ::= edge AT position */ yytestcase(yyruleno==51); +#line 640 "pikchr.y" +{ pik_set_at(p,&yymsp[-2].minor.yy0,&yymsp[0].minor.yy79,&yymsp[-1].minor.yy0); } +#line 2696 "pikchr.c" + break; + case 52: /* numproperty ::= HEIGHT|WIDTH|RADIUS|DIAMETER|THICKNESS */ +#line 644 "pikchr.y" +{yylhsminor.yy0 = yymsp[0].minor.yy0;} +#line 2701 "pikchr.c" + yymsp[0].minor.yy0 = yylhsminor.yy0; + break; + case 53: /* boolproperty ::= CW */ +#line 655 "pikchr.y" +{p->cur->cw = 1;} +#line 2707 "pikchr.c" + break; + case 54: /* boolproperty ::= CCW */ +#line 656 "pikchr.y" +{p->cur->cw = 0;} +#line 2712 "pikchr.c" + break; + case 55: /* boolproperty ::= LARROW */ +#line 657 "pikchr.y" +{p->cur->larrow=1; p->cur->rarrow=0; } +#line 2717 "pikchr.c" + break; + case 56: /* boolproperty ::= RARROW */ +#line 658 "pikchr.y" +{p->cur->larrow=0; p->cur->rarrow=1; } +#line 2722 "pikchr.c" + break; + case 57: /* boolproperty ::= LRARROW */ +#line 659 "pikchr.y" +{p->cur->larrow=1; p->cur->rarrow=1; } +#line 2727 "pikchr.c" + break; + case 58: /* boolproperty ::= INVIS */ +#line 660 "pikchr.y" +{p->cur->sw = 0.0;} +#line 2732 "pikchr.c" + break; + case 59: /* boolproperty ::= THICK */ +#line 661 "pikchr.y" +{p->cur->sw *= 1.5;} +#line 2737 "pikchr.c" + break; + case 60: /* boolproperty ::= THIN */ +#line 662 "pikchr.y" +{p->cur->sw *= 0.67;} +#line 2742 "pikchr.c" + break; + case 61: /* boolproperty ::= SOLID */ +#line 663 "pikchr.y" +{p->cur->sw = pik_value(p,"thickness",9,0); + p->cur->dotted = p->cur->dashed = 0.0;} +#line 2748 "pikchr.c" + break; + case 62: /* textposition ::= */ +#line 666 "pikchr.y" +{yymsp[1].minor.yy164 = 0;} +#line 2753 "pikchr.c" + break; + case 63: /* textposition ::= textposition CENTER|LJUST|RJUST|ABOVE|BELOW|ITALIC|BOLD|ALIGNED|BIG|SMALL */ +#line 669 "pikchr.y" +{yylhsminor.yy164 = (short int)pik_text_position(yymsp[-1].minor.yy164,&yymsp[0].minor.yy0);} +#line 2758 "pikchr.c" + yymsp[-1].minor.yy164 = yylhsminor.yy164; + break; + case 64: /* position ::= expr COMMA expr */ +#line 672 "pikchr.y" +{yylhsminor.yy79.x=yymsp[-2].minor.yy153; yylhsminor.yy79.y=yymsp[0].minor.yy153;} +#line 2764 "pikchr.c" + yymsp[-2].minor.yy79 = yylhsminor.yy79; + break; + case 65: /* position ::= place PLUS expr COMMA expr */ +#line 674 "pikchr.y" +{yylhsminor.yy79.x=yymsp[-4].minor.yy79.x+yymsp[-2].minor.yy153; yylhsminor.yy79.y=yymsp[-4].minor.yy79.y+yymsp[0].minor.yy153;} +#line 2770 "pikchr.c" + yymsp[-4].minor.yy79 = yylhsminor.yy79; + break; + case 66: /* position ::= place MINUS expr COMMA expr */ +#line 675 "pikchr.y" +{yylhsminor.yy79.x=yymsp[-4].minor.yy79.x-yymsp[-2].minor.yy153; yylhsminor.yy79.y=yymsp[-4].minor.yy79.y-yymsp[0].minor.yy153;} +#line 2776 "pikchr.c" + yymsp[-4].minor.yy79 = yylhsminor.yy79; + break; + case 67: /* position ::= place PLUS LP expr COMMA expr RP */ +#line 677 "pikchr.y" +{yylhsminor.yy79.x=yymsp[-6].minor.yy79.x+yymsp[-3].minor.yy153; yylhsminor.yy79.y=yymsp[-6].minor.yy79.y+yymsp[-1].minor.yy153;} +#line 2782 "pikchr.c" + yymsp[-6].minor.yy79 = yylhsminor.yy79; + break; + case 68: /* position ::= place MINUS LP expr COMMA expr RP */ +#line 679 "pikchr.y" +{yylhsminor.yy79.x=yymsp[-6].minor.yy79.x-yymsp[-3].minor.yy153; yylhsminor.yy79.y=yymsp[-6].minor.yy79.y-yymsp[-1].minor.yy153;} +#line 2788 "pikchr.c" + yymsp[-6].minor.yy79 = yylhsminor.yy79; + break; + case 69: /* position ::= LP position COMMA position RP */ +#line 680 "pikchr.y" +{yymsp[-4].minor.yy79.x=yymsp[-3].minor.yy79.x; yymsp[-4].minor.yy79.y=yymsp[-1].minor.yy79.y;} +#line 2794 "pikchr.c" + break; + case 70: /* position ::= LP position RP */ +#line 681 "pikchr.y" +{yymsp[-2].minor.yy79=yymsp[-1].minor.yy79;} +#line 2799 "pikchr.c" + break; + case 71: /* position ::= expr between position AND position */ +#line 683 "pikchr.y" +{yylhsminor.yy79 = pik_position_between(yymsp[-4].minor.yy153,yymsp[-2].minor.yy79,yymsp[0].minor.yy79);} +#line 2804 "pikchr.c" + yymsp[-4].minor.yy79 = yylhsminor.yy79; + break; + case 72: /* position ::= expr LT position COMMA position GT */ +#line 685 "pikchr.y" +{yylhsminor.yy79 = pik_position_between(yymsp[-5].minor.yy153,yymsp[-3].minor.yy79,yymsp[-1].minor.yy79);} +#line 2810 "pikchr.c" + yymsp[-5].minor.yy79 = yylhsminor.yy79; + break; + case 73: /* position ::= expr ABOVE position */ +#line 686 "pikchr.y" +{yylhsminor.yy79=yymsp[0].minor.yy79; yylhsminor.yy79.y += yymsp[-2].minor.yy153;} +#line 2816 "pikchr.c" + yymsp[-2].minor.yy79 = yylhsminor.yy79; + break; + case 74: /* position ::= expr BELOW position */ +#line 687 "pikchr.y" +{yylhsminor.yy79=yymsp[0].minor.yy79; yylhsminor.yy79.y -= yymsp[-2].minor.yy153;} +#line 2822 "pikchr.c" + yymsp[-2].minor.yy79 = yylhsminor.yy79; + break; + case 75: /* position ::= expr LEFT OF position */ +#line 688 "pikchr.y" +{yylhsminor.yy79=yymsp[0].minor.yy79; yylhsminor.yy79.x -= yymsp[-3].minor.yy153;} +#line 2828 "pikchr.c" + yymsp[-3].minor.yy79 = yylhsminor.yy79; + break; + case 76: /* position ::= expr RIGHT OF position */ +#line 689 "pikchr.y" +{yylhsminor.yy79=yymsp[0].minor.yy79; yylhsminor.yy79.x += yymsp[-3].minor.yy153;} +#line 2834 "pikchr.c" + yymsp[-3].minor.yy79 = yylhsminor.yy79; + break; + case 77: /* position ::= expr ON HEADING EDGEPT OF position */ +#line 691 "pikchr.y" +{yylhsminor.yy79 = pik_position_at_hdg(yymsp[-5].minor.yy153,&yymsp[-2].minor.yy0,yymsp[0].minor.yy79);} +#line 2840 "pikchr.c" + yymsp[-5].minor.yy79 = yylhsminor.yy79; + break; + case 78: /* position ::= expr HEADING EDGEPT OF position */ +#line 693 "pikchr.y" +{yylhsminor.yy79 = pik_position_at_hdg(yymsp[-4].minor.yy153,&yymsp[-2].minor.yy0,yymsp[0].minor.yy79);} +#line 2846 "pikchr.c" + yymsp[-4].minor.yy79 = yylhsminor.yy79; + break; + case 79: /* position ::= expr EDGEPT OF position */ +#line 695 "pikchr.y" +{yylhsminor.yy79 = pik_position_at_hdg(yymsp[-3].minor.yy153,&yymsp[-2].minor.yy0,yymsp[0].minor.yy79);} +#line 2852 "pikchr.c" + yymsp[-3].minor.yy79 = yylhsminor.yy79; + break; + case 80: /* position ::= expr ON HEADING expr FROM position */ +#line 697 "pikchr.y" +{yylhsminor.yy79 = pik_position_at_angle(yymsp[-5].minor.yy153,yymsp[-2].minor.yy153,yymsp[0].minor.yy79);} +#line 2858 "pikchr.c" + yymsp[-5].minor.yy79 = yylhsminor.yy79; + break; + case 81: /* position ::= expr HEADING expr FROM position */ +#line 699 "pikchr.y" +{yylhsminor.yy79 = pik_position_at_angle(yymsp[-4].minor.yy153,yymsp[-2].minor.yy153,yymsp[0].minor.yy79);} +#line 2864 "pikchr.c" + yymsp[-4].minor.yy79 = yylhsminor.yy79; + break; + case 82: /* place ::= edge OF object */ +#line 711 "pikchr.y" +{yylhsminor.yy79 = pik_place_of_elem(p,yymsp[0].minor.yy36,&yymsp[-2].minor.yy0);} +#line 2870 "pikchr.c" + yymsp[-2].minor.yy79 = yylhsminor.yy79; + break; + case 83: /* place2 ::= object */ +#line 712 "pikchr.y" +{yylhsminor.yy79 = pik_place_of_elem(p,yymsp[0].minor.yy36,0);} +#line 2876 "pikchr.c" + yymsp[0].minor.yy79 = yylhsminor.yy79; + break; + case 84: /* place2 ::= object DOT_E edge */ +#line 713 "pikchr.y" +{yylhsminor.yy79 = pik_place_of_elem(p,yymsp[-2].minor.yy36,&yymsp[0].minor.yy0);} +#line 2882 "pikchr.c" + yymsp[-2].minor.yy79 = yylhsminor.yy79; + break; + case 85: /* place2 ::= NTH VERTEX OF object */ +#line 714 "pikchr.y" +{yylhsminor.yy79 = pik_nth_vertex(p,&yymsp[-3].minor.yy0,&yymsp[-2].minor.yy0,yymsp[0].minor.yy36);} +#line 2888 "pikchr.c" + yymsp[-3].minor.yy79 = yylhsminor.yy79; + break; + case 86: /* object ::= nth */ +#line 726 "pikchr.y" +{yylhsminor.yy36 = pik_find_nth(p,0,&yymsp[0].minor.yy0);} +#line 2894 "pikchr.c" + yymsp[0].minor.yy36 = yylhsminor.yy36; + break; + case 87: /* object ::= nth OF|IN object */ +#line 727 "pikchr.y" +{yylhsminor.yy36 = pik_find_nth(p,yymsp[0].minor.yy36,&yymsp[-2].minor.yy0);} +#line 2900 "pikchr.c" + yymsp[-2].minor.yy36 = yylhsminor.yy36; + break; + case 88: /* objectname ::= THIS */ +#line 729 "pikchr.y" +{yymsp[0].minor.yy36 = p->cur;} +#line 2906 "pikchr.c" + break; + case 89: /* objectname ::= PLACENAME */ +#line 730 "pikchr.y" +{yylhsminor.yy36 = pik_find_byname(p,0,&yymsp[0].minor.yy0);} +#line 2911 "pikchr.c" + yymsp[0].minor.yy36 = yylhsminor.yy36; + break; + case 90: /* objectname ::= objectname DOT_U PLACENAME */ +#line 732 "pikchr.y" +{yylhsminor.yy36 = pik_find_byname(p,yymsp[-2].minor.yy36,&yymsp[0].minor.yy0);} +#line 2917 "pikchr.c" + yymsp[-2].minor.yy36 = yylhsminor.yy36; + break; + case 91: /* nth ::= NTH CLASSNAME */ +#line 734 "pikchr.y" +{yylhsminor.yy0=yymsp[0].minor.yy0; yylhsminor.yy0.eCode = pik_nth_value(p,&yymsp[-1].minor.yy0); } +#line 2923 "pikchr.c" + yymsp[-1].minor.yy0 = yylhsminor.yy0; + break; + case 92: /* nth ::= NTH LAST CLASSNAME */ +#line 735 "pikchr.y" +{yylhsminor.yy0=yymsp[0].minor.yy0; yylhsminor.yy0.eCode = -pik_nth_value(p,&yymsp[-2].minor.yy0); } +#line 2929 "pikchr.c" + yymsp[-2].minor.yy0 = yylhsminor.yy0; + break; + case 93: /* nth ::= LAST CLASSNAME */ +#line 736 "pikchr.y" +{yymsp[-1].minor.yy0=yymsp[0].minor.yy0; yymsp[-1].minor.yy0.eCode = -1;} +#line 2935 "pikchr.c" + break; + case 94: /* nth ::= LAST */ +#line 737 "pikchr.y" +{yylhsminor.yy0=yymsp[0].minor.yy0; yylhsminor.yy0.eCode = -1;} +#line 2940 "pikchr.c" + yymsp[0].minor.yy0 = yylhsminor.yy0; + break; + case 95: /* nth ::= NTH LB RB */ +#line 738 "pikchr.y" +{yylhsminor.yy0=yymsp[-1].minor.yy0; yylhsminor.yy0.eCode = pik_nth_value(p,&yymsp[-2].minor.yy0);} +#line 2946 "pikchr.c" + yymsp[-2].minor.yy0 = yylhsminor.yy0; + break; + case 96: /* nth ::= NTH LAST LB RB */ +#line 739 "pikchr.y" +{yylhsminor.yy0=yymsp[-1].minor.yy0; yylhsminor.yy0.eCode = -pik_nth_value(p,&yymsp[-3].minor.yy0);} +#line 2952 "pikchr.c" + yymsp[-3].minor.yy0 = yylhsminor.yy0; + break; + case 97: /* nth ::= LAST LB RB */ +#line 740 "pikchr.y" +{yymsp[-2].minor.yy0=yymsp[-1].minor.yy0; yymsp[-2].minor.yy0.eCode = -1; } +#line 2958 "pikchr.c" + break; + case 98: /* expr ::= expr PLUS expr */ +#line 742 "pikchr.y" +{yylhsminor.yy153=yymsp[-2].minor.yy153+yymsp[0].minor.yy153;} +#line 2963 "pikchr.c" + yymsp[-2].minor.yy153 = yylhsminor.yy153; + break; + case 99: /* expr ::= expr MINUS expr */ +#line 743 "pikchr.y" +{yylhsminor.yy153=yymsp[-2].minor.yy153-yymsp[0].minor.yy153;} +#line 2969 "pikchr.c" + yymsp[-2].minor.yy153 = yylhsminor.yy153; + break; + case 100: /* expr ::= expr STAR expr */ +#line 744 "pikchr.y" +{yylhsminor.yy153=yymsp[-2].minor.yy153*yymsp[0].minor.yy153;} +#line 2975 "pikchr.c" + yymsp[-2].minor.yy153 = yylhsminor.yy153; + break; + case 101: /* expr ::= expr SLASH expr */ +#line 745 "pikchr.y" +{ + if( yymsp[0].minor.yy153==0.0 ){ pik_error(p, &yymsp[-1].minor.yy0, "division by zero"); yylhsminor.yy153 = 0.0; } + else{ yylhsminor.yy153 = yymsp[-2].minor.yy153/yymsp[0].minor.yy153; } +} +#line 2984 "pikchr.c" + yymsp[-2].minor.yy153 = yylhsminor.yy153; + break; + case 102: /* expr ::= MINUS expr */ +#line 749 "pikchr.y" +{yymsp[-1].minor.yy153=-yymsp[0].minor.yy153;} +#line 2990 "pikchr.c" + break; + case 103: /* expr ::= PLUS expr */ +#line 750 "pikchr.y" +{yymsp[-1].minor.yy153=yymsp[0].minor.yy153;} +#line 2995 "pikchr.c" + break; + case 104: /* expr ::= LP expr RP */ +#line 751 "pikchr.y" +{yymsp[-2].minor.yy153=yymsp[-1].minor.yy153;} +#line 3000 "pikchr.c" + break; + case 105: /* expr ::= LP FILL|COLOR|THICKNESS RP */ +#line 752 "pikchr.y" +{yymsp[-2].minor.yy153=pik_get_var(p,&yymsp[-1].minor.yy0);} +#line 3005 "pikchr.c" + break; + case 106: /* expr ::= NUMBER */ +#line 753 "pikchr.y" +{yylhsminor.yy153=pik_atof(&yymsp[0].minor.yy0);} +#line 3010 "pikchr.c" + yymsp[0].minor.yy153 = yylhsminor.yy153; + break; + case 107: /* expr ::= ID */ +#line 754 "pikchr.y" +{yylhsminor.yy153=pik_get_var(p,&yymsp[0].minor.yy0);} +#line 3016 "pikchr.c" + yymsp[0].minor.yy153 = yylhsminor.yy153; + break; + case 108: /* expr ::= FUNC1 LP expr RP */ +#line 755 "pikchr.y" +{yylhsminor.yy153 = pik_func(p,&yymsp[-3].minor.yy0,yymsp[-1].minor.yy153,0.0);} +#line 3022 "pikchr.c" + yymsp[-3].minor.yy153 = yylhsminor.yy153; + break; + case 109: /* expr ::= FUNC2 LP expr COMMA expr RP */ +#line 756 "pikchr.y" +{yylhsminor.yy153 = pik_func(p,&yymsp[-5].minor.yy0,yymsp[-3].minor.yy153,yymsp[-1].minor.yy153);} +#line 3028 "pikchr.c" + yymsp[-5].minor.yy153 = yylhsminor.yy153; + break; + case 110: /* expr ::= DIST LP position COMMA position RP */ +#line 757 "pikchr.y" +{yymsp[-5].minor.yy153 = pik_dist(&yymsp[-3].minor.yy79,&yymsp[-1].minor.yy79);} +#line 3034 "pikchr.c" + break; + case 111: /* expr ::= place2 DOT_XY X */ +#line 758 "pikchr.y" +{yylhsminor.yy153 = yymsp[-2].minor.yy79.x;} +#line 3039 "pikchr.c" + yymsp[-2].minor.yy153 = yylhsminor.yy153; + break; + case 112: /* expr ::= place2 DOT_XY Y */ +#line 759 "pikchr.y" +{yylhsminor.yy153 = yymsp[-2].minor.yy79.y;} +#line 3045 "pikchr.c" + yymsp[-2].minor.yy153 = yylhsminor.yy153; + break; + case 113: /* expr ::= object DOT_L numproperty */ + case 114: /* expr ::= object DOT_L dashproperty */ yytestcase(yyruleno==114); + case 115: /* expr ::= object DOT_L colorproperty */ yytestcase(yyruleno==115); +#line 760 "pikchr.y" +{yylhsminor.yy153=pik_property_of(yymsp[-2].minor.yy36,&yymsp[0].minor.yy0);} +#line 3053 "pikchr.c" + yymsp[-2].minor.yy153 = yylhsminor.yy153; + break; + default: + /* (116) lvalue ::= ID */ yytestcase(yyruleno==116); + /* (117) lvalue ::= FILL */ yytestcase(yyruleno==117); + /* (118) lvalue ::= COLOR */ yytestcase(yyruleno==118); + /* (119) lvalue ::= THICKNESS */ yytestcase(yyruleno==119); + /* (120) rvalue ::= expr */ yytestcase(yyruleno==120); + /* (121) print ::= PRINT */ yytestcase(yyruleno==121); + /* (122) prlist ::= pritem (OPTIMIZED OUT) */ assert(yyruleno!=122); + /* (123) prlist ::= prlist prsep pritem */ yytestcase(yyruleno==123); + /* (124) direction ::= UP */ yytestcase(yyruleno==124); + /* (125) direction ::= DOWN */ yytestcase(yyruleno==125); + /* (126) direction ::= LEFT */ yytestcase(yyruleno==126); + /* (127) direction ::= RIGHT */ yytestcase(yyruleno==127); + /* (128) optrelexpr ::= relexpr (OPTIMIZED OUT) */ assert(yyruleno!=128); + /* (129) attribute_list ::= alist */ yytestcase(yyruleno==129); + /* (130) alist ::= */ yytestcase(yyruleno==130); + /* (131) alist ::= alist attribute */ yytestcase(yyruleno==131); + /* (132) attribute ::= boolproperty (OPTIMIZED OUT) */ assert(yyruleno!=132); + /* (133) attribute ::= WITH withclause */ yytestcase(yyruleno==133); + /* (134) go ::= GO */ yytestcase(yyruleno==134); + /* (135) go ::= */ yytestcase(yyruleno==135); + /* (136) even ::= UNTIL EVEN WITH */ yytestcase(yyruleno==136); + /* (137) even ::= EVEN WITH */ yytestcase(yyruleno==137); + /* (138) dashproperty ::= DOTTED */ yytestcase(yyruleno==138); + /* (139) dashproperty ::= DASHED */ yytestcase(yyruleno==139); + /* (140) colorproperty ::= FILL */ yytestcase(yyruleno==140); + /* (141) colorproperty ::= COLOR */ yytestcase(yyruleno==141); + /* (142) position ::= place */ yytestcase(yyruleno==142); + /* (143) between ::= WAY BETWEEN */ yytestcase(yyruleno==143); + /* (144) between ::= BETWEEN */ yytestcase(yyruleno==144); + /* (145) between ::= OF THE WAY BETWEEN */ yytestcase(yyruleno==145); + /* (146) place ::= place2 */ yytestcase(yyruleno==146); + /* (147) edge ::= CENTER */ yytestcase(yyruleno==147); + /* (148) edge ::= EDGEPT */ yytestcase(yyruleno==148); + /* (149) edge ::= TOP */ yytestcase(yyruleno==149); + /* (150) edge ::= BOTTOM */ yytestcase(yyruleno==150); + /* (151) edge ::= START */ yytestcase(yyruleno==151); + /* (152) edge ::= END */ yytestcase(yyruleno==152); + /* (153) edge ::= RIGHT */ yytestcase(yyruleno==153); + /* (154) edge ::= LEFT */ yytestcase(yyruleno==154); + /* (155) object ::= objectname */ yytestcase(yyruleno==155); + break; +/********** End reduce actions ************************************************/ + }; + assert( yyrulenoYY_MAX_SHIFT && yyact<=YY_MAX_SHIFTREDUCE) ); + + /* It is not possible for a REDUCE to be followed by an error */ + assert( yyact!=YY_ERROR_ACTION ); + + yymsp += yysize+1; + yypParser->yytos = yymsp; + yymsp->stateno = (YYACTIONTYPE)yyact; + yymsp->major = (YYCODETYPE)yygoto; + yyTraceShift(yypParser, yyact, "... then shift"); + return yyact; +} + +/* +** The following code executes when the parse fails +*/ +#ifndef YYNOERRORRECOVERY +static void yy_parse_failed( + yyParser *yypParser /* The parser */ +){ + pik_parserARG_FETCH + pik_parserCTX_FETCH +#ifndef NDEBUG + if( yyTraceFILE ){ + fprintf(yyTraceFILE,"%sFail!\n",yyTracePrompt); + } +#endif + while( yypParser->yytos>yypParser->yystack ) yy_pop_parser_stack(yypParser); + /* Here code is inserted which will be executed whenever the + ** parser fails */ +/************ Begin %parse_failure code ***************************************/ +/************ End %parse_failure code *****************************************/ + pik_parserARG_STORE /* Suppress warning about unused %extra_argument variable */ + pik_parserCTX_STORE +} +#endif /* YYNOERRORRECOVERY */ + +/* +** The following code executes when a syntax error first occurs. +*/ +static void yy_syntax_error( + yyParser *yypParser, /* The parser */ + int yymajor, /* The major type of the error token */ + pik_parserTOKENTYPE yyminor /* The minor type of the error token */ +){ + pik_parserARG_FETCH + pik_parserCTX_FETCH +#define TOKEN yyminor +/************ Begin %syntax_error code ****************************************/ +#line 520 "pikchr.y" + + if( TOKEN.z && TOKEN.z[0] ){ + pik_error(p, &TOKEN, "syntax error"); + }else{ + pik_error(p, 0, "syntax error"); + } + UNUSED_PARAMETER(yymajor); +#line 3164 "pikchr.c" +/************ End %syntax_error code ******************************************/ + pik_parserARG_STORE /* Suppress warning about unused %extra_argument variable */ + pik_parserCTX_STORE +} + +/* +** The following is executed when the parser accepts +*/ +static void yy_accept( + yyParser *yypParser /* The parser */ +){ + pik_parserARG_FETCH + pik_parserCTX_FETCH +#ifndef NDEBUG + if( yyTraceFILE ){ + fprintf(yyTraceFILE,"%sAccept!\n",yyTracePrompt); + } +#endif +#ifndef YYNOERRORRECOVERY + yypParser->yyerrcnt = -1; +#endif + assert( yypParser->yytos==yypParser->yystack ); + /* Here code is inserted which will be executed whenever the + ** parser accepts */ +/*********** Begin %parse_accept code *****************************************/ +/*********** End %parse_accept code *******************************************/ + pik_parserARG_STORE /* Suppress warning about unused %extra_argument variable */ + pik_parserCTX_STORE +} + +/* The main parser program. +** The first argument is a pointer to a structure obtained from +** "pik_parserAlloc" which describes the current state of the parser. +** The second argument is the major token number. The third is +** the minor token. The fourth optional argument is whatever the +** user wants (and specified in the grammar) and is available for +** use by the action routines. +** +** Inputs: +**
        +**
      • A pointer to the parser (an opaque structure.) +**
      • The major token number. +**
      • The minor token number. +**
      • An option argument of a grammar-specified type. +**
      +** +** Outputs: +** None. +*/ +void pik_parser( + void *yyp, /* The parser */ + int yymajor, /* The major token code number */ + pik_parserTOKENTYPE yyminor /* The value for the token */ + pik_parserARG_PDECL /* Optional %extra_argument parameter */ +){ + YYMINORTYPE yyminorunion; + YYACTIONTYPE yyact; /* The parser action. */ +#if !defined(YYERRORSYMBOL) && !defined(YYNOERRORRECOVERY) + int yyendofinput; /* True if we are at the end of input */ +#endif +#ifdef YYERRORSYMBOL + int yyerrorhit = 0; /* True if yymajor has invoked an error */ +#endif + yyParser *yypParser = (yyParser*)yyp; /* The parser */ + pik_parserCTX_FETCH + pik_parserARG_STORE + + assert( yypParser->yytos!=0 ); +#if !defined(YYERRORSYMBOL) && !defined(YYNOERRORRECOVERY) + yyendofinput = (yymajor==0); +#endif + + yyact = yypParser->yytos->stateno; +#ifndef NDEBUG + if( yyTraceFILE ){ + if( yyact < YY_MIN_REDUCE ){ + fprintf(yyTraceFILE,"%sInput '%s' in state %d\n", + yyTracePrompt,yyTokenName[yymajor],yyact); + }else{ + fprintf(yyTraceFILE,"%sInput '%s' with pending reduce %d\n", + yyTracePrompt,yyTokenName[yymajor],yyact-YY_MIN_REDUCE); + } + } +#endif + + do{ + assert( yyact==yypParser->yytos->stateno ); + yyact = yy_find_shift_action((YYCODETYPE)yymajor,yyact); + if( yyact >= YY_MIN_REDUCE ){ + yyact = yy_reduce(yypParser,yyact-YY_MIN_REDUCE,yymajor, + yyminor pik_parserCTX_PARAM); + }else if( yyact <= YY_MAX_SHIFTREDUCE ){ + yy_shift(yypParser,yyact,(YYCODETYPE)yymajor,yyminor); +#ifndef YYNOERRORRECOVERY + yypParser->yyerrcnt--; +#endif + break; + }else if( yyact==YY_ACCEPT_ACTION ){ + yypParser->yytos--; + yy_accept(yypParser); + return; + }else{ + assert( yyact == YY_ERROR_ACTION ); + yyminorunion.yy0 = yyminor; +#ifdef YYERRORSYMBOL + int yymx; +#endif +#ifndef NDEBUG + if( yyTraceFILE ){ + fprintf(yyTraceFILE,"%sSyntax Error!\n",yyTracePrompt); + } +#endif +#ifdef YYERRORSYMBOL + /* A syntax error has occurred. + ** The response to an error depends upon whether or not the + ** grammar defines an error token "ERROR". + ** + ** This is what we do if the grammar does define ERROR: + ** + ** * Call the %syntax_error function. + ** + ** * Begin popping the stack until we enter a state where + ** it is legal to shift the error symbol, then shift + ** the error symbol. + ** + ** * Set the error count to three. + ** + ** * Begin accepting and shifting new tokens. No new error + ** processing will occur until three tokens have been + ** shifted successfully. + ** + */ + if( yypParser->yyerrcnt<0 ){ + yy_syntax_error(yypParser,yymajor,yyminor); + } + yymx = yypParser->yytos->major; + if( yymx==YYERRORSYMBOL || yyerrorhit ){ +#ifndef NDEBUG + if( yyTraceFILE ){ + fprintf(yyTraceFILE,"%sDiscard input token %s\n", + yyTracePrompt,yyTokenName[yymajor]); + } +#endif + yy_destructor(yypParser, (YYCODETYPE)yymajor, &yyminorunion); + yymajor = YYNOCODE; + }else{ + while( yypParser->yytos >= yypParser->yystack + && (yyact = yy_find_reduce_action( + yypParser->yytos->stateno, + YYERRORSYMBOL)) > YY_MAX_SHIFTREDUCE + ){ + yy_pop_parser_stack(yypParser); + } + if( yypParser->yytos < yypParser->yystack || yymajor==0 ){ + yy_destructor(yypParser,(YYCODETYPE)yymajor,&yyminorunion); + yy_parse_failed(yypParser); +#ifndef YYNOERRORRECOVERY + yypParser->yyerrcnt = -1; +#endif + yymajor = YYNOCODE; + }else if( yymx!=YYERRORSYMBOL ){ + yy_shift(yypParser,yyact,YYERRORSYMBOL,yyminor); + } + } + yypParser->yyerrcnt = 3; + yyerrorhit = 1; + if( yymajor==YYNOCODE ) break; + yyact = yypParser->yytos->stateno; +#elif defined(YYNOERRORRECOVERY) + /* If the YYNOERRORRECOVERY macro is defined, then do not attempt to + ** do any kind of error recovery. Instead, simply invoke the syntax + ** error routine and continue going as if nothing had happened. + ** + ** Applications can set this macro (for example inside %include) if + ** they intend to abandon the parse upon the first syntax error seen. + */ + yy_syntax_error(yypParser,yymajor, yyminor); + yy_destructor(yypParser,(YYCODETYPE)yymajor,&yyminorunion); + break; +#else /* YYERRORSYMBOL is not defined */ + /* This is what we do if the grammar does not define ERROR: + ** + ** * Report an error message, and throw away the input token. + ** + ** * If the input token is $, then fail the parse. + ** + ** As before, subsequent error messages are suppressed until + ** three input tokens have been successfully shifted. + */ + if( yypParser->yyerrcnt<=0 ){ + yy_syntax_error(yypParser,yymajor, yyminor); + } + yypParser->yyerrcnt = 3; + yy_destructor(yypParser,(YYCODETYPE)yymajor,&yyminorunion); + if( yyendofinput ){ + yy_parse_failed(yypParser); +#ifndef YYNOERRORRECOVERY + yypParser->yyerrcnt = -1; +#endif + } + break; +#endif + } + }while( yypParser->yytos>yypParser->yystack ); +#ifndef NDEBUG + if( yyTraceFILE ){ + yyStackEntry *i; + char cDiv = '['; + fprintf(yyTraceFILE,"%sReturn. Stack=",yyTracePrompt); + for(i=&yypParser->yystack[1]; i<=yypParser->yytos; i++){ + fprintf(yyTraceFILE,"%c%s", cDiv, yyTokenName[i->major]); + cDiv = ' '; + } + fprintf(yyTraceFILE,"]\n"); + } +#endif + return; +} + +/* +** Return the fallback token corresponding to canonical token iToken, or +** 0 if iToken has no fallback. +*/ +int pik_parserFallback(int iToken){ +#ifdef YYFALLBACK + assert( iToken<(int)(sizeof(yyFallback)/sizeof(yyFallback[0])) ); + return yyFallback[iToken]; +#else + (void)iToken; + return 0; +#endif +} +#line 765 "pikchr.y" + + + +/* Chart of the 148 official CSS color names with their +** corresponding RGB values thru Color Module Level 4: +** https://developer.mozilla.org/en-US/docs/Web/CSS/color_value +** +** Two new names "None" and "Off" are added with a value +** of -1. +*/ +static const struct { + const char *zName; /* Name of the color */ + int val; /* RGB value */ +} aColor[] = { + { "AliceBlue", 0xf0f8ff }, + { "AntiqueWhite", 0xfaebd7 }, + { "Aqua", 0x00ffff }, + { "Aquamarine", 0x7fffd4 }, + { "Azure", 0xf0ffff }, + { "Beige", 0xf5f5dc }, + { "Bisque", 0xffe4c4 }, + { "Black", 0x000000 }, + { "BlanchedAlmond", 0xffebcd }, + { "Blue", 0x0000ff }, + { "BlueViolet", 0x8a2be2 }, + { "Brown", 0xa52a2a }, + { "BurlyWood", 0xdeb887 }, + { "CadetBlue", 0x5f9ea0 }, + { "Chartreuse", 0x7fff00 }, + { "Chocolate", 0xd2691e }, + { "Coral", 0xff7f50 }, + { "CornflowerBlue", 0x6495ed }, + { "Cornsilk", 0xfff8dc }, + { "Crimson", 0xdc143c }, + { "Cyan", 0x00ffff }, + { "DarkBlue", 0x00008b }, + { "DarkCyan", 0x008b8b }, + { "DarkGoldenrod", 0xb8860b }, + { "DarkGray", 0xa9a9a9 }, + { "DarkGreen", 0x006400 }, + { "DarkGrey", 0xa9a9a9 }, + { "DarkKhaki", 0xbdb76b }, + { "DarkMagenta", 0x8b008b }, + { "DarkOliveGreen", 0x556b2f }, + { "DarkOrange", 0xff8c00 }, + { "DarkOrchid", 0x9932cc }, + { "DarkRed", 0x8b0000 }, + { "DarkSalmon", 0xe9967a }, + { "DarkSeaGreen", 0x8fbc8f }, + { "DarkSlateBlue", 0x483d8b }, + { "DarkSlateGray", 0x2f4f4f }, + { "DarkSlateGrey", 0x2f4f4f }, + { "DarkTurquoise", 0x00ced1 }, + { "DarkViolet", 0x9400d3 }, + { "DeepPink", 0xff1493 }, + { "DeepSkyBlue", 0x00bfff }, + { "DimGray", 0x696969 }, + { "DimGrey", 0x696969 }, + { "DodgerBlue", 0x1e90ff }, + { "Firebrick", 0xb22222 }, + { "FloralWhite", 0xfffaf0 }, + { "ForestGreen", 0x228b22 }, + { "Fuchsia", 0xff00ff }, + { "Gainsboro", 0xdcdcdc }, + { "GhostWhite", 0xf8f8ff }, + { "Gold", 0xffd700 }, + { "Goldenrod", 0xdaa520 }, + { "Gray", 0x808080 }, + { "Green", 0x008000 }, + { "GreenYellow", 0xadff2f }, + { "Grey", 0x808080 }, + { "Honeydew", 0xf0fff0 }, + { "HotPink", 0xff69b4 }, + { "IndianRed", 0xcd5c5c }, + { "Indigo", 0x4b0082 }, + { "Ivory", 0xfffff0 }, + { "Khaki", 0xf0e68c }, + { "Lavender", 0xe6e6fa }, + { "LavenderBlush", 0xfff0f5 }, + { "LawnGreen", 0x7cfc00 }, + { "LemonChiffon", 0xfffacd }, + { "LightBlue", 0xadd8e6 }, + { "LightCoral", 0xf08080 }, + { "LightCyan", 0xe0ffff }, + { "LightGoldenrodYellow", 0xfafad2 }, + { "LightGray", 0xd3d3d3 }, + { "LightGreen", 0x90ee90 }, + { "LightGrey", 0xd3d3d3 }, + { "LightPink", 0xffb6c1 }, + { "LightSalmon", 0xffa07a }, + { "LightSeaGreen", 0x20b2aa }, + { "LightSkyBlue", 0x87cefa }, + { "LightSlateGray", 0x778899 }, + { "LightSlateGrey", 0x778899 }, + { "LightSteelBlue", 0xb0c4de }, + { "LightYellow", 0xffffe0 }, + { "Lime", 0x00ff00 }, + { "LimeGreen", 0x32cd32 }, + { "Linen", 0xfaf0e6 }, + { "Magenta", 0xff00ff }, + { "Maroon", 0x800000 }, + { "MediumAquamarine", 0x66cdaa }, + { "MediumBlue", 0x0000cd }, + { "MediumOrchid", 0xba55d3 }, + { "MediumPurple", 0x9370db }, + { "MediumSeaGreen", 0x3cb371 }, + { "MediumSlateBlue", 0x7b68ee }, + { "MediumSpringGreen", 0x00fa9a }, + { "MediumTurquoise", 0x48d1cc }, + { "MediumVioletRed", 0xc71585 }, + { "MidnightBlue", 0x191970 }, + { "MintCream", 0xf5fffa }, + { "MistyRose", 0xffe4e1 }, + { "Moccasin", 0xffe4b5 }, + { "NavajoWhite", 0xffdead }, + { "Navy", 0x000080 }, + { "None", -1 }, /* Non-standard addition */ + { "Off", -1 }, /* Non-standard addition */ + { "OldLace", 0xfdf5e6 }, + { "Olive", 0x808000 }, + { "OliveDrab", 0x6b8e23 }, + { "Orange", 0xffa500 }, + { "OrangeRed", 0xff4500 }, + { "Orchid", 0xda70d6 }, + { "PaleGoldenrod", 0xeee8aa }, + { "PaleGreen", 0x98fb98 }, + { "PaleTurquoise", 0xafeeee }, + { "PaleVioletRed", 0xdb7093 }, + { "PapayaWhip", 0xffefd5 }, + { "PeachPuff", 0xffdab9 }, + { "Peru", 0xcd853f }, + { "Pink", 0xffc0cb }, + { "Plum", 0xdda0dd }, + { "PowderBlue", 0xb0e0e6 }, + { "Purple", 0x800080 }, + { "RebeccaPurple", 0x663399 }, + { "Red", 0xff0000 }, + { "RosyBrown", 0xbc8f8f }, + { "RoyalBlue", 0x4169e1 }, + { "SaddleBrown", 0x8b4513 }, + { "Salmon", 0xfa8072 }, + { "SandyBrown", 0xf4a460 }, + { "SeaGreen", 0x2e8b57 }, + { "Seashell", 0xfff5ee }, + { "Sienna", 0xa0522d }, + { "Silver", 0xc0c0c0 }, + { "SkyBlue", 0x87ceeb }, + { "SlateBlue", 0x6a5acd }, + { "SlateGray", 0x708090 }, + { "SlateGrey", 0x708090 }, + { "Snow", 0xfffafa }, + { "SpringGreen", 0x00ff7f }, + { "SteelBlue", 0x4682b4 }, + { "Tan", 0xd2b48c }, + { "Teal", 0x008080 }, + { "Thistle", 0xd8bfd8 }, + { "Tomato", 0xff6347 }, + { "Turquoise", 0x40e0d0 }, + { "Violet", 0xee82ee }, + { "Wheat", 0xf5deb3 }, + { "White", 0xffffff }, + { "WhiteSmoke", 0xf5f5f5 }, + { "Yellow", 0xffff00 }, + { "YellowGreen", 0x9acd32 }, +}; + +/* Built-in variable names. +** +** This array is constant. When a script changes the value of one of +** these built-ins, a new PVar record is added at the head of +** the Pik.pVar list, which is searched first. Thus the new PVar entry +** will override this default value. +** +** Units are in inches, except for "color" and "fill" which are +** interpreted as 24-bit RGB values. +** +** Binary search used. Must be kept in sorted order. +*/ +static const struct { const char *zName; PNum val; } aBuiltin[] = { + { "arcrad", 0.25 }, + { "arrowhead", 2.0 }, + { "arrowht", 0.08 }, + { "arrowwid", 0.06 }, + { "boxht", 0.5 }, + { "boxrad", 0.0 }, + { "boxwid", 0.75 }, + { "charht", 0.14 }, + { "charwid", 0.08 }, + { "circlerad", 0.25 }, + { "color", 0.0 }, + { "cylht", 0.5 }, + { "cylrad", 0.075 }, + { "cylwid", 0.75 }, + { "dashwid", 0.05 }, + { "dotrad", 0.015 }, + { "ellipseht", 0.5 }, + { "ellipsewid", 0.75 }, + { "fileht", 0.75 }, + { "filerad", 0.15 }, + { "filewid", 0.5 }, + { "fill", -1.0 }, + { "lineht", 0.5 }, + { "linewid", 0.5 }, + { "movewid", 0.5 }, + { "ovalht", 0.5 }, + { "ovalwid", 1.0 }, + { "scale", 1.0 }, + { "textht", 0.5 }, + { "textwid", 0.75 }, + { "thickness", 0.015 }, +}; + + +/* Methods for the "arc" class */ +static void arcInit(Pik *p, PObj *pObj){ + pObj->w = pik_value(p, "arcrad",6,0); + pObj->h = pObj->w; +} +/* Hack: Arcs are here rendered as quadratic Bezier curves rather +** than true arcs. Multiple reasons: (1) the legacy-PIC parameters +** that control arcs are obscure and I could not figure out what they +** mean based on available documentation. (2) Arcs are rarely used, +** and so do not seem that important. +*/ +static PPoint arcControlPoint(int cw, PPoint f, PPoint t, PNum rScale){ + PPoint m; + PNum dx, dy; + m.x = 0.5*(f.x+t.x); + m.y = 0.5*(f.y+t.y); + dx = t.x - f.x; + dy = t.y - f.y; + if( cw ){ + m.x -= 0.5*rScale*dy; + m.y += 0.5*rScale*dx; + }else{ + m.x += 0.5*rScale*dy; + m.y -= 0.5*rScale*dx; + } + return m; +} +static void arcCheck(Pik *p, PObj *pObj){ + PPoint m; + if( p->nTPath>2 ){ + pik_error(p, &pObj->errTok, "arc geometry error"); + return; + } + m = arcControlPoint(pObj->cw, p->aTPath[0], p->aTPath[1], 0.5); + pik_bbox_add_xy(&pObj->bbox, m.x, m.y); +} +static void arcRender(Pik *p, PObj *pObj){ + PPoint f, m, t; + if( pObj->nPath<2 ) return; + if( pObj->sw<=0.0 ) return; + f = pObj->aPath[0]; + t = pObj->aPath[1]; + m = arcControlPoint(pObj->cw,f,t,1.0); + if( pObj->larrow ){ + pik_draw_arrowhead(p,&m,&f,pObj); + } + if( pObj->rarrow ){ + pik_draw_arrowhead(p,&m,&t,pObj); + } + pik_append_xy(p,"\n", -1); + + pik_append_txt(p, pObj, 0); +} + + +/* Methods for the "arrow" class */ +static void arrowInit(Pik *p, PObj *pObj){ + pObj->w = pik_value(p, "linewid",7,0); + pObj->h = pik_value(p, "lineht",6,0); + pObj->rad = pik_value(p, "linerad",7,0); + pObj->rarrow = 1; +} + +/* Methods for the "box" class */ +static void boxInit(Pik *p, PObj *pObj){ + pObj->w = pik_value(p, "boxwid",6,0); + pObj->h = pik_value(p, "boxht",5,0); + pObj->rad = pik_value(p, "boxrad",6,0); +} +/* Return offset from the center of the box to the compass point +** given by parameter cp */ +static PPoint boxOffset(Pik *p, PObj *pObj, int cp){ + PPoint pt = cZeroPoint; + PNum w2 = 0.5*pObj->w; + PNum h2 = 0.5*pObj->h; + PNum rad = pObj->rad; + PNum rx; + if( rad<=0.0 ){ + rx = 0.0; + }else{ + if( rad>w2 ) rad = w2; + if( rad>h2 ) rad = h2; + rx = 0.29289321881345252392*rad; + } + switch( cp ){ + case CP_C: break; + case CP_N: pt.x = 0.0; pt.y = h2; break; + case CP_NE: pt.x = w2-rx; pt.y = h2-rx; break; + case CP_E: pt.x = w2; pt.y = 0.0; break; + case CP_SE: pt.x = w2-rx; pt.y = rx-h2; break; + case CP_S: pt.x = 0.0; pt.y = -h2; break; + case CP_SW: pt.x = rx-w2; pt.y = rx-h2; break; + case CP_W: pt.x = -w2; pt.y = 0.0; break; + case CP_NW: pt.x = rx-w2; pt.y = h2-rx; break; + default: assert(0); + } + UNUSED_PARAMETER(p); + return pt; +} +static PPoint boxChop(Pik *p, PObj *pObj, PPoint *pPt){ + PNum dx, dy; + int cp = CP_C; + PPoint chop = pObj->ptAt; + if( pObj->w<=0.0 ) return chop; + if( pObj->h<=0.0 ) return chop; + dx = (pPt->x - pObj->ptAt.x)*pObj->h/pObj->w; + dy = (pPt->y - pObj->ptAt.y); + if( dx>0.0 ){ + if( dy>=2.414*dx ){ + cp = CP_N; + }else if( dy>=0.414*dx ){ + cp = CP_NE; + }else if( dy>=-0.414*dx ){ + cp = CP_E; + }else if( dy>-2.414*dx ){ + cp = CP_SE; + }else{ + cp = CP_S; + } + }else{ + if( dy>=-2.414*dx ){ + cp = CP_N; + }else if( dy>=-0.414*dx ){ + cp = CP_NW; + }else if( dy>=0.414*dx ){ + cp = CP_W; + }else if( dy>2.414*dx ){ + cp = CP_SW; + }else{ + cp = CP_S; + } + } + chop = pObj->type->xOffset(p,pObj,cp); + chop.x += pObj->ptAt.x; + chop.y += pObj->ptAt.y; + return chop; +} +static void boxFit(Pik *p, PObj *pObj, PNum w, PNum h){ + if( w>0 ) pObj->w = w; + if( h>0 ) pObj->h = h; + UNUSED_PARAMETER(p); +} +static void boxRender(Pik *p, PObj *pObj){ + PNum w2 = 0.5*pObj->w; + PNum h2 = 0.5*pObj->h; + PNum rad = pObj->rad; + PPoint pt = pObj->ptAt; + if( pObj->sw>0.0 ){ + if( rad<=0.0 ){ + pik_append_xy(p,"w2 ) rad = w2; + if( rad>h2 ) rad = h2; + x0 = pt.x - w2; + x1 = x0 + rad; + x3 = pt.x + w2; + x2 = x3 - rad; + y0 = pt.y - h2; + y1 = y0 + rad; + y3 = pt.y + h2; + y2 = y3 - rad; + pik_append_xy(p,"x1 ) pik_append_xy(p, "L", x2, y0); + pik_append_arc(p, rad, rad, x3, y1); + if( y2>y1 ) pik_append_xy(p, "L", x3, y2); + pik_append_arc(p, rad, rad, x2, y3); + if( x2>x1 ) pik_append_xy(p, "L", x1, y3); + pik_append_arc(p, rad, rad, x0, y2); + if( y2>y1 ) pik_append_xy(p, "L", x0, y1); + pik_append_arc(p, rad, rad, x1, y0); + pik_append(p,"Z\" ",-1); + } + pik_append_style(p,pObj,3); + pik_append(p,"\" />\n", -1); + } + pik_append_txt(p, pObj, 0); +} + +/* Methods for the "circle" class */ +static void circleInit(Pik *p, PObj *pObj){ + pObj->w = pik_value(p, "circlerad",9,0)*2; + pObj->h = pObj->w; + pObj->rad = 0.5*pObj->w; +} +static void circleNumProp(Pik *p, PObj *pObj, PToken *pId){ + /* For a circle, the width must equal the height and both must + ** be twice the radius. Enforce those constraints. */ + switch( pId->eType ){ + case T_RADIUS: + pObj->w = pObj->h = 2.0*pObj->rad; + break; + case T_WIDTH: + pObj->h = pObj->w; + pObj->rad = 0.5*pObj->w; + break; + case T_HEIGHT: + pObj->w = pObj->h; + pObj->rad = 0.5*pObj->w; + break; + } + UNUSED_PARAMETER(p); +} +static PPoint circleChop(Pik *p, PObj *pObj, PPoint *pPt){ + PPoint chop; + PNum dx = pPt->x - pObj->ptAt.x; + PNum dy = pPt->y - pObj->ptAt.y; + PNum dist = hypot(dx,dy); + if( distrad ) return pObj->ptAt; + chop.x = pObj->ptAt.x + dx*pObj->rad/dist; + chop.y = pObj->ptAt.y + dy*pObj->rad/dist; + UNUSED_PARAMETER(p); + return chop; +} +static void circleFit(Pik *p, PObj *pObj, PNum w, PNum h){ + PNum mx = 0.0; + if( w>0 ) mx = w; + if( h>mx ) mx = h; + if( w*h>0 && (w*w + h*h) > mx*mx ){ + mx = hypot(w,h); + } + if( mx>0.0 ){ + pObj->rad = 0.5*mx; + pObj->w = pObj->h = mx; + } + UNUSED_PARAMETER(p); +} + +static void circleRender(Pik *p, PObj *pObj){ + PNum r = pObj->rad; + PPoint pt = pObj->ptAt; + if( pObj->sw>0.0 ){ + pik_append_x(p,"\n", -1); + } + pik_append_txt(p, pObj, 0); +} + +/* Methods for the "cylinder" class */ +static void cylinderInit(Pik *p, PObj *pObj){ + pObj->w = pik_value(p, "cylwid",6,0); + pObj->h = pik_value(p, "cylht",5,0); + pObj->rad = pik_value(p, "cylrad",6,0); /* Minor radius of ellipses */ +} +static void cylinderFit(Pik *p, PObj *pObj, PNum w, PNum h){ + if( w>0 ) pObj->w = w; + if( h>0 ) pObj->h = h + 0.25*pObj->rad + pObj->sw; + UNUSED_PARAMETER(p); +} +static void cylinderRender(Pik *p, PObj *pObj){ + PNum w2 = 0.5*pObj->w; + PNum h2 = 0.5*pObj->h; + PNum rad = pObj->rad; + PPoint pt = pObj->ptAt; + if( pObj->sw>0.0 ){ + if( rad>h2 ){ + rad = h2; + }else if( rad<0 ){ + rad = 0; + } + pik_append_xy(p,"\n", -1); + } + pik_append_txt(p, pObj, 0); +} +static PPoint cylinderOffset(Pik *p, PObj *pObj, int cp){ + PPoint pt = cZeroPoint; + PNum w2 = pObj->w*0.5; + PNum h1 = pObj->h*0.5; + PNum h2 = h1 - pObj->rad; + switch( cp ){ + case CP_C: break; + case CP_N: pt.x = 0.0; pt.y = h1; break; + case CP_NE: pt.x = w2; pt.y = h2; break; + case CP_E: pt.x = w2; pt.y = 0.0; break; + case CP_SE: pt.x = w2; pt.y = -h2; break; + case CP_S: pt.x = 0.0; pt.y = -h1; break; + case CP_SW: pt.x = -w2; pt.y = -h2; break; + case CP_W: pt.x = -w2; pt.y = 0.0; break; + case CP_NW: pt.x = -w2; pt.y = h2; break; + default: assert(0); + } + UNUSED_PARAMETER(p); + return pt; +} + +/* Methods for the "dot" class */ +static void dotInit(Pik *p, PObj *pObj){ + pObj->rad = pik_value(p, "dotrad",6,0); + pObj->h = pObj->w = pObj->rad*6; + pObj->fill = pObj->color; +} +static void dotNumProp(Pik *p, PObj *pObj, PToken *pId){ + switch( pId->eType ){ + case T_COLOR: + pObj->fill = pObj->color; + break; + case T_FILL: + pObj->color = pObj->fill; + break; + } + UNUSED_PARAMETER(p); +} +static void dotCheck(Pik *p, PObj *pObj){ + pObj->w = pObj->h = 0; + pik_bbox_addellipse(&pObj->bbox, pObj->ptAt.x, pObj->ptAt.y, + pObj->rad, pObj->rad); + UNUSED_PARAMETER(p); +} +static PPoint dotOffset(Pik *p, PObj *pObj, int cp){ + UNUSED_PARAMETER(p); + UNUSED_PARAMETER(pObj); + UNUSED_PARAMETER(cp); + return cZeroPoint; +} +static void dotRender(Pik *p, PObj *pObj){ + PNum r = pObj->rad; + PPoint pt = pObj->ptAt; + if( pObj->sw>0.0 ){ + pik_append_x(p,"\n", -1); + } + pik_append_txt(p, pObj, 0); +} + + + +/* Methods for the "ellipse" class */ +static void ellipseInit(Pik *p, PObj *pObj){ + pObj->w = pik_value(p, "ellipsewid",10,0); + pObj->h = pik_value(p, "ellipseht",9,0); +} +static PPoint ellipseChop(Pik *p, PObj *pObj, PPoint *pPt){ + PPoint chop; + PNum s, dq, dist; + PNum dx = pPt->x - pObj->ptAt.x; + PNum dy = pPt->y - pObj->ptAt.y; + if( pObj->w<=0.0 ) return pObj->ptAt; + if( pObj->h<=0.0 ) return pObj->ptAt; + s = pObj->h/pObj->w; + dq = dx*s; + dist = hypot(dq,dy); + if( disth ) return pObj->ptAt; + chop.x = pObj->ptAt.x + 0.5*dq*pObj->h/(dist*s); + chop.y = pObj->ptAt.y + 0.5*dy*pObj->h/dist; + UNUSED_PARAMETER(p); + return chop; +} +static PPoint ellipseOffset(Pik *p, PObj *pObj, int cp){ + PPoint pt = cZeroPoint; + PNum w = pObj->w*0.5; + PNum w2 = w*0.70710678118654747608; + PNum h = pObj->h*0.5; + PNum h2 = h*0.70710678118654747608; + switch( cp ){ + case CP_C: break; + case CP_N: pt.x = 0.0; pt.y = h; break; + case CP_NE: pt.x = w2; pt.y = h2; break; + case CP_E: pt.x = w; pt.y = 0.0; break; + case CP_SE: pt.x = w2; pt.y = -h2; break; + case CP_S: pt.x = 0.0; pt.y = -h; break; + case CP_SW: pt.x = -w2; pt.y = -h2; break; + case CP_W: pt.x = -w; pt.y = 0.0; break; + case CP_NW: pt.x = -w2; pt.y = h2; break; + default: assert(0); + } + UNUSED_PARAMETER(p); + return pt; +} +static void ellipseRender(Pik *p, PObj *pObj){ + PNum w = pObj->w; + PNum h = pObj->h; + PPoint pt = pObj->ptAt; + if( pObj->sw>0.0 ){ + pik_append_x(p,"\n", -1); + } + pik_append_txt(p, pObj, 0); +} + +/* Methods for the "file" object */ +static void fileInit(Pik *p, PObj *pObj){ + pObj->w = pik_value(p, "filewid",7,0); + pObj->h = pik_value(p, "fileht",6,0); + pObj->rad = pik_value(p, "filerad",7,0); +} +/* Return offset from the center of the file to the compass point +** given by parameter cp */ +static PPoint fileOffset(Pik *p, PObj *pObj, int cp){ + PPoint pt = cZeroPoint; + PNum w2 = 0.5*pObj->w; + PNum h2 = 0.5*pObj->h; + PNum rx = pObj->rad; + PNum mn = w2

      mn ) rx = mn; + if( rx0 ) pObj->w = w; + if( h>0 ) pObj->h = h + 2*pObj->rad; + UNUSED_PARAMETER(p); +} +static void fileRender(Pik *p, PObj *pObj){ + PNum w2 = 0.5*pObj->w; + PNum h2 = 0.5*pObj->h; + PNum rad = pObj->rad; + PPoint pt = pObj->ptAt; + PNum mn = w2

      mn ) rad = mn; + if( radsw>0.0 ){ + pik_append_xy(p,"\n",-1); + pik_append_xy(p,"\n",-1); + } + pik_append_txt(p, pObj, 0); +} + + +/* Methods for the "line" class */ +static void lineInit(Pik *p, PObj *pObj){ + pObj->w = pik_value(p, "linewid",7,0); + pObj->h = pik_value(p, "lineht",6,0); + pObj->rad = pik_value(p, "linerad",7,0); +} +static PPoint lineOffset(Pik *p, PObj *pObj, int cp){ +#if 0 + /* In legacy PIC, the .center of an unclosed line is half way between + ** its .start and .end. */ + if( cp==CP_C && !pObj->bClose ){ + PPoint out; + out.x = 0.5*(pObj->ptEnter.x + pObj->ptExit.x) - pObj->ptAt.x; + out.y = 0.5*(pObj->ptEnter.x + pObj->ptExit.y) - pObj->ptAt.y; + return out; + } +#endif + return boxOffset(p,pObj,cp); +} +static void lineRender(Pik *p, PObj *pObj){ + int i; + if( pObj->sw>0.0 ){ + const char *z = "nPath; + if( pObj->larrow ){ + pik_draw_arrowhead(p,&pObj->aPath[1],&pObj->aPath[0],pObj); + } + if( pObj->rarrow ){ + pik_draw_arrowhead(p,&pObj->aPath[n-2],&pObj->aPath[n-1],pObj); + } + for(i=0; inPath; i++){ + pik_append_xy(p,z,pObj->aPath[i].x,pObj->aPath[i].y); + z = "L"; + } + if( pObj->bClose ){ + pik_append(p,"Z",1); + }else{ + pObj->fill = -1.0; + } + pik_append(p,"\" ",-1); + pik_append_style(p,pObj,pObj->bClose?3:0); + pik_append(p,"\" />\n", -1); + } + pik_append_txt(p, pObj, 0); +} + +/* Methods for the "move" class */ +static void moveInit(Pik *p, PObj *pObj){ + pObj->w = pik_value(p, "movewid",7,0); + pObj->h = pObj->w; + pObj->fill = -1.0; + pObj->color = -1.0; + pObj->sw = -1.0; +} +static void moveRender(Pik *p, PObj *pObj){ + /* No-op */ + UNUSED_PARAMETER(p); + UNUSED_PARAMETER(pObj); +} + +/* Methods for the "oval" class */ +static void ovalInit(Pik *p, PObj *pObj){ + pObj->h = pik_value(p, "ovalht",6,0); + pObj->w = pik_value(p, "ovalwid",7,0); + pObj->rad = 0.5*(pObj->hw?pObj->h:pObj->w); +} +static void ovalNumProp(Pik *p, PObj *pObj, PToken *pId){ + UNUSED_PARAMETER(p); + UNUSED_PARAMETER(pId); + /* Always adjust the radius to be half of the smaller of + ** the width and height. */ + pObj->rad = 0.5*(pObj->hw?pObj->h:pObj->w); +} +static void ovalFit(Pik *p, PObj *pObj, PNum w, PNum h){ + UNUSED_PARAMETER(p); + if( w>0 ) pObj->w = w; + if( h>0 ) pObj->h = h; + if( pObj->wh ) pObj->w = pObj->h; + pObj->rad = 0.5*(pObj->hw?pObj->h:pObj->w); +} + + + +/* Methods for the "spline" class */ +static void splineInit(Pik *p, PObj *pObj){ + pObj->w = pik_value(p, "linewid",7,0); + pObj->h = pik_value(p, "lineht",6,0); + pObj->rad = 1000; +} +/* Return a point along the path from "f" to "t" that is r units +** prior to reaching "t", except if the path is less than 2*r total, +** return the midpoint. +*/ +static PPoint radiusMidpoint(PPoint f, PPoint t, PNum r, int *pbMid){ + PNum dx = t.x - f.x; + PNum dy = t.y - f.y; + PNum dist = hypot(dx,dy); + PPoint m; + if( dist<=0.0 ) return t; + dx /= dist; + dy /= dist; + if( r > 0.5*dist ){ + r = 0.5*dist; + *pbMid = 1; + }else{ + *pbMid = 0; + } + m.x = t.x - r*dx; + m.y = t.y - r*dy; + return m; +} +static void radiusPath(Pik *p, PObj *pObj, PNum r){ + int i; + int n = pObj->nPath; + const PPoint *a = pObj->aPath; + PPoint m; + PPoint an = a[n-1]; + int isMid = 0; + int iLast = pObj->bClose ? n : n-1; + + pik_append_xy(p,"bClose ){ + pik_append(p,"Z",1); + }else{ + pObj->fill = -1.0; + } + pik_append(p,"\" ",-1); + pik_append_style(p,pObj,pObj->bClose?3:0); + pik_append(p,"\" />\n", -1); +} +static void splineRender(Pik *p, PObj *pObj){ + if( pObj->sw>0.0 ){ + int n = pObj->nPath; + PNum r = pObj->rad; + if( n<3 || r<=0.0 ){ + lineRender(p,pObj); + return; + } + if( pObj->larrow ){ + pik_draw_arrowhead(p,&pObj->aPath[1],&pObj->aPath[0],pObj); + } + if( pObj->rarrow ){ + pik_draw_arrowhead(p,&pObj->aPath[n-2],&pObj->aPath[n-1],pObj); + } + radiusPath(p,pObj,pObj->rad); + } + pik_append_txt(p, pObj, 0); +} + + +/* Methods for the "text" class */ +static void textInit(Pik *p, PObj *pObj){ + pik_value(p, "textwid",7,0); + pik_value(p, "textht",6,0); + pObj->sw = 0.0; +} +static PPoint textOffset(Pik *p, PObj *pObj, int cp){ + /* Automatically slim-down the width and height of text + ** statements so that the bounding box tightly encloses the text, + ** then get boxOffset() to do the offset computation. + */ + pik_size_to_fit(p, &pObj->errTok,3); + return boxOffset(p, pObj, cp); +} + +/* Methods for the "sublist" class */ +static void sublistInit(Pik *p, PObj *pObj){ + PList *pList = pObj->pSublist; + int i; + UNUSED_PARAMETER(p); + pik_bbox_init(&pObj->bbox); + for(i=0; in; i++){ + pik_bbox_addbox(&pObj->bbox, &pList->a[i]->bbox); + } + pObj->w = pObj->bbox.ne.x - pObj->bbox.sw.x; + pObj->h = pObj->bbox.ne.y - pObj->bbox.sw.y; + pObj->ptAt.x = 0.5*(pObj->bbox.ne.x + pObj->bbox.sw.x); + pObj->ptAt.y = 0.5*(pObj->bbox.ne.y + pObj->bbox.sw.y); + pObj->mCalc |= A_WIDTH|A_HEIGHT|A_RADIUS; +} + + +/* +** The following array holds all the different kinds of objects. +** The special [] object is separate. +*/ +static const PClass aClass[] = { + { /* name */ "arc", + /* isline */ 1, + /* eJust */ 0, + /* xInit */ arcInit, + /* xNumProp */ 0, + /* xCheck */ arcCheck, + /* xChop */ 0, + /* xOffset */ boxOffset, + /* xFit */ 0, + /* xRender */ arcRender + }, + { /* name */ "arrow", + /* isline */ 1, + /* eJust */ 0, + /* xInit */ arrowInit, + /* xNumProp */ 0, + /* xCheck */ 0, + /* xChop */ 0, + /* xOffset */ lineOffset, + /* xFit */ 0, + /* xRender */ splineRender + }, + { /* name */ "box", + /* isline */ 0, + /* eJust */ 1, + /* xInit */ boxInit, + /* xNumProp */ 0, + /* xCheck */ 0, + /* xChop */ boxChop, + /* xOffset */ boxOffset, + /* xFit */ boxFit, + /* xRender */ boxRender + }, + { /* name */ "circle", + /* isline */ 0, + /* eJust */ 0, + /* xInit */ circleInit, + /* xNumProp */ circleNumProp, + /* xCheck */ 0, + /* xChop */ circleChop, + /* xOffset */ ellipseOffset, + /* xFit */ circleFit, + /* xRender */ circleRender + }, + { /* name */ "cylinder", + /* isline */ 0, + /* eJust */ 1, + /* xInit */ cylinderInit, + /* xNumProp */ 0, + /* xCheck */ 0, + /* xChop */ boxChop, + /* xOffset */ cylinderOffset, + /* xFit */ cylinderFit, + /* xRender */ cylinderRender + }, + { /* name */ "dot", + /* isline */ 0, + /* eJust */ 0, + /* xInit */ dotInit, + /* xNumProp */ dotNumProp, + /* xCheck */ dotCheck, + /* xChop */ circleChop, + /* xOffset */ dotOffset, + /* xFit */ 0, + /* xRender */ dotRender + }, + { /* name */ "ellipse", + /* isline */ 0, + /* eJust */ 0, + /* xInit */ ellipseInit, + /* xNumProp */ 0, + /* xCheck */ 0, + /* xChop */ ellipseChop, + /* xOffset */ ellipseOffset, + /* xFit */ boxFit, + /* xRender */ ellipseRender + }, + { /* name */ "file", + /* isline */ 0, + /* eJust */ 1, + /* xInit */ fileInit, + /* xNumProp */ 0, + /* xCheck */ 0, + /* xChop */ boxChop, + /* xOffset */ fileOffset, + /* xFit */ fileFit, + /* xRender */ fileRender + }, + { /* name */ "line", + /* isline */ 1, + /* eJust */ 0, + /* xInit */ lineInit, + /* xNumProp */ 0, + /* xCheck */ 0, + /* xChop */ 0, + /* xOffset */ lineOffset, + /* xFit */ 0, + /* xRender */ splineRender + }, + { /* name */ "move", + /* isline */ 1, + /* eJust */ 0, + /* xInit */ moveInit, + /* xNumProp */ 0, + /* xCheck */ 0, + /* xChop */ 0, + /* xOffset */ boxOffset, + /* xFit */ 0, + /* xRender */ moveRender + }, + { /* name */ "oval", + /* isline */ 0, + /* eJust */ 1, + /* xInit */ ovalInit, + /* xNumProp */ ovalNumProp, + /* xCheck */ 0, + /* xChop */ boxChop, + /* xOffset */ boxOffset, + /* xFit */ ovalFit, + /* xRender */ boxRender + }, + { /* name */ "spline", + /* isline */ 1, + /* eJust */ 0, + /* xInit */ splineInit, + /* xNumProp */ 0, + /* xCheck */ 0, + /* xChop */ 0, + /* xOffset */ lineOffset, + /* xFit */ 0, + /* xRender */ splineRender + }, + { /* name */ "text", + /* isline */ 0, + /* eJust */ 0, + /* xInit */ textInit, + /* xNumProp */ 0, + /* xCheck */ 0, + /* xChop */ boxChop, + /* xOffset */ textOffset, + /* xFit */ boxFit, + /* xRender */ boxRender + }, +}; +static const PClass sublistClass = + { /* name */ "[]", + /* isline */ 0, + /* eJust */ 0, + /* xInit */ sublistInit, + /* xNumProp */ 0, + /* xCheck */ 0, + /* xChop */ 0, + /* xOffset */ boxOffset, + /* xFit */ 0, + /* xRender */ 0 + }; +static const PClass noopClass = + { /* name */ "noop", + /* isline */ 0, + /* eJust */ 0, + /* xInit */ 0, + /* xNumProp */ 0, + /* xCheck */ 0, + /* xChop */ 0, + /* xOffset */ boxOffset, + /* xFit */ 0, + /* xRender */ 0 + }; + + +/* +** Reduce the length of the line segment by amt (if possible) by +** modifying the location of *t. +*/ +static void pik_chop(PPoint *f, PPoint *t, PNum amt){ + PNum dx = t->x - f->x; + PNum dy = t->y - f->y; + PNum dist = hypot(dx,dy); + PNum r; + if( dist<=amt ){ + *t = *f; + return; + } + r = 1.0 - amt/dist; + t->x = f->x + r*dx; + t->y = f->y + r*dy; +} + +/* +** Draw an arrowhead on the end of the line segment from pFrom to pTo. +** Also, shorten the line segment (by changing the value of pTo) so that +** the shaft of the arrow does not extend into the arrowhead. +*/ +static void pik_draw_arrowhead(Pik *p, PPoint *f, PPoint *t, PObj *pObj){ + PNum dx = t->x - f->x; + PNum dy = t->y - f->y; + PNum dist = hypot(dx,dy); + PNum h = p->hArrow * pObj->sw; + PNum w = p->wArrow * pObj->sw; + PNum e1, ddx, ddy; + PNum bx, by; + if( pObj->color<0.0 ) return; + if( pObj->sw<=0.0 ) return; + if( dist<=0.0 ) return; /* Unable */ + dx /= dist; + dy /= dist; + e1 = dist - h; + if( e1<0.0 ){ + e1 = 0.0; + h = dist; + } + ddx = -w*dy; + ddy = w*dx; + bx = f->x + e1*dx; + by = f->y + e1*dy; + pik_append_xy(p,"x, t->y); + pik_append_xy(p," ",bx-ddx, by-ddy); + pik_append_xy(p," ",bx+ddx, by+ddy); + pik_append_clr(p,"\" style=\"fill:",pObj->color,"\"/>\n",0); + pik_chop(f,t,h/2); +} + +/* +** Compute the relative offset to an edge location from the reference for a +** an statement. +*/ +static PPoint pik_elem_offset(Pik *p, PObj *pObj, int cp){ + return pObj->type->xOffset(p, pObj, cp); +} + + +/* +** Append raw text to zOut +*/ +static void pik_append(Pik *p, const char *zText, int n){ + if( n<0 ) n = (int)strlen(zText); + if( p->nOut+n>=p->nOutAlloc ){ + int nNew = (p->nOut+n)*2 + 1; + char *z = realloc(p->zOut, nNew); + if( z==0 ){ + pik_error(p, 0, 0); + return; + } + p->zOut = z; + p->nOutAlloc = n; + } + memcpy(p->zOut+p->nOut, zText, n); + p->nOut += n; + p->zOut[p->nOut] = 0; +} + +/* +** Append text to zOut with HTML characters escaped. +** +** * The space character is changed into non-breaking space (U+00a0) +** if mFlags has the 0x01 bit set. This is needed when outputting +** text to preserve leading and trailing whitespace. Turns out we +** cannot use   as that is an HTML-ism and is not valid in XML. +** +** * The "&" character is changed into "&" if mFlags has the +** 0x02 bit set. This is needed when generating error message text. +** +** * Except for the above, only "<" and ">" are escaped. +*/ +static void pik_append_text(Pik *p, const char *zText, int n, int mFlags){ + int i; + char c = 0; + int bQSpace = mFlags & 1; + int bQAmp = mFlags & 2; + if( n<0 ) n = (int)strlen(zText); + while( n>0 ){ + for(i=0; i' ) break; + if( c==' ' && bQSpace ) break; + if( c=='&' && bQAmp ) break; + } + if( i ) pik_append(p, zText, i); + if( i==n ) break; + switch( c ){ + case '<': { pik_append(p, "<", 4); break; } + case '>': { pik_append(p, ">", 4); break; } + case '&': { pik_append(p, "&", 5); break; } + case ' ': { pik_append(p, "\302\240;", 2); break; } + } + i++; + n -= i; + zText += i; + i = 0; + } +} + +/* +** Append error message text. This is either a raw append, or an append +** with HTML escapes, depending on whether the PIKCHR_PLAINTEXT_ERRORS flag +** is set. +*/ +static void pik_append_errtxt(Pik *p, const char *zText, int n){ + if( p->mFlags & PIKCHR_PLAINTEXT_ERRORS ){ + pik_append(p, zText, n); + }else{ + pik_append_text(p, zText, n, 0); + } +} + +/* Append a PNum value +*/ +static void pik_append_num(Pik *p, const char *z,PNum v){ + char buf[100]; + snprintf(buf, sizeof(buf)-1, "%.10g", (double)v); + buf[sizeof(buf)-1] = 0; + pik_append(p, z, -1); + pik_append(p, buf, -1); +} + +/* Append a PPoint value (Used for debugging only) +*/ +static void pik_append_point(Pik *p, const char *z, PPoint *pPt){ + char buf[100]; + snprintf(buf, sizeof(buf)-1, "%.10g,%.10g", + (double)pPt->x, (double)pPt->y); + buf[sizeof(buf)-1] = 0; + pik_append(p, z, -1); + pik_append(p, buf, -1); +} + +/* +** Invert the RGB color so that it is appropriate for dark mode. +** Variable x hold the initial color. The color is intended for use +** as a background color if isBg is true, and as a foreground color +** if isBg is false. +*/ +static int pik_color_to_dark_mode(int x, int isBg){ + int r, g, b; + int mn, mx; + x = 0xffffff - x; + r = (x>>16) & 0xff; + g = (x>>8) & 0xff; + b = x & 0xff; + mx = r; + if( g>mx ) mx = g; + if( b>mx ) mx = b; + mn = r; + if( g127 ){ + r = (127*r)/mx; + g = (127*g)/mx; + b = (127*b)/mx; + } + }else{ + if( mn<128 && mx>mn ){ + r = 127 + ((r-mn)*128)/(mx-mn); + g = 127 + ((g-mn)*128)/(mx-mn); + b = 127 + ((b-mn)*128)/(mx-mn); + } + } + return r*0x10000 + g*0x100 + b; +} + +/* Append a PNum value surrounded by text. Do coordinate transformations +** on the value. +*/ +static void pik_append_x(Pik *p, const char *z1, PNum v, const char *z2){ + char buf[200]; + v -= p->bbox.sw.x; + snprintf(buf, sizeof(buf)-1, "%s%d%s", z1, (int)(p->rScale*v), z2); + buf[sizeof(buf)-1] = 0; + pik_append(p, buf, -1); +} +static void pik_append_y(Pik *p, const char *z1, PNum v, const char *z2){ + char buf[200]; + v = p->bbox.ne.y - v; + snprintf(buf, sizeof(buf)-1, "%s%d%s", z1, (int)(p->rScale*v), z2); + buf[sizeof(buf)-1] = 0; + pik_append(p, buf, -1); +} +static void pik_append_xy(Pik *p, const char *z1, PNum x, PNum y){ + char buf[200]; + x = x - p->bbox.sw.x; + y = p->bbox.ne.y - y; + snprintf(buf, sizeof(buf)-1, "%s%d,%d", z1, + (int)(p->rScale*x), (int)(p->rScale*y)); + buf[sizeof(buf)-1] = 0; + pik_append(p, buf, -1); +} +static void pik_append_dis(Pik *p, const char *z1, PNum v, const char *z2){ + char buf[200]; + snprintf(buf, sizeof(buf)-1, "%s%g%s", z1, p->rScale*v, z2); + buf[sizeof(buf)-1] = 0; + pik_append(p, buf, -1); +} + +/* Append a color specification to the output. +** +** In PIKCHR_DARK_MODE, the color is inverted. The "bg" flags indicates that +** the color is intended for use as a background color if true, or as a +** foreground color if false. The distinction only matters for color +** inversions in PIKCHR_DARK_MODE. +*/ +static void pik_append_clr(Pik *p,const char *z1,PNum v,const char *z2,int bg){ + char buf[200]; + int x = (int)v; + int r, g, b; + if( x==0 && p->fgcolor>0 && !bg ){ + x = p->fgcolor; + }else if( bg && x>=0xffffff && p->bgcolor>0 ){ + x = p->bgcolor; + }else if( p->mFlags & PIKCHR_DARK_MODE ){ + x = pik_color_to_dark_mode(x,bg); + } + r = (x>>16) & 0xff; + g = (x>>8) & 0xff; + b = x & 0xff; + snprintf(buf, sizeof(buf)-1, "%srgb(%d,%d,%d)%s", z1, r, g, b, z2); + buf[sizeof(buf)-1] = 0; + pik_append(p, buf, -1); +} + +/* Append an SVG path A record: +** +** A r1 r2 0 0 0 x y +*/ +static void pik_append_arc(Pik *p, PNum r1, PNum r2, PNum x, PNum y){ + char buf[200]; + x = x - p->bbox.sw.x; + y = p->bbox.ne.y - y; + snprintf(buf, sizeof(buf)-1, "A%d %d 0 0 0 %d %d", + (int)(p->rScale*r1), (int)(p->rScale*r2), + (int)(p->rScale*x), (int)(p->rScale*y)); + buf[sizeof(buf)-1] = 0; + pik_append(p, buf, -1); +} + +/* Append a style="..." text. But, leave the quote unterminated, in case +** the caller wants to add some more. +** +** eFill is non-zero to fill in the background, or 0 if no fill should +** occur. Non-zero values of eFill determine the "bg" flag to pik_append_clr() +** for cases when pObj->fill==pObj->color +** +** 1 fill is background, and color is foreground. +** 2 fill and color are both foreground. (Used by "dot" objects) +** 3 fill and color are both background. (Used by most other objs) +*/ +static void pik_append_style(Pik *p, PObj *pObj, int eFill){ + int clrIsBg = 0; + pik_append(p, " style=\"", -1); + if( pObj->fill>=0 && eFill ){ + int fillIsBg = 1; + if( pObj->fill==pObj->color ){ + if( eFill==2 ) fillIsBg = 0; + if( eFill==3 ) clrIsBg = 1; + } + pik_append_clr(p, "fill:", pObj->fill, ";", fillIsBg); + }else{ + pik_append(p,"fill:none;",-1); + } + if( pObj->sw>0.0 && pObj->color>=0.0 ){ + PNum sw = pObj->sw; + pik_append_dis(p, "stroke-width:", sw, ";"); + if( pObj->nPath>2 && pObj->rad<=pObj->sw ){ + pik_append(p, "stroke-linejoin:round;", -1); + } + pik_append_clr(p, "stroke:",pObj->color,";",clrIsBg); + if( pObj->dotted>0.0 ){ + PNum v = pObj->dotted; + if( sw<2.1/p->rScale ) sw = 2.1/p->rScale; + pik_append_dis(p,"stroke-dasharray:",sw,""); + pik_append_dis(p,",",v,";"); + }else if( pObj->dashed>0.0 ){ + PNum v = pObj->dashed; + pik_append_dis(p,"stroke-dasharray:",v,""); + pik_append_dis(p,",",v,";"); + } + } +} + +/* +** Compute the vertical locations for all text items in the +** object pObj. In other words, set every pObj->aTxt[*].eCode +** value to contain exactly one of: TP_ABOVE2, TP_ABOVE, TP_CENTER, +** TP_BELOW, or TP_BELOW2 is set. +*/ +static void pik_txt_vertical_layout(PObj *pObj){ + int n, i; + PToken *aTxt; + n = pObj->nTxt; + if( n==0 ) return; + aTxt = pObj->aTxt; + if( n==1 ){ + if( (aTxt[0].eCode & TP_VMASK)==0 ){ + aTxt[0].eCode |= TP_CENTER; + } + }else{ + int allSlots = 0; + int aFree[5]; + int iSlot; + int j, mJust; + /* If there is more than one TP_ABOVE, change the first to TP_ABOVE2. */ + for(j=mJust=0, i=n-1; i>=0; i--){ + if( aTxt[i].eCode & TP_ABOVE ){ + if( j==0 ){ + j++; + mJust = aTxt[i].eCode & TP_JMASK; + }else if( j==1 && mJust!=0 && (aTxt[i].eCode & mJust)==0 ){ + j++; + }else{ + aTxt[i].eCode = (aTxt[i].eCode & ~TP_VMASK) | TP_ABOVE2; + break; + } + } + } + /* If there is more than one TP_BELOW, change the last to TP_BELOW2 */ + for(j=mJust=0, i=0; i=4 && (allSlots & TP_ABOVE2)==0 ) aFree[iSlot++] = TP_ABOVE2; + if( (allSlots & TP_ABOVE)==0 ) aFree[iSlot++] = TP_ABOVE; + if( (n&1)!=0 ) aFree[iSlot++] = TP_CENTER; + if( (allSlots & TP_BELOW)==0 ) aFree[iSlot++] = TP_BELOW; + if( n>=4 && (allSlots & TP_BELOW2)==0 ) aFree[iSlot++] = TP_BELOW2; + } + /* Set the VMASK for all unassigned texts */ + for(i=iSlot=0; ieCode & TP_BIG ) scale *= 1.25; + if( t->eCode & TP_SMALL ) scale *= 0.8; + if( t->eCode & TP_XTRA ) scale *= scale; + return scale; +} + +/* Append multiple SVG elements for the text fields of the PObj. +** Parameters: +** +** p The Pik object into which we are rendering +** +** pObj Object containing the text to be rendered +** +** pBox If not NULL, do no rendering at all. Instead +** expand the box object so that it will include all +** of the text. +*/ +static void pik_append_txt(Pik *p, PObj *pObj, PBox *pBox){ + PNum jw; /* Justification margin relative to center */ + PNum ha2 = 0.0; /* Height of the top row of text */ + PNum ha1 = 0.0; /* Height of the second "above" row */ + PNum hc = 0.0; /* Height of the center row */ + PNum hb1 = 0.0; /* Height of the first "below" row of text */ + PNum hb2 = 0.0; /* Height of the second "below" row */ + PNum yBase = 0.0; + int n, i, nz; + PNum x, y, orig_y, s; + const char *z; + PToken *aTxt; + unsigned allMask = 0; + + if( p->nErr ) return; + if( pObj->nTxt==0 ) return; + aTxt = pObj->aTxt; + n = pObj->nTxt; + pik_txt_vertical_layout(pObj); + x = pObj->ptAt.x; + for(i=0; iaTxt[i].eCode; + if( pObj->type->isLine ){ + hc = pObj->sw*1.5; + }else if( pObj->rad>0.0 && pObj->type->xInit==cylinderInit ){ + yBase = -0.75*pObj->rad; + } + if( allMask & TP_CENTER ){ + for(i=0; iaTxt[i].eCode & TP_CENTER ){ + s = pik_font_scale(pObj->aTxt+i); + if( hccharHeight ) hc = s*p->charHeight; + } + } + } + if( allMask & TP_ABOVE ){ + for(i=0; iaTxt[i].eCode & TP_ABOVE ){ + s = pik_font_scale(pObj->aTxt+i)*p->charHeight; + if( ha1aTxt[i].eCode & TP_ABOVE2 ){ + s = pik_font_scale(pObj->aTxt+i)*p->charHeight; + if( ha2aTxt[i].eCode & TP_BELOW ){ + s = pik_font_scale(pObj->aTxt+i)*p->charHeight; + if( hb1aTxt[i].eCode & TP_BELOW2 ){ + s = pik_font_scale(pObj->aTxt+i)*p->charHeight; + if( hb2type->eJust==1 ){ + jw = 0.5*(pObj->w - 0.5*(p->charWidth + pObj->sw)); + }else{ + jw = 0.0; + } + for(i=0; iptAt.y; + y = yBase; + if( t->eCode & TP_ABOVE2 ) y += 0.5*hc + ha1 + 0.5*ha2; + if( t->eCode & TP_ABOVE ) y += 0.5*hc + 0.5*ha1; + if( t->eCode & TP_BELOW ) y -= 0.5*hc + 0.5*hb1; + if( t->eCode & TP_BELOW2 ) y -= 0.5*hc + hb1 + 0.5*hb2; + if( t->eCode & TP_LJUST ) nx -= jw; + if( t->eCode & TP_RJUST ) nx += jw; + + if( pBox!=0 ){ + /* If pBox is not NULL, do not draw any . Instead, just expand + ** pBox to include the text */ + PNum cw = pik_text_length(t)*p->charWidth*xtraFontScale*0.01; + PNum ch = p->charHeight*0.5*xtraFontScale; + PNum x0, y0, x1, y1; /* Boundary of text relative to pObj->ptAt */ + if( t->eCode & TP_BOLD ) cw *= 1.1; + if( t->eCode & TP_RJUST ){ + x0 = nx; + y0 = y-ch; + x1 = nx-cw; + y1 = y+ch; + }else if( t->eCode & TP_LJUST ){ + x0 = nx; + y0 = y-ch; + x1 = nx+cw; + y1 = y+ch; + }else{ + x0 = nx+cw/2; + y0 = y+ch; + x1 = nx-cw/2; + y1 = y-ch; + } + if( (t->eCode & TP_ALIGN)!=0 && pObj->nPath>=2 ){ + int nn = pObj->nPath; + PNum dx = pObj->aPath[nn-1].x - pObj->aPath[0].x; + PNum dy = pObj->aPath[nn-1].y - pObj->aPath[0].y; + if( dx!=0 || dy!=0 ){ + PNum dist = hypot(dx,dy); + PNum tt; + dx /= dist; + dy /= dist; + tt = dx*x0 - dy*y0; + y0 = dy*x0 - dx*y0; + x0 = tt; + tt = dx*x1 - dy*y1; + y1 = dy*x1 - dx*y1; + x1 = tt; + } + } + pik_bbox_add_xy(pBox, x+x0, orig_y+y0); + pik_bbox_add_xy(pBox, x+x1, orig_y+y1); + continue; + } + nx += x; + y += orig_y; + + pik_append_x(p, "eCode & TP_RJUST ){ + pik_append(p, " text-anchor=\"end\"", -1); + }else if( t->eCode & TP_LJUST ){ + pik_append(p, " text-anchor=\"start\"", -1); + }else{ + pik_append(p, " text-anchor=\"middle\"", -1); + } + if( t->eCode & TP_ITALIC ){ + pik_append(p, " font-style=\"italic\"", -1); + } + if( t->eCode & TP_BOLD ){ + pik_append(p, " font-weight=\"bold\"", -1); + } + if( pObj->color>=0.0 ){ + pik_append_clr(p, " fill=\"", pObj->color, "\"",0); + } + xtraFontScale *= p->fontScale; + if( xtraFontScale<=0.99 || xtraFontScale>=1.01 ){ + pik_append_num(p, " font-size=\"", xtraFontScale*100.0); + pik_append(p, "%\"", 2); + } + if( (t->eCode & TP_ALIGN)!=0 && pObj->nPath>=2 ){ + int nn = pObj->nPath; + PNum dx = pObj->aPath[nn-1].x - pObj->aPath[0].x; + PNum dy = pObj->aPath[nn-1].y - pObj->aPath[0].y; + if( dx!=0 || dy!=0 ){ + PNum ang = atan2(dy,dx)*-180/M_PI; + pik_append_num(p, " transform=\"rotate(", ang); + pik_append_xy(p, " ", x, orig_y); + pik_append(p,")\"",2); + } + } + pik_append(p," dominant-baseline=\"central\">",-1); + if( t->n>=2 && t->z[0]=='"' ){ + z = t->z+1; + nz = t->n-2; + }else{ + z = t->z; + nz = t->n; + } + while( nz>0 ){ + int j; + for(j=0; j\n", -1); + } +} + +/* +** Append text (that will go inside of a
      ...
      ) that +** shows the context of an error token. +*/ +static void pik_error_context(Pik *p, PToken *pErr, int nContext){ + int iErrPt; /* Index of first byte of error from start of input */ + int iErrCol; /* Column of the error token on its line */ + int iStart; /* Start position of the error context */ + int iEnd; /* End position of the error context */ + int iLineno; /* Line number of the error */ + int iFirstLineno; /* Line number of start of error context */ + int i; /* Loop counter */ + int iBump = 0; /* Bump the location of the error cursor */ + char zLineno[20]; /* Buffer in which to generate line numbers */ + + iErrPt = (int)(pErr->z - p->sIn.z); + if( iErrPt>=(int)p->sIn.n ){ + iErrPt = p->sIn.n-1; + iBump = 1; + }else{ + while( iErrPt>0 && (p->sIn.z[iErrPt]=='\n' || p->sIn.z[iErrPt]=='\r') ){ + iErrPt--; + iBump = 1; + } + } + iLineno = 1; + for(i=0; isIn.z[i]=='\n' ){ + iLineno++; + } + } + iStart = 0; + iFirstLineno = 1; + while( iFirstLineno+nContextsIn.z[iStart]!='\n' ){ iStart++; } + iStart++; + iFirstLineno++; + } + for(iEnd=iErrPt; p->sIn.z[iEnd]!=0 && p->sIn.z[iEnd]!='\n'; iEnd++){} + i = iStart; + while( iFirstLineno<=iLineno ){ + snprintf(zLineno,sizeof(zLineno)-1,"/* %4d */ ", iFirstLineno++); + zLineno[sizeof(zLineno)-1] = 0; + pik_append(p, zLineno, -1); + for(i=iStart; p->sIn.z[i]!=0 && p->sIn.z[i]!='\n'; i++){} + pik_append_errtxt(p, p->sIn.z+iStart, i-iStart); + iStart = i+1; + pik_append(p, "\n", 1); + } + for(iErrCol=0, i=iErrPt; i>0 && p->sIn.z[i]!='\n'; iErrCol++, i--){} + for(i=0; in; i++) pik_append(p, "^", 1); + pik_append(p, "\n", 1); +} + + +/* +** Generate an error message for the output. pErr is the token at which +** the error should point. zMsg is the text of the error message. If +** either pErr or zMsg is NULL, generate an out-of-memory error message. +** +** This routine is a no-op if there has already been an error reported. +*/ +static void pik_error(Pik *p, PToken *pErr, const char *zMsg){ + int i; + if( p==0 ) return; + if( p->nErr ) return; + p->nErr++; + if( zMsg==0 ){ + if( p->mFlags & PIKCHR_PLAINTEXT_ERRORS ){ + pik_append(p, "\nOut of memory\n", -1); + }else{ + pik_append(p, "\n

      Out of memory

      \n", -1); + } + return; + } + if( pErr==0 ){ + pik_append(p, "\n", 1); + pik_append_errtxt(p, zMsg, -1); + return; + } + if( (p->mFlags & PIKCHR_PLAINTEXT_ERRORS)==0 ){ + pik_append(p, "
      \n", -1);
      +  }
      +  pik_error_context(p, pErr, 5);
      +  pik_append(p, "ERROR: ", -1);
      +  pik_append_errtxt(p, zMsg, -1);
      +  pik_append(p, "\n", 1);
      +  for(i=p->nCtx-1; i>=0; i--){
      +    pik_append(p, "Called from:\n", -1);
      +    pik_error_context(p, &p->aCtx[i], 0);
      +  }
      +  if( (p->mFlags & PIKCHR_PLAINTEXT_ERRORS)==0 ){
      +    pik_append(p, "
      \n", -1); + } +} + +/* +** Process an "assert( e1 == e2 )" statement. Always return NULL. +*/ +static PObj *pik_assert(Pik *p, PNum e1, PToken *pEq, PNum e2){ + char zE1[100], zE2[100], zMsg[300]; + + /* Convert the numbers to strings using %g for comparison. This + ** limits the precision of the comparison to account for rounding error. */ + snprintf(zE1, sizeof(zE1), "%g", e1); zE1[sizeof(zE1)-1] = 0; + snprintf(zE2, sizeof(zE2), "%g", e2); zE1[sizeof(zE2)-1] = 0; + if( strcmp(zE1,zE2)!=0 ){ + snprintf(zMsg, sizeof(zMsg), "%.50s != %.50s", zE1, zE2); + pik_error(p, pEq, zMsg); + } + return 0; +} + +/* +** Process an "assert( place1 == place2 )" statement. Always return NULL. +*/ +static PObj *pik_position_assert(Pik *p, PPoint *e1, PToken *pEq, PPoint *e2){ + char zE1[100], zE2[100], zMsg[210]; + + /* Convert the numbers to strings using %g for comparison. This + ** limits the precision of the comparison to account for rounding error. */ + snprintf(zE1, sizeof(zE1), "(%g,%g)", e1->x, e1->y); zE1[sizeof(zE1)-1] = 0; + snprintf(zE2, sizeof(zE2), "(%g,%g)", e2->x, e2->y); zE1[sizeof(zE2)-1] = 0; + if( strcmp(zE1,zE2)!=0 ){ + snprintf(zMsg, sizeof(zMsg), "%s != %s", zE1, zE2); + pik_error(p, pEq, zMsg); + } + return 0; +} + +/* Free a complete list of objects */ +static void pik_elist_free(Pik *p, PList *pList){ + int i; + if( pList==0 ) return; + for(i=0; in; i++){ + pik_elem_free(p, pList->a[i]); + } + free(pList->a); + free(pList); + return; +} + +/* Free a single object, and its substructure */ +static void pik_elem_free(Pik *p, PObj *pObj){ + if( pObj==0 ) return; + free(pObj->zName); + pik_elist_free(p, pObj->pSublist); + free(pObj->aPath); + free(pObj); +} + +/* Convert a numeric literal into a number. Return that number. +** There is no error handling because the tokenizer has already +** assured us that the numeric literal is valid. +** +** Allowed number forms: +** +** (1) Floating point literal +** (2) Same as (1) but followed by a unit: "cm", "mm", "in", +** "px", "pt", or "pc". +** (3) Hex integers: 0x000000 +** +** This routine returns the result in inches. If a different unit +** is specified, the conversion happens automatically. +*/ +PNum pik_atof(PToken *num){ + char *endptr; + PNum ans; + if( num->n>=3 && num->z[0]=='0' && (num->z[1]=='x'||num->z[1]=='X') ){ + return (PNum)strtol(num->z+2, 0, 16); + } + ans = strtod(num->z, &endptr); + if( (int)(endptr - num->z)==(int)num->n-2 ){ + char c1 = endptr[0]; + char c2 = endptr[1]; + if( c1=='c' && c2=='m' ){ + ans /= 2.54; + }else if( c1=='m' && c2=='m' ){ + ans /= 25.4; + }else if( c1=='p' && c2=='x' ){ + ans /= 96; + }else if( c1=='p' && c2=='t' ){ + ans /= 72; + }else if( c1=='p' && c2=='c' ){ + ans /= 6; + } + } + return ans; +} + +/* +** Compute the distance between two points +*/ +static PNum pik_dist(PPoint *pA, PPoint *pB){ + PNum dx, dy; + dx = pB->x - pA->x; + dy = pB->y - pA->y; + return hypot(dx,dy); +} + +/* Return true if a bounding box is empty. +*/ +static int pik_bbox_isempty(PBox *p){ + return p->sw.x>p->ne.x; +} + +/* Initialize a bounding box to an empty container +*/ +static void pik_bbox_init(PBox *p){ + p->sw.x = 1.0; + p->sw.y = 1.0; + p->ne.x = 0.0; + p->ne.y = 0.0; +} + +/* Enlarge the PBox of the first argument so that it fully +** covers the second PBox +*/ +static void pik_bbox_addbox(PBox *pA, PBox *pB){ + if( pik_bbox_isempty(pA) ){ + *pA = *pB; + } + if( pik_bbox_isempty(pB) ) return; + if( pA->sw.x>pB->sw.x ) pA->sw.x = pB->sw.x; + if( pA->sw.y>pB->sw.y ) pA->sw.y = pB->sw.y; + if( pA->ne.xne.x ) pA->ne.x = pB->ne.x; + if( pA->ne.yne.y ) pA->ne.y = pB->ne.y; +} + +/* Enlarge the PBox of the first argument, if necessary, so that +** it contains the point described by the 2nd and 3rd arguments. +*/ +static void pik_bbox_add_xy(PBox *pA, PNum x, PNum y){ + if( pik_bbox_isempty(pA) ){ + pA->ne.x = x; + pA->ne.y = y; + pA->sw.x = x; + pA->sw.y = y; + return; + } + if( pA->sw.x>x ) pA->sw.x = x; + if( pA->sw.y>y ) pA->sw.y = y; + if( pA->ne.xne.x = x; + if( pA->ne.yne.y = y; +} + +/* Enlarge the PBox so that it is able to contain an ellipse +** centered at x,y and with radiuses rx and ry. +*/ +static void pik_bbox_addellipse(PBox *pA, PNum x, PNum y, PNum rx, PNum ry){ + if( pik_bbox_isempty(pA) ){ + pA->ne.x = x+rx; + pA->ne.y = y+ry; + pA->sw.x = x-rx; + pA->sw.y = y-ry; + return; + } + if( pA->sw.x>x-rx ) pA->sw.x = x-rx; + if( pA->sw.y>y-ry ) pA->sw.y = y-ry; + if( pA->ne.xne.x = x+rx; + if( pA->ne.yne.y = y+ry; +} + + + +/* Append a new object onto the end of an object list. The +** object list is created if it does not already exist. Return +** the new object list. +*/ +static PList *pik_elist_append(Pik *p, PList *pList, PObj *pObj){ + if( pObj==0 ) return pList; + if( pList==0 ){ + pList = malloc(sizeof(*pList)); + if( pList==0 ){ + pik_error(p, 0, 0); + pik_elem_free(p, pObj); + return 0; + } + memset(pList, 0, sizeof(*pList)); + } + if( pList->n>=pList->nAlloc ){ + int nNew = (pList->n+5)*2; + PObj **pNew = realloc(pList->a, sizeof(PObj*)*nNew); + if( pNew==0 ){ + pik_error(p, 0, 0); + pik_elem_free(p, pObj); + return pList; + } + pList->nAlloc = nNew; + pList->a = pNew; + } + pList->a[pList->n++] = pObj; + p->list = pList; + return pList; +} + +/* Convert an object class name into a PClass pointer +*/ +static const PClass *pik_find_class(PToken *pId){ + int first = 0; + int last = count(aClass) - 1; + do{ + int mid = (first+last)/2; + int c = strncmp(aClass[mid].zName, pId->z, pId->n); + if( c==0 ){ + c = aClass[mid].zName[pId->n]!=0; + if( c==0 ) return &aClass[mid]; + } + if( c<0 ){ + first = mid + 1; + }else{ + last = mid - 1; + } + }while( first<=last ); + return 0; +} + +/* Allocate and return a new PObj object. +** +** If pId!=0 then pId is an identifier that defines the object class. +** If pStr!=0 then it is a STRING literal that defines a text object. +** If pSublist!=0 then this is a [...] object. If all three parameters +** are NULL then this is a no-op object used to define a PLACENAME. +*/ +static PObj *pik_elem_new(Pik *p, PToken *pId, PToken *pStr,PList *pSublist){ + PObj *pNew; + int miss = 0; + + if( p->nErr ) return 0; + pNew = malloc( sizeof(*pNew) ); + if( pNew==0 ){ + pik_error(p,0,0); + pik_elist_free(p, pSublist); + return 0; + } + memset(pNew, 0, sizeof(*pNew)); + p->cur = pNew; + p->nTPath = 1; + p->thenFlag = 0; + if( p->list==0 || p->list->n==0 ){ + pNew->ptAt.x = pNew->ptAt.y = 0.0; + pNew->eWith = CP_C; + }else{ + PObj *pPrior = p->list->a[p->list->n-1]; + pNew->ptAt = pPrior->ptExit; + switch( p->eDir ){ + default: pNew->eWith = CP_W; break; + case DIR_LEFT: pNew->eWith = CP_E; break; + case DIR_UP: pNew->eWith = CP_S; break; + case DIR_DOWN: pNew->eWith = CP_N; break; + } + } + p->aTPath[0] = pNew->ptAt; + pNew->with = pNew->ptAt; + pNew->outDir = pNew->inDir = p->eDir; + pNew->iLayer = (int)pik_value(p, "layer", 5, &miss); + if( miss ) pNew->iLayer = 1000; + if( pNew->iLayer<0 ) pNew->iLayer = 0; + if( pSublist ){ + pNew->type = &sublistClass; + pNew->pSublist = pSublist; + sublistClass.xInit(p,pNew); + return pNew; + } + if( pStr ){ + PToken n; + n.z = "text"; + n.n = 4; + pNew->type = pik_find_class(&n); + assert( pNew->type!=0 ); + pNew->errTok = *pStr; + pNew->type->xInit(p, pNew); + pik_add_txt(p, pStr, pStr->eCode); + return pNew; + } + if( pId ){ + const PClass *pClass; + pNew->errTok = *pId; + pClass = pik_find_class(pId); + if( pClass ){ + pNew->type = pClass; + pNew->sw = pik_value(p, "thickness",9,0); + pNew->fill = pik_value(p, "fill",4,0); + pNew->color = pik_value(p, "color",5,0); + pClass->xInit(p, pNew); + return pNew; + } + pik_error(p, pId, "unknown object type"); + pik_elem_free(p, pNew); + return 0; + } + pNew->type = &noopClass; + pNew->ptExit = pNew->ptEnter = pNew->ptAt; + return pNew; +} + +/* +** If the ID token in the argument is the name of a macro, return +** the PMacro object for that macro +*/ +static PMacro *pik_find_macro(Pik *p, PToken *pId){ + PMacro *pMac; + for(pMac = p->pMacros; pMac; pMac=pMac->pNext){ + if( pMac->macroName.n==pId->n + && strncmp(pMac->macroName.z,pId->z,pId->n)==0 + ){ + return pMac; + } + } + return 0; +} + +/* Add a new macro +*/ +static void pik_add_macro( + Pik *p, /* Current Pikchr diagram */ + PToken *pId, /* The ID token that defines the macro name */ + PToken *pCode /* Macro body inside of {...} */ +){ + PMacro *pNew = pik_find_macro(p, pId); + if( pNew==0 ){ + pNew = malloc( sizeof(*pNew) ); + if( pNew==0 ){ + pik_error(p, 0, 0); + return; + } + pNew->pNext = p->pMacros; + p->pMacros = pNew; + pNew->macroName = *pId; + } + pNew->macroBody.z = pCode->z+1; + pNew->macroBody.n = pCode->n-2; + pNew->inUse = 0; +} + + +/* +** Set the output direction and exit point for an object +*/ +static void pik_elem_set_exit(PObj *pObj, int eDir){ + assert( ValidDir(eDir) ); + pObj->outDir = eDir; + if( !pObj->type->isLine || pObj->bClose ){ + pObj->ptExit = pObj->ptAt; + switch( pObj->outDir ){ + default: pObj->ptExit.x += pObj->w*0.5; break; + case DIR_LEFT: pObj->ptExit.x -= pObj->w*0.5; break; + case DIR_UP: pObj->ptExit.y += pObj->h*0.5; break; + case DIR_DOWN: pObj->ptExit.y -= pObj->h*0.5; break; + } + } +} + +/* Change the layout direction. +*/ +static void pik_set_direction(Pik *p, int eDir){ + assert( ValidDir(eDir) ); + p->eDir = (unsigned char)eDir; + + /* It seems to make sense to reach back into the last object and + ** change its exit point (its ".end") to correspond to the new + ** direction. Things just seem to work better this way. However, + ** legacy PIC does *not* do this. + ** + ** The difference can be seen in a script like this: + ** + ** arrow; circle; down; arrow + ** + ** You can make pikchr render the above exactly like PIC + ** by deleting the following three lines. But I (drh) think + ** it works better with those lines in place. + */ + if( p->list && p->list->n ){ + pik_elem_set_exit(p->list->a[p->list->n-1], eDir); + } +} + +/* Move all coordinates contained within an object (and within its +** substructure) by dx, dy +*/ +static void pik_elem_move(PObj *pObj, PNum dx, PNum dy){ + int i; + pObj->ptAt.x += dx; + pObj->ptAt.y += dy; + pObj->ptEnter.x += dx; + pObj->ptEnter.y += dy; + pObj->ptExit.x += dx; + pObj->ptExit.y += dy; + pObj->bbox.ne.x += dx; + pObj->bbox.ne.y += dy; + pObj->bbox.sw.x += dx; + pObj->bbox.sw.y += dy; + for(i=0; inPath; i++){ + pObj->aPath[i].x += dx; + pObj->aPath[i].y += dy; + } + if( pObj->pSublist ){ + pik_elist_move(pObj->pSublist, dx, dy); + } +} +static void pik_elist_move(PList *pList, PNum dx, PNum dy){ + int i; + for(i=0; in; i++){ + pik_elem_move(pList->a[i], dx, dy); + } +} + +/* +** Check to see if it is ok to set the value of paraemeter mThis. +** Return 0 if it is ok. If it not ok, generate an appropriate +** error message and return non-zero. +** +** Flags are set in pObj so that the same object or conflicting +** objects may not be set again. +** +** To be ok, bit mThis must be clear and no more than one of +** the bits identified by mBlockers may be set. +*/ +static int pik_param_ok( + Pik *p, /* For storing the error message (if any) */ + PObj *pObj, /* The object under construction */ + PToken *pId, /* Make the error point to this token */ + int mThis /* Value we are trying to set */ +){ + if( pObj->mProp & mThis ){ + pik_error(p, pId, "value is already set"); + return 1; + } + if( pObj->mCalc & mThis ){ + pik_error(p, pId, "value already fixed by prior constraints"); + return 1; + } + pObj->mProp |= mThis; + return 0; +} + + +/* +** Set a numeric property like "width 7" or "radius 200%". +** +** The rAbs term is an absolute value to add in. rRel is +** a relative value by which to change the current value. +*/ +void pik_set_numprop(Pik *p, PToken *pId, PRel *pVal){ + PObj *pObj = p->cur; + switch( pId->eType ){ + case T_HEIGHT: + if( pik_param_ok(p, pObj, pId, A_HEIGHT) ) return; + pObj->h = pObj->h*pVal->rRel + pVal->rAbs; + break; + case T_WIDTH: + if( pik_param_ok(p, pObj, pId, A_WIDTH) ) return; + pObj->w = pObj->w*pVal->rRel + pVal->rAbs; + break; + case T_RADIUS: + if( pik_param_ok(p, pObj, pId, A_RADIUS) ) return; + pObj->rad = pObj->rad*pVal->rRel + pVal->rAbs; + break; + case T_DIAMETER: + if( pik_param_ok(p, pObj, pId, A_RADIUS) ) return; + pObj->rad = pObj->rad*pVal->rRel + 0.5*pVal->rAbs; /* diam it 2x rad */ + break; + case T_THICKNESS: + if( pik_param_ok(p, pObj, pId, A_THICKNESS) ) return; + pObj->sw = pObj->sw*pVal->rRel + pVal->rAbs; + break; + } + if( pObj->type->xNumProp ){ + pObj->type->xNumProp(p, pObj, pId); + } + return; +} + +/* +** Set a color property. The argument is an RGB value. +*/ +void pik_set_clrprop(Pik *p, PToken *pId, PNum rClr){ + PObj *pObj = p->cur; + switch( pId->eType ){ + case T_FILL: + if( pik_param_ok(p, pObj, pId, A_FILL) ) return; + pObj->fill = rClr; + break; + case T_COLOR: + if( pik_param_ok(p, pObj, pId, A_COLOR) ) return; + pObj->color = rClr; + break; + } + if( pObj->type->xNumProp ){ + pObj->type->xNumProp(p, pObj, pId); + } + return; +} + +/* +** Set a "dashed" property like "dash 0.05" +** +** Use the value supplied by pVal if available. If pVal==0, use +** a default. +*/ +void pik_set_dashed(Pik *p, PToken *pId, PNum *pVal){ + PObj *pObj = p->cur; + PNum v; + switch( pId->eType ){ + case T_DOTTED: { + v = pVal==0 ? pik_value(p,"dashwid",7,0) : *pVal; + pObj->dotted = v; + pObj->dashed = 0.0; + break; + } + case T_DASHED: { + v = pVal==0 ? pik_value(p,"dashwid",7,0) : *pVal; + pObj->dashed = v; + pObj->dotted = 0.0; + break; + } + } +} + +/* +** If the current path information came from a "same" or "same as" +** reset it. +*/ +static void pik_reset_samepath(Pik *p){ + if( p->samePath ){ + p->samePath = 0; + p->nTPath = 1; + } +} + + +/* Add a new term to the path for a line-oriented object by transferring +** the information in the ptTo field over onto the path and into ptFrom +** resetting the ptTo. +*/ +static void pik_then(Pik *p, PToken *pToken, PObj *pObj){ + int n; + if( !pObj->type->isLine ){ + pik_error(p, pToken, "use with line-oriented objects only"); + return; + } + n = p->nTPath - 1; + if( n<1 && (pObj->mProp & A_FROM)==0 ){ + pik_error(p, pToken, "no prior path points"); + return; + } + p->thenFlag = 1; +} + +/* Advance to the next entry in p->aTPath. Return its index. +*/ +static int pik_next_rpath(Pik *p, PToken *pErr){ + int n = p->nTPath - 1; + if( n+1>=(int)count(p->aTPath) ){ + pik_error(0, pErr, "too many path elements"); + return n; + } + n++; + p->nTPath++; + p->aTPath[n] = p->aTPath[n-1]; + p->mTPath = 0; + return n; +} + +/* Add a direction term to an object. "up 0.5", or "left 3", or "down" +** or "down 50%". +*/ +static void pik_add_direction(Pik *p, PToken *pDir, PRel *pVal){ + PObj *pObj = p->cur; + int n; + int dir; + if( !pObj->type->isLine ){ + if( pDir ){ + pik_error(p, pDir, "use with line-oriented objects only"); + }else{ + PToken x = pik_next_semantic_token(&pObj->errTok); + pik_error(p, &x, "syntax error"); + } + return; + } + pik_reset_samepath(p); + n = p->nTPath - 1; + if( p->thenFlag || p->mTPath==3 || n==0 ){ + n = pik_next_rpath(p, pDir); + p->thenFlag = 0; + } + dir = pDir ? pDir->eCode : p->eDir; + switch( dir ){ + case DIR_UP: + if( p->mTPath & 2 ) n = pik_next_rpath(p, pDir); + p->aTPath[n].y += pVal->rAbs + pObj->h*pVal->rRel; + p->mTPath |= 2; + break; + case DIR_DOWN: + if( p->mTPath & 2 ) n = pik_next_rpath(p, pDir); + p->aTPath[n].y -= pVal->rAbs + pObj->h*pVal->rRel; + p->mTPath |= 2; + break; + case DIR_RIGHT: + if( p->mTPath & 1 ) n = pik_next_rpath(p, pDir); + p->aTPath[n].x += pVal->rAbs + pObj->w*pVal->rRel; + p->mTPath |= 1; + break; + case DIR_LEFT: + if( p->mTPath & 1 ) n = pik_next_rpath(p, pDir); + p->aTPath[n].x -= pVal->rAbs + pObj->w*pVal->rRel; + p->mTPath |= 1; + break; + } + pObj->outDir = dir; +} + +/* Process a movement attribute of one of these forms: +** +** pDist pHdgKW rHdg pEdgept +** GO distance HEADING angle +** GO distance compasspoint +*/ +static void pik_move_hdg( + Pik *p, /* The Pikchr context */ + PRel *pDist, /* Distance to move */ + PToken *pHeading, /* "heading" keyword if present */ + PNum rHdg, /* Angle argument to "heading" keyword */ + PToken *pEdgept, /* EDGEPT keyword "ne", "sw", etc... */ + PToken *pErr /* Token to use for error messages */ +){ + PObj *pObj = p->cur; + int n; + PNum rDist = pDist->rAbs + pik_value(p,"linewid",7,0)*pDist->rRel; + if( !pObj->type->isLine ){ + pik_error(p, pErr, "use with line-oriented objects only"); + return; + } + pik_reset_samepath(p); + do{ + n = pik_next_rpath(p, pErr); + }while( n<1 ); + if( pHeading ){ + if( rHdg<0.0 || rHdg>360.0 ){ + pik_error(p, pHeading, "headings should be between 0 and 360"); + return; + } + }else if( pEdgept->eEdge==CP_C ){ + pik_error(p, pEdgept, "syntax error"); + return; + }else{ + rHdg = pik_hdg_angle[pEdgept->eEdge]; + } + if( rHdg<=45.0 ){ + pObj->outDir = DIR_UP; + }else if( rHdg<=135.0 ){ + pObj->outDir = DIR_RIGHT; + }else if( rHdg<=225.0 ){ + pObj->outDir = DIR_DOWN; + }else if( rHdg<=315.0 ){ + pObj->outDir = DIR_LEFT; + }else{ + pObj->outDir = DIR_UP; + } + rHdg *= 0.017453292519943295769; /* degrees to radians */ + p->aTPath[n].x += rDist*sin(rHdg); + p->aTPath[n].y += rDist*cos(rHdg); + p->mTPath = 2; +} + + +/* Process a movement attribute of the form "right until even with ..." +** +** pDir is the first keyword, "right" or "left" or "up" or "down". +** The movement is in that direction until its closest approach to +** the point specified by pPoint. +*/ +static void pik_evenwith(Pik *p, PToken *pDir, PPoint *pPlace){ + PObj *pObj = p->cur; + int n; + if( !pObj->type->isLine ){ + pik_error(p, pDir, "use with line-oriented objects only"); + return; + } + pik_reset_samepath(p); + n = p->nTPath - 1; + if( p->thenFlag || p->mTPath==3 || n==0 ){ + n = pik_next_rpath(p, pDir); + p->thenFlag = 0; + } + switch( pDir->eCode ){ + case DIR_DOWN: + case DIR_UP: + if( p->mTPath & 2 ) n = pik_next_rpath(p, pDir); + p->aTPath[n].y = pPlace->y; + p->mTPath |= 2; + break; + case DIR_RIGHT: + case DIR_LEFT: + if( p->mTPath & 1 ) n = pik_next_rpath(p, pDir); + p->aTPath[n].x = pPlace->x; + p->mTPath |= 1; + break; + } + pObj->outDir = pDir->eCode; +} + +/* Set the "from" of an object +*/ +static void pik_set_from(Pik *p, PObj *pObj, PToken *pTk, PPoint *pPt){ + if( !pObj->type->isLine ){ + pik_error(p, pTk, "use \"at\" to position this object"); + return; + } + if( pObj->mProp & A_FROM ){ + pik_error(p, pTk, "line start location already fixed"); + return; + } + if( pObj->bClose ){ + pik_error(p, pTk, "polygon is closed"); + return; + } + if( p->nTPath>1 ){ + PNum dx = pPt->x - p->aTPath[0].x; + PNum dy = pPt->y - p->aTPath[0].y; + int i; + for(i=1; inTPath; i++){ + p->aTPath[i].x += dx; + p->aTPath[i].y += dy; + } + } + p->aTPath[0] = *pPt; + p->mTPath = 3; + pObj->mProp |= A_FROM; +} + +/* Set the "to" of an object +*/ +static void pik_add_to(Pik *p, PObj *pObj, PToken *pTk, PPoint *pPt){ + int n = p->nTPath-1; + if( !pObj->type->isLine ){ + pik_error(p, pTk, "use \"at\" to position this object"); + return; + } + if( pObj->bClose ){ + pik_error(p, pTk, "polygon is closed"); + return; + } + pik_reset_samepath(p); + if( n==0 || p->mTPath==3 || p->thenFlag ){ + n = pik_next_rpath(p, pTk); + } + p->aTPath[n] = *pPt; + p->mTPath = 3; +} + +static void pik_close_path(Pik *p, PToken *pErr){ + PObj *pObj = p->cur; + if( p->nTPath<3 ){ + pik_error(p, pErr, + "need at least 3 vertexes in order to close the polygon"); + return; + } + if( pObj->bClose ){ + pik_error(p, pErr, "polygon already closed"); + return; + } + pObj->bClose = 1; +} + +/* Lower the layer of the current object so that it is behind the +** given object. +*/ +static void pik_behind(Pik *p, PObj *pOther){ + PObj *pObj = p->cur; + if( p->nErr==0 && pObj->iLayer>=pOther->iLayer ){ + pObj->iLayer = pOther->iLayer - 1; + } +} + + +/* Set the "at" of an object +*/ +static void pik_set_at(Pik *p, PToken *pEdge, PPoint *pAt, PToken *pErrTok){ + PObj *pObj; + static unsigned char eDirToCp[] = { CP_E, CP_S, CP_W, CP_N }; + if( p->nErr ) return; + pObj = p->cur; + + if( pObj->type->isLine ){ + pik_error(p, pErrTok, "use \"from\" and \"to\" to position this object"); + return; + } + if( pObj->mProp & A_AT ){ + pik_error(p, pErrTok, "location fixed by prior \"at\""); + return; + } + pObj->mProp |= A_AT; + pObj->eWith = pEdge ? pEdge->eEdge : CP_C; + if( pObj->eWith>=CP_END ){ + int dir = pObj->eWith==CP_END ? pObj->outDir : pObj->inDir; + pObj->eWith = eDirToCp[dir]; + } + pObj->with = *pAt; +} + +/* +** Try to add a text attribute to an object +*/ +static void pik_add_txt(Pik *p, PToken *pTxt, int iPos){ + PObj *pObj = p->cur; + PToken *pT; + if( pObj->nTxt >= count(pObj->aTxt) ){ + pik_error(p, pTxt, "too many text terms"); + return; + } + pT = &pObj->aTxt[pObj->nTxt++]; + *pT = *pTxt; + pT->eCode = (short)iPos; +} + +/* Merge "text-position" flags +*/ +static int pik_text_position(int iPrev, PToken *pFlag){ + int iRes = iPrev; + switch( pFlag->eType ){ + case T_LJUST: iRes = (iRes&~TP_JMASK) | TP_LJUST; break; + case T_RJUST: iRes = (iRes&~TP_JMASK) | TP_RJUST; break; + case T_ABOVE: iRes = (iRes&~TP_VMASK) | TP_ABOVE; break; + case T_CENTER: iRes = (iRes&~TP_VMASK) | TP_CENTER; break; + case T_BELOW: iRes = (iRes&~TP_VMASK) | TP_BELOW; break; + case T_ITALIC: iRes |= TP_ITALIC; break; + case T_BOLD: iRes |= TP_BOLD; break; + case T_ALIGNED: iRes |= TP_ALIGN; break; + case T_BIG: if( iRes & TP_BIG ) iRes |= TP_XTRA; + else iRes = (iRes &~TP_SZMASK)|TP_BIG; break; + case T_SMALL: if( iRes & TP_SMALL ) iRes |= TP_XTRA; + else iRes = (iRes &~TP_SZMASK)|TP_SMALL; break; + } + return iRes; +} + +/* +** Table of scale-factor estimates for variable-width characters. +** Actual character widths vary by font. These numbers are only +** guesses. And this table only provides data for ASCII. +** +** 100 means normal width. +*/ +static const unsigned char awChar[] = { + /* Skip initial 32 control characters */ + /* ' ' */ 45, + /* '!' */ 55, + /* '"' */ 62, + /* '#' */ 115, + /* '$' */ 90, + /* '%' */ 132, + /* '&' */ 125, + /* '\''*/ 40, + + /* '(' */ 55, + /* ')' */ 55, + /* '*' */ 71, + /* '+' */ 115, + /* ',' */ 45, + /* '-' */ 48, + /* '.' */ 45, + /* '/' */ 50, + + /* '0' */ 91, + /* '1' */ 91, + /* '2' */ 91, + /* '3' */ 91, + /* '4' */ 91, + /* '5' */ 91, + /* '6' */ 91, + /* '7' */ 91, + + /* '8' */ 91, + /* '9' */ 91, + /* ':' */ 50, + /* ';' */ 50, + /* '<' */ 120, + /* '=' */ 120, + /* '>' */ 120, + /* '?' */ 78, + + /* '@' */ 142, + /* 'A' */ 102, + /* 'B' */ 105, + /* 'C' */ 110, + /* 'D' */ 115, + /* 'E' */ 105, + /* 'F' */ 98, + /* 'G' */ 105, + + /* 'H' */ 125, + /* 'I' */ 58, + /* 'J' */ 58, + /* 'K' */ 107, + /* 'L' */ 95, + /* 'M' */ 145, + /* 'N' */ 125, + /* 'O' */ 115, + + /* 'P' */ 95, + /* 'Q' */ 115, + /* 'R' */ 107, + /* 'S' */ 95, + /* 'T' */ 97, + /* 'U' */ 118, + /* 'V' */ 102, + /* 'W' */ 150, + + /* 'X' */ 100, + /* 'Y' */ 93, + /* 'Z' */ 100, + /* '[' */ 58, + /* '\\'*/ 50, + /* ']' */ 58, + /* '^' */ 119, + /* '_' */ 72, + + /* '`' */ 72, + /* 'a' */ 86, + /* 'b' */ 92, + /* 'c' */ 80, + /* 'd' */ 92, + /* 'e' */ 85, + /* 'f' */ 52, + /* 'g' */ 92, + + /* 'h' */ 92, + /* 'i' */ 47, + /* 'j' */ 47, + /* 'k' */ 88, + /* 'l' */ 48, + /* 'm' */ 135, + /* 'n' */ 92, + /* 'o' */ 86, + + /* 'p' */ 92, + /* 'q' */ 92, + /* 'r' */ 69, + /* 's' */ 75, + /* 't' */ 58, + /* 'u' */ 92, + /* 'v' */ 80, + /* 'w' */ 121, + + /* 'x' */ 81, + /* 'y' */ 80, + /* 'z' */ 76, + /* '{' */ 91, + /* '|'*/ 49, + /* '}' */ 91, + /* '~' */ 118, +}; + +/* Return an estimate of the width of the displayed characters +** in a character string. The returned value is 100 times the +** average character width. +** +** Omit "\" used to escape characters. And count entities like +** "<" as a single character. Multi-byte UTF8 characters count +** as a single character. +** +** Attempt to scale the answer by the actual characters seen. Wide +** characters count more than narrow characters. But the widths are +** only guesses. +*/ +static int pik_text_length(const PToken *pToken){ + int n = pToken->n; + const char *z = pToken->z; + int cnt, j; + for(j=1, cnt=0; j=0x20 && c<=0x7e ){ + cnt += awChar[c-0x20]; + }else{ + cnt += 100; + } + } + return cnt; +} + +/* Adjust the width, height, and/or radius of the object so that +** it fits around the text that has been added so far. +** +** (1) Only text specified prior to this attribute is considered. +** (2) The text size is estimated based on the charht and charwid +** variable settings. +** (3) The fitted attributes can be changed again after this +** attribute, for example using "width 110%" if this auto-fit +** underestimates the text size. +** (4) Previously set attributes will not be altered. In other words, +** "width 1in fit" might cause the height to change, but the +** width is now set. +** (5) This only works for attributes that have an xFit method. +** +** The eWhich parameter is: +** +** 1: Fit horizontally only +** 2: Fit vertically only +** 3: Fit both ways +*/ +static void pik_size_to_fit(Pik *p, PToken *pFit, int eWhich){ + PObj *pObj; + PNum w, h; + PBox bbox; + if( p->nErr ) return; + pObj = p->cur; + + if( pObj->nTxt==0 ){ + pik_error(0, pFit, "no text to fit to"); + return; + } + if( pObj->type->xFit==0 ) return; + pik_bbox_init(&bbox); + pik_compute_layout_settings(p); + pik_append_txt(p, pObj, &bbox); + w = (eWhich & 1)!=0 ? (bbox.ne.x - bbox.sw.x) + p->charWidth : 0; + if( eWhich & 2 ){ + PNum h1, h2; + h1 = (bbox.ne.y - pObj->ptAt.y); + h2 = (pObj->ptAt.y - bbox.sw.y); + h = 2.0*( h1

      charHeight; + }else{ + h = 0; + } + pObj->type->xFit(p, pObj, w, h); + pObj->mProp |= A_FIT; +} + +/* Set a local variable name to "val". +** +** The name might be a built-in variable or a color name. In either case, +** a new application-defined variable is set. Since app-defined variables +** are searched first, this will override any built-in variables. +*/ +static void pik_set_var(Pik *p, PToken *pId, PNum val, PToken *pOp){ + PVar *pVar = p->pVar; + while( pVar ){ + if( pik_token_eq(pId,pVar->zName)==0 ) break; + pVar = pVar->pNext; + } + if( pVar==0 ){ + char *z; + pVar = malloc( pId->n+1 + sizeof(*pVar) ); + if( pVar==0 ){ + pik_error(p, 0, 0); + return; + } + pVar->zName = z = (char*)&pVar[1]; + memcpy(z, pId->z, pId->n); + z[pId->n] = 0; + pVar->pNext = p->pVar; + pVar->val = pik_value(p, pId->z, pId->n, 0); + p->pVar = pVar; + } + switch( pOp->eCode ){ + case T_PLUS: pVar->val += val; break; + case T_STAR: pVar->val *= val; break; + case T_MINUS: pVar->val -= val; break; + case T_SLASH: + if( val==0.0 ){ + pik_error(p, pOp, "division by zero"); + }else{ + pVar->val /= val; + } + break; + default: pVar->val = val; break; + } + p->bLayoutVars = 0; /* Clear the layout setting cache */ +} + +/* +** Search for the variable named z[0..n-1] in: +** +** * Application defined variables +** * Built-in variables +** +** Return the value of the variable if found. If not found +** return 0.0. Also if pMiss is not NULL, then set it to 1 +** if not found. +** +** This routine is a subroutine to pik_get_var(). But it is also +** used by object implementations to look up (possibly overwritten) +** values for built-in variables like "boxwid". +*/ +static PNum pik_value(Pik *p, const char *z, int n, int *pMiss){ + PVar *pVar; + int first, last, mid, c; + for(pVar=p->pVar; pVar; pVar=pVar->pNext){ + if( strncmp(pVar->zName,z,n)==0 && pVar->zName[n]==0 ){ + return pVar->val; + } + } + first = 0; + last = count(aBuiltin)-1; + while( first<=last ){ + mid = (first+last)/2; + c = strncmp(z,aBuiltin[mid].zName,n); + if( c==0 && aBuiltin[mid].zName[n] ) c = 1; + if( c==0 ) return aBuiltin[mid].val; + if( c>0 ){ + first = mid+1; + }else{ + last = mid-1; + } + } + if( pMiss ) *pMiss = 1; + return 0.0; +} + +/* +** Look up a color-name. Unlike other names in this program, the +** color-names are not case sensitive. So "DarkBlue" and "darkblue" +** and "DARKBLUE" all find the same value (139). +** +** If not found, return -99.0. Also post an error if p!=NULL. +** +** Special color names "None" and "Off" return -1.0 without causing +** an error. +*/ +static PNum pik_lookup_color(Pik *p, PToken *pId){ + int first, last, mid, c = 0; + first = 0; + last = count(aColor)-1; + while( first<=last ){ + const char *zClr; + int c1, c2; + unsigned int i; + mid = (first+last)/2; + zClr = aColor[mid].zName; + for(i=0; in; i++){ + c1 = zClr[i]&0x7f; + if( isupper(c1) ) c1 = tolower(c1); + c2 = pId->z[i]&0x7f; + if( isupper(c2) ) c2 = tolower(c2); + c = c2 - c1; + if( c ) break; + } + if( c==0 && aColor[mid].zName[pId->n] ) c = -1; + if( c==0 ) return (double)aColor[mid].val; + if( c>0 ){ + first = mid+1; + }else{ + last = mid-1; + } + } + if( p ) pik_error(p, pId, "not a known color name"); + return -99.0; +} + +/* Get the value of a variable. +** +** Search in order: +** +** * Application defined variables +** * Built-in variables +** * Color names +** +** If no such variable is found, throw an error. +*/ +static PNum pik_get_var(Pik *p, PToken *pId){ + int miss = 0; + PNum v = pik_value(p, pId->z, pId->n, &miss); + if( miss==0 ) return v; + v = pik_lookup_color(0, pId); + if( v>-90.0 ) return v; + pik_error(p,pId,"no such variable"); + return 0.0; +} + +/* Convert a T_NTH token (ex: "2nd", "5th"} into a numeric value and +** return that value. Throw an error if the value is too big. +*/ +static short int pik_nth_value(Pik *p, PToken *pNth){ + int i = atoi(pNth->z); + if( i>1000 ){ + pik_error(p, pNth, "value too big - max '1000th'"); + i = 1; + } + if( i==0 && pik_token_eq(pNth,"first")==0 ) i = 1; + return (short int)i; +} + +/* Search for the NTH object. +** +** If pBasis is not NULL then it should be a [] object. Use the +** sublist of that [] object for the search. If pBasis is not a [] +** object, then throw an error. +** +** The pNth token describes the N-th search. The pNth->eCode value +** is one more than the number of items to skip. It is negative +** to search backwards. If pNth->eType==T_ID, then it is the name +** of a class to search for. If pNth->eType==T_LB, then +** search for a [] object. If pNth->eType==T_LAST, then search for +** any type. +** +** Raise an error if the item is not found. +*/ +static PObj *pik_find_nth(Pik *p, PObj *pBasis, PToken *pNth){ + PList *pList; + int i, n; + const PClass *pClass; + if( pBasis==0 ){ + pList = p->list; + }else{ + pList = pBasis->pSublist; + } + if( pList==0 ){ + pik_error(p, pNth, "no such object"); + return 0; + } + if( pNth->eType==T_LAST ){ + pClass = 0; + }else if( pNth->eType==T_LB ){ + pClass = &sublistClass; + }else{ + pClass = pik_find_class(pNth); + if( pClass==0 ){ + pik_error(0, pNth, "no such object type"); + return 0; + } + } + n = pNth->eCode; + if( n<0 ){ + for(i=pList->n-1; i>=0; i--){ + PObj *pObj = pList->a[i]; + if( pClass && pObj->type!=pClass ) continue; + n++; + if( n==0 ){ return pObj; } + } + }else{ + for(i=0; in; i++){ + PObj *pObj = pList->a[i]; + if( pClass && pObj->type!=pClass ) continue; + n--; + if( n==0 ){ return pObj; } + } + } + pik_error(p, pNth, "no such object"); + return 0; +} + +/* Search for an object by name. +** +** Search in pBasis->pSublist if pBasis is not NULL. If pBasis is NULL +** then search in p->list. +*/ +static PObj *pik_find_byname(Pik *p, PObj *pBasis, PToken *pName){ + PList *pList; + int i, j; + if( pBasis==0 ){ + pList = p->list; + }else{ + pList = pBasis->pSublist; + } + if( pList==0 ){ + pik_error(p, pName, "no such object"); + return 0; + } + /* First look explicitly tagged objects */ + for(i=pList->n-1; i>=0; i--){ + PObj *pObj = pList->a[i]; + if( pObj->zName && pik_token_eq(pName,pObj->zName)==0 ){ + return pObj; + } + } + /* If not found, do a second pass looking for any object containing + ** text which exactly matches pName */ + for(i=pList->n-1; i>=0; i--){ + PObj *pObj = pList->a[i]; + for(j=0; jnTxt; j++){ + if( pObj->aTxt[j].n==pName->n+2 + && memcmp(pObj->aTxt[j].z+1,pName->z,pName->n)==0 ){ + return pObj; + } + } + } + pik_error(p, pName, "no such object"); + return 0; +} + +/* Change most of the settings for the current object to be the +** same as the pOther object, or the most recent object of the same +** type if pOther is NULL. +*/ +static void pik_same(Pik *p, PObj *pOther, PToken *pErrTok){ + PObj *pObj = p->cur; + if( p->nErr ) return; + if( pOther==0 ){ + int i; + for(i=(p->list ? p->list->n : 0)-1; i>=0; i--){ + pOther = p->list->a[i]; + if( pOther->type==pObj->type ) break; + } + if( i<0 ){ + pik_error(p, pErrTok, "no prior objects of the same type"); + return; + } + } + if( pOther->nPath && pObj->type->isLine ){ + PNum dx, dy; + int i; + dx = p->aTPath[0].x - pOther->aPath[0].x; + dy = p->aTPath[0].y - pOther->aPath[0].y; + for(i=1; inPath; i++){ + p->aTPath[i].x = pOther->aPath[i].x + dx; + p->aTPath[i].y = pOther->aPath[i].y + dy; + } + p->nTPath = pOther->nPath; + p->mTPath = 3; + p->samePath = 1; + } + if( !pObj->type->isLine ){ + pObj->w = pOther->w; + pObj->h = pOther->h; + } + pObj->rad = pOther->rad; + pObj->sw = pOther->sw; + pObj->dashed = pOther->dashed; + pObj->dotted = pOther->dotted; + pObj->fill = pOther->fill; + pObj->color = pOther->color; + pObj->cw = pOther->cw; + pObj->larrow = pOther->larrow; + pObj->rarrow = pOther->rarrow; + pObj->bClose = pOther->bClose; + pObj->bChop = pOther->bChop; + pObj->inDir = pOther->inDir; + pObj->outDir = pOther->outDir; + pObj->iLayer = pOther->iLayer; +} + + +/* Return a "Place" associated with object pObj. If pEdge is NULL +** return the center of the object. Otherwise, return the corner +** described by pEdge. +*/ +static PPoint pik_place_of_elem(Pik *p, PObj *pObj, PToken *pEdge){ + PPoint pt = cZeroPoint; + const PClass *pClass; + if( pObj==0 ) return pt; + if( pEdge==0 ){ + return pObj->ptAt; + } + pClass = pObj->type; + if( pEdge->eType==T_EDGEPT || (pEdge->eEdge>0 && pEdge->eEdgexOffset(p, pObj, pEdge->eEdge); + pt.x += pObj->ptAt.x; + pt.y += pObj->ptAt.y; + return pt; + } + if( pEdge->eType==T_START ){ + return pObj->ptEnter; + }else{ + return pObj->ptExit; + } +} + +/* Do a linear interpolation of two positions. +*/ +static PPoint pik_position_between(PNum x, PPoint p1, PPoint p2){ + PPoint out; + out.x = p2.x*x + p1.x*(1.0 - x); + out.y = p2.y*x + p1.y*(1.0 - x); + return out; +} + +/* Compute the position that is dist away from pt at an heading angle of r +** +** The angle is a compass heading in degrees. North is 0 (or 360). +** East is 90. South is 180. West is 270. And so forth. +*/ +static PPoint pik_position_at_angle(PNum dist, PNum r, PPoint pt){ + r *= 0.017453292519943295769; /* degrees to radians */ + pt.x += dist*sin(r); + pt.y += dist*cos(r); + return pt; +} + +/* Compute the position that is dist away at a compass point +*/ +static PPoint pik_position_at_hdg(PNum dist, PToken *pD, PPoint pt){ + return pik_position_at_angle(dist, pik_hdg_angle[pD->eEdge], pt); +} + +/* Return the coordinates for the n-th vertex of a line. +*/ +static PPoint pik_nth_vertex(Pik *p, PToken *pNth, PToken *pErr, PObj *pObj){ + static const PPoint zero = {0, 0}; + int n; + if( p->nErr || pObj==0 ) return p->aTPath[0]; + if( !pObj->type->isLine ){ + pik_error(p, pErr, "object is not a line"); + return zero; + } + n = atoi(pNth->z); + if( n<1 || n>pObj->nPath ){ + pik_error(p, pNth, "no such vertex"); + return zero; + } + return pObj->aPath[n-1]; +} + +/* Return the value of a property of an object. +*/ +static PNum pik_property_of(PObj *pObj, PToken *pProp){ + PNum v = 0.0; + switch( pProp->eType ){ + case T_HEIGHT: v = pObj->h; break; + case T_WIDTH: v = pObj->w; break; + case T_RADIUS: v = pObj->rad; break; + case T_DIAMETER: v = pObj->rad*2.0; break; + case T_THICKNESS: v = pObj->sw; break; + case T_DASHED: v = pObj->dashed; break; + case T_DOTTED: v = pObj->dotted; break; + case T_FILL: v = pObj->fill; break; + case T_COLOR: v = pObj->color; break; + case T_X: v = pObj->ptAt.x; break; + case T_Y: v = pObj->ptAt.y; break; + case T_TOP: v = pObj->bbox.ne.y; break; + case T_BOTTOM: v = pObj->bbox.sw.y; break; + case T_LEFT: v = pObj->bbox.sw.x; break; + case T_RIGHT: v = pObj->bbox.ne.x; break; + } + return v; +} + +/* Compute one of the built-in functions +*/ +static PNum pik_func(Pik *p, PToken *pFunc, PNum x, PNum y){ + PNum v = 0.0; + switch( pFunc->eCode ){ + case FN_ABS: v = v<0.0 ? -v : v; break; + case FN_COS: v = cos(x); break; + case FN_INT: v = rint(x); break; + case FN_SIN: v = sin(x); break; + case FN_SQRT: + if( x<0.0 ){ + pik_error(p, pFunc, "sqrt of negative value"); + v = 0.0; + }else{ + v = sqrt(x); + } + break; + case FN_MAX: v = x>y ? x : y; break; + case FN_MIN: v = xzName); + pObj->zName = malloc(pName->n+1); + if( pObj->zName==0 ){ + pik_error(p,0,0); + }else{ + memcpy(pObj->zName,pName->z,pName->n); + pObj->zName[pName->n] = 0; + } + return; +} + +/* +** Search for object located at *pCenter that has an xChop method. +** Return a pointer to the object, or NULL if not found. +*/ +static PObj *pik_find_chopper(PList *pList, PPoint *pCenter){ + int i; + if( pList==0 ) return 0; + for(i=pList->n-1; i>=0; i--){ + PObj *pObj = pList->a[i]; + if( pObj->type->xChop!=0 + && pObj->ptAt.x==pCenter->x + && pObj->ptAt.y==pCenter->y + ){ + return pObj; + }else if( pObj->pSublist ){ + pObj = pik_find_chopper(pObj->pSublist,pCenter); + if( pObj ) return pObj; + } + } + return 0; +} + +/* +** There is a line traveling from pFrom to pTo. +** +** If point pTo is the exact enter of a choppable object, +** then adjust pTo by the appropriate amount in the direction +** of pFrom. +*/ +static void pik_autochop(Pik *p, PPoint *pFrom, PPoint *pTo){ + PObj *pObj = pik_find_chopper(p->list, pTo); + if( pObj ){ + *pTo = pObj->type->xChop(p, pObj, pFrom); + } +} + +/* This routine runs after all attributes have been received +** on an object. +*/ +static void pik_after_adding_attributes(Pik *p, PObj *pObj){ + int i; + PPoint ofst; + PNum dx, dy; + + if( p->nErr ) return; + + /* Position block objects */ + if( pObj->type->isLine==0 ){ + /* A height or width less than or equal to zero means "autofit". + ** Change the height or width to be big enough to contain the text, + */ + if( pObj->h<=0.0 ){ + if( pObj->nTxt==0 ){ + pObj->h = 0.0; + }else if( pObj->w<=0.0 ){ + pik_size_to_fit(p, &pObj->errTok, 3); + }else{ + pik_size_to_fit(p, &pObj->errTok, 2); + } + } + if( pObj->w<=0.0 ){ + if( pObj->nTxt==0 ){ + pObj->w = 0.0; + }else{ + pik_size_to_fit(p, &pObj->errTok, 1); + } + } + ofst = pik_elem_offset(p, pObj, pObj->eWith); + dx = (pObj->with.x - ofst.x) - pObj->ptAt.x; + dy = (pObj->with.y - ofst.y) - pObj->ptAt.y; + if( dx!=0 || dy!=0 ){ + pik_elem_move(pObj, dx, dy); + } + } + + /* For a line object with no movement specified, a single movement + ** of the default length in the current direction + */ + if( pObj->type->isLine && p->nTPath<2 ){ + pik_next_rpath(p, 0); + assert( p->nTPath==2 ); + switch( pObj->inDir ){ + default: p->aTPath[1].x += pObj->w; break; + case DIR_DOWN: p->aTPath[1].y -= pObj->h; break; + case DIR_LEFT: p->aTPath[1].x -= pObj->w; break; + case DIR_UP: p->aTPath[1].y += pObj->h; break; + } + if( pObj->type->xInit==arcInit ){ + pObj->outDir = (pObj->inDir + (pObj->cw ? 1 : 3))%4; + p->eDir = (unsigned char)pObj->outDir; + switch( pObj->outDir ){ + default: p->aTPath[1].x += pObj->w; break; + case DIR_DOWN: p->aTPath[1].y -= pObj->h; break; + case DIR_LEFT: p->aTPath[1].x -= pObj->w; break; + case DIR_UP: p->aTPath[1].y += pObj->h; break; + } + } + } + + /* Initialize the bounding box prior to running xCheck */ + pik_bbox_init(&pObj->bbox); + + /* Run object-specific code */ + if( pObj->type->xCheck!=0 ){ + pObj->type->xCheck(p,pObj); + if( p->nErr ) return; + } + + /* Compute final bounding box, entry and exit points, center + ** point (ptAt) and path for the object + */ + if( pObj->type->isLine ){ + pObj->aPath = malloc( sizeof(PPoint)*p->nTPath ); + if( pObj->aPath==0 ){ + pik_error(p, 0, 0); + return; + }else{ + pObj->nPath = p->nTPath; + for(i=0; inTPath; i++){ + pObj->aPath[i] = p->aTPath[i]; + } + } + + /* "chop" processing: + ** If the line goes to the center of an object with an + ** xChop method, then use the xChop method to trim the line. + */ + if( pObj->bChop && pObj->nPath>=2 ){ + int n = pObj->nPath; + pik_autochop(p, &pObj->aPath[n-2], &pObj->aPath[n-1]); + pik_autochop(p, &pObj->aPath[1], &pObj->aPath[0]); + } + + pObj->ptEnter = pObj->aPath[0]; + pObj->ptExit = pObj->aPath[pObj->nPath-1]; + + /* Compute the center of the line based on the bounding box over + ** the vertexes. This is a difference from PIC. In Pikchr, the + ** center of a line is the center of its bounding box. In PIC, the + ** center of a line is halfway between its .start and .end. For + ** straight lines, this is the same point, but for multi-segment + ** lines the result is usually diferent */ + for(i=0; inPath; i++){ + pik_bbox_add_xy(&pObj->bbox, pObj->aPath[i].x, pObj->aPath[i].y); + } + pObj->ptAt.x = (pObj->bbox.ne.x + pObj->bbox.sw.x)/2.0; + pObj->ptAt.y = (pObj->bbox.ne.y + pObj->bbox.sw.y)/2.0; + + /* Reset the width and height of the object to be the width and height + ** of the bounding box over vertexes */ + pObj->w = pObj->bbox.ne.x - pObj->bbox.sw.x; + pObj->h = pObj->bbox.ne.y - pObj->bbox.sw.y; + + /* If this is a polygon (if it has the "close" attribute), then + ** adjust the exit point */ + if( pObj->bClose ){ + /* For "closed" lines, the .end is one of the .e, .s, .w, or .n + ** points of the bounding box, as with block objects. */ + pik_elem_set_exit(pObj, pObj->inDir); + } + }else{ + PNum w2 = pObj->w/2.0; + PNum h2 = pObj->h/2.0; + pObj->ptEnter = pObj->ptAt; + pObj->ptExit = pObj->ptAt; + switch( pObj->inDir ){ + default: pObj->ptEnter.x -= w2; break; + case DIR_LEFT: pObj->ptEnter.x += w2; break; + case DIR_UP: pObj->ptEnter.y -= h2; break; + case DIR_DOWN: pObj->ptEnter.y += h2; break; + } + switch( pObj->outDir ){ + default: pObj->ptExit.x += w2; break; + case DIR_LEFT: pObj->ptExit.x -= w2; break; + case DIR_UP: pObj->ptExit.y += h2; break; + case DIR_DOWN: pObj->ptExit.y -= h2; break; + } + pik_bbox_add_xy(&pObj->bbox, pObj->ptAt.x - w2, pObj->ptAt.y - h2); + pik_bbox_add_xy(&pObj->bbox, pObj->ptAt.x + w2, pObj->ptAt.y + h2); + } + p->eDir = (unsigned char)pObj->outDir; +} + +/* Show basic information about each object as a comment in the +** generated HTML. Used for testing and debugging. Activated +** by the (undocumented) "debug = 1;" +** command. +*/ +static void pik_elem_render(Pik *p, PObj *pObj){ + char *zDir; + if( pObj==0 ) return; + pik_append(p,"\n", -1); +} + +/* Render a list of objects +*/ +void pik_elist_render(Pik *p, PList *pList){ + int i; + int iNextLayer = 0; + int iThisLayer; + int bMoreToDo; + int miss = 0; + int mDebug = (int)pik_value(p, "debug", 5, 0); + PNum colorLabel; + do{ + bMoreToDo = 0; + iThisLayer = iNextLayer; + iNextLayer = 0x7fffffff; + for(i=0; in; i++){ + PObj *pObj = pList->a[i]; + void (*xRender)(Pik*,PObj*); + if( pObj->iLayer>iThisLayer ){ + if( pObj->iLayeriLayer; + bMoreToDo = 1; + continue; /* Defer until another round */ + }else if( pObj->iLayertype->xRender; + if( xRender ){ + xRender(p, pObj); + } + if( pObj->pSublist ){ + pik_elist_render(p, pObj->pSublist); + } + } + }while( bMoreToDo ); + + /* If the color_debug_label value is defined, then go through + ** and paint a dot at every label location */ + colorLabel = pik_value(p, "debug_label_color", 17, &miss); + if( miss==0 && colorLabel>=0.0 ){ + PObj dot; + memset(&dot, 0, sizeof(dot)); + dot.type = &noopClass; + dot.rad = 0.015; + dot.sw = 0.015; + dot.fill = colorLabel; + dot.color = colorLabel; + dot.nTxt = 1; + dot.aTxt[0].eCode = TP_ABOVE; + for(i=0; in; i++){ + PObj *pObj = pList->a[i]; + if( pObj->zName==0 ) continue; + dot.ptAt = pObj->ptAt; + dot.aTxt[0].z = pObj->zName; + dot.aTxt[0].n = (int)strlen(pObj->zName); + dotRender(p, &dot); + } + } +} + +/* Add all objects of the list pList to the bounding box +*/ +static void pik_bbox_add_elist(Pik *p, PList *pList, PNum wArrow){ + int i; + for(i=0; in; i++){ + PObj *pObj = pList->a[i]; + if( pObj->sw>0.0 ) pik_bbox_addbox(&p->bbox, &pObj->bbox); + pik_append_txt(p, pObj, &p->bbox); + if( pObj->pSublist ) pik_bbox_add_elist(p, pObj->pSublist, wArrow); + + + /* Expand the bounding box to account for arrowheads on lines */ + if( pObj->type->isLine && pObj->nPath>0 ){ + if( pObj->larrow ){ + pik_bbox_addellipse(&p->bbox, pObj->aPath[0].x, pObj->aPath[0].y, + wArrow, wArrow); + } + if( pObj->rarrow ){ + int j = pObj->nPath-1; + pik_bbox_addellipse(&p->bbox, pObj->aPath[j].x, pObj->aPath[j].y, + wArrow, wArrow); + } + } + } +} + +/* Recompute key layout parameters from variables. */ +static void pik_compute_layout_settings(Pik *p){ + PNum thickness; /* Line thickness */ + PNum wArrow; /* Width of arrowheads */ + + /* Set up rendering parameters */ + if( p->bLayoutVars ) return; + thickness = pik_value(p,"thickness",9,0); + if( thickness<=0.01 ) thickness = 0.01; + wArrow = 0.5*pik_value(p,"arrowwid",8,0); + p->wArrow = wArrow/thickness; + p->hArrow = pik_value(p,"arrowht",7,0)/thickness; + p->fontScale = pik_value(p,"fontscale",9,0); + if( p->fontScale<=0.0 ) p->fontScale = 1.0; + p->rScale = 144.0; + p->charWidth = pik_value(p,"charwid",7,0)*p->fontScale; + p->charHeight = pik_value(p,"charht",6,0)*p->fontScale; + p->bLayoutVars = 1; +} + +/* Render a list of objects. Write the SVG into p->zOut. +** Delete the input object_list before returnning. +*/ +static void pik_render(Pik *p, PList *pList){ + if( pList==0 ) return; + if( p->nErr==0 ){ + PNum thickness; /* Stroke width */ + PNum margin; /* Extra bounding box margin */ + PNum w, h; /* Drawing width and height */ + PNum wArrow; + PNum pikScale; /* Value of the "scale" variable */ + int miss = 0; + + /* Set up rendering parameters */ + pik_compute_layout_settings(p); + thickness = pik_value(p,"thickness",9,0); + if( thickness<=0.01 ) thickness = 0.01; + margin = pik_value(p,"margin",6,0); + margin += thickness; + wArrow = p->wArrow*thickness; + miss = 0; + p->fgcolor = (int)pik_value(p,"fgcolor",7,&miss); + if( miss ){ + PToken t; + t.z = "fgcolor"; + t.n = 7; + p->fgcolor = (int)pik_lookup_color(0, &t); + } + miss = 0; + p->bgcolor = (int)pik_value(p,"bgcolor",7,&miss); + if( miss ){ + PToken t; + t.z = "bgcolor"; + t.n = 7; + p->bgcolor = (int)pik_lookup_color(0, &t); + } + + /* Compute a bounding box over all objects so that we can know + ** how big to declare the SVG canvas */ + pik_bbox_init(&p->bbox); + pik_bbox_add_elist(p, pList, wArrow); + + /* Expand the bounding box slightly to account for line thickness + ** and the optional "margin = EXPR" setting. */ + p->bbox.ne.x += margin + pik_value(p,"rightmargin",11,0); + p->bbox.ne.y += margin + pik_value(p,"topmargin",9,0); + p->bbox.sw.x -= margin + pik_value(p,"leftmargin",10,0); + p->bbox.sw.y -= margin + pik_value(p,"bottommargin",12,0); + + /* Output the SVG */ + pik_append(p, "zClass ){ + pik_append(p, " class=\"", -1); + pik_append(p, p->zClass, -1); + pik_append(p, "\"", 1); + } + w = p->bbox.ne.x - p->bbox.sw.x; + h = p->bbox.ne.y - p->bbox.sw.y; + p->wSVG = (int)(p->rScale*w); + p->hSVG = (int)(p->rScale*h); + pikScale = pik_value(p,"scale",5,0); + if( pikScale>=0.001 && pikScale<=1000.0 + && (pikScale<0.99 || pikScale>1.01) + ){ + p->wSVG = (int)(p->wSVG*pikScale); + p->hSVG = (int)(p->hSVG*pikScale); + pik_append_num(p, " width=\"", p->wSVG); + pik_append_num(p, "\" height=\"", p->hSVG); + pik_append(p, "\"", 1); + } + pik_append_dis(p, " viewBox=\"0 0 ",w,""); + pik_append_dis(p, " ",h,"\">\n"); + pik_elist_render(p, pList); + pik_append(p,"\n", -1); + }else{ + p->wSVG = -1; + p->hSVG = -1; + } + pik_elist_free(p, pList); +} + + + +/* +** An array of this structure defines a list of keywords. +*/ +typedef struct PikWord { + char *zWord; /* Text of the keyword */ + unsigned char nChar; /* Length of keyword text in bytes */ + unsigned char eType; /* Token code */ + unsigned char eCode; /* Extra code for the token */ + unsigned char eEdge; /* CP_* code for corner/edge keywords */ +} PikWord; + +/* +** Keywords +*/ +static const PikWord pik_keywords[] = { + { "above", 5, T_ABOVE, 0, 0 }, + { "abs", 3, T_FUNC1, FN_ABS, 0 }, + { "aligned", 7, T_ALIGNED, 0, 0 }, + { "and", 3, T_AND, 0, 0 }, + { "as", 2, T_AS, 0, 0 }, + { "assert", 6, T_ASSERT, 0, 0 }, + { "at", 2, T_AT, 0, 0 }, + { "behind", 6, T_BEHIND, 0, 0 }, + { "below", 5, T_BELOW, 0, 0 }, + { "between", 7, T_BETWEEN, 0, 0 }, + { "big", 3, T_BIG, 0, 0 }, + { "bold", 4, T_BOLD, 0, 0 }, + { "bot", 3, T_EDGEPT, 0, CP_S }, + { "bottom", 6, T_BOTTOM, 0, CP_S }, + { "c", 1, T_EDGEPT, 0, CP_C }, + { "ccw", 3, T_CCW, 0, 0 }, + { "center", 6, T_CENTER, 0, CP_C }, + { "chop", 4, T_CHOP, 0, 0 }, + { "close", 5, T_CLOSE, 0, 0 }, + { "color", 5, T_COLOR, 0, 0 }, + { "cos", 3, T_FUNC1, FN_COS, 0 }, + { "cw", 2, T_CW, 0, 0 }, + { "dashed", 6, T_DASHED, 0, 0 }, + { "define", 6, T_DEFINE, 0, 0 }, + { "diameter", 8, T_DIAMETER, 0, 0 }, + { "dist", 4, T_DIST, 0, 0 }, + { "dotted", 6, T_DOTTED, 0, 0 }, + { "down", 4, T_DOWN, DIR_DOWN, 0 }, + { "e", 1, T_EDGEPT, 0, CP_E }, + { "east", 4, T_EDGEPT, 0, CP_E }, + { "end", 3, T_END, 0, CP_END }, + { "even", 4, T_EVEN, 0, 0 }, + { "fill", 4, T_FILL, 0, 0 }, + { "first", 5, T_NTH, 0, 0 }, + { "fit", 3, T_FIT, 0, 0 }, + { "from", 4, T_FROM, 0, 0 }, + { "go", 2, T_GO, 0, 0 }, + { "heading", 7, T_HEADING, 0, 0 }, + { "height", 6, T_HEIGHT, 0, 0 }, + { "ht", 2, T_HEIGHT, 0, 0 }, + { "in", 2, T_IN, 0, 0 }, + { "int", 3, T_FUNC1, FN_INT, 0 }, + { "invis", 5, T_INVIS, 0, 0 }, + { "invisible", 9, T_INVIS, 0, 0 }, + { "italic", 6, T_ITALIC, 0, 0 }, + { "last", 4, T_LAST, 0, 0 }, + { "left", 4, T_LEFT, DIR_LEFT, CP_W }, + { "ljust", 5, T_LJUST, 0, 0 }, + { "max", 3, T_FUNC2, FN_MAX, 0 }, + { "min", 3, T_FUNC2, FN_MIN, 0 }, + { "n", 1, T_EDGEPT, 0, CP_N }, + { "ne", 2, T_EDGEPT, 0, CP_NE }, + { "north", 5, T_EDGEPT, 0, CP_N }, + { "nw", 2, T_EDGEPT, 0, CP_NW }, + { "of", 2, T_OF, 0, 0 }, + { "previous", 8, T_LAST, 0, 0, }, + { "print", 5, T_PRINT, 0, 0 }, + { "rad", 3, T_RADIUS, 0, 0 }, + { "radius", 6, T_RADIUS, 0, 0 }, + { "right", 5, T_RIGHT, DIR_RIGHT, CP_E }, + { "rjust", 5, T_RJUST, 0, 0 }, + { "s", 1, T_EDGEPT, 0, CP_S }, + { "same", 4, T_SAME, 0, 0 }, + { "se", 2, T_EDGEPT, 0, CP_SE }, + { "sin", 3, T_FUNC1, FN_SIN, 0 }, + { "small", 5, T_SMALL, 0, 0 }, + { "solid", 5, T_SOLID, 0, 0 }, + { "south", 5, T_EDGEPT, 0, CP_S }, + { "sqrt", 4, T_FUNC1, FN_SQRT, 0 }, + { "start", 5, T_START, 0, CP_START }, + { "sw", 2, T_EDGEPT, 0, CP_SW }, + { "t", 1, T_TOP, 0, CP_N }, + { "the", 3, T_THE, 0, 0 }, + { "then", 4, T_THEN, 0, 0 }, + { "thick", 5, T_THICK, 0, 0 }, + { "thickness", 9, T_THICKNESS, 0, 0 }, + { "thin", 4, T_THIN, 0, 0 }, + { "this", 4, T_THIS, 0, 0 }, + { "to", 2, T_TO, 0, 0 }, + { "top", 3, T_TOP, 0, CP_N }, + { "until", 5, T_UNTIL, 0, 0 }, + { "up", 2, T_UP, DIR_UP, 0 }, + { "vertex", 6, T_VERTEX, 0, 0 }, + { "w", 1, T_EDGEPT, 0, CP_W }, + { "way", 3, T_WAY, 0, 0 }, + { "west", 4, T_EDGEPT, 0, CP_W }, + { "wid", 3, T_WIDTH, 0, 0 }, + { "width", 5, T_WIDTH, 0, 0 }, + { "with", 4, T_WITH, 0, 0 }, + { "x", 1, T_X, 0, 0 }, + { "y", 1, T_Y, 0, 0 }, +}; + +/* +** Search a PikWordlist for the given keyword. Return a pointer to the +** keyword entry found. Or return 0 if not found. +*/ +static const PikWord *pik_find_word( + const char *zIn, /* Word to search for */ + int n, /* Length of zIn */ + const PikWord *aList, /* List to search */ + int nList /* Number of entries in aList */ +){ + int first = 0; + int last = nList-1; + while( first<=last ){ + int mid = (first + last)/2; + int sz = aList[mid].nChar; + int c = strncmp(zIn, aList[mid].zWord, szz character. Fill in other fields of the +** pToken object as appropriate. +*/ +static int pik_token_length(PToken *pToken, int bAllowCodeBlock){ + const unsigned char *z = (const unsigned char*)pToken->z; + int i; + unsigned char c, c2; + switch( z[0] ){ + case '\\': { + pToken->eType = T_WHITESPACE; + for(i=1; z[i]=='\r' || z[i]==' ' || z[i]=='\t'; i++){} + if( z[i]=='\n' ) return i+1; + pToken->eType = T_ERROR; + return 1; + } + case ';': + case '\n': { + pToken->eType = T_EOL; + return 1; + } + case '"': { + for(i=1; (c = z[i])!=0; i++){ + if( c=='\\' ){ + if( z[i+1]==0 ) break; + i++; + continue; + } + if( c=='"' ){ + pToken->eType = T_STRING; + return i+1; + } + } + pToken->eType = T_ERROR; + return i; + } + case ' ': + case '\t': + case '\f': + case '\r': { + for(i=1; (c = z[i])==' ' || c=='\t' || c=='\r' || c=='\t'; i++){} + pToken->eType = T_WHITESPACE; + return i; + } + case '#': { + for(i=1; (c = z[i])!=0 && c!='\n'; i++){} + pToken->eType = T_WHITESPACE; + /* If the comment is "#breakpoint" then invoke the pik_breakpoint() + ** routine. The pik_breakpoint() routie is a no-op that serves as + ** a convenient place to set a gdb breakpoint when debugging. */ + if( strncmp((const char*)z,"#breakpoint",11)==0 ) pik_breakpoint(z); + return i; + } + case '/': { + if( z[1]=='*' ){ + for(i=2; z[i]!=0 && (z[i]!='*' || z[i+1]!='/'); i++){} + if( z[i]=='*' ){ + pToken->eType = T_WHITESPACE; + return i+2; + }else{ + pToken->eType = T_ERROR; + return i; + } + }else if( z[1]=='/' ){ + for(i=2; z[i]!=0 && z[i]!='\n'; i++){} + pToken->eType = T_WHITESPACE; + return i; + }else if( z[1]=='=' ){ + pToken->eType = T_ASSIGN; + pToken->eCode = T_SLASH; + return 2; + }else{ + pToken->eType = T_SLASH; + return 1; + } + } + case '+': { + if( z[1]=='=' ){ + pToken->eType = T_ASSIGN; + pToken->eCode = T_PLUS; + return 2; + } + pToken->eType = T_PLUS; + return 1; + } + case '*': { + if( z[1]=='=' ){ + pToken->eType = T_ASSIGN; + pToken->eCode = T_STAR; + return 2; + } + pToken->eType = T_STAR; + return 1; + } + case '%': { pToken->eType = T_PERCENT; return 1; } + case '(': { pToken->eType = T_LP; return 1; } + case ')': { pToken->eType = T_RP; return 1; } + case '[': { pToken->eType = T_LB; return 1; } + case ']': { pToken->eType = T_RB; return 1; } + case ',': { pToken->eType = T_COMMA; return 1; } + case ':': { pToken->eType = T_COLON; return 1; } + case '>': { pToken->eType = T_GT; return 1; } + case '=': { + if( z[1]=='=' ){ + pToken->eType = T_EQ; + return 2; + } + pToken->eType = T_ASSIGN; + pToken->eCode = T_ASSIGN; + return 1; + } + case '-': { + if( z[1]=='>' ){ + pToken->eType = T_RARROW; + return 2; + }else if( z[1]=='=' ){ + pToken->eType = T_ASSIGN; + pToken->eCode = T_MINUS; + return 2; + }else{ + pToken->eType = T_MINUS; + return 1; + } + } + case '<': { + if( z[1]=='-' ){ + if( z[2]=='>' ){ + pToken->eType = T_LRARROW; + return 3; + }else{ + pToken->eType = T_LARROW; + return 2; + } + }else{ + pToken->eType = T_LT; + return 1; + } + } + case 0xe2: { + if( z[1]==0x86 ){ + if( z[2]==0x90 ){ + pToken->eType = T_LARROW; /* <- */ + return 3; + } + if( z[2]==0x92 ){ + pToken->eType = T_RARROW; /* -> */ + return 3; + } + if( z[2]==0x94 ){ + pToken->eType = T_LRARROW; /* <-> */ + return 3; + } + } + pToken->eType = T_ERROR; + return 1; + } + case '{': { + int len, depth; + i = 1; + if( bAllowCodeBlock ){ + depth = 1; + while( z[i] && depth>0 ){ + PToken x; + x.z = (char*)(z+i); + len = pik_token_length(&x, 0); + if( len==1 ){ + if( z[i]=='{' ) depth++; + if( z[i]=='}' ) depth--; + } + i += len; + } + }else{ + depth = 0; + } + if( depth ){ + pToken->eType = T_ERROR; + return 1; + } + pToken->eType = T_CODEBLOCK; + return i; + } + case '&': { + static const struct { + int nByte; /* Number of bytes in zEntity */ + int eCode; /* Corresponding token code */ + const char *zEntity; /* Name of the HTML entity */ + } aEntity[] = { + /* 123456789 1234567 */ + { 6, T_RARROW, "→" }, /* Same as -> */ + { 12, T_RARROW, "→" }, /* Same as -> */ + { 6, T_LARROW, "←" }, /* Same as <- */ + { 11, T_LARROW, "←" }, /* Same as <- */ + { 16, T_LRARROW, "↔" }, /* Same as <-> */ + }; + unsigned int i; + for(i=0; ieType = aEntity[i].eCode; + return aEntity[i].nByte; + } + } + pToken->eType = T_ERROR; + return 1; + } + default: { + c = z[0]; + if( c=='.' ){ + unsigned char c1 = z[1]; + if( islower(c1) ){ + const PikWord *pFound; + for(i=2; (c = z[i])>='a' && c<='z'; i++){} + pFound = pik_find_word((const char*)z+1, i-1, + pik_keywords, count(pik_keywords)); + if( pFound && (pFound->eEdge>0 || + pFound->eType==T_EDGEPT || + pFound->eType==T_START || + pFound->eType==T_END ) + ){ + /* Dot followed by something that is a 2-D place value */ + pToken->eType = T_DOT_E; + }else if( pFound && (pFound->eType==T_X || pFound->eType==T_Y) ){ + /* Dot followed by "x" or "y" */ + pToken->eType = T_DOT_XY; + }else{ + /* Any other "dot" */ + pToken->eType = T_DOT_L; + } + return 1; + }else if( isdigit(c1) ){ + i = 0; + /* no-op. Fall through to number handling */ + }else if( isupper(c1) ){ + for(i=2; (c = z[i])!=0 && (isalnum(c) || c=='_'); i++){} + pToken->eType = T_DOT_U; + return 1; + }else{ + pToken->eType = T_ERROR; + return 1; + } + } + if( (c>='0' && c<='9') || c=='.' ){ + int nDigit; + int isInt = 1; + if( c!='.' ){ + nDigit = 1; + for(i=1; (c = z[i])>='0' && c<='9'; i++){ nDigit++; } + if( i==1 && (c=='x' || c=='X') ){ + for(i=2; (c = z[i])!=0 && isxdigit(c); i++){} + pToken->eType = T_NUMBER; + return i; + } + }else{ + isInt = 0; + nDigit = 0; + i = 0; + } + if( c=='.' ){ + isInt = 0; + for(i++; (c = z[i])>='0' && c<='9'; i++){ nDigit++; } + } + if( nDigit==0 ){ + pToken->eType = T_ERROR; + return i; + } + if( c=='e' || c=='E' ){ + int iBefore = i; + i++; + c2 = z[i]; + if( c2=='+' || c2=='-' ){ + i++; + c2 = z[i]; + } + if( c2<'0' || c>'9' ){ + /* This is not an exp */ + i = iBefore; + }else{ + i++; + isInt = 0; + while( (c = z[i])>='0' && c<='9' ){ i++; } + } + } + c2 = c ? z[i+1] : 0; + if( isInt ){ + if( (c=='t' && c2=='h') + || (c=='r' && c2=='d') + || (c=='n' && c2=='d') + || (c=='s' && c2=='t') + ){ + pToken->eType = T_NTH; + return i+2; + } + } + if( (c=='i' && c2=='n') + || (c=='c' && c2=='m') + || (c=='m' && c2=='m') + || (c=='p' && c2=='t') + || (c=='p' && c2=='x') + || (c=='p' && c2=='c') + ){ + i += 2; + } + pToken->eType = T_NUMBER; + return i; + }else if( islower(c) ){ + const PikWord *pFound; + for(i=1; (c = z[i])!=0 && (isalnum(c) || c=='_'); i++){} + pFound = pik_find_word((const char*)z, i, + pik_keywords, count(pik_keywords)); + if( pFound ){ + pToken->eType = pFound->eType; + pToken->eCode = pFound->eCode; + pToken->eEdge = pFound->eEdge; + return i; + } + pToken->n = i; + if( pik_find_class(pToken)!=0 ){ + pToken->eType = T_CLASSNAME; + }else{ + pToken->eType = T_ID; + } + return i; + }else if( c>='A' && c<='Z' ){ + for(i=1; (c = z[i])!=0 && (isalnum(c) || c=='_'); i++){} + pToken->eType = T_PLACENAME; + return i; + }else if( c=='$' && z[1]>='1' && z[1]<='9' && !isdigit(z[2]) ){ + pToken->eType = T_PARAMETER; + pToken->eCode = z[1] - '1'; + return 2; + }else if( c=='_' || c=='$' || c=='@' ){ + for(i=1; (c = z[i])!=0 && (isalnum(c) || c=='_'); i++){} + pToken->eType = T_ID; + return i; + }else{ + pToken->eType = T_ERROR; + return 1; + } + } + } +} + +/* +** Return a pointer to the next non-whitespace token after pThis. +** This is used to help form error messages. +*/ +static PToken pik_next_semantic_token(PToken *pThis){ + PToken x; + int sz; + int i = pThis->n; + memset(&x, 0, sizeof(x)); + x.z = pThis->z; + while(1){ + x.z = pThis->z + i; + sz = pik_token_length(&x, 1); + if( x.eType!=T_WHITESPACE ){ + x.n = sz; + return x; + } + i += sz; + } +} + +/* Parser arguments to a macro invocation +** +** (arg1, arg2, ...) +** +** Arguments are comma-separated, except that commas within string +** literals or with (...), {...}, or [...] do not count. The argument +** list begins and ends with parentheses. There can be at most 9 +** arguments. +** +** Return the number of bytes in the argument list. +*/ +static unsigned int pik_parse_macro_args( + Pik *p, + const char *z, /* Start of the argument list */ + int n, /* Available bytes */ + PToken *args, /* Fill in with the arguments */ + PToken *pOuter /* Arguments of the next outer context, or NULL */ +){ + int nArg = 0; + int i, j, sz; + int iStart; + int depth = 0; + PToken x; + if( z[0]!='(' ) return 0; + args[0].z = z+1; + iStart = 1; + for(i=1; in>0 && isspace(t->z[0]) ){ t->n--; t->z++; } + while( t->n>0 && isspace(t->z[t->n-1]) ){ t->n--; } + if( t->n==2 && t->z[0]=='$' && t->z[1]>='1' && t->z[1]<='9' ){ + if( pOuter ) *t = pOuter[t->z[1]-'1']; + else t->n = 0; + } + } + return i+1; + } + x.z = z; + x.n = 1; + pik_error(p, &x, "unterminated macro argument list"); + return 0; +} + +/* +** Split up the content of a PToken into multiple tokens and +** send each to the parser. +*/ +void pik_tokenize(Pik *p, PToken *pIn, yyParser *pParser, PToken *aParam){ + unsigned int i; + int sz = 0; + PToken token; + PMacro *pMac; + for(i=0; in && pIn->z[i] && p->nErr==0; i+=sz){ + token.eCode = 0; + token.eEdge = 0; + token.z = pIn->z + i; + sz = pik_token_length(&token, 1); + if( token.eType==T_WHITESPACE ){ + /* no-op */ + }else if( sz>50000 ){ + token.n = 1; + pik_error(p, &token, "token is too long - max length 50000 bytes"); + break; + }else if( token.eType==T_ERROR ){ + token.n = (unsigned short)(sz & 0xffff); + pik_error(p, &token, "unrecognized token"); + break; + }else if( sz+i>pIn->n ){ + token.n = pIn->n - i; + pik_error(p, &token, "syntax error"); + break; + }else if( token.eType==T_PARAMETER ){ + /* Substitute a parameter into the input stream */ + if( aParam==0 || aParam[token.eCode].n==0 ){ + continue; + } + token.n = (unsigned short)(sz & 0xffff); + if( p->nCtx>=count(p->aCtx) ){ + pik_error(p, &token, "macros nested too deep"); + }else{ + p->aCtx[p->nCtx++] = token; + pik_tokenize(p, &aParam[token.eCode], pParser, 0); + p->nCtx--; + } + }else if( token.eType==T_ID + && (token.n = (unsigned short)(sz & 0xffff), + (pMac = pik_find_macro(p,&token))!=0) + ){ + PToken args[9]; + unsigned int j = i+sz; + if( pMac->inUse ){ + pik_error(p, &pMac->macroName, "recursive macro definition"); + break; + } + token.n = (short int)(sz & 0xffff); + if( p->nCtx>=count(p->aCtx) ){ + pik_error(p, &token, "macros nested too deep"); + break; + } + pMac->inUse = 1; + memset(args, 0, sizeof(args)); + p->aCtx[p->nCtx++] = token; + sz += pik_parse_macro_args(p, pIn->z+j, pIn->n-j, args, aParam); + pik_tokenize(p, &pMac->macroBody, pParser, args); + p->nCtx--; + pMac->inUse = 0; + }else{ +#if 0 + printf("******** Token %s (%d): \"%.*s\" **************\n", + yyTokenName[token.eType], token.eType, + (int)(isspace(token.z[0]) ? 0 : sz), token.z); +#endif + token.n = (unsigned short)(sz & 0xffff); + pik_parser(pParser, token.eType, token); + } + } +} + +/* +** Parse the PIKCHR script contained in zText[]. Return a rendering. Or +** if an error is encountered, return the error text. The error message +** is HTML formatted. So regardless of what happens, the return text +** is safe to be insertd into an HTML output stream. +** +** If pnWidth and pnHeight are not NULL, then this routine writes the +** width and height of the object into the integers that they +** point to. A value of -1 is written if an error is seen. +** +** If zClass is not NULL, then it is a class name to be included in +** the markup. +** +** The returned string is contained in memory obtained from malloc() +** and should be released by the caller. +*/ +char *pikchr( + const char *zText, /* Input PIKCHR source text. zero-terminated */ + const char *zClass, /* Add class="%s" to markup */ + unsigned int mFlags, /* Flags used to influence rendering behavior */ + int *pnWidth, /* Write width of here, if not NULL */ + int *pnHeight /* Write height here, if not NULL */ +){ + Pik s; + yyParser sParse; + + memset(&s, 0, sizeof(s)); + s.sIn.z = zText; + s.sIn.n = (unsigned int)strlen(zText); + s.eDir = DIR_RIGHT; + s.zClass = zClass; + s.mFlags = mFlags; + pik_parserInit(&sParse, &s); +#if 0 + pik_parserTrace(stdout, "parser: "); +#endif + pik_tokenize(&s, &s.sIn, &sParse, 0); + if( s.nErr==0 ){ + PToken token; + memset(&token,0,sizeof(token)); + token.z = zText + (s.sIn.n>0 ? s.sIn.n-1 : 0); + token.n = 1; + pik_parser(&sParse, 0, token); + } + pik_parserFinalize(&sParse); + if( s.zOut==0 && s.nErr==0 ){ + pik_append(&s, "\n", -1); + } + while( s.pVar ){ + PVar *pNext = s.pVar->pNext; + free(s.pVar); + s.pVar = pNext; + } + while( s.pMacros ){ + PMacro *pNext = s.pMacros->pNext; + free(s.pMacros); + s.pMacros = pNext; + } + if( pnWidth ) *pnWidth = s.nErr ? -1 : s.wSVG; + if( pnHeight ) *pnHeight = s.nErr ? -1 : s.hSVG; + if( s.zOut ){ + s.zOut[s.nOut] = 0; + s.zOut = realloc(s.zOut, s.nOut+1); + } + return s.zOut; +} + +#if defined(PIKCHR_FUZZ) +#include +int LLVMFuzzerTestOneInput(const uint8_t *aData, size_t nByte){ + int w,h; + char *zIn, *zOut; + zIn = malloc( nByte + 1 ); + if( zIn==0 ) return 0; + memcpy(zIn, aData, nByte); + zIn[nByte] = 0; + zOut = pikchr(zIn, "pikchr", 0, &w, &h); + free(zIn); + free(zOut); + return 0; +} +#endif /* PIKCHR_FUZZ */ + +#if defined(PIKCHR_SHELL) +/* Print a usage comment for the shell and exit. */ +static void usage(const char *argv0){ + fprintf(stderr, "usage: %s [OPTIONS] FILE ...\n", argv0); + fprintf(stderr, + "Convert Pikchr input files into SVG. Filename \"-\" means stdin.\n" + "Options:\n" + " --dont-stop Process all files even if earlier files have errors\n" + " --svg-only Omit raw SVG without the HTML wrapper\n" + ); + exit(1); +} + +/* Send text to standard output, but escape HTML markup */ +static void print_escape_html(const char *z){ + int j; + char c; + while( z[0]!=0 ){ + for(j=0; (c = z[j])!=0 && c!='<' && c!='>' && c!='&'; j++){} + if( j ) printf("%.*s", j, z); + z += j+1; + j = -1; + if( c=='<' ){ + printf("<"); + }else if( c=='>' ){ + printf(">"); + }else if( c=='&' ){ + printf("&"); + }else if( c==0 ){ + break; + } + } +} + +/* Read the content of file zFilename into memory obtained from malloc() +** Return the memory. If something goes wrong (ex: the file does not exist +** or cannot be opened) put an error message on stderr and return NULL. +** +** If the filename is "-" read stdin. +*/ +static char *readFile(const char *zFilename){ + FILE *in; + size_t n; + size_t nUsed = 0; + size_t nAlloc = 0; + char *z = 0, *zNew = 0; + in = strcmp(zFilename,"-")==0 ? stdin : fopen(zFilename, "rb"); + if( in==0 ){ + fprintf(stderr, "cannot open \"%s\" for reading\n", zFilename); + return 0; + } + while(1){ + if( nUsed+2>=nAlloc ){ + nAlloc = nAlloc*2 + 4000; + zNew = realloc(z, nAlloc); + } + if( zNew==0 ){ + free(z); + fprintf(stderr, "out of memory trying to allocate %lld bytes\n", + (long long int)nAlloc); + exit(1); + } + z = zNew; + n = fread(z+nUsed, 1, nAlloc-nUsed-1, in); + if( n<=0 ){ + break; + } + nUsed += n; + } + if( in!=stdin ) fclose(in); + z[nUsed] = 0; + return z; +} + + +/* Testing interface +** +** Generate HTML on standard output that displays both the original +** input text and the rendered SVG for all files named on the command +** line. +*/ +int main(int argc, char **argv){ + int i; + int bSvgOnly = 0; /* Output SVG only. No HTML wrapper */ + int bDontStop = 0; /* Continue in spite of errors */ + int exitCode = 0; /* What to return */ + int mFlags = 0; /* mFlags argument to pikchr() */ + const char *zStyle = ""; /* Extra styling */ + const char *zHtmlHdr = + "\n" + "\n" + "\nPIKCHR Test\n" + "\n" + "\n" + "\n" + "\n" + "\n" + ; + if( argc<2 ) usage(argv[0]); + for(i=1; iFile %s

      \n", argv[i]); + if( w<0 ){ + printf("

      ERROR

      \n%s\n", zOut); + }else{ + printf("
      \n",i,i); + printf("
      \n", + w,zStyle); + printf("%s
      \n", zOut); + printf("\n
      \n"); + } + } + free(zOut); + free(zIn); + } + if( !bSvgOnly ){ + printf("\n"); + } + return exitCode ? EXIT_FAILURE : EXIT_SUCCESS; +} +#endif /* PIKCHR_SHELL */ + +#ifdef PIKCHR_TCL +#include +/* +** An interface to TCL +** +** TCL command: pikchr $INPUTTEXT +** +** Returns a list of 3 elements which are the output text, the width, and +** the height. +** +** Register the "pikchr" command by invoking Pikchr_Init(Tcl_Interp*). Or +** compile this source file as a shared library and load it using the +** "load" command of Tcl. +** +** Compile this source-code file into a shared library using a command +** similar to this: +** +** gcc -c pikchr.so -DPIKCHR_TCL -shared pikchr.c +*/ +static int pik_tcl_command( + ClientData clientData, /* Not Used */ + Tcl_Interp *interp, /* The TCL interpreter that invoked this command */ + int objc, /* Number of arguments */ + Tcl_Obj *CONST objv[] /* Command arguments */ +){ + int w, h; /* Width and height of the pikchr */ + const char *zIn; /* Source text input */ + char *zOut; /* SVG output text */ + Tcl_Obj *pRes; /* The result TCL object */ + + (void)clientData; + if( objc!=2 ){ + Tcl_WrongNumArgs(interp, 1, objv, "PIKCHR_SOURCE_TEXT"); + return TCL_ERROR; + } + zIn = Tcl_GetString(objv[1]); + w = h = 0; + zOut = pikchr(zIn, "pikchr", 0, &w, &h); + if( zOut==0 ){ + return TCL_ERROR; /* Out of memory */ + } + pRes = Tcl_NewObj(); + Tcl_ListObjAppendElement(0, pRes, Tcl_NewStringObj(zOut, -1)); + free(zOut); + Tcl_ListObjAppendElement(0, pRes, Tcl_NewIntObj(w)); + Tcl_ListObjAppendElement(0, pRes, Tcl_NewIntObj(h)); + Tcl_SetObjResult(interp, pRes); + return TCL_OK; +} + +/* Invoke this routine to register the "pikchr" command with the interpreter +** given in the argument */ +int Pikchr_Init(Tcl_Interp *interp){ + Tcl_CreateObjCommand(interp, "pikchr", pik_tcl_command, 0, 0); + Tcl_PkgProvide (interp, PACKAGE_NAME, PACKAGE_VERSION); + return TCL_OK; +} + + +#endif /* PIKCHR_TCL */ + + +#line 8031 "pikchr.c" ADDED src/pikchrshow.c Index: src/pikchrshow.c ================================================================== --- /dev/null +++ src/pikchrshow.c @@ -0,0 +1,512 @@ +/* +** Copyright (c) 2020 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 fossil-specific code related to pikchr. +*/ +#include "config.h" +#include +#include +#include "pikchrshow.h" + +#if INTERFACE +/* These are described in pikchr_process()'s docs. */ +#define PIKCHR_PROCESS_PASSTHROUGH 0x0003 /* Pass through these flags */ +#define PIKCHR_PROCESS_TH1 0x0004 +#define PIKCHR_PROCESS_TH1_NOSVG 0x0008 +#define PIKCHR_PROCESS_NONCE 0x0010 +#define PIKCHR_PROCESS_ERR_PRE 0x0020 +#define PIKCHR_PROCESS_SRC 0x0040 +#define PIKCHR_PROCESS_DIV 0x0080 +#define PIKCHR_PROCESS_DIV_INDENT 0x0100 +#define PIKCHR_PROCESS_DIV_CENTER 0x0200 +#define PIKCHR_PROCESS_DIV_FLOAT_LEFT 0x0400 +#define PIKCHR_PROCESS_DIV_FLOAT_RIGHT 0x0800 +#define PIKCHR_PROCESS_DIV_TOGGLE 0x1000 +#define PIKCHR_PROCESS_DIV_SOURCE 0x2000 +#define PIKCHR_PROCESS_DIV_SOURCE_INLINE 0x4000 +#endif + +/* +** Processes a pikchr script, optionally with embedded TH1, and +** produces HTML code for it. zIn is the NUL-terminated input +** script. pikFlags may be a bitmask of any of the PIKCHR_PROCESS_xxx +** flags documented below. thFlags may be a bitmask of any of the +** TH_INIT_xxx and/or TH_R2B_xxx flags. Output is sent to pOut, +** appending to it without modifying any prior contents. +** +** Returns 0 on success, 1 if TH1 processing failed, or 2 if pikchr +** processing failed. In either case, the error message (if any) from +** TH1 or pikchr will be appended to pOut. +** +** pikFlags flag descriptions: +** +** - PIKCHR_PROCESS_TH1 means to run zIn through TH1, using the TH1 +** init flags specified in the 3rd argument. If thFlags is non-0 then +** this flag is assumed even if it is not specified. +** +** - PIKCHR_PROCESS_TH1_NOSVG means that processing stops after the +** TH1 eval step, thus the output will be (presumably) a +** TH1-generated/processed pikchr script (or whatever else the TH1 +** outputs). If this flag is set, PIKCHR_PROCESS_TH1 is assumed even +** if it is not specified. +** +** All of the remaining flags listed below are ignored if +** PIKCHR_PROCESS_TH1_NOSVG is specified! +** +** - PIKCHR_PROCESS_DIV: if set, the SVG result is wrapped in a DIV +** element which specifies a max-width style value based on the SVG's +** calculated size. This flag has multiple mutually exclusive forms: +** +** - PIKCHR_PROCESS_DIV uses default element alignment. +** - PIKCHR_PROCESS_DIV_INDENT indents the div. +** - PIKCHR_PROCESS_DIV_CENTER centers the div. +** - PIKCHR_PROCESS_DIV_FLOAT_LEFT floats the div left. +** - PIKCHR_PROCESS_DIV_FLOAT_RIGHT floats the div right. +** +** If more than one is specified, which one is used is undefined. Those +** flags may be OR'd with one or both of the following: +** +** - PIKCHR_PROCESS_DIV_TOGGLE: adds the 'toggle' CSS class to the +** outer DIV so that event-handler code can install different +** toggling behaviour than the default. Default is ctrl-click, but +** this flag enables single-click toggling for the element. +** +** - PIKCHR_PROCESS_DIV_SOURCE: adds the 'source' CSS class to the +** outer DIV, which is a hint to the client-side renderer (see +** fossil.pikchr.js) that the pikchr should initially be rendered +** in source code form mode (the default is to hide the source and +** show the SVG). +** +** - PIKCHR_PROCESS_DIV_SOURCE_INLINE: adds the 'source-inline' CSS +** class to the outer wrapper. This modifier changes how the +** 'source' CSS class gets applied: with this flag, the source view +** should be rendered "inline" (same position as the graphic), else +** it is to be left-aligned. +** +** - PIKCHR_PROCESS_NONCE: if set, the resulting SVG/DIV are wrapped +** in "safe nonce" comments, which are a fossil-internal mechanism +** which prevents the wiki/markdown processors from re-processing this +** output. This is necessary when calling this routine in the context +** of wiki/embedded doc processing, but not (e.g.) when fetching +** an image for /pikchrpage. +** +** - PIKCHR_PROCESS_SRC: if set, a new PRE.pikchr-src element is +** injected adjacent to the SVG element which contains the +** HTML-escaped content of the input script. If +** PIKCHR_PROCESS_DIV_SOURCE or PIKCHR_PROCESS_DIV_SOURCE_INLINE is +** set, this flag is automatically implied. +** +** - PIKCHR_PROCESS_ERR_PRE: if set and pikchr() fails, the resulting +** error report is wrapped in a PRE element, else it is retained +** as-is (intended only for console output). +*/ +int pikchr_process(const char * zIn, int pikFlags, int thFlags, + Blob * pOut){ + Blob bIn = empty_blob; + int isErr = 0; + const char *zNonce = (PIKCHR_PROCESS_NONCE & pikFlags) + ? safe_html_nonce(1) : 0; + + if(!(PIKCHR_PROCESS_DIV & pikFlags) + /* If any DIV_xxx flags are set, set DIV */ + && (PIKCHR_PROCESS_DIV_INDENT + | PIKCHR_PROCESS_DIV_CENTER + | PIKCHR_PROCESS_DIV_FLOAT_RIGHT + | PIKCHR_PROCESS_DIV_FLOAT_LEFT + | PIKCHR_PROCESS_DIV_SOURCE + | PIKCHR_PROCESS_DIV_SOURCE_INLINE + | PIKCHR_PROCESS_DIV_TOGGLE + ) & pikFlags){ + pikFlags |= PIKCHR_PROCESS_DIV; + } + if(!(PIKCHR_PROCESS_TH1 & pikFlags) + /* If any TH1_xxx flags are set, set TH1 */ + && (PIKCHR_PROCESS_TH1_NOSVG & pikFlags || thFlags!=0)){ + pikFlags |= PIKCHR_PROCESS_TH1; + } + if(zNonce){ + blob_appendf(pOut, "%s\n", zNonce); + } + if(PIKCHR_PROCESS_TH1 & pikFlags){ + Blob out = empty_blob; + isErr = Th_RenderToBlob(zIn, &out, thFlags) + ? 1 : 0; + if(isErr){ + blob_append(pOut, blob_str(&out), blob_size(&out)); + blob_reset(&out); + }else{ + bIn = out; + } + }else{ + blob_init(&bIn, zIn, -1); + } + if(!isErr){ + if(PIKCHR_PROCESS_TH1_NOSVG & pikFlags){ + blob_append(pOut, blob_str(&bIn), blob_size(&bIn)); + }else{ + int w = 0, h = 0; + const char * zContent = blob_str(&bIn); + char *zOut; + zOut = pikchr(zContent, "pikchr", + 0x01 | (pikFlags&PIKCHR_PROCESS_PASSTHROUGH), + &w, &h); + if( w>0 && h>0 ){ + const char * zClassToggle = ""; + const char * zClassSource = ""; + const char * zWrapperClass = ""; + if(PIKCHR_PROCESS_DIV & pikFlags){ + if(PIKCHR_PROCESS_DIV_CENTER & pikFlags){ + zWrapperClass = " center"; + }else if(PIKCHR_PROCESS_DIV_INDENT & pikFlags){ + zWrapperClass = " indent"; + }else if(PIKCHR_PROCESS_DIV_FLOAT_LEFT & pikFlags){ + zWrapperClass = " float-left"; + }else if(PIKCHR_PROCESS_DIV_FLOAT_RIGHT & pikFlags){ + zWrapperClass = " float-right"; + } + if(PIKCHR_PROCESS_DIV_TOGGLE & pikFlags){ + zClassToggle = " toggle"; + } + if(PIKCHR_PROCESS_DIV_SOURCE_INLINE & pikFlags){ + if(PIKCHR_PROCESS_DIV_SOURCE & pikFlags){ + zClassSource = " source source-inline"; + }else{ + zClassSource = " source-inline"; + } + pikFlags |= PIKCHR_PROCESS_SRC; + }else if(PIKCHR_PROCESS_DIV_SOURCE & pikFlags){ + zClassSource = " source"; + pikFlags |= PIKCHR_PROCESS_SRC; + } + blob_appendf(pOut,"
      " + "
      \n", + zWrapperClass/*safe-for-%s*/, + zClassToggle/*safe-for-%s*/, + zClassSource/*safe-for-%s*/, w); + } + blob_append(pOut, zOut, -1); + if(PIKCHR_PROCESS_DIV & pikFlags){ + blob_append(pOut, "
      \n", 7); + } + if(PIKCHR_PROCESS_SRC & pikFlags){ + blob_appendf(pOut, "
      %h
      \n", + blob_str(&bIn)); + } + if(PIKCHR_PROCESS_DIV & pikFlags){ + blob_append(pOut, "
      \n", 7); + } + }else{ + isErr = 2; + if(PIKCHR_PROCESS_ERR_PRE & pikFlags){ + blob_append(pOut, "
      \n", 20);
      +        }
      +        blob_append(pOut, zOut, -1);
      +        if(PIKCHR_PROCESS_ERR_PRE & pikFlags){
      +          blob_append(pOut, "\n
      \n", 8); + } + } + fossil_free(zOut); + } + } + if(zNonce){ + blob_appendf(pOut, "%s\n", zNonce); + } + blob_reset(&bIn); + return isErr; +} + +/* +** WEBPAGE: pikchrshow +** +** A pikchr code editor and previewer, allowing users to experiment +** with pikchr code or prototype it for use in copy/pasting into forum +** posts, wiki pages, or embedded docs. +** +** It optionally accepts a p=pikchr-script-code URL parameter or POST +** value to pre-populate the editor with that code. +*/ +void pikchrshow_page(void){ + const char *zContent = 0; + int isDark; /* true if the current skin is "dark" */ + int pikFlags = + PIKCHR_PROCESS_DIV + | PIKCHR_PROCESS_SRC + | PIKCHR_PROCESS_ERR_PRE; + + login_check_credentials(); + if( !g.perm.RdWiki && !g.perm.Read && !g.perm.RdForum ){ + cgi_redirectf("%R/login?g=%R/pikchrshow"); + } + zContent = PD("content",P("p")); + if(P("ajax")!=0){ + /* Called from the JS-side preview updater. + TODO: respond with JSON instead.*/ + cgi_set_content_type("text/html"); + if(zContent && *zContent){ + Blob out = empty_blob; + const int isErr = + pikchr_process(zContent, pikFlags, 0, &out); + if(isErr){ + cgi_printf_header("x-pikchrshow-is-error: %d\r\n", isErr); + } + CX("%b", &out); + blob_reset(&out); + }else{ + CX("
      No content! Nothing to render
      "); + } + return; + }/*ajax response*/ + style_emit_noscript_for_js_page(); + isDark = skin_detail_boolean("white-foreground"); + if(!zContent){ + zContent = "arrow right 200% \"Markdown\" \"Source\"\n" + "box rad 10px \"Markdown\" \"Formatter\" \"(markdown.c)\" fit\n" + "arrow right 200% \"HTML+SVG\" \"Output\"\n" + "arrow <-> down from last box.s\n" + "box same \"Pikchr\" \"Formatter\" \"(pikchr.c)\" fit\n"; + } + style_header("PikchrShow"); + CX(""); + CX("
      Input pikchr code and tap Preview (or Ctrl-Enter) to render " + "it:
      "); + CX("
      "); { + CX("
      "); { + CX("",zContent/*safe-for-%s*/); + CX("
      "); { + CX(""); + CX("
      "); { + CX(""); + CX(""); + CX(""); + CX("Stores/restores a single pikchr script to/from " + "browser-local storage from/to the editor." + /* gets turned into a help-buttonlet */); + } CX("
      "/*stash controls*/); + style_labeled_checkbox("flipcolors-wrapper", "flipcolors", + "Dark mode?", + "1", isDark, 0); + } CX("
      "/*#pikchrshow-controls*/); + } + CX("
      "/*#pikchrshow-form*/); + CX("
      "); { + CX("" + /* Reminder: Firefox does not properly flexbox a LEGEND + element, always flowing it in column mode. */); + CX("
      "); + if(*zContent){ + Blob out = empty_blob; + pikchr_process(zContent, pikFlags, 0, &out); + CX("%b", &out); + blob_reset(&out); + } CX("
      "/*#pikchrshow-output*/); + } CX("
      "/*#pikchrshow-output-wrapper*/); + } CX("
      "/*sbs-wrapper*/); + builtin_fossil_js_bundle_or("fetch", "copybutton", "popupwidget", + "storage", "pikchr", NULL); + builtin_request_js("fossil.page.pikchrshow.js"); + builtin_fulfill_js_requests(); + style_finish_page(); +} + +/* +** COMMAND: pikchr* +** +** Usage: %fossil pikchr [options] ?INFILE? ?OUTFILE? +** +** Accepts a pikchr script as input and outputs the rendered script as +** an SVG graphic. The INFILE and OUTFILE options default to stdin +** resp. stdout, and the names "-" can be used as aliases for those +** streams. +** +** Options: +** +** -div On success, add a DIV wrapper around the +** resulting SVG output which limits its max-width to +** its computed maximum ideal size. +** +** -div-indent Like -div but indent the div. +** +** -div-center Like -div but center the div. +** +** -div-left Like -div but float the div left. +** +** -div-right Like -div but float the div right. +** +** -div-toggle Set the 'toggle' CSS class on the div (used by the +** JavaScript-side post-processor). +** +** -div-source Set the 'source' CSS class on the div, which tells +** CSS to hide the SVG and reveal the source by default. +** +** -src Store the input pikchr's source code in the output as +** a separate element adjacent to the SVG one. Implied +** by -div-source. +** +** +** -th Process the input using TH1 before passing it to pikchr. +** +** -th-novar Disable $var and $ TH1 processing. Use this if the +** pikchr script uses '$' for its own purposes and that +** causes issues. This only affects parsing of '$' outside +** of TH1 script blocks. Code in such blocks is unaffected. +** +** -th-nosvg When using -th, output the post-TH1'd script +** instead of the pikchr-rendered output. +** +** -th-trace Trace TH1 execution (for debugging purposes). +** +** +** The -div-indent/center/left/right flags may not be combined. +** +** TH1-related Notes and Caveats: +** +** If the -th flag is used, this command must open a fossil database +** for certain functionality to work (via a checkout or the -R REPO +** flag). If opening a db fails, execution will continue but any TH1 +** commands which require a db will trigger a fatal error. +** +** In Fossil skins, TH1 variables in the form $varName are expanded +** as-is and those in the form $ are htmlized in the +** resulting output. This processor disables the htmlizing step, so $x +** and $ are equivalent unless the TH1-processed pikchr script +** invokes the TH1 command [enable_htmlify 1] to enable it. Normally +** that option will interfere with pikchr output, however, e.g. by +** HTML-encoding double-quotes. +** +** Many of the fossil-installed TH1 functions simply do not make any +** sense for pikchr scripts. +*/ +void pikchr_cmd(void){ + Blob bIn = empty_blob; + Blob bOut = empty_blob; + const char * zInfile = "-"; + const char * zOutfile = "-"; + const int fTh1 = find_option("th",0,0)!=0; + const int fNosvg = find_option("th-nosvg",0,0)!=0; + int isErr = 0; + int pikFlags = find_option("src",0,0)!=0 + ? PIKCHR_PROCESS_SRC : 0; + u32 fThFlags = TH_INIT_NO_ENCODE + | (find_option("th-novar",0,0)!=0 ? TH_R2B_NO_VARS : 0); + + Th_InitTraceLog()/*processes -th-trace flag*/; + + if(find_option("div",0,0)!=0){ + pikFlags |= PIKCHR_PROCESS_DIV; + }else if(find_option("div-indent",0,0)!=0){ + pikFlags |= PIKCHR_PROCESS_DIV_INDENT; + }else if(find_option("div-center",0,0)!=0){ + pikFlags |= PIKCHR_PROCESS_DIV_CENTER; + }else if(find_option("div-float-left",0,0)!=0){ + pikFlags |= PIKCHR_PROCESS_DIV_FLOAT_LEFT; + }else if(find_option("div-float-right",0,0)!=0){ + pikFlags |= PIKCHR_PROCESS_DIV_FLOAT_RIGHT; + } + if(find_option("div-toggle",0,0)!=0){ + pikFlags |= PIKCHR_PROCESS_DIV_TOGGLE; + } + if(find_option("div-source",0,0)!=0){ + pikFlags |= PIKCHR_PROCESS_DIV_SOURCE | PIKCHR_PROCESS_SRC; + } + + verify_all_options(); + if(g.argc>4){ + usage("?INFILE? ?OUTFILE?"); + } + if(g.argc>2){ + zInfile = g.argv[2]; + } + if(g.argc>3){ + zOutfile = g.argv[3]; + } + blob_read_from_file(&bIn, zInfile, ExtFILE); + if(fTh1){ + db_find_and_open_repository(OPEN_ANY_SCHEMA | OPEN_OK_NOT_FOUND, 0) + /* ^^^ needed for certain TH1 functions to work */; + pikFlags |= PIKCHR_PROCESS_TH1; + if(fNosvg) pikFlags |= PIKCHR_PROCESS_TH1_NOSVG; + } + isErr = pikchr_process(blob_str(&bIn), pikFlags, + fTh1 ? fThFlags : 0, &bOut); + if(isErr){ + fossil_fatal("%s ERROR:%c%b", 1==isErr ? "TH1" : "pikchr", + 1==isErr ? ' ' : '\n', + &bOut); + }else{ + blob_write_to_file(&bOut, zOutfile); + } + Th_PrintTraceLog(); + blob_reset(&bIn); + blob_reset(&bOut); +} Index: src/pivot.c ================================================================== --- src/pivot.c +++ src/pivot.c @@ -38,23 +38,24 @@ */ void pivot_set_primary(int rid){ /* Set up table used to do the search */ db_multi_exec( "CREATE TEMP TABLE IF NOT EXISTS aqueue(" - " rid INTEGER PRIMARY KEY," /* The record id for this version */ + " rid INTEGER," /* The record id for this version */ " mtime REAL," /* Time when this version was created */ " pending BOOLEAN," /* True if we have not check this one yet */ - " src BOOLEAN" /* 1 for primary. 0 for others */ - ");" + " src BOOLEAN," /* 1 for primary. 0 for others */ + " PRIMARY KEY(rid,src)" + ") WITHOUT ROWID;" "DELETE FROM aqueue;" "CREATE INDEX IF NOT EXISTS aqueue_idx1 ON aqueue(pending, mtime);" ); /* Insert the primary record */ - db_multi_exec( + db_multi_exec( "INSERT INTO aqueue(rid, mtime, pending, src)" - " SELECT %d, mtime, 1, 1 FROM plink WHERE cid=%d LIMIT 1", + " SELECT %d, mtime, 1, 1 FROM event WHERE objid=%d AND type='ci' LIMIT 1", rid, rid ); } /* @@ -62,31 +63,33 @@ ** must be at least one secondary but there can be more than one if ** desired. */ void pivot_set_secondary(int rid){ /* Insert the primary record */ - db_multi_exec( + db_multi_exec( "INSERT OR IGNORE INTO aqueue(rid, mtime, pending, src)" - " SELECT %d, mtime, 1, 0 FROM plink WHERE cid=%d", + " SELECT %d, mtime, 1, 0 FROM event WHERE objid=%d AND type='ci'", rid, rid ); } /* ** Find the most recent common ancestor of the primary and one of ** the secondaries. Return its rid. Return 0 if no common ancestor ** can be found. +** +** If ignoreMerges is true, follow only "primary" parent links. */ -int pivot_find(void){ +int pivot_find(int ignoreMerges){ Stmt q1, q2, u1, i1; int rid = 0; - + /* aqueue must contain at least one primary and one other. Otherwise ** we abort early */ if( db_int(0, "SELECT count(distinct src) FROM aqueue")<2 ){ - fossil_panic("lack both primary and secondary files"); + fossil_fatal("lack both primary and secondary files"); } /* Prepare queries we will be needing ** ** The first query finds the oldest pending version on the aqueue. This @@ -102,11 +105,12 @@ db_prepare(&q2, "SELECT 1 FROM aqueue A, plink, aqueue B" " WHERE plink.pid=:rid" " AND plink.cid=B.rid" " AND A.rid=:rid" - " AND A.src!=B.src" + " AND A.src!=B.src %s", + ignoreMerges ? "AND plink.isprim" : "" ); /* Mark the :rid record has having been checked. It is not the ** common ancestor. */ @@ -115,18 +119,19 @@ ); /* Add to the queue all ancestors of :rid. */ db_prepare(&i1, - "INSERT OR IGNORE INTO aqueue " + "REPLACE INTO aqueue " "SELECT plink.pid," - " coalesce((SELECT mtime FROM plink X WHERE X.cid=plink.pid), 0.0)," + " coalesce((SELECT mtime FROM event X WHERE X.objid=plink.pid), 0.0)," " 1," " aqueue.src " " FROM plink, aqueue" " WHERE plink.cid=:rid" - " AND aqueue.rid=:rid" + " AND aqueue.rid=:rid %s", + ignoreMerges ? "AND plink.isprim" : "" ); while( db_step(&q1)==SQLITE_ROW ){ rid = db_column_int(&q1, 0); db_reset(&q1); @@ -147,24 +152,49 @@ db_finalize(&u1); return rid; } /* -** COMMAND: test-find-pivot +** COMMAND: test-find-pivot +** +** Usage: %fossil test-find-pivot ?options? PRIMARY SECONDARY ... ** ** Test the pivot_find() procedure. +** +** Options: +** --ignore-merges Ignore merges for discovering name pivots */ void test_find_pivot(void){ int i, rid; + int ignoreMerges = find_option("ignore-merges",0,0)!=0; + int showDetails = find_option("details",0,0)!=0; if( g.argc<4 ){ - usage("PRIMARY SECONDARY ..."); + usage("?options? PRIMARY SECONDARY ..."); } db_must_be_within_tree(); pivot_set_primary(name_to_rid(g.argv[2])); for(i=3; i +#include +/* +** Print a fatal error and quit. +*/ +static void win32_fatal_error(const char *zMsg){ + fossil_fatal("%s", zMsg); +} +#else +#include +#include +#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 is +** a FILE that becomes the standard input of the child process. +** (The caller writes to *ppOut in order to send text to the child.) +** *ppIn is stdout from the child process. (The caller +** reads from *ppIn in order to receive input from the child.) +** Note that *ppIn is an unbuffered file descriptor, not a FILE. +** The process ID of the child is written into *pChildPid. +** +** Return the number of errors. +*/ +int popen2( + const char *zCmd, /* Command to run in the child process */ + int *pfdIn, /* Read from child using this file descriptor */ + FILE **ppOut, /* Write to child using this file descriptor */ + int *pChildPid, /* PID of the child process */ + int bDirect /* 0: run zCmd as a shell cmd. 1: run directly */ +){ +#ifdef _WIN32 + 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(fossil_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 pin[2], pout[2]; + *pfdIn = 0; + *ppOut = 0; + *pChildPid = 0; + + if( pipe(pin)<0 ){ + return 1; + } + if( pipe(pout)<0 ){ + close(pin[0]); + close(pin[1]); + return 1; + } + *pChildPid = fork(); + if( *pChildPid<0 ){ + close(pin[0]); + close(pin[1]); + close(pout[0]); + close(pout[1]); + *pChildPid = 0; + return 1; + } + 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]); + if( bDirect ){ + execl(zCmd, zCmd, (char*)0); + }else{ + execl("/bin/sh", "/bin/sh", "-c", zCmd, (char*)0); + } + return 1; + }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 +** popen2(). +*/ +void pclose2(int fdIn, FILE *pOut, int childPid){ +#ifdef _WIN32 + /* Not implemented, yet */ + close(fdIn); + fclose(pOut); +#else + close(fdIn); + fclose(pOut); + while( waitpid(0, 0, WNOHANG)>0 ) {} +#endif +} Index: src/pqueue.c ================================================================== --- src/pqueue.c +++ src/pqueue.c @@ -22,10 +22,14 @@ ** ** The way this queue is used, we never expect it to contain more ** than 2 or 3 elements, so a simple array is sufficient as the ** implementation. This could give worst case O(N) insert times, ** but because of the nature of the problem we expect O(1) performance. +** +** Compatibility note: Some versions of OpenSSL export a symbols +** like "pqueue_insert". This is, technically, a bug in OpenSSL. +** We work around it here by using "pqueuex_" instead of "pqueue_". */ #include "config.h" #include "pqueue.h" #include @@ -38,45 +42,46 @@ struct PQueue { int cnt; /* Number of entries in the queue */ int sz; /* Number of slots in a[] */ struct QueueElement { int id; /* ID of the element */ + void *p; /* Content pointer */ double value; /* Value of element. Kept in ascending order */ } *a; }; #endif /* ** Initialize a PQueue structure */ -void pqueue_init(PQueue *p){ +void pqueuex_init(PQueue *p){ memset(p, 0, sizeof(*p)); } /* ** Destroy a PQueue. Delete all of its content. */ -void pqueue_clear(PQueue *p){ +void pqueuex_clear(PQueue *p){ free(p->a); - pqueue_init(p); + pqueuex_init(p); } /* ** Change the size of the queue so that it contains N slots */ -static void pqueue_resize(PQueue *p, int N){ - p->a = realloc(p->a, sizeof(p->a[0])*N); +static void pqueuex_resize(PQueue *p, int N){ + p->a = fossil_realloc(p->a, sizeof(p->a[0])*N); p->sz = N; } /* ** Insert element e into the queue. */ -void pqueue_insert(PQueue *p, int e, double v){ +void pqueuex_insert(PQueue *p, int e, double v, void *pData){ int i, j; if( p->cnt+1>p->sz ){ - pqueue_resize(p, p->cnt+5); + pqueuex_resize(p, p->cnt+5); } for(i=0; icnt; i++){ if( p->a[i].value>v ){ for(j=p->cnt; j>i; j--){ p->a[j] = p->a[j-1]; @@ -83,26 +88,29 @@ } break; } } p->a[i].id = e; + p->a[i].p = pData; p->a[i].value = v; p->cnt++; } /* ** Extract the first element from the queue (the element with ** the smallest value) and return its ID. Return 0 if the queue ** is empty. */ -int pqueue_extract(PQueue *p){ +int pqueuex_extract(PQueue *p, void **pp){ int e, i; if( p->cnt==0 ){ + if( pp ) *pp = 0; return 0; } e = p->a[0].id; + if( pp ) *pp = p->a[0].p; for(i=0; icnt-1; i++){ p->a[i] = p->a[i+1]; } p->cnt--; return e; } Index: src/printf.c ================================================================== --- src/printf.c +++ src/printf.c @@ -13,22 +13,73 @@ ** drh@hwaci.com ** http://www.hwaci.com/drh/ ** ******************************************************************************* ** -** An implementation of printf() with extra conversion fields. +** This file contains implementions of routines for formatting output +** (ex: mprintf()) and for output to the console. */ #include "config.h" #include "printf.h" +#if defined(_WIN32) +# include +# include +#endif +#include + +/* Two custom conversions are used to show a prefix of artifact hashes: +** +** %!S Prefix of a length appropriate for URLs +** %S Prefix of a length appropriate for human display +** +** The following macros help determine those lengths. FOSSIL_HASH_DIGITS +** is the default number of digits to display to humans. This value can +** be overridden using the hash-digits setting. FOSSIL_HASH_DIGITS_URL +** is the minimum number of digits to be used in URLs. The number used +** will always be at least 6 more than the number used for human output, +** or HNAME_MAX, whichever is least. +*/ +#ifndef FOSSIL_HASH_DIGITS +# define FOSSIL_HASH_DIGITS 10 /* For %S (human display) */ +#endif +#ifndef FOSSIL_HASH_DIGITS_URL +# define FOSSIL_HASH_DIGITS_URL 16 /* For %!S (embedded in URLs) */ +#endif + +/* +** Return the number of artifact hash digits to display. The number is for +** human output if the bForUrl is false and is destined for a URL if +** bForUrl is false. +*/ +int hash_digits(int bForUrl){ + static int nDigitHuman = 0; + static int nDigitUrl = 0; + if( nDigitHuman==0 ){ + nDigitHuman = db_get_int("hash-digits", FOSSIL_HASH_DIGITS); + if( nDigitHuman < 6 ) nDigitHuman = 6; + if( nDigitHuman > HNAME_MAX ) nDigitHuman = HNAME_MAX; + nDigitUrl = nDigitHuman + 6; + if( nDigitUrl < FOSSIL_HASH_DIGITS_URL ) nDigitUrl = FOSSIL_HASH_DIGITS_URL; + if( nDigitUrl > HNAME_MAX ) nDigitUrl = HNAME_MAX; + } + return bForUrl ? nDigitUrl : nDigitHuman; +} + +/* +** Return the number of characters in a %S output. +*/ +int length_of_S_display(void){ + return hash_digits(0); +} /* ** Conversion types fall into various categories as defined by the ** following enumeration. */ #define etRADIX 1 /* Integer types. %d, %x, %o, and so forth */ #define etFLOAT 2 /* Floating point. %f */ -#define etEXP 3 /* Exponentional notation. %e and %E */ +#define etEXP 3 /* Exponential notation. %e and %E */ #define etGENERIC 4 /* Floating or exponential, depending on exponent. %g */ #define etSIZE 5 /* Return number of characters processed so far. %n */ #define etSTRING 6 /* Strings. %s */ #define etDYNSTRING 7 /* Dynamically allocated strings. %z */ #define etPERCENT 8 /* Percent symbol. %% */ @@ -38,19 +89,26 @@ #define etBLOB 11 /* Blob objects. %b */ #define etBLOBSQL 12 /* Blob objects quoted for SQL. %B */ #define etSQLESCAPE 13 /* Strings with '\'' doubled. %q */ #define etSQLESCAPE2 14 /* Strings with '\'' doubled and enclosed in '', NULL pointers replaced by SQL NULL. %Q */ -#define etPOINTER 15 /* The %p conversion */ -#define etHTMLIZE 16 /* Make text safe for HTML */ -#define etHTTPIZE 17 /* Make text safe for HTTP. "/" encoded as %2f */ -#define etURLIZE 18 /* Make text safe for HTTP. "/" not encoded */ -#define etFOSSILIZE 19 /* The fossil header encoding format. */ -#define etPATH 20 /* Path type */ -#define etWIKISTR 21 /* Wiki text rendered from a char* */ -#define etWIKIBLOB 22 /* Wiki text rendered from a Blob* */ -#define etSTRINGID 23 /* String with length limit for a UUID prefix */ +#define etSQLESCAPE3 15 /* Double '"' characters within an indentifier. %w */ +#define etPOINTER 16 /* The %p conversion */ +#define etHTMLIZE 17 /* Make text safe for HTML */ +#define etHTTPIZE 18 /* Make text safe for HTTP. "/" encoded as %2f */ +#define etURLIZE 19 /* Make text safe for HTTP. "/" not encoded */ +#define etFOSSILIZE 20 /* The fossil header encoding format. */ +#define etPATH 21 /* Path type */ +#define etWIKISTR 22 /* Timeline comment text rendered from a char*: %W */ +#define etSTRINGID 23 /* String with length limit for a hash prefix: %S */ +#define etROOT 24 /* String value of g.zTop: %R */ +#define etJSONSTR 25 /* String encoded as a JSON string literal: %j + Use %!j to include double-quotes around it. */ +#define etSHELLESC 26 /* Escape a filename for use in a shell command: %$ + See blob_append_escaped_arg() for details + "%$" -> adds "./" prefix if necessary. + "%!$" -> omits the "./" prefix. */ /* ** An "etByte" is an 8-bit unsigned value. */ @@ -78,29 +136,35 @@ /* ** The following table is searched linearly, so it is good to put the ** most frequently used conversion types first. +** +** NB: When modifying this table is it vital that you also update the fmtchr[] +** variable to match!!! */ static const char aDigits[] = "0123456789ABCDEF0123456789abcdef"; static const char aPrefix[] = "-x0\000X0"; +static const char fmtchr[] = "dsgzqQbBWhRtTwFSjcouxXfeEGin%p/$"; static const et_info fmtinfo[] = { { 'd', 10, 1, etRADIX, 0, 0 }, { 's', 0, 4, etSTRING, 0, 0 }, { 'g', 0, 1, etGENERIC, 30, 0 }, { 'z', 0, 6, etDYNSTRING, 0, 0 }, { 'q', 0, 4, etSQLESCAPE, 0, 0 }, { 'Q', 0, 4, etSQLESCAPE2, 0, 0 }, { 'b', 0, 2, etBLOB, 0, 0 }, { 'B', 0, 2, etBLOBSQL, 0, 0 }, - { 'w', 0, 2, etWIKISTR, 0, 0 }, - { 'W', 0, 2, etWIKIBLOB, 0, 0 }, + { 'W', 0, 2, etWIKISTR, 0, 0 }, { 'h', 0, 4, etHTMLIZE, 0, 0 }, + { 'R', 0, 0, etROOT, 0, 0 }, { 't', 0, 4, etHTTPIZE, 0, 0 }, /* "/" -> "%2F" */ { 'T', 0, 4, etURLIZE, 0, 0 }, /* "/" unchanged */ + { 'w', 0, 4, etSQLESCAPE3, 0, 0 }, { 'F', 0, 4, etFOSSILIZE, 0, 0 }, { 'S', 0, 4, etSTRINGID, 0, 0 }, + { 'j', 0, 0, etJSONSTR, 0, 0 }, { 'c', 0, 0, etCHARX, 0, 0 }, { 'o', 8, 0, etRADIX, 0, 2 }, { 'u', 10, 0, etRADIX, 0, 0 }, { 'x', 16, 0, etRADIX, 16, 1 }, { 'X', 16, 0, etRADIX, 0, 4 }, @@ -111,12 +175,27 @@ { 'i', 10, 1, etRADIX, 0, 0 }, { 'n', 0, 0, etSIZE, 0, 0 }, { '%', 0, 0, etPERCENT, 0, 0 }, { 'p', 16, 0, etPOINTER, 0, 1 }, { '/', 0, 0, etPATH, 0, 0 }, + { '$', 0, 0, etSHELLESC, 0, 0 }, + { etERROR, 0,0,0,0,0} /* Must be last */ }; -#define etNINFO (sizeof(fmtinfo)/sizeof(fmtinfo[0])) +#define etNINFO count(fmtinfo) + +/* +** Verify that the fmtchr[] and fmtinfo[] arrays are in agreement. +** +** This routine is a defense against programming errors. +*/ +void fossil_printf_selfcheck(void){ + int i; + for(i=0; fmtchr[i]; i++){ + assert( fmtchr[i]==fmtinfo[i].fmttype ); + } +} + /* ** "*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. @@ -149,30 +228,55 @@ ** 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(). */ +#if _XOPEN_SOURCE >= 700 || _POSIX_C_SOURCE >= 200809L +# define StrNLen32(Z,N) (int)strnlen(Z,N) +#else static int StrNLen32(const char *z, int N){ int n = 0; while( (N-- != 0) && *(z++)!=0 ){ n++; } return n; } +#endif + +/* +** Return an appropriate set of flags for wiki_convert() for displaying +** comments on a timeline. These flag settings are determined by +** configuration parameters. +** +** The altForm2 argument is true for "%!W" (with the "!" alternate-form-2 +** flags) and is false for plain "%W". The ! indicates that the text is +** to be rendered on a form rather than the timeline and that block markup +** is acceptable even if the "timeline-block-markup" setting is false. +*/ +static int wiki_convert_flags(int altForm2){ + static int wikiFlags = 0; + if( wikiFlags==0 ){ + if( altForm2 || db_get_boolean("timeline-block-markup", 0) ){ + wikiFlags = WIKI_INLINE | WIKI_NOBADLINKS; + }else{ + wikiFlags = WIKI_INLINE | WIKI_NOBLOCK | WIKI_NOBADLINKS; + } + if( db_get_boolean("timeline-plaintext", 0) ){ + wikiFlags |= WIKI_LINKSONLY; + } + if( db_get_boolean("timeline-hard-newlines", 0) ){ + wikiFlags |= WIKI_NEWLINE; + } + } + return wikiFlags; +} + /* ** The root program. All variations call this core. ** ** INPUTS: -** func This is a pointer to a function taking three arguments -** 1. A pointer to anything. Same as the "arg" parameter. -** 2. A pointer to the list of characters to be output -** (Note, this list is NOT null terminated.) -** 3. An integer number of characters to be output. -** (Note: This number might be zero.) -** -** arg This is the pointer to anything which will be passed as the -** first argument to "func". Use it for whatever you like. +** pBlob This is the blob where the output will be built. ** ** fmt This is the format string, as in the usual print. ** ** ap This is a pointer to a list of arguments. Same as in ** vfprint. @@ -204,10 +308,11 @@ 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 */ u64 longvalue; /* Value for integer types */ long double realvalue; /* Value for real types */ const et_info *infop; /* Pointer to the appropriate info structure */ char buf[etBUFSIZE]; /* Conversion buffer */ char prefix; /* Prefix character. "+" or "-" or " " or '\0'. */ @@ -221,31 +326,33 @@ 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 */ + char *zFmtLookup; count = length = 0; bufpt = 0; for(; (c=(*fmt))!=0; ++fmt){ if( c!='%' ){ - int amt; bufpt = (char *)fmt; - amt = 1; - while( (c=(*++fmt))!='%' && c!=0 ) amt++; - blob_append(pBlob,bufpt,amt); - count += amt; - if( c==0 ) break; +#if HAVE_STRCHRNUL + fmt = strchrnul(fmt, '%'); +#else + do{ fmt++; }while( *fmt && *fmt != '%' ); +#endif + blob_append(pBlob, bufpt, (int)(fmt - bufpt)); + if( *fmt==0 ) break; } if( (c=(*++fmt))==0 ){ errorflag = 1; blob_append(pBlob,"%",1); count++; break; } /* Find out what flags are present */ - flag_leftjustify = flag_plussign = flag_blanksign = + flag_leftjustify = flag_plussign = flag_blanksign = cThousand = flag_alternateform = flag_altform2 = flag_zeropad = 0; done = 0; do{ switch( c ){ case '-': flag_leftjustify = 1; break; @@ -252,10 +359,11 @@ 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; @@ -304,18 +412,17 @@ } }else{ flag_long = flag_longlong = 0; } /* Fetch the info entry for the field */ - infop = 0; - xtype = etERROR; - for(idx=0; idxtype; - break; - } + zFmtLookup = strchr(fmtchr,c); + if( zFmtLookup ){ + infop = &fmtinfo[zFmtLookup-fmtchr]; + xtype = infop->type; + }else{ + infop = 0; + xtype = etERROR; } zExtra = 0; /* Limit the precision to prevent overflowing buf[] during conversion */ if( precision>etBUFSIZE-40 && (infop->flags & FLAG_STRING)==0 ){ @@ -383,12 +490,27 @@ *(--bufpt) = cset[longvalue%base]; longvalue = longvalue/base; }while( longvalue>0 ); } length = &buf[etBUFSIZE-1]-bufpt; - for(idx=precision-length; idx>0; idx--){ + 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; @@ -545,11 +667,11 @@ buf[0] = '%'; bufpt = buf; length = 1; break; case etCHARX: - c = buf[0] = (xtype==etCHARX ? va_arg(ap,int) : *++fmt); + c = buf[0] = va_arg(ap,int); if( precision>=0 ){ for(idx=1; idx=0 && precision=0 && limit etBUFSIZE ){ - bufpt = zExtra = malloc( n + cnt + 2 ); + bufpt = zExtra = fossil_malloc( n + cnt + 2 ); }else{ bufpt = buf; } bufpt[0] = '\''; for(i=0, j=1; ietBUFSIZE ){ - bufpt = zExtra = malloc( n ); - if( bufpt==0 ) return -1; + bufpt = zExtra = fossil_malloc( n ); }else{ bufpt = buf; } j = 0; - if( needQuote ) bufpt[j++] = '\''; + if( needQuote ) bufpt[j++] = q; for(i=0; i=0 && precision=0 && precision=0 && precision0 ) blob_append(pBlob,spaces,nspace); } } if( zExtra ){ - free(zExtra); + fossil_free(zExtra); } }/* End for loop over the format string */ return errorflag ? -1 : count; } /* End of function */ /* -** Print into memory obtained from malloc(). +** Print into memory obtained from fossil_malloc(). */ char *mprintf(const char *zFormat, ...){ va_list ap; char *z; va_start(ap,zFormat); @@ -798,10 +939,68 @@ void fossil_error_reset(void){ free(g.zErrMsg); g.zErrMsg = 0; g.iErrPriority = 0; } + +/* True if the last character standard output cursor is setting at +** the beginning of a blank link. False if a \r has been to move the +** cursor to the beginning of the line or if not at the beginning of +** a line. +** was a \n +*/ +static int stdoutAtBOL = 1; + +/* +** Write to standard output or standard error. +** +** On windows, transform the output into the current terminal encoding +** if the output is going to the screen. If output is redirected into +** a file, no translation occurs. Switch output mode to binary to +** properly process line-endings, make sure to switch the mode back to +** text when done. +** No translation ever occurs on unix. +*/ +void fossil_puts(const char *z, int toStdErr){ + FILE* out = (toStdErr ? stderr : stdout); + int n = (int)strlen(z); + if( n==0 ) return; + assert( toStdErr==0 || toStdErr==1 ); + if( toStdErr==0 ) stdoutAtBOL = (z[n-1]=='\n'); +#if defined(_WIN32) + if( fossil_utf8_to_console(z, n, toStdErr) >= 0 ){ + return; + } + fflush(out); + _setmode(_fileno(out), _O_BINARY); +#endif + fwrite(z, 1, n, out); +#if defined(_WIN32) + fflush(out); + _setmode(_fileno(out), _O_TEXT); +#endif +} + +/* +** Force the standard output cursor to move to the beginning +** of a line, if it is not there already. +*/ +int fossil_force_newline(void){ + if( g.cgiOutput==0 && stdoutAtBOL==0 ){ + fossil_puts("\n", 0); + return 1; + } + return 0; +} + +/* +** Indicate that the cursor has moved to the start of a line by means +** other than writing to standard output. +*/ +void fossil_new_line_started(void){ + stdoutAtBOL = 1; +} /* ** Write output for user consumption. If g.cgiOutput is enabled, then ** send the output as part of the CGI reply. If g.cgiOutput is false, ** then write on standard output. @@ -810,8 +1009,262 @@ va_list ap; va_start(ap, zFormat); if( g.cgiOutput ){ cgi_vprintf(zFormat, ap); }else{ - vprintf(zFormat, ap); + Blob b = empty_blob; + vxprintf(&b, zFormat, ap); + fossil_puts(blob_str(&b), 0); + blob_reset(&b); + } + va_end(ap); +} +void fossil_vprint(const char *zFormat, va_list ap){ + if( g.cgiOutput ){ + cgi_vprintf(zFormat, ap); + }else{ + Blob b = empty_blob; + vxprintf(&b, zFormat, ap); + fossil_puts(blob_str(&b), 0); + blob_reset(&b); + } +} + +/* +** Print a trace message on standard error. +*/ +void fossil_trace(const char *zFormat, ...){ + va_list ap; + Blob b; + va_start(ap, zFormat); + b = empty_blob; + vxprintf(&b, zFormat, ap); + fossil_puts(blob_str(&b), 1); + blob_reset(&b); + va_end(ap); +} + +/* +** Write a message to the error log, if the error log filename is +** defined. +*/ +void fossil_errorlog(const char *zFormat, ...){ + struct tm *pNow; + time_t now; + FILE *out; + const char *z; + int i; + va_list ap; + static const char *const azEnv[] = { "HTTP_HOST", "HTTP_REFERER", + "HTTP_USER_AGENT", + "PATH_INFO", "QUERY_STRING", "REMOTE_ADDR", "REQUEST_METHOD", + "REQUEST_URI", "SCRIPT_NAME" }; + if( g.zErrlog==0 ) return; + if( g.zErrlog[0]=='-' && g.zErrlog[1]==0 ){ + out = stderr; + }else{ + out = fossil_fopen(g.zErrlog, "a"); + if( out==0 ) return; + } + now = time(0); + pNow = gmtime(&now); + fprintf(out, "------------- %04d-%02d-%02d %02d:%02d:%02d UTC ------------\n", + pNow->tm_year+1900, pNow->tm_mon+1, pNow->tm_mday, + pNow->tm_hour, pNow->tm_min, pNow->tm_sec); + va_start(ap, zFormat); + vfprintf(out, zFormat, ap); + fprintf(out, "\n"); + va_end(ap); + for(i=0; i%h(z)

      + cgi_set_status(400, "Bad Request"); + style_finish_page(); + cgi_reply(); + }else if( !g.fQuiet ){ + fossil_force_newline(); + fossil_trace("%s\n", z); + } + return rc; +} + +/* +** Print an error message, rollback all databases, and quit. These +** routines never return and produce a non-zero process exit status. +** +** The main difference between fossil_fatal() and fossil_panic() is that +** fossil_panic() makes an entry in the error log whereas fossil_fatal() +** does not. On POSIX platforms, if there is not an error log, then both +** routines work similarly with respect to user-visible effects. Hence, +** the routines are interchangable for commands and only act differently +** when processing web pages. On the Windows platform, fossil_panic() +** also displays a pop-up stating that an error has occured and allowing +** just-in-time debugging to commence. On all platforms, fossil_panic() +** ends execution with a SIGABRT signal, bypassing atexit processing. +** This signal can also produce a core dump on POSIX platforms. +** +** Use fossil_fatal() for malformed inputs that should be reported back +** to the user, but which do not represent a configuration problem or bug. +** +** Use fossil_panic() for any kind of error that should be brought to the +** attention of the system administrator or Fossil developers. It should +** be avoided for ordinary usage, parameter, OOM and I/O errors. +*/ +NORETURN void fossil_panic(const char *zFormat, ...){ + va_list ap; + int rc = 1; + char z[1000]; + static int once = 0; + + if( once ) exit(1); + once = 1; + mainInFatalError = 1; + /* db_force_rollback(); */ + va_start(ap, zFormat); + sqlite3_vsnprintf(sizeof(z),z,zFormat, ap); + va_end(ap); + if( g.fAnyTrace ){ + fprintf(stderr, "/***** panic on %d *****/\n", getpid()); + } + fossil_errorlog("panic: %s", z); + rc = fossil_print_error(rc, z); + abort(); + exit(rc); +} +NORETURN void fossil_fatal(const char *zFormat, ...){ + static int once = 0; + va_list ap; + char *z; + int rc = 1; + if( once ) exit(1); + once = 1; + mainInFatalError = 1; + va_start(ap, zFormat); + z = vmprintf(zFormat, ap); + va_end(ap); + rc = fossil_print_error(rc, z); + fossil_free(z); + db_force_rollback(); + fossil_exit(rc); +} + +/* This routine works like fossil_fatal() except that if called +** recursively, the recursive call is a no-op. +** +** Use this in places where an error might occur while doing +** fatal error shutdown processing. Unlike fossil_panic() and +** fossil_fatal() which never return, this routine might return if +** the fatal error handing is already in process. The caller must +** be prepared for this routine to return. +*/ +void fossil_fatal_recursive(const char *zFormat, ...){ + char *z; + va_list ap; + int rc = 1; + if( mainInFatalError ) return; + mainInFatalError = 1; + va_start(ap, zFormat); + z = vmprintf(zFormat, ap); + va_end(ap); + rc = fossil_print_error(rc, z); + db_force_rollback(); + fossil_exit(rc); +} + + +/* Print a warning message. +** +** Unlike fossil_fatal() and fossil_panic(), this routine does return +** and processing attempts to continue. A message is written to the +** error log, however, so this routine should only be used for situations +** that require administrator or developer attention. Minor problems +** in user inputs should not use this routine. +*/ +void fossil_warning(const char *zFormat, ...){ + char *z; + va_list ap; + va_start(ap, zFormat); + z = vmprintf(zFormat, ap); + va_end(ap); + fossil_errorlog("warning: %s", z); +#ifdef FOSSIL_ENABLE_JSON + if( g.json.isJsonMode!=0 ){ + /* + ** Avoid calling into the JSON support subsystem if it + ** has not yet been initialized, e.g. early SQLite log + ** messages, etc. + */ + if( !json_is_bootstrapped_early() ) json_bootstrap_early(); + json_warn( FSL_JSON_W_UNKNOWN, "%s", z ); + }else +#endif + { + if( g.cgiOutput==1 ){ + etag_cancel(); + cgi_printf("

      \n%h\n

      \n", z); + }else{ + fossil_force_newline(); + fossil_trace("%s\n", z); + } } + free(z); +} + +/* +** Turn off any LF to CRLF translation on the stream given as an +** argument. This is a no-op on unix but is necessary on windows. +*/ +void fossil_binary_mode(FILE *p){ +#if defined(_WIN32) + _setmode(_fileno(p), _O_BINARY); +#endif +#ifdef __EMX__ /* OS/2 */ + setmode(fileno(p), O_BINARY); +#endif } ADDED src/publish.c Index: src/publish.c ================================================================== --- /dev/null +++ src/publish.c @@ -0,0 +1,118 @@ +/* +** 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 file contains code used to implement the "publish" and +** "unpublished" commands. +*/ +#include "config.h" +#include "publish.h" +#include + +/* +** COMMAND: unpublished* +** +** Usage: %fossil unpublished ?OPTIONS? +** +** Show a list of unpublished or "private" artifacts. Unpublished artifacts +** will never push and hence will not be shared with collaborators. +** +** By default, this command only shows unpublished check-ins. To show +** all unpublished artifacts, use the --all command-line option. +** +** OPTIONS: +** --all Show all artifacts, not just check-ins +*/ +void unpublished_cmd(void){ + int bAll = find_option("all",0,0)!=0; + + db_find_and_open_repository(0,0); + verify_all_options(); + if( bAll ){ + describe_artifacts_to_stdout("IN private", 0); + }else{ + describe_artifacts_to_stdout( + "IN (SELECT rid FROM private CROSS JOIN event" + " WHERE private.rid=event.objid" + " AND event.type='ci')", 0); + } +} + +/* +** COMMAND: publish* +** +** Usage: %fossil publish ?--only? TAGS... +** +** Cause artifacts identified by TAGS... to be published (made non-private). +** This can be used (for example) to convert a private branch into a public +** branch, or to publish a bundle that was imported privately. +** +** If any of TAGS names a branch, then all check-ins on the most recent +** instance of that branch are included, not just the most recent check-in. +** +** If any of TAGS name check-ins then all files and tags associated with +** those check-ins are also published automatically. Except if the --only +** option is used, then only the specific artifacts identified by TAGS +** are published. +** +** If a TAG is already public, this command is a harmless no-op. +*/ +void publish_cmd(void){ + int bOnly = find_option("only",0,0)!=0; + int bTest = find_option("test",0,0)!=0; /* Undocumented --test option */ + int bExclusive = find_option("exclusive",0,0)!=0; /* undocumented */ + int i; + + db_find_and_open_repository(0,0); + verify_all_options(); + if( g.argc<3 ) usage("?--only? TAGS..."); + db_begin_transaction(); + db_multi_exec("CREATE TEMP TABLE ok(rid INTEGER PRIMARY KEY);"); + for(i=2; i0 AND value=%Q", + rid,TAG_BRANCH,g.argv[i]) ){ + rid = start_of_branch(rid, 1); + compute_descendants(rid, 1000000000); + }else{ + db_multi_exec("INSERT OR IGNORE INTO ok VALUES(%d)", rid); + } + } + if( !bOnly ){ + find_checkin_associates("ok", bExclusive); + } + if( bTest ){ + /* If the --test option is used, then do not actually publish any + ** artifacts. Instead, just list the artifact information on standard + ** output. The --test option is useful for verifying correct operation + ** of the logic that figures out which artifacts to publish, such as + ** the find_checkin_associates() routine + */ + describe_artifacts_to_stdout("IN ok", 0); + }else{ + /* Standard behavior is simply to remove the published documents from + ** the PRIVATE table */ + db_multi_exec( + "DELETE FROM ok WHERE rid NOT IN private;" + "DELETE FROM private WHERE rid IN ok;" + "INSERT OR IGNORE INTO unsent SELECT rid FROM ok;" + "INSERT OR IGNORE INTO unclustered SELECT rid FROM ok;" + ); + } + db_end_transaction(0); +} ADDED src/purge.c Index: src/purge.c ================================================================== --- /dev/null +++ src/purge.c @@ -0,0 +1,662 @@ +/* +** 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 file contains code used to implement the "purge" command and +** related functionality for removing check-ins from a repository. It also +** manages the graveyard of purged content. +*/ +#include "config.h" +#include "purge.h" +#include + +/* +** SQL code used to initialize the schema of the graveyard. +** +** The purgeevent table contains one entry for each purge event. For each +** purge event, multiple artifacts might have been removed. Each removed +** artifact is stored as an entry in the purgeitem table. +** +** The purgeevent and purgeitem tables are not synced, even by the +** "fossil config" command. They exist only as a backup in case of a +** mistaken purge or for content recovery in case there is a bug in the +** purge command. +*/ +static const char zPurgeInit[] = +@ CREATE TABLE IF NOT EXISTS "%w".purgeevent( +@ peid INTEGER PRIMARY KEY, -- Unique ID for the purge event +@ ctime DATETIME, -- When purge occurred. Seconds since 1970. +@ pnotes TEXT -- Human-readable notes about the purge event +@ ); +@ CREATE TABLE IF NOT EXISTS "%w".purgeitem( +@ piid INTEGER PRIMARY KEY, -- ID for the purge item +@ peid INTEGER REFERENCES purgeevent ON DELETE CASCADE, -- Purge event +@ orid INTEGER, -- Original RID before purged +@ uuid TEXT NOT NULL, -- hash of the purged artifact +@ srcid INTEGER, -- Basis purgeitem for delta compression +@ isPrivate BOOLEAN, -- True if artifact was originally private +@ sz INT NOT NULL, -- Uncompressed size of the purged artifact +@ desc TEXT, -- Brief description of this artifact +@ data BLOB -- Compressed artifact content +@ ); +; + +/* +** Flags for the purge_artifact_list() function. +*/ +#if INTERFACE +#define PURGE_MOVETO_GRAVEYARD 0x0001 /* Move artifacts in graveyard */ +#define PURGE_EXPLAIN_ONLY 0x0002 /* Show what would have happened */ +#define PURGE_PRINT_SUMMARY 0x0004 /* Print a summary report at end */ +#endif + +/* +** This routine purges multiple artifacts from the repository, transfering +** those artifacts into the PURGEITEM table. +** +** Prior to invoking this routine, the caller must create a (TEMP) table +** named zTab that contains the RID of every artifact to be purged. +** +** This routine does the following: +** +** (1) Create the purgeevent and purgeitem tables, if required +** (2) Create a new purgeevent +** (3) Make sure no DELTA table entries depend on purged artifacts +** (4) Create new purgeitem entries for each purged artifact +** (5) Remove purged artifacts from the BLOB table +** (6) Remove references to purged artifacts in the following tables: +** (a) EVENT +** (b) PRIVATE +** (c) MLINK +** (d) PLINK +** (e) LEAF +** (f) UNCLUSTERED +** (g) UNSENT +** (h) BACKLINK +** (i) ATTACHMENT +** (j) TICKETCHNG +** (7) If any ticket artifacts were removed (6j) then rebuild the +** corresponding ticket entries. Possibly remove entries from +** the ticket table. +** +** Steps 1-4 (saving the purged artifacts into the graveyard) are only +** undertaken if the moveToGraveyard flag is true. +*/ +int purge_artifact_list( + const char *zTab, /* TEMP table containing list of RIDS to be purged */ + const char *zNote, /* Text of the purgeevent.pnotes field */ + unsigned purgeFlags /* zero or more PURGE_* flags */ +){ + int peid = 0; /* New purgeevent ID */ + Stmt q; /* General-use prepared statement */ + char *z; + + assert( g.repositoryOpen ); /* Main database must already be open */ + db_begin_transaction(); + z = sqlite3_mprintf("IN \"%w\"", zTab); + describe_artifacts(z); + sqlite3_free(z); + describe_artifacts_to_stdout(0, 0); + + /* The explain-only flags causes this routine to list the artifacts + ** that would have been purged but to not actually make any changes + ** to the repository. + */ + if( purgeFlags & PURGE_EXPLAIN_ONLY ){ + db_end_transaction(0); + return 0; + } + + /* Make sure we are not removing a manifest that is the baseline of some + ** manifest that is being left behind. This step is not strictly necessary. + ** is is just a safety check. */ + if( purge_baseline_out_from_under_delta(zTab) ){ + fossil_panic("attempt to purge a baseline manifest without also purging " + "all of its deltas"); + } + + /* Make sure that no delta that is left behind requires a purged artifact + ** as its basis. If such artifacts exist, go ahead and undelta them now. + */ + db_prepare(&q, "SELECT rid FROM delta WHERE srcid IN \"%w\"" + " AND rid NOT IN \"%w\"", zTab, zTab); + while( db_step(&q)==SQLITE_ROW ){ + int rid = db_column_int(&q, 0); + content_undelta(rid); + verify_before_commit(rid); + } + db_finalize(&q); + + /* Construct the graveyard and copy the artifacts to be purged into the + ** graveyard */ + if( purgeFlags & PURGE_MOVETO_GRAVEYARD ){ + db_multi_exec(zPurgeInit /*works-like:"%w%w"*/, + "repository", "repository"); + db_multi_exec( + "INSERT INTO purgeevent(ctime,pnotes) VALUES(now(),%Q)", zNote + ); + peid = db_last_insert_rowid(); + db_prepare(&q, "SELECT rid FROM delta WHERE rid IN \"%w\"" + " AND srcid NOT IN \"%w\"", zTab, zTab); + while( db_step(&q)==SQLITE_ROW ){ + int rid = db_column_int(&q, 0); + content_undelta(rid); + } + db_finalize(&q); + db_multi_exec( + "INSERT INTO purgeitem(peid,orid,uuid,sz,isPrivate,desc,data)" + " SELECT %d, rid, uuid, size," + " EXISTS(SELECT 1 FROM private WHERE private.rid=blob.rid)," + " (SELECT summary FROM description WHERE rid=blob.rid)," + " content" + " FROM blob WHERE rid IN \"%w\"", + peid, zTab + ); + db_multi_exec( + "UPDATE purgeitem" + " SET srcid=(SELECT piid FROM purgeitem px, delta" + " WHERE px.orid=delta.srcid" + " AND delta.rid=purgeitem.orid)" + " WHERE peid=%d", + peid + ); + } + + /* Remove the artifacts being purged. Also remove all references to those + ** artifacts from the secondary tables. */ + db_multi_exec("DELETE FROM blob WHERE rid IN \"%w\"", zTab); + db_multi_exec("DELETE FROM delta WHERE rid IN \"%w\"", zTab); + db_multi_exec("DELETE FROM delta WHERE srcid IN \"%w\"", zTab); + db_multi_exec("DELETE FROM event WHERE objid IN \"%w\"", zTab); + db_multi_exec("DELETE FROM private WHERE rid IN \"%w\"", zTab); + db_multi_exec("DELETE FROM mlink WHERE mid IN \"%w\"", zTab); + db_multi_exec("DELETE FROM plink WHERE pid IN \"%w\"", zTab); + db_multi_exec("DELETE FROM plink WHERE cid IN \"%w\"", zTab); + db_multi_exec("DELETE FROM leaf WHERE rid IN \"%w\"", zTab); + db_multi_exec("DELETE FROM phantom WHERE rid IN \"%w\"", zTab); + db_multi_exec("DELETE FROM unclustered WHERE rid IN \"%w\"", zTab); + db_multi_exec("DELETE FROM unsent WHERE rid IN \"%w\"", zTab); + db_multi_exec("DELETE FROM tagxref" + " WHERE rid IN \"%w\"" + " OR srcid IN \"%w\"" + " OR origid IN \"%w\"", zTab, zTab, zTab); + db_multi_exec("DELETE FROM backlink WHERE srctype=0 AND srcid IN \"%w\"", + zTab); + db_multi_exec( + "CREATE TEMP TABLE \"%w_tickets\" AS" + " SELECT DISTINCT tkt_uuid FROM ticket WHERE tkt_id IN" + " (SELECT tkt_id FROM ticketchng WHERE tkt_rid IN \"%w\")", + zTab, zTab); + db_multi_exec("DELETE FROM ticketchng WHERE tkt_rid IN \"%w\"", zTab); + db_prepare(&q, "SELECT tkt_uuid FROM \"%w_tickets\"", zTab); + while( db_step(&q)==SQLITE_ROW ){ + ticket_rebuild_entry(db_column_text(&q, 0)); + } + db_finalize(&q); + /* db_multi_exec("DROP TABLE \"%w_tickets\"", zTab); */ + + /* Mission accomplished */ + db_end_transaction(0); + + if( purgeFlags & PURGE_PRINT_SUMMARY ){ + fossil_print("%d artifacts purged\n", + db_int(0, "SELECT count(*) FROM \"%w\";", zTab)); + fossil_print("undoable using \"%s purge undo %d\".\n", + g.nameOfExe, peid); + } + return peid; +} + +/* +** The TEMP table named zTab contains RIDs for a set of check-ins. +** +** Check to see if any check-in in zTab is a baseline manifest for some +** delta manifest that is not in zTab. Return true if zTab contains a +** baseline for a delta that is not in zTab. +** +** This is a database integrity preservation check. The check-ins in zTab +** are about to be deleted or otherwise made inaccessible. This routine +** is checking to ensure that purging the check-ins in zTab will not delete +** a baseline manifest out from under a delta. +*/ +int purge_baseline_out_from_under_delta(const char *zTab){ + if( !db_table_has_column("repository","plink","baseid") ){ + /* Skip this check if the current database is an older schema that + ** does not contain the PLINK.BASEID field. */ + return 0; + }else{ + return db_int(0, + "SELECT 1 FROM plink WHERE baseid IN \"%w\" AND cid NOT IN \"%w\"", + zTab, zTab); + } +} + + +/* +** The TEMP table named zTab contains the RIDs for a set of check-in +** artifacts. Expand this set (by adding new entries to zTab) to include +** all other artifacts that are used by the check-ins in +** the original list. +** +** If the bExclusive flag is true, then the set is only expanded by +** artifacts that are used exclusively by the check-ins in the set. +** When bExclusive is false, then all artifacts used by the check-ins +** are added even if those artifacts are also used by other check-ins +** not in the set. +** +** The "fossil publish" command with the (undocumented) --test and +** --exclusive options can be used for interactiving testing of this +** function. +*/ +void find_checkin_associates(const char *zTab, int bExclusive){ + db_begin_transaction(); + + /* Compute the set of files that need to be added to zTab */ + db_multi_exec("CREATE TEMP TABLE \"%w_files\"(fid INTEGER PRIMARY KEY)",zTab); + db_multi_exec( + "INSERT OR IGNORE INTO \"%w_files\"(fid)" + " SELECT fid FROM mlink WHERE fid!=0 AND mid IN \"%w\"", + zTab, zTab + ); + if( bExclusive ){ + /* But take out all files that are referenced by check-ins not in zTab */ + db_multi_exec( + "DELETE FROM \"%w_files\"" + " WHERE fid IN (SELECT fid FROM mlink" + " WHERE fid IN \"%w_files\"" + " AND mid NOT IN \"%w\")", + zTab, zTab, zTab + ); + } + + /* Compute the set of tags that need to be added to zTag */ + db_multi_exec("CREATE TEMP TABLE \"%w_tags\"(tid INTEGER PRIMARY KEY)",zTab); + db_multi_exec( + "INSERT OR IGNORE INTO \"%w_tags\"(tid)" + " SELECT DISTINCT srcid FROM tagxref WHERE rid in \"%w\" AND srcid!=0", + zTab, zTab + ); + if( bExclusive ){ + /* But take out tags that references some check-ins in zTab and other + ** check-ins not in zTab. The current Fossil implementation never creates + ** such tags, so the following should usually be a no-op. But the file + ** format specification allows such tags, so we should check for them. + */ + db_multi_exec( + "DELETE FROM \"%w_tags\"" + " WHERE tid IN (SELECT srcid FROM tagxref" + " WHERE srcid IN \"%w_tags\"" + " AND rid NOT IN \"%w\")", + zTab, zTab, zTab + ); + } + + /* Transfer the extra artifacts into zTab */ + db_multi_exec( + "INSERT OR IGNORE INTO \"%w\" SELECT fid FROM \"%w_files\";" + "INSERT OR IGNORE INTO \"%w\" SELECT tid FROM \"%w_tags\";" + "DROP TABLE \"%w_files\";" + "DROP TABLE \"%w_tags\";", + zTab, zTab, zTab, zTab, zTab, zTab + ); + + db_end_transaction(0); +} + +/* +** Display the content of a single purge event. +*/ +static void purge_list_event_content(int peid){ + Stmt q; + sqlite3_int64 sz = 0; + db_prepare(&q, "SELECT piid, substr(uuid,1,16), srcid, isPrivate," + " length(data), desc" + " FROM purgeitem WHERE peid=%d", peid); + while( db_step(&q)==SQLITE_ROW ){ + fossil_print(" %5d %s %4s %c %10d %s\n", + db_column_int(&q,0), + db_column_text(&q,1), + db_column_text(&q,2), + db_column_int(&q,3) ? 'P' : ' ', + db_column_int(&q,4), + db_column_text(&q,5)); + sz += db_column_int(&q,4); + } + db_finalize(&q); + fossil_print("%.11c%16s%.8c%10lld\n", ' ', "Total:", ' ', sz); +} + +/* +** Extract the content for purgeitem number piid into a Blob. Return +** the number of errors. +*/ +static int purge_extract_item( + int piid, /* ID of the item to extract */ + Blob *pOut /* Write the content into this blob */ +){ + Stmt q; + int srcid; + Blob h1, x; + static Bag busy; + + db_prepare(&q, "SELECT uuid, srcid, data FROM purgeitem" + " WHERE piid=%d", piid); + if( db_step(&q)!=SQLITE_ROW ){ + db_finalize(&q); + fossil_fatal("missing purge-item %d", piid); + } + if( bag_find(&busy, piid) ) return 1; + srcid = db_column_int(&q, 1); + blob_zero(pOut); + blob_zero(&x); + db_column_blob(&q, 2, &x); + blob_uncompress(&x, pOut); + blob_reset(&x); + if( srcid>0 ){ + Blob baseline, out; + bag_insert(&busy, piid); + purge_extract_item(srcid, &baseline); + blob_zero(&out); + blob_delta_apply(&baseline, pOut, &out); + blob_reset(pOut); + *pOut = out; + blob_reset(&baseline); + } + bag_remove(&busy, piid); + blob_zero(&h1); + db_column_blob(&q, 0, &h1); + if( hname_verify_hash(pOut, blob_buffer(&h1), blob_size(&h1))==0 ){ + fossil_fatal("incorrect artifact hash on %b", &h1); + } + blob_reset(&h1); + db_finalize(&q); + return 0; +} + +/* +** There is a TEMP table ix(piid,srcid) containing a set of purgeitems +** that need to be transferred to the BLOB table. This routine does +** all items that have srcid=iSrc. The pBasis blob holds the content +** of the source document if iSrc>0. +*/ +static void purge_item_resurrect(int iSrc, Blob *pBasis){ + Stmt q; + static Bag busy; + assert( pBasis!=0 || iSrc==0 ); + if( iSrc>0 ){ + if( bag_find(&busy, iSrc) ){ + fossil_fatal("delta loop while uncompressing purged artifacts"); + } + bag_insert(&busy, iSrc); + } + db_prepare(&q, + "SELECT uuid, data, isPrivate, ix.piid" + " FROM ix, purgeitem" + " WHERE ix.srcid=%d" + " AND ix.piid=purgeitem.piid;", + iSrc + ); + while( db_step(&q)==SQLITE_ROW ){ + Blob h1, c1, c2; + int isPriv, rid; + blob_zero(&h1); + db_column_blob(&q, 0, &h1); + blob_zero(&c1); + db_column_blob(&q, 1, &c1); + blob_uncompress(&c1, &c1); + blob_zero(&c2); + if( pBasis ){ + blob_delta_apply(pBasis, &c1, &c2); + blob_reset(&c1); + }else{ + c2 = c1; + } + if( hname_verify_hash(&c2, blob_buffer(&h1), blob_size(&h1))==0 ){ + fossil_fatal("incorrect hash on %b", &h1); + } + isPriv = db_column_int(&q, 2); + rid = content_put_ex(&c2, blob_str(&h1), 0, 0, isPriv); + if( rid==0 ){ + fossil_fatal("%s", g.zErrMsg); + }else{ + if( !isPriv ) content_make_public(rid); + content_get(rid, &c1); + manifest_crosslink(rid, &c1, MC_NO_ERRORS); + } + purge_item_resurrect(db_column_int(&q,3), &c2); + blob_reset(&c2); + } + db_finalize(&q); + if( iSrc>0 ) bag_remove(&busy, iSrc); +} + +/* +** COMMAND: purge* +** +** The purge command removes content from a repository and stores that content +** in a "graveyard". The graveyard exists so that content can be recovered +** using the "fossil purge undo" command. The "fossil purge obliterate" +** command empties the graveyard, making the content unrecoverable. +** +** WARNING: This command can potentially destroy historical data and +** leave your repository in a goofy state. Know what you are doing! +** Make a backup of your repository before using this command! +** +** FURTHER WARNING: This command is a work-in-progress and may yet +** contain bugs. +** +** > fossil purge artifacts HASH... ?OPTIONS? +** +** Move arbitrary artifacts identified by the HASH list into the +** graveyard. +** +** > fossil purge cat HASH... +** +** Write the content of one or more artifacts in the graveyard onto +** standard output. +** +** > fossil purge checkins TAGS... ?OPTIONS? +** +** Move the check-ins or branches identified by TAGS and all of +** their descendants out of the repository and into the graveyard. +** If TAGS includes a branch name then it means all the check-ins +** on the most recent occurrence of that branch. +** +** > fossil purge files NAME ... ?OPTIONS? +** +** Move all instances of files called NAME into the graveyard. +** NAME should be the name of the file relative to the root of the +** repository. If NAME is a directory, then all files within that +** directory are moved. +** +** > fossil purge list|ls ?-l? +** +** Show the graveyard of prior purges. The -l option gives more +** detail in the output. +** +** > fossil purge obliterate ID... ?--force? +** +** Remove one or more purge events from the graveyard. Once a purge +** event is obliterated, it can no longer be undone. The --force +** option suppresses the confirmation prompt. +** +** > fossil purge tickets NAME ... ?OPTIONS? +** +** TBD... +** +** > fossil purge undo ID +** +** Restore the content previously removed by purge ID. +** +** > fossil purge wiki NAME ... ?OPTIONS? +** +** TBD... +** +** COMMON OPTIONS: +** +** --explain Make no changes, but show what would happen +** --dry-run An alias for --explain +*/ +void purge_cmd(void){ + int purgeFlags = PURGE_MOVETO_GRAVEYARD | PURGE_PRINT_SUMMARY; + const char *zSubcmd; + int n; + int i; + Stmt q; + + if( g.argc<3 ) usage("SUBCOMMAND ?ARGS?"); + zSubcmd = g.argv[2]; + db_find_and_open_repository(0,0); + n = (int)strlen(zSubcmd); + if( find_option("explain",0,0)!=0 || find_option("dry-run",0,0)!=0 ){ + purgeFlags |= PURGE_EXPLAIN_ONLY; + } + if( strncmp(zSubcmd, "artifacts", n)==0 ){ + verify_all_options(); + db_begin_transaction(); + db_multi_exec("CREATE TEMP TABLE ok(rid INTEGER PRIMARY KEY)"); + for(i=3; i - -/* -** Schema changes -*/ -static const char zSchemaUpdates[] = -@ -- Index on the delta table -@ -- -@ CREATE INDEX IF NOT EXISTS delta_i1 ON delta(srcid); -@ -@ -- 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 IF NOT EXISTS shun(uuid UNIQUE); -@ -@ -- Artifacts that should not be pushed are stored in the "private" -@ -- table. -@ -- -@ CREATE TABLE IF NOT EXISTS private(rid INTEGER PRIMARY KEY); -@ -@ -- An entry in this table describes a database query that generates a -@ -- table of tickets. -@ -- -@ CREATE TABLE IF NOT EXISTS reportfmt( -@ rn integer primary key, -- Report number -@ owner text, -- Owner of this report format (not used) -@ title text, -- Title of this report -@ 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 IF NOT EXISTS concealed( -@ hash TEXT PRIMARY KEY, -@ content TEXT -@ ); -; - -/* -** Variables used for progress information -*/ -static int totalSize; /* Total number of artifacts to process */ -static int processCnt; /* Number processed so far */ -static int ttyOutput; /* Do progress output */ -static Bag bagDone; /* Bag of records rebuilt */ +#include + +/* +** Update the schema as necessary +*/ +static void rebuild_update_schema(void){ + /* Verify that the PLINK table has a new column added by the + ** 2014-11-28 schema change. Create it if necessary. This code + ** can be removed in the future, once all users have upgraded to the + ** 2014-11-28 or later schema. + */ + if( !db_table_has_column("repository","plink","baseid") ){ + db_multi_exec( + "ALTER TABLE repository.plink ADD COLUMN baseid;" + ); + } + + /* Verify that the MLINK table has the newer columns added by the + ** 2015-01-24 schema change. Create them if necessary. This code + ** can be removed in the future, once all users have upgraded to the + ** 2015-01-24 or later schema. + */ + if( !db_table_has_column("repository","mlink","isaux") ){ + db_begin_transaction(); + db_multi_exec( + "ALTER TABLE repository.mlink ADD COLUMN pmid INTEGER DEFAULT 0;" + "ALTER TABLE repository.mlink ADD COLUMN isaux BOOLEAN DEFAULT 0;" + ); + db_end_transaction(0); + } + + /* Add the user.mtime column if it is missing. (2011-04-27) + */ + if( !db_table_has_column("repository", "user", "mtime") ){ + db_unprotect(PROTECT_ALL); + db_multi_exec( + "CREATE TEMP TABLE temp_user AS SELECT * FROM user;" + "DROP TABLE user;" + "CREATE TABLE user(\n" + " uid INTEGER PRIMARY KEY,\n" + " login TEXT UNIQUE,\n" + " pw TEXT,\n" + " cap TEXT,\n" + " cookie TEXT,\n" + " ipaddr TEXT,\n" + " cexpire DATETIME,\n" + " info TEXT,\n" + " mtime DATE,\n" + " photo BLOB\n" + ");" + "INSERT OR IGNORE INTO user" + " SELECT uid, login, pw, cap, cookie," + " ipaddr, cexpire, info, now(), photo FROM temp_user;" + "DROP TABLE temp_user;" + ); + db_protect_pop(); + } + + /* Add the config.mtime column if it is missing. (2011-04-27) + */ + if( !db_table_has_column("repository", "config", "mtime") ){ + db_unprotect(PROTECT_CONFIG); + db_multi_exec( + "ALTER TABLE config ADD COLUMN mtime INTEGER;" + "UPDATE config SET mtime=now();" + ); + db_protect_pop(); + } + + /* Add the shun.mtime and shun.scom columns if they are missing. + ** (2011-04-27) + */ + if( !db_table_has_column("repository", "shun", "mtime") ){ + db_multi_exec( + "ALTER TABLE shun ADD COLUMN mtime INTEGER;" + "ALTER TABLE shun ADD COLUMN scom TEXT;" + "UPDATE shun SET mtime=now();" + ); + } + + /* Add the reportfmt.mtime column if it is missing. (2011-04-27) + */ + if( !db_table_has_column("repository", "reportfmt", "mtime") ){ + static const char zCreateReportFmtTable[] = + @ -- An entry in this table describes a database query that generates a + @ -- table of tickets. + @ -- + @ CREATE TABLE IF NOT EXISTS reportfmt( + @ rn INTEGER PRIMARY KEY, -- Report number + @ owner TEXT, -- Owner of this report format (not used) + @ title TEXT UNIQUE, -- Title of this report + @ mtime INTEGER, -- Time last modified. Seconds since 1970 + @ cols TEXT, -- A color-key specification + @ sqlcode TEXT -- An SQL SELECT statement for this report + @ ); + ; + db_multi_exec( + "CREATE TEMP TABLE old_fmt AS SELECT * FROM reportfmt;" + "DROP TABLE reportfmt;" + ); + db_multi_exec("%s", zCreateReportFmtTable/*safe-for-%s*/); + db_multi_exec( + "INSERT OR IGNORE INTO reportfmt(rn,owner,title,cols,sqlcode,mtime)" + " SELECT rn, owner, title, cols, sqlcode, now() FROM old_fmt;" + "INSERT OR IGNORE INTO reportfmt(rn,owner,title,cols,sqlcode,mtime)" + " SELECT rn, owner, title || ' (' || rn || ')', cols, sqlcode, now()" + " FROM old_fmt;" + ); + } + + /* Add the concealed.mtime column if it is missing. (2011-04-27) + */ + if( !db_table_has_column("repository", "concealed", "mtime") ){ + db_multi_exec( + "ALTER TABLE concealed ADD COLUMN mtime INTEGER;" + "UPDATE concealed SET mtime=now();" + ); + } + + /* Do the fossil-2.0 updates to the schema. (2017-02-28) + */ + rebuild_schema_update_2_0(); +} + +/* +** Update the repository schema for Fossil version 2.0. (2017-02-28) +** (1) Change the CHECK constraint on BLOB.UUID so that the length +** is greater than or equal to 40, not exactly equal to 40. +*/ +void rebuild_schema_update_2_0(void){ + char *z = db_text(0, "SELECT sql FROM repository.sqlite_schema" + " WHERE name='blob'"); + if( z ){ + /* Search for: length(uuid)==40 + ** 0123456789 12345 */ + int i; + for(i=10; z[i]; i++){ + if( z[i]=='=' && strncmp(&z[i-6],"(uuid)==40",10)==0 ){ + int rc = 0; + z[i] = '>'; + sqlite3_db_config(g.db, SQLITE_DBCONFIG_DEFENSIVE, 0, &rc); + db_multi_exec( + "PRAGMA writable_schema=ON;" + "UPDATE repository.sqlite_schema SET sql=%Q WHERE name LIKE 'blob';" + "PRAGMA writable_schema=OFF;", + z + ); + sqlite3_db_config(g.db, SQLITE_DBCONFIG_DEFENSIVE, 1, &rc); + break; + } + } + fossil_free(z); + } + db_multi_exec( + "CREATE VIEW IF NOT EXISTS " + " repository.artifact(rid,rcvid,size,atype,srcid,hash,content) AS " + " SELECT blob.rid,rcvid,size,1,srcid,uuid,content" + " FROM blob LEFT JOIN delta ON (blob.rid=delta.rid);" + ); +} + +/* +** Variables used to store state information about an on-going "rebuild" +** or "deconstruct". +*/ +static int totalSize; /* Total number of artifacts to process */ +static int processCnt; /* Number processed so far */ +static int ttyOutput; /* Do progress output */ +static Bag bagDone = Bag_INIT; /* Bag of records rebuilt */ + +static char *zFNameFormat; /* Format string for filenames on deconstruct */ +static int cchFNamePrefix; /* Length of directory prefix in zFNameFormat */ +static const char *zDestDir;/* Destination directory on deconstruct */ +static int prefixLength; /* Length of directory prefix for deconstruct */ +static int fKeepRid1; /* Flag to preserve RID=1 on de- and reconstruct */ + + +/* +** Draw the percent-complete message. +** The input is actually the permill complete. +*/ +static void percent_complete(int permill){ + static int lastOutput = -1; + if( permill>lastOutput ){ + fossil_print(" %d.%d%% complete...\r", permill/10, permill%10); + fflush(stdout); + lastOutput = permill; + } +} + +/* +** Frees rebuild-level cached state. Intended only to be called by the +** app-level atexit() handler. +*/ +void rebuild_clear_cache(){ + bag_clear(&bagDone); +} /* ** Called after each artifact is processed */ -static void rebuild_step_done(rid){ +static void rebuild_step_done(int rid){ /* assert( bag_find(&bagDone, rid)==0 ); */ bag_insert(&bagDone, rid); if( ttyOutput ){ processCnt++; - if (!g.fQuiet) { - printf("%d (%d%%)...\r", processCnt, (processCnt*100/totalSize)); - fflush(stdout); + if (!g.fQuiet && totalSize>0) { + percent_complete((processCnt*1000)/totalSize); } } } /* ** Rebuild cross-referencing information for the artifact ** rid with content pBase and all of its descendants. This ** routine clears the content buffer before returning. -*/ -static void rebuild_step(int rid, int size, Blob *pBase){ - Stmt q1; - Bag children; - Blob copy; - Blob *pUse; - int nChild, i, cid; - - /* Fix up the "blob.size" field if needed. */ - if( size!=blob_size(pBase) ){ - db_multi_exec( - "UPDATE blob SET size=%d WHERE rid=%d", blob_size(pBase), rid - ); - } - - /* Find all children of artifact rid */ - db_prepare(&q1, "SELECT rid FROM delta WHERE srcid=%d", rid); - bag_init(&children); - while( db_step(&q1)==SQLITE_ROW ){ - int cid = db_column_int(&q1, 0); - if( !bag_find(&bagDone, cid) ){ - bag_insert(&children, cid); - } - } - nChild = bag_count(&children); - db_finalize(&q1); - - /* Crosslink the artifact */ - if( nChild==0 ){ - pUse = pBase; - }else{ - blob_copy(©, pBase); - pUse = © - } - manifest_crosslink(rid, pUse); - blob_reset(pUse); - - /* Call all children recursively */ - for(cid=bag_first(&children), i=1; cid; cid=bag_next(&children, cid), i++){ - Stmt q2; - int sz; - if( nChild==i ){ - pUse = pBase; - }else{ - blob_copy(©, pBase); - pUse = © - } - db_prepare(&q2, "SELECT content, size FROM blob WHERE rid=%d", cid); - if( db_step(&q2)==SQLITE_ROW && (sz = db_column_int(&q2,1))>=0 ){ - Blob delta; - db_ephemeral_blob(&q2, 0, &delta); - blob_uncompress(&delta, &delta); - blob_delta_apply(pUse, &delta, pUse); - blob_reset(&delta); - db_finalize(&q2); - rebuild_step(cid, sz, pUse); - }else{ - db_finalize(&q2); - blob_reset(pUse); - } - } - bag_clear(&children); - rebuild_step_done(rid); -} - -/* -** Check to see if the the "sym-trunk" tag exists. If not, create it +** +** If the zFNameFormat variable is set, then this routine is +** called to run "fossil deconstruct" instead of the usual +** "fossil rebuild". In that case, instead of rebuilding the +** cross-referencing information, write the file content out +** to the appropriate directory. +** +** In both cases, this routine automatically recurses to process +** other artifacts that are deltas off of the current artifact. +** This is the most efficient way to extract all of the original +** artifact content from the Fossil repository. +*/ +static void rebuild_step(int rid, int size, Blob *pBase){ + static Stmt q1; + Bag children; + Blob copy; + Blob *pUse; + int nChild, i, cid; + + while( rid>0 ){ + + /* Fix up the "blob.size" field if needed. */ + if( size!=blob_size(pBase) ){ + db_multi_exec( + "UPDATE blob SET size=%d WHERE rid=%d", blob_size(pBase), rid + ); + } + + /* Find all children of artifact rid */ + db_static_prepare(&q1, "SELECT rid FROM delta WHERE srcid=:rid"); + db_bind_int(&q1, ":rid", rid); + bag_init(&children); + while( db_step(&q1)==SQLITE_ROW ){ + int cid = db_column_int(&q1, 0); + if( !bag_find(&bagDone, cid) ){ + bag_insert(&children, cid); + } + } + nChild = bag_count(&children); + db_reset(&q1); + + /* Crosslink the artifact */ + if( nChild==0 ){ + pUse = pBase; + }else{ + blob_copy(©, pBase); + pUse = © + } + if( zFNameFormat==0 ){ + /* We are doing "fossil rebuild" */ + manifest_crosslink(rid, pUse, MC_NONE); + }else{ + /* We are doing "fossil deconstruct" */ + char *zUuid = db_text(0, "SELECT uuid FROM blob WHERE rid=%d", rid); + char *zFile = mprintf(zFNameFormat /*works-like:"%s:%s"*/, + zUuid, zUuid+prefixLength); + blob_write_to_file(pUse,zFile); + if( rid==1 && fKeepRid1!=0 ){ + char *zFnDotRid1 = mprintf("%s/.rid1", zDestDir); + char *zFnRid1 = zFile + cchFNamePrefix + 1; /* Skip directory slash */ + Blob bFileContents = empty_blob; + blob_appendf(&bFileContents, + "# The file holding the artifact with RID=1\n" + "%s\n", zFnRid1); + blob_write_to_file(&bFileContents, zFnDotRid1); + blob_reset(&bFileContents); + free(zFnDotRid1); + } + free(zFile); + free(zUuid); + blob_reset(pUse); + } + assert( blob_is_reset(pUse) ); + rebuild_step_done(rid); + + /* Call all children recursively */ + rid = 0; + for(cid=bag_first(&children), i=1; cid; cid=bag_next(&children, cid), i++){ + static Stmt q2; + int sz; + db_static_prepare(&q2, "SELECT content, size FROM blob WHERE rid=:rid"); + db_bind_int(&q2, ":rid", cid); + if( db_step(&q2)==SQLITE_ROW && (sz = db_column_int(&q2,1))>=0 ){ + Blob delta, next; + db_ephemeral_blob(&q2, 0, &delta); + blob_uncompress(&delta, &delta); + blob_delta_apply(pBase, &delta, &next); + blob_reset(&delta); + db_reset(&q2); + if( i0 ){ + processCnt += incrSize; + percent_complete((processCnt*1000)/totalSize); + } + if( doClustering ) create_cluster(); + if( ttyOutput && !g.fQuiet && totalSize>0 ){ + processCnt += incrSize; + percent_complete((processCnt*1000)/totalSize); + } if(!g.fQuiet && ttyOutput ){ - printf("\n"); + percent_complete(1000); + fossil_print("\n"); } + db_protect_pop(); return errCnt; } /* -** COMMAND: rebuild +** Number of neighbors to search +*/ +#define N_NEIGHBOR 5 + +/* +** Attempt to convert more full-text blobs into delta-blobs for +** storage efficiency. +*/ +void extra_deltification(void){ + Stmt q; + int aPrev[N_NEIGHBOR]; + int nPrev; + int rid; + int prevfnid, fnid; + db_begin_transaction(); + + /* Look for manifests that have not been deltaed and try to make them + ** children of one of the 5 chronologically subsequent check-ins + */ + db_prepare(&q, + "SELECT rid FROM event, blob" + " WHERE blob.rid=event.objid" + " AND event.type='ci'" + " AND NOT EXISTS(SELECT 1 FROM delta WHERE rid=blob.rid)" + " ORDER BY event.mtime DESC" + ); + nPrev = 0; + while( db_step(&q)==SQLITE_ROW ){ + rid = db_column_int(&q, 0); + if( nPrev>0 ){ + content_deltify(rid, aPrev, nPrev, 0); + } + if( nPrev0 ){ + content_deltify(rid, aPrev, nPrev, 0); + } + if( nPrev0;" + "INSERT OR IGNORE INTO private" + " SELECT fid FROM mlink" + " EXCEPT SELECT fid FROM mlink WHERE mid NOT IN private_ckin;" + "INSERT OR IGNORE INTO private SELECT rid FROM private_ckin;" + "DROP TABLE private_ckin;", TAG_PRIVATE + ); + fix_private_blob_dependencies(0); +} + + +/* +** COMMAND: rebuild ** -** Usage: %fossil rebuild ?REPOSITORY? +** Usage: %fossil rebuild ?REPOSITORY? ?OPTIONS? ** ** Reconstruct the named repository database from the core ** records. Run this command after updating the fossil ** executable in a way that changes the database schema. +** +** Options: +** --analyze Run ANALYZE on the database after rebuilding +** --cluster Compute clusters for unclustered artifacts +** --compress Strive to make the database as small as possible +** --compress-only Skip the rebuilding step. Do --compress only +** --deanalyze Remove ANALYZE tables from the database +** --force Force the rebuild to complete even if errors are seen +** --ifneeded Only do the rebuild if it would change the schema version +** --index Always add in the full-text search index +** --noverify Skip the verification of changes to the BLOB table +** --noindex Always omit the full-text search index +** --pagesize N Set the database pagesize to N. (512..65536 and power of 2) +** --quiet Only show output if there are errors +** --randomize Scan artifacts in a random order +** --stats Show artifact statistics after rebuilding +** --vacuum Run VACUUM on the database after rebuilding +** --wal Set Write-Ahead-Log journalling mode on the database */ void rebuild_database(void){ int forceFlag; int randomizeFlag; - int errCnt; + int errCnt = 0; + int omitVerify; + int doClustering; + const char *zPagesize; + int newPagesize = 0; + int activateWal; + int runVacuum; + int runDeanalyze; + int runAnalyze; + int runCompress; + int showStats; + int runReindex; + int optNoIndex; + int optIndex; + int optIfNeeded; + int compressOnlyFlag; + omitVerify = find_option("noverify",0,0)!=0; forceFlag = find_option("force","f",0)!=0; randomizeFlag = find_option("randomize", 0, 0)!=0; + doClustering = find_option("cluster", 0, 0)!=0; + runVacuum = find_option("vacuum",0,0)!=0; + runDeanalyze = find_option("deanalyze",0,0)!=0; + runAnalyze = find_option("analyze",0,0)!=0; + runCompress = find_option("compress",0,0)!=0; + zPagesize = find_option("pagesize",0,1); + showStats = find_option("stats",0,0)!=0; + optIndex = find_option("index",0,0)!=0; + optNoIndex = find_option("noindex",0,0)!=0; + optIfNeeded = find_option("ifneeded",0,0)!=0; + compressOnlyFlag = find_option("compress-only",0,0)!=0; + if( compressOnlyFlag ) runCompress = runVacuum = 1; + if( zPagesize ){ + newPagesize = atoi(zPagesize); + if( newPagesize<512 || newPagesize>65536 + || (newPagesize&(newPagesize-1))!=0 + ){ + fossil_fatal("page size must be a power of two between 512 and 65536"); + } + } + activateWal = find_option("wal",0,0)!=0; if( g.argc==3 ){ db_open_repository(g.argv[2]); }else{ - db_find_and_open_repository(1); + db_find_and_open_repository(OPEN_ANY_SCHEMA, 0); if( g.argc!=2 ){ usage("?REPOSITORY-FILENAME?"); } - db_close(); + db_close(1); db_open_repository(g.zRepositoryName); } + runReindex = search_index_exists() && !compressOnlyFlag; + if( optIndex ) runReindex = 1; + if( optNoIndex ) runReindex = 0; + if( optIfNeeded && fossil_strcmp(db_get("aux-schema",""),AUX_SCHEMA_MAX)==0 ){ + return; + } + + /* We should be done with options.. */ + verify_all_options(); + db_begin_transaction(); - ttyOutput = 1; - errCnt = rebuild_db(randomizeFlag, 1); + db_unprotect(PROTECT_ALL); + if( !compressOnlyFlag ){ + search_drop_index(); + ttyOutput = 1; + errCnt = rebuild_db(randomizeFlag, 1, doClustering); + reconstruct_private_table(); + } + db_multi_exec( + "REPLACE INTO config(name,value,mtime) VALUES('content-schema',%Q,now());" + "REPLACE INTO config(name,value,mtime) VALUES('aux-schema',%Q,now());" + "REPLACE INTO config(name,value,mtime) VALUES('rebuilt',%Q,now());", + CONTENT_SCHEMA, AUX_SCHEMA_MAX, get_version() + ); if( errCnt && !forceFlag ){ - printf("%d errors. Rolling back changes. Use --force to force a commit.\n", - errCnt); + fossil_print( + "%d errors. Rolling back changes. Use --force to force a commit.\n", + errCnt + ); db_end_transaction(1); }else{ + if( runCompress ){ + fossil_print("Extra delta compression... "); fflush(stdout); + extra_deltification(); + runVacuum = 1; + } + if( omitVerify ) verify_cancel(); db_end_transaction(0); + if( runCompress ) fossil_print("done\n"); + db_close(0); + db_open_repository(g.zRepositoryName); + if( newPagesize ){ + db_multi_exec("PRAGMA page_size=%d", newPagesize); + runVacuum = 1; + } + if( runDeanalyze ){ + db_multi_exec("DROP TABLE IF EXISTS sqlite_stat1;" + "DROP TABLE IF EXISTS sqlite_stat3;" + "DROP TABLE IF EXISTS sqlite_stat4;"); + } + if( runAnalyze ){ + fossil_print("Analyzing the database... "); fflush(stdout); + db_multi_exec("ANALYZE;"); + fossil_print("done\n"); + } + if( runVacuum ){ + fossil_print("Vacuuming the database... "); fflush(stdout); + db_multi_exec("VACUUM"); + fossil_print("done\n"); + } + if( activateWal ){ + db_multi_exec("PRAGMA journal_mode=WAL;"); + } + } + if( runReindex ) search_rebuild_index(); + db_protect_pop(); + if( showStats ){ + static const struct { int idx; const char *zLabel; } aStat[] = { + { CFTYPE_ANY, "Artifacts:" }, + { CFTYPE_MANIFEST, "Manifests:" }, + { CFTYPE_CLUSTER, "Clusters:" }, + { CFTYPE_CONTROL, "Tags:" }, + { CFTYPE_WIKI, "Wikis:" }, + { CFTYPE_TICKET, "Tickets:" }, + { CFTYPE_ATTACHMENT,"Attachments:" }, + { CFTYPE_EVENT, "Events:" }, + }; + int i; + int subtotal = 0; + for(i=0; i0 ) subtotal += g.parseCnt[k]; + } + fossil_print("%-15s %6d\n", "Other:", g.parseCnt[CFTYPE_ANY] - subtotal); } } /* -** COMMAND: test-detach +** COMMAND: test-detach +** +** Usage: %fossil test-detach ?REPOSITORY? ** ** Change the project-code and make other changes in order to prevent ** the repository from ever again pushing or pulling to other ** repositories. Used to create a "test" repository for development ** testing by cloning a working project repository. */ void test_detach_cmd(void){ - db_find_and_open_repository(1); + db_find_and_open_repository(0, 2); db_begin_transaction(); + db_unprotect(PROTECT_CONFIG); db_multi_exec( - "DELETE FROM config WHERE name='last-sync-url';" + "DELETE FROM config WHERE name GLOB 'last-sync-*';" + "DELETE FROM config WHERE name GLOB 'sync-*:*';" "UPDATE config SET value=lower(hex(randomblob(20)))" " WHERE name='project-code';" "UPDATE config SET value='detached-' || value" " WHERE name='project-name' AND value NOT GLOB 'detached-*';" ); + db_protect_pop(); + db_end_transaction(0); +} + +/* +** COMMAND: test-create-clusters +** +** Create clusters for all unclustered artifacts if the number of unclustered +** artifacts exceeds the current clustering threshold. +*/ +void test_createcluster_cmd(void){ + if( g.argc==3 ){ + db_open_repository(g.argv[2]); + }else{ + db_find_and_open_repository(0, 0); + if( g.argc!=2 ){ + usage("?REPOSITORY-FILENAME?"); + } + db_close(1); + db_open_repository(g.zRepositoryName); + } + db_begin_transaction(); + create_cluster(); db_end_transaction(0); } /* -** COMMAND: scrub -** %fossil scrub [--verily] [--force] [REPOSITORY] +** COMMAND: test-clusters +** +** Verify that all non-private and non-shunned artifacts are accessible +** through the cluster chain. +*/ +void test_clusters_cmd(void){ + Bag pending; + Stmt q; + int n; + + db_find_and_open_repository(0, 2); + bag_init(&pending); + db_multi_exec( + "CREATE TEMP TABLE xdone(x INTEGER PRIMARY KEY);" + "INSERT INTO xdone SELECT rid FROM unclustered;" + "INSERT OR IGNORE INTO xdone SELECT rid FROM private;" + "INSERT OR IGNORE INTO xdone" + " SELECT blob.rid FROM shun JOIN blob USING(uuid);" + ); + db_prepare(&q, + "SELECT rid FROM unclustered WHERE rid IN" + " (SELECT rid FROM tagxref WHERE tagid=%d)", TAG_CLUSTER + ); + while( db_step(&q)==SQLITE_ROW ){ + bag_insert(&pending, db_column_int(&q, 0)); + } + db_finalize(&q); + while( bag_count(&pending)>0 ){ + Manifest *p; + int rid = bag_first(&pending); + int i; + + bag_remove(&pending, rid); + p = manifest_get(rid, CFTYPE_CLUSTER, 0); + if( p==0 ){ + fossil_fatal("bad cluster: rid=%d", rid); + } + for(i=0; inCChild; i++){ + const char *zUuid = p->azCChild[i]; + int crid = name_to_rid(zUuid); + if( crid==0 ){ + fossil_warning("cluster (rid=%d) references unknown artifact %s", + rid, zUuid); + continue; + } + db_multi_exec("INSERT OR IGNORE INTO xdone VALUES(%d)", crid); + if( db_exists("SELECT 1 FROM tagxref WHERE tagid=%d AND rid=%d", + TAG_CLUSTER, crid) ){ + bag_insert(&pending, crid); + } + } + manifest_destroy(p); + } + n = db_int(0, "SELECT count(*) FROM /*scan*/" + " (SELECT rid FROM blob EXCEPT SELECT x FROM xdone)"); + if( n==0 ){ + fossil_print("all artifacts reachable through clusters\n"); + }else{ + fossil_print("%d unreachable artifacts:\n", n); + db_prepare(&q, "SELECT rid, uuid FROM blob WHERE rid NOT IN xdone"); + while( db_step(&q)==SQLITE_ROW ){ + fossil_print(" %3d %s\n", db_column_int(&q,0), db_column_text(&q,1)); + } + db_finalize(&q); + } +} + +/* +** COMMAND: scrub* +** +** Usage: %fossil scrub ?OPTIONS? ?REPOSITORY? ** ** The command removes sensitive information (such as passwords) from a -** repository so that the respository can be sent to an untrusted reader. +** repository so that the repository can be sent to an untrusted reader. ** ** By default, only passwords are removed. However, if the --verily option ** is added, then private branches, concealed email addresses, IP ** addresses of correspondents, and similar privacy-sensitive fields -** are also purged. +** are also purged. If the --private option is used, then only private +** branches are removed and all other information is left intact. ** -** This command permanently deletes the scrubbed information. The effects -** of this command are irreversible. Use with caution. +** This command permanently deletes the scrubbed information. THE EFFECTS +** OF THIS COMMAND ARE IRREVERSIBLE. USE WITH CAUTION! ** ** The user is prompted to confirm the scrub unless the --force option ** is used. +** +** Options: +** --force Do not prompt for confirmation +** --private Only private branches are removed from the repository +** --verily Scrub real thoroughly (see above) */ void scrub_cmd(void){ int bVerily = find_option("verily",0,0)!=0; int bForce = find_option("force", "f", 0)!=0; + int privateOnly = find_option("private",0,0)!=0; int bNeedRebuild = 0; - if( g.argc!=2 && g.argc!=3 ) usage("?REPOSITORY?"); - if( g.argc==2 ){ - db_must_be_within_tree(); - }else{ - db_open_repository(g.argv[2]); - } + db_find_and_open_repository(OPEN_ANY_SCHEMA, 2); + db_close(1); + db_open_repository(g.zRepositoryName); + + /* We should be done with options.. */ + verify_all_options(); + if( !bForce ){ Blob ans; - blob_zero(&ans); - prompt_user("Scrubbing the repository will permanently remove user\n" - "passwords and other information. Changes cannot be undone.\n" - "Continue (y/N)? ", &ans); - if( blob_str(&ans)[0]!='y' ){ - exit(1); + char cReply; + prompt_user( + "Scrubbing the repository will permanently delete information.\n" + "Changes cannot be undone. Continue (y/N)? ", &ans); + cReply = blob_str(&ans)[0]; + if( cReply!='y' && cReply!='Y' ){ + fossil_exit(1); } } db_begin_transaction(); - db_multi_exec( - "UPDATE user SET pw='';" - "DELETE FROM config WHERE name GLOB 'last-sync-*';" - ); - if( bVerily ){ - bNeedRebuild = db_exists("SELECT 1 FROM private"); - db_multi_exec( - "DELETE FROM concealed;" - "UPDATE rcvfrom SET ipaddr='unknown';" - "UPDATE user SET photo=NULL, info='';" - "INSERT INTO shun SELECT uuid FROM blob WHERE rid IN private;" - ); + if( privateOnly || bVerily ){ + bNeedRebuild = db_exists("SELECT 1 FROM private"); + delete_private_content(); + } + if( !privateOnly ){ + db_unprotect(PROTECT_ALL); + db_multi_exec( + "UPDATE user SET pw='';" + "DELETE FROM config WHERE name GLOB 'last-sync-*';" + "DELETE FROM config WHERE name GLOB 'sync-*:*';" + "DELETE FROM config WHERE name GLOB 'peer-*';" + "DELETE FROM config WHERE name GLOB 'login-group-*';" + "DELETE FROM config WHERE name GLOB 'skin:*';" + "DELETE FROM config WHERE name GLOB 'subrepo:*';" + "DELETE FROM config WHERE name GLOB 'http-auth:*';" + "DELETE FROM config WHERE name GLOB 'cert:*';" + ); + if( bVerily ){ + db_multi_exec( + "DELETE FROM concealed;\n" + "UPDATE rcvfrom SET ipaddr='unknown';\n" + "DROP TABLE IF EXISTS accesslog;\n" + "UPDATE user SET photo=NULL, info='';\n" + "DROP TABLE IF EXISTS purgeevent;\n" + "DROP TABLE IF EXISTS purgeitem;\n" + "DROP TABLE IF EXISTS admin_log;\n" + "DROP TABLE IF EXISTS vcache;\n" + "DROP TABLE IF EXISTS chat;\n" + ); + } + db_protect_pop(); } if( !bNeedRebuild ){ db_end_transaction(0); + db_unprotect(PROTECT_ALL); db_multi_exec("VACUUM;"); + db_protect_pop(); }else{ - rebuild_db(0, 1); + rebuild_db(0, 1, 0); db_end_transaction(0); } } + +/* +** Recursively read all files from the directory zPath and install +** every file read as a new artifact in the repository. +*/ +void recon_read_dir(char *zPath){ + DIR *d; + struct dirent *pEntry; + Blob aContent; /* content of the just read artifact */ + static int nFileRead = 0; + void *zUnicodePath; + char *zUtf8Name; + static int recursionLevel = 0; /* Bookkeeping about the recursion level */ + static char *zFnRid1 = 0; /* The file holding the artifact with RID=1 */ + static int cchPathInitial = 0; /* The length of zPath on first recursion */ + + recursionLevel++; + if( recursionLevel==1 ){ + cchPathInitial = strlen(zPath); + if( fKeepRid1!=0 ){ + char *zFnDotRid1 = mprintf("%s/.rid1", zPath); + Blob bFileContents; + if( blob_read_from_file(&bFileContents, zFnDotRid1, ExtFILE)!=-1 ){ + Blob line, value; + while( blob_line(&bFileContents, &line)>0 ){ + if( blob_token(&line, &value)==0 ) continue; /* Empty line */ + if( blob_buffer(&value)[0]=='#' ) continue; /* Comment */ + blob_trim(&value); + zFnRid1 = mprintf("%s/%s", zPath, blob_str(&value)); + break; + } + blob_reset(&bFileContents); + if( zFnRid1 ){ + if( blob_read_from_file(&aContent, zFnRid1, ExtFILE)==-1 ){ + fossil_fatal("some unknown error occurred while reading \"%s\"", + zFnRid1); + }else{ + recon_set_hash_policy(0, zFnRid1); + content_put(&aContent); + recon_restore_hash_policy(); + blob_reset(&aContent); + fossil_print("\r%d", ++nFileRead); + fflush(stdout); + } + }else{ + fossil_fatal("an error occurred while reading or parsing \"%s\"", + zFnDotRid1); + } + } + free(zFnDotRid1); + } + } + zUnicodePath = fossil_utf8_to_path(zPath, 1); + d = opendir(zUnicodePath); + if( d ){ + while( (pEntry=readdir(d))!=0 ){ + Blob path; + char *zSubpath; + + if( pEntry->d_name[0]=='.' ){ + continue; + } + zUtf8Name = fossil_path_to_utf8(pEntry->d_name); + zSubpath = mprintf("%s/%s", zPath, zUtf8Name); + fossil_path_free(zUtf8Name); +#ifdef _DIRENT_HAVE_D_TYPE + if( (pEntry->d_type==DT_UNKNOWN || pEntry->d_type==DT_LNK) + ? (file_isdir(zSubpath, ExtFILE)==1) : (pEntry->d_type==DT_DIR) ) +#else + if( file_isdir(zSubpath, ExtFILE)==1 ) +#endif + { + recon_read_dir(zSubpath); + }else if( fossil_strcmp(zSubpath, zFnRid1)!=0 ){ + blob_init(&path, 0, 0); + blob_appendf(&path, "%s", zSubpath); + if( blob_read_from_file(&aContent, blob_str(&path), ExtFILE)==-1 ){ + fossil_fatal("some unknown error occurred while reading \"%s\"", + blob_str(&path)); + } + recon_set_hash_policy(cchPathInitial, blob_str(&path)); + content_put(&aContent); + recon_restore_hash_policy(); + blob_reset(&path); + blob_reset(&aContent); + fossil_print("\r%d", ++nFileRead); + fflush(stdout); + } + free(zSubpath); + } + closedir(d); + }else { + fossil_fatal("encountered error %d while trying to open \"%s\".", + errno, g.argv[3]); + } + fossil_path_free(zUnicodePath); + if( recursionLevel==1 && zFnRid1!=0 ) free(zFnRid1); + recursionLevel--; +} + +/* +** Helper functions called from recon_read_dir() to set and restore the correct +** hash policy for an artifact read from disk, inferred from the length of the +** path name. +*/ +static int saved_eHashPolicy = -1; + +void recon_set_hash_policy( + const int cchPathPrefix, /* Directory prefix length for zUuidAsFilePath */ + const char *zUuidAsFilePath /* Relative, well-formed, from recon_read_dir() */ +){ + int cchUuidAsFilePath; + const char *zHashPart; + int cchHashPart = 0; + int new_eHashPolicy = -1; + assert( HNAME_COUNT==2 ); /* Review function if new hashes are implemented. */ + if( zUuidAsFilePath==0 ) return; + cchUuidAsFilePath = strlen(zUuidAsFilePath); + if( cchUuidAsFilePath==0 ) return; + if( cchPathPrefix>=cchUuidAsFilePath ) return; + for( zHashPart = zUuidAsFilePath + cchPathPrefix; *zHashPart; zHashPart++ ){ + if( *zHashPart!='/' ) cchHashPart++; + } + if( cchHashPart>=HNAME_LEN_K256 ){ + new_eHashPolicy = HPOLICY_SHA3; + }else if( cchHashPart>=HNAME_LEN_SHA1 ){ + new_eHashPolicy = HPOLICY_SHA1; + } + if( new_eHashPolicy!=-1 ){ + saved_eHashPolicy = g.eHashPolicy; + g.eHashPolicy = new_eHashPolicy; + } +} + +void recon_restore_hash_policy(){ + if( saved_eHashPolicy!=-1 ){ + g.eHashPolicy = saved_eHashPolicy; + saved_eHashPolicy = -1; + } +} + +#if 0 +/* +** COMMAND: test-hash-from-path* +** +** Usage: %fossil test-hash-from-path ?OPTIONS? DESTINATION UUID +** +** Generate a sample path name from DESTINATION and UUID, as the `deconstruct' +** command would do. Then try to guess the hash policy from the path name, as +** the `reconstruct' command would do. +** +** No files or directories will be created. +** +** Options: +** -L|--prefixlength N Set the length of the names of the DESTINATION +** subdirectories to N +*/ +void test_hash_from_path_cmd(void) { + char *zDest; + char *zUuid; + char *zFile; + const char *zHashPolicy = "unknown"; + const char *zPrefixOpt = find_option("prefixlength","L",1); + int iPrefixLength; + if( !zPrefixOpt ){ + iPrefixLength = 2; + }else{ + iPrefixLength = atoi(zPrefixOpt); + if( iPrefixLength<0 || iPrefixLength>9 ){ + fossil_fatal("N(%s) is not a valid prefix length!",zPrefixOpt); + } + } + if( g.argc!=4 ){ + usage ("?OPTIONS? DESTINATION UUID"); + } + zDest = g.argv[2]; + zUuid = g.argv[3]; + if( iPrefixLength ){ + zFNameFormat = mprintf("%s/%%.%ds/%%s",zDest,iPrefixLength); + }else{ + zFNameFormat = mprintf("%s/%%s",zDest); + } + cchFNamePrefix = strlen(zDest); + zFile = mprintf(zFNameFormat /*works-like:"%s:%s"*/, + zUuid, zUuid+iPrefixLength); + recon_set_hash_policy(cchFNamePrefix,zFile); + if( saved_eHashPolicy!=-1 ){ + zHashPolicy = hpolicy_name(); + } + recon_restore_hash_policy(); + fossil_print( + "\nPath Name: %s" + "\nHash Policy: %s\n", + zFile,zHashPolicy); + free(zFile); + free(zFNameFormat); + zFNameFormat = 0; + cchFNamePrefix = 0; +} +#endif + +/* +** Helper functions used by the `deconstruct' and `reconstruct' commands to +** save and restore the contents of the PRIVATE table. +*/ +void private_export(char *zFileName) +{ + Stmt q; + Blob fctx = empty_blob; + blob_append(&fctx, "# The hashes of private artifacts\n", -1); + db_prepare(&q, + "SELECT uuid FROM blob WHERE rid IN ( SELECT rid FROM private );"); + while( db_step(&q)==SQLITE_ROW ){ + const char *zUuid = db_column_text(&q, 0); + blob_append(&fctx, zUuid, -1); + blob_append(&fctx, "\n", -1); + } + db_finalize(&q); + blob_write_to_file(&fctx, zFileName); + blob_reset(&fctx); +} +void private_import(char *zFileName) +{ + Blob fctx; + if( blob_read_from_file(&fctx, zFileName, ExtFILE)!=-1 ){ + Blob line, value; + while( blob_line(&fctx, &line)>0 ){ + char *zUuid; + int nUuid; + if( blob_token(&line, &value)==0 ) continue; /* Empty line */ + if( blob_buffer(&value)[0]=='#' ) continue; /* Comment */ + blob_trim(&value); + zUuid = blob_buffer(&value); + nUuid = blob_size(&value); + zUuid[nUuid] = 0; + if( hname_validate(zUuid, nUuid)!=HNAME_ERROR ){ + canonical16(zUuid, nUuid); + db_multi_exec( + "INSERT OR IGNORE INTO private" + " SELECT rid FROM blob WHERE uuid = %Q;", + zUuid); + } + } + blob_reset(&fctx); + } +} + +/* +** COMMAND: reconstruct* +** +** Usage: %fossil reconstruct ?OPTIONS? FILENAME DIRECTORY +** +** This command studies the artifacts (files) in DIRECTORY and reconstructs the +** Fossil record from them. It places the new Fossil repository in FILENAME. +** Subdirectories are read, files with leading '.' in the filename are ignored. +** +** Options: +** -K|--keep-rid1 Read the filename of the artifact with RID=1 from the +** file .rid in DIRECTORY. +** -P|--keep-private Mark the artifacts listed in the file .private in +** DIRECTORY as private in the new Fossil repository. +*/ +void reconstruct_cmd(void) { + char *zPassword; + int fKeepPrivate; + fKeepRid1 = find_option("keep-rid1","K",0)!=0; + fKeepPrivate = find_option("keep-private","P",0)!=0; + if( g.argc!=4 ){ + usage("FILENAME DIRECTORY"); + } + if( file_isdir(g.argv[3], ExtFILE)!=1 ){ + fossil_print("\"%s\" is not a directory\n\n", g.argv[3]); + usage("FILENAME DIRECTORY"); + } + db_create_repository(g.argv[2]); + db_open_repository(g.argv[2]); + + /* We should be done with options.. */ + verify_all_options(); + + db_open_config(0, 0); + db_begin_transaction(); + db_initial_setup(0, 0, 0); + + fossil_print("Reading files from directory \"%s\"...\n", g.argv[3]); + recon_read_dir(g.argv[3]); + fossil_print("\nBuilding the Fossil repository...\n"); + + rebuild_db(0, 1, 1); + + /* Backwards compatibility: Mark check-ins with "+private" tags as private. */ + reconstruct_private_table(); + /* Newer method: Import the list of private artifacts to the PRIVATE table. */ + if( fKeepPrivate ){ + char *zFnDotPrivate = mprintf("%s/.private", g.argv[3]); + private_import(zFnDotPrivate); + free(zFnDotPrivate); + } + + /* Skip the verify_before_commit() step on a reconstruct. Most artifacts + ** will have been changed and verification therefore takes a really, really + ** long time. + */ + verify_cancel(); + + db_end_transaction(0); + fossil_print("project-id: %s\n", db_get("project-code", 0)); + fossil_print("server-id: %s\n", db_get("server-code", 0)); + zPassword = db_text(0, "SELECT pw FROM user WHERE login=%Q", g.zLogin); + fossil_print("admin-user: %s (initial password is \"%s\")\n", g.zLogin, zPassword); +} + +/* +** COMMAND: deconstruct* +** +** Usage %fossil deconstruct ?OPTIONS? DESTINATION +** +** This command exports all artifacts of a given repository and writes all +** artifacts to the file system. The DESTINATION directory will be populated +** with subdirectories AA and files AA/BBBBBBBBB.., where AABBBBBBBBB.. is the +** 40+ character artifact ID, AA the first 2 characters. +** If -L|--prefixlength is given, the length (default 2) of the directory prefix +** can be set to 0,1,..,9 characters. +** +** Options: +** -R|--repository REPO Deconstruct given REPOSITORY. +** -K|--keep-rid1 Save the filename of the artifact with RID=1 to +** the file .rid1 in the DESTINATION directory. +** -L|--prefixlength N Set the length of the names of the DESTINATION +** subdirectories to N. +** --private Include private artifacts. +** -P|--keep-private Save the list of private artifacts to the file +** .private in the DESTINATION directory (implies +** the --private option). +*/ +void deconstruct_cmd(void){ + const char *zPrefixOpt; + Stmt s; + int privateFlag; + int fKeepPrivate; + + fKeepRid1 = find_option("keep-rid1","K",0)!=0; + /* get and check prefix length argument and build format string */ + zPrefixOpt=find_option("prefixlength","L",1); + if( !zPrefixOpt ){ + prefixLength = 2; + }else{ + if( zPrefixOpt[0]>='0' && zPrefixOpt[0]<='9' && !zPrefixOpt[1] ){ + prefixLength = (int)(*zPrefixOpt-'0'); + }else{ + fossil_fatal("N(%s) is not a valid prefix length!",zPrefixOpt); + } + } + /* open repository and open query for all artifacts */ + db_find_and_open_repository(OPEN_ANY_SCHEMA, 0); + privateFlag = find_option("private",0,0)!=0; + fKeepPrivate = find_option("keep-private","P",0)!=0; + if( fKeepPrivate ) privateFlag = 1; + verify_all_options(); + /* check number of arguments */ + if( g.argc!=3 ){ + usage ("?OPTIONS? DESTINATION"); + } + /* get and check argument destination directory */ + zDestDir = g.argv[g.argc-1]; + if( !*zDestDir || !file_isdir(zDestDir, ExtFILE)) { + fossil_fatal("DESTINATION(%s) is not a directory!",zDestDir); + } +#ifndef _WIN32 + if( file_access(zDestDir, W_OK) ){ + fossil_fatal("DESTINATION(%s) is not writeable!",zDestDir); + } +#else + /* write access on windows is not checked, errors will be + ** detected on blob_write_to_file + */ +#endif + if( prefixLength ){ + zFNameFormat = mprintf("%s/%%.%ds/%%s",zDestDir,prefixLength); + }else{ + zFNameFormat = mprintf("%s/%%s",zDestDir); + } + cchFNamePrefix = strlen(zDestDir); + + bag_init(&bagDone); + ttyOutput = 1; + processCnt = 0; + if (!g.fQuiet) { + fossil_print("0 (0%%)...\r"); + fflush(stdout); + } + totalSize = db_int(0, "SELECT count(*) FROM blob"); + db_prepare(&s, + "SELECT rid, size FROM blob /*scan*/" + " WHERE NOT EXISTS(SELECT 1 FROM shun WHERE uuid=blob.uuid)" + " AND NOT EXISTS(SELECT 1 FROM delta WHERE rid=blob.rid) %s", + privateFlag==0 ? "AND rid NOT IN private" : "" + ); + while( db_step(&s)==SQLITE_ROW ){ + int rid = db_column_int(&s, 0); + int size = db_column_int(&s, 1); + if( size>=0 ){ + Blob content; + content_get(rid, &content); + rebuild_step(rid, size, &content); + } + } + db_finalize(&s); + db_prepare(&s, + "SELECT rid, size FROM blob" + " WHERE NOT EXISTS(SELECT 1 FROM shun WHERE uuid=blob.uuid) %s", + privateFlag==0 ? "AND rid NOT IN private" : "" + ); + while( db_step(&s)==SQLITE_ROW ){ + int rid = db_column_int(&s, 0); + int size = db_column_int(&s, 1); + if( size>=0 ){ + if( !bag_find(&bagDone, rid) ){ + Blob content; + content_get(rid, &content); + rebuild_step(rid, size, &content); + } + } + } + db_finalize(&s); + + /* Export the list of private artifacts. */ + if( fKeepPrivate ){ + char *zFnDotPrivate = mprintf("%s/.private", zDestDir); + private_export(zFnDotPrivate); + free(zFnDotPrivate); + } + + if(!g.fQuiet && ttyOutput ){ + fossil_print("\n"); + } + + /* free filename format string */ + free(zFNameFormat); + zFNameFormat = 0; +} ADDED src/regexp.c Index: src/regexp.c ================================================================== --- /dev/null +++ src/regexp.c @@ -0,0 +1,982 @@ +/* +** Copyright (c) 2013 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 was adapted from the ext/misc/regexp.c file in SQLite3. That +** file is in the public domain. +** +** See ../www/grep.md for details of the algorithm and RE dialect. +** +** +** 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. +*/ +#include "config.h" +#include "regexp.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 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 ReStateSet { + unsigned nState; /* Number of current states */ + ReStateNumber *aState; /* Current states */ +} ReStateSet; + +#if INTERFACE +/* An input string read one character at a time. +*/ +struct 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. +*/ +struct ReCompiled { + 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)(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[] */ +}; +#endif + +/* Add a state to the given state set if it is not already there */ +static void re_add_state(ReStateSet *pSet, int newState){ + unsigned i; + for(i=0; inState; i++) if( pSet->aState[i]==newState ) return; + pSet->aState[pSet->nState++] = (ReStateNumber)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 re_next_char(ReInput *p){ + unsigned c; + if( p->i>=p->mx ) return 0; + c = p->z[p->i++]; + if( c>=0x80 ){ + if( (c&0xe0)==0xc0 && p->imx && (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+1mx && (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<=0x7ff || (c>=0xd800 && c<=0xdfff) ) c = 0xfffd; + }else if( (c&0xf8)==0xf0 && p->i+3mx && (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 re_next_char_nocase(ReInput *p){ + unsigned c = re_next_char(p); + return unicode_fold(c,2); +} + +/* Return true if c is a perl "word" character: [A-Za-z0-9_] */ +static int re_word_char(int c){ + return unicode_isalnum(c) || c=='_'; +} + +/* Return true if c is a "digit" character: [0-9] */ +static int re_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 re_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 re_match(ReCompiled *pRe, const unsigned char *zIn, int nIn){ + ReStateSet aStateSet[2], *pThis, *pNext; + ReStateNumber aSpace[100]; + ReStateNumber *pToFree; + unsigned int i = 0; + unsigned int iSwap = 0; + int c = RE_EOF+1; + int cPrev = 0; + int rc = 0; + 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 = fossil_malloc( sizeof(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; + re_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; inState; i++){ + int x = pThis->aState[i]; + switch( pRe->aOp[x] ){ + case RE_OP_MATCH: { + if( pRe->aArg[x]==c ) re_add_state(pNext, x+1); + break; + } + case RE_OP_ANY: { + if( c!=0 ) re_add_state(pNext, x+1); + break; + } + case RE_OP_WORD: { + if( re_word_char(c) ) re_add_state(pNext, x+1); + break; + } + case RE_OP_NOTWORD: { + if( !re_word_char(c) && c!=0 ) re_add_state(pNext, x+1); + break; + } + case RE_OP_DIGIT: { + if( re_digit_char(c) ) re_add_state(pNext, x+1); + break; + } + case RE_OP_NOTDIGIT: { + if( !re_digit_char(c) && c!=0 ) re_add_state(pNext, x+1); + break; + } + case RE_OP_SPACE: { + if( re_space_char(c) ) re_add_state(pNext, x+1); + break; + } + case RE_OP_NOTSPACE: { + if( !re_space_char(c) && c!=0 ) re_add_state(pNext, x+1); + break; + } + case RE_OP_BOUNDARY: { + if( re_word_char(c)!=re_word_char(cPrev) ) re_add_state(pThis, x+1); + break; + } + case RE_OP_ANYSTAR: { + re_add_state(pNext, x); + re_add_state(pThis, x+1); + break; + } + case RE_OP_FORK: { + re_add_state(pThis, x+pRe->aArg[x]); + re_add_state(pThis, x+1); + break; + } + case RE_OP_GOTO: { + re_add_state(pThis, x+pRe->aArg[x]); + break; + } + case RE_OP_ACCEPT: { + rc = 1; + goto re_match_end; + } + case RE_OP_CC_EXC: { + if( c==0 ) break; + /* fall-through */ + } + case RE_OP_CC_INC: { + int j = 1; + int n = pRe->aArg[x]; + int hit = 0; + for(j=1; j>0 && jaOp[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 ) re_add_state(pNext, x+n); + break; + } + } + } + } + for(i=0; inState; i++){ + if( pRe->aOp[pNext->aState[i]]==RE_OP_ACCEPT ){ rc = 1; break; } + } +re_match_end: + fossil_free(pToFree); + return rc; +} + +/* Resize the opcode and argument arrays for an RE under construction. +*/ +static int re_resize(ReCompiled *p, int N){ + char *aOp; + int *aArg; + aOp = fossil_realloc(p->aOp, N*sizeof(p->aOp[0])); + if( aOp==0 ) return 1; + p->aOp = aOp; + aArg = fossil_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 re_insert(ReCompiled *p, int iBefore, int op, int arg){ + int i; + if( p->nAlloc<=p->nState && re_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] = (char)op; + p->aArg[iBefore] = arg; + return iBefore; +} + +/* Append a new opcode and argument to the end of the RE under construction. +*/ +static int re_append(ReCompiled *p, int op, int arg){ + return re_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 re_copy(ReCompiled *p, int iStart, int N){ + if( p->nState+N>=p->nAlloc && re_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 re_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 re_esc_char(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+4sIn.mx ){ + const unsigned char *zIn = p->sIn.z + p->sIn.i; + if( re_hex(zIn[1],&v) + && re_hex(zIn[2],&v) + && re_hex(zIn[3],&v) + && re_hex(zIn[4],&v) + ){ + p->sIn.i += 5; + return v; + } + } + if( c=='x' && p->sIn.i+2sIn.mx ){ + const unsigned char *zIn = p->sIn.z + p->sIn.i; + if( re_hex(zIn[1],&v) + && re_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 *re_subcompile_string(ReCompiled*); + +/* Peek at the next byte of input */ +static unsigned char rePeek(ReCompiled *p){ + return p->sIn.isIn.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 *re_subcompile_re(ReCompiled *p){ + const char *zErr; + int iStart, iEnd, iGoto; + iStart = p->nState; + zErr = re_subcompile_string(p); + if( zErr ) return zErr; + while( rePeek(p)=='|' ){ + iEnd = p->nState; + re_insert(p, iStart, RE_OP_FORK, iEnd + 2 - iStart); + iGoto = re_append(p, RE_OP_GOTO, 0); + p->sIn.i++; + zErr = re_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 *re_subcompile_string(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 = re_subcompile_re(p); + if( zErr ) return zErr; + if( rePeek(p)!=')' ) return "unmatched '('"; + p->sIn.i++; + break; + } + case '.': { + if( rePeek(p)=='*' ){ + re_append(p, RE_OP_ANYSTAR, 0); + p->sIn.i++; + }else{ + re_append(p, RE_OP_ANY, 0); + } + break; + } + case '*': { + if( iPrev<0 ) return "'*' without operand"; + re_insert(p, iPrev, RE_OP_GOTO, p->nState - iPrev + 1); + re_append(p, RE_OP_FORK, iPrev - p->nState + 1); + break; + } + case '+': { + if( iPrev<0 ) return "'+' without operand"; + re_append(p, RE_OP_FORK, iPrev - p->nState); + break; + } + case '?': { + if( iPrev<0 ) return "'?' without operand"; + re_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=rePeek(p))>='0' && c<='9' ){ m = m*10 + c - '0'; p->sIn.i++; } + n = m; + if( c==',' ){ + p->sIn.i++; + n = 0; + while( (c=rePeek(p))>='0' && c<='9' ){ n = n*10 + c-'0'; p->sIn.i++; } + } + if( c!='}' ) return "unmatched '{'"; + if( n>0 && nsIn.i++; + sz = p->nState - iPrev; + if( m==0 ){ + if( n==0 ) return "both m and n are zero in '{m,n}'"; + re_insert(p, iPrev, RE_OP_FORK, sz+1); + n--; + }else{ + for(j=1; j0 ){ + re_append(p, RE_OP_FORK, -sz); + } + break; + } + case '[': { + int iFirst = p->nState; + if( rePeek(p)=='^' ){ + re_append(p, RE_OP_CC_EXC, 0); + p->sIn.i++; + }else{ + re_append(p, RE_OP_CC_INC, 0); + } + while( (c = p->xNextChar(&p->sIn))!=0 ){ + if( c=='[' && rePeek(p)==':' ){ + return "POSIX character classes not supported"; + } + if( c=='\\' ) c = re_esc_char(p); + if( rePeek(p)=='-' ){ + re_append(p, RE_OP_CC_RANGE, c); + p->sIn.i++; + c = p->xNextChar(&p->sIn); + if( c=='\\' ) c = re_esc_char(p); + re_append(p, RE_OP_CC_RANGE, c); + }else{ + re_append(p, RE_OP_CC_VALUE, c); + } + if( rePeek(p)==']' ){ p->sIn.i++; break; } + } + if( c==0 ) return "unclosed '['"; + p->aArg[iFirst] = p->nState - iFirst; + break; + } + case '\\': { + int specialOp = 0; + switch( rePeek(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++; + re_append(p, specialOp, 0); + }else{ + c = re_esc_char(p); + re_append(p, RE_OP_MATCH, c); + } + break; + } + default: { + re_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 re_free(ReCompiled *pRe){ + if( pRe ){ + fossil_free(pRe->aOp); + fossil_free(pRe->aArg); + fossil_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 *re_compile(ReCompiled **ppRe, const char *zIn, int noCase){ + ReCompiled *pRe; + const char *zErr; + int i, j; + + *ppRe = 0; + pRe = fossil_malloc( sizeof(*pRe) ); + if( pRe==0 ){ + return "out of memory"; + } + memset(pRe, 0, sizeof(*pRe)); + pRe->xNextChar = noCase ? re_next_char_nocase : re_next_char; + if( re_resize(pRe, 30) ){ + re_free(pRe); + return "out of memory"; + } + if( zIn[0]=='^' ){ + zIn++; + }else{ + re_append(pRe, RE_OP_ANYSTAR, 0); + } + pRe->sIn.z = (unsigned char*)zIn; + pRe->sIn.i = 0; + pRe->sIn.mx = (int)strlen(zIn); + zErr = re_subcompile_re(pRe); + if( zErr ){ + re_free(pRe); + return zErr; + } + if( rePeek(pRe)=='$' && pRe->sIn.i+1>=pRe->sIn.mx ){ + re_append(pRe, RE_OP_MATCH, RE_EOF); + re_append(pRe, RE_OP_ACCEPT, 0); + *ppRe = pRe; + }else if( pRe->sIn.i>=pRe->sIn.mx ){ + re_append(pRe, RE_OP_ACCEPT, 0); + *ppRe = pRe; + }else{ + re_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 && !noCase ){ + 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++] = (unsigned char)x; + }else if( x<=0xfff ){ + pRe->zInit[j++] = (unsigned char)(0xc0 | (x>>6)); + pRe->zInit[j++] = 0x80 | (x&0x3f); + }else if( x<=0xffff ){ + pRe->zInit[j++] = (unsigned char)(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 +){ + 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() */ + + (void)argc; /* Unused */ + pRe = sqlite3_get_auxdata(context, 0); + if( pRe==0 ){ + zPattern = (const char*)sqlite3_value_text(argv[0]); + if( zPattern==0 ) return; + zErr = re_compile(&pRe, zPattern, sqlite3_user_data(context)!=0); + if( zErr ){ + re_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, re_match(pRe, zStr, -1)); + } + if( setAux ){ + sqlite3_set_auxdata(context, 0, pRe, (void(*)(void*))re_free); + } +} + +/* +** Invoke this routine to register the regexp() function with the +** SQLite database connection. +*/ +int re_add_sql_func(sqlite3 *db){ + int rc; + rc = sqlite3_create_function(db, "regexp", 2, SQLITE_UTF8|SQLITE_INNOCUOUS, + 0, re_sql_func, 0, 0); + if( rc==SQLITE_OK ){ + /* The regexpi(PATTERN,STRING) function is a case-insensitive version + ** of regexp(PATTERN,STRING). */ + rc = sqlite3_create_function(db, "regexpi", 2, SQLITE_UTF8|SQLITE_INNOCUOUS, + (void*)db, re_sql_func, 0, 0); + } + return rc; +} + +/* +** Run a "grep" over a single file read from disk. +*/ +static void grep_file(ReCompiled *pRe, const char *zFile, FILE *in){ + int ln = 0; + int n; + char zLine[2000]; + while( fgets(zLine, sizeof(zLine), in) ){ + ln++; + n = (int)strlen(zLine); + while( n && (zLine[n-1]=='\n' || zLine[n-1]=='\r') ) n--; + if( re_match(pRe, (const unsigned char*)zLine, n) ){ + fossil_print("%s:%d:%.*s\n", zFile, ln, n, zLine); + } + } +} + +/* +** Flags for grep_buffer() +*/ +#define GREP_EXISTS 0x001 /* If any match, print only the name and stop */ +#define GREP_QUIET 0x002 /* Return code only */ + +/* +** Run a "grep" over a text file +*/ +static int grep_buffer( + ReCompiled *pRe, + const char *zName, + const char *z, + u32 flags +){ + int i, j, n, ln, cnt; + for(i=j=ln=cnt=0; z[i]; i=j+1){ + for(j=i; z[j] && z[j]!='\n'; j++){} + n = j - i; + ln++; + if( re_match(pRe, (const unsigned char*)(z+i), j-i) ){ + cnt++; + if( flags & GREP_EXISTS ){ + if( (flags & GREP_QUIET)==0 && zName ) fossil_print("%s\n", zName); + break; + } + if( (flags & GREP_QUIET)==0 ){ + if( cnt==1 && zName ){ + fossil_print("== %s\n", zName); + } + fossil_print("%d:%.*s\n", ln, n, z+i); + } + } + } + return cnt; +} + +/* +** COMMAND: test-grep +** +** Usage: %fossil test-grep REGEXP [FILE...] +** +** Run a regular expression match over the named disk files, or against +** standard input if no disk files are named on the command-line. +** +** Options: +** +** -i|--ignore-case Ignore case +*/ +void re_test_grep(void){ + ReCompiled *pRe; + const char *zErr; + int ignoreCase = find_option("ignore-case","i",0)!=0; + if( g.argc<3 ){ + usage("REGEXP [FILE...]"); + } + zErr = re_compile(&pRe, g.argv[2], ignoreCase); + if( zErr ) fossil_fatal("%s", zErr); + if( g.argc==3 ){ + grep_file(pRe, "-", stdin); + }else{ + int i; + for(i=3; izRepoName. The discovered information is stored in other +** fields of the RepoInfo object. +*/ +static void remote_repo_info(RepoInfo *pRepo){ + sqlite3 *db; + sqlite3_stmt *pStmt; + int rc; + + pRepo->isRepolistSkin = 0; + pRepo->isValid = 0; + pRepo->zProjName = 0; + pRepo->zLoginGroup = 0; + pRepo->rMTime = 0.0; + + g.dbIgnoreErrors++; + rc = sqlite3_open_v2(pRepo->zRepoName, &db, SQLITE_OPEN_READWRITE, 0); + if( rc ) goto finish_repo_list; + rc = sqlite3_prepare_v2(db, "SELECT value FROM config" + " WHERE name='repolist-skin'", + -1, &pStmt, 0); + if( rc ) goto finish_repo_list; + if( sqlite3_step(pStmt)==SQLITE_ROW ){ + pRepo->isRepolistSkin = sqlite3_column_int(pStmt,0); + } + sqlite3_finalize(pStmt); + if( rc ) goto finish_repo_list; + rc = sqlite3_prepare_v2(db, "SELECT value FROM config" + " WHERE name='project-name'", + -1, &pStmt, 0); + if( rc ) goto finish_repo_list; + if( sqlite3_step(pStmt)==SQLITE_ROW ){ + pRepo->zProjName = fossil_strdup((char*)sqlite3_column_text(pStmt,0)); + } + sqlite3_finalize(pStmt); + rc = sqlite3_prepare_v2(db, "SELECT value FROM config" + " WHERE name='login-group-name'", + -1, &pStmt, 0); + if( rc==SQLITE_OK && sqlite3_step(pStmt)==SQLITE_ROW ){ + pRepo->zLoginGroup = fossil_strdup((char*)sqlite3_column_text(pStmt,0)); + } + sqlite3_finalize(pStmt); + rc = sqlite3_prepare_v2(db, "SELECT max(mtime) FROM event", -1, &pStmt, 0); + if( rc==SQLITE_OK && sqlite3_step(pStmt)==SQLITE_ROW ){ + pRepo->rMTime = sqlite3_column_double(pStmt,0); + } + pRepo->isValid = 1; + sqlite3_finalize(pStmt); +finish_repo_list: + g.dbIgnoreErrors--; + sqlite3_close(db); +} + +/* +** Generate a web-page that lists all repositories located under the +** g.zRepositoryName directory and return non-zero. +** +** For the special case when g.zRepositoryName is a non-chroot-jail "/", +** compose the list using the "repo:" entries in the global_config +** table of the configuration database. These entries comprise all +** of the repositories known to the "all" command. The special case +** processing is disallowed for chroot jails because g.zRepositoryName +** is always "/" inside a chroot jail and so it cannot be used as a flag +** to signal the special processing in that case. The special case +** processing is intended for the "fossil all ui" command which never +** runs in a chroot jail anyhow. +** +** Or, if no repositories can be located beneath g.zRepositoryName, +** close g.db and return 0. +*/ +int repo_list_page(void){ + Blob base; /* document root for all repositories */ + int n = 0; /* Number of repositories found */ + int allRepo; /* True if running "fossil ui all". + ** False if a directory scan of base for repos */ + Blob html; /* Html for the body of the repository list */ + char *zSkinRepo = 0; /* Name of the repository database used for skins */ + char *zSkinUrl = 0; /* URL for the skin database */ + + assert( g.db==0 ); + blob_init(&html, 0, 0); + if( fossil_strcmp(g.zRepositoryName,"/")==0 && !g.fJail ){ + /* For the special case of the "repository directory" being "/", + ** show all of the repositories named in the ~/.fossil database. + ** + ** On unix systems, then entries are of the form "repo:/home/..." + ** and on Windows systems they are like on unix, starting with a "/" + ** or they can begin with a drive letter: "repo:C:/Users/...". In either + ** case, we want returned path to omit any initial "/". + */ + db_open_config(1, 0); + db_multi_exec( + "CREATE TEMP VIEW sfile AS" + " SELECT ltrim(substr(name,6),'/') AS 'pathname' FROM global_config" + " WHERE name GLOB 'repo:*'" + ); + allRepo = 1; + }else{ + /* The default case: All repositories under the g.zRepositoryName + ** directory. + */ + blob_init(&base, g.zRepositoryName, -1); + sqlite3_open(":memory:", &g.db); + db_multi_exec("CREATE TABLE sfile(pathname TEXT);"); + db_multi_exec("CREATE TABLE vfile(pathname);"); + vfile_scan(&base, blob_size(&base), 0, 0, 0, ExtFILE); + db_multi_exec("DELETE FROM sfile WHERE pathname NOT GLOB '*[^/].fossil'"); + allRepo = 0; + } + n = db_int(0, "SELECT count(*) FROM sfile"); + if( n==0 ){ + sqlite3_close(g.db); + g.db = 0; + g.repositoryOpen = 0; + g.localOpen = 0; + return 0; + }else{ + Stmt q; + double rNow; + blob_append_sql(&html, + "\n" + "\n" + "\n"); + db_prepare(&q, "SELECT pathname" + " FROM sfile ORDER BY pathname COLLATE nocase;"); + rNow = db_double(0, "SELECT julianday('now')"); + while( db_step(&q)==SQLITE_ROW ){ + const char *zName = db_column_text(&q, 0); + int nName = (int)strlen(zName); + char *zUrl; + char *zAge; + char *zFull; + RepoInfo x; + int iAge; + if( nName<7 ) continue; + zUrl = sqlite3_mprintf("%.*s", nName-7, zName); + if( zName[0]=='/' +#ifdef _WIN32 + || sqlite3_strglob("[a-zA-Z]:/*", zName)==0 +#endif + ){ + zFull = mprintf("%s", zName); + }else if ( allRepo ){ + zFull = mprintf("/%s", zName); + }else{ + zFull = mprintf("%s/%s", g.zRepositoryName, zName); + } + x.zRepoName = zFull; + remote_repo_info(&x); + if( x.isRepolistSkin ){ + if( zSkinRepo==0 ){ + zSkinRepo = mprintf("%s", x.zRepoName); + zSkinUrl = mprintf("%s", zUrl); + } + } + fossil_free(zFull); + if( !x.isValid ){ + continue; + } + if( x.isRepolistSkin==2 && !allRepo ){ + /* Repositories with repolist-skin==2 are omitted from directory + ** scan lists, but included in "fossil all ui" lists */ + continue; + } + iAge = (rNow - x.rMTime)*86400; + if( iAge<0 ) x.rMTime = rNow; + zAge = human_readable_age(rNow - x.rMTime); + blob_append_sql(&html, "\n", x.zProjName); + fossil_free(x.zProjName); + }else{ + blob_append_sql(&html, "\n"); + } + blob_append_sql(&html, + "\n", + iAge, zAge); + fossil_free(zAge); + if( x.zLoginGroup ){ + blob_append_sql(&html, "\n", x.zLoginGroup); + fossil_free(x.zLoginGroup); + }else{ + blob_append_sql(&html, "\n"); + } + sqlite3_free(zUrl); + } + db_finalize(&q); + blob_append_sql(&html,"
      Filename" + "Project Name" + "Last Modified" + "Login Group
      "); + if( sqlite3_strglob("*.fossil", zName)!=0 ){ + /* The "fossil server DIRECTORY" and "fossil ui DIRECTORY" commands + ** do not work for repositories whose names do not end in ".fossil". + ** So do not hyperlink those cases. */ + blob_append_sql(&html,"%h",zName); + } else if( sqlite3_strglob("*/.*", zName)==0 ){ + /* Do not show hyperlinks for hidden repos */ + blob_append_sql(&html, "%h (hidden)", zName); + } else if( allRepo && sqlite3_strglob("[a-zA-Z]:/?*", zName)!=0 ){ + blob_append_sql(&html, + "/%h\n", + zUrl, zName); + }else{ + blob_append_sql(&html, + "%h\n", + zUrl, zName); + } + if( x.zProjName ){ + blob_append_sql(&html, "%h%h%h
      \n"); + } + if( zSkinRepo ){ + char *zNewBase = mprintf("%s/%s", g.zBaseURL, zSkinUrl); + g.zBaseURL = 0; + set_base_url(zNewBase); + db_open_repository(zSkinRepo); + fossil_free(zSkinRepo); + fossil_free(zSkinUrl); + } + if( g.repositoryOpen ){ + /* This case runs if remote_repository_info() found a repository + ** that has the "repolist_skin" property set to non-zero and left + ** that repository open in g.db. Use the skin of that repository + ** for display. */ + login_check_credentials(); + style_set_current_feature("repolist"); + style_header("Repository List"); + @ %s(blob_str(&html)) + style_table_sorter(); + style_finish_page(); + }else{ + /* If no repositories were found that had the "repolist_skin" + ** property set, then use a default skin */ + @ + @ + @ + @ + @ Repository List + @ + @ + @

      Fossil Repositories

      + @ %s(blob_str(&html)) + @ + @ + @ + } + blob_reset(&html); + cgi_reply(); + return n; +} + +/* +** COMMAND: test-list-page +** +** Usage: %fossil test-list-page DIRECTORY +** +** Show all repositories underneath DIRECTORY. Or if DIRECTORY is "/" +** show all repositories in the ~/.fossil file. +*/ +void test_list_page(void){ + if( g.argc<3 ){ + g.zRepositoryName = "/"; + }else{ + g.zRepositoryName = g.argv[2]; + } + g.httpOut = stdout; + repo_list_page(); +} Index: src/report.c ================================================================== --- src/report.c +++ src/report.c @@ -12,98 +12,115 @@ ** Author contact information: ** drh@hwaci.com ** http://www.hwaci.com/drh/ ** ******************************************************************************* -** +** ** Code to generate the ticket listings */ #include "config.h" +#include #include "report.h" #include /* Forward references to static routines */ static void report_format_hints(void); +#ifndef SQLITE_RECURSIVE +# define SQLITE_RECURSIVE 33 +#endif + /* -** WEBPAGE: /reportlist +** WEBPAGE: reportlist +** +** Main menu for Tickets. */ void view_list(void){ const char *zScript; Blob ril; /* Report Item List */ Stmt q; int rn = 0; int cnt = 0; login_check_credentials(); - if( !g.okRdTkt && !g.okNewTkt ){ login_needed(); return; } + if( !g.perm.RdTkt && !g.perm.NewTkt ){ + login_needed(g.anon.RdTkt || g.anon.NewTkt); + return; + } style_header("Ticket Main Menu"); + ticket_standard_submenu(T_ALL_BUT(T_REPLIST)); if( g.thTrace ) Th_Trace("BEGIN_REPORTLIST
      \n", -1); zScript = ticket_reportlist_code(); if( g.thTrace ) Th_Trace("BEGIN_REPORTLIST_SCRIPT
      \n", -1); - + blob_zero(&ril); ticket_init(); db_prepare(&q, "SELECT rn, title, owner FROM reportfmt ORDER BY title"); while( db_step(&q)==SQLITE_ROW ){ const char *zTitle = db_column_text(&q, 1); const char *zOwner = db_column_text(&q, 2); - if( zTitle[0] =='_' && !g.okTktFmt ){ + if( zTitle[0] =='_' && !g.perm.TktFmt ){ continue; } rn = db_column_int(&q, 0); cnt++; blob_appendf(&ril, "
      \n"); } + db_finalize(&q); Th_Store("report_items", blob_str(&ril)); - + Th_Render(zScript); - + blob_reset(&ril); if( g.thTrace ) Th_Trace("END_REPORTLIST
      \n", -1); - style_footer(); + style_finish_page(); } /* ** Remove whitespace from both ends of a string. */ char *trim_string(const char *zOrig){ int i; - while( isspace(*zOrig) ){ zOrig++; } + while( fossil_isspace(*zOrig) ){ zOrig++; } i = strlen(zOrig); - while( i>0 && isspace(zOrig[i-1]) ){ i--; } + while( i>0 && fossil_isspace(zOrig[i-1]) ){ i--; } return mprintf("%.*s", i, zOrig); } /* ** Extract a numeric (integer) value from a string. */ char *extract_integer(const char *zOrig){ if( zOrig == NULL || zOrig[0] == 0 ) return ""; - while( *zOrig && !isdigit(*zOrig) ){ zOrig++; } + while( *zOrig && !fossil_isdigit(*zOrig) ){ zOrig++; } if( *zOrig ){ /* we have a digit. atoi() will get as much of the number as it ** can. We'll run it through mprintf() to get a string. Not ** an efficient way to do it, but effective. */ @@ -112,24 +129,24 @@ return ""; } /* ** Remove blank lines from the beginning of a string and -** all whitespace from the end. Removes whitespace preceeding a NL, -** which also converts any CRNL sequence into a single NL. +** all whitespace from the end. Removes whitespace preceding a LF, +** which also converts any CRLF sequence into a single LF. */ char *remove_blank_lines(const char *zOrig){ int i, j, n; char *z; - for(i=j=0; isspace(zOrig[i]); i++){ if( zOrig[i]=='\n' ) j = i+1; } + for(i=j=0; fossil_isspace(zOrig[i]); i++){ if( zOrig[i]=='\n' ) j = i+1; } n = strlen(&zOrig[j]); - while( n>0 && isspace(zOrig[j+n-1]) ){ n--; } + while( n>0 && fossil_isspace(zOrig[j+n-1]) ){ n--; } z = mprintf("%.*s", n, &zOrig[j]); for(i=j=0; z[i]; i++){ - if( z[i+1]=='\n' && z[i]!='\n' && isspace(z[i]) ){ + if( z[i+1]=='\n' && z[i]!='\n' && fossil_isspace(z[i]) ){ z[j] = z[i]; - while(isspace(z[j]) && z[j] != '\n' ){ j--; } + while(fossil_isspace(z[j]) && z[j] != '\n' ){ j--; } j++; continue; } z[j++] = z[i]; @@ -144,10 +161,13 @@ /* ** This is the SQLite authorizer callback used to make sure that the ** SQL statements entered by users do not try to do anything untoward. ** If anything suspicious is tried, set *(char**)pError to an error ** message obtained from malloc. +** +** Use the "fossil test-db-prepare --auth-report SQL" command to perform +** manual testing of this authorizer. */ static int report_query_authorizer( void *pError, int code, const char *zArg1, @@ -156,36 +176,63 @@ const char *zArg4 ){ int rc = SQLITE_OK; if( *(char**)pError ){ /* We've already seen an error. No need to continue. */ - return SQLITE_OK; + return SQLITE_DENY; } switch( code ){ case SQLITE_SELECT: + case SQLITE_RECURSIVE: case SQLITE_FUNCTION: { break; } case SQLITE_READ: { - static const char *azAllowed[] = { - "ticket", + static const char *const azAllowed[] = { + "backlink", "blob", + "event", "filename", + "json_each", + "json_tree", "mlink", "plink", - "event", "tag", "tagxref", + "ticket", + "ticketchng", + "unversioned", }; - int i; - for(i=0; i0 ){ + lwr = i + 1; + }else{ + break; + } + } + if( cmp ){ + /* Always ok to access tables whose names begin with "fx_" */ + cmp = sqlite3_strnicmp(zArg1, "fx_", 3); } - if( i>=sizeof(azAllowed)/sizeof(azAllowed[0]) ){ + if( cmp ){ *(char**)pError = mprintf("access to table \"%s\" is restricted",zArg1); rc = SQLITE_DENY; - }else if( !g.okRdAddr && strncmp(zArg2, "private_", 8)==0 ){ + }else if( !g.perm.RdAddr && sqlite3_strnicmp(zArg2, "private_", 8)==0 ){ rc = SQLITE_IGNORE; } break; } default: { @@ -194,10 +241,22 @@ break; } } return rc; } + +/* +** Activate the ticket report query authorizer. Must be followed by an +** eventual call to report_unrestrict_sql(). +*/ +void report_restrict_sql(char **pzErr){ + db_set_authorizer(report_query_authorizer,(void*)pzErr,"Ticket-Report"); + sqlite3_limit(g.db, SQLITE_LIMIT_VDBE_OP, 10000); +} +void report_unrestrict_sql(void){ + db_clear_authorizer(); +} /* ** Check the given SQL to see if is a valid query that does not ** attempt to do anything dangerous. Return 0 on success and a @@ -210,15 +269,17 @@ const char *zTail; sqlite3_stmt *pStmt; int rc; /* First make sure the SQL is a single query command by verifying that - ** the first token is "SELECT" and that there are no unquoted semicolons. + ** the first token is "SELECT" or "WITH" and that there are no unquoted + ** semicolons. */ - for(i=0; isspace(zSql[i]); i++){} - if( strncasecmp(&zSql[i],"select",6)!=0 ){ - return mprintf("The SQL must be a SELECT statement"); + for(i=0; fossil_isspace(zSql[i]); i++){} + if( fossil_strnicmp(&zSql[i], "select", 6)!=0 + && fossil_strnicmp(&zSql[i], "with", 4)!=0 ){ + return mprintf("The SQL must be a SELECT or WITH statement"); } for(i=0; zSql[i]; i++){ if( zSql[i]==';' ){ int bad; int c = zSql[i+1]; @@ -232,26 +293,33 @@ return mprintf("Semi-colon detected! " "Only a single SQL statement is allowed"); } } } - + /* Compile the statement and check for illegal accesses or syntax errors. */ - sqlite3_set_authorizer(g.db, report_query_authorizer, (void*)&zErr); - rc = sqlite3_prepare(g.db, zSql, -1, &pStmt, &zTail); + report_restrict_sql(&zErr); + rc = sqlite3_prepare_v2(g.db, zSql, -1, &pStmt, &zTail); if( rc!=SQLITE_OK ){ zErr = mprintf("Syntax error: %s", sqlite3_errmsg(g.db)); } + if( !sqlite3_stmt_readonly(pStmt) ){ + zErr = mprintf("SQL must not modify the database"); + } if( pStmt ){ sqlite3_finalize(pStmt); } - sqlite3_set_authorizer(g.db, 0, 0); + report_unrestrict_sql(); return zErr; } /* -** WEBPAGE: /rptsql +** WEBPAGE: rptsql +** URL: /rptsql?rn=N +** +** Display the SQL query used to generate a ticket report. The rn=N +** query parameter identifies the specific report number to be displayed. */ void view_see_sql(void){ int rn; const char *zTitle; const char *zSQL; @@ -258,47 +326,59 @@ const char *zOwner; const char *zClrKey; Stmt q; login_check_credentials(); - if( !g.okTktFmt ){ - login_needed(); + if( !g.perm.TktFmt ){ + login_needed(g.anon.TktFmt); return; } rn = atoi(PD("rn","0")); db_prepare(&q, "SELECT title, sqlcode, owner, cols " "FROM reportfmt WHERE rn=%d",rn); + style_set_current_feature("report"); style_header("SQL For Report Format Number %d", rn); if( db_step(&q)!=SQLITE_ROW ){ @

      Unknown report number: %d(rn)

      - style_footer(); + style_finish_page(); + db_finalize(&q); return; } zTitle = db_column_text(&q, 0); zSQL = db_column_text(&q, 1); zOwner = db_column_text(&q, 2); zClrKey = db_column_text(&q, 3); @ @ - @ + @ @ - @ + @ @ @ @ @
      Title:%h(zTitle)
      %h(zTitle)
      Owner:%h(zOwner)
      %h(zOwner)
      SQL:
      -  @ %h(zSQL)
      +  @ %h(zSQL)
         @ 
      output_color_key(zClrKey, 0, "border=0 cellspacing=0 cellpadding=3"); @
      report_format_hints(); - style_footer(); + style_finish_page(); + db_finalize(&q); } /* -** WEBPAGE: /rptnew -** WEBPAGE: /rptedit +** WEBPAGE: rptnew +** WEBPAGE: rptedit +** +** Create (/rptnew) or edit (/rptedit) a ticket report format. +** Query parameters: +** +** rn=N Ticket report number. (required) +** t=TITLE Title of the report format +** w=USER Owner of the report format +** s=SQL SQL text used to implement the report +** k=KEY Color key */ void view_edit(void){ int rn; const char *zTitle; const char *z; @@ -306,14 +386,15 @@ const char *zClrKey; char *zSQL; char *zErr = 0; login_check_credentials(); - if( !g.okTktFmt ){ - login_needed(); + if( !g.perm.TktFmt ){ + login_needed(g.anon.TktFmt); return; } + style_set_current_feature("report"); /*view_add_functions(0);*/ rn = atoi(PD("rn","0")); zTitle = P("t"); zOwner = PD("w",g.zLogin); z = P("s"); @@ -328,11 +409,11 @@ zTitle = db_text(0, "SELECT title FROM reportfmt " "WHERE rn=%d", rn); if( zTitle==0 ) cgi_redirect("reportlist"); style_header("Are You Sure?"); - @
      + @ @

      You are about to delete all traces of the report @ %h(zTitle) from @ the database. This is an irreversible operation. All records @ related to this report will be removed and cannot be recovered.

      @ @@ -339,11 +420,11 @@ @ login_insert_csrf_secret(); @ @ @
      - style_footer(); + style_finish_page(); return; }else if( P("can") ){ /* user cancelled */ cgi_redirect("reportlist"); return; @@ -350,23 +431,29 @@ } if( zTitle && zSQL ){ if( zSQL[0]==0 ){ zErr = "Please supply an SQL query statement"; }else if( (zTitle = trim_string(zTitle))[0]==0 ){ - zErr = "Please supply a title"; + zErr = "Please supply a title"; }else{ zErr = verify_sql_statement(zSQL); } + if( zErr==0 + && db_exists("SELECT 1 FROM reportfmt WHERE title=%Q and rn<>%d", + zTitle, rn) + ){ + zErr = mprintf("There is already another report named \"%h\"", zTitle); + } if( zErr==0 ){ login_verify_csrf_secret(); if( rn>0 ){ db_multi_exec("UPDATE reportfmt SET title=%Q, sqlcode=%Q," - " owner=%Q, cols=%Q WHERE rn=%d", + " owner=%Q, cols=%Q, mtime=now() WHERE rn=%d", zTitle, zSQL, zOwner, zClrKey, rn); }else{ - db_multi_exec("INSERT INTO reportfmt(title,sqlcode,owner,cols) " - "VALUES(%Q,%Q,%Q,%Q)", + db_multi_exec("INSERT INTO reportfmt(title,sqlcode,owner,cols,mtime) " + "VALUES(%Q,%Q,%Q,%Q,now())", zTitle, zSQL, zOwner, zClrKey); rn = db_last_insert_rowid(); } cgi_redirect(mprintf("rptview?rn=%d", rn)); return; @@ -391,70 +478,70 @@ zTitle = mprintf("Copy Of %s", zTitle); zOwner = g.zLogin; } } if( zOwner==0 ) zOwner = g.zLogin; - style_submenu_element("Cancel", "Cancel", "reportlist"); + style_submenu_element("Cancel", "reportlist"); if( rn>0 ){ - style_submenu_element("Delete", "Delete", "rptedit?rn=%d&del1=1", rn); - } - style_header(rn>0 ? "Edit Report Format":"Create New Report Format"); - if( zErr ){ - @
      %h(zErr)
      - } - @
      - @ - @

      Report Title:
      - @

      - @

      Enter a complete SQL query statement against the "TICKET" table:
      + style_submenu_element("Delete", "rptedit?rn=%d&del1=1", rn); + } + style_header("%s", rn>0 ? "Edit Report Format":"Create New Report Format"); + if( zErr ){ + @

      %h(zErr)
      + } + @
      + @ + @

      Report Title:
      + @

      + @

      Enter a complete SQL query statement against the "TICKET" table:
      @ @

      login_insert_csrf_secret(); - if( g.okAdmin ){ + if( g.perm.Admin ){ @

      Report owner: - @ + @ @

      } else { - @ + @ } @

      Enter an optional color key in the following box. (If blank, no @ color key is displayed.) Each line contains the text for a single @ entry in the key. The first token of each line is the background - @ color for that line.
      + @ color for that line.
      @ @

      - if( !g.okAdmin && strcmp(zOwner,g.zLogin)!=0 ){ + if( !g.perm.Admin && fossil_strcmp(zOwner,g.zLogin)!=0 ){ @

      This report format is owned by %h(zOwner). You are not allowed @ to change it.

      @ report_format_hints(); - style_footer(); + style_finish_page(); return; } - @ + @ if( rn>0 ){ - @ + @ } - @ + @
      report_format_hints(); - style_footer(); + style_finish_page(); } /* ** Output a bunch of text that provides information about report ** formats */ static void report_format_hints(void){ char *zSchema; - zSchema = db_text(0,"SELECT sql FROM sqlite_master WHERE name='ticket'"); + zSchema = db_text(0,"SELECT sql FROM sqlite_schema WHERE name='ticket'"); if( zSchema==0 ){ - zSchema = db_text(0,"SELECT sql FROM repository.sqlite_master" + zSchema = db_text(0,"SELECT sql FROM repository.sqlite_schema" " WHERE name='ticket'"); } - @

      TICKET Schema

      + @

      TICKET Schema

      @
      -  @ %h(zSchema)
      +  @ %h(zSchema)
         @ 
      @

      Notes

      @
        @
      • The SQL must consist of a single SELECT statement

      • @ @@ -462,14 +549,21 @@ @ is assumed to hold a ticket number. A hyperlink will be created from @ that column to a detailed view of the ticket.

        @ @
      • If a column of the result set is named "bgcolor" then the content @ of that column determines the background color of the row.

      • + @ + @
      • The text of all columns prior to the first column whose name begins + @ with underscore ("_") is shown character-for-character as it appears in + @ the database. In other words, it is assumed to have a mimetype of + @ text/plain. @ @

      • The first column whose name begins with underscore ("_") and all - @ subsequent columns are shown on their own rows in the table. This might - @ be useful for displaying the description of tickets. + @ subsequent columns are shown on their own rows in the table and with + @ wiki formatting. In other words, such rows are shown with a mimetype + @ of text/x-fossil-wiki. This is recommended for the "description" field + @ of tickets. @

      • @ @
      • The query can join other tables in the database besides TICKET. @

      • @
      @@ -478,17 +572,17 @@ @

      In this example, the first column in the result set is named @ "bgcolor". The value of this column is not displayed. Instead, it @ selects the background color of each row based on the TICKET.STATUS @ field of the database. The color key at the right shows the various @ color codes.

      - @ - @ - @ - @ - @ - @ - @ + @
      new or active
      review
      fixed
      tested
      defer
      closed
      + @ + @ + @ + @ + @ + @ @
      new or active
      review
      fixed
      tested
      defer
      closed
      @
         @ SELECT
         @   CASE WHEN status IN ('new','active') THEN '#f2dcdc'
         @        WHEN status='review' THEN '#e8e8bd'
      @@ -510,16 +604,16 @@
         @ FROM ticket
         @ 
      @

      To base the background color on the TICKET.PRIORITY or @ TICKET.SEVERITY fields, substitute the following code for the @ first column of the query:

      - @ - @ - @ - @ - @ - @ + @
      1
      2
      3
      4
      5
      + @ + @ + @ + @ + @ @
      1
      2
      3
      4
      5
      @
         @ SELECT
         @   CASE priority WHEN 1 THEN '#f2dcdc'
         @        WHEN 2 THEN '#e8e8bd'
      @@ -563,58 +657,16 @@
         @    sdate(changetime) AS 'Changed',
         @    assignedto AS 'Assigned',
         @    severity AS 'Svr',
         @    priority AS 'Pri',
         @    title AS 'Title',
      -  @    description AS '_Description',   -- When the column name begins with '_'
      -  @    remarks AS '_Remarks'            -- the data is shown on a separate row.
      -  @  FROM ticket
      -  @ 
      - @ - @

      Or, to see part of the description on the same row, use the - @ wiki() function with some string manipulation. Using the - @ tkt() function on the ticket number will also generate a linked - @ field, but without the extra edit column: - @

      - @
      -  @  SELECT
      -  @    tkt(tn) AS '',
      -  @    title AS 'Title',
      -  @    wiki(substr(description,0,80)) AS 'Description'
      -  @  FROM ticket
      -  @ 
      - @ -} - -#if 0 /* NOT USED */ -static void column_header(int rn,const char *zCol, int nCol, int nSorted, - const char *zDirection, const char *zExtra -){ - int set = (nCol==nSorted); - int desc = !strcmp(zDirection,"DESC"); - - /* - ** Clicking same column header 3 times in a row resets any sorting. - ** Note that we link to rptview, which means embedded reports will get - ** sent to the actual report view page as soon as a user tries to do - ** any sorting. I don't see that as a Bad Thing. - */ - if(set && desc){ - @ - @ %h(zCol) - }else{ - if(set){ - @ %h(zCol) - } -} -#endif + @ description AS '_Description', -- When the column name begins with '_' + @ remarks AS '_Remarks' -- content is rendered as wiki + @ FROM ticket + @ + @ +} /* ** The state of the report generation. */ struct GenerateHTML { @@ -622,39 +674,36 @@ int nCount; /* Row number */ int nCol; /* Number of columns */ int isMultirow; /* True if multiple table rows per query result row */ int iNewRow; /* Index of first column that goes on separate row */ int iBg; /* Index of column that defines background color */ + int wikiFlags; /* Flags passed into wiki_convert() */ + const char *zWikiStart; /* HTML before display of multi-line wiki */ + const char *zWikiEnd; /* HTML after display of multi-line wiki */ }; /* ** The callback function for db_query */ static int generate_html( void *pUser, /* Pointer to output state */ int nArg, /* Number of columns in this result row */ - char **azArg, /* Text of data in all columns */ - char **azName /* Names of the columns */ + const char **azArg, /* Text of data in all columns */ + const char **azName /* Names of the columns */ ){ struct GenerateHTML *pState = (struct GenerateHTML*)pUser; int i; - const char *zTid; /* Ticket UUID. (value of column named '#') */ - int rn; /* Report number */ - char *zBg = 0; /* Use this background color */ - char zPage[30]; /* Text version of the ticket number */ - - /* Get the report number - */ - rn = pState->rn; + const char *zTid; /* Ticket hash. (value of column named '#') */ + const char *zBg = 0; /* Use this background color */ /* Do initialization */ if( pState->nCount==0 ){ /* Turn off the authorizer. It is no longer doing anything since the ** query has already been prepared. */ - sqlite3_set_authorizer(g.db, 0, 0); + db_clear_authorizer(); /* Figure out the number of columns, the column that determines background ** color, and whether or not this row of data is represented by multiple ** rows in the table. */ @@ -661,36 +710,48 @@ pState->nCol = 0; pState->isMultirow = 0; pState->iNewRow = -1; pState->iBg = -1; for(i=0; iiBg = i; continue; } - if( g.okWrite && azName[i][0]=='#' ){ + if( g.perm.Write && azName[i][0]=='#' ){ pState->nCol++; } if( !pState->isMultirow ){ if( azName[i][0]=='_' ){ pState->isMultirow = 1; pState->iNewRow = i; + pState->wikiFlags = WIKI_NOBADLINKS; + pState->zWikiStart = ""; + pState->zWikiEnd = ""; + if( P("plaintext") ){ + pState->wikiFlags |= WIKI_LINKSONLY; + pState->zWikiStart = "
      ";
      +            pState->zWikiEnd = "
      "; + style_submenu_element("Formatted", "%R/rptview?rn=%d", pState->rn); + }else{ + style_submenu_element("Plaintext", "%R/rptview?rn=%d&plaintext", + pState->rn); + } }else{ pState->nCol++; } } } /* The first time this routine is called, output a table header */ - @ + @ zTid = 0; for(i=0; iiBg ) continue; if( pState->iNewRow>=0 && i>=pState->iNewRow ){ - if( g.okWrite && zTid ){ + if( g.perm.Write && zTid ){ @   zTid = 0; } if( zName[0]=='_' ) zName++; @ nCol)>%h(zName) @@ -699,14 +760,14 @@ zTid = zName; } @ %h(zName) } } - if( g.okWrite && zTid ){ + if( g.perm.Write && zTid ){ @   } - @ + @ } if( azArg==0 ){ @ @ No records match the report criteria @ @@ -723,47 +784,45 @@ /* Output the data for this entry from the database */ zBg = pState->iBg>=0 ? azArg[pState->iBg] : 0; if( zBg==0 ) zBg = "white"; - @ + @ zTid = 0; - zPage[0] = 0; for(i=0; iiBg ) continue; zData = azArg[i]; if( zData==0 ) zData = ""; if( pState->iNewRow>=0 && i>=pState->iNewRow ){ - if( zTid && g.okWrite ){ - @ edit + if( zTid && g.perm.Write ){ + @ %z(href("%R/tktedit/%h",zTid))edit zTid = 0; } if( zData[0] ){ Blob content; - @ nCol)> + @ + @ nCol)> + @ %s(pState->zWikiStart) blob_init(&content, zData, -1); - wiki_convert(&content, 0, 0); + wiki_convert(&content, 0, pState->wikiFlags); blob_reset(&content); + @ %s(pState->zWikiEnd) } }else if( azName[i][0]=='#' ){ zTid = zData; - if( g.okHistory ){ - @ %h(zData) - }else{ - @ %h(zData) - } + @ %z(href("%R/tktview?name=%h",zData))%h(zData) }else if( zData[0]==0 ){ @   }else{ @ @ %h(zData) @ } } - if( zTid && g.okWrite ){ - @ edit + if( zTid && g.perm.Write ){ + @ %z(href("%R/tktedit/%h",zTid))edit } @ return 0; } @@ -772,15 +831,15 @@ ** spaces. */ static void output_no_tabs(const char *z){ while( z && z[0] ){ int i, j; - for(i=0; z[i] && (!isspace(z[i]) || z[i]==' '); i++){} + for(i=0; z[i] && (!fossil_isspace(z[i]) || z[i]==' '); i++){} if( i>0 ){ cgi_printf("%.*s", i, z); } - for(j=i; isspace(z[j]); j++){} + for(j=i; fossil_isspace(z[j]); j++){} if( j>i ){ cgi_printf("%*s", j-i, ""); } z += j; } @@ -790,12 +849,12 @@ ** Output a row as a tab-separated line of text. */ static int output_tab_separated( void *pUser, /* Pointer to row-count integer */ int nArg, /* Number of columns in this result row */ - char **azArg, /* Text of data in all columns */ - char **azName /* Names of the columns */ + const char **azArg, /* Text of data in all columns */ + const char **azName /* Names of the columns */ ){ int *pCount = (int*)pUser; int i; if( *pCount==0 ){ @@ -815,28 +874,29 @@ /* ** Generate HTML that describes a color key. */ void output_color_key(const char *zClrKey, int horiz, char *zTabArgs){ int i, j, k; - char *zSafeKey, *zToFree; - while( isspace(*zClrKey) ) zClrKey++; + const char *zSafeKey; + char *zToFree; + while( fossil_isspace(*zClrKey) ) zClrKey++; if( zClrKey[0]==0 ) return; @ if( horiz ){ @ } - zToFree = zSafeKey = mprintf("%h", zClrKey); + zSafeKey = zToFree = mprintf("%h", zClrKey); while( zSafeKey[0] ){ - while( isspace(*zSafeKey) ) zSafeKey++; - for(i=0; zSafeKey[i] && !isspace(zSafeKey[i]); i++){} - for(j=i; isspace(zSafeKey[j]); j++){} + while( fossil_isspace(*zSafeKey) ) zSafeKey++; + for(i=0; zSafeKey[i] && !fossil_isspace(zSafeKey[i]); i++){} + for(j=i; fossil_isspace(zSafeKey[j]); j++){} for(k=j; zSafeKey[k] && zSafeKey[k]!='\n' && zSafeKey[k]!='\r'; k++){} if( !horiz ){ - cgi_printf("\n", + cgi_printf("\n", i, zSafeKey, k-j, &zSafeKey[j]); }else{ - cgi_printf("\n", + cgi_printf("\n", i, zSafeKey, k-j, &zSafeKey[j]); } zSafeKey += k; } free(zToFree); @@ -844,22 +904,90 @@ @ } @
      %.*s
      %.*s
      %.*s%.*s
      } +/* +** Execute a single read-only SQL statement. Invoke xCallback() on each +** row. +*/ +static int db_exec_readonly( + sqlite3 *db, /* The database on which the SQL executes */ + const char *zSql, /* The SQL to be executed */ + int (*xCallback)(void*,int,const char**, const char**), + /* Invoke this callback routine */ + void *pArg, /* First argument to xCallback() */ + char **pzErrMsg /* Write error messages here */ +){ + int rc = SQLITE_OK; /* Return code */ + const char *zLeftover; /* Tail of unprocessed SQL */ + sqlite3_stmt *pStmt = 0; /* The current SQL statement */ + const char **azCols = 0; /* Names of result columns */ + int nCol; /* Number of columns of output */ + const char **azVals = 0; /* Text of all output columns */ + int i; /* Loop counter */ + int nVar; /* Number of parameters */ + + pStmt = 0; + rc = sqlite3_prepare_v2(db, zSql, -1, &pStmt, &zLeftover); + assert( rc==SQLITE_OK || pStmt==0 ); + if( rc!=SQLITE_OK ){ + return rc; + } + if( !pStmt ){ + /* this happens for a comment or white-space */ + return SQLITE_OK; + } + if( !sqlite3_stmt_readonly(pStmt) ){ + sqlite3_finalize(pStmt); + return SQLITE_ERROR; + } + + nVar = sqlite3_bind_parameter_count(pStmt); + for(i=1; i<=nVar; i++){ + const char *zVar = sqlite3_bind_parameter_name(pStmt, i); + if( zVar==0 ) continue; + if( zVar[0]!='$' && zVar[0]!='@' && zVar[0]!=':' ) continue; + if( !fossil_islower(zVar[1]) ) continue; + if( strcmp(zVar, "$login")==0 ){ + sqlite3_bind_text(pStmt, i, g.zLogin, -1, SQLITE_TRANSIENT); + }else{ + sqlite3_bind_text(pStmt, i, P(zVar+1), -1, SQLITE_TRANSIENT); + } + } + nCol = sqlite3_column_count(pStmt); + azVals = fossil_malloc(2*nCol*sizeof(const char*) + 1); + while( (rc = sqlite3_step(pStmt))==SQLITE_ROW ){ + if( azCols==0 ){ + azCols = &azVals[nCol]; + for(i=0; i + style_set_current_feature("report"); + /* style_finish_page() should provide escaping via %h formatting */ + if( zQS[0] ){ + style_submenu_element("Raw","%R/%s?tablist=1&%s",g.zPath,zQS); + style_submenu_element("Reports","%R/reportlist?%s",zQS); + } else { + style_submenu_element("Raw","%R/%s?tablist=1",g.zPath); + style_submenu_element("Reports","%R/reportlist"); + } + if( g.perm.Admin + || (g.perm.TktFmt && g.zLogin && fossil_strcmp(g.zLogin,zOwner)==0) ){ + style_submenu_element("Edit", "rptedit?rn=%d", rn); + } + if( g.perm.TktFmt ){ + style_submenu_element("SQL", "rptsql?rn=%d",rn); + } + if( g.perm.NewTkt ){ + style_submenu_element("New Ticket", "%R/tktnew"); + } + style_header("%s", zTitle); + output_color_key(zClrKey, 1, + "border=\"0\" cellpadding=\"3\" cellspacing=\"0\" class=\"report\""); + @ sState.rn = rn; sState.nCount = 0; - sqlite3_set_authorizer(g.db, report_query_authorizer, (void*)&zErr1); - sqlite3_exec(g.db, zSql, generate_html, &sState, &zErr2); - sqlite3_set_authorizer(g.db, 0, 0); - @
      + report_restrict_sql(&zErr1); + db_exec_readonly(g.db, zSql, generate_html, &sState, &zErr2); + report_unrestrict_sql(); + @ if( zErr1 ){ - @

      Error: %h(zErr1)

      + @

      Error: %h(zErr1)

      }else if( zErr2 ){ - @

      Error: %h(zErr2)

      + @

      Error: %h(zErr2)

      } - style_footer(); + style_table_sorter(); + style_finish_page(); }else{ - sqlite3_set_authorizer(g.db, report_query_authorizer, (void*)&zErr1); - sqlite3_exec(g.db, zSql, output_tab_separated, &count, &zErr2); - sqlite3_set_authorizer(g.db, 0, 0); + report_restrict_sql(&zErr1); + db_exec_readonly(g.db, zSql, output_tab_separated, &count, &zErr2); + report_unrestrict_sql(); cgi_set_content_type("text/plain"); } } + +/* +** report number for full table ticket export +*/ +static const char zFullTicketRptRn[] = "0"; + +/* +** report title for full table ticket export +*/ +static const char zFullTicketRptTitle[] = "full ticket export"; + +/* +** show all reports, which can be used for ticket show. +** Output is written to stdout as tab delimited table +*/ +void rpt_list_reports(void){ + Stmt q; + fossil_print("Available reports:\n"); + fossil_print("%s\t%s\n","report number","report title"); + fossil_print("%s\t%s\n",zFullTicketRptRn,zFullTicketRptTitle); + db_prepare(&q,"SELECT rn,title FROM reportfmt ORDER BY rn"); + while( db_step(&q)==SQLITE_ROW ){ + const char *zRn = db_column_text(&q, 0); + const char *zTitle = db_column_text(&q, 1); + + fossil_print("%s\t%s\n",zRn,zTitle); + } + db_finalize(&q); +} + +/* +** user defined separator used by ticket show command +*/ +static const char *zSep = 0; + +/* +** select the quoting algorithm for "ticket show" +*/ +#if INTERFACE +typedef enum eTktShowEnc { tktNoTab=0, tktFossilize=1 } tTktShowEncoding; +#endif +static tTktShowEncoding tktEncode = tktNoTab; + +/* +** Output the text given in the argument. Convert tabs and newlines into +** spaces. +*/ +static void output_no_tabs_file(const char *z){ + switch( tktEncode ){ + case tktFossilize: + { char *zFosZ; + + if( z && *z ){ + zFosZ = fossilize(z,-1); + fossil_print("%s",zFosZ); + free(zFosZ); + } + break; + } + default: + while( z && z[0] ){ + int i, j; + for(i=0; z[i] && (!fossil_isspace(z[i]) || z[i]==' '); i++){} + if( i>0 ){ + fossil_print("%.*s", i, z); + } + for(j=i; fossil_isspace(z[j]); j++){} + if( j>i ){ + fossil_print("%*s", j-i, ""); + } + z += j; + } + break; + } +} + +/* +** Output a row as a tab-separated line of text. +*/ +int output_separated_file( + void *pUser, /* Pointer to row-count integer */ + int nArg, /* Number of columns in this result row */ + const char **azArg, /* Text of data in all columns */ + const char **azName /* Names of the columns */ +){ + int *pCount = (int*)pUser; + int i; + + if( *pCount==0 ){ + for(i=0; i #include "rss.h" #include -#include /* ** WEBPAGE: timeline.rss +** URL: /timeline.rss?y=TYPE&n=LIMIT&tkt=HASH&tag=TAG&wiki=NAME&name=FILENAME +** +** Produce an RSS feed of the timeline. +** +** TYPE may be: all, ci (show check-ins only), t (show ticket changes only), +** w (show wiki only), e (show tech notes only), f (show forum posts only), +** g (show tag/branch changes only). +** +** LIMIT is the number of items to show. +** +** tkt=HASH filters for only those events for the specified ticket. tag=TAG +** filters for a tag, and wiki=NAME for a wiki page. Only one may be used. +** +** In addition, name=FILENAME filters for a specific file. This may be +** combined with one of the other filters (useful for looking at a specific +** branch). */ + void page_timeline_rss(void){ Stmt q; int nLine=0; char *zPubDate, *zProjectName, *zProjectDescr, *zFreeProjectName=0; Blob bSQL; const char *zType = PD("y","all"); /* Type of events. All if NULL */ + const char *zTicketUuid = PD("tkt",NULL); + const char *zTag = PD("tag",NULL); + const char *zFilename = PD("name",NULL); + const char *zWiki = PD("wiki",NULL); int nLimit = atoi(PD("n","20")); + int nTagId; const char zSQL1[] = @ SELECT @ blob.rid, @ uuid, @ event.mtime, @ coalesce(ecomment,comment), @ coalesce(euser,user), @ (SELECT count(*) FROM plink WHERE pid=blob.rid AND isprim), - @ (SELECT count(*) FROM plink WHERE cid=blob.rid) + @ (SELECT count(*) FROM plink WHERE cid=blob.rid), + @ (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 @ FROM event, blob @ WHERE blob.rid=event.objid ; login_check_credentials(); - if( !g.okRead && !g.okRdTkt && !g.okRdWiki ){ + if( !g.perm.Read && !g.perm.RdTkt && !g.perm.RdWiki ){ return; } blob_zero(&bSQL); blob_append( &bSQL, zSQL1, -1 ); if( zType[0]!='a' ){ - if( zType[0]=='c' && !g.okRead ) zType = "x"; - if( zType[0]=='w' && !g.okRdWiki ) zType = "x"; - if( zType[0]=='t' && !g.okRdTkt ) zType = "x"; - blob_appendf(&bSQL, " AND event.type=%Q", zType); + if( zType[0]=='c' && !g.perm.Read ) zType = "x"; + if( zType[0]=='w' && !g.perm.RdWiki ) zType = "x"; + if( zType[0]=='t' && !g.perm.RdTkt ) zType = "x"; + blob_append_sql(&bSQL, " AND event.type=%Q", zType); }else{ - if( !g.okRead ){ - if( g.okRdTkt && g.okRdWiki ){ + if( !g.perm.Read ){ + if( g.perm.RdTkt && g.perm.RdWiki ){ blob_append(&bSQL, " AND event.type!='ci'", -1); - }else if( g.okRdTkt ){ + }else if( g.perm.RdTkt ){ blob_append(&bSQL, " AND event.type=='t'", -1); + }else{ blob_append(&bSQL, " AND event.type=='w'", -1); } - }else if( !g.okRdWiki ){ - if( g.okRdTkt ){ + }else if( !g.perm.RdWiki ){ + if( g.perm.RdTkt ){ blob_append(&bSQL, " AND event.type!='w'", -1); }else{ blob_append(&bSQL, " AND event.type=='ci'", -1); } - }else if( !g.okRdTkt ){ - assert( !g.okRdTkt &&& g.okRead && g.okRdWiki ); + }else if( !g.perm.RdTkt ){ + assert( !g.perm.RdTkt && g.perm.Read && g.perm.RdWiki ); blob_append(&bSQL, " AND event.type!='t'", -1); } } + + if( zTicketUuid ){ + nTagId = db_int(0, "SELECT tagid FROM tag WHERE tagname GLOB 'tkt-%q*'", + zTicketUuid); + if ( nTagId==0 ){ + nTagId = -1; + } + }else if( zTag ){ + nTagId = db_int(0, "SELECT tagid FROM tag WHERE tagname GLOB 'sym-%q*'", + zTag); + if ( nTagId==0 ){ + nTagId = -1; + } + }else if( zWiki ){ + nTagId = db_int(0, "SELECT tagid FROM tag WHERE tagname GLOB 'wiki-%q*'", + zWiki); + if ( nTagId==0 ){ + nTagId = -1; + } + }else{ + nTagId = 0; + } + + if( nTagId==-1 ){ + blob_append_sql(&bSQL, " AND 0"); + }else if( nTagId!=0 ){ + blob_append_sql(&bSQL, " AND (EXISTS(SELECT 1 FROM tagxref" + " WHERE tagid=%d AND tagtype>0 AND rid=blob.rid))", nTagId); + } + + if( zFilename ){ + blob_append_sql(&bSQL, + " AND (SELECT mlink.fnid FROM mlink WHERE event.objid=mlink.mid) IN (SELECT fnid FROM filename WHERE name=%Q %s)", + zFilename, filename_collation() + ); + } blob_append( &bSQL, " ORDER BY event.mtime DESC", -1 ); cgi_set_content_type("application/rss+xml"); @@ -94,29 +156,221 @@ } zPubDate = cgi_rfc822_datestamp(time(NULL)); @ - @ + @ @ @ %h(zProjectName) @ %s(g.zBaseURL) @ %h(zProjectDescr) @ %s(zPubDate) @ Fossil version %s(MANIFEST_VERSION) %s(MANIFEST_DATE) - db_prepare(&q, blob_str(&bSQL)); + free(zPubDate); + db_prepare(&q, "%s", blob_sql_text(&bSQL)); + blob_reset( &bSQL ); + while( db_step(&q)==SQLITE_ROW && nLine1 && nChild>1 ){ + zPrefix = "*MERGE/FORK* "; + }else if( nParent>1 ){ + zPrefix = "*MERGE* "; + }else if( nChild>1 ){ + zPrefix = "*FORK* "; + } + + if( zTagList ){ + zSuffix = mprintf(" (tags: %s)", zTagList); + } + + @ + @ %s(zPrefix)%h(zCom)%h(zSuffix) + @ %s(g.zBaseURL)/info/%s(zId) + @ %s(zPrefix)%h(zCom)%h(zSuffix) + @ %s(zDate) + @ %h(zAuthor) + @ %s(g.zBaseURL)/info/%s(zId) + @ + free(zDate); + free(zSuffix); + nLine++; + } + + db_finalize(&q); + @ + @ + + if( zFreeProjectName != 0 ){ + free( zFreeProjectName ); + } +} + +/* +** COMMAND: rss* +** +** Usage: %fossil rss ?OPTIONS? +** +** The CLI variant of the /timeline.rss page, this produces an RSS +** feed of the timeline to stdout. Options: +** +** -type|y FLAG May be: all (default), ci (show check-ins only), +** t (show tickets only), w (show wiki only). +** +** -limit|n LIMIT The maximum number of items to show. +** +** -tkt HASH Filter for only those events for the specified ticket. +** +** -tag TAG Filter for a tag +** +** -wiki NAME Filter on a specific wiki page. +** +** Only one of -tkt, -tag, or -wiki may be used. +** +** -name FILENAME Filter for a specific file. This may be combined +** with one of the other filters (useful for looking +** at a specific branch). +** +** -url STRING Set the RSS feed's root URL to the given string. +** The default is "URL-PLACEHOLDER" (without quotes). +*/ +void cmd_timeline_rss(void){ + Stmt q; + int nLine=0; + char *zPubDate, *zProjectName, *zProjectDescr, *zFreeProjectName=0; + Blob bSQL; + const char *zType = find_option("type","y",1); /* Type of events. All if NULL */ + const char *zTicketUuid = find_option("tkt",NULL,1); + const char *zTag = find_option("tag",NULL,1); + const char *zFilename = find_option("name",NULL,1); + const char *zWiki = find_option("wiki",NULL,1); + const char *zLimit = find_option("limit", "n",1); + const char *zBaseURL = find_option("url", NULL, 1); + int nLimit = atoi( (zLimit && *zLimit) ? zLimit : "20" ); + int nTagId; + const char zSQL1[] = + @ SELECT + @ blob.rid, + @ uuid, + @ event.mtime, + @ coalesce(ecomment,comment), + @ coalesce(euser,user), + @ (SELECT count(*) FROM plink WHERE pid=blob.rid AND isprim), + @ (SELECT count(*) FROM plink WHERE cid=blob.rid), + @ (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 + @ FROM event, blob + @ WHERE blob.rid=event.objid + ; + if(!zType || !*zType){ + zType = "all"; + } + if(!zBaseURL || !*zBaseURL){ + zBaseURL = "URL-PLACEHOLDER"; + } + + db_find_and_open_repository(0, 0); + + /* We should be done with options.. */ + verify_all_options(); + + blob_zero(&bSQL); + blob_append( &bSQL, zSQL1, -1 ); + + if( zType[0]!='a' ){ + blob_append_sql(&bSQL, " AND event.type=%Q", zType); + } + + if( zTicketUuid ){ + nTagId = db_int(0, "SELECT tagid FROM tag WHERE tagname GLOB 'tkt-%q*'", + zTicketUuid); + if ( nTagId==0 ){ + nTagId = -1; + } + }else if( zTag ){ + nTagId = db_int(0, "SELECT tagid FROM tag WHERE tagname GLOB 'sym-%q*'", + zTag); + if ( nTagId==0 ){ + nTagId = -1; + } + }else if( zWiki ){ + nTagId = db_int(0, "SELECT tagid FROM tag WHERE tagname GLOB 'wiki-%q*'", + zWiki); + if ( nTagId==0 ){ + nTagId = -1; + } + }else{ + nTagId = 0; + } + + if( nTagId==-1 ){ + blob_append_sql(&bSQL, " AND 0"); + }else if( nTagId!=0 ){ + blob_append_sql(&bSQL, " AND (EXISTS(SELECT 1 FROM tagxref" + " WHERE tagid=%d AND tagtype>0 AND rid=blob.rid))", nTagId); + } + + if( zFilename ){ + blob_append_sql(&bSQL, + " AND (SELECT mlink.fnid FROM mlink WHERE event.objid=mlink.mid) IN (SELECT fnid FROM filename WHERE name=%Q %s)", + zFilename, filename_collation() + ); + } + + blob_append( &bSQL, " ORDER BY event.mtime DESC", -1 ); + + zProjectName = db_get("project-name", 0); + if( zProjectName==0 ){ + zFreeProjectName = zProjectName = mprintf("Fossil source repository for: %s", + zBaseURL); + } + zProjectDescr = db_get("project-description", 0); + if( zProjectDescr==0 ){ + zProjectDescr = zProjectName; + } + + zPubDate = cgi_rfc822_datestamp(time(NULL)); + + fossil_print(""); + fossil_print(""); + fossil_print("\n"); + fossil_print("%h\n", zProjectName); + fossil_print("%s\n", zBaseURL); + fossil_print("%h\n", zProjectDescr); + fossil_print("%s\n", zPubDate); + fossil_print("Fossil version %s %s\n", + MANIFEST_VERSION, MANIFEST_DATE); + free(zPubDate); + db_prepare(&q, "%s", blob_sql_text(&bSQL)); blob_reset( &bSQL ); - while( db_step(&q)==SQLITE_ROW && nLine<=nLimit ){ + while( db_step(&q)==SQLITE_ROW && nLine1 && nChild>1 ){ zPrefix = "*MERGE/FORK* "; @@ -124,25 +378,30 @@ zPrefix = "*MERGE* "; }else if( nChild>1 ){ zPrefix = "*FORK* "; } - @ - @ %s(zPrefix)%h(zCom) - @ %s(g.zBaseURL)/ci/%s(zId) - @ %s(zPrefix)%h(zCom) - @ %s(zDate) - @ %h(zAuthor) - @ %s(g.zBaseURL)/ci/%s(zId) - @ + if( zTagList ){ + zSuffix = mprintf(" (tags: %s)", zTagList); + } + + fossil_print(""); + fossil_print("%s%h%h\n", zPrefix, zCom, zSuffix); + fossil_print("%s/info/%s\n", zBaseURL, zId); + fossil_print("%s%h%h\n", zPrefix, zCom, zSuffix); + fossil_print("%s\n", zDate); + fossil_print("%h\n", zAuthor); + fossil_print("%s/info/%s\n", g.zBaseURL, zId); + fossil_print("\n"); free(zDate); + free(zSuffix); nLine++; } db_finalize(&q); - @ - @ + fossil_print("\n"); + fossil_print("\n"); if( zFreeProjectName != 0 ){ free( zFreeProjectName ); } } ADDED src/sbsdiff.js Index: src/sbsdiff.js ================================================================== --- /dev/null +++ src/sbsdiff.js @@ -0,0 +1,41 @@ +/* The javascript in this file was added by Joel Bruick on 2013-07-06, +** originally as in-line javascript. It does some kind of setup for +** side-by-side diff display, but I'm not really sure what. +*/ +(function(){ + var SCROLL_LEN = 25; + function initSbsDiff(diff){ + var txtCols = diff.querySelectorAll('.difftxtcol'); + var txtPres = diff.querySelectorAll('.difftxtcol pre'); + var width = Math.max(txtPres[0].scrollWidth, txtPres[1].scrollWidth); + var i; + for(i=0; i<2; i++){ + txtPres[i].style.width = width + 'px'; + txtCols[i].onscroll = function(e){ + txtCols[0].scrollLeft = txtCols[1].scrollLeft = this.scrollLeft; + }; + } + diff.tabIndex = 0; + diff.onkeydown = function(e){ + e = e || event; + var len = {37: -SCROLL_LEN, 39: SCROLL_LEN}[e.keyCode]; + if( !len ) return; + txtCols[0].scrollLeft += len; + return false; + }; + } + var i, diffs = document.querySelectorAll('.sbsdiffcols') + /* Maintenance reminder: using forEach() here breaks + MSIE<=11, and we need to keep those browsers working on + the /info page. */; + for(i=0; i0 ) +@ CHECK( length(uuid)>=40 AND rid>0 ) @ ); @ CREATE TABLE delta( -@ rid INTEGER PRIMARY KEY, -- Record ID -@ srcid INTEGER NOT NULL REFERENCES blob -- Record holding source document +@ rid INTEGER PRIMARY KEY, -- BLOB that is delta-compressed +@ srcid INTEGER NOT NULL REFERENCES blob -- Baseline for delta-compression @ ); @ CREATE INDEX 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 rcvfrom( @ rcvid INTEGER PRIMARY KEY, -- Received-From ID @ uid INTEGER REFERENCES user, -- User login -@ mtime DATETIME, -- Time or receipt +@ mtime DATETIME, -- Time of receipt. Julian day. @ nonce TEXT UNIQUE, -- Nonce used for login @ ipaddr TEXT -- Remote IP address. NULL for direct. @ ); @ @ -- Information about users @@ -99,26 +118,28 @@ @ -- hash based on the project-code, the user login, and the cleartext @ -- password. @ -- @ CREATE TABLE user( @ uid INTEGER PRIMARY KEY, -- User ID -@ login TEXT, -- login name of the user +@ 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. +@ -- The config table holds miscellanous information about the repository. @ -- in the form of name-value pairs. @ -- @ CREATE TABLE 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 @@ -128,29 +149,66 @@ @ -- @ -- 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 shun(uuid UNIQUE); +@ CREATE TABLE 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. +@ -- +@ -- A phantom artifact (that is, an artifact with BLOB.SIZE<0 - an artifact +@ -- for which we do not know the content) might also be marked as private. +@ -- This comes about when an artifact is named in a manifest or tag but +@ -- the content of that artifact is held privately by some other peer +@ -- repository. @ -- @ CREATE TABLE private(rid INTEGER PRIMARY KEY); @ @ -- An entry in this table describes a database query that generates a @ -- table of tickets. @ -- @ CREATE TABLE reportfmt( -@ rn integer primary key, -- Report number -@ owner text, -- Owner of this report format (not used) -@ title text, -- Title of this report -@ cols text, -- A color-key specification -@ sqlcode text -- An SQL SELECT statement for this report +@ 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 concealed( +@ hash TEXT PRIMARY KEY, -- The SHA1 hash of content +@ mtime DATE, -- Time created. Seconds since 1970 +@ content TEXT -- Content intended to be concealed @ ); -@ INSERT INTO reportfmt(title,cols,sqlcode) VALUES('All Tickets','#ffffff Key: +@ +@ -- The application ID helps the unix "file" command to identify the +@ -- database as a fossil repository. +@ PRAGMA application_id=252006673; +; + +/* +** The default reportfmt entry for the schema. This is in an extra +** script so that (configure reset) can install the default report. +*/ +const char zRepositorySchemaDefaultReports[] = +@ 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 @@ -166,23 +224,10 @@ @ type, @ status, @ subsystem, @ title @ FROM ticket'); -@ -@ -- 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 concealed( -@ hash TEXT PRIMARY KEY, -@ content TEXT -@ ); ; const char zRepositorySchema2[] = @ -- Filenames @ -- @@ -189,62 +234,112 @@ @ CREATE TABLE filename( @ fnid INTEGER PRIMARY KEY, -- Filename ID @ name TEXT UNIQUE -- Name of file page @ ); @ -@ -- Linkages between manifests, files created by that manifest, and +@ -- Linkages between check-ins, files created by each check-in, and @ -- the names of those files. @ -- -@ -- pid==0 if the file is added by check-in mid. -@ -- fid==0 if the file is removed by check-in mid. +@ -- 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. +@ -- fid==0 if the file is removed by check-in mid. @ -- @ CREATE TABLE mlink( -@ mid INTEGER REFERENCES blob, -- Manifest ID where change occurs -@ pid INTEGER REFERENCES blob, -- File ID in parent manifest -@ fid INTEGER REFERENCES blob, -- Changed file ID in this manifest -@ fnid INTEGER REFERENCES filename, -- Name of the file -@ pfnid INTEGER REFERENCES filename -- Previous name. 0 if unchanged +@ mid INTEGER, -- Check-in that contains fid +@ fid INTEGER, -- New file content. 0 if deleted +@ pmid INTEGER, -- Check-in that contains pid +@ pid INTEGER, -- Prev file content. 0 if new. -1 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 mlink_i1 ON mlink(mid); @ CREATE INDEX mlink_i2 ON mlink(fnid); @ CREATE INDEX mlink_i3 ON mlink(fid); @ CREATE INDEX mlink_i4 ON mlink(pid); @ -@ -- Parent/child linkages +@ -- Parent/child linkages between check-ins @ -- @ CREATE TABLE 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 +@ 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 plink_i2 ON plink(cid); +@ CREATE INDEX plink_i2 ON plink(cid,pid); +@ +@ -- A "leaf" check-in is a check-in 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 check-ins) +@ -- and so it makes sense to precompute the set of leaves. There is +@ -- one entry in the following table for each leaf. +@ -- +@ CREATE TABLE leaf(rid INTEGER PRIMARY KEY); @ -@ -- Events used to generate a timeline +@ -- Events used to generate a timeline. Type meanings: +@ -- ci Check-ins +@ -- e Technotes +@ -- f Forum posts +@ -- g Tags +@ -- t Ticket changes +@ -- w Wiki page edit @ -- @ CREATE TABLE event( -@ type TEXT, -- Type of event -@ mtime DATETIME, -- Date and time when the event occurs +@ type TEXT, -- Type of event: ci, e, f, g, t, w +@ 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 +@ brief TEXT, -- Short comment when tagid already seen +@ omtime DATETIME -- Original unchanged date+time, or NULL @ ); @ CREATE INDEX 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. +@ -- file hash but we do not (yet) know the file content. @ -- @ CREATE TABLE 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 orphan( +@ rid INTEGER PRIMARY KEY, -- Delta manifest with a phantom baseline +@ baseline INTEGER -- Phantom baseline of this orphan +@ ); +@ CREATE INDEX 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. @ -- @@ -263,16 +358,16 @@ @ -- @ CREATE TABLE unsent( @ rid INTEGER PRIMARY KEY -- Record ID of the phantom @ ); @ -@ -- Each baseline or manifest can have one or more tags. A tag +@ -- Each artifact 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 +@ -- the wiki page. Tickets changes are tagged with "ticket-HASH" where +@ -- HASH 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 tag( @ tagid INTEGER PRIMARY KEY, -- Numeric tag ID @@ -285,23 +380,25 @@ @ INSERT INTO tag VALUES(5, 'hidden'); -- TAG_HIDDEN @ INSERT INTO tag VALUES(6, 'private'); -- TAG_PRIVATE @ INSERT INTO tag VALUES(7, 'cluster'); -- TAG_CLUSTER @ INSERT INTO tag VALUES(8, 'branch'); -- TAG_BRANCH @ INSERT INTO tag VALUES(9, 'closed'); -- TAG_CLOSED +@ INSERT INTO tag VALUES(10,'parent'); -- TAG_PARENT +@ INSERT INTO tag VALUES(11,'note'); -- TAG_NOTE @ -@ -- Assignments of tags to baselines. Note that we allow tags to +@ -- Assignments of tags to artifacts. 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 tagxref( @ tagid INTEGER REFERENCES tag, -- The tag that added or removed -@ tagtype INTEGER, -- 0:cancel 1:single 2:branch +@ 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 +@ mtime TIMESTAMP, -- Time of addition or removal. Julian day @ rid INTEGER REFERENCE blob, -- Artifact tag is applied to @ UNIQUE(rid, tagid) @ ); @ CREATE INDEX tagxref_i1 ON tagxref(tagid, mtime); @ @@ -310,13 +407,13 @@ @ -- the following table for that hyperlink. This table is used to @ -- facilitate the display of "back links". @ -- @ CREATE TABLE 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 +@ srctype INT, -- 0=comment 1=ticket 2=wiki. See BKLNK_* below. +@ srcid INT, -- EVENT.OBJID for the source document +@ mtime TIMESTAMP, -- time that the hyperlink was added. Julian day. @ UNIQUE(target, srctype, srcid) @ ); @ CREATE INDEX backlink_src ON backlink(srcid, srctype); @ @ -- Each attachment is an entry in the following table. Only @@ -323,13 +420,13 @@ @ -- the most recent attachment (identified by the D card) is saved. @ -- @ CREATE TABLE attachment( @ attachid INTEGER PRIMARY KEY, -- Local id for this attachment @ isLatest BOOLEAN DEFAULT 0, -- True if this is the one to use -@ mtime TIMESTAMP, -- Time when attachment last changed -@ src TEXT, -- UUID of the attachment. NULL to delete -@ target TEXT, -- Object attached to. Wikiname or Tkt UUID +@ mtime TIMESTAMP, -- Last changed. Julian day. +@ src TEXT, -- Hash of the attachment. NULL to delete +@ target TEXT, -- Object attached to. Wikiname or Tkt hash @ filename TEXT, -- Filename for the attachment @ comment TEXT, -- Comment associated with this attachment @ user TEXT -- Name of user adding attachment @ ); @ CREATE INDEX attachment_idx1 ON attachment(target, filename, mtime); @@ -343,10 +440,11 @@ @ CREATE TABLE 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, @@ -355,34 +453,66 @@ @ private_contact TEXT, @ resolution TEXT, @ title TEXT, @ comment TEXT @ ); +@ CREATE TABLE 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 ticketchng_idx1 ON ticketchng(tkt_id, tkt_mtime); +@ +@ -- For tracking cherrypick merges +@ CREATE TABLE cherrypick( +@ parentid INT, +@ childid INT, +@ isExclude BOOLEAN DEFAULT false, +@ PRIMARY KEY(parentid, childid) +@ ) WITHOUT ROWID; +@ CREATE INDEX cherrypick_cid ON cherrypick(childid); ; +/* +** Allowed values for backlink.srctype +*/ +#if INTERFACE +# define BKLNK_COMMENT 0 /* Check-in comment */ +# define BKLNK_TICKET 1 /* Ticket body or title */ +# define BKLNK_WIKI 2 /* Wiki */ +# define BKLNK_EVENT 3 /* Technote */ +# define BKLNK_FORUM 4 /* Forum post */ +# define ValidBklnk(X) (X>=0 && X<=4) /* True if backlink.srctype is valid */ +#endif + /* ** Predefined tagid values */ #if INTERFACE # define TAG_BGCOLOR 1 /* Set the background color for display */ # define TAG_COMMENT 2 /* The check-in comment */ # define TAG_USER 3 /* User who made a checking */ # define TAG_DATE 4 /* The date of a check-in */ -# define TAG_HIDDEN 5 /* Do not display or sync */ -# define TAG_PRIVATE 6 /* Display but do not sync */ +# define TAG_HIDDEN 5 /* Do not display in timeline */ +# define TAG_PRIVATE 6 /* Do not sync */ # define TAG_CLUSTER 7 /* A cluster */ # define TAG_BRANCH 8 /* Value is name of the current branch */ # define TAG_CLOSED 9 /* Do not display this check-in as a leaf */ -#endif -#if EXPORT_INTERFACE -# define MAX_INT_TAG 9 /* The largest pre-assigned tag id */ +# define TAG_PARENT 10 /* Change to parentage on a check-in */ +# define TAG_NOTE 11 /* Extra text appended to a check-in comment */ #endif /* -** The schema for the locate FOSSIL database file found at the root -** of very check-out. This database contains the complete state of -** the checkout. +** The schema for the local FOSSIL database file found at the root +** of every check-out. This database contains the complete state of +** the checkout. See also the addendum in zLocalSchemaVmerge[]. */ const char zLocalSchema[] = @ -- 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 @@ -403,41 +533,90 @@ @ -- 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. -@ -- The difference between vfile.chnged==2 and a regular add is that -@ -- with vfile.chnged==2 we know that the current version of the file -@ -- is already in the repository. -@ -- +@ -- Vfile.chnged meaning: +@ -- 0 File is unmodified +@ -- 1 Manually edited and/or modified as part of a merge command +@ -- 2 Replaced by a merge command +@ -- 3 Added by a merge command +@ -- 4,5 Same as 2,3 except merge using --integrate @ -- @ CREATE TABLE 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 -@ deleted BOOLEAN DEFAULT 0, -- True if deleted +@ vid INTEGER REFERENCES blob, -- The checkin this file is part of. +@ chnged INT DEFAULT 0, -- 0:unchng 1:edit 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, -- Modification time of file on disk +@ 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 @ UNIQUE(pathname,vid) @ ); @ +@ -- Identifier for this file type. +@ -- The integer is the same as 'FSLC'. +@ PRAGMA application_id=252006674; +; + +/* Additional local database initialization following the schema +** enhancement of 2019-01-19, in which the mhash column was added +** to vmerge and vfile. +*/ +const char zLocalSchemaVmerge[] = @ -- 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 here means the version record itself. +@ -- 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 vmerge( @ id INTEGER REFERENCES vfile, -- VFILE entry that has been merged @ merge INTEGER, -- Merged with this record -@ UNIQUE(id, merge) +@ mhash TEXT -- SHA1/SHA3 hash for merge object +@ ); +@ CREATE UNIQUE INDEX 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 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; +@ +; + +/* +** The following table holds information about forum posts. It +** is created on-demand whenever the manifest parser encounters +** a forum-post artifact. +*/ +static const char zForumSchema[] = +@ CREATE TABLE repository.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 repository.forumthread ON forumpost(froot,fmtime); ; + +/* Create the forum-post schema if it does not already exist */ +void schema_forum(void){ + if( !db_table_exists("repository","forumpost") ){ + db_multi_exec("%s",zForumSchema/*safe-for-%s*/); + } +} ADDED src/scroll.js Index: src/scroll.js ================================================================== --- /dev/null +++ src/scroll.js @@ -0,0 +1,2 @@ +/* Cause the page to scroll so that the #scrollToMe is visible */ +(document.getElementById('scrollToMe')||document.body).scrollIntoView(true); Index: src/search.c ================================================================== --- src/search.c +++ src/search.c @@ -13,63 +13,66 @@ ** drh@hwaci.com ** http://www.hwaci.com/drh/ ** ******************************************************************************* ** -** This file contains code to implement the "/doc" web page and related -** pages. +** This file contains code to implement a search functions +** against timeline comments, check-in content, wiki pages, tickets, +** and/or forum posts. +** +** The search can be either a per-query "grep"-like search that scans +** the entire corpus. Or it can use the FTS4 or FTS5 search engine of +** SQLite. The choice is a administrator configuration option. +** +** The first option is referred to as "full-scan search". The second +** option is called "indexed search". +** +** The code in this file is ordered approximately as follows: +** +** (1) The full-scan search engine +** (2) The indexed search engine +** (3) Higher level interfaces that use either (1) or (b2) according +** to the current search configuration settings */ #include "config.h" #include "search.h" #include #if INTERFACE + +/* Maximum number of search terms for full-scan search */ +#define SEARCH_MAX_TERM 8 + /* -** A compiled search patter +** A compiled search pattern used for full-scan search. */ struct Search { - int nTerm; - struct srchTerm { - char *z; - int n; - } a[8]; + int nTerm; /* Number of search terms */ + struct srchTerm { /* For each search term */ + char *z; /* Text */ + int n; /* length */ + } a[SEARCH_MAX_TERM]; + /* Snippet controls */ + char *zPattern; /* The search pattern */ + char *zMarkBegin; /* Start of a match */ + char *zMarkEnd; /* End of a match */ + char *zMarkGap; /* A gap between two matches */ + unsigned fSrchFlg; /* Flags */ + int iScore; /* Score of the last match attempt */ + Blob snip; /* Snippet for the most recent match */ }; + +#define SRCHFLG_HTML 0x01 /* Escape snippet text for HTML */ +#define SRCHFLG_STATIC 0x04 /* The static gSearch object */ + #endif /* -** Compile a search pattern -*/ -Search *search_init(const char *zPattern){ - int nPattern = strlen(zPattern); - Search *p; - char *z; - int i; - - p = malloc( nPattern + sizeof(*p) + 1); - if( p==0 ) fossil_panic("out of memory"); - z = (char*)&p[1]; - strcpy(z, zPattern); - memset(p, 0, sizeof(*p)); - while( *z && p->nTerma)/sizeof(p->a[0]) ){ - while( !isalnum(*z) && *z ){ z++; } - if( *z==0 ) break; - p->a[p->nTerm].z = z; - for(i=1; isalnum(z[i]) || z[i]=='_'; i++){} - p->a[p->nTerm].n = i; - z += i; - p->nTerm++; - } - return p; -} - - -/* -** Destroy a search context. -*/ -void search_end(Search *p){ - free(p); -} +** There is a single global Search object: +*/ +static Search gSearch; + /* ** Theses characters constitute a word boundary */ static const char isBoundary[] = { @@ -88,124 +91,2016 @@ 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, }; +#define ISALNUM(x) (!isBoundary[(x)&0xff]) + + +/* +** Destroy a full-scan search context. +*/ +void search_end(Search *p){ + if( p ){ + fossil_free(p->zPattern); + fossil_free(p->zMarkBegin); + fossil_free(p->zMarkEnd); + fossil_free(p->zMarkGap); + if( p->iScore ) blob_reset(&p->snip); + memset(p, 0, sizeof(*p)); + if( p!=&gSearch ) fossil_free(p); + } +} + +/* +** Compile a full-scan search pattern +*/ +static Search *search_init( + const char *zPattern, /* The search pattern */ + const char *zMarkBegin, /* Start of a match */ + const char *zMarkEnd, /* End of a match */ + const char *zMarkGap, /* A gap between two matches */ + unsigned fSrchFlg /* Flags */ +){ + Search *p; + char *z; + int i; + + if( fSrchFlg & SRCHFLG_STATIC ){ + p = &gSearch; + search_end(p); + }else{ + p = fossil_malloc(sizeof(*p)); + memset(p, 0, sizeof(*p)); + } + p->zPattern = z = mprintf("%s", zPattern); + p->zMarkBegin = mprintf("%s", zMarkBegin); + p->zMarkEnd = mprintf("%s", zMarkEnd); + p->zMarkGap = mprintf("%s", zMarkGap); + p->fSrchFlg = fSrchFlg; + blob_init(&p->snip, 0, 0); + while( *z && p->nTerma[p->nTerm].z = z; + for(i=1; ISALNUM(z[i]); i++){} + p->a[p->nTerm].n = i; + z += i; + p->nTerm++; + } + return p; +} + /* -** Compare a search pattern against an input string and return a score. +** Append n bytes of text to snippet zTxt. Encode the text appropriately. +*/ +static void snippet_text_append( + Search *p, /* The search context */ + Blob *pSnip, /* Append to this snippet */ + const char *zTxt, /* Text to append */ + int n /* How many bytes to append */ +){ + if( n>0 ){ + if( p->fSrchFlg & SRCHFLG_HTML ){ + blob_appendf(pSnip, "%#h", n, zTxt); + }else{ + blob_append(pSnip, zTxt, n); + } + } +} + +/* This the core search engine for full-scan search. +** +** Compare a search pattern against one or more input strings which +** collectively comprise a document. Return a match score. Any +** postive value means there was a match. Zero means that one or +** more terms are missing. +** +** The score and a snippet are record for future use. ** ** Scoring: ** * All terms must match at least once or the score is zero -** * 10 bonus points if the first occurrance is an exact match -** * 1 additional point for each subsequent match of the same word -** * Extra points of two consecutive words of the pattern are consecutive +** * One point for each matching term +** * Extra points if consecutive words of the pattern are consecutive ** in the document */ -int search_score(Search *p, const char *zDoc){ - int iPrev = 999; - int score = 10; - int iBonus = 0; - int i, j; - unsigned char seen[8]; - - memset(seen, 0, sizeof(seen)); - for(i=0; zDoc[i]; i++){ - char c = zDoc[i]; - if( isBoundary[c&0xff] ) continue; - for(j=0; jnTerm; j++){ - int n = p->a[j].n; - if( sqlite3_strnicmp(p->a[j].z, &zDoc[i], n)==0 ){ - score += 1; - if( !seen[j] ){ - if( isBoundary[zDoc[i+n]&0xff] ) score += 10; - seen[j] = 1; - } - if( j==iPrev+1 ){ - score += iBonus; - } - i += n-1; - iPrev = j; - iBonus = 50; - break; - } - } - iBonus /= 2; - while( !isBoundary[zDoc[i]&0xff] ){ i++; } - } - - /* Every term must be seen or else the score is zero */ - for(j=0; jnTerm; j++){ - if( !seen[j] ) return 0; - } - +static int search_match( + Search *p, /* Search pattern and flags */ + int nDoc, /* Number of strings in this document */ + const char **azDoc /* Text of each string */ +){ + int score; /* Final score */ + int i; /* Offset into current document */ + int ii; /* Loop counter */ + int j; /* Loop over search terms */ + int k; /* Loop over prior terms */ + int iWord = 0; /* Current word number */ + int iDoc; /* Current document number */ + int wantGap = 0; /* True if a zMarkGap is wanted */ + const char *zDoc; /* Current document text */ + const int CTX = 50; /* Amount of snippet context */ + int anMatch[SEARCH_MAX_TERM]; /* Number of terms in best match */ + int aiBestDoc[SEARCH_MAX_TERM]; /* Document containing best match */ + int aiBestOfst[SEARCH_MAX_TERM]; /* Byte offset to start of best match */ + int aiLastDoc[SEARCH_MAX_TERM]; /* Document containing most recent match */ + int aiLastOfst[SEARCH_MAX_TERM]; /* Byte offset to the most recent match */ + int aiWordIdx[SEARCH_MAX_TERM]; /* Word index of most recent match */ + + memset(anMatch, 0, sizeof(anMatch)); + memset(aiWordIdx, 0xff, sizeof(aiWordIdx)); + for(iDoc=0; iDocnTerm; j++){ + int n = p->a[j].n; + if( sqlite3_strnicmp(p->a[j].z, &zDoc[i], n)==0 + && (!ISALNUM(zDoc[i+n]) || p->a[j].z[n]=='*') + ){ + aiWordIdx[j] = iWord; + aiLastDoc[j] = iDoc; + aiLastOfst[j] = i; + for(k=1; j-k>=0 && anMatch[j-k] && aiWordIdx[j-k]==iWord-k; k++){} + for(ii=0; iinTerm; j++) score *= anMatch[j]; + blob_reset(&p->snip); + p->iScore = score; + if( score==0 ) return score; + + + /* Prepare a snippet that describes the matching text. + */ + while(1){ + int iOfst; + int iTail; + int iBest; + for(ii=0; iinTerm && anMatch[ii]==0; ii++){} + if( ii>=p->nTerm ) break; /* This is where the loop exits */ + iBest = ii; + iDoc = aiBestDoc[ii]; + iOfst = aiBestOfst[ii]; + for(; iinTerm; ii++){ + if( anMatch[ii]==0 ) continue; + if( aiBestDoc[ii]>iDoc ) continue; + if( aiBestOfst[ii]>iOfst ) continue; + iDoc = aiBestDoc[ii]; + iOfst = aiBestOfst[ii]; + iBest = ii; + } + iTail = iOfst + p->a[iBest].n; + anMatch[iBest] = 0; + for(ii=0; iinTerm; ii++){ + if( anMatch[ii]==0 ) continue; + if( aiBestDoc[ii]!=iDoc ) continue; + if( aiBestOfst[ii]<=iTail+CTX*2 ){ + if( iTaila[ii].n ){ + iTail = aiBestOfst[ii]+p->a[ii].n; + } + anMatch[ii] = 0; + ii = -1; + continue; + } + } + zDoc = azDoc[iDoc]; + iOfst -= CTX; + if( iOfst<0 ) iOfst = 0; + while( iOfst>0 && ISALNUM(zDoc[iOfst-1]) ) iOfst--; + while( zDoc[iOfst] && !ISALNUM(zDoc[iOfst]) ) iOfst++; + for(ii=0; ii0 || wantGap ) blob_append(&p->snip, p->zMarkGap, -1); + wantGap = zDoc[iTail]!=0; + zDoc += iOfst; + iTail -= iOfst; + + /* Add a snippet segment using characters iOfst..iOfst+iTail from zDoc */ + for(i=0; inTerm; j++){ + int n = p->a[j].n; + if( sqlite3_strnicmp(p->a[j].z, &zDoc[i], n)==0 + && (!ISALNUM(zDoc[i+n]) || p->a[j].z[n]=='*') + ){ + snippet_text_append(p, &p->snip, zDoc, i); + zDoc += i; + iTail -= i; + blob_append(&p->snip, p->zMarkBegin, -1); + if( p->a[j].z[n]=='*' ){ + while( ISALNUM(zDoc[n]) ) n++; + } + snippet_text_append(p, &p->snip, zDoc, n); + zDoc += n; + iTail -= n; + blob_append(&p->snip, p->zMarkEnd, -1); + i = -1; + break; + } /* end-if */ + } /* end for(j) */ + if( jnTerm ){ + while( ISALNUM(zDoc[i]) && isnip, zDoc, iTail); + } + if( wantGap ) blob_append(&p->snip, p->zMarkGap, -1); return score; } /* -** This is an SQLite function that scores its input using -** a pre-computed pattern. +** COMMAND: test-match +** +** Usage: %fossil test-match SEARCHSTRING FILE1 FILE2 ... +** +** Run the full-scan search algorithm using SEARCHSTRING against +** the text of the files listed. Output matches and snippets. +** +** Options: +** +** --begin TEXT Text to insert before each match +** --end TEXT Text to insert after each match +** --gap TEXT Text to indicate elided content +** --html Input is HTML +** --static Use the static Search object +*/ +void test_match_cmd(void){ + Search *p; + int i; + Blob x; + int score; + char *zDoc; + int flg = 0; + char *zBegin = (char*)find_option("begin",0,1); + char *zEnd = (char*)find_option("end",0,1); + char *zGap = (char*)find_option("gap",0,1); + if( find_option("html",0,0)!=0 ) flg |= SRCHFLG_HTML; + if( find_option("static",0,0)!=0 ) flg |= SRCHFLG_STATIC; + verify_all_options(); + if( g.argc<4 ) usage("SEARCHSTRING FILE1..."); + if( zBegin==0 ) zBegin = "[["; + if( zEnd==0 ) zEnd = "]]"; + if( zGap==0 ) zGap = " ... "; + p = search_init(g.argv[2], zBegin, zEnd, zGap, flg); + for(i=3; iiScore); + blob_reset(&x); + if( score ){ + fossil_print("%.78c\n%s\n%.78c\n\n", '=', blob_str(&p->snip), '='); + } + } + search_end(p); +} + +/* +** An SQL function to initialize the full-scan search pattern: +** +** search_init(PATTERN,BEGIN,END,GAP,FLAGS) +** +** All arguments are optional. PATTERN is the search pattern. If it +** is omitted, then the global search pattern is reset. BEGIN and END +** and GAP are the strings used to construct snippets. FLAGS is an +** integer bit pattern containing the various SRCH_CKIN, SRCH_DOC, +** SRCH_TKT, SRCH_FORUM, or SRCH_ALL bits to determine what is to be +** searched. +*/ +static void search_init_sqlfunc( + sqlite3_context *context, + int argc, + sqlite3_value **argv +){ + const char *zPattern = 0; + const char *zBegin = ""; + const char *zEnd = ""; + const char *zGap = " ... "; + unsigned int flg = SRCHFLG_HTML; + switch( argc ){ + default: + flg = (unsigned int)sqlite3_value_int(argv[4]); + case 4: + zGap = (const char*)sqlite3_value_text(argv[3]); + case 3: + zEnd = (const char*)sqlite3_value_text(argv[2]); + case 2: + zBegin = (const char*)sqlite3_value_text(argv[1]); + case 1: + zPattern = (const char*)sqlite3_value_text(argv[0]); + } + if( zPattern && zPattern[0] ){ + search_init(zPattern, zBegin, zEnd, zGap, flg | SRCHFLG_STATIC); + }else{ + search_end(&gSearch); + } +} + +/* search_match(TEXT, TEXT, ....) +** +** Using the full-scan search engine created by the most recent call +** to search_init(), match the input the TEXT arguments. +** Remember the results in the global full-scan search object. +** Return non-zero on a match and zero on a miss. +*/ +static void search_match_sqlfunc( + sqlite3_context *context, + int argc, + sqlite3_value **argv +){ + const char *azDoc[5]; + int nDoc; + int rc; + for(nDoc=0; nDoc0 ){ + sqlite3_result_text(context, blob_str(&gSearch.snip), -1, fossil_free); + blob_init(&gSearch.snip, 0, 0); + } +} + +/* stext(TYPE, RID, ARG) +** +** This is an SQLite function that computes the searchable text. +** It is a wrapper around the search_stext() routine. See the +** search_stext() routine for further detail. +*/ +static void search_stext_sqlfunc( + sqlite3_context *context, + int argc, + sqlite3_value **argv +){ + const char *zType = (const char*)sqlite3_value_text(argv[0]); + int rid = sqlite3_value_int(argv[1]); + const char *zName = (const char*)sqlite3_value_text(argv[2]); + sqlite3_result_text(context, search_stext_cached(zType[0],rid,zName,0), -1, + SQLITE_TRANSIENT); +} + +/* title(TYPE, RID, ARG) +** +** Return the title of the document to be search. +*/ +static void search_title_sqlfunc( + sqlite3_context *context, + int argc, + sqlite3_value **argv +){ + const char *zType = (const char*)sqlite3_value_text(argv[0]); + int rid = sqlite3_value_int(argv[1]); + const char *zName = (const char*)sqlite3_value_text(argv[2]); + int nHdr = 0; + char *z = search_stext_cached(zType[0], rid, zName, &nHdr); + if( nHdr || zType[0]!='d' ){ + sqlite3_result_text(context, z, nHdr, SQLITE_TRANSIENT); + }else{ + sqlite3_result_value(context, argv[2]); + } +} + +/* body(TYPE, RID, ARG) +** +** Return the body of the document to be search. +*/ +static void search_body_sqlfunc( + sqlite3_context *context, + int argc, + sqlite3_value **argv +){ + const char *zType = (const char*)sqlite3_value_text(argv[0]); + int rid = sqlite3_value_int(argv[1]); + const char *zName = (const char*)sqlite3_value_text(argv[2]); + int nHdr = 0; + char *z = search_stext_cached(zType[0], rid, zName, &nHdr); + sqlite3_result_text(context, z+nHdr+1, -1, SQLITE_TRANSIENT); +} + +/* urlencode(X) +** +** Encode a string for use as a query parameter in a URL. This is +** the equivalent of printf("%T",X). +*/ +static void search_urlencode_sqlfunc( + sqlite3_context *context, + int argc, + sqlite3_value **argv +){ + char *z = mprintf("%T",sqlite3_value_text(argv[0])); + sqlite3_result_text(context, z, -1, fossil_free); } /* -** Register the "score()" SQL function to score its input text -** using the given Search object. Once this function is registered, -** do not delete the Search object. +** Register the various SQL functions (defined above) needed to implement +** full-scan search. */ -void search_sql_setup(Search *p){ - sqlite3_create_function(g.db, "score", 1, SQLITE_UTF8, p, +void search_sql_setup(sqlite3 *db){ + static int once = 0; + static const int enc = SQLITE_UTF8|SQLITE_INNOCUOUS; + if( once++ ) return; + sqlite3_create_function(db, "search_match", -1, enc, 0, + search_match_sqlfunc, 0, 0); + sqlite3_create_function(db, "search_score", 0, enc, 0, search_score_sqlfunc, 0, 0); + sqlite3_create_function(db, "search_snippet", 0, enc, 0, + search_snippet_sqlfunc, 0, 0); + sqlite3_create_function(db, "search_init", -1, enc, 0, + search_init_sqlfunc, 0, 0); + sqlite3_create_function(db, "stext", 3, enc, 0, + search_stext_sqlfunc, 0, 0); + sqlite3_create_function(db, "title", 3, enc, 0, + search_title_sqlfunc, 0, 0); + sqlite3_create_function(db, "body", 3, enc, 0, + search_body_sqlfunc, 0, 0); + sqlite3_create_function(db, "urlencode", 1, enc, 0, + search_urlencode_sqlfunc, 0, 0); } /* ** Testing the search function. ** -** COMMAND: search -** %fossil search pattern... +** COMMAND: search* +** +** Usage: %fossil search [-a|-all] [-n|-limit #] [-W|-width #] pattern... +** +** Search for timeline entries matching all words provided on the +** command line. Whole-word matches scope more highly than partial +** matches. +** +** Note: The command only search the EVENT table. So it will only +** display check-in comments or other comments that appear on an +** unaugmented timeline. It does not search document text or forum +** messages. +** +** Outputs, by default, some top-N fraction of the results. The -all +** option can be used to output all matches, regardless of their search +** score. The -limit option can be used to limit the number of entries +** returned. The -width option can be used to set the output width used +** when printing matches. +** +** Options: ** -** Search for timeline entries matching the pattern. +** -a|--all Output all matches, not just best matches. +** -n|--limit N Limit output to N matches. +** -W|--width WIDTH Set display width to WIDTH columns, 0 for +** unlimited. Defaults the terminal's width. */ void search_cmd(void){ - Search *p; Blob pattern; int i; + Blob sql = empty_blob; Stmt q; int iBest; + char fAll = NULL != find_option("all", "a", 0); /* If set, do not lop + off the end of the + results. */ + const char *zLimit = find_option("limit","n",1); + const char *zWidth = find_option("width","W",1); + int nLimit = zLimit ? atoi(zLimit) : -1000; /* Max number of matching + lines/entries to list */ + int width; + if( zWidth ){ + width = atoi(zWidth); + if( (width!=0) && (width<=20) ){ + fossil_fatal("-W|--width value must be >20 or 0"); + } + }else{ + width = -1; + } - db_must_be_within_tree(); - if( g.argc<2 ) return; + db_find_and_open_repository(0, 0); + if( g.argc<3 ) return; blob_init(&pattern, g.argv[2], -1); for(i=3; i0;" + " WHERE blob.rid=event.objid" + " AND search_match(coalesce(ecomment,comment));" ); iBest = db_int(0, "SELECT max(x) FROM srch"); - db_prepare(&q, - "SELECT rid, uuid, date, comment, 0, 0 FROM srch" - " WHERE x>%d ORDER BY x DESC, date DESC", - iBest/3 + blob_append(&sql, + "SELECT rid, uuid, date, comment, 0, 0 FROM srch " + "WHERE 1 ", -1); + if(!fAll){ + blob_append_sql(&sql,"AND x>%d ", iBest/3); + } + blob_append(&sql, "ORDER BY x DESC, date DESC ", -1); + db_prepare(&q, "%s", blob_sql_text(&sql)); + blob_reset(&sql); + print_timeline(&q, nLimit, width, 0, 0); + db_finalize(&q); +} + +#if INTERFACE +/* What to search for */ +#define SRCH_CKIN 0x0001 /* Search over check-in comments */ +#define SRCH_DOC 0x0002 /* Search over embedded documents */ +#define SRCH_TKT 0x0004 /* Search over tickets */ +#define SRCH_WIKI 0x0008 /* Search over wiki */ +#define SRCH_TECHNOTE 0x0010 /* Search over tech notes */ +#define SRCH_FORUM 0x0020 /* Search over forum messages */ +#define SRCH_ALL 0x003f /* Search over everything */ +#endif + +/* +** Remove bits from srchFlags which are disallowed by either the +** current server configuration or by user permissions. Return +** the revised search flags mask. +*/ +unsigned int search_restrict(unsigned int srchFlags){ + static unsigned int knownGood = 0; + static unsigned int knownBad = 0; + static const struct { unsigned m; const char *zKey; } aSetng[] = { + { SRCH_CKIN, "search-ci" }, + { SRCH_DOC, "search-doc" }, + { SRCH_TKT, "search-tkt" }, + { SRCH_WIKI, "search-wiki" }, + { SRCH_TECHNOTE, "search-technote" }, + { SRCH_FORUM, "search-forum" }, + }; + int i; + if( g.perm.Read==0 ) srchFlags &= ~(SRCH_CKIN|SRCH_DOC|SRCH_TECHNOTE); + if( g.perm.RdTkt==0 ) srchFlags &= ~(SRCH_TKT); + if( g.perm.RdWiki==0 ) srchFlags &= ~(SRCH_WIKI); + if( g.perm.RdForum==0) srchFlags &= ~(SRCH_FORUM); + for(i=0; i", "", " ... ", + SRCHFLG_STATIC|SRCHFLG_HTML); + if( (srchFlags & SRCH_DOC)!=0 ){ + char *zDocGlob = db_get("doc-glob",""); + char *zDocBr = db_get("doc-branch","trunk"); + if( zDocGlob && zDocGlob[0] && zDocBr && zDocBr[0] ){ + db_multi_exec( + "CREATE VIRTUAL TABLE IF NOT EXISTS temp.foci USING files_of_checkin;" + ); + db_multi_exec( + "INSERT INTO x(label,url,score,id,date,snip)" + " SELECT printf('Document: %%s',title('d',blob.rid,foci.filename))," + " printf('/doc/%T/%%s',foci.filename)," + " search_score()," + " 'd'||blob.rid," + " (SELECT datetime(event.mtime) FROM event" + " WHERE objid=symbolic_name_to_rid('trunk'))," + " search_snippet()" + " FROM foci CROSS JOIN blob" + " WHERE checkinID=symbolic_name_to_rid('trunk')" + " AND blob.uuid=foci.uuid" + " AND search_match(title('d',blob.rid,foci.filename)," + " body('d',blob.rid,foci.filename))" + " AND %z", + zDocBr, glob_expr("foci.filename", zDocGlob) + ); + } + } + if( (srchFlags & SRCH_WIKI)!=0 ){ + db_multi_exec( + "WITH wiki(name,rid,mtime) AS (" + " SELECT substr(tagname,6), tagxref.rid, max(tagxref.mtime)" + " FROM tag, tagxref" + " WHERE tag.tagname GLOB 'wiki-*'" + " AND tagxref.tagid=tag.tagid" + " GROUP BY 1" + ")" + "INSERT INTO x(label,url,score,id,date,snip)" + " SELECT printf('Wiki: %%s',name)," + " printf('/wiki?name=%%s',urlencode(name))," + " search_score()," + " 'w'||rid," + " datetime(mtime)," + " search_snippet()" + " FROM wiki" + " WHERE search_match(title('w',rid,name),body('w',rid,name));" + ); + } + if( (srchFlags & SRCH_CKIN)!=0 ){ + db_multi_exec( + "WITH ckin(uuid,rid,mtime) AS (" + " SELECT blob.uuid, event.objid, event.mtime" + " FROM event, blob" + " WHERE event.type='ci'" + " AND blob.rid=event.objid" + ")" + "INSERT INTO x(label,url,score,id,date,snip)" + " SELECT printf('Check-in [%%.10s] on %%s',uuid,datetime(mtime))," + " printf('/timeline?c=%%s',uuid)," + " search_score()," + " 'c'||rid," + " datetime(mtime)," + " search_snippet()" + " FROM ckin" + " WHERE search_match('',body('c',rid,NULL));" + ); + } + if( (srchFlags & SRCH_TKT)!=0 ){ + db_multi_exec( + "INSERT INTO x(label,url,score,id,date,snip)" + " SELECT printf('Ticket: %%s (%%s)',title('t',tkt_id,NULL)," + "datetime(tkt_mtime))," + " printf('/tktview/%%.20s',tkt_uuid)," + " search_score()," + " 't'||tkt_id," + " datetime(tkt_mtime)," + " search_snippet()" + " FROM ticket" + " WHERE search_match(title('t',tkt_id,NULL),body('t',tkt_id,NULL));" + ); + } + if( (srchFlags & SRCH_TECHNOTE)!=0 ){ + db_multi_exec( + "WITH technote(uuid,rid,mtime) AS (" + " SELECT substr(tagname,7), tagxref.rid, max(tagxref.mtime)" + " FROM tag, tagxref" + " WHERE tag.tagname GLOB 'event-*'" + " AND tagxref.tagid=tag.tagid" + " GROUP BY 1" + ")" + "INSERT INTO x(label,url,score,id,date,snip)" + " SELECT printf('Tech Note: %%s',uuid)," + " printf('/technote/%%s',uuid)," + " search_score()," + " 'e'||rid," + " datetime(mtime)," + " search_snippet()" + " FROM technote" + " WHERE search_match('',body('e',rid,NULL));" + ); + } + if( (srchFlags & SRCH_FORUM)!=0 ){ + db_multi_exec( + "INSERT INTO x(label,url,score,id,date,snip)" + " SELECT 'Forum '||comment," + " '/forumpost/'||uuid," + " search_score()," + " 'f'||rid," + " datetime(event.mtime)," + " search_snippet()" + " FROM event JOIN blob on event.objid=blob.rid" + " WHERE search_match('',body('f',rid,NULL));" + ); + } +} + +/* +** Number of significant bits in a u32 +*/ +static int nbits(u32 x){ + int n = 0; + while( x ){ n++; x >>= 1; } + return n; +} + +/* +** Implemenation of the rank() function used with rank(matchinfo(*,'pcsx')). +*/ +static void search_rank_sqlfunc( + sqlite3_context *context, + int argc, + sqlite3_value **argv +){ + const unsigned *aVal = (unsigned int*)sqlite3_value_blob(argv[0]); + int nVal = sqlite3_value_bytes(argv[0])/4; + int nCol; /* Number of columns in the index */ + int nTerm; /* Number of search terms in the query */ + int i, j; /* Loop counter */ + double r = 0.0; /* Score */ + const unsigned *aX, *aS; + + if( nVal<2 ) return; + nTerm = aVal[0]; + nCol = aVal[1]; + if( nVal<2+3*nCol*nTerm+nCol ) return; + aS = aVal+2; + aX = aS+nCol; + for(j=0; j0 ){ + x = 0.0; + for(i=0; i','',' ... ',-1,35)" + " FROM ftsidx CROSS JOIN ftsdocs" + " WHERE ftsidx MATCH %Q" + " AND ftsdocs.rowid=ftsidx.docid", + zPat + ); + fossil_free(zPat); + if( srchFlags!=SRCH_ALL ){ + const char *zSep = " AND ("; + static const struct { unsigned m; char c; } aMask[] = { + { SRCH_CKIN, 'c' }, + { SRCH_DOC, 'd' }, + { SRCH_TKT, 't' }, + { SRCH_WIKI, 'w' }, + { SRCH_TECHNOTE, 'e' }, + { SRCH_FORUM, 'f' }, + }; + int i; + for(i=0; iTEXT" where TEXT contains +** no white-space or punctuation, then return the length of the mark. +*/ +static int isSnippetMark(const char *z){ + int n; + if( strncmp(z,"",6)!=0 ) return 0; + n = 6; + while( fossil_isalnum(z[n]) ) n++; + if( strncmp(&z[n],"",7)!=0 ) return 0; + return n+7; +} + +/* +** Return a copy of zSnip (in memory obtained from fossil_malloc()) that +** has all "<" characters, other than those on and , +** converted into "<". This is similar to htmlize() except that +** and are preserved. +*/ +static char *cleanSnippet(const char *zSnip){ + int i; + int n = 0; + char *z; + if( zSnip==0 ) zSnip = ""; + for(i=0; zSnip[i]; i++) if( zSnip[i]=='<' ) n++; + z = fossil_malloc( i+n*4+1 ); + i = 0; + while( zSnip[0] ){ + if( zSnip[0]=='<' ){ + n = isSnippetMark(zSnip); + if( n ){ + memcpy(&z[i], zSnip, n); + zSnip += n; + i += n; + continue; + }else{ + memcpy(&z[i], "<", 4); + i += 4; + zSnip++; + } + }else{ + z[i++] = zSnip[0]; + zSnip++; + } + } + z[i] = 0; + return z; +} + + +/* +** This routine generates web-page output for a search operation. +** Other web-pages can invoke this routine to add search results +** in the middle of the page. +** +** This routine works for both full-scan and indexed search. The +** appropriate low-level search routine is called according to the +** current configuration. +** +** Return the number of rows. +*/ +int search_run_and_output( + const char *zPattern, /* The query pattern */ + unsigned int srchFlags, /* What to search over */ + int fDebug /* Extra debugging output */ +){ + Stmt q; + int nRow = 0; + int nLimit = db_get_int("search-limit", 100); + + if( P("searchlimit")!=0 ){ + nLimit = atoi(P("searchlimit")); + } + srchFlags = search_restrict(srchFlags); + if( srchFlags==0 ) return 0; + search_sql_setup(g.db); + add_content_sql_commands(g.db); + db_multi_exec( + "CREATE TEMP TABLE x(label,url,score,id,date,snip);" + ); + if( !search_index_exists() ){ + search_fullscan(zPattern, srchFlags); /* Full-scan search */ + }else{ + search_update_index(srchFlags); /* Update the index, if necessary */ + search_indexed(zPattern, srchFlags); /* Indexed search */ + } + db_prepare(&q, "SELECT url, snip, label, score, id, substr(date,1,10)" + " FROM x" + " ORDER BY score DESC, date DESC;"); + while( db_step(&q)==SQLITE_ROW ){ + const char *zUrl = db_column_text(&q, 0); + const char *zSnippet = db_column_text(&q, 1); + const char *zLabel = db_column_text(&q, 2); + const char *zDate = db_column_text(&q, 5); + if( nRow==0 ){ + @
        + } + nRow++; + @
      1. %h(zLabel) + if( fDebug ){ + @ (%e(db_column_double(&q,3)), %s(db_column_text(&q,4)) + } + @
        %z(cleanSnippet(zSnippet)) \ + if( zDate && zDate[0] && strstr(zLabel,zDate)==0 ){ + @ (%h(zDate)) + } + @

      2. + if( nLimit && nRow>=nLimit ) break; + } + db_finalize(&q); + if( nRow ){ + @
      + } + return nRow; +} + +/* +** Generate some HTML for doing search. At a minimum include the +** Search-Text entry form. If the "s" query parameter is present, also +** show search results. +** +** The srchFlags parameter restricts the set of documents to be searched. +** srchFlags should normally be either a single search category or all +** categories. Any srchFlags with two or more bits set +** is treated like SRCH_ALL for display purposes. +** +** This routine automatically restricts srchFlag according to user +** permissions and the server configuration. The entry box is shown +** disabled if srchFlags is 0 after these restrictions are applied. +** +** The mFlags value controls options: +** +** 0x01 If the y= query parameter is present, use it as an addition +** restriction what to search. +** +** 0x02 Show nothing if search is disabled. +** +** Return true if there are search results. +*/ +int search_screen(unsigned srchFlags, int mFlags){ + const char *zType = 0; + const char *zClass = 0; + const char *zDisable1; + const char *zDisable2; + const char *zPattern; + int fDebug = PB("debug"); + int haveResult = 0; + srchFlags = search_restrict(srchFlags); + switch( srchFlags ){ + case SRCH_CKIN: zType = " Check-ins"; zClass = "Ckin"; break; + case SRCH_DOC: zType = " Docs"; zClass = "Doc"; break; + case SRCH_TKT: zType = " Tickets"; zClass = "Tkt"; break; + case SRCH_WIKI: zType = " Wiki"; zClass = "Wiki"; break; + case SRCH_TECHNOTE: zType = " Tech Notes"; zClass = "Note"; break; + case SRCH_FORUM: zType = " Forum"; zClass = "Frm"; break; + } + if( srchFlags==0 ){ + if( mFlags & 0x02 ) return 0; + zDisable1 = " disabled"; + zDisable2 = " disabled"; + zPattern = ""; + }else{ + zDisable1 = ""; /* Was: " autofocus" */ + zDisable2 = ""; + zPattern = PD("s",""); + } + @
      + if( zClass ){ + @
      + }else{ + @
      + } + @ + if( (mFlags & 0x01)!=0 && (srchFlags & (srchFlags-1))!=0 ){ + static const struct { const char *z; const char *zNm; unsigned m; } aY[] = { + { "all", "All", SRCH_ALL }, + { "c", "Check-ins", SRCH_CKIN }, + { "d", "Docs", SRCH_DOC }, + { "t", "Tickets", SRCH_TKT }, + { "w", "Wiki", SRCH_WIKI }, + { "e", "Tech Notes", SRCH_TECHNOTE }, + { "f", "Forum", SRCH_FORUM }, + }; + const char *zY = PD("y","all"); + unsigned newFlags = srchFlags; + int i; + @ + srchFlags = newFlags; + } + if( fDebug ){ + @ + } + @ + if( srchFlags==0 ){ + @

      Search is disabled

      + } + @
      + while( fossil_isspace(zPattern[0]) ) zPattern++; + if( zPattern[0] ){ + if( zClass ){ + @
      + }else{ + @
      + } + if( search_run_and_output(zPattern, srchFlags, fDebug)==0 ){ + @

      No matches for: %h(zPattern)

      + } + @
      + haveResult = 1; + } + return haveResult; +} + +/* +** WEBPAGE: search +** +** Search for check-in comments, documents, tickets, or wiki that +** match a user-supplied pattern. +** +** s=PATTERN Specify the full-text pattern to search for +** y=TYPE What to search. +** c -> check-ins +** d -> documentation +** t -> tickets +** w -> wiki +** e -> tech notes +** f -> forum +** all -> everything +*/ +void search_page(void){ + login_check_credentials(); + style_header("Search"); + search_screen(SRCH_ALL, 1); + style_finish_page(); +} + + +/* +** This is a helper function for search_stext(). Writing into pOut +** the search text obtained from pIn according to zMimetype. +** +** The title of the document is the first line of text. All subsequent +** lines are the body. If the document has no title, the first line +** is blank. +*/ +static void get_stext_by_mimetype( + Blob *pIn, + const char *zMimetype, + Blob *pOut +){ + Blob html, title; + blob_init(&html, 0, 0); + blob_init(&title, 0, 0); + if( zMimetype==0 ) zMimetype = "text/plain"; + if( fossil_strcmp(zMimetype,"text/x-fossil-wiki")==0 ){ + Blob tail; + blob_init(&tail, 0, 0); + if( wiki_find_title(pIn, &title, &tail) ){ + blob_appendf(pOut, "%s\n", blob_str(&title)); + wiki_convert(&tail, &html, 0); + blob_reset(&tail); + }else{ + blob_append(pOut, "\n", 1); + wiki_convert(pIn, &html, 0); + } + html_to_plaintext(blob_str(&html), pOut); + }else if( fossil_strcmp(zMimetype,"text/x-markdown")==0 ){ + markdown_to_html(pIn, &title, &html); + if( blob_size(&title) ){ + blob_appendf(pOut, "%s\n", blob_str(&title)); + }else{ + blob_append(pOut, "\n", 1); + } + html_to_plaintext(blob_str(&html), pOut); + }else if( fossil_strcmp(zMimetype,"text/html")==0 ){ + if( doc_is_embedded_html(pIn, &title) ){ + blob_appendf(pOut, "%s\n", blob_str(&title)); + } + html_to_plaintext(blob_str(pIn), pOut); + }else{ + blob_append(pOut, "\n", 1); + blob_append(pOut, blob_buffer(pIn), blob_size(pIn)); + } + blob_reset(&html); + blob_reset(&title); +} + +/* +** Query pQuery is pointing at a single row of output. Append a text +** representation of every text-compatible column to pAccum. +*/ +static void append_all_ticket_fields(Blob *pAccum, Stmt *pQuery, int iTitle){ + int n = db_column_count(pQuery); + int i; + const char *zMime = 0; + if( iTitle>=0 && iTitlezThreadTitle ){ + blob_appendf(&wiki, "

      %h

      \n", pWiki->zThreadTitle); + } + blob_appendf(&wiki, "From %s:\n\n%s", pWiki->zUser, pWiki->zWiki); + }else{ + blob_init(&wiki, pWiki->zWiki, -1); + } + get_stext_by_mimetype(&wiki, wiki_filter_mimetypes(pWiki->zMimetype), + pOut); + blob_reset(&wiki); + manifest_destroy(pWiki); + break; + } + case 'c': { /* Check-in Comments */ + static Stmt q; + static int isPlainText = -1; + db_static_prepare(&q, + "SELECT coalesce(ecomment,comment)" + " ||' (user: '||coalesce(euser,user,'?')" + " ||', tags: '||" + " (SELECT group_concat(substr(tag.tagname,5),',')" + " FROM tag, tagxref" + " WHERE tagname GLOB 'sym-*' AND tag.tagid=tagxref.tagid" + " AND tagxref.rid=event.objid AND tagxref.tagtype>0)" + " ||')'" + " FROM event WHERE objid=:x AND type='ci'"); + if( isPlainText<0 ){ + isPlainText = db_get_boolean("timeline-plaintext",0); + } + db_bind_int(&q, ":x", rid); + if( db_step(&q)==SQLITE_ROW ){ + blob_append(pOut, "\n", 1); + if( isPlainText ){ + db_column_blob(&q, 0, pOut); + }else{ + Blob x; + blob_init(&x,0,0); + db_column_blob(&q, 0, &x); + get_stext_by_mimetype(&x, "text/x-fossil-wiki", pOut); + blob_reset(&x); + } + } + db_reset(&q); + break; + } + case 't': { /* Tickets */ + static Stmt q1; + static int iTitle = -1; + db_static_prepare(&q1, "SELECT * FROM ticket WHERE tkt_id=:rid"); + db_bind_int(&q1, ":rid", rid); + if( db_step(&q1)==SQLITE_ROW ){ + if( iTitle<0 ){ + int n = db_column_count(&q1); + for(iTitle=0; iTitle0 ){ + blob_reset(&cache.stext); + }else{ + blob_init(&cache.stext,0,0); + } + cache.cType = cType; + cache.rid = rid; + if( cType==0 ) return 0; + search_stext(cType, rid, zName, &cache.stext); + z = blob_str(&cache.stext); + for(i=0; z[i] && z[i]!='\n'; i++){} + cache.nTitle = i; + } + if( pnTitle ) *pnTitle = cache.nTitle; + return blob_str(&cache.stext); +} + +/* +** COMMAND: test-search-stext +** +** Usage: fossil test-search-stext TYPE RID NAME +** +** Compute the search text for document TYPE-RID whose name is NAME. +** The TYPE is one of "c", "d", "t", "w", or "e". The RID is the document +** ID. The NAME is used to figure out a mimetype to use for formatting +** the raw document text. +*/ +void test_search_stext(void){ + Blob out; + db_find_and_open_repository(0,0); + if( g.argc!=5 ) usage("TYPE RID NAME"); + search_stext(g.argv[2][0], atoi(g.argv[3]), g.argv[4], &out); + fossil_print("%s\n",blob_str(&out)); + blob_reset(&out); +} + +/* +** COMMAND: test-convert-stext +** +** Usage: fossil test-convert-stext FILE MIMETYPE +** +** Read the content of FILE and convert it to stext according to MIMETYPE. +** Send the result to standard output. +*/ +void test_convert_stext(void){ + Blob in, out; + db_find_and_open_repository(0,0); + if( g.argc!=4 ) usage("FILENAME MIMETYPE"); + blob_read_from_file(&in, g.argv[2], ExtFILE); + blob_init(&out, 0, 0); + get_stext_by_mimetype(&in, g.argv[3], &out); + fossil_print("%s\n",blob_str(&out)); + blob_reset(&in); + blob_reset(&out); +} + +/* The schema for the full-text index +*/ +static const char zFtsSchema[] = +@ -- One entry for each possible search result +@ CREATE TABLE IF NOT EXISTS repository.ftsdocs( +@ rowid INTEGER PRIMARY KEY, -- Maps to the ftsidx.docid +@ type CHAR(1), -- Type of document +@ rid INTEGER, -- BLOB.RID or TAG.TAGID for the document +@ name TEXT, -- Additional document description +@ idxed BOOLEAN, -- True if currently in the index +@ label TEXT, -- Label to print on search results +@ url TEXT, -- URL to access this document +@ mtime DATE, -- Date when document created +@ bx TEXT, -- Temporary "body" content cache +@ UNIQUE(type,rid) +@ ); +@ CREATE INDEX repository.ftsdocIdxed ON ftsdocs(type,rid,name) WHERE idxed==0; +@ CREATE INDEX repository.ftsdocName ON ftsdocs(name) WHERE type='w'; +@ CREATE VIEW IF NOT EXISTS repository.ftscontent AS +@ SELECT rowid, type, rid, name, idxed, label, url, mtime, +@ title(type,rid,name) AS 'title', body(type,rid,name) AS 'body' +@ FROM ftsdocs; +@ CREATE VIRTUAL TABLE IF NOT EXISTS repository.ftsidx +@ USING fts4(content="ftscontent", title, body%s); +; +static const char zFtsDrop[] = +@ DROP TABLE IF EXISTS repository.ftsidx; +@ DROP VIEW IF EXISTS repository.ftscontent; +@ DROP TABLE IF EXISTS repository.ftsdocs; +; + +/* +** Create or drop the tables associated with a full-text index. +*/ +static int searchIdxExists = -1; +void search_create_index(void){ + int useStemmer = db_get_boolean("search-stemmer",0); + const char *zExtra = useStemmer ? ",tokenize=porter" : ""; + search_sql_setup(g.db); + db_multi_exec(zFtsSchema/*works-like:"%s"*/, zExtra/*safe-for-%s*/); + searchIdxExists = 1; +} +void search_drop_index(void){ + db_multi_exec(zFtsDrop/*works-like:""*/); + searchIdxExists = 0; +} + +/* +** Return true if the full-text search index exists +*/ +int search_index_exists(void){ + if( searchIdxExists<0 ){ + searchIdxExists = db_table_exists("repository","ftsdocs"); + } + return searchIdxExists; +} + +/* +** Fill the FTSDOCS table with unindexed entries for everything +** in the repository. This uses INSERT OR IGNORE so entries already +** in FTSDOCS are unchanged. +*/ +void search_fill_index(void){ + if( !search_index_exists() ) return; + search_sql_setup(g.db); + db_multi_exec( + "INSERT OR IGNORE INTO ftsdocs(type,rid,idxed)" + " SELECT 'c', objid, 0 FROM event WHERE type='ci';" + ); + db_multi_exec( + "WITH latest_wiki(rid,name,mtime) AS (" + " SELECT tagxref.rid, substr(tag.tagname,6), max(tagxref.mtime)" + " FROM tag, tagxref" + " WHERE tag.tagname GLOB 'wiki-*'" + " AND tagxref.tagid=tag.tagid" + " AND tagxref.value>0" + " GROUP BY 2" + ") INSERT OR IGNORE INTO ftsdocs(type,rid,name,idxed)" + " SELECT 'w', rid, name, 0 FROM latest_wiki;" + ); + db_multi_exec( + "INSERT OR IGNORE INTO ftsdocs(type,rid,idxed)" + " SELECT 't', tkt_id, 0 FROM ticket;" + ); + db_multi_exec( + "INSERT OR IGNORE INTO ftsdocs(type,rid,name,idxed)" + " SELECT type, objid, comment, 0 FROM event WHERE type IN ('e','f');" + ); +} + +/* +** The document described by cType,rid,zName is about to be added or +** updated. If the document has already been indexed, then unindex it +** now while we still have access to the old content. Add the document +** to the queue of documents that need to be indexed or reindexed. +*/ +void search_doc_touch(char cType, int rid, const char *zName){ + if( search_index_exists() && !content_is_private(rid) ){ + char zType[2]; + zType[0] = cType; + zType[1] = 0; + search_sql_setup(g.db); + db_multi_exec( + "DELETE FROM ftsidx WHERE docid IN" + " (SELECT rowid FROM ftsdocs WHERE type=%Q AND rid=%d AND idxed)", + zType, rid + ); + db_multi_exec( + "REPLACE INTO ftsdocs(type,rid,name,idxed)" + " VALUES(%Q,%d,%Q,0)", + zType, rid, zName + ); + if( cType=='w' || cType=='e' ){ + db_multi_exec( + "DELETE FROM ftsidx WHERE docid IN" + " (SELECT rowid FROM ftsdocs WHERE type='%c' AND name=%Q AND idxed)", + cType, zName + ); + db_multi_exec( + "DELETE FROM ftsdocs WHERE type='%c' AND name=%Q AND rid!=%d", + cType, zName, rid + ); + } + /* All forum posts are always indexed */ + } +} + +/* +** If the doc-glob and doc-br settings are valid for document search +** and if the latest check-in on doc-br is in the unindexed set of +** check-ins, then update all 'd' entries in FTSDOCS that have +** changed. +*/ +static void search_update_doc_index(void){ + const char *zDocBr = db_get("doc-branch","trunk"); + int ckid = zDocBr ? symbolic_name_to_rid(zDocBr,"ci") : 0; + double rTime; + if( ckid==0 ) return; + if( !db_exists("SELECT 1 FROM ftsdocs WHERE type='c' AND rid=%d" + " AND NOT idxed", ckid) ) return; + + /* If we get this far, it means that changes to 'd' entries are + ** required. */ + rTime = db_double(0.0, "SELECT mtime FROM event WHERE objid=%d", ckid); + db_multi_exec( + "CREATE TEMP TABLE current_docs(rid INTEGER PRIMARY KEY, name);" + "CREATE VIRTUAL TABLE IF NOT EXISTS temp.foci USING files_of_checkin;" + "INSERT OR IGNORE INTO current_docs(rid, name)" + " SELECT blob.rid, foci.filename FROM foci, blob" + " WHERE foci.checkinID=%d AND blob.uuid=foci.uuid" + " AND %z", + ckid, glob_expr("foci.filename", db_get("doc-glob","")) + ); + db_multi_exec( + "DELETE FROM ftsidx WHERE docid IN" + " (SELECT rowid FROM ftsdocs WHERE type='d'" + " AND rid NOT IN (SELECT rid FROM current_docs))" + ); + db_multi_exec( + "DELETE FROM ftsdocs WHERE type='d'" + " AND rid NOT IN (SELECT rid FROM current_docs)" + ); + db_multi_exec( + "INSERT OR IGNORE INTO ftsdocs(type,rid,name,idxed,label,bx,url,mtime)" + " SELECT 'd', rid, name, 0," + " title('d',rid,name)," + " body('d',rid,name)," + " printf('/doc/%T/%%s',urlencode(name))," + " %.17g" + " FROM current_docs", + zDocBr, rTime + ); + db_multi_exec( + "INSERT INTO ftsidx(docid,title,body)" + " SELECT rowid, label, bx FROM ftsdocs WHERE type='d' AND NOT idxed" + ); + db_multi_exec( + "UPDATE ftsdocs SET" + " idxed=1," + " bx=NULL," + " label='Document: '||label" + " WHERE type='d' AND NOT idxed" + ); +} + +/* +** Deal with all of the unindexed 'c' terms in FTSDOCS +*/ +static void search_update_checkin_index(void){ + db_multi_exec( + "INSERT INTO ftsidx(docid,title,body)" + " SELECT rowid, '', body('c',rid,NULL) FROM ftsdocs" + " WHERE type='c' AND NOT idxed;" + ); + db_multi_exec( + "UPDATE ftsdocs SET idxed=1, name=NULL," + " (label,url,mtime) = " + " (SELECT printf('Check-in [%%.16s] on %%s',blob.uuid," + " datetime(event.mtime))," + " printf('/timeline?y=ci&c=%%.20s',blob.uuid)," + " event.mtime" + " FROM event, blob" + " WHERE event.objid=ftsdocs.rid" + " AND blob.rid=ftsdocs.rid)" + "WHERE ftsdocs.type='c' AND NOT ftsdocs.idxed" + ); +} + +/* +** Deal with all of the unindexed 't' terms in FTSDOCS +*/ +static void search_update_ticket_index(void){ + db_multi_exec( + "INSERT INTO ftsidx(docid,title,body)" + " SELECT rowid, title('t',rid,NULL), body('t',rid,NULL) FROM ftsdocs" + " WHERE type='t' AND NOT idxed;" + ); + if( db_changes()==0 ) return; + db_multi_exec( + "UPDATE ftsdocs SET idxed=1, name=NULL," + " (label,url,mtime) =" + " (SELECT printf('Ticket: %%s (%%s)',title('t',tkt_id,null)," + " datetime(tkt_mtime))," + " printf('/tktview/%%.20s',tkt_uuid)," + " tkt_mtime" + " FROM ticket" + " WHERE tkt_id=ftsdocs.rid)" + "WHERE ftsdocs.type='t' AND NOT ftsdocs.idxed" + ); +} + +/* +** Deal with all of the unindexed 'w' terms in FTSDOCS +*/ +static void search_update_wiki_index(void){ + db_multi_exec( + "INSERT INTO ftsidx(docid,title,body)" + " SELECT rowid, title('w',rid,NULL),body('w',rid,NULL) FROM ftsdocs" + " WHERE type='w' AND NOT idxed;" + ); + if( db_changes()==0 ) return; + db_multi_exec( + "UPDATE ftsdocs SET idxed=1," + " (name,label,url,mtime) = " + " (SELECT ftsdocs.name," + " 'Wiki: '||ftsdocs.name," + " '/wiki?name='||urlencode(ftsdocs.name)," + " tagxref.mtime" + " FROM tagxref WHERE tagxref.rid=ftsdocs.rid)" + " WHERE ftsdocs.type='w' AND NOT ftsdocs.idxed" + ); +} + +/* +** Deal with all of the unindexed 'f' terms in FTSDOCS +*/ +static void search_update_forum_index(void){ + db_multi_exec( + "INSERT INTO ftsidx(docid,title,body)" + " SELECT rowid, title('f',rid,NULL),body('f',rid,NULL) FROM ftsdocs" + " WHERE type='f' AND NOT idxed;" + ); + if( db_changes()==0 ) return; + db_multi_exec( + "UPDATE ftsdocs SET idxed=1, name=NULL," + " (label,url,mtime) = " + " (SELECT 'Forum '||event.comment," + " '/forumpost/'||blob.uuid," + " event.mtime" + " FROM event, blob" + " WHERE event.objid=ftsdocs.rid" + " AND blob.rid=ftsdocs.rid)" + "WHERE ftsdocs.type='f' AND NOT ftsdocs.idxed" + ); +} + +/* +** Deal with all of the unindexed 'e' terms in FTSDOCS +*/ +static void search_update_technote_index(void){ + db_multi_exec( + "INSERT INTO ftsidx(docid,title,body)" + " SELECT rowid, title('e',rid,NULL),body('e',rid,NULL) FROM ftsdocs" + " WHERE type='e' AND NOT idxed;" + ); + if( db_changes()==0 ) return; + db_multi_exec( + "UPDATE ftsdocs SET idxed=1," + " (name,label,url,mtime) = " + " (SELECT ftsdocs.name," + " 'Tech Note: '||ftsdocs.name," + " '/technote/'||substr(tag.tagname,7)," + " tagxref.mtime" + " FROM tagxref, tag USING (tagid)" + " WHERE tagxref.rid=ftsdocs.rid" + " AND tagname GLOB 'event-*')" + " WHERE ftsdocs.type='e' AND NOT ftsdocs.idxed" + ); +} + +/* +** Deal with all of the unindexed entries in the FTSDOCS table - that +** is to say, all the entries with FTSDOCS.IDXED=0. Add them to the +** index. +*/ +void search_update_index(unsigned int srchFlags){ + if( !search_index_exists() ) return; + if( !db_exists("SELECT 1 FROM ftsdocs WHERE NOT idxed") ) return; + search_sql_setup(g.db); + if( srchFlags & (SRCH_CKIN|SRCH_DOC) ){ + search_update_doc_index(); + search_update_checkin_index(); + } + if( srchFlags & SRCH_TKT ){ + search_update_ticket_index(); + } + if( srchFlags & SRCH_WIKI ){ + search_update_wiki_index(); + } + if( srchFlags & SRCH_TECHNOTE ){ + search_update_technote_index(); + } + if( srchFlags & SRCH_FORUM ){ + search_update_forum_index(); + } +} + +/* +** Construct, prepopulate, and then update the full-text index. +*/ +void search_rebuild_index(void){ + fossil_print("rebuilding the search index..."); + fflush(stdout); + search_create_index(); + search_fill_index(); + search_update_index(search_restrict(SRCH_ALL)); + fossil_print(" done\n"); +} + +/* +** COMMAND: fts-config* +** +** Usage: fossil fts-config ?SUBCOMMAND? ?ARGUMENT? +** +** The "fossil fts-config" command configures the full-text search capabilities +** of the repository. Subcommands: +** +** reindex Rebuild the search index. This is a no-op if +** index search is disabled +** +** index (on|off) Turn the search index on or off +** +** enable cdtwe Enable various kinds of search. c=Check-ins, +** d=Documents, t=Tickets, w=Wiki, e=Tech Notes. +** +** disable cdtwe Disable various kinds of search +** +** stemmer (on|off) Turn the Porter stemmer on or off for indexed +** search. (Unindexed search is never stemmed.) +** +** The current search settings are displayed after any changes are applied. +** Run this command with no arguments to simply see the settings. +*/ +void fts_config_cmd(void){ + static const struct { + int iCmd; + const char *z; + } aCmd[] = { + { 1, "reindex" }, + { 2, "index" }, + { 3, "disable" }, + { 4, "enable" }, + { 5, "stemmer" }, + }; + static const struct { + const char *zSetting; + const char *zName; + const char *zSw; + } aSetng[] = { + { "search-ci", "check-in search:", "c" }, + { "search-doc", "document search:", "d" }, + { "search-tkt", "ticket search:", "t" }, + { "search-wiki", "wiki search:", "w" }, + { "search-technote", "tech note search:", "e" }, + { "search-forum", "forum search:", "f" }, + }; + char *zSubCmd = 0; + int i, j, n; + int iCmd = 0; + int iAction = 0; + db_find_and_open_repository(0, 0); + if( g.argc>2 ){ + zSubCmd = g.argv[2]; + n = (int)strlen(zSubCmd); + for(i=0; i=count(aCmd) ){ + Blob all; + blob_init(&all,0,0); + for(i=0; i=1 ){ + search_drop_index(); + } + if( iAction>=2 ){ + search_rebuild_index(); + } + + /* Always show the status before ending */ + for(i=0; iIndexed search is disabled + style_finish_page(); + return; + } + search_sql_setup(g.db); + style_submenu_element("Setup","%R/srchsetup"); + if( zId!=0 && (id = atoi(zId))>0 ){ + /* Show information about a single ftsdocs entry */ + style_header("Information about ftsdoc entry %d", id); + style_submenu_element("Summary","%R/test-ftsdocs"); + db_prepare(&q, + "SELECT type||rid, name, idxed, label, url, datetime(mtime)" + " FROM ftsdocs WHERE rowid=%d", id + ); + if( db_step(&q)==SQLITE_ROW ){ + const char *zUrl = db_column_text(&q,4); + const char *zDocId = db_column_text(&q,0); + char *zName; + char *z; + @ + @
      docid:  %d(id) + @
      id:%s(zDocId) + @
      name:%h(db_column_text(&q,1)) + @
      idxed:%d(db_column_int(&q,2)) + @
      label:%h(db_column_text(&q,3)) + @
      url: + @ %h(zUrl) + @
      mtime:%s(db_column_text(&q,5)) + z = db_text(0, "SELECT title FROM ftsidx WHERE docid=%d",id); + if( z && z[0] ){ + @
      title:%h(z) + fossil_free(z); + } + z = db_text(0, "SELECT body FROM ftsidx WHERE docid=%d",id); + if( z && z[0] ){ + @
      body:%h(z) + fossil_free(z); + } + @
      + zName = mprintf("Indexed '%c' docs",zDocId[0]); + style_submenu_element(zName,"%R/test-ftsdocs?y=%c&ixed=1",zDocId[0]); + zName = mprintf("Unindexed '%c' docs",zDocId[0]); + style_submenu_element(zName,"%R/test-ftsdocs?y=%c&ixed=0",zDocId[0]); + } + db_finalize(&q); + style_finish_page(); + return; + } + if( zType!=0 && zType[0]!=0 && zType[1]==0 && + zIdxed!=0 && (zIdxed[0]=='1' || zIdxed[0]=='0') && zIdxed[1]==0 + ){ + int ixed = zIdxed[0]=='1'; + char *zName; + style_header("List of '%c' documents that are%s indexed", + zType[0], ixed ? "" : " not"); + style_submenu_element("Summary","%R/test-ftsdocs"); + if( ixed==0 ){ + zName = mprintf("Indexed '%c' docs",zType[0]); + style_submenu_element(zName,"%R/test-ftsdocs?y=%c&ixed=1",zType[0]); + }else{ + zName = mprintf("Unindexed '%c' docs",zType[0]); + style_submenu_element(zName,"%R/test-ftsdocs?y=%c&ixed=0",zType[0]); + } + db_prepare(&q, + "SELECT rowid, type||rid ||' '|| coalesce(label,'')" + " FROM ftsdocs WHERE type='%c' AND %s idxed", + zType[0], ixed ? "" : "NOT" + ); + @ + db_finalize(&q); + style_finish_page(); + return; + } + style_header("Summary of ftsdocs"); + db_prepare(&q, + "SELECT type, sum(idxed IS TRUE), sum(idxed IS FALSE), count(*)" + " FROM ftsdocs" + " GROUP BY 1 ORDER BY 4 DESC" ); - print_timeline(&q, 1000); + @ + @ + @ + @ + while( db_step(&q)==SQLITE_ROW ){ + const char *zType = db_column_text(&q,0); + int nIndexed = db_column_int(&q, 1); + int nUnindexed = db_column_int(&q, 2); + int nTotal = db_column_int(&q, 3); + @ + cnt1 += nIndexed; + cnt2 += nUnindexed; + cnt3 += nTotal; + } db_finalize(&q); + @ + @
      TypeIndexedUnindexedTotal + @
      %h(zType) + if( nIndexed>0 ){ + @ \ + @ %d(nIndexed) + }else{ + @ 0 + } + if( nUnindexed>0 ){ + @ \ + @ %d(nUnindexed) + }else{ + @ 0 + } + @ %d(nTotal) + @
      Total%d(cnt1)%d(cnt2) + @ %d(cnt3) + @ + @
      + style_finish_page(); } ADDED src/security_audit.c Index: src/security_audit.c ================================================================== --- /dev/null +++ src/security_audit.c @@ -0,0 +1,726 @@ +/* +** 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 implements various web pages use for running a security audit +** of a Fossil configuration. +*/ +#include "config.h" +#include +#include "security_audit.h" + +/* +** Return TRUE if any of the capability letters in zTest are found +** in the capability string zCap. +*/ +static int hasAnyCap(const char *zCap, const char *zTest){ + while( zTest[0] ){ + if( strchr(zCap, zTest[0]) ) return 1; + zTest++; + } + return 0; +} + +/* +** Parse the content-security-policy +** into separate fields, and return a pointer to a null-terminated +** array of pointers to strings, one entry for each field. Or return +** a NULL pointer if no CSP could be located in the header. +** +** Memory to hold the returned array and of the strings is obtained from +** a single memory allocation, which the caller should free to avoid a +** memory leak. +*/ +static char **parse_content_security_policy(void){ + char **azCSP = 0; + int nCSP = 0; + char *zAll; + char *zCopy; + int nAll = 0; + int jj; + int nSemi; + + zAll = style_csp(0); + nAll = (int)strlen(zAll); + for(jj=nSemi=0; jj0 && fossil_isspace(zCopy[k]); k--){ zCopy[k] = 0; } + zCopy[jj] = 0; + while( jj+1 + + /* Step 1: Determine if the repository is public or private. "Public" + ** means that any anonymous user on the internet can access all content. + ** "Private" repos require (non-anonymous) login to access all content, + ** though some content may be accessible anonymously. + */ + zAnonCap = db_text("", "SELECT fullcap(NULL)"); + zDevCap = db_text("", "SELECT fullcap('v')"); + zReadCap = db_text("", "SELECT fullcap('u')"); + zPubPages = db_get("public-pages",0); + hasSelfReg = db_get_boolean("self-register",0); + pCap = capability_add(0, db_get("default-perms","u")); + capability_expand(pCap); + zSelfCap = capability_string(pCap); + capability_free(pCap); + if( hasAnyCap(zAnonCap,"as") ){ + @
    5. This repository is Wildly INSECURE because + @ it grants administrator privileges to anonymous users. You + @ should take this repository private + @ immediately! Or, at least remove the Setup and Admin privileges + @ for users "anonymous" and "login" on the + @ User Configuration page. + }else if( hasAnyCap(zSelfCap,"as") && hasSelfReg ){ + @

    6. This repository is Wildly INSECURE because + @ it grants administrator privileges to self-registered users. You + @ should take this repository private + @ and/or disable self-registration + @ immediately! Or, at least remove the Setup and Admin privileges + @ from the default permissions for new users. + }else if( hasAnyCap(zAnonCap,"y") ){ + @

    7. This repository is INSECURE because + @ it allows anonymous users to push unversioned files. + @

      Fix this by taking the repository private + @ or by removing the "y" permission from users "anonymous" and + @ "nobody" on the User Configuration page. + }else if( hasAnyCap(zSelfCap,"y") ){ + @

    8. This repository is INSECURE because + @ it allows self-registered users to push unversioned files. + @

      Fix this by taking the repository private + @ or by removing the "y" permission from the default permissions or + @ by disabling self-registration. + }else if( hasAnyCap(zAnonCap,"goz") ){ + @

    9. This repository is PUBLIC. All + @ checked-in content can be accessed by anonymous users. + @ Take it private.

      + }else if( hasAnyCap(zSelfCap,"goz") && hasSelfReg ){ + @

    10. This repository is PUBLIC because all + @ checked-in content can be accessed by self-registered users. + @ This repostory would be private if you disabled self-registration.

      + }else if( !hasAnyCap(zAnonCap, "jrwy234567") + && (!hasSelfReg || !hasAnyCap(zSelfCap, "jrwy234567")) + && (zPubPages==0 || zPubPages[0]==0) ){ + @
    11. This repository is Completely PRIVATE. + @ A valid login and password is required to access any content. + }else{ + @

    12. This repository is Mostly PRIVATE. + @ A valid login and password is usually required, however some + @ content can be accessed either anonymously or by self-registered + @ users: + @

        + if( hasSelfReg ){ + if( hasAnyCap(zAnonCap,"j") || hasAnyCap(zSelfCap,"j") ){ + @
      • Wiki pages + } + if( hasAnyCap(zAnonCap,"r") || hasAnyCap(zSelfCap,"r") ){ + @
      • Tickets + } + if( hasAnyCap(zAnonCap,"234567") || hasAnyCap(zSelfCap,"234567") ){ + @
      • Forum posts + } + } + if( zPubPages && zPubPages[0] ){ + Glob *pGlob = glob_create(zPubPages); + int i; + @
      • "Public Pages" are URLs that match any of these GLOB patterns: + @

          + for(i=0; inPattern; i++){ + @
        • %h(pGlob->azPattern[i]) + } + @
        + @

        Anoymous users are vested with capabilities "%h(zSelfCap)" on + @ public pages. See the "Public Pages" entry in the + @ "User capability summary" below. + } + @

      + if( zPubPages && zPubPages[0] ){ + @

      Change GLOB patterns exceptions using the "Public pages" setting + @ on the Access Settings page.

      + } + } + + /* Make sure the HTTPS is required for login, at least, so that the + ** password does not go across the Internet in the clear. + */ + if( db_get_int("redirect-to-https",0)==0 ){ + @
    13. WARNING: + @ Sensitive material such as login passwords can be sent over an + @ unencrypted connection. + @

      Fix this by changing the "Redirect to HTTPS" setting on the + @ Access Control page. If you were using + @ the old "Redirect to HTTPS on Login Page" setting, switch to the + @ new setting: it has a more secure implementation. + } + +#ifdef FOSSIL_ENABLE_TH1_DOCS + /* The use of embedded TH1 is dangerous. Warn if it is possible. + */ + if( !Th_AreDocsEnabled() ){ + @

    14. + @ This server is compiled with -DFOSSIL_ENABLE_TH1_DOCS. TH1 docs + @ are disabled for this particular repository, so you are safe for + @ now. However, to prevent future problems caused by accidentally + @ enabling TH1 docs in the future, it is recommended that you + @ recompile Fossil without the -DFOSSIL_ENABLE_TH1_DOCS flag.

      + }else{ + @
    15. DANGER: + @ This server is compiled with -DFOSSIL_ENABLE_TH1_DOCS and TH1 docs + @ are enabled for this repository. Anyone who can check-in or push + @ to this repository can create a malicious TH1 script and then cause + @ that script to be run on the server. This is a serious security concern. + @ TH1 docs should only be enabled for repositories with a very limited + @ number of trusted committers, and the repository should be monitored + @ closely to ensure no hostile content sneaks in. If a bad TH1 script + @ does make it into the repository, the only want to prevent it from + @ being run is to shun it.

      + @ + @

      Disable TH1 docs by recompiling Fossil without the + @ -DFOSSIL_ENABLE_TH1_DOCS flag, and/or clear the th1-docs setting + @ and ensure that the TH1_ENABLE_DOCS environment variable does not + @ exist in the environment.

      + } +#endif + + /* Anonymous users should not be able to harvest email addresses + ** from tickets. + */ + if( hasAnyCap(zAnonCap, "e") ){ + @
    16. WARNING: + @ Anonymous users can view email addresses and other personally + @ identifiable information on tickets. + @

      Fix this by removing the "Email" privilege + @ (capability "e") from users + @ "anonymous" and "nobody" on the + @ User Configuration page. + } + + /* Anonymous users probably should not be allowed to push content + ** to the repository. + */ + if( hasAnyCap(zAnonCap, "i") ){ + @

    17. WARNING: + @ Anonymous users can push new check-ins into the repository. + @

      Fix this by removing the "Check-in" privilege + @ (capability "i") from users + @ "anonymous" and "nobody" on the + @ User Configuration page. + } + + /* Anonymous users probably should not be allowed act as moderators + ** for wiki or tickets. + */ + if( hasAnyCap(zAnonCap, "lq5") ){ + @

    18. WARNING: + @ Anonymous users can act as moderators for wiki, tickets, or + @ forum posts. This defeats the whole purpose of moderation. + @

      Fix this by removing the "Mod-Wiki", "Mod-Tkt", and "Mod-Forum" + @ privileges (capabilities "fq5") + @ from users "anonymous" and "nobody" + @ on the User Configuration page. + } + + /* The strict-manifest-syntax setting should be on. */ + if( db_get_boolean("strict-manifest-syntax",1)==0 ){ + @

    19. WARNING: + @ The "strict-manifest-syntax" flag is off. This is a security + @ risk. Turn this setting on (its default) to protect the users + @ of this repository. + } + + /* Obsolete: */ + if( hasAnyCap(zAnonCap, "d") || + hasAnyCap(zDevCap, "d") || + hasAnyCap(zReadCap, "d") ){ + @

    20. WARNING: + @ One or more users has the obsolete + @ "d" capability. You should remove it using the + @ User Configuration page in case we + @ ever reuse the letter for another purpose. + } + + /* If anonymous users are allowed to create new Wiki, then + ** wiki moderation should be activated to pervent spam. + */ + if( hasAnyCap(zAnonCap, "fk") ){ + if( db_get_boolean("modreq-wiki",0)==0 ){ + @

    21. WARNING: + @ Anonymous users can create or edit wiki without moderation. + @ This can result in robots inserting lots of wiki spam into + @ repository. + @ Fix this by removing the "New-Wiki" and "Write-Wiki" + @ privileges from users "anonymous" and "nobody" on the + @ User Configuration page or + @ by enabling wiki moderation on the + @ Moderation Setup page. + }else{ + @

    22. + @ Anonymous users can create or edit wiki, but moderator + @ approval is required before the edits become permanent. + } + } + + /* Anonymous users should not be able to create trusted forum + ** posts. + */ + if( hasAnyCap(zAnonCap, "456") ){ + @

    23. WARNING: + @ Anonymous users can create forum posts that are + @ accepted into the permanent record without moderation. + @ This can result in robots generating spam on forum posts. + @ Fix this by removing the "WriteTrusted-Forum" privilege + @ (capabilities "456") from + @ users "anonymous" and "nobody" on the + @ User Configuration page or + } + + /* Anonymous users should not be able to send announcements. + */ + if( hasAnyCap(zAnonCap, "A") ){ + @

    24. WARNING: + @ Anonymous users can send announcements to anybody who is signed + @ up to receive announcements. This can result in spam. + @ Fix this by removing the "Announce" privilege + @ (capability "A") from + @ users "anonymous" and "nobody" on the + @ User Configuration page or + } + + /* Administrative privilege should only be provided to + ** specific individuals, not to entire classes of people. + ** And not too many people should have administrator privilege. + */ + z = db_text(0, + "SELECT group_concat(" + "printf('%%s',uid,login)," + "' and ')" + " FROM user" + " WHERE cap GLOB '*[as]*'" + " AND login in ('anonymous','nobody','reader','developer')" + ); + if( z && z[0] ){ + @

    25. WARNING: + @ Administrative privilege ('a' or 's') + @ is granted to an entire class of users: %s(z). + @ Administrative privilege should only be + @ granted to specific individuals. + } + n = db_int(0,"SELECT count(*) FROM user WHERE fullcap(cap) GLOB '*[as]*'"); + if( n==0 ){ + @

    26. + @ No users have administrator privilege. + }else{ + z = db_text(0, + "SELECT group_concat(" + "printf('%%s',uid,login)," + "', ')" + " FROM user" + " WHERE fullcap(cap) GLOB '*[as]*'" + ); + @

    27. + @ Users with administrator privilege are: %s(z) + fossil_free(z); + if( n>3 ){ + @

    28. WARNING: + @ Administrator privilege is granted to + @ %d(n) users. + @ Ideally, administrator privilege ('s' or 'a') should only + @ be granted to one or two users. + } + } + + /* The push-unversioned privilege should only be provided to + ** specific individuals, not to entire classes of people. + ** And no too many people should have this privilege. + */ + z = db_text(0, + "SELECT group_concat(" + "printf('%%s',uid,login)," + "' and ')" + " FROM user" + " WHERE cap GLOB '*y*'" + " AND login in ('anonymous','nobody','reader','developer')" + ); + if( z && z[0] ){ + @

    29. WARNING: + @ The "Write-Unver" privilege is granted to an entire class of users: %s(z). + @ The Write-Unver privilege should only be granted to specific individuals. + fossil_free(z); + } + n = db_int(0,"SELECT count(*) FROM user WHERE cap GLOB '*y*'"); + if( n>0 ){ + z = db_text(0, + "SELECT group_concat(" + "printf('%%s',uid,login),', ')" + " FROM user WHERE fullcap(cap) GLOB '*y*'" + ); + @

    30. + @ Users with "Write-Unver" privilege: %s(z) + fossil_free(z); + if( n>3 ){ + @

      Caution: + @ The "Write-Unver" privilege ('y') is granted to an excessive + @ number of users (%d(n)). + @ Ideally, the Write-Unver privilege should only + @ be granted to one or two users. + } + } + + /* Notify if REMOTE_USER or HTTP_AUTHENTICATION is used for login. + */ + if( db_get_boolean("remote_user_ok", 0) ){ + @

    31. + @ This repository trusts that the REMOTE_USER environment variable set + @ up by the webserver contains the name of an authenticated user. + @ Fossil's built-in authentication mechanism is bypassed. + @

      Fix this by deactivating the "Allow REMOTE_USER authentication" + @ checkbox on the Access Control page. + } + if( db_get_boolean("http_authentication_ok", 0) ){ + @

    32. + @ This repository trusts that the HTTP_AUTHENITICATION environment + @ variable set up by the webserver contains the name of an + @ authenticated user. + @ Fossil's built-in authentication mechanism is bypassed. + @

      Fix this by deactivating the "Allow HTTP_AUTHENTICATION authentication" + @ checkbox on the Access Control page. + } + + /* Logging should be turned on + */ + if( db_get_boolean("access-log",0)==0 ){ + @

    33. + @ The User Log is disabled. The user log + @ keeps a record of successful and unsucessful login attempts and is + @ useful for security monitoring. + } + if( db_get_boolean("admin-log",0)==0 ){ + @

    34. + @ The Administrative Log is disabled. + @ The administrative log provides a record of configuration changes + @ and is useful for security monitoring. + } + +#if !defined(_WIN32) && !defined(FOSSIL_OMIT_LOAD_AVERAGE) + /* Make sure that the load-average limiter is armed and working */ + if( load_average()==0.0 ){ + @

    35. + @ Unable to get the system load average. This can prevent Fossil + @ from throttling expensive operations during peak demand. + @

      If running in a chroot jail on Linux, verify that the /proc + @ filesystem is mounted within the jail, so that the load average + @ can be obtained from the /proc/loadavg file. + }else { + double r = atof(db_get("max-loadavg", 0)); + if( r<=0.0 ){ + @

    36. + @ Load average limiting is turned off. This can cause the server + @ to bog down if many requests for expensive services (such as + @ large diffs or tarballs) arrive at about the same time. + @

      To fix this, set the "Server Load Average Limit" on the + @ Access Control page to approximately + @ the number of available cores on your server, or maybe just a little + @ less. + }else if( r>=8.0 ){ + @

    37. + @ The "Server Load Average Limit" on the + @ Access Control page is set to %g(r), + @ which seems high. Is this server really a %d((int)r)-core machine? + } + } +#endif + + if( g.zErrlog==0 || fossil_strcmp(g.zErrlog,"-")==0 ){ + @

    38. + @ The server error log is disabled. + @ To set up an error log, + if( fossil_strcmp(g.zCmdName, "cgi")==0 ){ + @ make an entry like "errorlog: FILENAME" in the + @ CGI script at %h(P("SCRIPT_FILENAME")). + }else{ + @ add the "--errorlog FILENAME" option to the + @ "%h(g.argv[0]) %h(g.zCmdName)" command that launched this server. + } + }else{ + FILE *pTest = fossil_fopen(g.zErrlog,"a"); + if( pTest==0 ){ + @

    39. + @ Error: + @ There is an error log at "%h(g.zErrlog)" but that file is not + @ writable and so no logging will occur. + }else{ + fclose(pTest); + @

    40. + @ The error log at "%h(g.zErrlog)" is + @ %,lld(file_size(g.zErrlog, ExtFILE)) bytes in size. + } + } + + if( g.zExtRoot ){ + int nFile; + int nCgi; + ext_files(); + nFile = db_int(0, "SELECT count(*) FROM sfile"); + nCgi = nFile==0 ? 0 : db_int(0,"SELECT count(*) FROM sfile WHERE isexe"); + @

    41. CGI Extensions are enabled with a document root + @ at %h(g.zExtRoot) holding + @ %d(nCgi) CGIs and %d(nFile-nCgi) static content and data files. + } + + if( fileedit_glob()!=0 ){ + @

    42. Online File Editing is enabled + @ for this repository. Clear the + @ "fileedit-glob" setting to + @ disable online editing.

      + } + + @
    43. User capability summary: + capability_summary(); + + + azCSP = parse_content_security_policy(); + if( azCSP==0 ){ + @

    44. WARNING: No Content Security Policy (CSP) is specified in the + @ header. Though not required, a strong CSP is recommended. Fossil will + @ automatically insert an appropriate CSP if you let it generate the + @ HTML <head> element by omitting <body> + @ from the header configuration in your customized skin. + @ + }else{ + int ii; + @

    45. Content Security Policy: + @

        + for(ii=0; azCSP[ii]; ii++){ + @
      1. %h(azCSP[ii]) + } + @
      + } + fossil_free(azCSP); + + if( alert_enabled() ){ + @
    46. Email alert configuration summary: + @ + stats_for_email(); + @
      + }else{ + @

    47. Email alerts are disabled + } + + n = db_int(0,"SELECT count(*) FROM (" + "SELECT rid FROM phantom EXCEPT SELECT rid FROM private)"); + if( n>0 ){ + @

    48. \ + @ There exists public phantom artifacts in this repository, shown below. + @ Phantom artifacts are artifacts whose hash name is referenced by some + @ other artifact but whose content is unknown. Some phantoms are marked + @ private and those are ignored. But public phantoms cause unnecessary + @ sync traffic and might represent malicious attempts to corrupt the + @ repository structure. + @

      + @ To suppress unnecessary sync traffic caused by phantoms, add the RID + @ of each phantom to the "private" table. Example: + @

      +    @    INSERT INTO private SELECT rid FROM blob WHERE content IS NULL;
      +    @ 
      + @

      + table_of_public_phantoms(); + @
    49. + } + + @ + style_finish_page(); +} + +/* +** WEBPAGE: takeitprivate +** +** Disable anonymous access to this website +*/ +void takeitprivate_page(void){ + login_check_credentials(); + if( !g.perm.Admin ){ + login_needed(0); + return; + } + if( P("cancel") ){ + /* User pressed the cancel button. Go back */ + cgi_redirect("secaudit0"); + } + if( P("apply") ){ + db_unprotect(PROTECT_ALL); + db_multi_exec( + "UPDATE user SET cap=''" + " WHERE login IN ('nobody','anonymous');" + "DELETE FROM config WHERE name='public-pages';" + ); + db_set("self-register","0",0); + db_protect_pop(); + cgi_redirect("secaudit0"); + } + style_header("Make This Website Private"); + @

      Click the "Make It Private" button below to disable all + @ anonymous access to this repository. A valid login and password + @ will be required to access this repository after clicking that + @ button.

      + @ + @

      Click the "Cancel" button to leave things as they are.

      + @ + @
      + @ + @ + @
      + + style_finish_page(); +} + +/* +** The maximum number of bytes of log to show +*/ +#define MXSHOWLOG 50000 + +/* +** WEBPAGE: errorlog +** +** Show the content of the error log. Only the administrator can view +** this page. +*/ +void errorlog_page(void){ + i64 szFile; + FILE *in; + char z[10000]; + login_check_credentials(); + if( !g.perm.Admin ){ + login_needed(0); + return; + } + style_header("Server Error Log"); + style_submenu_element("Test", "%R/test-warning"); + style_submenu_element("Refresh", "%R/errorlog"); + if( g.zErrlog==0 || fossil_strcmp(g.zErrlog,"-")==0 ){ + @

      To create a server error log: + @

        + @
      1. + @ If the server is running as CGI, then create a line in the CGI file + @ like this: + @

        +    @ errorlog: FILENAME
        +    @ 
        + @
      2. + @ If the server is running using one of + @ the "fossil http" or "fossil server" commands then add + @ a command-line option "--errorlog FILENAME" to that + @ command. + @

      + style_finish_page(); + return; + } + if( P("truncate1") && cgi_csrf_safe(1) ){ + fclose(fopen(g.zErrlog,"w")); + } + if( P("download") ){ + Blob log; + blob_read_from_file(&log, g.zErrlog, ExtFILE); + cgi_set_content_type("text/plain"); + cgi_set_content(&log); + return; + } + szFile = file_size(g.zErrlog, ExtFILE); + if( P("truncate") ){ + @
      + @

      Confirm that you want to truncate the %,lld(szFile)-byte error log: + @ + @ + @

      + style_finish_page(); + return; + } + @

      The server error log at "%h(g.zErrlog)" is %,lld(szFile) bytes in size. + style_submenu_element("Download", "%R/errorlog?download"); + style_submenu_element("Truncate", "%R/errorlog?truncate"); + in = fossil_fopen(g.zErrlog, "rb"); + if( in==0 ){ + @

      Unable to open that file for reading!

      + style_finish_page(); + return; + } + if( szFile>MXSHOWLOG && P("all")==0 ){ + @
      + @

      Only the last %,d(MXSHOWLOG) bytes are shown. + @ + @

      + fseek(in, -MXSHOWLOG, SEEK_END); + } + @
      + @
      +  while( fgets(z, sizeof(z), in) ){
      +    @ %h(z)\
      +  }
      +  fclose(in);
      +  @ 
      + style_finish_page(); +} Index: src/setup.c ================================================================== --- src/setup.c +++ src/setup.c @@ -2,11 +2,11 @@ ** Copyright (c) 2007 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: @@ -15,14 +15,30 @@ ** ******************************************************************************* ** ** Implementation of the Setup page */ -#include #include "config.h" +#include #include "setup.h" +/* +** Increment the "cfgcnt" variable, so that ETags will know that +** the configuration has changed. +*/ +void setup_incr_cfgcnt(void){ + static int once = 1; + if( once ){ + once = 0; + db_unprotect(PROTECT_CONFIG); + db_multi_exec("UPDATE config SET value=value+1 WHERE name='cfgcnt'"); + if( db_changes()==0 ){ + db_multi_exec("INSERT INTO config(name,value) VALUES('cfgcnt',1)"); + } + db_protect_pop(); + } +} /* ** Output a single entry for a menu generated using an HTML table. ** If zLink is not NULL or an empty string, then it is the page that ** the menu entry will hyperlink to. If zLink is NULL or "", then @@ -37,628 +53,175 @@ if( zLink && zLink[0] ){ @ %h(zTitle) }else{ @ %h(zTitle) } - @ %h(zDesc) + @ %h(zDesc) } + + /* -** WEBPAGE: /setup +** WEBPAGE: setup +** +** Main menu for the administrative pages. Requires Admin or Setup +** privileges. Links to sub-pages only usable by Setup users are +** shown only to Setup users. */ void setup_page(void){ + int setup_user = 0; login_check_credentials(); - if( !g.okSetup ){ - login_needed(); + if( !g.perm.Admin ){ + login_needed(0); } + setup_user = g.perm.Setup; + style_set_current_feature("setup"); style_header("Server Administration"); - @ + + /* Make sure the header contains . Issue a warning + ** if it does not. */ + if( !cgi_header_contains("Configuration Error: Please add + @ <base href="$secureurl/$current_page"> after + @ <head> in the + @ HTML header!

      + } + +#if !defined(_WIN32) + /* Check for /dev/null and /dev/urandom. We want both devices to be present, + ** but they are sometimes omitted (by mistake) from chroot jails. */ + if( access("/dev/null", R_OK|W_OK) ){ + @

      WARNING: Device "/dev/null" is not available + @ for reading and writing.

      + } + if( access("/dev/urandom", R_OK) ){ + @

      WARNING: Device "/dev/urandom" is not available + @ for reading. This means that the pseudo-random number generator used + @ by SQLite will be poorly seeded.

      + } +#endif + + @
      setup_menu_entry("Users", "setup_ulist", "Grant privileges to individual users."); - setup_menu_entry("Access", "setup_access", - "Control access settings."); - setup_menu_entry("Configuration", "setup_config", - "Configure the WWW components of the repository"); + if( setup_user ){ + setup_menu_entry("Access", "setup_access", + "Control access settings."); + setup_menu_entry("Configuration", "setup_config", + "Configure the WWW components of the repository"); + } + setup_menu_entry("Security-Audit", "secaudit0", + "Analyze the current configuration for security problems"); + if( setup_user ){ + setup_menu_entry("Settings", "setup_settings", + "Web interface to the \"fossil settings\" command"); + } setup_menu_entry("Timeline", "setup_timeline", "Timeline display preferences"); - setup_menu_entry("Tickets", "tktsetup", - "Configure the trouble-ticketing system for this repository"); + if( setup_user ){ + setup_menu_entry("Login-Group", "setup_login_group", + "Manage single sign-on between this repository and others" + " on the same server"); + setup_menu_entry("Tickets", "tktsetup", + "Configure the trouble-ticketing system for this repository"); + setup_menu_entry("Wiki", "setup_wiki", + "Configure the wiki for this repository"); + setup_menu_entry("Chat", "setup_chat", + "Configure the chatroom"); + } + setup_menu_entry("Search","srchsetup", + "Configure the built-in search engine"); + setup_menu_entry("URL Aliases", "waliassetup", + "Configure URL aliases"); + if( setup_user ){ + setup_menu_entry("Notification", "setup_notification", + "Automatic notifications of changes via outbound email"); + setup_menu_entry("Email-Server", "setup_smtp", + "Activate and configure the built-in email server"); + setup_menu_entry("Transfers", "xfersetup", + "Configure the transfer system for this repository"); + } setup_menu_entry("Skins", "setup_skin", - "Select from a menu of prepackaged \"skins\" for the web interface"); - setup_menu_entry("CSS", "setup_editcss", - "Edit the Cascading Style Sheet used by all pages of this repository"); - setup_menu_entry("Header", "setup_header", - "Edit HTML text inserted at the top of every page"); - setup_menu_entry("Footer", "setup_footer", - "Edit HTML text inserted at the bottom of every page"); + "Select and/or modify the web interface \"skins\""); + setup_menu_entry("Moderation", "setup_modreq", + "Enable/Disable requiring moderator approval of Wiki and/or Ticket" + " changes and attachments."); + setup_menu_entry("Ad-Unit", "setup_adunit", + "Edit HTML text for an ad unit inserted after the menu bar"); + setup_menu_entry("URLs & Checkouts", "urllist", + "Show URLs used to access this repo and known check-outs"); + if( setup_user ){ + setup_menu_entry("Web-Cache", "cachestat", + "View the status of the expensive-page cache"); + } setup_menu_entry("Logo", "setup_logo", - "Change the logo image for the server"); + "Change the logo and background images for the server"); setup_menu_entry("Shunned", "shun", "Show artifacts that are shunned by this repository"); - setup_menu_entry("Log", "rcvfromlist", - "A record of received artifacts and their sources"); - setup_menu_entry("Stats", "stat", - "Display repository statistics"); - @
      - - style_footer(); -} - -/* -** WEBPAGE: setup_ulist -** -** Show a list of users. Clicking on any user jumps to the edit -** screen for that user. -*/ -void setup_ulist(void){ - Stmt s; - - login_check_credentials(); - if( !g.okAdmin ){ - login_needed(); - return; - } - - style_submenu_element("Add", "Add User", "setup_uedit"); - style_header("User List"); - @ - @
      - @ Users: - @
      - @ - @ - @ - @ - @ - @ - db_prepare(&s, "SELECT uid, login, cap, info FROM user ORDER BY login"); - while( db_step(&s)==SQLITE_ROW ){ - const char *zCap = db_column_text(&s, 2); - if( strstr(zCap, "s") ) zCap = "s"; - @ - @ - @ - @ - @ - @ - } - @
      User ID Capabilities Contact Info
      - if( g.okAdmin && (zCap[0]!='s' || g.okSetup) ){ - @ - } - @ %h(db_column_text(&s,1)) - if( g.okAdmin ){ - @ - } - @    %s(zCap)   %s(db_column_text(&s,3))
      - @
      - @ Notes: - @
        - @
      1. The permission flags are as follows:

        - @ - @ - @ - @ - @ - @ - @ - @ - @ - @ - @ - @ - @ - @ - @ - @ - @ - @ - @ - @ - @ - @ - @ - @ - @ - @ - @ - @ - @ - @ - @ - @ - @ - @ - @ - @ - @ - @ - @ - @ - @ - @ - @ - @ - @ - @
        aAdmin: Create and delete users
        bAttach: Add attachments to wiki or tickets
        cAppend-Tkt: Append to tickets
        dDelete: Delete wiki and tickets
        eEmail: View sensitive data such as EMail addresses
        fNew-Wiki: Create new wiki pages
        gClone: Clone the repository
        hHyperlinks: Show hyperlinks to detailed - @ repository history
        iCheck-In: Commit new versions in the repository
        jRead-Wiki: View wiki pages
        kWrite-Wiki: Edit wiki pages
        mAppend-Wiki: Append to wiki pages
        nNew-Tkt: Create new tickets
        oCheck-Out: Check out versions
        pPassword: Change your own password
        rRead-Tkt: View tickets
        sSetup/Super-user: Setup and configure this website
        tTkt-Report: Create new bug summary reports
        uReader: Inherit privileges of - @ user reader
        vDeveloper: Inherit privileges of - @ user developer
        wWrite-Tkt: Edit tickets
        zZip download: Download a baseline via the - @ /zip URL even without checkout - @ and history permissions
        - @
      2. - @ - @
      3. - @ Every user, logged in or not, inherits the privileges of nobody. - @

      4. - @ - @
      5. - @ Any human can login as anonymous since the password is - @ clearly displayed on the login page for them to type. The purpose - @ of requiring anonymous to log in is to prevent access by spiders. - @ Every logged-in user inherits the combined privileges of - @ anonymous and - @ nobody. - @

      6. - @ - @
      7. - @ Users with privilege v inherit the combined privileges of - @ developer, anonymous, and nobody. - @

      8. - @ - @
      - @
      - style_footer(); -} - -/* -** Return true if zPw is a valid password string. A valid -** password string is: -** -** (1) A zero-length string, or -** (2) a string that contains a character other than '*'. -*/ -static int isValidPwString(const char *zPw){ - if( zPw==0 ) return 0; - if( zPw[0]==0 ) return 1; - while( zPw[0]=='*' ){ zPw++; } - return zPw[0]!=0; -} - -/* -** WEBPAGE: /setup_uedit -*/ -void user_edit(void){ - const char *zId, *zLogin, *zInfo, *zCap, *zPw; - char *oaa, *oas, *oar, *oaw, *oan, *oai, *oaj, *oao, *oap; - char *oak, *oad, *oac, *oaf, *oam, *oah, *oag, *oae; - char *oat, *oau, *oav, *oab, *oaz; - const char *inherit[128]; - int doWrite; - int uid; - int higherUser = 0; /* True if user being edited is SETUP and the */ - /* user doing the editing is ADMIN. Disallow editing */ - - /* Must have ADMIN privleges to access this page - */ - login_check_credentials(); - if( !g.okAdmin ){ login_needed(); return; } - - /* Check to see if an ADMIN user is trying to edit a SETUP account. - ** Don't allow that. - */ - zId = PD("id", "0"); - uid = atoi(zId); - if( zId && !g.okSetup && uid>0 ){ - char *zOldCaps; - zOldCaps = db_text(0, "SELECT cap FROM user WHERE uid=%d",uid); - higherUser = zOldCaps && strchr(zOldCaps,'s'); - } - - if( P("can") ){ - cgi_redirect("setup_ulist"); - return; - } - - /* If we have all the necessary information, write the new or - ** modified user record. After writing the user record, redirect - ** to the page that displays a list of users. - */ - doWrite = cgi_all("login","info","pw") && !higherUser; - if( doWrite ){ - char zCap[50]; - int i = 0; - int aa = P("aa")!=0; - int ab = P("ab")!=0; - int ad = P("ad")!=0; - int ae = P("ae")!=0; - int ai = P("ai")!=0; - int aj = P("aj")!=0; - int ak = P("ak")!=0; - int an = P("an")!=0; - int ao = P("ao")!=0; - int ap = P("ap")!=0; - int ar = P("ar")!=0; - int as = g.okSetup && P("as")!=0; - int aw = P("aw")!=0; - int ac = P("ac")!=0; - int af = P("af")!=0; - int am = P("am")!=0; - int ah = P("ah")!=0; - int ag = P("ag")!=0; - int at = P("at")!=0; - int au = P("au")!=0; - int av = P("av")!=0; - int az = P("az")!=0; - if( aa ){ zCap[i++] = 'a'; } - if( ab ){ zCap[i++] = 'b'; } - if( ac ){ zCap[i++] = 'c'; } - if( ad ){ zCap[i++] = 'd'; } - if( ae ){ zCap[i++] = 'e'; } - if( af ){ zCap[i++] = 'f'; } - if( ah ){ zCap[i++] = 'h'; } - if( ag ){ zCap[i++] = 'g'; } - if( ai ){ zCap[i++] = 'i'; } - if( aj ){ zCap[i++] = 'j'; } - if( ak ){ zCap[i++] = 'k'; } - if( am ){ zCap[i++] = 'm'; } - if( an ){ zCap[i++] = 'n'; } - if( ao ){ zCap[i++] = 'o'; } - if( ap ){ zCap[i++] = 'p'; } - if( ar ){ zCap[i++] = 'r'; } - if( as ){ zCap[i++] = 's'; } - if( at ){ zCap[i++] = 't'; } - if( au ){ zCap[i++] = 'u'; } - if( av ){ zCap[i++] = 'v'; } - if( aw ){ zCap[i++] = 'w'; } - if( az ){ zCap[i++] = 'z'; } - - zCap[i] = 0; - zPw = P("pw"); - zLogin = P("login"); - if( isValidPwString(zPw) ){ - zPw = sha1_shared_secret(zPw, zLogin); - }else{ - zPw = db_text(0, "SELECT pw FROM user WHERE uid=%d", uid); - } - if( uid>0 && - db_exists("SELECT 1 FROM user WHERE login=%Q AND uid!=%d", zLogin, uid) - ){ - style_header("User Creation Error"); - @ Login "%h(zLogin)" is already used by a different - @ user. - @ - @

      [Bummer]

      - style_footer(); - return; - } - login_verify_csrf_secret(); - db_multi_exec( - "REPLACE INTO user(uid,login,info,pw,cap) " - "VALUES(nullif(%d,0),%Q,%Q,%Q,'%s')", - uid, P("login"), P("info"), zPw, zCap - ); - cgi_redirect("setup_ulist"); - return; - } - - /* Load the existing information about the user, if any - */ - zLogin = ""; - zInfo = ""; - zCap = ""; - zPw = ""; - oaa = oab = oac = oad = oae = oaf = oag = oah = oai = oaj = oak = oam = - oan = oao = oap = oar = oas = oat = oau = oav = oaw = oaz = ""; - if( uid ){ - zLogin = db_text("", "SELECT login FROM user WHERE uid=%d", uid); - zInfo = db_text("", "SELECT info FROM user WHERE uid=%d", uid); - zCap = db_text("", "SELECT cap FROM user WHERE uid=%d", uid); - zPw = db_text("", "SELECT pw FROM user WHERE uid=%d", uid); - if( strchr(zCap, 'a') ) oaa = " checked"; - if( strchr(zCap, 'b') ) oab = " checked"; - if( strchr(zCap, 'c') ) oac = " checked"; - if( strchr(zCap, 'd') ) oad = " checked"; - if( strchr(zCap, 'e') ) oae = " checked"; - if( strchr(zCap, 'f') ) oaf = " checked"; - if( strchr(zCap, 'g') ) oag = " checked"; - if( strchr(zCap, 'h') ) oah = " checked"; - if( strchr(zCap, 'i') ) oai = " checked"; - if( strchr(zCap, 'j') ) oaj = " checked"; - if( strchr(zCap, 'k') ) oak = " checked"; - if( strchr(zCap, 'm') ) oam = " checked"; - if( strchr(zCap, 'n') ) oan = " checked"; - if( strchr(zCap, 'o') ) oao = " checked"; - if( strchr(zCap, 'p') ) oap = " checked"; - if( strchr(zCap, 'r') ) oar = " checked"; - if( strchr(zCap, 's') ) oas = " checked"; - if( strchr(zCap, 't') ) oat = " checked"; - if( strchr(zCap, 'u') ) oau = " checked"; - if( strchr(zCap, 'v') ) oav = " checked"; - if( strchr(zCap, 'w') ) oaw = " checked"; - if( strchr(zCap, 'z') ) oaz = " checked"; - } - - /* figure out inherited permissions */ - memset(inherit, 0, sizeof(inherit)); - if( strcmp(zLogin, "developer") ){ - char *z1, *z2; - z1 = z2 = db_text(0,"SELECT cap FROM user WHERE login='developer'"); - while( z1 && *z1 ){ - inherit[0x7f & *(z1++)] = ""; - } - free(z2); - } - if( strcmp(zLogin, "reader") ){ - char *z1, *z2; - z1 = z2 = db_text(0,"SELECT cap FROM user WHERE login='reader'"); - while( z1 && *z1 ){ - inherit[0x7f & *(z1++)] = ""; - } - free(z2); - } - if( strcmp(zLogin, "anonymous") ){ - char *z1, *z2; - z1 = z2 = db_text(0,"SELECT cap FROM user WHERE login='anonymous'"); - while( z1 && *z1 ){ - inherit[0x7f & *(z1++)] = ""; - } - free(z2); - } - if( strcmp(zLogin, "nobody") ){ - char *z1, *z2; - z1 = z2 = db_text(0,"SELECT cap FROM user WHERE login='nobody'"); - while( z1 && *z1 ){ - inherit[0x7f & *(z1++)] = ""; - } - free(z2); - } - - /* Begin generating the page - */ - style_submenu_element("Cancel", "Cancel", "setup_ulist"); - if( uid ){ - style_header(mprintf("Edit User %h", zLogin)); - }else{ - style_header("Add A New User"); - } - @
      - @
      - login_insert_csrf_secret(); - @ - @ - @ - if( uid ){ - @ - }else{ - @ - } - @ - @ - @ - @ - @ - @ - @ - @ - @ - @ - @ - @ - @ - @ - @ - if( zPw[0] ){ - /* Obscure the password for all users */ - @ - }else{ - /* Show an empty password as an empty input field */ - @ - } - @ - if( !higherUser ){ - @ - @ - @ - } - @
      User ID:%d(uid) (new user)
      Login:
      Contact Info:
      Capabilities: -#define B(x) inherit[x] - if( g.okSetup ){ - @ %s(B('s'))Setup
      - } - @ %s(B('a'))Admin
      - @ %s(B('d'))Delete
      - @ %s(B('e'))Email
      - @ %s(B('p'))Password
      - @ %s(B('i'))Check-In
      - @ %s(B('o'))Check-Out
      - @ %s(B('h'))History
      - @ %s(B('u'))Reader
      - @ %s(B('v'))Developer
      - @ %s(B('g'))Clone
      - @ %s(B('j'))Read Wiki
      - @ %s(B('f'))New Wiki
      - @ %s(B('m'))Append Wiki
      - @ %s(B('k'))Write Wiki
      - @ %s(B('b'))Attachments
      - @ %s(B('r'))Read Ticket
      - @ %s(B('n'))New Ticket
      - @ %s(B('c'))Append Ticket
      - @ %s(B('w'))Write Ticket
      - @ %s(B('t'))Ticket Report
      - @ %s(B('z'))Download Zip - @
      Password:
        - @
      - @

      Privileges And Capabilities:

      - @
        - if( higherUser ){ - @
      • - @ User %h(zLogin) has Setup privileges and you only have Admin privileges - @ so you are not permitted to make changes to %h(zLogin). - @

      • - @ - } - @
      • - @ The Setup user can make arbitrary configuration changes. - @ An Admin user can add other users and change user privileges - @ and reset user passwords. Both automatically get all other privileges - @ listed below. Use these two settings with discretion. - @

      • - @ - @
      • - @ The "" mark indicates - @ the privileges of "nobody" that are available to all users - @ regardless of whether or not they are logged in. - @

      • - @ - @
      • - @ The "" mark indicates - @ the privileges of "anonymous" that are inherited by all logged-in users. - @

      • - @ - @
      • - @ The "" mark indicates - @ the privileges of "developer" that are inherited by all users with - @ the Developer privilege. - @

      • - @ - @
      • - @ The "" mark indicates - @ the privileges of "reader" that are inherited by all users with - @ the Reader privilege. - @

      • - @ - @
      • - @ The Delete privilege give the user the ability to erase - @ wiki, tickets, and attachments that have been added by anonymous - @ users. This capability is intended for deletion of spam. The - @ delete capability is only in effect for 24 hours after the item - @ is first posted. The Setup user can delete anything at any time. - @

      • - @ - @
      • - @ The History privilege allows a user to see most hyperlinks. - @ This is recommended ON for most logged-in users but OFF for - @ user "nobody" to avoid problems with spiders trying to walk every - @ historical version of every baseline and file. - @

      • - @ - @
      • - @ The Zip privilege allows a user to see the "download as ZIP" - @ hyperlink and permits access to the /zip page. This allows - @ users to download ZIP archives without granting other rights like - @ Read or History. This privilege is recommended for - @ user nobody so that automatic package downloaders can obtain - @ the sources without going through the login procedure. - @

      • - @ - @
      • - @ The Check-in privilege allows remote users to "push". - @ The Check-out privilege allows remote users to "pull". - @ The Clone privilege allows remote users to "clone". - @

      • - @ - @

      • - @ The Read Wiki, New Wiki, Append Wiki, and - @ Write Wiki privileges control access to wiki pages. The - @ Read Ticket, New Ticket, Append Ticket, and - @ Write Ticket privileges control access to trouble tickets. - @ The Ticket Report privilege allows the user to create or edit - @ ticket report formats. - @

      • - @ - @
      • - @ Users with the Password privilege are allowed to change their - @ own password. Recommended ON for most users but OFF for special - @ users "developer", "anonymous", and "nobody". - @

      • - @ - @
      • - @ The EMail privilege allows the display of sensitive information - @ such as the email address of users and contact information on tickets. - @ Recommended OFF for "anonymous" and for "nobody" but ON for - @ "developer". - @

      • - @ - @
      • - @ The Attachment privilege is needed in order to add attachments - @ to tickets or wiki. Write privilege on the ticket or wiki is also - @ required.

      • - @ - @
      • - @ Login is prohibited if the password is an empty string. - @

      • - @
      - @ - @

      Special Logins

      - @ - @
        - @
      • - @ No login is required for user "nobody". The capabilities - @ of the nobody user are inherited by all users, regardless of - @ whether or not they are logged in. To disable universal access - @ to the repository, make sure no user named "nobody" exists or - @ that the nobody user has no capabilities enabled. - @ The password for nobody is ignore. To avoid problems with - @ spiders overloading the server, it is recommended - @ that the 'h' (History) capability be turned off for the nobody - @ user. - @

      • - @ - @
      • - @ Login is required for user "anonymous" but the password - @ is displayed on the login screen beside the password entry box - @ so anybody who can read should be able to login as anonymous. - @ On the other hand, spiders and web-crawlers will typically not - @ be able to login. Set the capabilities of the anonymous user - @ to things that you want any human to be able to do, but not any - @ spider. Every other logged-in user inherits the privileges of - @ anonymous. - @

      • - @ - @
      • - @ The "developer" user is intended as a template for trusted users - @ with check-in privileges. When adding new trusted users, simply - @ select the Developer privilege to cause the new user to inherit - @ all privileges of the "developer" user. Similarly, the "reader" - @ user is a template for users who are allowed more access than anonymous, - @ but less than a developer. - @

      • - @
      - @ - style_footer(); -} - + setup_menu_entry("Artifact Receipts Log", "rcvfromlist", + "A record of received artifacts and their sources"); + setup_menu_entry("User Log", "access_log", + "A record of login attempts"); + setup_menu_entry("Administrative Log", "admin_log", + "View the admin_log entries"); + setup_menu_entry("Error Log", "errorlog", + "View the Fossil server error log"); + setup_menu_entry("Unversioned Files", "uvlist?byage=1", + "Show all unversioned files held"); + setup_menu_entry("Stats", "stat", + "Repository Status Reports"); + setup_menu_entry("Sitemap", "sitemap", + "Links to miscellaneous pages"); + if( setup_user ){ + setup_menu_entry("SQL", "admin_sql", + "Enter raw SQL commands"); + setup_menu_entry("TH1", "admin_th1", + "Enter raw TH1 commands"); + } + @ + + style_finish_page(); +} /* ** Generate a checkbox for an attribute. */ -static void onoff_attribute( +void onoff_attribute( const char *zLabel, /* The text label on the checkbox */ const char *zVar, /* The corresponding row in the VAR table */ const char *zQParm, /* The query parameter */ - int dfltVal /* Default value if VAR table entry does not exist */ + int dfltVal, /* Default value if VAR table entry does not exist */ + int disabled /* 1 if disabled */ ){ const char *zQ = P(zQParm); int iVal = db_get_boolean(zVar, dfltVal); - if( zQ==0 && P("submit") ){ + if( zQ==0 && !disabled && P("submit") ){ zQ = "off"; } if( zQ ){ - int iQ = strcmp(zQ,"on")==0 || atoi(zQ); + int iQ = fossil_strcmp(zQ,"on")==0 || atoi(zQ); if( iQ!=iVal ){ login_verify_csrf_secret(); + db_protect_only(PROTECT_NONE); db_set(zVar, iQ ? "1" : "0", 0); + db_protect_pop(); + setup_incr_cfgcnt(); + admin_log("Set option [%q] to [%q].", + zVar, iQ ? "on" : "off"); iVal = iQ; } } + @ } /* ** Generate an entry box for an attribute. */ @@ -665,397 +228,1925 @@ void entry_attribute( const char *zLabel, /* The text label on the entry box */ int width, /* Width of the entry box */ const char *zVar, /* The corresponding row in the VAR table */ const char *zQParm, /* The query parameter */ - char *zDflt /* Default value if VAR table entry does not exist */ + const char *zDflt, /* Default value if VAR table entry does not exist */ + int disabled /* 1 if disabled */ ){ const char *zVal = db_get(zVar, zDflt); const char *zQ = P(zQParm); - if( zQ && strcmp(zQ,zVal)!=0 ){ + if( zQ && fossil_strcmp(zQ,zVal)!=0 ){ + const int nZQ = (int)strlen(zQ); login_verify_csrf_secret(); + setup_incr_cfgcnt(); + db_protect_only(PROTECT_NONE); db_set(zVar, zQ, 0); + db_protect_pop(); + admin_log("Set entry_attribute %Q to: %.*s%s", + zVar, 20, zQ, (nZQ>20 ? "..." : "")); zVal = zQ; } - @ - @ %s(zLabel) + @ %s(zLabel) } /* ** Generate a text box for an attribute. */ -static void textarea_attribute( +const char *textarea_attribute( const char *zLabel, /* The text label on the textarea */ int rows, /* Rows in the textarea */ int cols, /* Columns in the textarea */ const char *zVar, /* The corresponding row in the VAR table */ const char *zQP, /* The query parameter */ - const char *zDflt /* Default value if VAR table entry does not exist */ + const char *zDflt, /* Default value if VAR table entry does not exist */ + int disabled /* 1 if the textarea should not be editable */ ){ - const char *z = db_get(zVar, (char*)zDflt); + const char *z = db_get(zVar, zDflt); const char *zQ = P(zQP); - if( zQ && strcmp(zQ,z)!=0 ){ + if( zQ && !disabled && fossil_strcmp(zQ,z)!=0){ + const int nZQ = (int)strlen(zQ); login_verify_csrf_secret(); + db_protect_only(PROTECT_NONE); db_set(zVar, zQ, 0); + db_protect_pop(); + setup_incr_cfgcnt(); + admin_log("Set textarea_attribute %Q to: %.*s%s", + zVar, 20, zQ, (nZQ>20 ? "..." : "")); z = zQ; } if( rows>0 && cols>0 ){ - @ - @ %s(zLabel) + @ + if( *zLabel ){ + @ %s(zLabel) + } + } + return z; +} + +/* +** Generate a text box for an attribute. +*/ +void multiple_choice_attribute( + const char *zLabel, /* The text label on the menu */ + const char *zVar, /* The corresponding row in the VAR table */ + const char *zQP, /* The query parameter */ + const char *zDflt, /* Default value if VAR table entry does not exist */ + int nChoice, /* Number of choices */ + const char *const *azChoice /* Choices in pairs (VAR value, Display) */ +){ + const char *z = db_get(zVar, zDflt); + const char *zQ = P(zQP); + int i; + if( zQ && fossil_strcmp(zQ,z)!=0){ + const int nZQ = (int)strlen(zQ); + login_verify_csrf_secret(); + db_unprotect(PROTECT_ALL); + db_set(zVar, zQ, 0); + setup_incr_cfgcnt(); + db_protect_pop(); + admin_log("Set multiple_choice_attribute %Q to: %.*s%s", + zVar, 20, zQ, (nZQ>20 ? "..." : "")); + z = zQ; + } + @ %h(zLabel) } /* ** WEBPAGE: setup_access +** +** The access-control settings page. Requires Setup privileges. */ void setup_access(void){ + static const char *const azRedirectOpts[] = { + "0", "Off", + "1", "Login Page Only", + "2", "All Pages" + }; login_check_credentials(); - if( !g.okSetup ){ - login_needed(); + if( !g.perm.Setup ){ + login_needed(0); + return; } + style_set_current_feature("setup"); style_header("Access Control Settings"); db_begin_transaction(); - @
      + @
      login_insert_csrf_secret(); - @
      + @

      + @
      + multiple_choice_attribute("Redirect to HTTPS", + "redirect-to-https", "redirhttps", "0", + count(azRedirectOpts)/2, azRedirectOpts); + @

      Force the use of HTTPS by redirecting to HTTPS when an + @ unencrypted request is received. This feature can be enabled + @ for the Login page only, or for all pages. + @

      Further details: When enabled, this option causes the $secureurl TH1 + @ variable is set to an "https:" variant of $baseurl. Otherwise, + @ $secureurl is just an alias for $baseurl. + @ (Property: "redirect-to-https". "0" for off, "1" for Login page only, + @ "2" otherwise.) + @


      onoff_attribute("Require password for local access", - "localauth", "localauth", 0); - @

      When enabled, the password sign-in is required for - @ web access coming from 127.0.0.1. When disabled, web access - @ from 127.0.0.1 is allows without any login - the user id is selected - @ from the ~/.fossil database. Password login is always required - @ for incoming web connections on internet addresses other than - @ 127.0.0.1.

    50. "); if( zTitle[0] == '_' ){ blob_appendf(&ril, "%s", zTitle); } else { - blob_appendf(&ril, "%h", rn, zTitle); + blob_appendf(&ril, "%z%h", href("%R/rptview?rn=%d", rn), zTitle); } blob_appendf(&ril, "   "); - if( g.okWrite && zOwner && zOwner[0] ){ - blob_appendf(&ril, "(by %h) ", zOwner); - } - if( g.okTktFmt ){ - blob_appendf(&ril, "[copy] ", rn); - } - if( g.okAdmin || (g.okWrTkt && zOwner && strcmp(g.zLogin,zOwner)==0) ){ - blob_appendf(&ril, "[edit] ", rn); - } - if( g.okTktFmt ){ - blob_appendf(&ril, "[sql] ", rn); + if( g.perm.Write && zOwner && zOwner[0] ){ + blob_appendf(&ril, "(by %h) ", zOwner); + } + if( g.perm.TktFmt ){ + blob_appendf(&ril, "[%zcopy] ", + href("%R/rptedit?rn=%d©=1", rn)); + } + if( g.perm.Admin + || (g.perm.WrTkt && zOwner && fossil_strcmp(g.zLogin,zOwner)==0) + ){ + blob_appendf(&ril, "[%zedit]", + href("%R/rptedit?rn=%d", rn)); + } + if( g.perm.TktFmt ){ + blob_appendf(&ril, "[%zsql]", + href("%R/rptsql?rn=%d", rn)); } blob_appendf(&ril, "
    51. - - @
      + "localauth", "localauth", 0, 0); + @

      When enabled, the password sign-in is always required for + @ web access. When disabled, unrestricted web access from 127.0.0.1 + @ is allowed for the fossil ui command or + @ from the fossil server, + @ fossil http commands when the + @ "--localauth" command line options is used, or from the + @ fossil cgi if a line containing + @ the word "localauth" appears in the CGI script. + @ + @

      A password is always required if any one or more + @ of the following are true: + @

        + @
      1. This button is checked + @
      2. The inbound TCP/IP connection is not from 127.0.0.1 + @
      3. The server is started using either of the + @ fossil server or + @ fossil http commands + @ without the "--localauth" option. + @
      4. The server is started from CGI without the "localauth" keyword + @ in the CGI script. + @
      + @ (Property: "localauth") + @ + @
      + onoff_attribute("Enable /test_env", + "test_env_enable", "test_env_enable", 0, 0); + @

      When enabled, the %h(g.zBaseURL)/test_env URL is available to all + @ users. When disabled (the default) only users Admin and Setup can visit + @ the /test_env page. + @ (Property: "test_env_enable") + @

      + @ + @
      + onoff_attribute("Enable /artifact_stats", + "artifact_stats_enable", "artifact_stats_enable", 0, 0); + @

      When enabled, the %h(g.zBaseURL)/artifact_stats URL is available to all + @ users. When disabled (the default) only users with check-in privilege may + @ access the /artifact_stats page. + @ (Property: "artifact_stats_enable") + @

      + @ + @
      onoff_attribute("Allow REMOTE_USER authentication", - "remote_user_ok", "remote_user_ok", 0); + "remote_user_ok", "remote_user_ok", 0, 0); @

      When enabled, if the REMOTE_USER environment variable is set to the @ login name of a valid user and no other login credentials are available, @ then the REMOTE_USER is accepted as an authenticated user. - @

      - - @
      - entry_attribute("Login expiration time", 6, "cookie-expire", "cex", "8766"); + @ (Property: "remote_user_ok") + @

      + @ + @
      + onoff_attribute("Allow HTTP_AUTHENTICATION authentication", + "http_authentication_ok", "http_authentication_ok", 0, 0); + @

      When enabled, allow the use of the HTTP_AUTHENTICATION environment + @ variable or the "Authentication:" HTTP header to find the username and + @ password. This is another way of supporting Basic Authentication. + @ (Property: "http_authentication_ok") + @

      + @ + @
      + entry_attribute("Login expiration time", 6, "cookie-expire", "cex", + "8766", 0); @

      The number of hours for which a login is valid. This must be a - @ positive number. The default is 8760 hours which is approximately equal - @ to a year.

      + @ positive number. The default is 8766 hours which is approximately equal + @ to a year. + @ (Property: "cookie-expire")

      - @
      + @
      entry_attribute("Download packet limit", 10, "max-download", "mxdwn", - "5000000"); + "5000000", 0); @

      Fossil tries to limit out-bound sync, clone, and pull packets @ to this many bytes, uncompressed. If the client requires more data @ than this, then the client will issue multiple HTTP requests. @ Values below 1 million are not recommended. 5 million is a - @ reasonable number.

      + @ reasonable number. (Property: "max-download")

      + + @
      + entry_attribute("Download time limit", 11, "max-download-time", "mxdwnt", + "30", 0); + + @

      Fossil tries to spend less than this many seconds gathering + @ the out-bound data of sync, clone, and pull packets. + @ If the client request takes longer, a partial reply is given similar + @ to the download packet limit. 30s is a reasonable default. + @ (Property: "max-download-time")

      + + @
      + entry_attribute("Server Load Average Limit", 11, "max-loadavg", "mxldavg", + "0.0", 0); + @

      Some expensive operations (such as computing tarballs, zip archives, + @ or annotation/blame pages) are prohibited if the load average on the host + @ computer is too large. Set the threshold for disallowing expensive + @ computations here. Set this to 0.0 to disable the load average limit. + @ This limit is only enforced on Unix servers. On Linux systems, + @ access to the /proc virtual filesystem is required, which means this limit + @ might not work inside a chroot() jail. + @ (Property: "max-loadavg")

      + + @
      + onoff_attribute( + "Enable hyperlinks for \"nobody\" based on User-Agent and Javascript", + "auto-hyperlink", "autohyperlink", 1, 0); + @

      Enable hyperlinks (the equivalent of the "h" permission) for all users, + @ including user "nobody", as long as + @

      1. the User-Agent string in the + @ HTTP header indicates that the request is coming from an actual human + @ being, and + @
      2. the user agent is able to + @ run Javascript in order to set the href= attribute of hyperlinks, and + @
      3. mouse movement is detected (optional - see the checkbox below), and + @
      4. a number of milliseconds have passed since the page loaded.
      + @ + @

      This setting is designed to give easy access to humans while + @ keeping out robots and spiders. + @ You do not normally want a robot to walk your entire repository because + @ if it does, your server will end up computing diffs and annotations for + @ every historical version of every file and creating ZIPs and tarballs of + @ every historical check-in, which can use a lot of CPU and bandwidth + @ even for relatively small projects.

      + @ + @

      Additional parameters that control this behavior:

      + @
      + onoff_attribute("Require mouse movement before enabling hyperlinks", + "auto-hyperlink-mouseover", "ahmo", 0, 0); + @
      + entry_attribute("Delay in milliseconds before enabling hyperlinks", 5, + "auto-hyperlink-delay", "ah-delay", "50", 0); + @
      + @

      For maximum robot defense, the "require mouse movement" should + @ be turned on and the "Delay" should be at least 50 milliseconds.

      + @ (Properties: "auto-hyperlink", + @ "auto-hyperlink-mouseover", and "auto-hyperlink-delay")

      + + @
      + onoff_attribute("Require a CAPTCHA if not logged in", + "require-captcha", "reqcapt", 1, 0); + @

      Require a CAPTCHA for edit operations (appending, creating, or + @ editing wiki or tickets or adding attachments to wiki or tickets) + @ for users who are not logged in. (Property: "require-captcha")

      + + @
      + entry_attribute("Public pages", 30, "public-pages", + "pubpage", "", 0); + @

      A comma-separated list of glob patterns for pages that are accessible + @ without needing a login and using the privileges given by the + @ "Default privileges" setting below. + @ + @

      Example use case: Set this field to "/doc/trunk/www/*" and set + @ the "Default privileges" to include the "o" privilege + @ to give anonymous users read-only permission to the + @ latest version of the embedded documentation in the www/ folder without + @ allowing them to see the rest of the source code. + @ (Property: "public-pages") + @

      + + @
      + onoff_attribute("Allow users to register themselves", + "self-register", "selfreg", 0, 0); + @

      Allow users to register themselves on the /register webpage. + @ A self-registration creates a new entry in the USER table and + @ perhaps also in the SUBSCRIBER table if email notification is + @ enabled. + @ (Property: "self-register")

      + + @
      + onoff_attribute("Email verification required for self-registration", + "selfreg-verify", "sfverify", 0, 0); + @

      If enabled, self-registration creates a new entry in the USER table + @ with only capabilities "7". The default user capabilities are not + @ added until the email address associated with the self-registration + @ has been verified. This setting only makes sense if + @ email notifications are enabled. + @ (Property: "selfreg-verify")

      + + @
      + onoff_attribute("Allow anonymous subscriptions", + "anon-subscribe", "anonsub", 1, 0); + @

      If disabled, email notification subscriptions are only allowed + @ for users with a login. If Nobody or Anonymous visit the /subscribe + @ page, they are redirected to /register or /login. + @ (Property: "anon-subscribe")

      + + @
      + entry_attribute("Authorized subscription email addresses", 35, + "auth-sub-email", "asemail", "", 0); + @

      This is a comma-separated list of GLOB patterns that specify + @ email addresses that are authorized to subscriptions. If blank + @ (the usual case), then any email address can be used to self-register. + @ This setting is used to limit subscriptions to members of a particular + @ organization or group based on their email address. + @ (Property: "auth-sub-email")

      + + @
      + entry_attribute("Default privileges", 10, "default-perms", + "defaultperms", "u", 0); + @

      Permissions given to users that...

      • register themselves using + @ the self-registration procedure (if enabled), or
      • access "public" + @ pages identified by the public-pages glob pattern above, or
      • + @ are users newly created by the administrator.
      + @

      Recommended value: "u" for Reader. + @ Capability Key. + @ (Property: "default-perms") + @

      - @
      + @
      onoff_attribute("Show javascript button to fill in CAPTCHA", - "auto-captcha", "autocaptcha", 0); + "auto-captcha", "autocaptcha", 0, 0); @

      When enabled, a button appears on the login screen for user @ "anonymous" that will automatically fill in the CAPTCHA password. - @ This is less secure that forcing the user to do it manually, but is + @ This is less secure than forcing the user to do it manually, but is @ probably secure enough and it is certainly more convenient for - @ anonymous users.

      + @ anonymous users. (Property: "auto-captcha")

      - @
      - @

      - @ + @
      + @

      + @
      db_end_transaction(0); - style_footer(); + style_finish_page(); +} + +/* +** WEBPAGE: setup_login_group +** +** Change how the current repository participates in a login +** group. +*/ +void setup_login_group(void){ + const char *zGroup; + char *zErrMsg = 0; + Blob fullName; + char *zSelfRepo; + const char *zRepo = PD("repo", ""); + const char *zLogin = PD("login", ""); + const char *zPw = PD("pw", ""); + const char *zNewName = PD("newname", "New Login Group"); + + login_check_credentials(); + if( !g.perm.Setup ){ + login_needed(0); + return; + } + file_canonical_name(g.zRepositoryName, &fullName, 0); + zSelfRepo = fossil_strdup(blob_str(&fullName)); + blob_reset(&fullName); + if( P("join")!=0 ){ + login_group_join(zRepo, 1, zLogin, zPw, zNewName, &zErrMsg); + }else if( P("leave") ){ + login_group_leave(&zErrMsg); + } + style_set_current_feature("setup"); + style_header("Login Group Configuration"); + if( zErrMsg ){ + @

      %s(zErrMsg)

      + } + zGroup = login_group_name(); + if( zGroup==0 ){ + @

      This repository (in the file named "%h(zSelfRepo)") + @ is not currently part of any login-group. + @ To join a login group, fill out the form below.

      + @ + @
      + login_insert_csrf_secret(); + @
      + @ + @ + @ + @ + @ + @ + @ + @ + @ + @ + @ + @ + @ + @ + @
      Repository filename \ + @ in group to join: + @
      Login on the above repo: + @
      Password: + @ \ + @
      Name of login-group: + @ + @ (only used if creating a new login-group).
      + @
      + }else{ + Stmt q; + int n = 0; + @

      This repository (in the file "%h(zSelfRepo)") + @ is currently part of the "%h(zGroup)" login group. + @ Other repositories in that group are:

      + @ + @ + db_prepare(&q, + "SELECT value," + " (SELECT value FROM config" + " WHERE name=('peer-name-' || substr(x.name,11)))" + " FROM config AS x" + " WHERE name GLOB 'peer-repo-*'" + " ORDER BY value" + ); + while( db_step(&q)==SQLITE_ROW ){ + const char *zRepo = db_column_text(&q, 0); + const char *zTitle = db_column_text(&q, 1); + n++; + @ + } + db_finalize(&q); + @
      Project Name + @ Repository File
      %d(n). + @ %h(zTitle)%h(zRepo)
      + @ + @

      + login_insert_csrf_secret(); + @ To leave this login group press + @ + @

      + @
      For best results, use the same number of + @ IP octets in the login cookie across all repositories in the + @ same Login Group. + @

      Implementation Details

      + @

      The following are fields from the CONFIG table related to login-groups, + @ provided here for instructional and debugging purposes:

      + @ + @ + @ + @ + db_prepare(&q, "SELECT name, value, datetime(mtime,'unixepoch') FROM config" + " WHERE name GLOB 'peer-*'" + " OR name GLOB 'project-*'" + " OR name GLOB 'login-group-*'" + " ORDER BY name"); + while( db_step(&q)==SQLITE_ROW ){ + @ + @ + @ + } + db_finalize(&q); + @
      Config.NameConfig.ValueConfig.mtime
      %h(db_column_text(&q,0))%h(db_column_text(&q,1))%h(db_column_text(&q,2))
      + style_table_sorter(); + } + style_finish_page(); } /* ** WEBPAGE: setup_timeline +** +** Edit administrative settings controlling the display of +** timelines. */ void setup_timeline(void){ + double tmDiff; + char zTmDiff[20]; + static const char *const azTimeFormats[] = { + "0", "HH:MM", + "1", "HH:MM:SS", + "2", "YYYY-MM-DD HH:MM", + "3", "YYMMDD HH:MM", + "4", "(off)" + }; login_check_credentials(); - if( !g.okSetup ){ - login_needed(); + if( !g.perm.Admin ){ + login_needed(0); + return; } + style_set_current_feature("setup"); style_header("Timeline Display Preferences"); db_begin_transaction(); - @
      + @
      login_insert_csrf_secret(); + @

      - @
      + @
      onoff_attribute("Allow block-markup in timeline", - "timeline-block-markup", "tbm", 0); + "timeline-block-markup", "tbm", 0, 0); @

      In timeline displays, check-in comments can be displayed with or - @ without block markup (paragraphs, tables, etc.)

      + @ without block markup such as paragraphs, tables, etc. + @ (Property: "timeline-block-markup")

      + + @
      + onoff_attribute("Plaintext comments on timelines", + "timeline-plaintext", "tpt", 0, 0); + @

      In timeline displays, check-in comments are displayed literally, + @ without any wiki or HTML interpretation. Use CSS to change + @ display formatting features such as fonts and line-wrapping behavior. + @ (Property: "timeline-plaintext")

      + + @
      + onoff_attribute("Truncate comment at first blank line (Git-style)", + "timeline-truncate-at-blank", "ttb", 0, 0); + @

      In timeline displays, check-in comments are displayed only through + @ the first blank line. This is the traditional way to display comments + @ in Git repositories (Property: "timeline-truncate-at-blank")

      + + @
      + onoff_attribute("Break comments at newline characters", + "timeline-hard-newlines", "thnl", 0, 0); + @

      In timeline displays, newline characters in check-in comments force + @ a line break on the display. + @ (Property: "timeline-hard-newlines")

      - @
      + @
      onoff_attribute("Use Universal Coordinated Time (UTC)", - "timeline-utc", "utc", 1); + "timeline-utc", "utc", 1, 0); @

      Show times as UTC (also sometimes called Greenwich Mean Time (GMT) or - @ Zulu) instead of in local time.

      - - @
      - onoff_attribute("Show version differences by default", - "show-version-diffs", "vdiff", 0); - @

      On the version-information pages linked from the timeline can either - @ show complete diffs of all file changes, or can just list the names of - @ the files that have changed. Users can get to either page by - @ clicking. This setting selects the default.

      - - @
      + @ Zulu) instead of in local time. On this server, local time is currently + tmDiff = db_double(0.0, "SELECT julianday('now')"); + tmDiff = db_double(0.0, + "SELECT (julianday(%.17g,'localtime')-julianday(%.17g))*24.0", + tmDiff, tmDiff); + sqlite3_snprintf(sizeof(zTmDiff), zTmDiff, "%.1f", tmDiff); + if( strcmp(zTmDiff, "0.0")==0 ){ + @ the same as UTC and so this setting will make no difference in + @ the display.

      + }else if( tmDiff<0.0 ){ + sqlite3_snprintf(sizeof(zTmDiff), zTmDiff, "%.1f", -tmDiff); + @ %s(zTmDiff) hours behind UTC.

      + }else{ + @ %s(zTmDiff) hours ahead of UTC.

      + } + @

      (Property: "timeline-utc") + + @


      + multiple_choice_attribute("Style", "timeline-default-style", + "tdss", "0", N_TIMELINE_VIEW_STYLE, timeline_view_styles); + @

      The default timeline viewing style, for when the user has not + @ specified an alternative. (Property: "timeline-default-style")

      + + @
      + entry_attribute("Default Number Of Rows", 6, "timeline-default-length", + "tldl", "50", 0); + @

      The maximum number of rows to show on a timeline in the absence + @ of a user display preference cookie setting or an explicit n= query + @ parameter. (Property: "timeline-default-length")

      + + @
      + multiple_choice_attribute("Per-Item Time Format", "timeline-date-format", + "tdf", "0", count(azTimeFormats)/2, azTimeFormats); + @

      If the "HH:MM" or "HH:MM:SS" format is selected, then the date is shown + @ in a separate box (using CSS class "timelineDate") whenever the date + @ changes. With the "YYYY-MM-DD HH:MM" and "YYMMDD ..." formats, + @ the complete date and time is shown on every timeline entry using the + @ CSS class "timelineTime". (Property: "timeline-date-format")

      + + @
      entry_attribute("Max timeline comment length", 6, - "timeline-max-comment", "tmc", "0"); + "timeline-max-comment", "tmc", "0", 0); @

      The maximum length of a comment to be displayed in a timeline. - @ "0" there is no length limit.

      + @ "0" there is no length limit. + @ (Property: "timeline-max-comment")

      + + @
      + entry_attribute("Tooltip dwell time (milliseconds)", 6, + "timeline-dwelltime", "tdt", "100", 0); + @
      + entry_attribute("Tooltip close time (milliseconds)", 6, + "timeline-closetime", "tct", "250", 0); + @

      The dwell time defines how long the mouse pointer + @ should be stationary above an object of the graph before a tooltip + @ appears.
      + @ The close time defines how long the mouse pointer + @ can be away from an object before a tooltip is closed.

      + @

      Set dwell time to "0" to disable tooltips.
      + @ Set close time to "0" to keep tooltips visible until + @ the mouse is clicked elsewhere.

      + @

      (Properties: "timeline-dwelltime", "timeline-closetime")

      + + @
      + onoff_attribute("Timestamp hyperlinks to /info", + "timeline-tslink-info", "ttlti", 0, 0); + @

      The hyperlink on the timestamp associated with each timeline entry, + @ on the far left-hand side of the screen, normally targets another + @ /timeline page that shows the entry in context. However, if this + @ option is turned on, that hyperlink targets the /info page showing + @ the details of the entry. + @

      The /timeline link is the default since it is often useful to + @ see an entry in context, and because that link is not otherwise + @ accessible on the timeline. The /info link is also accessible by + @ double-clicking the timeline node or by clicking on the hash that + @ follows "check-in:" in the supplemental information section on the + @ right of the entry. + @

      (Properties: "timeline-tslink-info") + + @


      + @

      + @
      + db_end_transaction(0); + style_finish_page(); +} + +/* +** WEBPAGE: setup_settings +** +** Change or view miscellaneous settings. Part of the +** /setup pages requiring Setup privileges. +*/ +void setup_settings(void){ + int nSetting; + int i; + Setting const *pSet; + const Setting *aSetting = setting_info(&nSetting); + + login_check_credentials(); + if( !g.perm.Setup ){ + login_needed(0); + return; + } - @
      - @

      - @ + style_set_current_feature("setup"); + style_header("Settings"); + if(!g.repositoryOpen){ + /* Provide read-only access to versioned settings, + but only if no repo file was explicitly provided. */ + db_open_local(0); + } + db_begin_transaction(); + @

      Settings marked with (v) are "versionable" and will be overridden + @ by the contents of managed files named + @ ".fossil-settings/SETTING-NAME". + @ If the file for a versionable setting exists, the value cannot be + @ changed on this screen.


      + @ + @

      + @
      + login_insert_csrf_secret(); + for(i=0, pSet=aSetting; iwidth==0 ){ + int hasVersionableValue = pSet->versionable && + (db_get_versioned(pSet->name, NULL)!=0); + onoff_attribute("", pSet->name, + pSet->var!=0 ? pSet->var : pSet->name, + is_truth(pSet->def), hasVersionableValue); + @ %h(pSet->name) + if( pSet->versionable ){ + @ (v)
      + } else { + @
      + } + } + } + @
      + @
      + @ + for(i=0, pSet=aSetting; iwidth>0 && !pSet->forceTextArea ){ + int hasVersionableValue = pSet->versionable && + (db_get_versioned(pSet->name, NULL)!=0); + @ + } + } + @
      + @ %h(pSet->name) + if( pSet->versionable ){ + @ (v) + } else { + @ + } + @ + entry_attribute("", /*pSet->width*/ 25, pSet->name, + pSet->var!=0 ? pSet->var : pSet->name, + (char*)pSet->def, hasVersionableValue); + @
      + @
      + for(i=0, pSet=aSetting; iwidth>0 && pSet->forceTextArea ){ + int hasVersionableValue = db_get_versioned(pSet->name, NULL)!=0; + @ %s(pSet->name) + if( pSet->versionable ){ + @ (v)
      + } else { + @
      + } + textarea_attribute("", /*rows*/ 2, /*cols*/ 35, pSet->name, + pSet->var!=0 ? pSet->var : pSet->name, + (char*)pSet->def, hasVersionableValue); + @
      + } + } + @
      + @
      db_end_transaction(0); - style_footer(); + style_finish_page(); } + +/* +** SETTING: mainmenu width=70 block-text +** +** The mainmenu setting specifies the entries on the main menu +** for many skins. The mainmenu should be a TCL list. Each set +** of four consecutive values defines a single main menu item: +** +** * The first term is text that appears on the menu. +** +** * The second term is a hyperlink to take when a user clicks on the +** entry. Hyperlinks that start with "/" are relative to the +** repository root. +** +** * The third term is an argument to the TH1 "capexpr" command. +** If capexpr evalutes to true, then the entry is shown. If not, +** the entry is omitted. "*" is always true. "{}" is never true. +** +** * The fourth term is a list of extra class names to apply to the +** new menu entry. Some skins use classes "desktoponly" and +** "wideonly" to only show the entries when the web browser +** screen is wide or very wide, respectively. +** +** Some custom skins might not use this property. Whether the property +** is used or not a choice made by the skin designer. Some skins may add +** extra choices (such as the hamburger button) to the menu. +*/ +/* +** SETTING: sitemap-extra width=70 block-text +** +** The sitemap-extra setting defines extra links to appear on the +** /sitemap web page as sub-items of the "Home Page" entry before the +** "Documentation Search" entry (if any). For skins that use the /sitemap +** page to construct a hamburger menu dropdown, new entries added here +** will appear on the hamburger menu. +** +** This setting should be a TCL list divided into triples. Each +** triple defines a new entry: +** +** * The first term is the display name of the /sitemap entry +** +** * The second term is a hyperlink to take when a user clicks on the +** entry. Hyperlinks that start with "/" are relative to the +** repository root. +** +** * The third term is an argument to the TH1 "capexpr" command. +** If capexpr evalutes to true, then the entry is shown. If not, +** the entry is omitted. "*" is always true. +** +** The default value is blank, meaning no added entries. +*/ + /* ** WEBPAGE: setup_config +** +** The "Admin/Configuration" page. Requires Setup privilege. */ void setup_config(void){ login_check_credentials(); - if( !g.okSetup ){ - login_needed(); + if( !g.perm.Setup ){ + login_needed(0); + return; } + style_set_current_feature("setup"); style_header("WWW Configuration"); db_begin_transaction(); - @
      + @
      login_insert_csrf_secret(); + @

      @
      - entry_attribute("Project Name", 60, "project-name", "pn", ""); - @

      Give your project a name so visitors know what this site is about. - @ The project name will also be used as the RSS feed title.

      + entry_attribute("Project Name", 60, "project-name", "pn", "", 0); + @

      A brief project name so visitors know what this site is about. + @ The project name will also be used as the RSS feed title. + @ (Property: "project-name") + @

      @
      - textarea_attribute("Project Description", 5, 60, - "project-description", "pd", ""); + textarea_attribute("Project Description", 3, 80, + "project-description", "pd", "", 0); @

      Describe your project. This will be used in page headers for search - @ engines as well as a short RSS description.

      + @ engines as well as a short RSS description. + @ (Property: "project-description")

      + @
      + entry_attribute("Tarball and ZIP-archive Prefix", 20, "short-project-name", + "spn", "", 0); + @

      This is used as a prefix on the names of generated tarballs and + @ ZIP archive. For best results, keep this prefix brief and avoid special + @ characters such as "/" and "\". + @ If no tarball prefix is specified, then the full Project Name above is used. + @ (Property: "short-project-name") + @

      + @
      + entry_attribute("Download Tag", 20, "download-tag", "dlt", "trunk", 0); + @

      The /download page is designed to provide + @ a convenient place for newbies + @ to download a ZIP archive or a tarball of the project. By default, + @ the latest trunk check-in is downloaded. Change this tag to something + @ else (ex: release) to alter the behavior of the /download page. + @ (Property: "download-tag") + @

      @
      - entry_attribute("Index Page", 60, "index-page", "idxpg", "/home"); + entry_attribute("Index Page", 60, "index-page", "idxpg", "/home", 0); @

      Enter the pathname of the page to display when the "Home" menu @ option is selected and when no pathname is @ specified in the URL. For example, if you visit the url:

      @ - @
      %h(g.zBaseURL)
      + @

      %h(g.zBaseURL)

      @ @

      And you have specified an index page of "/home" the above will @ automatically redirect to:

      @ - @
      %h(g.zBaseURL)/home
      + @

      %h(g.zBaseURL)/home

      @ @

      The default "/home" page displays a Wiki page with the same name @ as the Project Name specified above. Some sites prefer to redirect - @ to a documentation page (ex: "/doc/tip/index.wiki") or to "/timeline".

      + @ to a documentation page (ex: "/doc/trunk/index.wiki") or to "/timeline".

      + @ + @

      Note: To avoid a redirect loop or other problems, this entry must + @ begin with "/" and it must specify a valid page. For example, + @ "/home" will work but "home" will not, since it omits the + @ leading "/".

      + @

      (Property: "index-page") + @


      + @

      The main menu for the web interface + @

      + @ + @

      This setting should be a TCL list. Each set of four consecutive + @ values defines a single main menu item: + @

        + @
      1. The first term is text that appears on the menu. + @
      2. The second term is a hyperlink to take when a user clicks on the + @ entry. Hyperlinks that start with "/" are relative to the + @ repository root. + @
      3. The third term is an argument to the TH1 "capexpr" command. + @ If capexpr evalutes to true, then the entry is shown. If not, + @ the entry is omitted. "*" is always true. "{}" is never true. + @
      4. The fourth term is a list of extra class names to apply to the new + @ menu entry. Some skins use classes "desktoponly" and "wideonly" + @ to only show the entries when the web browser screen is wide or + @ very wide, respectively. + @
      + @ + @

      Some custom skins might not use this property. Whether the property + @ is used or not a choice made by the skin designer. Some skins may add extra + @ choices (such as the hamburger button) to the menu that are not shown + @ on this list. (Property: mainmenu) + @

      + if(P("resetMenu")!=0){ + db_unset("mainmenu", 0); + cgi_delete_parameter("mmenu"); + } + textarea_attribute("Main Menu", 12, 80, + "mainmenu", "mmenu", style_default_mainmenu(), 0); + @

      + @

      + @ + @

      + @
      + @

      Extra links to appear on the /sitemap page, + @ as sub-items of the "Home Page" entry, appearing before the + @ "Documentation Search" entry (if any). In skins that use the /sitemap + @ page to construct a hamburger menu dropdown, new entries added here + @ will appear on the hamburger menu. + @ + @

      This setting should be a TCL list divided into triples. Each + @ triple defines a new entry: + @

        + @
      1. The first term is the display name of the /sitemap entry + @
      2. The second term is a hyperlink to take when a user clicks on the + @ entry. Hyperlinks that start with "/" are relative to the + @ repository root. + @
      3. The third term is an argument to the TH1 "capexpr" command. + @ If capexpr evalutes to true, then the entry is shown. If not, + @ the entry is omitted. "*" is always true. + @
      + @ + @

      The default value is blank, meaning no added entries. + @ (Property: sitemap-extra) + @

      + textarea_attribute("Custom Sitemap Entries", 8, 80, + "sitemap-extra", "smextra", "", 0); + @


      + @

      + @
      + db_end_transaction(0); + style_finish_page(); +} + +/* +** WEBPAGE: setup_wiki +** +** The "Admin/Wiki" page. Requires Setup privilege. +*/ +void setup_wiki(void){ + login_check_credentials(); + if( !g.perm.Setup ){ + login_needed(0); + return; + } + + style_set_current_feature("setup"); + style_header("Wiki Configuration"); + db_begin_transaction(); + @
      + login_insert_csrf_secret(); + @

      + @
      + onoff_attribute("Associate Wiki Pages With Branches, Tags, or Checkins", + "wiki-about", "wiki-about", 1, 0); + @

      + @ Associate wiki pages with branches, tags, or checkins, based on + @ the wiki page name. Wiki pages that begin with "branch/", "checkin/" + @ or "tag/" and which continue with the name of an existing branch, checkin + @ or tag are treated specially when this feature is enabled. + @

        + @
      • branch/branch-name + @
      • checkin/full-checkin-hash + @
      • tag/tag-name + @
      + @ (Property: "wiki-about")

      + @
      + entry_attribute("Allow Unsafe HTML In Markdown", 6, + "safe-html", "safe-html", "", 0); + @

      Allow "unsafe" HTML (ex: <script>, <form>, etc) to be + @ generated by Markdown-formatted documents. + @ This setting is a string where each character indicates a "type" of + @ document in which to allow unsafe HTML: + @

        + @
      • b → checked-in files, embedded documentation + @
      • f → forum posts + @
      • t → tickets + @
      • w → wiki pages + @
      + @ Include letters for each type of document for which unsafe HTML should + @ be allowed. For example, to allow unsafe HTML only for checked-in files, + @ make this setting be just "b". To allow unsafe HTML anywhere except + @ in forum posts, make this setting be "btw". The default is an + @ empty string which means that Fossil never allows Markdown documents + @ to generate unsafe HTML. + @ (Property: "safe-html")

      + @
      + @ The current interwiki tag map is as follows: + interwiki_append_map_table(cgi_output_blob()); + @

      Visit %R/intermap for details or to + @ modify the interwiki tag map. @


      onoff_attribute("Use HTML as wiki markup language", - "wiki-use-html", "wiki-use-html", 0); - @

      Use HTML as the wiki markup language. Wiki links will still be parsed but - @ all other wiki formatting will be ignored. This option is helpful if you have - @ chosen to use a rich HTML editor for wiki markup such as TinyMCE.

      + "wiki-use-html", "wiki-use-html", 0, 0); + @

      Use HTML as the wiki markup language. Wiki links will still be parsed + @ but all other wiki formatting will be ignored.

      @

      CAUTION: when @ enabling, all HTML tags and attributes are accepted in the wiki. @ No sanitization is done. This means that it is very possible for malicious @ users to inject dangerous HTML, CSS and JavaScript code into your wiki.

      @

      This should only be enabled when wiki editing is limited - @ to trusted users. It should not be used on a publically - @ editable wiki.

      - @
      - @

      - @ - db_end_transaction(0); - style_footer(); -} - -/* -** WEBPAGE: setup_editcss -*/ -void setup_editcss(void){ - login_check_credentials(); - if( !g.okSetup ){ - login_needed(); - } - db_begin_transaction(); - if( P("clear")!=0 ){ - db_multi_exec("DELETE FROM config WHERE name='css'"); - cgi_replace_parameter("css", zDefaultCSS); - db_end_transaction(0); - cgi_redirect("setup_editcss"); - }else{ - textarea_attribute(0, 0, 0, "css", "css", zDefaultCSS); - } - if( P("submit")!=0 ){ - db_end_transaction(0); - cgi_redirect("setup_editcss"); - } - style_header("Edit CSS"); - @
      - login_insert_csrf_secret(); - @ Edit the CSS below:
      - textarea_attribute("", 40, 80, "css", "css", zDefaultCSS); - @
      - @ - @ - @
      - @

      Note: Press your browser Reload button after modifying the - @ CSS in order to pull in the modified CSS file.

      - @
      - @ The default CSS is shown below for reference. Other examples - @ of CSS files can be seen on the skins page. - @ See also the header and - @ footer editing screens. - @
      -  @ %h(zDefaultCSS)
      -  @ 
      - style_footer(); - db_end_transaction(0); -} - -/* -** WEBPAGE: setup_header -*/ -void setup_header(void){ - login_check_credentials(); - if( !g.okSetup ){ - login_needed(); - } - db_begin_transaction(); - if( P("clear")!=0 ){ - db_multi_exec("DELETE FROM config WHERE name='header'"); - cgi_replace_parameter("header", zDefaultHeader); - }else{ - textarea_attribute(0, 0, 0, "header", "header", zDefaultHeader); - } - style_header("Edit Page Header"); - @
      - login_insert_csrf_secret(); - @

      Edit HTML text with embedded TH1 (a TCL dialect) that will be used to - @ generate the beginning of every page through start of the main - @ menu.

      - textarea_attribute("", 40, 80, "header", "header", zDefaultHeader); - @
      - @ - @ - @
      - @
      - @ The default header is shown below for reference. Other examples - @ of headers can be seen on the skins page. - @ See also the CSS and - @ footer editing screeens. - @
      -  @ %h(zDefaultHeader)
      -  @ 
      - style_footer(); - db_end_transaction(0); -} - -/* -** WEBPAGE: setup_footer -*/ -void setup_footer(void){ - login_check_credentials(); - if( !g.okSetup ){ - login_needed(); - } - db_begin_transaction(); - if( P("clear")!=0 ){ - db_multi_exec("DELETE FROM config WHERE name='footer'"); - cgi_replace_parameter("footer", zDefaultFooter); - }else{ - textarea_attribute(0, 0, 0, "footer", "footer", zDefaultFooter); - } - style_header("Edit Page Footer"); - @
      - login_insert_csrf_secret(); - @

      Edit HTML text with embedded TH1 (a TCL dialect) that will be used to - @ generate the end of every page.

      - textarea_attribute("", 20, 80, "footer", "footer", zDefaultFooter); - @
      - @ - @ - @
      - @
      - @ The default footer is shown below for reference. Other examples - @ of footers can be seen on the skins page. - @ See also the CSS and - @ header editing screens. - @
      -  @ %h(zDefaultFooter)
      -  @ 
      - style_footer(); + @ to trusted users. It should not be used on a publicly + @ editable wiki.

      + @ (Property: "wiki-use-html") + @
      + @

      + @
      + db_end_transaction(0); + style_finish_page(); +} + +/* +** WEBPAGE: setup_chat +** +** The "Admin/Chat" page. Requires Setup privilege. +*/ +void setup_chat(void){ + static const char *const azAlerts[] = { + "alerts/plunk.wav", "Plunk", + "alerts/bflat3.wav", "Tone-1", + "alerts/bflat2.wav", "Tone-2", + "alerts/bloop.wav", "Bloop", + }; + + login_check_credentials(); + if( !g.perm.Setup ){ + login_needed(0); + return; + } + + style_set_current_feature("setup"); + style_header("Chat Configuration"); + db_begin_transaction(); + @
      + login_insert_csrf_secret(); + @

      + @
      + entry_attribute("Initial Chat History Size", 10, + "chat-initial-history", "chatih", "50", 0); + @

      When /chat first starts up, it preloads up to this many historical + @ messages. + @ (Property: "chat-initial-history")

      + @
      + entry_attribute("Minimum Number Of Historical Messages To Retain", 10, + "chat-keep-count", "chatkc", "50", 0); + @

      The chat subsystem purges older messages. But it will always retain + @ the N most recent messages where N is the value of this setting. + @ (Property: "chat-keep-count")

      + @
      + entry_attribute("Maximum Message Age In Days", 10, + "chat-keep-days", "chatkd", "7", 0); + @

      Chat message are removed after N days, where N is the value of + @ this setting. N may be fractional. So, for example, to only keep + @ an historical record of chat messages for 12 hours, set this value + @ to 0.5. + @ (Property: "chat-keep-days")

      + @
      + entry_attribute("Chat Polling Timeout", 10, + "chat-poll-timeout", "chatpt", "420", 0); + @

      New chat content is downloaded using the "long poll" technique. + @ HTTP requests are made to /chat-poll which blocks waiting on new + @ content to arrive. But the /chat-poll cannot block forever. It + @ eventual must give up and return an empty message set. This setting + @ determines how long /chat-poll will wait before giving up. The + @ default setting of approximately 7 minutes works well on many systems. + @ Shorter delays might be required on installations that use proxies + @ or web-servers with short timeouts. For best efficiency, this value + @ should be larger rather than smaller. + @ (Property: "chat-poll-timeout")

      + @
      + + multiple_choice_attribute("Alert sound", + "chat-alert-sound", "snd", azAlerts[0], + count(azAlerts)/2, azAlerts); + @

      The sound used in the client-side chat to indicate that a new + @ chat message has arrived. + @ (Property: "chat-alert-sound")

      + @
      + @

      + @
      + db_end_transaction(0); + @ + style_finish_page(); +} + +/* +** WEBPAGE: setup_modreq +** +** Admin page for setting up moderation of tickets and wiki. +*/ +void setup_modreq(void){ + login_check_credentials(); + if( !g.perm.Admin ){ + login_needed(0); + return; + } + + style_set_current_feature("setup"); + style_header("Moderator For Wiki And Tickets"); + db_begin_transaction(); + @
      + login_insert_csrf_secret(); + @
      + onoff_attribute("Moderate ticket changes", + "modreq-tkt", "modreq-tkt", 0, 0); + @

      When enabled, any change to tickets is subject to the approval + @ by a ticket moderator - a user with the "q" or Mod-Tkt privilege. + @ Ticket changes enter the system and are shown locally, but are not + @ synced until they are approved. The moderator has the option to + @ delete the change rather than approve it. Ticket changes made by + @ a user who has the Mod-Tkt privilege are never subject to + @ moderation. (Property: "modreq-tkt") + @ + @


      + onoff_attribute("Moderate wiki changes", + "modreq-wiki", "modreq-wiki", 0, 0); + @

      When enabled, any change to wiki is subject to the approval + @ by a wiki moderator - a user with the "l" or Mod-Wiki privilege. + @ Wiki changes enter the system and are shown locally, but are not + @ synced until they are approved. The moderator has the option to + @ delete the change rather than approve it. Wiki changes made by + @ a user who has the Mod-Wiki privilege are never subject to + @ moderation. (Property: "modreq-wiki") + @

      + + @
      + @

      + @
      + db_end_transaction(0); + style_finish_page(); + +} + +/* +** WEBPAGE: setup_adunit +** +** Administrative page for configuring and controlling ad units +** and how they are displayed. +*/ +void setup_adunit(void){ + login_check_credentials(); + if( !g.perm.Admin ){ + login_needed(0); + return; + } + db_begin_transaction(); + if( P("clear")!=0 && cgi_csrf_safe(1) ){ + db_unprotect(PROTECT_CONFIG); + db_multi_exec("DELETE FROM config WHERE name GLOB 'adunit*'"); + db_protect_pop(); + cgi_replace_parameter("adunit",""); + cgi_replace_parameter("adright",""); + setup_incr_cfgcnt(); + } + + style_set_current_feature("setup"); + style_header("Edit Ad Unit"); + @
      + login_insert_csrf_secret(); + @ Banner Ad-Unit:
      + textarea_attribute("", 6, 80, "adunit", "adunit", "", 0); + @
      + @ Right-Column Ad-Unit:
      + textarea_attribute("", 6, 80, "adunit-right", "adright", "", 0); + @
      + onoff_attribute("Omit ads to administrator", + "adunit-omit-if-admin", "oia", 0, 0); + @
      + onoff_attribute("Omit ads to logged-in users", + "adunit-omit-if-user", "oiu", 0, 0); + @
      + onoff_attribute("Temporarily disable all ads", + "adunit-disable", "oall", 0, 0); + @
      + @ + @ + @
      + @
      + @ Ad-Unit Notes:
        + @
      • Leave both Ad-Units blank to disable all advertising. + @
      • The "Banner Ad-Unit" is used for wide pages. + @
      • The "Right-Column Ad-Unit" is used on pages with tall, narrow content. + @
      • If the "Right-Column Ad-Unit" is blank, the "Banner Ad-Unit" is + @ used on all pages. + @
      • Properties: "adunit", "adunit-right", "adunit-omit-if-admin", and + @ "adunit-omit-if-user". + @
      • Suggested CSS changes: + @
        +  @ div.adunit_banner {
        +  @   margin: auto;
        +  @   width: 100%%;
        +  @ }
        +  @ div.adunit_right {
        +  @   float: right;
        +  @ }
        +  @ div.adunit_right_container {
        +  @   min-height: height-of-right-column-ad-unit;
        +  @ }
        +  @ 
        + @
      • For a place-holder Ad-Unit for testing, Copy/Paste the following + @ with appropriate adjustments to "width:" and "height:". + @
        +  @ <div style='
        +  @   margin: 0 auto;
        +  @   width: 600px;
        +  @   height: 90px;
        +  @   border: 1px solid #f11;
        +  @   background-color: #fcc;
        +  @ '>Demo Ad</div>
        +  @ 
        + @
      • + style_finish_page(); db_end_transaction(0); } /* ** WEBPAGE: setup_logo +** +** Administrative page for changing the logo, background, and icon images. */ void setup_logo(void){ - const char *zMime = "image/gif"; - const char *aImg = P("im"); - int szImg = atoi(PD("im:bytes","0")); - if( szImg>0 ){ - zMime = PD("im:mimetype","image/gif"); + const char *zLogoMtime = db_get_mtime("logo-image", 0, 0); + const char *zLogoMime = db_get("logo-mimetype","image/gif"); + const char *aLogoImg = P("logoim"); + int szLogoImg = atoi(PD("logoim:bytes","0")); + const char *zBgMtime = db_get_mtime("background-image", 0, 0); + const char *zBgMime = db_get("background-mimetype","image/gif"); + const char *aBgImg = P("bgim"); + int szBgImg = atoi(PD("bgim:bytes","0")); + const char *zIconMtime = db_get_mtime("icon-image", 0, 0); + const char *zIconMime = db_get("icon-mimetype","image/gif"); + const char *aIconImg = P("iconim"); + int szIconImg = atoi(PD("iconim:bytes","0")); + if( szLogoImg>0 ){ + zLogoMime = PD("logoim:mimetype","image/gif"); + } + if( szBgImg>0 ){ + zBgMime = PD("bgim:mimetype","image/gif"); + } + if( szIconImg>0 ){ + zIconMime = PD("iconim:mimetype","image/gif"); } login_check_credentials(); - if( !g.okSetup ){ - login_needed(); + if( !g.perm.Admin ){ + login_needed(0); + return; } db_begin_transaction(); - if( P("set")!=0 && zMime && zMime[0] && szImg>0 ){ + if( !cgi_csrf_safe(1) ){ + /* Allow no state changes if not safe from CSRF */ + }else if( P("setlogo")!=0 && zLogoMime && zLogoMime[0] && szLogoImg>0 ){ + Blob img; + Stmt ins; + blob_init(&img, aLogoImg, szLogoImg); + db_unprotect(PROTECT_CONFIG); + db_prepare(&ins, + "REPLACE INTO config(name,value,mtime)" + " VALUES('logo-image',:bytes,now())" + ); + db_bind_blob(&ins, ":bytes", &img); + db_step(&ins); + db_finalize(&ins); + db_multi_exec( + "REPLACE INTO config(name,value,mtime) VALUES('logo-mimetype',%Q,now())", + zLogoMime + ); + db_protect_pop(); + db_end_transaction(0); + cgi_redirect("setup_logo"); + }else if( P("clrlogo")!=0 ){ + db_unprotect(PROTECT_CONFIG); + db_multi_exec( + "DELETE FROM config WHERE name IN " + "('logo-image','logo-mimetype')" + ); + db_protect_pop(); + db_end_transaction(0); + cgi_redirect("setup_logo"); + }else if( P("setbg")!=0 && zBgMime && zBgMime[0] && szBgImg>0 ){ + Blob img; + Stmt ins; + blob_init(&img, aBgImg, szBgImg); + db_unprotect(PROTECT_CONFIG); + db_prepare(&ins, + "REPLACE INTO config(name,value,mtime)" + " VALUES('background-image',:bytes,now())" + ); + db_bind_blob(&ins, ":bytes", &img); + db_step(&ins); + db_finalize(&ins); + db_multi_exec( + "REPLACE INTO config(name,value,mtime)" + " VALUES('background-mimetype',%Q,now())", + zBgMime + ); + db_protect_pop(); + db_end_transaction(0); + cgi_redirect("setup_logo"); + }else if( P("clrbg")!=0 ){ + db_unprotect(PROTECT_CONFIG); + db_multi_exec( + "DELETE FROM config WHERE name IN " + "('background-image','background-mimetype')" + ); + db_protect_pop(); + db_end_transaction(0); + cgi_redirect("setup_logo"); + }else if( P("seticon")!=0 && zIconMime && zIconMime[0] && szIconImg>0 ){ Blob img; Stmt ins; - blob_init(&img, aImg, szImg); + blob_init(&img, aIconImg, szIconImg); + db_unprotect(PROTECT_CONFIG); db_prepare(&ins, - "REPLACE INTO config(name, value)" - " VALUES('logo-image',:bytes)" + "REPLACE INTO config(name,value,mtime)" + " VALUES('icon-image',:bytes,now())" ); db_bind_blob(&ins, ":bytes", &img); db_step(&ins); db_finalize(&ins); db_multi_exec( - "REPLACE INTO config(name, value) VALUES('logo-mimetype',%Q)", - zMime + "REPLACE INTO config(name,value,mtime)" + " VALUES('icon-mimetype',%Q,now())", + zIconMime ); + db_protect_pop(); db_end_transaction(0); cgi_redirect("setup_logo"); - }else if( P("clr")!=0 ){ + }else if( P("clricon")!=0 ){ + db_unprotect(PROTECT_CONFIG); db_multi_exec( - "DELETE FROM config WHERE name GLOB 'logo-*'" + "DELETE FROM config WHERE name IN " + "('icon-image','icon-mimetype')" ); + db_protect_pop(); db_end_transaction(0); cgi_redirect("setup_logo"); } - style_header("Edit Project Logo"); - @

        The current project logo has a MIME-Type of %h(zMime) and looks - @ like this:

        - @
        logo
        + style_set_current_feature("setup"); + style_header("Edit Project Logo And Background"); + @

        The current project logo has a MIME-Type of %h(zLogoMime) + @ and looks like this:

        + @

        logo + @

        @ + @
        @

        The logo is accessible to all users at this URL: @ %s(g.zBaseURL)/logo. @ The logo may or may not appear on each - @ page depending on the CSS and - @ header setup.

        - @ - @ - @

        To set a new logo image, select a file to use as the logo using - @ the entry box below and then press the "Change Logo" button.

        + @ page depending on the CSS and + @ header setup. + @ To change the logo image, use the following form:

        login_insert_csrf_secret(); @ Logo Image file: - @
        - @ - @ - @ - @ - @

        Note: Your browser has probably cached the logo image, so - @ you will probably need to press the Reload button on your browser after - @ changing the logo to provoke your browser to reload the new logo image. - @

        - style_footer(); + @ + @

        + @ + @

        + @

        (Properties: "logo-image" and "logo-mimetype") + @

        + @
        + @ + @

        The current background image has a MIME-Type of %h(zBgMime) + @ and looks like this:

        + @

        background + @

        + @ + @
        + @

        The background image is accessible to all users at this URL: + @ %s(g.zBaseURL)/background. + @ The background image may or may not appear on each + @ page depending on the CSS and + @ header setup. + @ To change the background image, use the following form:

        + login_insert_csrf_secret(); + @ Background image file: + @ + @

        + @ + @

        + @
        + @

        (Properties: "background-image" and "background-mimetype") + @


        + @ + @

        The current icon image has a MIME-Type of %h(zIconMime) + @ and looks like this:

        + @

        icon + @

        + @ + @
        + @

        The icon image is accessible to all users at this URL: + @ %s(g.zBaseURL)/favicon.ico. + @ The icon image may or may not appear on each + @ page depending on the web browser in use and the MIME-Types that it + @ supports for icon images. + @ To change the icon image, use the following form:

        + login_insert_csrf_secret(); + @ Icon image file: + @ + @

        + @ + @

        + @
        + @

        (Properties: "icon-image" and "icon-mimetype") + @


        + @ + @

        Note: Your browser has probably cached these + @ images, so you may need to press the Reload button before changes will + @ take effect.

        + style_finish_page(); db_end_transaction(0); } + +/* +** Prevent the RAW SQL feature from being used to ATTACH a different +** database and query it. +** +** Actually, the RAW SQL feature only does a single statement per request. +** So it is not possible to ATTACH and then do a separate query. This +** routine is not strictly necessary, therefore. But it does not hurt +** to be paranoid. +*/ +int raw_sql_query_authorizer( + void *pError, + int code, + const char *zArg1, + const char *zArg2, + const char *zArg3, + const char *zArg4 +){ + if( code==SQLITE_ATTACH ){ + return SQLITE_DENY; + } + return SQLITE_OK; +} + + +/* +** WEBPAGE: admin_sql +** +** Run raw SQL commands against the database file using the web interface. +** Requires Setup privileges. +*/ +void sql_page(void){ + const char *zQ; + int go = P("go")!=0; + login_check_credentials(); + if( !g.perm.Setup ){ + login_needed(0); + return; + } + add_content_sql_commands(g.db); + zQ = cgi_csrf_safe(1) ? P("q") : 0; + style_set_current_feature("setup"); + style_header("Raw SQL Commands"); + @

        Caution: There are no restrictions on the SQL that can be + @ run by this page. You can do serious and irrepairable damage to the + @ repository. Proceed with extreme caution.

        + @ +#if 0 + @

        Only the first statement in the entry box will be run. + @ Any subsequent statements will be silently ignored.

        + @ + @

        Database names:

        • repository + if( g.zConfigDbName ){ + @
        • configdb + } + if( g.localOpen ){ + @
        • localdb + } + @

        +#endif + + if( P("configtab") ){ + /* If the user presses the "CONFIG Table Query" button, populate the + ** query text with a pre-packaged query against the CONFIG table */ + zQ = "SELECT\n" + " CASE WHEN length(name)<50 THEN name ELSE printf('%.50s...',name)" + " END AS name,\n" + " CASE WHEN typeof(value)<>'blob' AND length(value)<80 THEN value\n" + " ELSE '...' END AS value,\n" + " datetime(mtime, 'unixepoch') AS mtime\n" + "FROM config\n" + "-- ORDER BY mtime DESC; -- optional"; + go = 1; + } + @ + @
        + login_insert_csrf_secret(); + @ SQL:
        + @
        + @ + @ + @ + @ + @
        + if( P("schema") ){ + zQ = sqlite3_mprintf( + "SELECT sql FROM repository.sqlite_schema" + " WHERE sql IS NOT NULL ORDER BY name"); + go = 1; + }else if( P("tablelist") ){ + zQ = sqlite3_mprintf( + "SELECT name FROM repository.sqlite_schema WHERE type='table'" + " ORDER BY name"); + go = 1; + } + if( go ){ + sqlite3_stmt *pStmt; + int rc; + const char *zTail; + int nCol; + int nRow = 0; + int i; + @
        + login_verify_csrf_secret(); + sqlite3_set_authorizer(g.db, raw_sql_query_authorizer, 0); + rc = sqlite3_prepare_v2(g.db, zQ, -1, &pStmt, &zTail); + if( rc!=SQLITE_OK ){ + @
        %h(sqlite3_errmsg(g.db))
        + sqlite3_finalize(pStmt); + }else if( pStmt==0 ){ + /* No-op */ + }else if( (nCol = sqlite3_column_count(pStmt))==0 ){ + sqlite3_step(pStmt); + rc = sqlite3_finalize(pStmt); + if( rc ){ + @
        %h(sqlite3_errmsg(g.db))
        + } + }else{ + @ + while( sqlite3_step(pStmt)==SQLITE_ROW ){ + if( nRow==0 ){ + @ + for(i=0; i%h(sqlite3_column_name(pStmt, i)) + } + @ + } + nRow++; + @ + for(i=0; i + @ %s(sqlite3_column_text(pStmt, i)) + break; + } + case SQLITE_NULL: { + @ + break; + } + case SQLITE_TEXT: { + const char *zText = (const char*)sqlite3_column_text(pStmt, i); + @ + break; + } + case SQLITE_BLOB: { + @ + break; + } + } + } + @ + } + sqlite3_finalize(pStmt); + @
        NULL%h(zText) + @ %d(sqlite3_column_bytes(pStmt, i))-byte BLOB
        + } + } + style_finish_page(); +} + + +/* +** WEBPAGE: admin_th1 +** +** Run raw TH1 commands using the web interface. If Tcl integration was +** enabled at compile-time and the "tcl" setting is enabled, Tcl commands +** may be run as well. Requires Admin privilege. +*/ +void th1_page(void){ + const char *zQ = P("q"); + int go = P("go")!=0; + login_check_credentials(); + if( !g.perm.Setup ){ + login_needed(0); + return; + } + style_set_current_feature("setup"); + style_header("Raw TH1 Commands"); + @

        Caution: There are no restrictions on the TH1 that can be + @ run by this page. If Tcl integration was enabled at compile-time and + @ the "tcl" setting is enabled, Tcl commands may be run as well.

        + @ + @
        + login_insert_csrf_secret(); + @ TH1:
        + @
        + @ + @
        + if( go ){ + const char *zR; + int rc; + int n; + @
        + login_verify_csrf_secret(); + rc = Th_Eval(g.interp, 0, zQ, -1); + zR = Th_GetResult(g.interp, &n); + if( rc==TH_OK ){ + @
        %h(zR)
        + }else{ + @
        %h(zR)
        + } + } + style_finish_page(); +} + +/* +** WEBPAGE: admin_log +** +** Shows the contents of the admin_log table, which is only created if +** the admin-log setting is enabled. Requires Admin or Setup ('a' or +** 's') permissions. +*/ +void page_admin_log(){ + Stmt stLog; + int limit; /* How many entries to show */ + int ofst; /* Offset to the first entry */ + int fLogEnabled; + int counter = 0; + login_check_credentials(); + if( !g.perm.Admin ){ + login_needed(0); + return; + } + style_set_current_feature("setup"); + style_header("Admin Log"); + create_admin_log_table(); + limit = atoi(PD("n","200")); + ofst = atoi(PD("x","0")); + fLogEnabled = db_get_boolean("admin-log", 0); + @
        Admin logging is %s(fLogEnabled?"on":"off"). + @ (Change this on the settings page.)
        + + if( ofst>0 ){ + int prevx = ofst - limit; + if( prevx<0 ) prevx = 0; + @

        [Newer]

        + } + db_prepare(&stLog, + "SELECT datetime(time,'unixepoch'), who, page, what " + "FROM admin_log " + "ORDER BY time DESC"); + style_table_sorter(); + @ + @ + @ + @ + @ + @ + @ + while( SQLITE_ROW == db_step(&stLog) ){ + const char *zTime = db_column_text(&stLog, 0); + const char *zUser = db_column_text(&stLog, 1); + const char *zPage = db_column_text(&stLog, 2); + const char *zMessage = db_column_text(&stLog, 3); + counter++; + if( counterofst+limit ) break; + @ + @ + @ + @ + @ + @ + } + db_finalize(&stLog); + @
        TimeUserPageMessage
        %s(zTime)%s(zUser)%s(zPage)%h(zMessage)
        + if( counter>ofst+limit ){ + @

        [Older]

        + } + style_finish_page(); +} + +/* +** WEBPAGE: srchsetup +** +** Configure the search engine. Requires Admin privilege. +*/ +void page_srchsetup(){ + login_check_credentials(); + if( !g.perm.Admin ){ + login_needed(0); + return; + } + style_set_current_feature("setup"); + style_header("Search Configuration"); + @
        + login_insert_csrf_secret(); + @
        + @ Server-specific settings that affect the + @ /search webpage. + @
        + @
        + textarea_attribute("Document Glob List", 3, 35, "doc-glob", "dg", "", 0); + @

        The "Document Glob List" is a comma- or newline-separated list + @ of GLOB expressions that identify all documents within the source + @ tree that are to be searched when "Document Search" is enabled. + @ Some examples: + @ + @ + @ + @ + @ + @
        *.wiki,*.html,*.md,*.txt + @ Search all wiki, HTML, Markdown, and Text files
        doc/*.md,*/README.txt,README.txt + @ Search all Markdown files in the doc/ subfolder and all README.txt + @ files.
        *Search all checked-in files
        (blank) + @ Search nothing. (Disables document search).
        + @


        + entry_attribute("Document Branch", 20, "doc-branch", "db", "trunk", 0); + @

        When searching documents, use the versions of the files found at the + @ type of the "Document Branch" branch. Recommended value: "trunk". + @ Document search is disabled if blank. + @


        + onoff_attribute("Search Check-in Comments", "search-ci", "sc", 0, 0); + @
        + onoff_attribute("Search Documents", "search-doc", "sd", 0, 0); + @
        + onoff_attribute("Search Tickets", "search-tkt", "st", 0, 0); + @
        + onoff_attribute("Search Wiki", "search-wiki", "sw", 0, 0); + @
        + onoff_attribute("Search Tech Notes", "search-technote", "se", 0, 0); + @
        + onoff_attribute("Search Forum", "search-forum", "sf", 0, 0); + @
        + @

        + @
        + if( P("fts0") ){ + search_drop_index(); + }else if( P("fts1") ){ + search_drop_index(); + search_create_index(); + search_fill_index(); + search_update_index(search_restrict(SRCH_ALL)); + } + if( search_index_exists() ){ + @

        Currently using an SQLite FTS4 search index. This makes search + @ run faster, especially on large repositories, but takes up space.

        + onoff_attribute("Use Porter Stemmer","search-stemmer","ss",0,0); + @

        + @ + style_submenu_element("FTS Index Debugging","%R/test-ftsdocs"); + }else{ + @

        The SQLite FTS4 search index is disabled. All searching will be + @ a full-text scan. This usually works fine, but can be slow for + @ larger repositories.

        + onoff_attribute("Use Porter Stemmer","search-stemmer","ss",0,0); + @

        + } + @

        + style_finish_page(); +} + +/* +** A URL Alias originally called zOldName is now zNewName/zValue. +** Write SQL to make this change into pSql. +** +** If zNewName or zValue is an empty string, then delete the entry. +** +** If zOldName is an empty string, create a new entry. +*/ +static void setup_update_url_alias( + Blob *pSql, + const char *zOldName, + const char *zNewName, + const char *zValue +){ + if( !cgi_csrf_safe(1) ) return; + if( zNewName[0]==0 || zValue[0]==0 ){ + if( zOldName[0] ){ + blob_append_sql(pSql, + "DELETE FROM config WHERE name='walias:%q';\n", + zOldName); + } + return; + } + if( zOldName[0]==0 ){ + blob_append_sql(pSql, + "INSERT INTO config(name,value,mtime) VALUES('walias:%q',%Q,now());\n", + zNewName, zValue); + return; + } + if( strcmp(zOldName, zNewName)!=0 ){ + blob_append_sql(pSql, + "UPDATE config SET name='walias:%q', value=%Q, mtime=now()" + " WHERE name='walias:%q';\n", + zNewName, zValue, zOldName); + }else{ + blob_append_sql(pSql, + "UPDATE config SET value=%Q, mtime=now()" + " WHERE name='walias:%q' AND value<>%Q;\n", + zValue, zOldName, zValue); + } +} + +/* +** WEBPAGE: waliassetup +** +** Configure the URL aliases +*/ +void page_waliassetup(){ + Stmt q; + int cnt = 0; + Blob namelist; + login_check_credentials(); + if( !g.perm.Admin ){ + login_needed(0); + return; + } + style_set_current_feature("setup"); + style_header("URL Alias Configuration"); + if( P("submit")!=0 ){ + Blob token; + Blob sql; + const char *zNewName; + const char *zValue; + char zCnt[10]; + login_verify_csrf_secret(); + blob_init(&namelist, PD("namelist",""), -1); + blob_init(&sql, 0, 0); + while( blob_token(&namelist, &token) ){ + const char *zOldName = blob_str(&token); + sqlite3_snprintf(sizeof(zCnt), zCnt, "n%d", cnt); + zNewName = PD(zCnt, ""); + sqlite3_snprintf(sizeof(zCnt), zCnt, "v%d", cnt); + zValue = PD(zCnt, ""); + setup_update_url_alias(&sql, zOldName, zNewName, zValue); + cnt++; + blob_reset(&token); + } + sqlite3_snprintf(sizeof(zCnt), zCnt, "n%d", cnt); + zNewName = PD(zCnt,""); + sqlite3_snprintf(sizeof(zCnt), zCnt, "v%d", cnt); + zValue = PD(zCnt,""); + setup_update_url_alias(&sql, "", zNewName, zValue); + db_unprotect(PROTECT_CONFIG); + db_multi_exec("%s", blob_sql_text(&sql)); + db_protect_pop(); + blob_reset(&sql); + blob_reset(&namelist); + cnt = 0; + } + db_prepare(&q, + "SELECT substr(name,8), value FROM config WHERE name GLOB 'walias:/*'" + " UNION ALL SELECT '', ''" + ); + @
        + login_insert_csrf_secret(); + @ + @ + cnt++; + if( blob_size(&namelist)>0 ) blob_append(&namelist, " ", 1); + blob_append(&namelist, zName, -1); + } + db_finalize(&q); + @ + @
        AliasURI That The Alias Maps Into + blob_init(&namelist, 0, 0); + while( db_step(&q)==SQLITE_ROW ){ + const char *zName = db_column_text(&q, 0); + const char *zValue = db_column_text(&q, 1); + @
        + @ + @ + @ + @
        + @ + @ + @
        + @
        + @

        When the first term of an incoming URL exactly matches one of + @ the "Aliases" on the left-hand side (LHS) above, the URL is + @ converted into the corresponding form on the right-hand side (RHS). + @

          + @
        • + @ The LHS is compared against only the first term of the incoming URL. + @ All LHS entries in the alias table should therefore begin with a + @ single "/" followed by a single path element. + @

        • + @ The RHS entries in the alias table should begin with a single "/" + @ followed by a path element, and optionally followed by "?" and a + @ list of query parameters. + @

        • + @ Query parameters on the RHS are added to the set of query parameters + @ in the incoming URL. + @

        • + @ If the same query parameter appears in both the incoming URL and + @ on the RHS of the alias, the RHS query parameter value overwrites + @ the value on the incoming URL. + @

        • + @ If a query parameter on the RHS of the alias is of the form "X!" + @ (a name followed by "!") then the X query parameter is removed + @ from the incoming URL if + @ it exists. + @

        • + @ Only a single alias operation occurs. It is not possible to nest aliases. + @ The RHS entries must be built-in webpage names. + @

        • + @ The alias table is only checked if no built-in webpage matches + @ the incoming URL. + @ Hence, it is not possible to override a built-in webpage using aliases. + @ This is by design. + @

        + @ + @

        To delete an entry from the alias table, change its name or value to an + @ empty string and press "Apply Changes". + @ + @

        To add a new alias, fill in the name and value in the bottom row + @ of the table above and press "Apply Changes". + style_finish_page(); +} ADDED src/setupuser.c Index: src/setupuser.c ================================================================== --- /dev/null +++ src/setupuser.c @@ -0,0 +1,916 @@ +/* +** Copyright (c) 2007 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/ +** +******************************************************************************* +** +** Setup pages associated with user management. The code in this +** file was formerly part of the "setup.c" module, but has been broken +** out into its own module to improve maintainability. +** +** Note: Do not confuse "Users" with "Subscribers". Code to deal with +** subscribers is over in the "alerts.c" source file. +*/ +#include "config.h" +#include +#include "setupuser.h" + +/* +** WEBPAGE: setup_ulist +** +** Show a list of users. Clicking on any user jumps to the edit +** screen for that user. Requires Admin privileges. +** +** Query parameters: +** +** with=CAP Only show users that have one or more capabilities in CAP. +** ubg Color backgrounds by username hash +*/ +void setup_ulist(void){ + Stmt s; + double rNow; + const char *zWith = P("with"); + int bUnusedOnly = P("unused")!=0; + int bUbg = P("ubg")!=0; + + login_check_credentials(); + if( !g.perm.Admin ){ + login_needed(0); + return; + } + + style_submenu_element("Add", "setup_uedit"); + style_submenu_element("Log", "access_log"); + style_submenu_element("Help", "setup_ulist_notes"); + if( alert_tables_exist() ){ + style_submenu_element("Subscribers", "subscribers"); + } + style_set_current_feature("setup"); + style_header("User List"); + if( (zWith==0 || zWith[0]==0) && !bUnusedOnly ){ + @ + @ + @ + @ + db_prepare(&s, + "SELECT uid, login, cap, date(mtime,'unixepoch')" + " FROM user" + " WHERE login IN ('anonymous','nobody','developer','reader')" + " ORDER BY login" + ); + while( db_step(&s)==SQLITE_ROW ){ + int uid = db_column_int(&s, 0); + const char *zLogin = db_column_text(&s, 1); + const char *zCap = db_column_text(&s, 2); + const char *zDate = db_column_text(&s, 4); + @ + @ + } + db_finalize(&s); + @
        Category + @ Capabilities (key) + @ Info Last Change
        %h(zLogin) + @ %h(zCap) + + if( fossil_strcmp(zLogin,"anonymous")==0 ){ + @ All logged-in users + }else if( fossil_strcmp(zLogin,"developer")==0 ){ + @ Users with 'v' capability + }else if( fossil_strcmp(zLogin,"nobody")==0 ){ + @ All users without login + }else if( fossil_strcmp(zLogin,"reader")==0 ){ + @ Users with 'u' capability + }else{ + @ + } + if( zDate && zDate[0] ){ + @ %h(zDate) + }else{ + @ + } + @
        + @

        Users
        + }else{ + style_submenu_element("All Users", "setup_ulist"); + if( bUnusedOnly ){ + @
        Unused logins
        + }else if( zWith ){ + if( zWith[1]==0 ){ + @
        Users with capability "%h(zWith)"
        + }else{ + @
        Users with any capability in "%h(zWith)"
        + } + } + } + if( !bUnusedOnly ){ + style_submenu_element("Unused", "setup_ulist?unused"); + } + @ + @ + @ + @ + db_multi_exec( + "CREATE TEMP TABLE lastAccess(uname TEXT PRIMARY KEY, atime REAL)" + "WITHOUT ROWID;" + ); + if( db_table_exists("repository","accesslog") ){ + db_multi_exec( + "INSERT INTO lastAccess(uname, atime)" + " SELECT uname, max(mtime) FROM (" + " SELECT uname, mtime FROM accesslog WHERE success" + " UNION ALL" + " SELECT login AS uname, rcvfrom.mtime AS mtime" + " FROM rcvfrom JOIN user USING(uid))" + " GROUP BY 1;" + ); + } + if( !db_table_exists("repository","subscriber") ){ + db_multi_exec( + "CREATE TEMP TABLE subscriber(suname PRIMARY KEY, ssub, subscriberId)" + "WITHOUT ROWID;" + ); + } + if( bUnusedOnly ){ + zWith = mprintf( + " AND login NOT IN (" + "SELECT user FROM event WHERE user NOT NULL " + "UNION ALL SELECT euser FROM event WHERE euser NOT NULL%s)" + " AND uid NOT IN (SELECT uid FROM rcvfrom)", + alert_tables_exist() ? + " UNION ALL SELECT suname FROM subscriber WHERE suname NOT NULL":""); + }else if( zWith && zWith[0] ){ + zWith = mprintf(" AND fullcap(cap) GLOB '*[%q]*'", zWith); + }else{ + zWith = ""; + } + db_prepare(&s, + "SELECT uid, login, cap, info, date(user.mtime,'unixepoch')," + " lower(login) AS sortkey, " + " CASE WHEN info LIKE '%%expires 20%%'" + " THEN substr(info,instr(lower(info),'expires')+8,10)" + " END AS exp," + "atime," + " subscriber.ssub, subscriber.subscriberId" + " FROM user LEFT JOIN lastAccess ON login=uname" + " LEFT JOIN subscriber ON login=suname" + " WHERE login NOT IN ('anonymous','nobody','developer','reader') %s" + " ORDER BY sortkey", zWith/*safe-for-%s*/ + ); + rNow = db_double(0.0, "SELECT julianday('now');"); + while( db_step(&s)==SQLITE_ROW ){ + int uid = db_column_int(&s, 0); + const char *zLogin = db_column_text(&s, 1); + const char *zCap = db_column_text(&s, 2); + const char *zInfo = db_column_text(&s, 3); + const char *zDate = db_column_text(&s, 4); + const char *zSortKey = db_column_text(&s,5); + const char *zExp = db_column_text(&s,6); + double rATime = db_column_double(&s,7); + char *zAge = 0; + const char *zSub; + int sid = db_column_int(&s,9); + if( rATime>0.0 ){ + zAge = human_readable_age(rNow - rATime); + } + if( bUbg ){ + @ + }else{ + @ + } + @ + fossil_free(zAge); + } + @
        Login NameCapsInfoDateExpireLast Login\ + @ Alerts
        \ + @ %h(zLogin) + @ %h(zCap) + @ %h(zInfo) + @ %h(zDate?zDate:"") + @ %h(zExp?zExp:"") + @ %s(zAge?zAge:"") + if( db_column_type(&s,8)==SQLITE_NULL ){ + @ + }else if( (zSub = db_column_text(&s,8))==0 || zSub[0]==0 ){ + @ off + }else{ + @ %h(zSub) + } + + @
        + db_finalize(&s); + style_table_sorter(); + style_finish_page(); +} + +/* +** WEBPAGE: setup_ulist_notes +** +** A documentation page showing notes about user configuration. This +** information used to be a side-bar on the user list page, but has been +** factored out for improved presentation. +*/ +void setup_ulist_notes(void){ + style_set_current_feature("setup"); + style_header("User Configuration Notes"); + @

        User Configuration Notes:

        + @
          + @
        1. + @ Every user, logged in or not, inherits the privileges of + @ nobody. + @

        2. + @ + @
        3. + @ Any human can login as anonymous since the + @ password is clearly displayed on the login page for them to type. The + @ purpose of requiring anonymous to log in is to prevent access by spiders. + @ Every logged-in user inherits the combined privileges of + @ anonymous and + @ nobody. + @

        4. + @ + @
        5. + @ Users with privilege u inherit the combined + @ privileges of reader, + @ anonymous, and + @ nobody. + @

        6. + @ + @
        7. + @ Users with privilege v inherit the combined + @ privileges of developer, + @ anonymous, and + @ nobody. + @

        8. + @ + @
        9. The permission flags are as follows:

          + capabilities_table(CAPCLASS_ALL); + @
        10. + @
        + style_finish_page(); +} + +/* +** WEBPAGE: setup_ucap_list +** +** A documentation page showing the meaning of the various user capabilities +** code letters. +*/ +void setup_ucap_list(void){ + style_set_current_feature("setup"); + style_header("User Capability Codes"); + @

        All capabilities

        + capabilities_table(CAPCLASS_ALL); + @

        Capabilities associated with checked-in content

        + capabilities_table(CAPCLASS_CODE); + @

        Capabilities associated with data transfer and sync

        + capabilities_table(CAPCLASS_DATA); + @

        Capabilities associated with the forum

        + capabilities_table(CAPCLASS_FORUM); + @

        Capabilities associated with tickets

        + capabilities_table(CAPCLASS_TKT); + @

        Capabilities associated with wiki

        + capabilities_table(CAPCLASS_WIKI); + @

        Administrative capabilities

        + capabilities_table(CAPCLASS_SUPER); + @

        Miscellaneous capabilities

        + capabilities_table(CAPCLASS_OTHER); + style_finish_page(); +} + +/* +** Return true if zPw is a valid password string. A valid +** password string is: +** +** (1) A zero-length string, or +** (2) a string that contains a character other than '*'. +*/ +static int isValidPwString(const char *zPw){ + if( zPw==0 ) return 0; + if( zPw[0]==0 ) return 1; + while( zPw[0]=='*' ){ zPw++; } + return zPw[0]!=0; +} + +/* +** WEBPAGE: setup_uedit +** +** Edit information about a user or create a new user. +** Requires Admin privileges. +*/ +void user_edit(void){ + const char *zId, *zLogin, *zInfo, *zCap, *zPw; + const char *zGroup; + const char *zOldLogin; + int uid, i; + char *zDeleteVerify = 0; /* Delete user verification text */ + int higherUser = 0; /* True if user being edited is SETUP and the */ + /* user doing the editing is ADMIN. Disallow editing */ + const char *inherit[128]; + int a[128]; + const char *oa[128]; + + /* Must have ADMIN privileges to access this page + */ + login_check_credentials(); + if( !g.perm.Admin ){ login_needed(0); return; } + + /* Check to see if an ADMIN user is trying to edit a SETUP account. + ** Don't allow that. + */ + zId = PD("id", "0"); + uid = atoi(zId); + if( zId && !g.perm.Setup && uid>0 ){ + char *zOldCaps; + zOldCaps = db_text(0, "SELECT cap FROM user WHERE uid=%d",uid); + higherUser = zOldCaps && strchr(zOldCaps,'s'); + } + + if( P("can") ){ + /* User pressed the cancel button */ + cgi_redirect(cgi_referer("setup_ulist")); + return; + } + + /* Check for requests to delete the user */ + if( P("delete") && cgi_csrf_safe(1) ){ + int n; + if( P("verifydelete") ){ + /* Verified delete user request */ + db_unprotect(PROTECT_USER); + if( db_table_exists("repository","subscriber") ){ + /* Also delete any subscriptions associated with this user */ + db_multi_exec("DELETE FROM subscriber WHERE suname=" + "(SELECT login FROM user WHERE uid=%d)", uid); + } + db_multi_exec("DELETE FROM user WHERE uid=%d", uid); + db_protect_pop(); + moderation_disapprove_for_missing_users(); + admin_log("Deleted user [%s] (uid %d).", + PD("login","???")/*safe-for-%s*/, uid); + cgi_redirect(cgi_referer("setup_ulist")); + return; + } + n = db_int(0, "SELECT count(*) FROM event" + " WHERE user=%Q AND objid NOT IN private", + P("login")); + if( n==0 ){ + zDeleteVerify = mprintf("Check this box and press \"Delete User\" again"); + }else{ + zDeleteVerify = mprintf( + "User \"%s\" has %d or more artifacts in the block-chain. " + "Delete anyhow?", + P("login")/*safe-for-%s*/, n); + } + } + + style_set_current_feature("setup"); + + /* If we have all the necessary information, write the new or + ** modified user record. After writing the user record, redirect + ** to the page that displays a list of users. + */ + if( !cgi_all("login","info","pw","apply") ){ + /* need all of the above properties to make a change. Since one or + ** more are missing, no-op */ + }else if( higherUser ){ + /* An Admin (a) user cannot edit a Superuser (s) */ + }else if( zDeleteVerify!=0 ){ + /* Need to verify a delete request */ + }else if( !cgi_csrf_safe(1) ){ + /* This might be a cross-site request forgery, so ignore it */ + }else{ + /* We have all the information we need to make the change to the user */ + char c; + char zCap[70], zNm[4]; + zNm[0] = 'a'; + zNm[2] = 0; + for(i=0, c='a'; c<='z'; c++){ + zNm[1] = c; + a[c&0x7f] = ((c!='s' && c!='y') || g.perm.Setup) && P(zNm)!=0; + if( a[c&0x7f] ) zCap[i++] = c; + } + for(c='0'; c<='9'; c++){ + zNm[1] = c; + a[c&0x7f] = P(zNm)!=0; + if( a[c&0x7f] ) zCap[i++] = c; + } + for(c='A'; c<='Z'; c++){ + zNm[1] = c; + a[c&0x7f] = P(zNm)!=0; + if( a[c&0x7f] ) zCap[i++] = c; + } + + zCap[i] = 0; + zPw = P("pw"); + zLogin = P("login"); + if( strlen(zLogin)==0 ){ + const char *zRef = cgi_referer("setup_ulist"); + style_header("User Creation Error"); + @ Empty login not allowed. + @ + @

        + @ [Bummer]

        + style_finish_page(); + return; + } + if( isValidPwString(zPw) ){ + zPw = sha1_shared_secret(zPw, zLogin, 0); + }else{ + zPw = db_text(0, "SELECT pw FROM user WHERE uid=%d", uid); + } + zOldLogin = db_text(0, "SELECT login FROM user WHERE uid=%d", uid); + if( db_exists("SELECT 1 FROM user WHERE login=%Q AND uid!=%d",zLogin,uid) ){ + const char *zRef = cgi_referer("setup_ulist"); + style_header("User Creation Error"); + @ Login "%h(zLogin)" is already used by + @ a different user. + @ + @

        + @ [Bummer]

        + style_finish_page(); + return; + } + login_verify_csrf_secret(); + db_unprotect(PROTECT_USER); + db_multi_exec( + "REPLACE INTO user(uid,login,info,pw,cap,mtime) " + "VALUES(nullif(%d,0),%Q,%Q,%Q,%Q,now())", + uid, zLogin, P("info"), zPw, zCap + ); + db_protect_pop(); + setup_incr_cfgcnt(); + admin_log( "Updated user [%q] with capabilities [%q].", + zLogin, zCap ); + if( atoi(PD("all","0"))>0 ){ + Blob sql; + char *zErr = 0; + blob_zero(&sql); + if( zOldLogin==0 ){ + blob_appendf(&sql, + "INSERT INTO user(login)" + " SELECT %Q WHERE NOT EXISTS(SELECT 1 FROM user WHERE login=%Q);", + zLogin, zLogin + ); + zOldLogin = zLogin; + } + blob_appendf(&sql, + "UPDATE user SET login=%Q," + " pw=coalesce(shared_secret(%Q,%Q," + "(SELECT value FROM config WHERE name='project-code')),pw)," + " info=%Q," + " cap=%Q," + " mtime=now()" + " WHERE login=%Q;", + zLogin, P("pw"), zLogin, P("info"), zCap, + zOldLogin + ); + db_unprotect(PROTECT_USER); + login_group_sql(blob_str(&sql), "
      • ", "
      • \n", &zErr); + db_protect_pop(); + blob_reset(&sql); + admin_log( "Updated user [%q] in all login groups " + "with capabilities [%q].", + zLogin, zCap ); + if( zErr ){ + const char *zRef = cgi_referer("setup_ulist"); + style_header("User Change Error"); + admin_log( "Error updating user '%q': %s'.", zLogin, zErr ); + @ %h(zErr) + @ + @

        + @ [Bummer]

        + style_finish_page(); + return; + } + } + cgi_redirect(cgi_referer("setup_ulist")); + return; + } + + /* Load the existing information about the user, if any + */ + zLogin = ""; + zInfo = ""; + zCap = ""; + zPw = ""; + for(i='a'; i<='z'; i++) oa[i] = ""; + for(i='0'; i<='9'; i++) oa[i] = ""; + for(i='A'; i<='Z'; i++) oa[i] = ""; + if( uid ){ + zLogin = db_text("", "SELECT login FROM user WHERE uid=%d", uid); + zInfo = db_text("", "SELECT info FROM user WHERE uid=%d", uid); + zCap = db_text("", "SELECT cap FROM user WHERE uid=%d", uid); + zPw = db_text("", "SELECT pw FROM user WHERE uid=%d", uid); + for(i=0; zCap[i]; i++){ + char c = zCap[i]; + if( (c>='a' && c<='z') || (c>='0' && c<='9') || (c>='A' && c<='Z') ){ + oa[c&0x7f] = " checked=\"checked\""; + } + } + } + + /* figure out inherited permissions */ + memset((char *)inherit, 0, sizeof(inherit)); + if( fossil_strcmp(zLogin, "developer") ){ + char *z1, *z2; + z1 = z2 = db_text(0,"SELECT cap FROM user WHERE login='developer'"); + while( z1 && *z1 ){ + inherit[0x7f & *(z1++)] = + "[D]"; + } + free(z2); + } + if( fossil_strcmp(zLogin, "reader") ){ + char *z1, *z2; + z1 = z2 = db_text(0,"SELECT cap FROM user WHERE login='reader'"); + while( z1 && *z1 ){ + inherit[0x7f & *(z1++)] = + "[R]"; + } + free(z2); + } + if( fossil_strcmp(zLogin, "anonymous") ){ + char *z1, *z2; + z1 = z2 = db_text(0,"SELECT cap FROM user WHERE login='anonymous'"); + while( z1 && *z1 ){ + inherit[0x7f & *(z1++)] = + "[A]"; + } + free(z2); + } + if( fossil_strcmp(zLogin, "nobody") ){ + char *z1, *z2; + z1 = z2 = db_text(0,"SELECT cap FROM user WHERE login='nobody'"); + while( z1 && *z1 ){ + inherit[0x7f & *(z1++)] = + "[N]"; + } + free(z2); + } + + /* Begin generating the page + */ + style_submenu_element("Cancel", "%s", cgi_referer("setup_ulist")); + if( uid ){ + style_header("Edit User %h", zLogin); + style_submenu_element("Access Log", "%R/access_log?u=%t", zLogin); + }else{ + style_header("Add A New User"); + } + @
        + @
        + login_insert_csrf_secret(); + if( login_is_special(zLogin) ){ + @ + @ + @ + } + @ + @ + @ + @ + if( uid ){ + @ + }else{ + @ + } + @ + @ + @ + if( login_is_special(zLogin) ){ + @ + }else{ + @ + @ + @ + @ + } + @ + @ + @ + @ + @ + @ + @ + @ + @ + if( !login_is_special(zLogin) ){ + @ + @ + if( zPw[0] ){ + /* Obscure the password for all users */ + @ + }else{ + /* Show an empty password as an empty input field */ + char *zRPW = fossil_random_password(12); + @ + } + @ + } + zGroup = login_group_name(); + if( zGroup ){ + @ + @ + @ + } + if( !higherUser ){ + if( zDeleteVerify ){ + @ + @ + @ + @ + } + @ + @ + @ + @ + } + @
        User ID:%d(uid) \ + @ (new user)
        Login:%h(zLogin) + if( alert_tables_exist() ){ + int sid; + sid = db_int(0, "SELECT subscriberId FROM subscriber" + " WHERE suname=%Q", zLogin); + if( sid>0 ){ + @   \ + @ (subscription info for %h(zLogin))\ + } + } + @
        Contact Info:
        Capabilities: +#define B(x) inherit[x] + @
        + @
          + if( g.perm.Setup ){ + @
        • + } + @
        • + @
        • + @
        • +#if 0 /* Not Used */ + @
        • +#endif + @
        • + @
        • + @
        • + @
        • + @
        • + @
        • + @
        • + @
        • + @
        • + @
        • + @
        • + @
        • + @
        • + @
        • + @
        • + @
        • + @
        • + @
        • + @
        • + @
        • + @
        • + @
        • + @
        • + @
        • + @
        • + @
        • + @
        • + @
        • + @
        • + @
        • + @
        + @
        Selected Cap: + @ (missing JS?) + @ (key) + @
        Password: Password suggestion: %z(zRPW)
        Scope: + @ + @ Apply changes to this repository only.
        + @ + @ Apply changes to all repositories in the "%h(zGroup)" + @ login group.
        Verify:
          + if( !login_is_special(zLogin) ){ + @ + } + @
        + @
        + @
        + builtin_request_js("useredit.js"); + @
        + @

        Notes On Privileges And Capabilities:

        + @
          + if( higherUser ){ + @
        • + @ User %h(zLogin) has Setup privileges and you only have Admin privileges + @ so you are not permitted to make changes to %h(zLogin). + @

        • + @ + } + @
        • + @ The Setup user can make arbitrary + @ configuration changes. An Admin user + @ can add other users and change user privileges + @ and reset user passwords. Both automatically get all other privileges + @ listed below. Use these two settings with discretion. + @

        • + @ + @
        • + @ The "N" subscript suffix + @ indicates the privileges of nobody that + @ are available to all users regardless of whether or not they are logged in. + @

        • + @ + @
        • + @ The "A" + @ subscript suffix + @ indicates the privileges of anonymous that + @ are inherited by all logged-in users. + @

        • + @ + @
        • + @ The "D" + @ subscript suffix indicates the privileges of + @ developer that + @ are inherited by all users with the + @ Developer privilege. + @

        • + @ + @
        • + @ The "R" subscript suffix + @ indicates the privileges of reader that + @ are inherited by all users with the Reader + @ privilege. + @

        • + @ + @
        • + @ The Delete privilege give the user the + @ ability to erase wiki, tickets, and attachments that have been added + @ by anonymous users. This capability is intended for deletion of spam. + @ The delete capability is only in effect for 24 hours after the item + @ is first posted. The Setup user can + @ delete anything at any time. + @

        • + @ + @
        • + @ The Hyperlinks privilege allows a user + @ to see most hyperlinks. This is recommended ON for most logged-in users + @ but OFF for user "nobody" to avoid problems with spiders trying to walk + @ every diff and annotation of every historical check-in and file. + @

        • + @ + @
        • + @ The Zip privilege allows a user to + @ see the "download as ZIP" + @ hyperlink and permits access to the /zip page. This allows + @ users to download ZIP archives without granting other rights like + @ Read or + @ Hyperlink. The "z" privilege is recommended + @ for user nobody so that automatic package + @ downloaders can obtain the sources without going through the login + @ procedure. + @

        • + @ + @
        • + @ The Check-in privilege allows remote + @ users to "push". The Check-out privilege + @ allows remote users to "pull". The Clone + @ privilege allows remote users to "clone". + @

        • + @ + @
        • + @ The Read Wiki, + @ New Wiki, + @ Append Wiki, and + @ Write Wiki privileges control access to wiki pages. The + @ Read Ticket, + @ New Ticket, + @ Append Ticket, and + @ Write Ticket privileges control access + @ to trouble tickets. + @ The Ticket Report privilege allows + @ the user to create or edit ticket report formats. + @

        • + @ + @
        • + @ Users with the Password privilege + @ are allowed to change their own password. Recommended ON for most + @ users but OFF for special users developer, + @ anonymous, + @ and nobody. + @

        • + @ + @
        • + @ The View-PII privilege allows the display + @ of personally-identifiable information information such as the + @ email address of users and contact + @ information on tickets. Recommended OFF for + @ anonymous and for + @ nobody but ON for + @ developer. + @

        • + @ + @
        • + @ The Attachment privilege is needed in + @ order to add attachments to tickets or wiki. Write privilege on the + @ ticket or wiki is also required. + @

        • + @ + @
        • + @ Login is prohibited if the password is an empty string. + @

        • + @
        + @ + @

        Special Logins

        + @ + @
          + @
        • + @ No login is required for user nobody. The + @ capabilities of the nobody user are + @ inherited by all users, regardless of whether or not they are logged in. + @ To disable universal access to the repository, make sure that the + @ nobody user has no capabilities + @ enabled. The password for nobody is ignored. + @

        • + @ + @
        • + @ Login is required for user anonymous but the + @ password is displayed on the login screen beside the password entry box + @ so anybody who can read should be able to login as anonymous. + @ On the other hand, spiders and web-crawlers will typically not + @ be able to login. Set the capabilities of the + @ anonymous + @ user to things that you want any human to be able to do, but not any + @ spider. Every other logged-in user inherits the privileges of + @ anonymous. + @

        • + @ + @
        • + @ The developer user is intended as a template + @ for trusted users with check-in privileges. When adding new trusted users, + @ simply select the developer privilege to + @ cause the new user to inherit all privileges of the + @ developer + @ user. Similarly, the reader user is a + @ template for users who are allowed more access than + @ anonymous, + @ but less than a developer. + @

        • + @
        + style_finish_page(); +} Index: src/sha1.c ================================================================== --- src/sha1.c +++ src/sha1.c @@ -1,434 +1,282 @@ /* -** This implementation of SHA1 is adapted from the example implementation -** contained in RFC-3174. -*/ -#include -#include -#include "config.h" -#include "sha1.h" - -/* - * If you do not have the ISO standard stdint.h header file, then you - * must typdef the following: - * name meaning - * uint32_t unsigned 32 bit integer - * uint8_t unsigned 8 bit integer (i.e., unsigned char) - * - */ -#define SHA1HashSize 20 -#define shaSuccess 0 -#define shaInputTooLong 1 -#define shaStateError 2 - -/* - * This structure will hold context information for the SHA-1 - * hashing operation - */ -typedef struct SHA1Context SHA1Context; -struct SHA1Context { - uint32_t Intermediate_Hash[SHA1HashSize/4]; /* Message Digest */ - - uint32_t Length_Low; /* Message length in bits */ - uint32_t Length_High; /* Message length in bits */ - - int Message_Block_Index; /* Index into message block array */ - uint8_t Message_Block[64]; /* 512-bit message blocks */ - - int Computed; /* Is the digest computed? */ - int Corrupted; /* Is the message digest corrupted? */ -}; - -/* - * sha1.c - * - * Description: - * This file implements the Secure Hashing Algorithm 1 as - * defined in FIPS PUB 180-1 published April 17, 1995. - * - * The SHA-1, produces a 160-bit message digest for a given - * data stream. It should take about 2**n steps to find a - * message with the same digest as a given message and - * 2**(n/2) to find any two messages with the same digest, - * when n is the digest size in bits. Therefore, this - * algorithm can serve as a means of providing a - * "fingerprint" for a message. - * - * Portability Issues: - * SHA-1 is defined in terms of 32-bit "words". This code - * uses (included via "sha1.h" to define 32 and 8 - * bit unsigned integer types. If your C compiler does not - * support 32 bit unsigned integers, this code is not - * appropriate. - * - * Caveats: - * SHA-1 is designed to work with messages less than 2^64 bits - * long. Although SHA-1 allows a message digest to be generated - * for messages of any number of bits less than 2^64, this - * implementation only works with messages with a length that is - * a multiple of the size of an 8-bit character. - * - */ - -/* - * Define the SHA1 circular left shift macro - */ -#define SHA1CircularShift(bits,word) \ - (((word) << (bits)) | ((word) >> (32-(bits)))) - -/* Local Function Prototyptes */ -static void SHA1PadMessage(SHA1Context *); -static void SHA1ProcessMessageBlock(SHA1Context *); - -/* - * SHA1Reset - * - * Description: - * This function will initialize the SHA1Context in preparation - * for computing a new SHA1 message digest. - * - * Parameters: - * context: [in/out] - * The context to reset. - * - * Returns: - * sha Error Code. - * - */ -static int SHA1Reset(SHA1Context *context) -{ - context->Length_Low = 0; - context->Length_High = 0; - context->Message_Block_Index = 0; - - context->Intermediate_Hash[0] = 0x67452301; - context->Intermediate_Hash[1] = 0xEFCDAB89; - context->Intermediate_Hash[2] = 0x98BADCFE; - context->Intermediate_Hash[3] = 0x10325476; - context->Intermediate_Hash[4] = 0xC3D2E1F0; - - context->Computed = 0; - context->Corrupted = 0; - - return shaSuccess; -} - -/* - * SHA1Result - * - * Description: - * This function will return the 160-bit message digest into the - * Message_Digest array provided by the caller. - * NOTE: The first octet of hash is stored in the 0th element, - * the last octet of hash in the 19th element. - * - * Parameters: - * context: [in/out] - * The context to use to calculate the SHA-1 hash. - * Message_Digest: [out] - * Where the digest is returned. - * - * Returns: - * sha Error Code. - * - */ -static int SHA1Result( SHA1Context *context, - uint8_t Message_Digest[SHA1HashSize]) -{ - int i; - - if (context->Corrupted) - { - return context->Corrupted; - } - - if (!context->Computed) - { - SHA1PadMessage(context); - for(i=0; i<64; ++i) - { - /* message may be sensitive, clear it out */ - context->Message_Block[i] = 0; - } - context->Length_Low = 0; /* and clear length */ - context->Length_High = 0; - context->Computed = 1; - - } - - for(i = 0; i < SHA1HashSize; ++i) - { - Message_Digest[i] = context->Intermediate_Hash[i>>2] - >> 8 * ( 3 - ( i & 0x03 ) ); - } - - return shaSuccess; -} - -/* - * SHA1Input - * - * Description: - * This function accepts an array of octets as the next portion - * of the message. - * - * Parameters: - * context: [in/out] - * The SHA context to update - * message_array: [in] - * An array of characters representing the next portion of - * the message. - * length: [in] - * The length of the message in message_array - * - * Returns: - * sha Error Code. - * - */ -static -int SHA1Input( SHA1Context *context, - const uint8_t *message_array, - unsigned length) -{ - if (!length) - { - return shaSuccess; - } - - if (context->Computed) - { - context->Corrupted = shaStateError; - - return shaStateError; - } - - if (context->Corrupted) - { - return context->Corrupted; - } - while(length-- && !context->Corrupted) - { - context->Message_Block[context->Message_Block_Index++] = - (*message_array & 0xFF); - - context->Length_Low += 8; - if (context->Length_Low == 0) - { - context->Length_High++; - if (context->Length_High == 0) - { - /* Message is too long */ - context->Corrupted = 1; - } - } - - if (context->Message_Block_Index == 64) - { - SHA1ProcessMessageBlock(context); - } - - message_array++; - } - - return shaSuccess; -} - -/* - * SHA1ProcessMessageBlock - * - * Description: - * This function will process the next 512 bits of the message - * stored in the Message_Block array. - * - * Parameters: - * None. - * - * Returns: - * Nothing. - * - * Comments: - * Many of the variable names in this code, especially the - * single character names, were used because those were the - * names used in the publication. - * - * - */ -static void SHA1ProcessMessageBlock(SHA1Context *context) -{ - const uint32_t K[] = { /* Constants defined in SHA-1 */ - 0x5A827999, - 0x6ED9EBA1, - 0x8F1BBCDC, - 0xCA62C1D6 - }; - int t; /* Loop counter */ - uint32_t temp; /* Temporary word value */ - uint32_t W[80]; /* Word sequence */ - uint32_t A, B, C, D, E; /* Word buffers */ - - /* - * Initialize the first 16 words in the array W - */ - for(t = 0; t < 16; t++) - { - W[t] = context->Message_Block[t * 4] << 24; - W[t] |= context->Message_Block[t * 4 + 1] << 16; - W[t] |= context->Message_Block[t * 4 + 2] << 8; - W[t] |= context->Message_Block[t * 4 + 3]; - } - - for(t = 16; t < 80; t++) - { - W[t] = SHA1CircularShift(1,W[t-3] ^ W[t-8] ^ W[t-14] ^ W[t-16]); - } - - A = context->Intermediate_Hash[0]; - B = context->Intermediate_Hash[1]; - C = context->Intermediate_Hash[2]; - D = context->Intermediate_Hash[3]; - E = context->Intermediate_Hash[4]; - - for(t = 0; t < 20; t++) - { - temp = SHA1CircularShift(5,A) + - ((B & C) | ((~B) & D)) + E + W[t] + K[0]; - E = D; - D = C; - C = SHA1CircularShift(30,B); - - B = A; - A = temp; - } - - for(t = 20; t < 40; t++) - { - temp = SHA1CircularShift(5,A) + (B ^ C ^ D) + E + W[t] + K[1]; - E = D; - D = C; - C = SHA1CircularShift(30,B); - B = A; - A = temp; - } - - for(t = 40; t < 60; t++) - { - temp = SHA1CircularShift(5,A) + - ((B & C) | (B & D) | (C & D)) + E + W[t] + K[2]; - E = D; - D = C; - C = SHA1CircularShift(30,B); - B = A; - A = temp; - } - - for(t = 60; t < 80; t++) - { - temp = SHA1CircularShift(5,A) + (B ^ C ^ D) + E + W[t] + K[3]; - E = D; - D = C; - C = SHA1CircularShift(30,B); - B = A; - A = temp; - } - - context->Intermediate_Hash[0] += A; - context->Intermediate_Hash[1] += B; - context->Intermediate_Hash[2] += C; - context->Intermediate_Hash[3] += D; - context->Intermediate_Hash[4] += E; - - context->Message_Block_Index = 0; -} - -/* - * SHA1PadMessage - * - - * Description: - * According to the standard, the message must be padded to an even - * 512 bits. The first padding bit must be a '1'. The last 64 - * bits represent the length of the original message. All bits in - * between should be 0. This function will pad the message - * according to those rules by filling the Message_Block array - * accordingly. It will also call the ProcessMessageBlock function - * provided appropriately. When it returns, it can be assumed that - * the message digest has been computed. - * - * Parameters: - * context: [in/out] - * The context to pad - * ProcessMessageBlock: [in] - * The appropriate SHA*ProcessMessageBlock function - * Returns: - * Nothing. - * - */ -static void SHA1PadMessage(SHA1Context *context) -{ - /* - * Check to see if the current message block is too small to hold - * the initial padding bits and length. If so, we will pad the - * block, process it, and then continue padding into a second - * block. - */ - if (context->Message_Block_Index > 55) - { - context->Message_Block[context->Message_Block_Index++] = 0x80; - while(context->Message_Block_Index < 64) - { - context->Message_Block[context->Message_Block_Index++] = 0; - } - - SHA1ProcessMessageBlock(context); - - while(context->Message_Block_Index < 56) - { - context->Message_Block[context->Message_Block_Index++] = 0; - } - } - else - { - context->Message_Block[context->Message_Block_Index++] = 0x80; - while(context->Message_Block_Index < 56) - { - - context->Message_Block[context->Message_Block_Index++] = 0; - } - } - - /* - * Store the message length as the last 8 octets - */ - context->Message_Block[56] = context->Length_High >> 24; - context->Message_Block[57] = context->Length_High >> 16; - context->Message_Block[58] = context->Length_High >> 8; - context->Message_Block[59] = context->Length_High; - context->Message_Block[60] = context->Length_Low >> 24; - context->Message_Block[61] = context->Length_Low >> 16; - context->Message_Block[62] = context->Length_Low >> 8; - context->Message_Block[63] = context->Length_Low; - - SHA1ProcessMessageBlock(context); -} - +** Copyright (c) 2006 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 implementation of SHA1. +*/ +#include "config.h" +#include +#include "sha1.h" + + +/* +** SHA1 Implementation #1 is the hardened SHA1 implementation by +** Marc Stevens. Code obtained from GitHub +** +** https://github.com/cr-marcstevens/sha1collisiondetection +** +** Downloaded on 2017-03-01 then repackaged to work with Fossil +** and makeheaders. +*/ +#if FOSSIL_HARDENED_SHA1 + +#if INTERFACE +typedef void(*collision_block_callback)(uint64_t, const uint32_t*, const uint32_t*, const uint32_t*, const uint32_t*); +struct SHA1_CTX { + 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; + collision_block_callback callback; + + uint32_t ihv1[5]; + uint32_t ihv2[5]; + uint32_t m1[80]; + uint32_t m2[80]; + uint32_t states[80][5]; +}; +#endif +void SHA1DCInit(SHA1_CTX*); +void SHA1DCUpdate(SHA1_CTX*, const unsigned char*, unsigned); +int SHA1DCFinal(unsigned char[20], SHA1_CTX*); + +#define SHA1Context SHA1_CTX +#define SHA1Init SHA1DCInit +#define SHA1Update SHA1DCUpdate +#define SHA1Final SHA1DCFinal + +/* +** SHA1 Implementation #2: use the SHA1 algorithm built into SSL +*/ +#elif defined(FOSSIL_ENABLE_SSL) + +# include +# define SHA1Context SHA_CTX +# define SHA1Init SHA1_Init +# define SHA1Update SHA1_Update +# define SHA1Final SHA1_Final + +/* +** SHA1 Implementation #3: If none of the previous two SHA1 +** algorithms work, there is this built-in. This built-in was the +** original implementation used by Fossil. +*/ +#else +/* +** 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 +** 100% Public Domain +*/ +typedef struct SHA1Context SHA1Context; +struct SHA1Context { + unsigned int state[5]; + unsigned int count[2]; + unsigned char buffer[64]; +}; + +/* + * 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. + */ +#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) +#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] + +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; +} + + +/* + * SHA1Init - Initialize new context + */ +static void SHA1Init(SHA1Context *context){ + /* SHA1 initialization constants */ + context->state[0] = 0x67452301; + context->state[1] = 0xEFCDAB89; + context->state[2] = 0x98BADCFE; + context->state[3] = 0x10325476; + context->state[4] = 0xC3D2E1F0; + context->count[0] = context->count[1] = 0; +} + + +/* + * Run your data through this. + */ +static void SHA1Update( + SHA1Context *context, + const unsigned char *data, + unsigned int len +){ + unsigned int i, j; + + j = context->count[0]; + if ((context->count[0] += len << 3) < j) + context->count[1] += (len>>29)+1; + j = (j >> 3) & 63; + if ((j + len) > 63) { + (void)memcpy(&context->buffer[j], data, (i = 64-j)); + SHA1Transform(context->state, context->buffer); + for ( ; i + 63 < len; i += 64) + SHA1Transform(context->state, &data[i]); + j = 0; + } else { + i = 0; + } + (void)memcpy(&context->buffer[j], &data[i], len - i); +} + + +/* + * Add padding and return the message digest. + */ +static void SHA1Final(unsigned char *digest, SHA1Context *context){ + unsigned int i; + unsigned char finalcount[8]; + + for (i = 0; i < 8; i++) { + finalcount[i] = (unsigned char)((context->count[(i >= 4 ? 0 : 1)] + >> ((3-(i & 3)) * 8) ) & 255); /* Endian independent */ + } + SHA1Update(context, (const unsigned char *)"\200", 1); + while ((context->count[0] & 504) != 448) + SHA1Update(context, (const unsigned char *)"\0", 1); + SHA1Update(context, finalcount, 8); /* Should cause a SHA1Transform() */ + + if (digest) { + for (i = 0; i < 20; i++) + digest[i] = (unsigned char) + ((context->state[i>>2] >> ((3-(i & 3)) * 8) ) & 255); + } +} +#endif /* Built-in SHA1 implemenation */ /* ** 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]". */ static void DigestToBase16(unsigned char *digest, char *zBuf){ - static char const zEncode[] = "0123456789abcdef"; - int i, j; - - for(j=i=0; i<20; i++){ - int a = digest[i]; - zBuf[j++] = zEncode[(a>>4)&0xf]; - zBuf[j++] = zEncode[a & 0xf]; - } - zBuf[j] = 0; + static const char zEncode[] = "0123456789abcdef"; + int ix; + + for(ix=0; ix<20; ix++){ + *zBuf++ = zEncode[(*digest>>4)&0xf]; + *zBuf++ = zEncode[*digest++ & 0xf]; + } + *zBuf = '\0'; } /* ** The state of a incremental SHA1 checksum computation. Only one ** such computation can be underway at a time, of course. @@ -439,18 +287,18 @@ /* ** Add more text to the incremental SHA1 checksum. */ void sha1sum_step_text(const char *zText, int nBytes){ if( !incrInit ){ - SHA1Reset(&incrCtx); + SHA1Init(&incrCtx); incrInit = 1; } if( nBytes<=0 ){ if( nBytes==0 ) return; nBytes = strlen(zText); } - SHA1Input(&incrCtx, (unsigned char*)zText, nBytes); + SHA1Update(&incrCtx, (unsigned char*)zText, nBytes); } /* ** Add the content of a blob to the incremental SHA1 checksum. */ @@ -458,21 +306,21 @@ sha1sum_step_text(blob_buffer(p), blob_size(p)); } /* ** Finish the incremental SHA1 checksum. Store the result in blob pOut -** if pOut!=0. Also return a pointer to the result. +** if pOut!=0. Also return a pointer to the result. ** ** This resets the incremental checksum preparing for the next round ** of computation. The return pointer points to a static buffer that ** is overwritten by subsequent calls to this function. */ char *sha1sum_finish(Blob *pOut){ unsigned char zResult[20]; static char zOut[41]; sha1sum_step_text(0,0); - SHA1Result(&incrCtx, zResult); + SHA1Final(zResult, &incrCtx); incrInit = 0; DigestToBase16(zResult, zOut); if( pOut ){ blob_zero(pOut); blob_append(pOut, zOut, 40); @@ -481,35 +329,46 @@ } /* ** Compute the SHA1 checksum of a file on disk. Store the resulting -** checksum in the blob pCksum. pCksum is assumed to be ininitialized. +** checksum in the blob pCksum. pCksum is assumed to be initialized. ** ** Return the number of errors. */ -int sha1sum_file(const char *zFilename, Blob *pCksum){ +int sha1sum_file(const char *zFilename, int eFType, Blob *pCksum){ FILE *in; SHA1Context ctx; unsigned char zResult[20]; char zBuf[10240]; - in = fopen(zFilename,"rb"); + if( eFType==RepoFILE && file_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; } - SHA1Reset(&ctx); + SHA1Init(&ctx); for(;;){ int n; n = fread(zBuf, 1, sizeof(zBuf), in); if( n<=0 ) break; - SHA1Input(&ctx, (unsigned char*)zBuf, (unsigned)n); + SHA1Update(&ctx, (unsigned char*)zBuf, (unsigned)n); } fclose(in); blob_zero(pCksum); blob_resize(pCksum, 40); - SHA1Result(&ctx, zResult); + SHA1Final(zResult, &ctx); DigestToBase16(zResult, blob_buffer(pCksum)); return 0; } /* @@ -521,19 +380,19 @@ */ int sha1sum_blob(const Blob *pIn, Blob *pCksum){ SHA1Context ctx; unsigned char zResult[20]; - SHA1Reset(&ctx); - SHA1Input(&ctx, (unsigned char*)blob_buffer(pIn), blob_size(pIn)); + SHA1Init(&ctx); + SHA1Update(&ctx, (unsigned char*)blob_buffer(pIn), blob_size(pIn)); if( pIn==pCksum ){ blob_reset(pCksum); }else{ blob_zero(pCksum); } blob_resize(pCksum, 40); - SHA1Result(&ctx, zResult); + SHA1Final(zResult, &ctx); DigestToBase16(zResult, blob_buffer(pCksum)); return 0; } /* @@ -543,20 +402,20 @@ char *sha1sum(const char *zIn){ SHA1Context ctx; unsigned char zResult[20]; char zDigest[41]; - SHA1Reset(&ctx); - SHA1Input(&ctx, (unsigned const char*)zIn, strlen(zIn)); - SHA1Result(&ctx, zResult); + SHA1Init(&ctx); + SHA1Update(&ctx, (unsigned const char*)zIn, strlen(zIn)); + SHA1Final(zResult, &ctx); DigestToBase16(zResult, zDigest); return mprintf("%s", zDigest); } /* ** Convert a cleartext password for a specific user into a SHA1 hash. -** +** ** The algorithm here is: ** ** SHA1( project-code + "/" + login + "/" + password ) ** ** In words: The users login name and password are appended to the @@ -564,59 +423,113 @@ ** ** The result of this function is the shared secret used by a client ** to authenticate to a server for the sync protocol. It is also the ** value stored in the USER.PW field of the database. By mixing in the ** login name and the project id with the hash, different shared secrets -** are obtained even if two users select the same password, or if a +** are obtained even if two users select the same password, or if a ** single user selects the same password for multiple projects. */ -char *sha1_shared_secret(const char *zPw, const char *zLogin){ +char *sha1_shared_secret( + const char *zPw, /* The password to encrypt */ + const char *zLogin, /* Username */ + const char *zProjCode /* Project-code. Use built-in project code if NULL */ +){ static char *zProjectId = 0; SHA1Context ctx; unsigned char zResult[20]; char zDigest[41]; - SHA1Reset(&ctx); - if( zProjectId==0 ){ - zProjectId = db_get("project-code", 0); - - /* 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( zProjectId==0 ){ - return mprintf("%s", zPw); - } - } - SHA1Input(&ctx, (unsigned char*)zProjectId, strlen(zProjectId)); - SHA1Input(&ctx, (unsigned char*)"/", 1); - SHA1Input(&ctx, (unsigned char*)zLogin, strlen(zLogin)); - SHA1Input(&ctx, (unsigned char*)"/", 1); - SHA1Input(&ctx, (unsigned const char*)zPw, strlen(zPw)); - SHA1Result(&ctx, zResult); + SHA1Init(&ctx); + if( zProjCode==0 ){ + if( zProjectId==0 ){ + zProjectId = db_get("project-code", 0); + + /* 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( zProjectId==0 ){ + return mprintf("%s", zPw); + } + } + zProjCode = zProjectId; + } + SHA1Update(&ctx, (unsigned char*)zProjCode, strlen(zProjCode)); + SHA1Update(&ctx, (unsigned char*)"/", 1); + SHA1Update(&ctx, (unsigned char*)zLogin, strlen(zLogin)); + SHA1Update(&ctx, (unsigned char*)"/", 1); + SHA1Update(&ctx, (unsigned const char*)zPw, strlen(zPw)); + SHA1Final(zResult, &ctx); DigestToBase16(zResult, zDigest); return mprintf("%s", zDigest); } /* -** COMMAND: sha1sum -** %fossil sha1sum FILE... +** Implement the shared_secret() SQL function. shared_secret() takes two or +** three arguments; the third argument is optional. +** +** (1) The cleartext password +** (2) The login name +** (3) The project code +** +** Returns sha1($password/$login/$projcode). +*/ +void sha1_shared_secret_sql_function( + sqlite3_context *context, + int argc, + sqlite3_value **argv +){ + const char *zPw; + const char *zLogin; + const char *zProjid; + + assert( argc==2 || argc==3 ); + zPw = (const char*)sqlite3_value_text(argv[0]); + if( zPw==0 || zPw[0]==0 ) return; + zLogin = (const char*)sqlite3_value_text(argv[1]); + if( zLogin==0 ) return; + if( argc==3 ){ + zProjid = (const char*)sqlite3_value_text(argv[2]); + if( zProjid && zProjid[0]==0 ) zProjid = 0; + }else{ + zProjid = 0; + } + sqlite3_result_text(context, sha1_shared_secret(zPw, zLogin, zProjid), -1, + fossil_free); +} + +/* +** COMMAND: sha1sum* +** +** Usage: %fossil sha1sum FILE... ** ** Compute an SHA1 checksum of all files named on the command-line. -** If an file is named "-" then take its content from standard input. +** If a file is named "-" then take its content from standard input. +** Options: +** +** -h|--dereference If FILE is a symbolic link, compute the hash +** on the object that the link points to. Normally, +** the hash is over the name of the object that +** the link points to. +** +** See also: [[md5sum]], [[sha3sum]] */ void sha1sum_test(void){ int i; Blob in; Blob cksum; - + int eFType = SymFILE; + if( find_option("dereference","h",0)!=0 ){ + eFType = ExtFILE; + } + for(i=2; i, 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 +#include +#include + +#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*); +void sha1_message_expansion(uint32_t W[80]); +void sha1_compression(uint32_t ihv[5], const uint32_t m[16]); +void sha1_compression_W(uint32_t ihv[5], const uint32_t W[80]); +void sha1_compression_states(uint32_t ihv[5], const uint32_t W[80], uint32_t states[80][5]); +extern sha1_recompression_type sha1_recompression_step[80]; +typedef void(*collision_block_callback)(uint64_t, const uint32_t*, const uint32_t*, const uint32_t*, const uint32_t*); +typedef struct { + 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; + collision_block_callback callback; + + uint32_t ihv1[5]; + uint32_t ihv2[5]; + uint32_t m1[80]; + uint32_t m2[80]; + uint32_t states[80][5]; +} SHA1_CTX; + +/******************** File: lib/ubc_check.c **************************/ +/*** +* Copyright 2017 Marc Stevens , Dan Shumow +* 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; + + + +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); +} + +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; +} + + + +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; +} + + + +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) + +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, +}; + + + + + +void sha1_process(SHA1_CTX* 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; + } + } + } + } +} + + + + + +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); + } +} + +void SHA1DCInit(SHA1_CTX* ctx) +{ + static const union { unsigned char bytes[4]; uint32_t value; } endianness = { { 0, 1, 2, 3 } }; + static const uint32_t littleendian = 0x03020100; + 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; +} + +void SHA1DCSetSafeHash(SHA1_CTX* ctx, int safehash) +{ + if (safehash) + ctx->safe_hash = 1; + else + ctx->safe_hash = 0; +} + + +void SHA1DCSetUseUBC(SHA1_CTX* ctx, int ubc_check) +{ + if (ubc_check) + ctx->ubc_check = 1; + else + ctx->ubc_check = 0; +} + +void SHA1DCSetUseDetectColl(SHA1_CTX* ctx, int detect_coll) +{ + if (detect_coll) + ctx->detect_coll = 1; + else + ctx->detect_coll = 0; +} + +void SHA1DCSetDetectReducedRoundCollision(SHA1_CTX* ctx, int reduced_round_coll) +{ + if (reduced_round_coll) + ctx->reduced_round_coll = 1; + else + ctx->reduced_round_coll = 0; +} + +void SHA1DCSetCallback(SHA1_CTX* ctx, collision_block_callback callback) +{ + ctx->callback = callback; +} + +void SHA1DCUpdate(SHA1_CTX* ctx, const unsigned char* buf, unsigned len) +{ + 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); + } +} + +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 +}; + +int SHA1DCFinal(unsigned char output[20], SHA1_CTX *ctx) +{ + uint32_t last = ctx->total & 63; + uint32_t padn = (last < 56) ? (56 - last) : (120 - last); + uint64_t total; + SHA1DCUpdate(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)); + output[0] = (unsigned char)(ctx->ihv[0] >> 24); + output[1] = (unsigned char)(ctx->ihv[0] >> 16); + output[2] = (unsigned char)(ctx->ihv[0] >> 8); + output[3] = (unsigned char)(ctx->ihv[0]); + output[4] = (unsigned char)(ctx->ihv[1] >> 24); + output[5] = (unsigned char)(ctx->ihv[1] >> 16); + output[6] = (unsigned char)(ctx->ihv[1] >> 8); + output[7] = (unsigned char)(ctx->ihv[1]); + output[8] = (unsigned char)(ctx->ihv[2] >> 24); + output[9] = (unsigned char)(ctx->ihv[2] >> 16); + output[10] = (unsigned char)(ctx->ihv[2] >> 8); + output[11] = (unsigned char)(ctx->ihv[2]); + output[12] = (unsigned char)(ctx->ihv[3] >> 24); + output[13] = (unsigned char)(ctx->ihv[3] >> 16); + output[14] = (unsigned char)(ctx->ihv[3] >> 8); + output[15] = (unsigned char)(ctx->ihv[3]); + output[16] = (unsigned char)(ctx->ihv[4] >> 24); + output[17] = (unsigned char)(ctx->ihv[4] >> 16); + output[18] = (unsigned char)(ctx->ihv[4] >> 8); + output[19] = (unsigned char)(ctx->ihv[4]); + return ctx->found_collision; +} +#endif /* FOSSIL_HARDENED_SHA1 */ ADDED src/sha3.c Index: src/sha3.c ================================================================== --- /dev/null +++ src/sha3.c @@ -0,0 +1,679 @@ +/* +** 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. +*/ +#include "config.h" +#include "sha3.h" + +/* +** 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 + + +/* +** State structure for a SHA3 hash in progress +*/ +typedef struct SHA3Context SHA3Context; +struct SHA3Context { + union { + u64 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]. */ +}; + +/* +** A single step of the Keccak mixing function for a 1600-bit state +*/ +static void KeccakF1600Step(SHA3Context *p){ + int i; + u64 B0, B1, B2, B3, B4; + u64 C0, C1, C2, C3, C4; + u64 D0, D1, D2, D3, D4; + static const u64 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<>(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 ); + } +} + +/* +** Initialize a new hash. iSize determines the size of the hash +** in bits and should be one of 224, 256, 384, or 512. Or iSize +** can be zero to use the default hash size of 256 bits. +*/ +static void SHA3Init(SHA3Context *p, int iSize){ + memset(p, 0, sizeof(*p)); + 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 unsigned int one = 1; + if( 1==*(unsigned char*)&one ){ + /* Little endian. No byte swapping. */ + p->ixMask = 0; + }else{ + /* Big endian. Byte swap. */ + p->ixMask = 7; + } + } +#endif +} + +/* +** Make consecutive calls to the SHA3Update function to add new content +** to the hash +*/ +static void SHA3Update( + SHA3Context *p, + const unsigned char *aData, + unsigned int nData +){ + unsigned int i = 0; +#if SHA3_BYTEORDER==1234 + if( (p->nLoaded % 8)==0 && ((aData - (const unsigned char*)0)&7)==0 ){ + for(; i+7u.s[p->nLoaded/8] ^= *(u64*)&aData[i]; + p->nLoaded += 8; + if( p->nLoaded>=p->nRate ){ + KeccakF1600Step(p); + p->nLoaded = 0; + } + } + } +#endif + for(; iu.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; + } + } +} + +/* +** After all content has been added, invoke SHA3Final() to compute +** the final hash. The function returns a pointer to the binary +** hash value. +*/ +static unsigned char *SHA3Final(SHA3Context *p){ + unsigned int i; + if( p->nLoaded==p->nRate-1 ){ + const unsigned char c1 = 0x86; + SHA3Update(p, &c1, 1); + }else{ + const unsigned char c2 = 0x06; + const unsigned char c3 = 0x80; + SHA3Update(p, &c2, 1); + p->nLoaded = p->nRate - 1; + SHA3Update(p, &c3, 1); + } + for(i=0; inRate; i++){ + p->u.x[i+p->nRate] = p->u.x[i^p->ixMask]; + } + return &p->u.x[p->nRate]; +} + +/* +** Convert a digest into base-16. digest should be declared as +** "unsigned char digest[20]" in the calling function. The SHA3 +** digest is stored in the first 20 bytes. zBuf should +** be "char zBuf[41]". +*/ +static void DigestToBase16(unsigned char *digest, char *zBuf, int nByte){ + static const char zEncode[] = "0123456789abcdef"; + int ix; + + for(ix=0; ix>4)&0xf]; + *zBuf++ = zEncode[*digest++ & 0xf]; + } + *zBuf = '\0'; +} + +/* +** The state of a incremental SHA3 checksum computation. Only one +** such computation can be underway at a time, of course. +*/ +static SHA3Context incrCtx; +static int incrInit = 0; + +/* +** Initialize a new global SHA3 hash. +*/ +void sha3sum_init(int iSize){ + assert( incrInit==0 ); + incrInit = iSize; + SHA3Init(&incrCtx, incrInit); +} + +/* +** Add more text to the incremental SHA3 checksum. +*/ +void sha3sum_step_text(const char *zText, int nBytes){ + assert( incrInit ); + if( nBytes<=0 ){ + if( nBytes==0 ) return; + nBytes = strlen(zText); + } + SHA3Update(&incrCtx, (unsigned char*)zText, nBytes); +} + +/* +** Add the content of a blob to the incremental SHA3 checksum. +*/ +void sha3sum_step_blob(Blob *p){ + assert( incrInit ); + SHA3Update(&incrCtx, (unsigned char*)blob_buffer(p), blob_size(p)); +} + +/* +** Finish the incremental SHA3 checksum. Store the result in blob pOut +** if pOut!=0. Also return a pointer to the result. +** +** This resets the incremental checksum preparing for the next round +** of computation. The return pointer points to a static buffer that +** is overwritten by subsequent calls to this function. +*/ +char *sha3sum_finish(Blob *pOut){ + static char zOut[132]; + DigestToBase16(SHA3Final(&incrCtx), zOut, incrInit/8); + if( pOut ){ + blob_zero(pOut); + blob_append(pOut, zOut, incrInit/4); + } + incrInit = 0; + return zOut; +} + + +/* +** Compute the SHA3 checksum of a file on disk. Store the resulting +** checksum in the blob pCksum. pCksum is assumed to be initialized. +** +** Return the number of errors. +*/ +int sha3sum_file(const char *zFilename, int eFType, int iSize, Blob *pCksum){ + FILE *in; + SHA3Context ctx; + char zBuf[10240]; + + if( eFType==RepoFILE && file_islink(zFilename) ){ + /* Instead of file content, return sha3 of link destination path */ + Blob destinationPath; + int rc; + + blob_read_link(&destinationPath, zFilename); + rc = sha3sum_blob(&destinationPath, iSize, pCksum); + blob_reset(&destinationPath); + return rc; + } + + in = fossil_fopen(zFilename,"rb"); + if( in==0 ){ + return 1; + } + SHA3Init(&ctx, iSize); + for(;;){ + int n; + n = fread(zBuf, 1, sizeof(zBuf), in); + if( n<=0 ) break; + SHA3Update(&ctx, (unsigned char*)zBuf, (unsigned)n); + } + fclose(in); + blob_zero(pCksum); + blob_resize(pCksum, iSize/4); + DigestToBase16(SHA3Final(&ctx), blob_buffer(pCksum), iSize/8); + return 0; +} + +/* +** Compute the SHA3 checksum of a blob in memory. Store the resulting +** checksum in the blob pCksum. pCksum is assumed to be either +** uninitialized or the same blob as pIn. +** +** Return the number of errors. +*/ +int sha3sum_blob(const Blob *pIn, int iSize, Blob *pCksum){ + SHA3Context ctx; + SHA3Init(&ctx, iSize); + SHA3Update(&ctx, (unsigned char*)blob_buffer(pIn), blob_size(pIn)); + if( pIn==pCksum ){ + blob_reset(pCksum); + }else{ + blob_zero(pCksum); + } + blob_resize(pCksum, iSize/4); + DigestToBase16(SHA3Final(&ctx), blob_buffer(pCksum), iSize/8); + return 0; +} + +#if 0 /* NOT USED */ +/* +** Compute the SHA3 checksum of a zero-terminated string. The +** result is held in memory obtained from mprintf(). +*/ +char *sha3sum(const char *zIn, int iSize){ + SHA3Context ctx; + char zDigest[132]; + + SHA3Init(&ctx, iSize); + SHA3Update(&ctx, (unsigned const char*)zIn, strlen(zIn)); + DigestToBase16(SHA3Final(&ctx), zDigest, iSize/8); + return mprintf("%s", zDigest); +} +#endif + +/* +** COMMAND: sha3sum* +** +** Usage: %fossil sha3sum FILE... +** +** Compute an SHA3 checksum of all files named on the command-line. +** If a file is named "-" then take its content from standard input. +** +** To be clear: The official NIST FIPS-202 implementation of SHA3 +** with the added 01 padding is used, not the original Keccak submission. +** +** Options: +** +** --224 Compute a SHA3-224 hash +** --256 Compute a SHA3-256 hash (the default) +** --384 Compute a SHA3-384 hash +** --512 Compute a SHA3-512 hash +** --size N An N-bit hash. N must be a multiple of 32 between +** 128 and 512. +** -h|--dereference If FILE is a symbolic link, compute the hash on +** the object pointed to, not on the link itself. +** +** See also: [[md5sum]], [[sha1sum]] +*/ +void sha3sum_test(void){ + int i; + Blob in; + Blob cksum = empty_blob; + int iSize = 256; + int eFType = SymFILE; + + if( find_option("dereference","h",0) ) eFType = ExtFILE; + if( find_option("224",0,0)!=0 ) iSize = 224; + else if( find_option("256",0,0)!=0 ) iSize = 256; + else if( find_option("384",0,0)!=0 ) iSize = 384; + else if( find_option("512",0,0)!=0 ) iSize = 512; + else{ + const char *zN = find_option("size",0,1); + if( zN!=0 ){ + int n = atoi(zN); + if( n%32!=0 || n<128 || n>512 ){ + fossil_fatal("--size must be a multiple of 64 between 128 and 512"); + } + iSize = n; + } + } + verify_all_options(); + + for(i=2; i 0 ){ + fossil_fatal("Cannot read file: %s", g.argv[i]); + } + fossil_print("%s %s\n", blob_str(&cksum), g.argv[i]); + blob_reset(&cksum); + } +} ADDED src/shell.c Index: src/shell.c ================================================================== --- /dev/null +++ src/shell.c @@ -0,0 +1,22381 @@ +/* DO NOT EDIT! +** This file is automatically generated by the script in the canonical +** SQLite source tree at tool/mkshellc.tcl. That script combines source +** code from various constituent source files of SQLite into this single +** "shell.c" file used to implement the SQLite command-line shell. +** +** Most of the code found below comes from the "src/shell.c.in" file in +** the canonical SQLite source tree. That main file contains "INCLUDE" +** lines that specify other files in the canonical source tree that are +** inserted to getnerate this complete program source file. +** +** The code from multiple files is combined into this single "shell.c" +** source file to help make the command-line program easier to compile. +** +** To modify this program, get a copy of the canonical SQLite source tree, +** edit the src/shell.c.in" and/or some of the other files that are included +** by "src/shell.c.in", then rerun the tool/mkshellc.tcl script. +*/ +/* +** 2001 September 15 +** +** The author disclaims copyright to this source code. In place of +** a legal notice, here is a blessing: +** +** May you do good and not evil. +** May you find forgiveness for yourself and forgive others. +** May you share freely, never taking more than you give. +** +************************************************************************* +** This file contains code to implement the "sqlite" command line +** utility for accessing SQLite databases. +*/ +#if (defined(_WIN32) || defined(WIN32)) && !defined(_CRT_SECURE_NO_WARNINGS) +/* This needs to come before any includes for MSVC compiler */ +#define _CRT_SECURE_NO_WARNINGS +#endif + +/* +** Determine if we are dealing with WinRT, which provides only a subset of +** the full Win32 API. +*/ +#if !defined(SQLITE_OS_WINRT) +# define SQLITE_OS_WINRT 0 +#endif + +/* +** Warning pragmas copied from msvc.h in the core. +*/ +#if defined(_MSC_VER) +#pragma warning(disable : 4054) +#pragma warning(disable : 4055) +#pragma warning(disable : 4100) +#pragma warning(disable : 4127) +#pragma warning(disable : 4130) +#pragma warning(disable : 4152) +#pragma warning(disable : 4189) +#pragma warning(disable : 4206) +#pragma warning(disable : 4210) +#pragma warning(disable : 4232) +#pragma warning(disable : 4244) +#pragma warning(disable : 4305) +#pragma warning(disable : 4306) +#pragma warning(disable : 4702) +#pragma warning(disable : 4706) +#endif /* defined(_MSC_VER) */ + +/* +** No support for loadable extensions in VxWorks. +*/ +#if (defined(__RTP__) || defined(_WRS_KERNEL)) && !SQLITE_OMIT_LOAD_EXTENSION +# define SQLITE_OMIT_LOAD_EXTENSION 1 +#endif + +/* +** Enable large-file support for fopen() and friends on unix. +*/ +#ifndef SQLITE_DISABLE_LFS +# define _LARGE_FILE 1 +# ifndef _FILE_OFFSET_BITS +# define _FILE_OFFSET_BITS 64 +# endif +# define _LARGEFILE_SOURCE 1 +#endif + +#include +#include +#include +#include +#include "sqlite3.h" +typedef sqlite3_int64 i64; +typedef sqlite3_uint64 u64; +typedef unsigned char u8; +#if SQLITE_USER_AUTHENTICATION +# include "sqlite3userauth.h" +#endif +#include +#include + +#if !defined(_WIN32) && !defined(WIN32) +# include +# if !defined(__RTP__) && !defined(_WRS_KERNEL) +# include +# endif +#endif +#if (!defined(_WIN32) && !defined(WIN32)) || defined(__MINGW32__) +# include +# include +# define GETPID getpid +# if defined(__MINGW32__) +# define DIRENT dirent +# ifndef S_ISLNK +# define S_ISLNK(mode) (0) +# endif +# endif +#else +# define GETPID (int)GetCurrentProcessId +#endif +#include +#include + +#if HAVE_READLINE +# include +# include +#endif + +#if HAVE_EDITLINE +# include +#endif + +#if HAVE_EDITLINE || HAVE_READLINE + +# define shell_add_history(X) add_history(X) +# define shell_read_history(X) read_history(X) +# define shell_write_history(X) write_history(X) +# define shell_stifle_history(X) stifle_history(X) +# define shell_readline(X) readline(X) + +#elif HAVE_LINENOISE + +# include "linenoise.h" +# define shell_add_history(X) linenoiseHistoryAdd(X) +# define shell_read_history(X) linenoiseHistoryLoad(X) +# define shell_write_history(X) linenoiseHistorySave(X) +# define shell_stifle_history(X) linenoiseHistorySetMaxLen(X) +# define shell_readline(X) linenoise(X) + +#else + +# define shell_read_history(X) +# define shell_write_history(X) +# define shell_stifle_history(X) + +# define SHELL_USE_LOCAL_GETLINE 1 +#endif + + +#if defined(_WIN32) || defined(WIN32) +# if SQLITE_OS_WINRT +# define SQLITE_OMIT_POPEN 1 +# else +# include +# include +# define isatty(h) _isatty(h) +# ifndef access +# define access(f,m) _access((f),(m)) +# endif +# ifndef unlink +# define unlink _unlink +# endif +# ifndef strdup +# define strdup _strdup +# endif +# undef popen +# define popen _popen +# undef pclose +# define pclose _pclose +# endif +#else + /* Make sure isatty() has a prototype. */ + extern int isatty(int); + +# if !defined(__RTP__) && !defined(_WRS_KERNEL) + /* popen and pclose are not C89 functions and so are + ** sometimes omitted from the header */ + extern FILE *popen(const char*,const char*); + extern int pclose(FILE*); +# else +# define SQLITE_OMIT_POPEN 1 +# endif +#endif + +#if defined(_WIN32_WCE) +/* Windows CE (arm-wince-mingw32ce-gcc) does not provide isatty() + * thus we always assume that we have a console. That can be + * overridden with the -batch command line option. + */ +#define isatty(x) 1 +#endif + +/* ctype macros that work with signed characters */ +#define IsSpace(X) isspace((unsigned char)X) +#define IsDigit(X) isdigit((unsigned char)X) +#define ToLower(X) (char)tolower((unsigned char)X) + +#if defined(_WIN32) || defined(WIN32) +#if SQLITE_OS_WINRT +#include +#endif +#include + +/* string conversion routines only needed on Win32 */ +extern char *sqlite3_win32_unicode_to_utf8(LPCWSTR); +extern char *sqlite3_win32_mbcs_to_utf8_v2(const char *, int); +extern char *sqlite3_win32_utf8_to_mbcs_v2(const char *, int); +extern LPWSTR sqlite3_win32_utf8_to_unicode(const char *zText); +#endif + +/* On Windows, we normally run with output mode of TEXT so that \n characters +** are automatically translated into \r\n. However, this behavior needs +** to be disabled in some cases (ex: when generating CSV output and when +** rendering quoted strings that contain \n characters). The following +** routines take care of that. +*/ +#if (defined(_WIN32) || defined(WIN32)) && !SQLITE_OS_WINRT +static void setBinaryMode(FILE *file, int isOutput){ + if( isOutput ) fflush(file); + _setmode(_fileno(file), _O_BINARY); +} +static void setTextMode(FILE *file, int isOutput){ + if( isOutput ) fflush(file); + _setmode(_fileno(file), _O_TEXT); +} +#else +# define setBinaryMode(X,Y) +# define setTextMode(X,Y) +#endif + + +/* True if the timer is enabled */ +static int enableTimer = 0; + +/* Return the current wall-clock time */ +static sqlite3_int64 timeOfDay(void){ + static sqlite3_vfs *clockVfs = 0; + sqlite3_int64 t; + if( clockVfs==0 ) clockVfs = sqlite3_vfs_find(0); + if( clockVfs==0 ) return 0; /* Never actually happens */ + if( clockVfs->iVersion>=2 && clockVfs->xCurrentTimeInt64!=0 ){ + clockVfs->xCurrentTimeInt64(clockVfs, &t); + }else{ + double r; + clockVfs->xCurrentTime(clockVfs, &r); + t = (sqlite3_int64)(r*86400000.0); + } + return t; +} + +#if !defined(_WIN32) && !defined(WIN32) && !defined(__minux) +#include +#include + +/* VxWorks does not support getrusage() as far as we can determine */ +#if defined(_WRS_KERNEL) || defined(__RTP__) +struct rusage { + struct timeval ru_utime; /* user CPU time used */ + struct timeval ru_stime; /* system CPU time used */ +}; +#define getrusage(A,B) memset(B,0,sizeof(*B)) +#endif + +/* Saved resource information for the beginning of an operation */ +static struct rusage sBegin; /* CPU time at start */ +static sqlite3_int64 iBegin; /* Wall-clock time at start */ + +/* +** Begin timing an operation +*/ +static void beginTimer(void){ + if( enableTimer ){ + getrusage(RUSAGE_SELF, &sBegin); + iBegin = timeOfDay(); + } +} + +/* Return the difference of two time_structs in seconds */ +static double timeDiff(struct timeval *pStart, struct timeval *pEnd){ + return (pEnd->tv_usec - pStart->tv_usec)*0.000001 + + (double)(pEnd->tv_sec - pStart->tv_sec); +} + +/* +** Print the timing results. +*/ +static void endTimer(void){ + if( enableTimer ){ + sqlite3_int64 iEnd = timeOfDay(); + struct rusage sEnd; + getrusage(RUSAGE_SELF, &sEnd); + printf("Run Time: real %.3f user %f sys %f\n", + (iEnd - iBegin)*0.001, + timeDiff(&sBegin.ru_utime, &sEnd.ru_utime), + timeDiff(&sBegin.ru_stime, &sEnd.ru_stime)); + } +} + +#define BEGIN_TIMER beginTimer() +#define END_TIMER endTimer() +#define HAS_TIMER 1 + +#elif (defined(_WIN32) || defined(WIN32)) + +/* Saved resource information for the beginning of an operation */ +static HANDLE hProcess; +static FILETIME ftKernelBegin; +static FILETIME ftUserBegin; +static sqlite3_int64 ftWallBegin; +typedef BOOL (WINAPI *GETPROCTIMES)(HANDLE, LPFILETIME, LPFILETIME, + LPFILETIME, LPFILETIME); +static GETPROCTIMES getProcessTimesAddr = NULL; + +/* +** Check to see if we have timer support. Return 1 if necessary +** support found (or found previously). +*/ +static int hasTimer(void){ + if( getProcessTimesAddr ){ + return 1; + } else { +#if !SQLITE_OS_WINRT + /* GetProcessTimes() isn't supported in WIN95 and some other Windows + ** versions. See if the version we are running on has it, and if it + ** does, save off a pointer to it and the current process handle. + */ + hProcess = GetCurrentProcess(); + if( hProcess ){ + HINSTANCE hinstLib = LoadLibrary(TEXT("Kernel32.dll")); + if( NULL != hinstLib ){ + getProcessTimesAddr = + (GETPROCTIMES) GetProcAddress(hinstLib, "GetProcessTimes"); + if( NULL != getProcessTimesAddr ){ + return 1; + } + FreeLibrary(hinstLib); + } + } +#endif + } + return 0; +} + +/* +** Begin timing an operation +*/ +static void beginTimer(void){ + if( enableTimer && getProcessTimesAddr ){ + FILETIME ftCreation, ftExit; + getProcessTimesAddr(hProcess,&ftCreation,&ftExit, + &ftKernelBegin,&ftUserBegin); + ftWallBegin = timeOfDay(); + } +} + +/* Return the difference of two FILETIME structs in seconds */ +static double timeDiff(FILETIME *pStart, FILETIME *pEnd){ + sqlite_int64 i64Start = *((sqlite_int64 *) pStart); + sqlite_int64 i64End = *((sqlite_int64 *) pEnd); + return (double) ((i64End - i64Start) / 10000000.0); +} + +/* +** Print the timing results. +*/ +static void endTimer(void){ + if( enableTimer && getProcessTimesAddr){ + FILETIME ftCreation, ftExit, ftKernelEnd, ftUserEnd; + sqlite3_int64 ftWallEnd = timeOfDay(); + getProcessTimesAddr(hProcess,&ftCreation,&ftExit,&ftKernelEnd,&ftUserEnd); + printf("Run Time: real %.3f user %f sys %f\n", + (ftWallEnd - ftWallBegin)*0.001, + timeDiff(&ftUserBegin, &ftUserEnd), + timeDiff(&ftKernelBegin, &ftKernelEnd)); + } +} + +#define BEGIN_TIMER beginTimer() +#define END_TIMER endTimer() +#define HAS_TIMER hasTimer() + +#else +#define BEGIN_TIMER +#define END_TIMER +#define HAS_TIMER 0 +#endif + +/* +** Used to prevent warnings about unused parameters +*/ +#define UNUSED_PARAMETER(x) (void)(x) + +/* +** Number of elements in an array +*/ +#define ArraySize(X) (int)(sizeof(X)/sizeof(X[0])) + +/* +** If the following flag is set, then command execution stops +** at an error if we are not interactive. +*/ +static int bail_on_error = 0; + +/* +** Threat stdin as an interactive input if the following variable +** is true. Otherwise, assume stdin is connected to a file or pipe. +*/ +static int stdin_is_interactive = 1; + +/* +** On Windows systems we have to know if standard output is a console +** in order to translate UTF-8 into MBCS. The following variable is +** true if translation is required. +*/ +static int stdout_is_console = 1; + +/* +** The following is the open SQLite database. We make a pointer +** to this database a static variable so that it can be accessed +** by the SIGINT handler to interrupt database processing. +*/ +static sqlite3 *globalDb = 0; + +/* +** True if an interrupt (Control-C) has been received. +*/ +static volatile int seenInterrupt = 0; + +#ifdef SQLITE_DEBUG +/* +** Out-of-memory simulator variables +*/ +static unsigned int oomCounter = 0; /* Simulate OOM when equals 1 */ +static unsigned int oomRepeat = 0; /* Number of OOMs in a row */ +static void*(*defaultMalloc)(int) = 0; /* The low-level malloc routine */ +#endif /* SQLITE_DEBUG */ + +/* +** This is the name of our program. It is set in main(), used +** in a number of other places, mostly for error messages. +*/ +static char *Argv0; + +/* +** Prompt strings. Initialized in main. Settable with +** .prompt main continue +*/ +static char mainPrompt[20]; /* First line prompt. default: "sqlite> "*/ +static char continuePrompt[20]; /* Continuation prompt. default: " ...> " */ + +/* +** Render output like fprintf(). Except, if the output is going to the +** console and if this is running on a Windows machine, translate the +** output from UTF-8 into MBCS. +*/ +#if defined(_WIN32) || defined(WIN32) +void utf8_printf(FILE *out, const char *zFormat, ...){ + va_list ap; + va_start(ap, zFormat); + if( stdout_is_console && (out==stdout || out==stderr) ){ + char *z1 = sqlite3_vmprintf(zFormat, ap); + char *z2 = sqlite3_win32_utf8_to_mbcs_v2(z1, 0); + sqlite3_free(z1); + fputs(z2, out); + sqlite3_free(z2); + }else{ + vfprintf(out, zFormat, ap); + } + va_end(ap); +} +#elif !defined(utf8_printf) +# define utf8_printf fprintf +#endif + +/* +** Render output like fprintf(). This should not be used on anything that +** includes string formatting (e.g. "%s"). +*/ +#if !defined(raw_printf) +# define raw_printf fprintf +#endif + +/* Indicate out-of-memory and exit. */ +static void shell_out_of_memory(void){ + raw_printf(stderr,"Error: out of memory\n"); + exit(1); +} + +#ifdef SQLITE_DEBUG +/* This routine is called when a simulated OOM occurs. It is broken +** out as a separate routine to make it easy to set a breakpoint on +** the OOM +*/ +void shellOomFault(void){ + if( oomRepeat>0 ){ + oomRepeat--; + }else{ + oomCounter--; + } +} +#endif /* SQLITE_DEBUG */ + +#ifdef SQLITE_DEBUG +/* This routine is a replacement malloc() that is used to simulate +** Out-Of-Memory (OOM) errors for testing purposes. +*/ +static void *oomMalloc(int nByte){ + if( oomCounter ){ + if( oomCounter==1 ){ + shellOomFault(); + return 0; + }else{ + oomCounter--; + } + } + return defaultMalloc(nByte); +} +#endif /* SQLITE_DEBUG */ + +#ifdef SQLITE_DEBUG +/* Register the OOM simulator. This must occur before any memory +** allocations */ +static void registerOomSimulator(void){ + sqlite3_mem_methods mem; + sqlite3_config(SQLITE_CONFIG_GETMALLOC, &mem); + defaultMalloc = mem.xMalloc; + mem.xMalloc = oomMalloc; + sqlite3_config(SQLITE_CONFIG_MALLOC, &mem); +} +#endif + +/* +** Write I/O traces to the following stream. +*/ +#ifdef SQLITE_ENABLE_IOTRACE +static FILE *iotrace = 0; +#endif + +/* +** This routine works like printf in that its first argument is a +** format string and subsequent arguments are values to be substituted +** in place of % fields. The result of formatting this string +** is written to iotrace. +*/ +#ifdef SQLITE_ENABLE_IOTRACE +static void SQLITE_CDECL iotracePrintf(const char *zFormat, ...){ + va_list ap; + char *z; + if( iotrace==0 ) return; + va_start(ap, zFormat); + z = sqlite3_vmprintf(zFormat, ap); + va_end(ap); + utf8_printf(iotrace, "%s", z); + sqlite3_free(z); +} +#endif + +/* +** Output string zUtf to stream pOut as w characters. If w is negative, +** then right-justify the text. W is the width in UTF-8 characters, not +** in bytes. This is different from the %*.*s specification in printf +** since with %*.*s the width is measured in bytes, not characters. +*/ +static void utf8_width_print(FILE *pOut, int w, const char *zUtf){ + int i; + int n; + int aw = w<0 ? -w : w; + for(i=n=0; zUtf[i]; i++){ + if( (zUtf[i]&0xc0)!=0x80 ){ + n++; + if( n==aw ){ + do{ i++; }while( (zUtf[i]&0xc0)==0x80 ); + break; + } + } + } + if( n>=aw ){ + utf8_printf(pOut, "%.*s", i, zUtf); + }else if( w<0 ){ + utf8_printf(pOut, "%*s%s", aw-n, "", zUtf); + }else{ + utf8_printf(pOut, "%s%*s", zUtf, aw-n, ""); + } +} + + +/* +** Determines if a string is a number of not. +*/ +static int isNumber(const char *z, int *realnum){ + if( *z=='-' || *z=='+' ) z++; + if( !IsDigit(*z) ){ + return 0; + } + z++; + if( realnum ) *realnum = 0; + while( IsDigit(*z) ){ z++; } + if( *z=='.' ){ + z++; + if( !IsDigit(*z) ) return 0; + while( IsDigit(*z) ){ z++; } + if( realnum ) *realnum = 1; + } + if( *z=='e' || *z=='E' ){ + z++; + if( *z=='+' || *z=='-' ) z++; + if( !IsDigit(*z) ) return 0; + while( IsDigit(*z) ){ z++; } + if( realnum ) *realnum = 1; + } + return *z==0; +} + +/* +** Compute a string length that is limited to what can be stored in +** lower 30 bits of a 32-bit signed integer. +*/ +static int strlen30(const char *z){ + const char *z2 = z; + while( *z2 ){ z2++; } + return 0x3fffffff & (int)(z2 - z); +} + +/* +** Return the length of a string in characters. Multibyte UTF8 characters +** count as a single character. +*/ +static int strlenChar(const char *z){ + int n = 0; + while( *z ){ + if( (0xc0&*(z++))!=0x80 ) n++; + } + return n; +} + +/* +** Return true if zFile does not exist or if it is not an ordinary file. +*/ +#ifdef _WIN32 +# define notNormalFile(X) 0 +#else +static int notNormalFile(const char *zFile){ + struct stat x; + int rc; + memset(&x, 0, sizeof(x)); + rc = stat(zFile, &x); + return rc || !S_ISREG(x.st_mode); +} +#endif + +/* +** This routine reads a line of text from FILE in, stores +** the text in memory obtained from malloc() and returns a pointer +** to the text. NULL is returned at end of file, or if malloc() +** fails. +** +** If zLine is not NULL then it is a malloced buffer returned from +** a previous call to this routine that may be reused. +*/ +static char *local_getline(char *zLine, FILE *in){ + int nLine = zLine==0 ? 0 : 100; + int n = 0; + + while( 1 ){ + if( n+100>nLine ){ + nLine = nLine*2 + 100; + zLine = realloc(zLine, nLine); + if( zLine==0 ) shell_out_of_memory(); + } + if( fgets(&zLine[n], nLine - n, in)==0 ){ + if( n==0 ){ + free(zLine); + return 0; + } + zLine[n] = 0; + break; + } + while( zLine[n] ) n++; + if( n>0 && zLine[n-1]=='\n' ){ + n--; + if( n>0 && zLine[n-1]=='\r' ) n--; + zLine[n] = 0; + break; + } + } +#if defined(_WIN32) || defined(WIN32) + /* For interactive input on Windows systems, translate the + ** multi-byte characterset characters into UTF-8. */ + if( stdin_is_interactive && in==stdin ){ + char *zTrans = sqlite3_win32_mbcs_to_utf8_v2(zLine, 0); + if( zTrans ){ + int nTrans = strlen30(zTrans)+1; + if( nTrans>nLine ){ + zLine = realloc(zLine, nTrans); + if( zLine==0 ) shell_out_of_memory(); + } + memcpy(zLine, zTrans, nTrans); + sqlite3_free(zTrans); + } + } +#endif /* defined(_WIN32) || defined(WIN32) */ + return zLine; +} + +/* +** Retrieve a single line of input text. +** +** If in==0 then read from standard input and prompt before each line. +** If isContinuation is true, then a continuation prompt is appropriate. +** If isContinuation is zero, then the main prompt should be used. +** +** If zPrior is not NULL then it is a buffer from a prior call to this +** routine that can be reused. +** +** The result is stored in space obtained from malloc() and must either +** be freed by the caller or else passed back into this routine via the +** zPrior argument for reuse. +*/ +static char *one_input_line(FILE *in, char *zPrior, int isContinuation){ + char *zPrompt; + char *zResult; + if( in!=0 ){ + zResult = local_getline(zPrior, in); + }else{ + zPrompt = isContinuation ? continuePrompt : mainPrompt; +#if SHELL_USE_LOCAL_GETLINE + printf("%s", zPrompt); + fflush(stdout); + zResult = local_getline(zPrior, stdin); +#else + free(zPrior); + zResult = shell_readline(zPrompt); + if( zResult && *zResult ) shell_add_history(zResult); +#endif + } + return zResult; +} + + +/* +** Return the value of a hexadecimal digit. Return -1 if the input +** is not a hex digit. +*/ +static int hexDigitValue(char c){ + if( c>='0' && c<='9' ) return c - '0'; + if( c>='a' && c<='f' ) return c - 'a' + 10; + if( c>='A' && c<='F' ) return c - 'A' + 10; + return -1; +} + +/* +** Interpret zArg as an integer value, possibly with suffixes. +*/ +static sqlite3_int64 integerValue(const char *zArg){ + sqlite3_int64 v = 0; + static const struct { char *zSuffix; int iMult; } aMult[] = { + { "KiB", 1024 }, + { "MiB", 1024*1024 }, + { "GiB", 1024*1024*1024 }, + { "KB", 1000 }, + { "MB", 1000000 }, + { "GB", 1000000000 }, + { "K", 1000 }, + { "M", 1000000 }, + { "G", 1000000000 }, + }; + int i; + int isNeg = 0; + if( zArg[0]=='-' ){ + isNeg = 1; + zArg++; + }else if( zArg[0]=='+' ){ + zArg++; + } + if( zArg[0]=='0' && zArg[1]=='x' ){ + int x; + zArg += 2; + while( (x = hexDigitValue(zArg[0]))>=0 ){ + v = (v<<4) + x; + zArg++; + } + }else{ + while( IsDigit(zArg[0]) ){ + v = v*10 + zArg[0] - '0'; + zArg++; + } + } + for(i=0; iz); + initText(p); +} + +/* zIn is either a pointer to a NULL-terminated string in memory obtained +** from malloc(), or a NULL pointer. The string pointed to by zAppend is +** added to zIn, and the result returned in memory obtained from malloc(). +** zIn, if it was not NULL, is freed. +** +** If the third argument, quote, is not '\0', then it is used as a +** quote character for zAppend. +*/ +static void appendText(ShellText *p, char const *zAppend, char quote){ + int len; + int i; + int nAppend = strlen30(zAppend); + + len = nAppend+p->n+1; + if( quote ){ + len += 2; + for(i=0; in+len>=p->nAlloc ){ + p->nAlloc = p->nAlloc*2 + len + 20; + p->z = realloc(p->z, p->nAlloc); + if( p->z==0 ) shell_out_of_memory(); + } + + if( quote ){ + char *zCsr = p->z+p->n; + *zCsr++ = quote; + for(i=0; in = (int)(zCsr - p->z); + *zCsr = '\0'; + }else{ + memcpy(p->z+p->n, zAppend, nAppend); + p->n += nAppend; + p->z[p->n] = '\0'; + } +} + +/* +** Attempt to determine if identifier zName needs to be quoted, either +** because it contains non-alphanumeric characters, or because it is an +** SQLite keyword. Be conservative in this estimate: When in doubt assume +** that quoting is required. +** +** Return '"' if quoting is required. Return 0 if no quoting is required. +*/ +static char quoteChar(const char *zName){ + int i; + if( !isalpha((unsigned char)zName[0]) && zName[0]!='_' ) return '"'; + for(i=0; zName[i]; i++){ + if( !isalnum((unsigned char)zName[i]) && zName[i]!='_' ) return '"'; + } + return sqlite3_keyword_check(zName, i) ? '"' : 0; +} + +/* +** Construct a fake object name and column list to describe the structure +** of the view, virtual table, or table valued function zSchema.zName. +*/ +static char *shellFakeSchema( + sqlite3 *db, /* The database connection containing the vtab */ + const char *zSchema, /* Schema of the database holding the vtab */ + const char *zName /* The name of the virtual table */ +){ + sqlite3_stmt *pStmt = 0; + char *zSql; + ShellText s; + char cQuote; + char *zDiv = "("; + int nRow = 0; + + zSql = sqlite3_mprintf("PRAGMA \"%w\".table_info=%Q;", + zSchema ? zSchema : "main", zName); + sqlite3_prepare_v2(db, zSql, -1, &pStmt, 0); + sqlite3_free(zSql); + initText(&s); + if( zSchema ){ + cQuote = quoteChar(zSchema); + if( cQuote && sqlite3_stricmp(zSchema,"temp")==0 ) cQuote = 0; + appendText(&s, zSchema, cQuote); + appendText(&s, ".", 0); + } + cQuote = quoteChar(zName); + appendText(&s, zName, cQuote); + while( sqlite3_step(pStmt)==SQLITE_ROW ){ + const char *zCol = (const char*)sqlite3_column_text(pStmt, 1); + nRow++; + appendText(&s, zDiv, 0); + zDiv = ","; + cQuote = quoteChar(zCol); + appendText(&s, zCol, cQuote); + } + appendText(&s, ")", 0); + sqlite3_finalize(pStmt); + if( nRow==0 ){ + freeText(&s); + s.z = 0; + } + return s.z; +} + +/* +** SQL function: shell_module_schema(X) +** +** Return a fake schema for the table-valued function or eponymous virtual +** table X. +*/ +static void shellModuleSchema( + sqlite3_context *pCtx, + int nVal, + sqlite3_value **apVal +){ + const char *zName = (const char*)sqlite3_value_text(apVal[0]); + char *zFake = shellFakeSchema(sqlite3_context_db_handle(pCtx), 0, zName); + UNUSED_PARAMETER(nVal); + if( zFake ){ + sqlite3_result_text(pCtx, sqlite3_mprintf("/* %s */", zFake), + -1, sqlite3_free); + free(zFake); + } +} + +/* +** SQL function: shell_add_schema(S,X) +** +** Add the schema name X to the CREATE statement in S and return the result. +** Examples: +** +** CREATE TABLE t1(x) -> CREATE TABLE xyz.t1(x); +** +** Also works on +** +** CREATE INDEX +** CREATE UNIQUE INDEX +** CREATE VIEW +** CREATE TRIGGER +** CREATE VIRTUAL TABLE +** +** This UDF is used by the .schema command to insert the schema name of +** attached databases into the middle of the sqlite_schema.sql field. +*/ +static void shellAddSchemaName( + sqlite3_context *pCtx, + int nVal, + sqlite3_value **apVal +){ + static const char *aPrefix[] = { + "TABLE", + "INDEX", + "UNIQUE INDEX", + "VIEW", + "TRIGGER", + "VIRTUAL TABLE" + }; + int i = 0; + const char *zIn = (const char*)sqlite3_value_text(apVal[0]); + const char *zSchema = (const char*)sqlite3_value_text(apVal[1]); + const char *zName = (const char*)sqlite3_value_text(apVal[2]); + sqlite3 *db = sqlite3_context_db_handle(pCtx); + UNUSED_PARAMETER(nVal); + if( zIn!=0 && strncmp(zIn, "CREATE ", 7)==0 ){ + for(i=0; i<(int)(sizeof(aPrefix)/sizeof(aPrefix[0])); i++){ + int n = strlen30(aPrefix[i]); + if( strncmp(zIn+7, aPrefix[i], n)==0 && zIn[n+7]==' ' ){ + char *z = 0; + char *zFake = 0; + if( zSchema ){ + char cQuote = quoteChar(zSchema); + if( cQuote && sqlite3_stricmp(zSchema,"temp")!=0 ){ + z = sqlite3_mprintf("%.*s \"%w\".%s", n+7, zIn, zSchema, zIn+n+8); + }else{ + z = sqlite3_mprintf("%.*s %s.%s", n+7, zIn, zSchema, zIn+n+8); + } + } + if( zName + && aPrefix[i][0]=='V' + && (zFake = shellFakeSchema(db, zSchema, zName))!=0 + ){ + if( z==0 ){ + z = sqlite3_mprintf("%s\n/* %s */", zIn, zFake); + }else{ + z = sqlite3_mprintf("%z\n/* %s */", z, zFake); + } + free(zFake); + } + if( z ){ + sqlite3_result_text(pCtx, z, -1, sqlite3_free); + return; + } + } + } + } + sqlite3_result_value(pCtx, apVal[0]); +} + +/* +** The source code for several run-time loadable extensions is inserted +** below by the ../tool/mkshellc.tcl script. Before processing that included +** code, we need to override some macros to make the included program code +** work here in the middle of this regular program. +*/ +#define SQLITE_EXTENSION_INIT1 +#define SQLITE_EXTENSION_INIT2(X) (void)(X) + +#if defined(_WIN32) && defined(_MSC_VER) +/************************* Begin test_windirent.h ******************/ +/* +** 2015 November 30 +** +** The author disclaims copyright to this source code. In place of +** a legal notice, here is a blessing: +** +** May you do good and not evil. +** May you find forgiveness for yourself and forgive others. +** May you share freely, never taking more than you give. +** +************************************************************************* +** This file contains declarations for most of the opendir() family of +** POSIX functions on Win32 using the MSVCRT. +*/ + +#if defined(_WIN32) && defined(_MSC_VER) && !defined(SQLITE_WINDIRENT_H) +#define SQLITE_WINDIRENT_H + +/* +** We need several data types from the Windows SDK header. +*/ + +#ifndef WIN32_LEAN_AND_MEAN +#define WIN32_LEAN_AND_MEAN +#endif + +#include "windows.h" + +/* +** We need several support functions from the SQLite core. +*/ + +/* #include "sqlite3.h" */ + +/* +** We need several things from the ANSI and MSVCRT headers. +*/ + +#include +#include +#include +#include +#include +#include +#include + +/* +** We may need several defines that should have been in "sys/stat.h". +*/ + +#ifndef S_ISREG +#define S_ISREG(mode) (((mode) & S_IFMT) == S_IFREG) +#endif + +#ifndef S_ISDIR +#define S_ISDIR(mode) (((mode) & S_IFMT) == S_IFDIR) +#endif + +#ifndef S_ISLNK +#define S_ISLNK(mode) (0) +#endif + +/* +** We may need to provide the "mode_t" type. +*/ + +#ifndef MODE_T_DEFINED + #define MODE_T_DEFINED + typedef unsigned short mode_t; +#endif + +/* +** We may need to provide the "ino_t" type. +*/ + +#ifndef INO_T_DEFINED + #define INO_T_DEFINED + typedef unsigned short ino_t; +#endif + +/* +** We need to define "NAME_MAX" if it was not present in "limits.h". +*/ + +#ifndef NAME_MAX +# ifdef FILENAME_MAX +# define NAME_MAX (FILENAME_MAX) +# else +# define NAME_MAX (260) +# endif +#endif + +/* +** We need to define "NULL_INTPTR_T" and "BAD_INTPTR_T". +*/ + +#ifndef NULL_INTPTR_T +# define NULL_INTPTR_T ((intptr_t)(0)) +#endif + +#ifndef BAD_INTPTR_T +# define BAD_INTPTR_T ((intptr_t)(-1)) +#endif + +/* +** We need to provide the necessary structures and related types. +*/ + +#ifndef DIRENT_DEFINED +#define DIRENT_DEFINED +typedef struct DIRENT DIRENT; +typedef DIRENT *LPDIRENT; +struct DIRENT { + ino_t d_ino; /* Sequence number, do not use. */ + unsigned d_attributes; /* Win32 file attributes. */ + char d_name[NAME_MAX + 1]; /* Name within the directory. */ +}; +#endif + +#ifndef DIR_DEFINED +#define DIR_DEFINED +typedef struct DIR DIR; +typedef DIR *LPDIR; +struct DIR { + intptr_t d_handle; /* Value returned by "_findfirst". */ + DIRENT d_first; /* DIRENT constructed based on "_findfirst". */ + DIRENT d_next; /* DIRENT constructed based on "_findnext". */ +}; +#endif + +/* +** Provide a macro, for use by the implementation, to determine if a +** particular directory entry should be skipped over when searching for +** the next directory entry that should be returned by the readdir() or +** readdir_r() functions. +*/ + +#ifndef is_filtered +# define is_filtered(a) ((((a).attrib)&_A_HIDDEN) || (((a).attrib)&_A_SYSTEM)) +#endif + +/* +** Provide the function prototype for the POSIX compatiable getenv() +** function. This function is not thread-safe. +*/ + +extern const char *windirent_getenv(const char *name); + +/* +** Finally, we can provide the function prototypes for the opendir(), +** readdir(), readdir_r(), and closedir() POSIX functions. +*/ + +extern LPDIR opendir(const char *dirname); +extern LPDIRENT readdir(LPDIR dirp); +extern INT readdir_r(LPDIR dirp, LPDIRENT entry, LPDIRENT *result); +extern INT closedir(LPDIR dirp); + +#endif /* defined(WIN32) && defined(_MSC_VER) */ + +/************************* End test_windirent.h ********************/ +/************************* Begin test_windirent.c ******************/ +/* +** 2015 November 30 +** +** The author disclaims copyright to this source code. In place of +** a legal notice, here is a blessing: +** +** May you do good and not evil. +** May you find forgiveness for yourself and forgive others. +** May you share freely, never taking more than you give. +** +************************************************************************* +** This file contains code to implement most of the opendir() family of +** POSIX functions on Win32 using the MSVCRT. +*/ + +#if defined(_WIN32) && defined(_MSC_VER) +/* #include "test_windirent.h" */ + +/* +** Implementation of the POSIX getenv() function using the Win32 API. +** This function is not thread-safe. +*/ +const char *windirent_getenv( + const char *name +){ + static char value[32768]; /* Maximum length, per MSDN */ + DWORD dwSize = sizeof(value) / sizeof(char); /* Size in chars */ + DWORD dwRet; /* Value returned by GetEnvironmentVariableA() */ + + memset(value, 0, sizeof(value)); + dwRet = GetEnvironmentVariableA(name, value, dwSize); + if( dwRet==0 || dwRet>dwSize ){ + /* + ** The function call to GetEnvironmentVariableA() failed -OR- + ** the buffer is not large enough. Either way, return NULL. + */ + return 0; + }else{ + /* + ** The function call to GetEnvironmentVariableA() succeeded + ** -AND- the buffer contains the entire value. + */ + return value; + } +} + +/* +** Implementation of the POSIX opendir() function using the MSVCRT. +*/ +LPDIR opendir( + const char *dirname +){ + struct _finddata_t data; + LPDIR dirp = (LPDIR)sqlite3_malloc(sizeof(DIR)); + SIZE_T namesize = sizeof(data.name) / sizeof(data.name[0]); + + if( dirp==NULL ) return NULL; + memset(dirp, 0, sizeof(DIR)); + + /* TODO: Remove this if Unix-style root paths are not used. */ + if( sqlite3_stricmp(dirname, "/")==0 ){ + dirname = windirent_getenv("SystemDrive"); + } + + memset(&data, 0, sizeof(struct _finddata_t)); + _snprintf(data.name, namesize, "%s\\*", dirname); + dirp->d_handle = _findfirst(data.name, &data); + + if( dirp->d_handle==BAD_INTPTR_T ){ + closedir(dirp); + return NULL; + } + + /* TODO: Remove this block to allow hidden and/or system files. */ + if( is_filtered(data) ){ +next: + + memset(&data, 0, sizeof(struct _finddata_t)); + if( _findnext(dirp->d_handle, &data)==-1 ){ + closedir(dirp); + return NULL; + } + + /* TODO: Remove this block to allow hidden and/or system files. */ + if( is_filtered(data) ) goto next; + } + + dirp->d_first.d_attributes = data.attrib; + strncpy(dirp->d_first.d_name, data.name, NAME_MAX); + dirp->d_first.d_name[NAME_MAX] = '\0'; + + return dirp; +} + +/* +** Implementation of the POSIX readdir() function using the MSVCRT. +*/ +LPDIRENT readdir( + LPDIR dirp +){ + struct _finddata_t data; + + if( dirp==NULL ) return NULL; + + if( dirp->d_first.d_ino==0 ){ + dirp->d_first.d_ino++; + dirp->d_next.d_ino++; + + return &dirp->d_first; + } + +next: + + memset(&data, 0, sizeof(struct _finddata_t)); + if( _findnext(dirp->d_handle, &data)==-1 ) return NULL; + + /* TODO: Remove this block to allow hidden and/or system files. */ + if( is_filtered(data) ) goto next; + + dirp->d_next.d_ino++; + dirp->d_next.d_attributes = data.attrib; + strncpy(dirp->d_next.d_name, data.name, NAME_MAX); + dirp->d_next.d_name[NAME_MAX] = '\0'; + + return &dirp->d_next; +} + +/* +** Implementation of the POSIX readdir_r() function using the MSVCRT. +*/ +INT readdir_r( + LPDIR dirp, + LPDIRENT entry, + LPDIRENT *result +){ + struct _finddata_t data; + + if( dirp==NULL ) return EBADF; + + if( dirp->d_first.d_ino==0 ){ + dirp->d_first.d_ino++; + dirp->d_next.d_ino++; + + entry->d_ino = dirp->d_first.d_ino; + entry->d_attributes = dirp->d_first.d_attributes; + strncpy(entry->d_name, dirp->d_first.d_name, NAME_MAX); + entry->d_name[NAME_MAX] = '\0'; + + *result = entry; + return 0; + } + +next: + + memset(&data, 0, sizeof(struct _finddata_t)); + if( _findnext(dirp->d_handle, &data)==-1 ){ + *result = NULL; + return ENOENT; + } + + /* TODO: Remove this block to allow hidden and/or system files. */ + if( is_filtered(data) ) goto next; + + entry->d_ino = (ino_t)-1; /* not available */ + entry->d_attributes = data.attrib; + strncpy(entry->d_name, data.name, NAME_MAX); + entry->d_name[NAME_MAX] = '\0'; + + *result = entry; + return 0; +} + +/* +** Implementation of the POSIX closedir() function using the MSVCRT. +*/ +INT closedir( + LPDIR dirp +){ + INT result = 0; + + if( dirp==NULL ) return EINVAL; + + if( dirp->d_handle!=NULL_INTPTR_T && dirp->d_handle!=BAD_INTPTR_T ){ + result = _findclose(dirp->d_handle); + } + + sqlite3_free(dirp); + return result; +} + +#endif /* defined(WIN32) && defined(_MSC_VER) */ + +/************************* End test_windirent.c ********************/ +#define dirent DIRENT +#endif +/************************* Begin ../ext/misc/shathree.c ******************/ +/* +** 2017-03-08 +** +** The author disclaims copyright to this source code. In place of +** a legal notice, here is a blessing: +** +** May you do good and not evil. +** May you find forgiveness for yourself and forgive others. +** May you share freely, never taking more than you give. +** +****************************************************************************** +** +** This SQLite extension implements functions that compute SHA3 hashes. +** Two SQL functions are implemented: +** +** sha3(X,SIZE) +** sha3_query(Y,SIZE) +** +** The sha3(X) function computes the SHA3 hash of the input X, or NULL if +** X is NULL. +** +** The sha3_query(Y) function evalutes all queries in the SQL statements of Y +** and returns a hash of their results. +** +** The SIZE argument is optional. If omitted, the SHA3-256 hash algorithm +** is used. If SIZE is included it must be one of the integers 224, 256, +** 384, or 512, to determine SHA3 hash variant that is computed. +*/ +/* #include "sqlite3ext.h" */ +SQLITE_EXTENSION_INIT1 +#include +#include +#include + +#ifndef SQLITE_AMALGAMATION +/* typedef sqlite3_uint64 u64; */ +#endif /* SQLITE_AMALGAMATION */ + +/****************************************************************************** +** The Hash Engine +*/ +/* +** 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 + + +/* +** State structure for a SHA3 hash in progress +*/ +typedef struct SHA3Context SHA3Context; +struct SHA3Context { + union { + u64 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]. */ +}; + +/* +** A single step of the Keccak mixing function for a 1600-bit state +*/ +static void KeccakF1600Step(SHA3Context *p){ + int i; + u64 b0, b1, b2, b3, b4; + u64 c0, c1, c2, c3, c4; + u64 d0, d1, d2, d3, d4; + static const u64 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<>(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 ); + } +} + +/* +** Initialize a new hash. iSize determines the size of the hash +** in bits and should be one of 224, 256, 384, or 512. Or iSize +** can be zero to use the default hash size of 256 bits. +*/ +static void SHA3Init(SHA3Context *p, int iSize){ + memset(p, 0, sizeof(*p)); + 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 unsigned int one = 1; + if( 1==*(unsigned char*)&one ){ + /* Little endian. No byte swapping. */ + p->ixMask = 0; + }else{ + /* Big endian. Byte swap. */ + p->ixMask = 7; + } + } +#endif +} + +/* +** Make consecutive calls to the SHA3Update function to add new content +** to the hash +*/ +static void SHA3Update( + SHA3Context *p, + const unsigned char *aData, + unsigned int nData +){ + unsigned int i = 0; +#if SHA3_BYTEORDER==1234 + if( (p->nLoaded % 8)==0 && ((aData - (const unsigned char*)0)&7)==0 ){ + for(; i+7u.s[p->nLoaded/8] ^= *(u64*)&aData[i]; + p->nLoaded += 8; + if( p->nLoaded>=p->nRate ){ + KeccakF1600Step(p); + p->nLoaded = 0; + } + } + } +#endif + for(; iu.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; + } + } +} + +/* +** After all content has been added, invoke SHA3Final() to compute +** the final hash. The function returns a pointer to the binary +** hash value. +*/ +static unsigned char *SHA3Final(SHA3Context *p){ + unsigned int i; + if( p->nLoaded==p->nRate-1 ){ + const unsigned char c1 = 0x86; + SHA3Update(p, &c1, 1); + }else{ + const unsigned char c2 = 0x06; + const unsigned char c3 = 0x80; + SHA3Update(p, &c2, 1); + p->nLoaded = p->nRate - 1; + SHA3Update(p, &c3, 1); + } + for(i=0; inRate; i++){ + p->u.x[i+p->nRate] = p->u.x[i^p->ixMask]; + } + return &p->u.x[p->nRate]; +} +/* End of the hashing logic +*****************************************************************************/ + +/* +** Implementation of the sha3(X,SIZE) function. +** +** Return a BLOB which is the SIZE-bit SHA3 hash of X. The default +** size is 256. If X is a BLOB, it is hashed as is. +** For all other non-NULL types of input, X is converted into a UTF-8 string +** and the string is hashed without the trailing 0x00 terminator. The hash +** of a NULL value is NULL. +*/ +static void sha3Func( + sqlite3_context *context, + int argc, + sqlite3_value **argv +){ + SHA3Context cx; + int eType = sqlite3_value_type(argv[0]); + int nByte = sqlite3_value_bytes(argv[0]); + int iSize; + if( argc==1 ){ + iSize = 256; + }else{ + iSize = sqlite3_value_int(argv[1]); + if( iSize!=224 && iSize!=256 && iSize!=384 && iSize!=512 ){ + sqlite3_result_error(context, "SHA3 size should be one of: 224 256 " + "384 512", -1); + return; + } + } + if( eType==SQLITE_NULL ) return; + SHA3Init(&cx, iSize); + if( eType==SQLITE_BLOB ){ + SHA3Update(&cx, sqlite3_value_blob(argv[0]), nByte); + }else{ + SHA3Update(&cx, sqlite3_value_text(argv[0]), nByte); + } + sqlite3_result_blob(context, SHA3Final(&cx), iSize/8, SQLITE_TRANSIENT); +} + +/* Compute a string using sqlite3_vsnprintf() with a maximum length +** of 50 bytes and add it to the hash. +*/ +static void hash_step_vformat( + SHA3Context *p, /* Add content to this context */ + const char *zFormat, + ... +){ + va_list ap; + int n; + char zBuf[50]; + va_start(ap, zFormat); + sqlite3_vsnprintf(sizeof(zBuf),zBuf,zFormat,ap); + va_end(ap); + n = (int)strlen(zBuf); + SHA3Update(p, (unsigned char*)zBuf, n); +} + +/* +** Implementation of the sha3_query(SQL,SIZE) function. +** +** This function compiles and runs the SQL statement(s) given in the +** argument. The results are hashed using a SIZE-bit SHA3. The default +** size is 256. +** +** The format of the byte stream that is hashed is summarized as follows: +** +** S: +** R +** N +** I +** F +** B: +** T: +** +** is the original SQL text for each statement run and is +** the size of that text. The SQL text is UTF-8. A single R character +** occurs before the start of each row. N means a NULL value. +** I mean an 8-byte little-endian integer . F is a floating point +** number with an 8-byte little-endian IEEE floating point value . +** B means blobs of bytes. T means text rendered as +** bytes of UTF-8. The and values are expressed as an ASCII +** text integers. +** +** For each SQL statement in the X input, there is one S segment. Each +** S segment is followed by zero or more R segments, one for each row in the +** result set. After each R, there are one or more N, I, F, B, or T segments, +** one for each column in the result set. Segments are concatentated directly +** with no delimiters of any kind. +*/ +static void sha3QueryFunc( + sqlite3_context *context, + int argc, + sqlite3_value **argv +){ + sqlite3 *db = sqlite3_context_db_handle(context); + const char *zSql = (const char*)sqlite3_value_text(argv[0]); + sqlite3_stmt *pStmt = 0; + int nCol; /* Number of columns in the result set */ + int i; /* Loop counter */ + int rc; + int n; + const char *z; + SHA3Context cx; + int iSize; + + if( argc==1 ){ + iSize = 256; + }else{ + iSize = sqlite3_value_int(argv[1]); + if( iSize!=224 && iSize!=256 && iSize!=384 && iSize!=512 ){ + sqlite3_result_error(context, "SHA3 size should be one of: 224 256 " + "384 512", -1); + return; + } + } + if( zSql==0 ) return; + SHA3Init(&cx, iSize); + while( zSql[0] ){ + rc = sqlite3_prepare_v2(db, zSql, -1, &pStmt, &zSql); + if( rc ){ + char *zMsg = sqlite3_mprintf("error SQL statement [%s]: %s", + zSql, sqlite3_errmsg(db)); + sqlite3_finalize(pStmt); + sqlite3_result_error(context, zMsg, -1); + sqlite3_free(zMsg); + return; + } + if( !sqlite3_stmt_readonly(pStmt) ){ + char *zMsg = sqlite3_mprintf("non-query: [%s]", sqlite3_sql(pStmt)); + sqlite3_finalize(pStmt); + sqlite3_result_error(context, zMsg, -1); + sqlite3_free(zMsg); + return; + } + nCol = sqlite3_column_count(pStmt); + z = sqlite3_sql(pStmt); + if( z ){ + n = (int)strlen(z); + hash_step_vformat(&cx,"S%d:",n); + SHA3Update(&cx,(unsigned char*)z,n); + } + + /* Compute a hash over the result of the query */ + while( SQLITE_ROW==sqlite3_step(pStmt) ){ + SHA3Update(&cx,(const unsigned char*)"R",1); + for(i=0; i=1; j--){ + x[j] = u & 0xff; + u >>= 8; + } + x[0] = 'I'; + SHA3Update(&cx, x, 9); + break; + } + case SQLITE_FLOAT: { + sqlite3_uint64 u; + int j; + unsigned char x[9]; + double r = sqlite3_column_double(pStmt,i); + memcpy(&u, &r, 8); + for(j=8; j>=1; j--){ + x[j] = u & 0xff; + u >>= 8; + } + x[0] = 'F'; + SHA3Update(&cx,x,9); + break; + } + case SQLITE_TEXT: { + int n2 = sqlite3_column_bytes(pStmt, i); + const unsigned char *z2 = sqlite3_column_text(pStmt, i); + hash_step_vformat(&cx,"T%d:",n2); + SHA3Update(&cx, z2, n2); + break; + } + case SQLITE_BLOB: { + int n2 = sqlite3_column_bytes(pStmt, i); + const unsigned char *z2 = sqlite3_column_blob(pStmt, i); + hash_step_vformat(&cx,"B%d:",n2); + SHA3Update(&cx, z2, n2); + break; + } + } + } + } + sqlite3_finalize(pStmt); + } + sqlite3_result_blob(context, SHA3Final(&cx), iSize/8, SQLITE_TRANSIENT); +} + + +#ifdef _WIN32 + +#endif +int sqlite3_shathree_init( + sqlite3 *db, + char **pzErrMsg, + const sqlite3_api_routines *pApi +){ + int rc = SQLITE_OK; + SQLITE_EXTENSION_INIT2(pApi); + (void)pzErrMsg; /* Unused parameter */ + rc = sqlite3_create_function(db, "sha3", 1, + SQLITE_UTF8 | SQLITE_INNOCUOUS | SQLITE_DETERMINISTIC, + 0, sha3Func, 0, 0); + if( rc==SQLITE_OK ){ + rc = sqlite3_create_function(db, "sha3", 2, + SQLITE_UTF8 | SQLITE_INNOCUOUS | SQLITE_DETERMINISTIC, + 0, sha3Func, 0, 0); + } + if( rc==SQLITE_OK ){ + rc = sqlite3_create_function(db, "sha3_query", 1, + SQLITE_UTF8 | SQLITE_DIRECTONLY, + 0, sha3QueryFunc, 0, 0); + } + if( rc==SQLITE_OK ){ + rc = sqlite3_create_function(db, "sha3_query", 2, + SQLITE_UTF8 | SQLITE_DIRECTONLY, + 0, sha3QueryFunc, 0, 0); + } + return rc; +} + +/************************* End ../ext/misc/shathree.c ********************/ +/************************* Begin ../ext/misc/fileio.c ******************/ +/* +** 2014-06-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. +** +****************************************************************************** +** +** This SQLite extension implements SQL functions readfile() and +** writefile(), and eponymous virtual type "fsdir". +** +** WRITEFILE(FILE, DATA [, MODE [, MTIME]]): +** +** If neither of the optional arguments is present, then this UDF +** function writes blob DATA to file FILE. If successful, the number +** of bytes written is returned. If an error occurs, NULL is returned. +** +** If the first option argument - MODE - is present, then it must +** be passed an integer value that corresponds to a POSIX mode +** value (file type + permissions, as returned in the stat.st_mode +** field by the stat() system call). Three types of files may +** be written/created: +** +** regular files: (mode & 0170000)==0100000 +** symbolic links: (mode & 0170000)==0120000 +** directories: (mode & 0170000)==0040000 +** +** For a directory, the DATA is ignored. For a symbolic link, it is +** interpreted as text and used as the target of the link. For a +** regular file, it is interpreted as a blob and written into the +** named file. Regardless of the type of file, its permissions are +** set to (mode & 0777) before returning. +** +** If the optional MTIME argument is present, then it is interpreted +** as an integer - the number of seconds since the unix epoch. The +** modification-time of the target file is set to this value before +** returning. +** +** If three or more arguments are passed to this function and an +** error is encountered, an exception is raised. +** +** READFILE(FILE): +** +** Read and return the contents of file FILE (type blob) from disk. +** +** FSDIR: +** +** Used as follows: +** +** SELECT * FROM fsdir($path [, $dir]); +** +** Parameter $path is an absolute or relative pathname. If the file that it +** refers to does not exist, it is an error. If the path refers to a regular +** file or symbolic link, it returns a single row. Or, if the path refers +** to a directory, it returns one row for the directory, and one row for each +** file within the hierarchy rooted at $path. +** +** Each row has the following columns: +** +** name: Path to file or directory (text value). +** mode: Value of stat.st_mode for directory entry (an integer). +** mtime: Value of stat.st_mtime for directory entry (an integer). +** data: For a regular file, a blob containing the file data. For a +** symlink, a text value containing the text of the link. For a +** directory, NULL. +** +** If a non-NULL value is specified for the optional $dir parameter and +** $path is a relative path, then $path is interpreted relative to $dir. +** And the paths returned in the "name" column of the table are also +** relative to directory $dir. +*/ +/* #include "sqlite3ext.h" */ +SQLITE_EXTENSION_INIT1 +#include +#include +#include + +#include +#include +#include +#if !defined(_WIN32) && !defined(WIN32) +# include +# include +# include +# include +#else +# include "windows.h" +# include +# include +/* # include "test_windirent.h" */ +# define dirent DIRENT +# ifndef chmod +# define chmod _chmod +# endif +# ifndef stat +# define stat _stat +# endif +# define mkdir(path,mode) _mkdir(path) +# define lstat(path,buf) stat(path,buf) +#endif +#include +#include + + +/* +** Structure of the fsdir() table-valued function +*/ + /* 0 1 2 3 4 5 */ +#define FSDIR_SCHEMA "(name,mode,mtime,data,path HIDDEN,dir HIDDEN)" +#define FSDIR_COLUMN_NAME 0 /* Name of the file */ +#define FSDIR_COLUMN_MODE 1 /* Access mode */ +#define FSDIR_COLUMN_MTIME 2 /* Last modification time */ +#define FSDIR_COLUMN_DATA 3 /* File content */ +#define FSDIR_COLUMN_PATH 4 /* Path to top of search */ +#define FSDIR_COLUMN_DIR 5 /* Path is relative to this directory */ + + +/* +** Set the result stored by context ctx to a blob containing the +** contents of file zName. Or, leave the result unchanged (NULL) +** if the file does not exist or is unreadable. +** +** If the file exceeds the SQLite blob size limit, through an +** SQLITE_TOOBIG error. +** +** Throw an SQLITE_IOERR if there are difficulties pulling the file +** off of disk. +*/ +static void readFileContents(sqlite3_context *ctx, const char *zName){ + FILE *in; + sqlite3_int64 nIn; + void *pBuf; + sqlite3 *db; + int mxBlob; + + in = fopen(zName, "rb"); + if( in==0 ){ + /* File does not exist or is unreadable. Leave the result set to NULL. */ + return; + } + fseek(in, 0, SEEK_END); + nIn = ftell(in); + rewind(in); + db = sqlite3_context_db_handle(ctx); + mxBlob = sqlite3_limit(db, SQLITE_LIMIT_LENGTH, -1); + if( nIn>mxBlob ){ + sqlite3_result_error_code(ctx, SQLITE_TOOBIG); + fclose(in); + return; + } + pBuf = sqlite3_malloc64( nIn ? nIn : 1 ); + if( pBuf==0 ){ + sqlite3_result_error_nomem(ctx); + fclose(in); + return; + } + if( nIn==(sqlite3_int64)fread(pBuf, 1, (size_t)nIn, in) ){ + sqlite3_result_blob64(ctx, pBuf, nIn, sqlite3_free); + }else{ + sqlite3_result_error_code(ctx, SQLITE_IOERR); + sqlite3_free(pBuf); + } + fclose(in); +} + +/* +** Implementation of the "readfile(X)" SQL function. The entire content +** of the file named X is read and returned as a BLOB. NULL is returned +** if the file does not exist or is unreadable. +*/ +static void readfileFunc( + sqlite3_context *context, + int argc, + sqlite3_value **argv +){ + const char *zName; + (void)(argc); /* Unused parameter */ + zName = (const char*)sqlite3_value_text(argv[0]); + if( zName==0 ) return; + readFileContents(context, zName); +} + +/* +** Set the error message contained in context ctx to the results of +** vprintf(zFmt, ...). +*/ +static void ctxErrorMsg(sqlite3_context *ctx, const char *zFmt, ...){ + char *zMsg = 0; + va_list ap; + va_start(ap, zFmt); + zMsg = sqlite3_vmprintf(zFmt, ap); + sqlite3_result_error(ctx, zMsg, -1); + sqlite3_free(zMsg); + va_end(ap); +} + +#if defined(_WIN32) +/* +** This function is designed to convert a Win32 FILETIME structure into the +** number of seconds since the Unix Epoch (1970-01-01 00:00:00 UTC). +*/ +static sqlite3_uint64 fileTimeToUnixTime( + LPFILETIME pFileTime +){ + SYSTEMTIME epochSystemTime; + ULARGE_INTEGER epochIntervals; + FILETIME epochFileTime; + ULARGE_INTEGER fileIntervals; + + memset(&epochSystemTime, 0, sizeof(SYSTEMTIME)); + epochSystemTime.wYear = 1970; + epochSystemTime.wMonth = 1; + epochSystemTime.wDay = 1; + SystemTimeToFileTime(&epochSystemTime, &epochFileTime); + epochIntervals.LowPart = epochFileTime.dwLowDateTime; + epochIntervals.HighPart = epochFileTime.dwHighDateTime; + + fileIntervals.LowPart = pFileTime->dwLowDateTime; + fileIntervals.HighPart = pFileTime->dwHighDateTime; + + return (fileIntervals.QuadPart - epochIntervals.QuadPart) / 10000000; +} + +/* +** This function attempts to normalize the time values found in the stat() +** buffer to UTC. This is necessary on Win32, where the runtime library +** appears to return these values as local times. +*/ +static void statTimesToUtc( + const char *zPath, + struct stat *pStatBuf +){ + HANDLE hFindFile; + WIN32_FIND_DATAW fd; + LPWSTR zUnicodeName; + extern LPWSTR sqlite3_win32_utf8_to_unicode(const char*); + zUnicodeName = sqlite3_win32_utf8_to_unicode(zPath); + if( zUnicodeName ){ + memset(&fd, 0, sizeof(WIN32_FIND_DATAW)); + hFindFile = FindFirstFileW(zUnicodeName, &fd); + if( hFindFile!=NULL ){ + pStatBuf->st_ctime = (time_t)fileTimeToUnixTime(&fd.ftCreationTime); + pStatBuf->st_atime = (time_t)fileTimeToUnixTime(&fd.ftLastAccessTime); + pStatBuf->st_mtime = (time_t)fileTimeToUnixTime(&fd.ftLastWriteTime); + FindClose(hFindFile); + } + sqlite3_free(zUnicodeName); + } +} +#endif + +/* +** This function is used in place of stat(). On Windows, special handling +** is required in order for the included time to be returned as UTC. On all +** other systems, this function simply calls stat(). +*/ +static int fileStat( + const char *zPath, + struct stat *pStatBuf +){ +#if defined(_WIN32) + int rc = stat(zPath, pStatBuf); + if( rc==0 ) statTimesToUtc(zPath, pStatBuf); + return rc; +#else + return stat(zPath, pStatBuf); +#endif +} + +/* +** This function is used in place of lstat(). On Windows, special handling +** is required in order for the included time to be returned as UTC. On all +** other systems, this function simply calls lstat(). +*/ +static int fileLinkStat( + const char *zPath, + struct stat *pStatBuf +){ +#if defined(_WIN32) + int rc = lstat(zPath, pStatBuf); + if( rc==0 ) statTimesToUtc(zPath, pStatBuf); + return rc; +#else + return lstat(zPath, pStatBuf); +#endif +} + +/* +** Argument zFile is the name of a file that will be created and/or written +** by SQL function writefile(). This function ensures that the directory +** zFile will be written to exists, creating it if required. The permissions +** for any path components created by this function are set in accordance +** with the current umask. +** +** If an OOM condition is encountered, SQLITE_NOMEM is returned. Otherwise, +** SQLITE_OK is returned if the directory is successfully created, or +** SQLITE_ERROR otherwise. +*/ +static int makeDirectory( + const char *zFile +){ + char *zCopy = sqlite3_mprintf("%s", zFile); + int rc = SQLITE_OK; + + if( zCopy==0 ){ + rc = SQLITE_NOMEM; + }else{ + int nCopy = (int)strlen(zCopy); + int i = 1; + + while( rc==SQLITE_OK ){ + struct stat sStat; + int rc2; + + for(; zCopy[i]!='/' && i=0 ){ +#if defined(_WIN32) +#if !SQLITE_OS_WINRT + /* Windows */ + FILETIME lastAccess; + FILETIME lastWrite; + SYSTEMTIME currentTime; + LONGLONG intervals; + HANDLE hFile; + LPWSTR zUnicodeName; + extern LPWSTR sqlite3_win32_utf8_to_unicode(const char*); + + GetSystemTime(¤tTime); + SystemTimeToFileTime(¤tTime, &lastAccess); + intervals = Int32x32To64(mtime, 10000000) + 116444736000000000; + lastWrite.dwLowDateTime = (DWORD)intervals; + lastWrite.dwHighDateTime = intervals >> 32; + zUnicodeName = sqlite3_win32_utf8_to_unicode(zFile); + if( zUnicodeName==0 ){ + return 1; + } + hFile = CreateFileW( + zUnicodeName, FILE_WRITE_ATTRIBUTES, 0, NULL, OPEN_EXISTING, + FILE_FLAG_BACKUP_SEMANTICS, NULL + ); + sqlite3_free(zUnicodeName); + if( hFile!=INVALID_HANDLE_VALUE ){ + BOOL bResult = SetFileTime(hFile, NULL, &lastAccess, &lastWrite); + CloseHandle(hFile); + return !bResult; + }else{ + return 1; + } +#endif +#elif defined(AT_FDCWD) && 0 /* utimensat() is not universally available */ + /* Recent unix */ + struct timespec times[2]; + times[0].tv_nsec = times[1].tv_nsec = 0; + times[0].tv_sec = time(0); + times[1].tv_sec = mtime; + if( utimensat(AT_FDCWD, zFile, times, AT_SYMLINK_NOFOLLOW) ){ + return 1; + } +#else + /* Legacy unix */ + struct timeval times[2]; + times[0].tv_usec = times[1].tv_usec = 0; + times[0].tv_sec = time(0); + times[1].tv_sec = mtime; + if( utimes(zFile, times) ){ + return 1; + } +#endif + } + + return 0; +} + +/* +** Implementation of the "writefile(W,X[,Y[,Z]]])" SQL function. +** Refer to header comments at the top of this file for details. +*/ +static void writefileFunc( + sqlite3_context *context, + int argc, + sqlite3_value **argv +){ + const char *zFile; + mode_t mode = 0; + int res; + sqlite3_int64 mtime = -1; + + if( argc<2 || argc>4 ){ + sqlite3_result_error(context, + "wrong number of arguments to function writefile()", -1 + ); + return; + } + + zFile = (const char*)sqlite3_value_text(argv[0]); + if( zFile==0 ) return; + if( argc>=3 ){ + mode = (mode_t)sqlite3_value_int(argv[2]); + } + if( argc==4 ){ + mtime = sqlite3_value_int64(argv[3]); + } + + res = writeFile(context, zFile, argv[1], mode, mtime); + if( res==1 && errno==ENOENT ){ + if( makeDirectory(zFile)==SQLITE_OK ){ + res = writeFile(context, zFile, argv[1], mode, mtime); + } + } + + if( argc>2 && res!=0 ){ + if( S_ISLNK(mode) ){ + ctxErrorMsg(context, "failed to create symlink: %s", zFile); + }else if( S_ISDIR(mode) ){ + ctxErrorMsg(context, "failed to create directory: %s", zFile); + }else{ + ctxErrorMsg(context, "failed to write file: %s", zFile); + } + } +} + +/* +** SQL function: lsmode(MODE) +** +** Given a numberic st_mode from stat(), convert it into a human-readable +** text string in the style of "ls -l". +*/ +static void lsModeFunc( + sqlite3_context *context, + int argc, + sqlite3_value **argv +){ + int i; + int iMode = sqlite3_value_int(argv[0]); + char z[16]; + (void)argc; + if( S_ISLNK(iMode) ){ + z[0] = 'l'; + }else if( S_ISREG(iMode) ){ + z[0] = '-'; + }else if( S_ISDIR(iMode) ){ + z[0] = 'd'; + }else{ + z[0] = '?'; + } + for(i=0; i<3; i++){ + int m = (iMode >> ((2-i)*3)); + char *a = &z[1 + i*3]; + a[0] = (m & 0x4) ? 'r' : '-'; + a[1] = (m & 0x2) ? 'w' : '-'; + a[2] = (m & 0x1) ? 'x' : '-'; + } + z[10] = '\0'; + sqlite3_result_text(context, z, -1, SQLITE_TRANSIENT); +} + +#ifndef SQLITE_OMIT_VIRTUALTABLE + +/* +** Cursor type for recursively iterating through a directory structure. +*/ +typedef struct fsdir_cursor fsdir_cursor; +typedef struct FsdirLevel FsdirLevel; + +struct FsdirLevel { + DIR *pDir; /* From opendir() */ + char *zDir; /* Name of directory (nul-terminated) */ +}; + +struct fsdir_cursor { + sqlite3_vtab_cursor base; /* Base class - must be first */ + + int nLvl; /* Number of entries in aLvl[] array */ + int iLvl; /* Index of current entry */ + FsdirLevel *aLvl; /* Hierarchy of directories being traversed */ + + const char *zBase; + int nBase; + + struct stat sStat; /* Current lstat() results */ + char *zPath; /* Path to current entry */ + sqlite3_int64 iRowid; /* Current rowid */ +}; + +typedef struct fsdir_tab fsdir_tab; +struct fsdir_tab { + sqlite3_vtab base; /* Base class - must be first */ +}; + +/* +** Construct a new fsdir virtual table object. +*/ +static int fsdirConnect( + sqlite3 *db, + void *pAux, + int argc, const char *const*argv, + sqlite3_vtab **ppVtab, + char **pzErr +){ + fsdir_tab *pNew = 0; + int rc; + (void)pAux; + (void)argc; + (void)argv; + (void)pzErr; + rc = sqlite3_declare_vtab(db, "CREATE TABLE x" FSDIR_SCHEMA); + if( rc==SQLITE_OK ){ + pNew = (fsdir_tab*)sqlite3_malloc( sizeof(*pNew) ); + if( pNew==0 ) return SQLITE_NOMEM; + memset(pNew, 0, sizeof(*pNew)); + sqlite3_vtab_config(db, SQLITE_VTAB_DIRECTONLY); + } + *ppVtab = (sqlite3_vtab*)pNew; + return rc; +} + +/* +** This method is the destructor for fsdir vtab objects. +*/ +static int fsdirDisconnect(sqlite3_vtab *pVtab){ + sqlite3_free(pVtab); + return SQLITE_OK; +} + +/* +** Constructor for a new fsdir_cursor object. +*/ +static int fsdirOpen(sqlite3_vtab *p, sqlite3_vtab_cursor **ppCursor){ + fsdir_cursor *pCur; + (void)p; + pCur = sqlite3_malloc( sizeof(*pCur) ); + if( pCur==0 ) return SQLITE_NOMEM; + memset(pCur, 0, sizeof(*pCur)); + pCur->iLvl = -1; + *ppCursor = &pCur->base; + return SQLITE_OK; +} + +/* +** Reset a cursor back to the state it was in when first returned +** by fsdirOpen(). +*/ +static void fsdirResetCursor(fsdir_cursor *pCur){ + int i; + for(i=0; i<=pCur->iLvl; i++){ + FsdirLevel *pLvl = &pCur->aLvl[i]; + if( pLvl->pDir ) closedir(pLvl->pDir); + sqlite3_free(pLvl->zDir); + } + sqlite3_free(pCur->zPath); + sqlite3_free(pCur->aLvl); + pCur->aLvl = 0; + pCur->zPath = 0; + pCur->zBase = 0; + pCur->nBase = 0; + pCur->nLvl = 0; + pCur->iLvl = -1; + pCur->iRowid = 1; +} + +/* +** Destructor for an fsdir_cursor. +*/ +static int fsdirClose(sqlite3_vtab_cursor *cur){ + fsdir_cursor *pCur = (fsdir_cursor*)cur; + + fsdirResetCursor(pCur); + sqlite3_free(pCur); + return SQLITE_OK; +} + +/* +** Set the error message for the virtual table associated with cursor +** pCur to the results of vprintf(zFmt, ...). +*/ +static void fsdirSetErrmsg(fsdir_cursor *pCur, const char *zFmt, ...){ + va_list ap; + va_start(ap, zFmt); + pCur->base.pVtab->zErrMsg = sqlite3_vmprintf(zFmt, ap); + va_end(ap); +} + + +/* +** Advance an fsdir_cursor to its next row of output. +*/ +static int fsdirNext(sqlite3_vtab_cursor *cur){ + fsdir_cursor *pCur = (fsdir_cursor*)cur; + mode_t m = pCur->sStat.st_mode; + + pCur->iRowid++; + if( S_ISDIR(m) ){ + /* Descend into this directory */ + int iNew = pCur->iLvl + 1; + FsdirLevel *pLvl; + if( iNew>=pCur->nLvl ){ + int nNew = iNew+1; + sqlite3_int64 nByte = nNew*sizeof(FsdirLevel); + FsdirLevel *aNew = (FsdirLevel*)sqlite3_realloc64(pCur->aLvl, nByte); + if( aNew==0 ) return SQLITE_NOMEM; + memset(&aNew[pCur->nLvl], 0, sizeof(FsdirLevel)*(nNew-pCur->nLvl)); + pCur->aLvl = aNew; + pCur->nLvl = nNew; + } + pCur->iLvl = iNew; + pLvl = &pCur->aLvl[iNew]; + + pLvl->zDir = pCur->zPath; + pCur->zPath = 0; + pLvl->pDir = opendir(pLvl->zDir); + if( pLvl->pDir==0 ){ + fsdirSetErrmsg(pCur, "cannot read directory: %s", pCur->zPath); + return SQLITE_ERROR; + } + } + + while( pCur->iLvl>=0 ){ + FsdirLevel *pLvl = &pCur->aLvl[pCur->iLvl]; + struct dirent *pEntry = readdir(pLvl->pDir); + if( pEntry ){ + if( pEntry->d_name[0]=='.' ){ + if( pEntry->d_name[1]=='.' && pEntry->d_name[2]=='\0' ) continue; + if( pEntry->d_name[1]=='\0' ) continue; + } + sqlite3_free(pCur->zPath); + pCur->zPath = sqlite3_mprintf("%s/%s", pLvl->zDir, pEntry->d_name); + if( pCur->zPath==0 ) return SQLITE_NOMEM; + if( fileLinkStat(pCur->zPath, &pCur->sStat) ){ + fsdirSetErrmsg(pCur, "cannot stat file: %s", pCur->zPath); + return SQLITE_ERROR; + } + return SQLITE_OK; + } + closedir(pLvl->pDir); + sqlite3_free(pLvl->zDir); + pLvl->pDir = 0; + pLvl->zDir = 0; + pCur->iLvl--; + } + + /* EOF */ + sqlite3_free(pCur->zPath); + pCur->zPath = 0; + return SQLITE_OK; +} + +/* +** Return values of columns for the row at which the series_cursor +** is currently pointing. +*/ +static int fsdirColumn( + sqlite3_vtab_cursor *cur, /* The cursor */ + sqlite3_context *ctx, /* First argument to sqlite3_result_...() */ + int i /* Which column to return */ +){ + fsdir_cursor *pCur = (fsdir_cursor*)cur; + switch( i ){ + case FSDIR_COLUMN_NAME: { + sqlite3_result_text(ctx, &pCur->zPath[pCur->nBase], -1, SQLITE_TRANSIENT); + break; + } + + case FSDIR_COLUMN_MODE: + sqlite3_result_int64(ctx, pCur->sStat.st_mode); + break; + + case FSDIR_COLUMN_MTIME: + sqlite3_result_int64(ctx, pCur->sStat.st_mtime); + break; + + case FSDIR_COLUMN_DATA: { + mode_t m = pCur->sStat.st_mode; + if( S_ISDIR(m) ){ + sqlite3_result_null(ctx); +#if !defined(_WIN32) && !defined(WIN32) + }else if( S_ISLNK(m) ){ + char aStatic[64]; + char *aBuf = aStatic; + sqlite3_int64 nBuf = 64; + int n; + + while( 1 ){ + n = readlink(pCur->zPath, aBuf, nBuf); + if( nzPath); + } + } + case FSDIR_COLUMN_PATH: + default: { + /* The FSDIR_COLUMN_PATH and FSDIR_COLUMN_DIR are input parameters. + ** always return their values as NULL */ + break; + } + } + return SQLITE_OK; +} + +/* +** Return the rowid for the current row. In this implementation, the +** first row returned is assigned rowid value 1, and each subsequent +** row a value 1 more than that of the previous. +*/ +static int fsdirRowid(sqlite3_vtab_cursor *cur, sqlite_int64 *pRowid){ + fsdir_cursor *pCur = (fsdir_cursor*)cur; + *pRowid = pCur->iRowid; + return SQLITE_OK; +} + +/* +** Return TRUE if the cursor has been moved off of the last +** row of output. +*/ +static int fsdirEof(sqlite3_vtab_cursor *cur){ + fsdir_cursor *pCur = (fsdir_cursor*)cur; + return (pCur->zPath==0); +} + +/* +** xFilter callback. +** +** idxNum==1 PATH parameter only +** idxNum==2 Both PATH and DIR supplied +*/ +static int fsdirFilter( + sqlite3_vtab_cursor *cur, + int idxNum, const char *idxStr, + int argc, sqlite3_value **argv +){ + const char *zDir = 0; + fsdir_cursor *pCur = (fsdir_cursor*)cur; + (void)idxStr; + fsdirResetCursor(pCur); + + if( idxNum==0 ){ + fsdirSetErrmsg(pCur, "table function fsdir requires an argument"); + return SQLITE_ERROR; + } + + assert( argc==idxNum && (argc==1 || argc==2) ); + zDir = (const char*)sqlite3_value_text(argv[0]); + if( zDir==0 ){ + fsdirSetErrmsg(pCur, "table function fsdir requires a non-NULL argument"); + return SQLITE_ERROR; + } + if( argc==2 ){ + pCur->zBase = (const char*)sqlite3_value_text(argv[1]); + } + if( pCur->zBase ){ + pCur->nBase = (int)strlen(pCur->zBase)+1; + pCur->zPath = sqlite3_mprintf("%s/%s", pCur->zBase, zDir); + }else{ + pCur->zPath = sqlite3_mprintf("%s", zDir); + } + + if( pCur->zPath==0 ){ + return SQLITE_NOMEM; + } + if( fileLinkStat(pCur->zPath, &pCur->sStat) ){ + fsdirSetErrmsg(pCur, "cannot stat file: %s", pCur->zPath); + return SQLITE_ERROR; + } + + return SQLITE_OK; +} + +/* +** SQLite will invoke this method one or more times while planning a query +** that uses the generate_series virtual table. This routine needs to create +** a query plan for each invocation and compute an estimated cost for that +** plan. +** +** In this implementation idxNum is used to represent the +** query plan. idxStr is unused. +** +** The query plan is represented by values of idxNum: +** +** (1) The path value is supplied by argv[0] +** (2) Path is in argv[0] and dir is in argv[1] +*/ +static int fsdirBestIndex( + sqlite3_vtab *tab, + sqlite3_index_info *pIdxInfo +){ + int i; /* Loop over constraints */ + int idxPath = -1; /* Index in pIdxInfo->aConstraint of PATH= */ + int idxDir = -1; /* Index in pIdxInfo->aConstraint of DIR= */ + int seenPath = 0; /* True if an unusable PATH= constraint is seen */ + int seenDir = 0; /* True if an unusable DIR= constraint is seen */ + const struct sqlite3_index_constraint *pConstraint; + + (void)tab; + pConstraint = pIdxInfo->aConstraint; + for(i=0; inConstraint; i++, pConstraint++){ + if( pConstraint->op!=SQLITE_INDEX_CONSTRAINT_EQ ) continue; + switch( pConstraint->iColumn ){ + case FSDIR_COLUMN_PATH: { + if( pConstraint->usable ){ + idxPath = i; + seenPath = 0; + }else if( idxPath<0 ){ + seenPath = 1; + } + break; + } + case FSDIR_COLUMN_DIR: { + if( pConstraint->usable ){ + idxDir = i; + seenDir = 0; + }else if( idxDir<0 ){ + seenDir = 1; + } + break; + } + } + } + if( seenPath || seenDir ){ + /* If input parameters are unusable, disallow this plan */ + return SQLITE_CONSTRAINT; + } + + if( idxPath<0 ){ + pIdxInfo->idxNum = 0; + /* The pIdxInfo->estimatedCost should have been initialized to a huge + ** number. Leave it unchanged. */ + pIdxInfo->estimatedRows = 0x7fffffff; + }else{ + pIdxInfo->aConstraintUsage[idxPath].omit = 1; + pIdxInfo->aConstraintUsage[idxPath].argvIndex = 1; + if( idxDir>=0 ){ + pIdxInfo->aConstraintUsage[idxDir].omit = 1; + pIdxInfo->aConstraintUsage[idxDir].argvIndex = 2; + pIdxInfo->idxNum = 2; + pIdxInfo->estimatedCost = 10.0; + }else{ + pIdxInfo->idxNum = 1; + pIdxInfo->estimatedCost = 100.0; + } + } + + return SQLITE_OK; +} + +/* +** Register the "fsdir" virtual table. +*/ +static int fsdirRegister(sqlite3 *db){ + static sqlite3_module fsdirModule = { + 0, /* iVersion */ + 0, /* xCreate */ + fsdirConnect, /* xConnect */ + fsdirBestIndex, /* xBestIndex */ + fsdirDisconnect, /* xDisconnect */ + 0, /* xDestroy */ + fsdirOpen, /* xOpen - open a cursor */ + fsdirClose, /* xClose - close a cursor */ + fsdirFilter, /* xFilter - configure scan constraints */ + fsdirNext, /* xNext - advance a cursor */ + fsdirEof, /* xEof - check for end of scan */ + fsdirColumn, /* xColumn - read data */ + fsdirRowid, /* xRowid - read data */ + 0, /* xUpdate */ + 0, /* xBegin */ + 0, /* xSync */ + 0, /* xCommit */ + 0, /* xRollback */ + 0, /* xFindMethod */ + 0, /* xRename */ + 0, /* xSavepoint */ + 0, /* xRelease */ + 0, /* xRollbackTo */ + 0, /* xShadowName */ + }; + + int rc = sqlite3_create_module(db, "fsdir", &fsdirModule, 0); + return rc; +} +#else /* SQLITE_OMIT_VIRTUALTABLE */ +# define fsdirRegister(x) SQLITE_OK +#endif + +#ifdef _WIN32 + +#endif +int sqlite3_fileio_init( + sqlite3 *db, + char **pzErrMsg, + const sqlite3_api_routines *pApi +){ + int rc = SQLITE_OK; + SQLITE_EXTENSION_INIT2(pApi); + (void)pzErrMsg; /* Unused parameter */ + rc = sqlite3_create_function(db, "readfile", 1, + SQLITE_UTF8|SQLITE_DIRECTONLY, 0, + readfileFunc, 0, 0); + if( rc==SQLITE_OK ){ + rc = sqlite3_create_function(db, "writefile", -1, + SQLITE_UTF8|SQLITE_DIRECTONLY, 0, + writefileFunc, 0, 0); + } + if( rc==SQLITE_OK ){ + rc = sqlite3_create_function(db, "lsmode", 1, SQLITE_UTF8, 0, + lsModeFunc, 0, 0); + } + if( rc==SQLITE_OK ){ + rc = fsdirRegister(db); + } + return rc; +} + +/************************* End ../ext/misc/fileio.c ********************/ +/************************* Begin ../ext/misc/completion.c ******************/ +/* +** 2017-07-10 +** +** The author disclaims copyright to this source code. In place of +** a legal notice, here is a blessing: +** +** May you do good and not evil. +** May you find forgiveness for yourself and forgive others. +** May you share freely, never taking more than you give. +** +************************************************************************* +** +** This file implements an eponymous virtual table that returns suggested +** completions for a partial SQL input. +** +** Suggested usage: +** +** SELECT DISTINCT candidate COLLATE nocase +** FROM completion($prefix,$wholeline) +** ORDER BY 1; +** +** The two query parameters are optional. $prefix is the text of the +** current word being typed and that is to be completed. $wholeline is +** the complete input line, used for context. +** +** The raw completion() table might return the same candidate multiple +** times, for example if the same column name is used to two or more +** tables. And the candidates are returned in an arbitrary order. Hence, +** the DISTINCT and ORDER BY are recommended. +** +** This virtual table operates at the speed of human typing, and so there +** is no attempt to make it fast. Even a slow implementation will be much +** faster than any human can type. +** +*/ +/* #include "sqlite3ext.h" */ +SQLITE_EXTENSION_INIT1 +#include +#include +#include + +#ifndef SQLITE_OMIT_VIRTUALTABLE + +/* completion_vtab is a subclass of sqlite3_vtab which will +** serve as the underlying representation of a completion virtual table +*/ +typedef struct completion_vtab completion_vtab; +struct completion_vtab { + sqlite3_vtab base; /* Base class - must be first */ + sqlite3 *db; /* Database connection for this completion vtab */ +}; + +/* completion_cursor is a subclass of sqlite3_vtab_cursor which will +** serve as the underlying representation of a cursor that scans +** over rows of the result +*/ +typedef struct completion_cursor completion_cursor; +struct completion_cursor { + sqlite3_vtab_cursor base; /* Base class - must be first */ + sqlite3 *db; /* Database connection for this cursor */ + int nPrefix, nLine; /* Number of bytes in zPrefix and zLine */ + char *zPrefix; /* The prefix for the word we want to complete */ + char *zLine; /* The whole that we want to complete */ + const char *zCurrentRow; /* Current output row */ + int szRow; /* Length of the zCurrentRow string */ + sqlite3_stmt *pStmt; /* Current statement */ + sqlite3_int64 iRowid; /* The rowid */ + int ePhase; /* Current phase */ + int j; /* inter-phase counter */ +}; + +/* Values for ePhase: +*/ +#define COMPLETION_FIRST_PHASE 1 +#define COMPLETION_KEYWORDS 1 +#define COMPLETION_PRAGMAS 2 +#define COMPLETION_FUNCTIONS 3 +#define COMPLETION_COLLATIONS 4 +#define COMPLETION_INDEXES 5 +#define COMPLETION_TRIGGERS 6 +#define COMPLETION_DATABASES 7 +#define COMPLETION_TABLES 8 /* Also VIEWs and TRIGGERs */ +#define COMPLETION_COLUMNS 9 +#define COMPLETION_MODULES 10 +#define COMPLETION_EOF 11 + +/* +** The completionConnect() method is invoked to create a new +** completion_vtab that describes the completion virtual table. +** +** Think of this routine as the constructor for completion_vtab objects. +** +** All this routine needs to do is: +** +** (1) Allocate the completion_vtab object and initialize all fields. +** +** (2) Tell SQLite (via the sqlite3_declare_vtab() interface) what the +** result set of queries against completion will look like. +*/ +static int completionConnect( + sqlite3 *db, + void *pAux, + int argc, const char *const*argv, + sqlite3_vtab **ppVtab, + char **pzErr +){ + completion_vtab *pNew; + int rc; + + (void)(pAux); /* Unused parameter */ + (void)(argc); /* Unused parameter */ + (void)(argv); /* Unused parameter */ + (void)(pzErr); /* Unused parameter */ + +/* Column numbers */ +#define COMPLETION_COLUMN_CANDIDATE 0 /* Suggested completion of the input */ +#define COMPLETION_COLUMN_PREFIX 1 /* Prefix of the word to be completed */ +#define COMPLETION_COLUMN_WHOLELINE 2 /* Entire line seen so far */ +#define COMPLETION_COLUMN_PHASE 3 /* ePhase - used for debugging only */ + + sqlite3_vtab_config(db, SQLITE_VTAB_INNOCUOUS); + rc = sqlite3_declare_vtab(db, + "CREATE TABLE x(" + " candidate TEXT," + " prefix TEXT HIDDEN," + " wholeline TEXT HIDDEN," + " phase INT HIDDEN" /* Used for debugging only */ + ")"); + if( rc==SQLITE_OK ){ + pNew = sqlite3_malloc( sizeof(*pNew) ); + *ppVtab = (sqlite3_vtab*)pNew; + if( pNew==0 ) return SQLITE_NOMEM; + memset(pNew, 0, sizeof(*pNew)); + pNew->db = db; + } + return rc; +} + +/* +** This method is the destructor for completion_cursor objects. +*/ +static int completionDisconnect(sqlite3_vtab *pVtab){ + sqlite3_free(pVtab); + return SQLITE_OK; +} + +/* +** Constructor for a new completion_cursor object. +*/ +static int completionOpen(sqlite3_vtab *p, sqlite3_vtab_cursor **ppCursor){ + completion_cursor *pCur; + pCur = sqlite3_malloc( sizeof(*pCur) ); + if( pCur==0 ) return SQLITE_NOMEM; + memset(pCur, 0, sizeof(*pCur)); + pCur->db = ((completion_vtab*)p)->db; + *ppCursor = &pCur->base; + return SQLITE_OK; +} + +/* +** Reset the completion_cursor. +*/ +static void completionCursorReset(completion_cursor *pCur){ + sqlite3_free(pCur->zPrefix); pCur->zPrefix = 0; pCur->nPrefix = 0; + sqlite3_free(pCur->zLine); pCur->zLine = 0; pCur->nLine = 0; + sqlite3_finalize(pCur->pStmt); pCur->pStmt = 0; + pCur->j = 0; +} + +/* +** Destructor for a completion_cursor. +*/ +static int completionClose(sqlite3_vtab_cursor *cur){ + completionCursorReset((completion_cursor*)cur); + sqlite3_free(cur); + return SQLITE_OK; +} + +/* +** Advance a completion_cursor to its next row of output. +** +** The ->ePhase, ->j, and ->pStmt fields of the completion_cursor object +** record the current state of the scan. This routine sets ->zCurrentRow +** to the current row of output and then returns. If no more rows remain, +** then ->ePhase is set to COMPLETION_EOF which will signal the virtual +** table that has reached the end of its scan. +** +** The current implementation just lists potential identifiers and +** keywords and filters them by zPrefix. Future enhancements should +** take zLine into account to try to restrict the set of identifiers and +** keywords based on what would be legal at the current point of input. +*/ +static int completionNext(sqlite3_vtab_cursor *cur){ + completion_cursor *pCur = (completion_cursor*)cur; + int eNextPhase = 0; /* Next phase to try if current phase reaches end */ + int iCol = -1; /* If >=0, step pCur->pStmt and use the i-th column */ + pCur->iRowid++; + while( pCur->ePhase!=COMPLETION_EOF ){ + switch( pCur->ePhase ){ + case COMPLETION_KEYWORDS: { + if( pCur->j >= sqlite3_keyword_count() ){ + pCur->zCurrentRow = 0; + pCur->ePhase = COMPLETION_DATABASES; + }else{ + sqlite3_keyword_name(pCur->j++, &pCur->zCurrentRow, &pCur->szRow); + } + iCol = -1; + break; + } + case COMPLETION_DATABASES: { + if( pCur->pStmt==0 ){ + sqlite3_prepare_v2(pCur->db, "PRAGMA database_list", -1, + &pCur->pStmt, 0); + } + iCol = 1; + eNextPhase = COMPLETION_TABLES; + break; + } + case COMPLETION_TABLES: { + if( pCur->pStmt==0 ){ + sqlite3_stmt *pS2; + char *zSql = 0; + const char *zSep = ""; + sqlite3_prepare_v2(pCur->db, "PRAGMA database_list", -1, &pS2, 0); + while( sqlite3_step(pS2)==SQLITE_ROW ){ + const char *zDb = (const char*)sqlite3_column_text(pS2, 1); + zSql = sqlite3_mprintf( + "%z%s" + "SELECT name FROM \"%w\".sqlite_schema", + zSql, zSep, zDb + ); + if( zSql==0 ) return SQLITE_NOMEM; + zSep = " UNION "; + } + sqlite3_finalize(pS2); + sqlite3_prepare_v2(pCur->db, zSql, -1, &pCur->pStmt, 0); + sqlite3_free(zSql); + } + iCol = 0; + eNextPhase = COMPLETION_COLUMNS; + break; + } + case COMPLETION_COLUMNS: { + if( pCur->pStmt==0 ){ + sqlite3_stmt *pS2; + char *zSql = 0; + const char *zSep = ""; + sqlite3_prepare_v2(pCur->db, "PRAGMA database_list", -1, &pS2, 0); + while( sqlite3_step(pS2)==SQLITE_ROW ){ + const char *zDb = (const char*)sqlite3_column_text(pS2, 1); + zSql = sqlite3_mprintf( + "%z%s" + "SELECT pti.name FROM \"%w\".sqlite_schema AS sm" + " JOIN pragma_table_info(sm.name,%Q) AS pti" + " WHERE sm.type='table'", + zSql, zSep, zDb, zDb + ); + if( zSql==0 ) return SQLITE_NOMEM; + zSep = " UNION "; + } + sqlite3_finalize(pS2); + sqlite3_prepare_v2(pCur->db, zSql, -1, &pCur->pStmt, 0); + sqlite3_free(zSql); + } + iCol = 0; + eNextPhase = COMPLETION_EOF; + break; + } + } + if( iCol<0 ){ + /* This case is when the phase presets zCurrentRow */ + if( pCur->zCurrentRow==0 ) continue; + }else{ + if( sqlite3_step(pCur->pStmt)==SQLITE_ROW ){ + /* Extract the next row of content */ + pCur->zCurrentRow = (const char*)sqlite3_column_text(pCur->pStmt, iCol); + pCur->szRow = sqlite3_column_bytes(pCur->pStmt, iCol); + }else{ + /* When all rows are finished, advance to the next phase */ + sqlite3_finalize(pCur->pStmt); + pCur->pStmt = 0; + pCur->ePhase = eNextPhase; + continue; + } + } + if( pCur->nPrefix==0 ) break; + if( pCur->nPrefix<=pCur->szRow + && sqlite3_strnicmp(pCur->zPrefix, pCur->zCurrentRow, pCur->nPrefix)==0 + ){ + break; + } + } + + return SQLITE_OK; +} + +/* +** Return values of columns for the row at which the completion_cursor +** is currently pointing. +*/ +static int completionColumn( + sqlite3_vtab_cursor *cur, /* The cursor */ + sqlite3_context *ctx, /* First argument to sqlite3_result_...() */ + int i /* Which column to return */ +){ + completion_cursor *pCur = (completion_cursor*)cur; + switch( i ){ + case COMPLETION_COLUMN_CANDIDATE: { + sqlite3_result_text(ctx, pCur->zCurrentRow, pCur->szRow,SQLITE_TRANSIENT); + break; + } + case COMPLETION_COLUMN_PREFIX: { + sqlite3_result_text(ctx, pCur->zPrefix, -1, SQLITE_TRANSIENT); + break; + } + case COMPLETION_COLUMN_WHOLELINE: { + sqlite3_result_text(ctx, pCur->zLine, -1, SQLITE_TRANSIENT); + break; + } + case COMPLETION_COLUMN_PHASE: { + sqlite3_result_int(ctx, pCur->ePhase); + break; + } + } + return SQLITE_OK; +} + +/* +** Return the rowid for the current row. In this implementation, the +** rowid is the same as the output value. +*/ +static int completionRowid(sqlite3_vtab_cursor *cur, sqlite_int64 *pRowid){ + completion_cursor *pCur = (completion_cursor*)cur; + *pRowid = pCur->iRowid; + return SQLITE_OK; +} + +/* +** Return TRUE if the cursor has been moved off of the last +** row of output. +*/ +static int completionEof(sqlite3_vtab_cursor *cur){ + completion_cursor *pCur = (completion_cursor*)cur; + return pCur->ePhase >= COMPLETION_EOF; +} + +/* +** This method is called to "rewind" the completion_cursor object back +** to the first row of output. This method is always called at least +** once prior to any call to completionColumn() or completionRowid() or +** completionEof(). +*/ +static int completionFilter( + sqlite3_vtab_cursor *pVtabCursor, + int idxNum, const char *idxStr, + int argc, sqlite3_value **argv +){ + completion_cursor *pCur = (completion_cursor *)pVtabCursor; + int iArg = 0; + (void)(idxStr); /* Unused parameter */ + (void)(argc); /* Unused parameter */ + completionCursorReset(pCur); + if( idxNum & 1 ){ + pCur->nPrefix = sqlite3_value_bytes(argv[iArg]); + if( pCur->nPrefix>0 ){ + pCur->zPrefix = sqlite3_mprintf("%s", sqlite3_value_text(argv[iArg])); + if( pCur->zPrefix==0 ) return SQLITE_NOMEM; + } + iArg = 1; + } + if( idxNum & 2 ){ + pCur->nLine = sqlite3_value_bytes(argv[iArg]); + if( pCur->nLine>0 ){ + pCur->zLine = sqlite3_mprintf("%s", sqlite3_value_text(argv[iArg])); + if( pCur->zLine==0 ) return SQLITE_NOMEM; + } + } + if( pCur->zLine!=0 && pCur->zPrefix==0 ){ + int i = pCur->nLine; + while( i>0 && (isalnum(pCur->zLine[i-1]) || pCur->zLine[i-1]=='_') ){ + i--; + } + pCur->nPrefix = pCur->nLine - i; + if( pCur->nPrefix>0 ){ + pCur->zPrefix = sqlite3_mprintf("%.*s", pCur->nPrefix, pCur->zLine + i); + if( pCur->zPrefix==0 ) return SQLITE_NOMEM; + } + } + pCur->iRowid = 0; + pCur->ePhase = COMPLETION_FIRST_PHASE; + return completionNext(pVtabCursor); +} + +/* +** SQLite will invoke this method one or more times while planning a query +** that uses the completion virtual table. This routine needs to create +** a query plan for each invocation and compute an estimated cost for that +** plan. +** +** There are two hidden parameters that act as arguments to the table-valued +** function: "prefix" and "wholeline". Bit 0 of idxNum is set if "prefix" +** is available and bit 1 is set if "wholeline" is available. +*/ +static int completionBestIndex( + sqlite3_vtab *tab, + sqlite3_index_info *pIdxInfo +){ + int i; /* Loop over constraints */ + int idxNum = 0; /* The query plan bitmask */ + int prefixIdx = -1; /* Index of the start= constraint, or -1 if none */ + int wholelineIdx = -1; /* Index of the stop= constraint, or -1 if none */ + int nArg = 0; /* Number of arguments that completeFilter() expects */ + const struct sqlite3_index_constraint *pConstraint; + + (void)(tab); /* Unused parameter */ + pConstraint = pIdxInfo->aConstraint; + for(i=0; inConstraint; i++, pConstraint++){ + if( pConstraint->usable==0 ) continue; + if( pConstraint->op!=SQLITE_INDEX_CONSTRAINT_EQ ) continue; + switch( pConstraint->iColumn ){ + case COMPLETION_COLUMN_PREFIX: + prefixIdx = i; + idxNum |= 1; + break; + case COMPLETION_COLUMN_WHOLELINE: + wholelineIdx = i; + idxNum |= 2; + break; + } + } + if( prefixIdx>=0 ){ + pIdxInfo->aConstraintUsage[prefixIdx].argvIndex = ++nArg; + pIdxInfo->aConstraintUsage[prefixIdx].omit = 1; + } + if( wholelineIdx>=0 ){ + pIdxInfo->aConstraintUsage[wholelineIdx].argvIndex = ++nArg; + pIdxInfo->aConstraintUsage[wholelineIdx].omit = 1; + } + pIdxInfo->idxNum = idxNum; + pIdxInfo->estimatedCost = (double)5000 - 1000*nArg; + pIdxInfo->estimatedRows = 500 - 100*nArg; + return SQLITE_OK; +} + +/* +** This following structure defines all the methods for the +** completion virtual table. +*/ +static sqlite3_module completionModule = { + 0, /* iVersion */ + 0, /* xCreate */ + completionConnect, /* xConnect */ + completionBestIndex, /* xBestIndex */ + completionDisconnect, /* xDisconnect */ + 0, /* xDestroy */ + completionOpen, /* xOpen - open a cursor */ + completionClose, /* xClose - close a cursor */ + completionFilter, /* xFilter - configure scan constraints */ + completionNext, /* xNext - advance a cursor */ + completionEof, /* xEof - check for end of scan */ + completionColumn, /* xColumn - read data */ + completionRowid, /* xRowid - read data */ + 0, /* xUpdate */ + 0, /* xBegin */ + 0, /* xSync */ + 0, /* xCommit */ + 0, /* xRollback */ + 0, /* xFindMethod */ + 0, /* xRename */ + 0, /* xSavepoint */ + 0, /* xRelease */ + 0, /* xRollbackTo */ + 0 /* xShadowName */ +}; + +#endif /* SQLITE_OMIT_VIRTUALTABLE */ + +int sqlite3CompletionVtabInit(sqlite3 *db){ + int rc = SQLITE_OK; +#ifndef SQLITE_OMIT_VIRTUALTABLE + rc = sqlite3_create_module(db, "completion", &completionModule, 0); +#endif + return rc; +} + +#ifdef _WIN32 + +#endif +int sqlite3_completion_init( + sqlite3 *db, + char **pzErrMsg, + const sqlite3_api_routines *pApi +){ + int rc = SQLITE_OK; + SQLITE_EXTENSION_INIT2(pApi); + (void)(pzErrMsg); /* Unused parameter */ +#ifndef SQLITE_OMIT_VIRTUALTABLE + rc = sqlite3CompletionVtabInit(db); +#endif + return rc; +} + +/************************* End ../ext/misc/completion.c ********************/ +/************************* Begin ../ext/misc/appendvfs.c ******************/ +/* +** 2017-10-20 +** +** The author disclaims copyright to this source code. In place of +** a legal notice, here is a blessing: +** +** May you do good and not evil. +** May you find forgiveness for yourself and forgive others. +** May you share freely, never taking more than you give. +** +****************************************************************************** +** +** This file implements a VFS shim that allows an SQLite database to be +** appended onto the end of some other file, such as an executable. +** +** A special record must appear at the end of the file that identifies the +** file as an appended database and provides the offset to the first page +** of the exposed content. (Or, it is the length of the content prefix.) +** For best performance page 1 should be located at a disk page boundary, +** though that is not required. +** +** When opening a database using this VFS, the connection might treat +** the file as an ordinary SQLite database, or it might treat it as a +** database appended onto some other file. The decision is made by +** applying the following rules in order: +** +** (1) An empty file is an ordinary database. +** +** (2) If the file ends with the appendvfs trailer string +** "Start-Of-SQLite3-NNNNNNNN" that file is an appended database. +** +** (3) If the file begins with the standard SQLite prefix string +** "SQLite format 3", that file is an ordinary database. +** +** (4) If none of the above apply and the SQLITE_OPEN_CREATE flag is +** set, then a new database is appended to the already existing file. +** +** (5) Otherwise, SQLITE_CANTOPEN is returned. +** +** To avoid unnecessary complications with the PENDING_BYTE, the size of +** the file containing the database is limited to 1GiB. (1073741824 bytes) +** This VFS will not read or write past the 1GiB mark. This restriction +** might be lifted in future versions. For now, if you need a larger +** database, then keep it in a separate file. +** +** If the file being opened is a plain database (not an appended one), then +** this shim is a pass-through into the default underlying VFS. (rule 3) +**/ +/* #include "sqlite3ext.h" */ +SQLITE_EXTENSION_INIT1 +#include +#include + +/* The append mark at the end of the database is: +** +** Start-Of-SQLite3-NNNNNNNN +** 123456789 123456789 12345 +** +** The NNNNNNNN represents a 64-bit big-endian unsigned integer which is +** the offset to page 1, and also the length of the prefix content. +*/ +#define APND_MARK_PREFIX "Start-Of-SQLite3-" +#define APND_MARK_PREFIX_SZ 17 +#define APND_MARK_FOS_SZ 8 +#define APND_MARK_SIZE (APND_MARK_PREFIX_SZ+APND_MARK_FOS_SZ) + +/* +** Maximum size of the combined prefix + database + append-mark. This +** must be less than 0x40000000 to avoid locking issues on Windows. +*/ +#define APND_MAX_SIZE (0x40000000) + +/* +** Try to align the database to an even multiple of APND_ROUNDUP bytes. +*/ +#ifndef APND_ROUNDUP +#define APND_ROUNDUP 4096 +#endif +#define APND_ALIGN_MASK ((sqlite3_int64)(APND_ROUNDUP-1)) +#define APND_START_ROUNDUP(fsz) (((fsz)+APND_ALIGN_MASK) & ~APND_ALIGN_MASK) + +/* +** Forward declaration of objects used by this utility +*/ +typedef struct sqlite3_vfs ApndVfs; +typedef struct ApndFile ApndFile; + +/* Access to a lower-level VFS that (might) implement dynamic loading, +** access to randomness, etc. +*/ +#define ORIGVFS(p) ((sqlite3_vfs*)((p)->pAppData)) +#define ORIGFILE(p) ((sqlite3_file*)(((ApndFile*)(p))+1)) + +/* An open appendvfs file +** +** An instance of this structure describes the appended database file. +** A separate sqlite3_file object is always appended. The appended +** sqlite3_file object (which can be accessed using ORIGFILE()) describes +** the entire file, including the prefix, the database, and the +** append-mark. +** +** The structure of an AppendVFS database is like this: +** +** +-------------+---------+----------+-------------+ +** | prefix-file | padding | database | append-mark | +** +-------------+---------+----------+-------------+ +** ^ ^ +** | | +** iPgOne iMark +** +** +** "prefix file" - file onto which the database has been appended. +** "padding" - zero or more bytes inserted so that "database" +** starts on an APND_ROUNDUP boundary +** "database" - The SQLite database file +** "append-mark" - The 25-byte "Start-Of-SQLite3-NNNNNNNN" that indicates +** the offset from the start of prefix-file to the start +** of "database". +** +** The size of the database is iMark - iPgOne. +** +** The NNNNNNNN in the "Start-Of-SQLite3-NNNNNNNN" suffix is the value +** of iPgOne stored as a big-ending 64-bit integer. +** +** iMark will be the size of the underlying file minus 25 (APND_MARKSIZE). +** Or, iMark is -1 to indicate that it has not yet been written. +*/ +struct ApndFile { + sqlite3_file base; /* Subclass. MUST BE FIRST! */ + sqlite3_int64 iPgOne; /* Offset to the start of the database */ + sqlite3_int64 iMark; /* Offset of the append mark. -1 if unwritten */ + /* Always followed by another sqlite3_file that describes the whole file */ +}; + +/* +** Methods for ApndFile +*/ +static int apndClose(sqlite3_file*); +static int apndRead(sqlite3_file*, void*, int iAmt, sqlite3_int64 iOfst); +static int apndWrite(sqlite3_file*,const void*,int iAmt, sqlite3_int64 iOfst); +static int apndTruncate(sqlite3_file*, sqlite3_int64 size); +static int apndSync(sqlite3_file*, int flags); +static int apndFileSize(sqlite3_file*, sqlite3_int64 *pSize); +static int apndLock(sqlite3_file*, int); +static int apndUnlock(sqlite3_file*, int); +static int apndCheckReservedLock(sqlite3_file*, int *pResOut); +static int apndFileControl(sqlite3_file*, int op, void *pArg); +static int apndSectorSize(sqlite3_file*); +static int apndDeviceCharacteristics(sqlite3_file*); +static int apndShmMap(sqlite3_file*, int iPg, int pgsz, int, void volatile**); +static int apndShmLock(sqlite3_file*, int offset, int n, int flags); +static void apndShmBarrier(sqlite3_file*); +static int apndShmUnmap(sqlite3_file*, int deleteFlag); +static int apndFetch(sqlite3_file*, sqlite3_int64 iOfst, int iAmt, void **pp); +static int apndUnfetch(sqlite3_file*, sqlite3_int64 iOfst, void *p); + +/* +** Methods for ApndVfs +*/ +static int apndOpen(sqlite3_vfs*, const char *, sqlite3_file*, int , int *); +static int apndDelete(sqlite3_vfs*, const char *zName, int syncDir); +static int apndAccess(sqlite3_vfs*, const char *zName, int flags, int *); +static int apndFullPathname(sqlite3_vfs*, const char *zName, int, char *zOut); +static void *apndDlOpen(sqlite3_vfs*, const char *zFilename); +static void apndDlError(sqlite3_vfs*, int nByte, char *zErrMsg); +static void (*apndDlSym(sqlite3_vfs *pVfs, void *p, const char*zSym))(void); +static void apndDlClose(sqlite3_vfs*, void*); +static int apndRandomness(sqlite3_vfs*, int nByte, char *zOut); +static int apndSleep(sqlite3_vfs*, int microseconds); +static int apndCurrentTime(sqlite3_vfs*, double*); +static int apndGetLastError(sqlite3_vfs*, int, char *); +static int apndCurrentTimeInt64(sqlite3_vfs*, sqlite3_int64*); +static int apndSetSystemCall(sqlite3_vfs*, const char*,sqlite3_syscall_ptr); +static sqlite3_syscall_ptr apndGetSystemCall(sqlite3_vfs*, const char *z); +static const char *apndNextSystemCall(sqlite3_vfs*, const char *zName); + +static sqlite3_vfs apnd_vfs = { + 3, /* iVersion (set when registered) */ + 0, /* szOsFile (set when registered) */ + 1024, /* mxPathname */ + 0, /* pNext */ + "apndvfs", /* zName */ + 0, /* pAppData (set when registered) */ + apndOpen, /* xOpen */ + apndDelete, /* xDelete */ + apndAccess, /* xAccess */ + apndFullPathname, /* xFullPathname */ + apndDlOpen, /* xDlOpen */ + apndDlError, /* xDlError */ + apndDlSym, /* xDlSym */ + apndDlClose, /* xDlClose */ + apndRandomness, /* xRandomness */ + apndSleep, /* xSleep */ + apndCurrentTime, /* xCurrentTime */ + apndGetLastError, /* xGetLastError */ + apndCurrentTimeInt64, /* xCurrentTimeInt64 */ + apndSetSystemCall, /* xSetSystemCall */ + apndGetSystemCall, /* xGetSystemCall */ + apndNextSystemCall /* xNextSystemCall */ +}; + +static const sqlite3_io_methods apnd_io_methods = { + 3, /* iVersion */ + apndClose, /* xClose */ + apndRead, /* xRead */ + apndWrite, /* xWrite */ + apndTruncate, /* xTruncate */ + apndSync, /* xSync */ + apndFileSize, /* xFileSize */ + apndLock, /* xLock */ + apndUnlock, /* xUnlock */ + apndCheckReservedLock, /* xCheckReservedLock */ + apndFileControl, /* xFileControl */ + apndSectorSize, /* xSectorSize */ + apndDeviceCharacteristics, /* xDeviceCharacteristics */ + apndShmMap, /* xShmMap */ + apndShmLock, /* xShmLock */ + apndShmBarrier, /* xShmBarrier */ + apndShmUnmap, /* xShmUnmap */ + apndFetch, /* xFetch */ + apndUnfetch /* xUnfetch */ +}; + +/* +** Close an apnd-file. +*/ +static int apndClose(sqlite3_file *pFile){ + pFile = ORIGFILE(pFile); + return pFile->pMethods->xClose(pFile); +} + +/* +** Read data from an apnd-file. +*/ +static int apndRead( + sqlite3_file *pFile, + void *zBuf, + int iAmt, + sqlite_int64 iOfst +){ + ApndFile *paf = (ApndFile *)pFile; + pFile = ORIGFILE(pFile); + return pFile->pMethods->xRead(pFile, zBuf, iAmt, paf->iPgOne+iOfst); +} + +/* +** Add the append-mark onto what should become the end of the file. +* If and only if this succeeds, internal ApndFile.iMark is updated. +* Parameter iWriteEnd is the appendvfs-relative offset of the new mark. +*/ +static int apndWriteMark( + ApndFile *paf, + sqlite3_file *pFile, + sqlite_int64 iWriteEnd +){ + sqlite_int64 iPgOne = paf->iPgOne; + unsigned char a[APND_MARK_SIZE]; + int i = APND_MARK_FOS_SZ; + int rc; + assert(pFile == ORIGFILE(paf)); + memcpy(a, APND_MARK_PREFIX, APND_MARK_PREFIX_SZ); + while( --i >= 0 ){ + a[APND_MARK_PREFIX_SZ+i] = (unsigned char)(iPgOne & 0xff); + iPgOne >>= 8; + } + iWriteEnd += paf->iPgOne; + if( SQLITE_OK==(rc = pFile->pMethods->xWrite + (pFile, a, APND_MARK_SIZE, iWriteEnd)) ){ + paf->iMark = iWriteEnd; + } + return rc; +} + +/* +** Write data to an apnd-file. +*/ +static int apndWrite( + sqlite3_file *pFile, + const void *zBuf, + int iAmt, + sqlite_int64 iOfst +){ + ApndFile *paf = (ApndFile *)pFile; + sqlite_int64 iWriteEnd = iOfst + iAmt; + if( iWriteEnd>=APND_MAX_SIZE ) return SQLITE_FULL; + pFile = ORIGFILE(pFile); + /* If append-mark is absent or will be overwritten, write it. */ + if( paf->iMark < 0 || paf->iPgOne + iWriteEnd > paf->iMark ){ + int rc = apndWriteMark(paf, pFile, iWriteEnd); + if( SQLITE_OK!=rc ) return rc; + } + return pFile->pMethods->xWrite(pFile, zBuf, iAmt, paf->iPgOne+iOfst); +} + +/* +** Truncate an apnd-file. +*/ +static int apndTruncate(sqlite3_file *pFile, sqlite_int64 size){ + ApndFile *paf = (ApndFile *)pFile; + pFile = ORIGFILE(pFile); + /* The append mark goes out first so truncate failure does not lose it. */ + if( SQLITE_OK!=apndWriteMark(paf, pFile, size) ) return SQLITE_IOERR; + /* Truncate underlying file just past append mark */ + return pFile->pMethods->xTruncate(pFile, paf->iMark+APND_MARK_SIZE); +} + +/* +** Sync an apnd-file. +*/ +static int apndSync(sqlite3_file *pFile, int flags){ + pFile = ORIGFILE(pFile); + return pFile->pMethods->xSync(pFile, flags); +} + +/* +** Return the current file-size of an apnd-file. +** If the append mark is not yet there, the file-size is 0. +*/ +static int apndFileSize(sqlite3_file *pFile, sqlite_int64 *pSize){ + ApndFile *paf = (ApndFile *)pFile; + *pSize = ( paf->iMark >= 0 )? (paf->iMark - paf->iPgOne) : 0; + return SQLITE_OK; +} + +/* +** Lock an apnd-file. +*/ +static int apndLock(sqlite3_file *pFile, int eLock){ + pFile = ORIGFILE(pFile); + return pFile->pMethods->xLock(pFile, eLock); +} + +/* +** Unlock an apnd-file. +*/ +static int apndUnlock(sqlite3_file *pFile, int eLock){ + pFile = ORIGFILE(pFile); + return pFile->pMethods->xUnlock(pFile, eLock); +} + +/* +** Check if another file-handle holds a RESERVED lock on an apnd-file. +*/ +static int apndCheckReservedLock(sqlite3_file *pFile, int *pResOut){ + pFile = ORIGFILE(pFile); + return pFile->pMethods->xCheckReservedLock(pFile, pResOut); +} + +/* +** File control method. For custom operations on an apnd-file. +*/ +static int apndFileControl(sqlite3_file *pFile, int op, void *pArg){ + ApndFile *paf = (ApndFile *)pFile; + int rc; + pFile = ORIGFILE(pFile); + if( op==SQLITE_FCNTL_SIZE_HINT ) *(sqlite3_int64*)pArg += paf->iPgOne; + rc = pFile->pMethods->xFileControl(pFile, op, pArg); + if( rc==SQLITE_OK && op==SQLITE_FCNTL_VFSNAME ){ + *(char**)pArg = sqlite3_mprintf("apnd(%lld)/%z", paf->iPgOne,*(char**)pArg); + } + return rc; +} + +/* +** Return the sector-size in bytes for an apnd-file. +*/ +static int apndSectorSize(sqlite3_file *pFile){ + pFile = ORIGFILE(pFile); + return pFile->pMethods->xSectorSize(pFile); +} + +/* +** Return the device characteristic flags supported by an apnd-file. +*/ +static int apndDeviceCharacteristics(sqlite3_file *pFile){ + pFile = ORIGFILE(pFile); + return pFile->pMethods->xDeviceCharacteristics(pFile); +} + +/* Create a shared memory file mapping */ +static int apndShmMap( + sqlite3_file *pFile, + int iPg, + int pgsz, + int bExtend, + void volatile **pp +){ + pFile = ORIGFILE(pFile); + return pFile->pMethods->xShmMap(pFile,iPg,pgsz,bExtend,pp); +} + +/* Perform locking on a shared-memory segment */ +static int apndShmLock(sqlite3_file *pFile, int offset, int n, int flags){ + pFile = ORIGFILE(pFile); + return pFile->pMethods->xShmLock(pFile,offset,n,flags); +} + +/* Memory barrier operation on shared memory */ +static void apndShmBarrier(sqlite3_file *pFile){ + pFile = ORIGFILE(pFile); + pFile->pMethods->xShmBarrier(pFile); +} + +/* Unmap a shared memory segment */ +static int apndShmUnmap(sqlite3_file *pFile, int deleteFlag){ + pFile = ORIGFILE(pFile); + return pFile->pMethods->xShmUnmap(pFile,deleteFlag); +} + +/* Fetch a page of a memory-mapped file */ +static int apndFetch( + sqlite3_file *pFile, + sqlite3_int64 iOfst, + int iAmt, + void **pp +){ + ApndFile *p = (ApndFile *)pFile; + if( p->iMark < 0 || iOfst+iAmt > p->iMark ){ + return SQLITE_IOERR; /* Cannot read what is not yet there. */ + } + pFile = ORIGFILE(pFile); + return pFile->pMethods->xFetch(pFile, iOfst+p->iPgOne, iAmt, pp); +} + +/* Release a memory-mapped page */ +static int apndUnfetch(sqlite3_file *pFile, sqlite3_int64 iOfst, void *pPage){ + ApndFile *p = (ApndFile *)pFile; + pFile = ORIGFILE(pFile); + return pFile->pMethods->xUnfetch(pFile, iOfst+p->iPgOne, pPage); +} + +/* +** Try to read the append-mark off the end of a file. Return the +** start of the appended database if the append-mark is present. +** If there is no valid append-mark, return -1; +** +** An append-mark is only valid if the NNNNNNNN start-of-database offset +** indicates that the appended database contains at least one page. The +** start-of-database value must be a multiple of 512. +*/ +static sqlite3_int64 apndReadMark(sqlite3_int64 sz, sqlite3_file *pFile){ + int rc, i; + sqlite3_int64 iMark; + int msbs = 8 * (APND_MARK_FOS_SZ-1); + unsigned char a[APND_MARK_SIZE]; + + if( APND_MARK_SIZE!=(sz & 0x1ff) ) return -1; + rc = pFile->pMethods->xRead(pFile, a, APND_MARK_SIZE, sz-APND_MARK_SIZE); + if( rc ) return -1; + if( memcmp(a, APND_MARK_PREFIX, APND_MARK_PREFIX_SZ)!=0 ) return -1; + iMark = ((sqlite3_int64)(a[APND_MARK_PREFIX_SZ] & 0x7f)) << msbs; + for(i=1; i<8; i++){ + msbs -= 8; + iMark |= (sqlite3_int64)a[APND_MARK_PREFIX_SZ+i]< (sz - APND_MARK_SIZE - 512) ) return -1; + if( iMark & 0x1ff ) return -1; + return iMark; +} + +static const char apvfsSqliteHdr[] = "SQLite format 3"; +/* +** Check to see if the file is an appendvfs SQLite database file. +** Return true iff it is such. Parameter sz is the file's size. +*/ +static int apndIsAppendvfsDatabase(sqlite3_int64 sz, sqlite3_file *pFile){ + int rc; + char zHdr[16]; + sqlite3_int64 iMark = apndReadMark(sz, pFile); + if( iMark>=0 ){ + /* If file has the correct end-marker, the expected odd size, and the + ** SQLite DB type marker where the end-marker puts it, then it + ** is an appendvfs database. + */ + rc = pFile->pMethods->xRead(pFile, zHdr, sizeof(zHdr), iMark); + if( SQLITE_OK==rc + && memcmp(zHdr, apvfsSqliteHdr, sizeof(zHdr))==0 + && (sz & 0x1ff) == APND_MARK_SIZE + && sz>=512+APND_MARK_SIZE + ){ + return 1; /* It's an appendvfs database */ + } + } + return 0; +} + +/* +** Check to see if the file is an ordinary SQLite database file. +** Return true iff so. Parameter sz is the file's size. +*/ +static int apndIsOrdinaryDatabaseFile(sqlite3_int64 sz, sqlite3_file *pFile){ + char zHdr[16]; + if( apndIsAppendvfsDatabase(sz, pFile) /* rule 2 */ + || (sz & 0x1ff) != 0 + || SQLITE_OK!=pFile->pMethods->xRead(pFile, zHdr, sizeof(zHdr), 0) + || memcmp(zHdr, apvfsSqliteHdr, sizeof(zHdr))!=0 + ){ + return 0; + }else{ + return 1; + } +} + +/* +** Open an apnd file handle. +*/ +static int apndOpen( + sqlite3_vfs *pApndVfs, + const char *zName, + sqlite3_file *pFile, + int flags, + int *pOutFlags +){ + ApndFile *pApndFile = (ApndFile*)pFile; + sqlite3_file *pBaseFile = ORIGFILE(pFile); + sqlite3_vfs *pBaseVfs = ORIGVFS(pApndVfs); + int rc; + sqlite3_int64 sz = 0; + if( (flags & SQLITE_OPEN_MAIN_DB)==0 ){ + /* The appendvfs is not to be used for transient or temporary databases. + ** Just use the base VFS open to initialize the given file object and + ** open the underlying file. (Appendvfs is then unused for this file.) + */ + return pBaseVfs->xOpen(pBaseVfs, zName, pFile, flags, pOutFlags); + } + memset(pApndFile, 0, sizeof(ApndFile)); + pFile->pMethods = &apnd_io_methods; + pApndFile->iMark = -1; /* Append mark not yet written */ + + rc = pBaseVfs->xOpen(pBaseVfs, zName, pBaseFile, flags, pOutFlags); + if( rc==SQLITE_OK ){ + rc = pBaseFile->pMethods->xFileSize(pBaseFile, &sz); + if( rc ){ + pBaseFile->pMethods->xClose(pBaseFile); + } + } + if( rc ){ + pFile->pMethods = 0; + return rc; + } + if( apndIsOrdinaryDatabaseFile(sz, pBaseFile) ){ + /* The file being opened appears to be just an ordinary DB. Copy + ** the base dispatch-table so this instance mimics the base VFS. + */ + memmove(pApndFile, pBaseFile, pBaseVfs->szOsFile); + return SQLITE_OK; + } + pApndFile->iPgOne = apndReadMark(sz, pFile); + if( pApndFile->iPgOne>=0 ){ + pApndFile->iMark = sz - APND_MARK_SIZE; /* Append mark found */ + return SQLITE_OK; + } + if( (flags & SQLITE_OPEN_CREATE)==0 ){ + pBaseFile->pMethods->xClose(pBaseFile); + rc = SQLITE_CANTOPEN; + pFile->pMethods = 0; + }else{ + /* Round newly added appendvfs location to #define'd page boundary. + ** Note that nothing has yet been written to the underlying file. + ** The append mark will be written along with first content write. + ** Until then, paf->iMark value indicates it is not yet written. + */ + pApndFile->iPgOne = APND_START_ROUNDUP(sz); + } + return rc; +} + +/* +** Delete an apnd file. +** For an appendvfs, this could mean delete the appendvfs portion, +** leaving the appendee as it was before it gained an appendvfs. +** For now, this code deletes the underlying file too. +*/ +static int apndDelete(sqlite3_vfs *pVfs, const char *zPath, int dirSync){ + return ORIGVFS(pVfs)->xDelete(ORIGVFS(pVfs), zPath, dirSync); +} + +/* +** All other VFS methods are pass-thrus. +*/ +static int apndAccess( + sqlite3_vfs *pVfs, + const char *zPath, + int flags, + int *pResOut +){ + return ORIGVFS(pVfs)->xAccess(ORIGVFS(pVfs), zPath, flags, pResOut); +} +static int apndFullPathname( + sqlite3_vfs *pVfs, + const char *zPath, + int nOut, + char *zOut +){ + return ORIGVFS(pVfs)->xFullPathname(ORIGVFS(pVfs),zPath,nOut,zOut); +} +static void *apndDlOpen(sqlite3_vfs *pVfs, const char *zPath){ + return ORIGVFS(pVfs)->xDlOpen(ORIGVFS(pVfs), zPath); +} +static void apndDlError(sqlite3_vfs *pVfs, int nByte, char *zErrMsg){ + ORIGVFS(pVfs)->xDlError(ORIGVFS(pVfs), nByte, zErrMsg); +} +static void (*apndDlSym(sqlite3_vfs *pVfs, void *p, const char *zSym))(void){ + return ORIGVFS(pVfs)->xDlSym(ORIGVFS(pVfs), p, zSym); +} +static void apndDlClose(sqlite3_vfs *pVfs, void *pHandle){ + ORIGVFS(pVfs)->xDlClose(ORIGVFS(pVfs), pHandle); +} +static int apndRandomness(sqlite3_vfs *pVfs, int nByte, char *zBufOut){ + return ORIGVFS(pVfs)->xRandomness(ORIGVFS(pVfs), nByte, zBufOut); +} +static int apndSleep(sqlite3_vfs *pVfs, int nMicro){ + return ORIGVFS(pVfs)->xSleep(ORIGVFS(pVfs), nMicro); +} +static int apndCurrentTime(sqlite3_vfs *pVfs, double *pTimeOut){ + return ORIGVFS(pVfs)->xCurrentTime(ORIGVFS(pVfs), pTimeOut); +} +static int apndGetLastError(sqlite3_vfs *pVfs, int a, char *b){ + return ORIGVFS(pVfs)->xGetLastError(ORIGVFS(pVfs), a, b); +} +static int apndCurrentTimeInt64(sqlite3_vfs *pVfs, sqlite3_int64 *p){ + return ORIGVFS(pVfs)->xCurrentTimeInt64(ORIGVFS(pVfs), p); +} +static int apndSetSystemCall( + sqlite3_vfs *pVfs, + const char *zName, + sqlite3_syscall_ptr pCall +){ + return ORIGVFS(pVfs)->xSetSystemCall(ORIGVFS(pVfs),zName,pCall); +} +static sqlite3_syscall_ptr apndGetSystemCall( + sqlite3_vfs *pVfs, + const char *zName +){ + return ORIGVFS(pVfs)->xGetSystemCall(ORIGVFS(pVfs),zName); +} +static const char *apndNextSystemCall(sqlite3_vfs *pVfs, const char *zName){ + return ORIGVFS(pVfs)->xNextSystemCall(ORIGVFS(pVfs), zName); +} + + +#ifdef _WIN32 + +#endif +/* +** This routine is called when the extension is loaded. +** Register the new VFS. +*/ +int sqlite3_appendvfs_init( + sqlite3 *db, + char **pzErrMsg, + const sqlite3_api_routines *pApi +){ + int rc = SQLITE_OK; + sqlite3_vfs *pOrig; + SQLITE_EXTENSION_INIT2(pApi); + (void)pzErrMsg; + (void)db; + pOrig = sqlite3_vfs_find(0); + if( pOrig==0 ) return SQLITE_ERROR; + apnd_vfs.iVersion = pOrig->iVersion; + apnd_vfs.pAppData = pOrig; + apnd_vfs.szOsFile = pOrig->szOsFile + sizeof(ApndFile); + rc = sqlite3_vfs_register(&apnd_vfs, 0); +#ifdef APPENDVFS_TEST + if( rc==SQLITE_OK ){ + rc = sqlite3_auto_extension((void(*)(void))apndvfsRegister); + } +#endif + if( rc==SQLITE_OK ) rc = SQLITE_OK_LOAD_PERMANENTLY; + return rc; +} + +/************************* End ../ext/misc/appendvfs.c ********************/ +/************************* Begin ../ext/misc/memtrace.c ******************/ +/* +** 2019-01-21 +** +** The author disclaims copyright to this source code. In place of +** a legal notice, here is a blessing: +** +** May you do good and not evil. +** May you find forgiveness for yourself and forgive others. +** May you share freely, never taking more than you give. +** +************************************************************************* +** +** This file implements an extension that uses the SQLITE_CONFIG_MALLOC +** mechanism to add a tracing layer on top of SQLite. If this extension +** is registered prior to sqlite3_initialize(), it will cause all memory +** allocation activities to be logged on standard output, or to some other +** FILE specified by the initializer. +** +** This file needs to be compiled into the application that uses it. +** +** This extension is used to implement the --memtrace option of the +** command-line shell. +*/ +#include +#include +#include + +/* The original memory allocation routines */ +static sqlite3_mem_methods memtraceBase; +static FILE *memtraceOut; + +/* Methods that trace memory allocations */ +static void *memtraceMalloc(int n){ + if( memtraceOut ){ + fprintf(memtraceOut, "MEMTRACE: allocate %d bytes\n", + memtraceBase.xRoundup(n)); + } + return memtraceBase.xMalloc(n); +} +static void memtraceFree(void *p){ + if( p==0 ) return; + if( memtraceOut ){ + fprintf(memtraceOut, "MEMTRACE: free %d bytes\n", memtraceBase.xSize(p)); + } + memtraceBase.xFree(p); +} +static void *memtraceRealloc(void *p, int n){ + if( p==0 ) return memtraceMalloc(n); + if( n==0 ){ + memtraceFree(p); + return 0; + } + if( memtraceOut ){ + fprintf(memtraceOut, "MEMTRACE: resize %d -> %d bytes\n", + memtraceBase.xSize(p), memtraceBase.xRoundup(n)); + } + return memtraceBase.xRealloc(p, n); +} +static int memtraceSize(void *p){ + return memtraceBase.xSize(p); +} +static int memtraceRoundup(int n){ + return memtraceBase.xRoundup(n); +} +static int memtraceInit(void *p){ + return memtraceBase.xInit(p); +} +static void memtraceShutdown(void *p){ + memtraceBase.xShutdown(p); +} + +/* The substitute memory allocator */ +static sqlite3_mem_methods ersaztMethods = { + memtraceMalloc, + memtraceFree, + memtraceRealloc, + memtraceSize, + memtraceRoundup, + memtraceInit, + memtraceShutdown, + 0 +}; + +/* Begin tracing memory allocations to out. */ +int sqlite3MemTraceActivate(FILE *out){ + int rc = SQLITE_OK; + if( memtraceBase.xMalloc==0 ){ + rc = sqlite3_config(SQLITE_CONFIG_GETMALLOC, &memtraceBase); + if( rc==SQLITE_OK ){ + rc = sqlite3_config(SQLITE_CONFIG_MALLOC, &ersaztMethods); + } + } + memtraceOut = out; + return rc; +} + +/* Deactivate memory tracing */ +int sqlite3MemTraceDeactivate(void){ + int rc = SQLITE_OK; + if( memtraceBase.xMalloc!=0 ){ + rc = sqlite3_config(SQLITE_CONFIG_MALLOC, &memtraceBase); + if( rc==SQLITE_OK ){ + memset(&memtraceBase, 0, sizeof(memtraceBase)); + } + } + memtraceOut = 0; + return rc; +} + +/************************* End ../ext/misc/memtrace.c ********************/ +/************************* Begin ../ext/misc/uint.c ******************/ +/* +** 2020-04-14 +** +** The author disclaims copyright to this source code. In place of +** a legal notice, here is a blessing: +** +** May you do good and not evil. +** May you find forgiveness for yourself and forgive others. +** May you share freely, never taking more than you give. +** +****************************************************************************** +** +** This SQLite extension implements the UINT collating sequence. +** +** UINT works like BINARY for text, except that embedded strings +** of digits compare in numeric order. +** +** * Leading zeros are handled properly, in the sense that +** they do not mess of the maginitude comparison of embedded +** strings of digits. "x00123y" is equal to "x123y". +** +** * Only unsigned integers are recognized. Plus and minus +** signs are ignored. Decimal points and exponential notation +** are ignored. +** +** * Embedded integers can be of arbitrary length. Comparison +** is *not* limited integers that can be expressed as a +** 64-bit machine integer. +*/ +/* #include "sqlite3ext.h" */ +SQLITE_EXTENSION_INIT1 +#include +#include +#include + +/* +** Compare text in lexicographic order, except strings of digits +** compare in numeric order. +*/ +static int uintCollFunc( + void *notUsed, + int nKey1, const void *pKey1, + int nKey2, const void *pKey2 +){ + const unsigned char *zA = (const unsigned char*)pKey1; + const unsigned char *zB = (const unsigned char*)pKey2; + int i=0, j=0, x; + (void)notUsed; + while( i +#include +#include +#include + +/* Mark a function parameter as unused, to suppress nuisance compiler +** warnings. */ +#ifndef UNUSED_PARAMETER +# define UNUSED_PARAMETER(X) (void)(X) +#endif + + +/* A decimal object */ +typedef struct Decimal Decimal; +struct Decimal { + char sign; /* 0 for positive, 1 for negative */ + char oom; /* True if an OOM is encountered */ + char isNull; /* True if holds a NULL rather than a number */ + char isInit; /* True upon initialization */ + int nDigit; /* Total number of digits */ + int nFrac; /* Number of digits to the right of the decimal point */ + signed char *a; /* Array of digits. Most significant first. */ +}; + +/* +** Release memory held by a Decimal, but do not free the object itself. +*/ +static void decimal_clear(Decimal *p){ + sqlite3_free(p->a); +} + +/* +** Destroy a Decimal object +*/ +static void decimal_free(Decimal *p){ + if( p ){ + decimal_clear(p); + sqlite3_free(p); + } +} + +/* +** Allocate a new Decimal object. Initialize it to the number given +** by the input string. +*/ +static Decimal *decimal_new( + sqlite3_context *pCtx, + sqlite3_value *pIn, + int nAlt, + const unsigned char *zAlt +){ + Decimal *p; + int n, i; + const unsigned char *zIn; + int iExp = 0; + p = sqlite3_malloc( sizeof(*p) ); + if( p==0 ) goto new_no_mem; + p->sign = 0; + p->oom = 0; + p->isInit = 1; + p->isNull = 0; + p->nDigit = 0; + p->nFrac = 0; + if( zAlt ){ + n = nAlt, + zIn = zAlt; + }else{ + if( sqlite3_value_type(pIn)==SQLITE_NULL ){ + p->a = 0; + p->isNull = 1; + return p; + } + n = sqlite3_value_bytes(pIn); + zIn = sqlite3_value_text(pIn); + } + p->a = sqlite3_malloc64( n+1 ); + if( p->a==0 ) goto new_no_mem; + for(i=0; isspace(zIn[i]); i++){} + if( zIn[i]=='-' ){ + p->sign = 1; + i++; + }else if( zIn[i]=='+' ){ + i++; + } + while( i='0' && c<='9' ){ + p->a[p->nDigit++] = c - '0'; + }else if( c=='.' ){ + p->nFrac = p->nDigit + 1; + }else if( c=='e' || c=='E' ){ + int j = i+1; + int neg = 0; + if( j>=n ) break; + if( zIn[j]=='-' ){ + neg = 1; + j++; + }else if( zIn[j]=='+' ){ + j++; + } + while( j='0' && zIn[j]<='9' ){ + iExp = iExp*10 + zIn[j] - '0'; + } + j++; + } + if( neg ) iExp = -iExp; + break; + } + i++; + } + if( p->nFrac ){ + p->nFrac = p->nDigit - (p->nFrac - 1); + } + if( iExp>0 ){ + if( p->nFrac>0 ){ + if( iExp<=p->nFrac ){ + p->nFrac -= iExp; + iExp = 0; + }else{ + iExp -= p->nFrac; + p->nFrac = 0; + } + } + if( iExp>0 ){ + p->a = sqlite3_realloc64(p->a, p->nDigit + iExp + 1 ); + if( p->a==0 ) goto new_no_mem; + memset(p->a+p->nDigit, 0, iExp); + p->nDigit += iExp; + } + }else if( iExp<0 ){ + int nExtra; + iExp = -iExp; + nExtra = p->nDigit - p->nFrac - 1; + if( nExtra ){ + if( nExtra>=iExp ){ + p->nFrac += iExp; + iExp = 0; + }else{ + iExp -= nExtra; + p->nFrac = p->nDigit - 1; + } + } + if( iExp>0 ){ + p->a = sqlite3_realloc64(p->a, p->nDigit + iExp + 1 ); + if( p->a==0 ) goto new_no_mem; + memmove(p->a+iExp, p->a, p->nDigit); + memset(p->a, 0, iExp); + p->nDigit += iExp; + p->nFrac += iExp; + } + } + return p; + +new_no_mem: + if( pCtx ) sqlite3_result_error_nomem(pCtx); + sqlite3_free(p); + return 0; +} + +/* +** Make the given Decimal the result. +*/ +static void decimal_result(sqlite3_context *pCtx, Decimal *p){ + char *z; + int i, j; + int n; + if( p==0 || p->oom ){ + sqlite3_result_error_nomem(pCtx); + return; + } + if( p->isNull ){ + sqlite3_result_null(pCtx); + return; + } + z = sqlite3_malloc( p->nDigit+4 ); + if( z==0 ){ + sqlite3_result_error_nomem(pCtx); + return; + } + i = 0; + if( p->nDigit==0 || (p->nDigit==1 && p->a[0]==0) ){ + p->sign = 0; + } + if( p->sign ){ + z[0] = '-'; + i = 1; + } + n = p->nDigit - p->nFrac; + if( n<=0 ){ + z[i++] = '0'; + } + j = 0; + while( n>1 && p->a[j]==0 ){ + j++; + n--; + } + while( n>0 ){ + z[i++] = p->a[j] + '0'; + j++; + n--; + } + if( p->nFrac ){ + z[i++] = '.'; + do{ + z[i++] = p->a[j] + '0'; + j++; + }while( jnDigit ); + } + z[i] = 0; + sqlite3_result_text(pCtx, z, i, sqlite3_free); +} + +/* +** SQL Function: decimal(X) +** +** Convert input X into decimal and then back into text +*/ +static void decimalFunc( + sqlite3_context *context, + int argc, + sqlite3_value **argv +){ + Decimal *p = decimal_new(context, argv[0], 0, 0); + UNUSED_PARAMETER(argc); + decimal_result(context, p); + decimal_free(p); +} + +/* +** Compare to Decimal objects. Return negative, 0, or positive if the +** first object is less than, equal to, or greater than the second. +** +** Preconditions for this routine: +** +** pA!=0 +** pA->isNull==0 +** pB!=0 +** pB->isNull==0 +*/ +static int decimal_cmp(const Decimal *pA, const Decimal *pB){ + int nASig, nBSig, rc, n; + if( pA->sign!=pB->sign ){ + return pA->sign ? -1 : +1; + } + if( pA->sign ){ + const Decimal *pTemp = pA; + pA = pB; + pB = pTemp; + } + nASig = pA->nDigit - pA->nFrac; + nBSig = pB->nDigit - pB->nFrac; + if( nASig!=nBSig ){ + return nASig - nBSig; + } + n = pA->nDigit; + if( n>pB->nDigit ) n = pB->nDigit; + rc = memcmp(pA->a, pB->a, n); + if( rc==0 ){ + rc = pA->nDigit - pB->nDigit; + } + return rc; +} + +/* +** SQL Function: decimal_cmp(X, Y) +** +** Return negative, zero, or positive if X is less then, equal to, or +** greater than Y. +*/ +static void decimalCmpFunc( + sqlite3_context *context, + int argc, + sqlite3_value **argv +){ + Decimal *pA = 0, *pB = 0; + int rc; + + UNUSED_PARAMETER(argc); + pA = decimal_new(context, argv[0], 0, 0); + if( pA==0 || pA->isNull ) goto cmp_done; + pB = decimal_new(context, argv[1], 0, 0); + if( pB==0 || pB->isNull ) goto cmp_done; + rc = decimal_cmp(pA, pB); + if( rc<0 ) rc = -1; + else if( rc>0 ) rc = +1; + sqlite3_result_int(context, rc); +cmp_done: + decimal_free(pA); + decimal_free(pB); +} + +/* +** Expand the Decimal so that it has a least nDigit digits and nFrac +** digits to the right of the decimal point. +*/ +static void decimal_expand(Decimal *p, int nDigit, int nFrac){ + int nAddSig; + int nAddFrac; + if( p==0 ) return; + nAddFrac = nFrac - p->nFrac; + nAddSig = (nDigit - p->nDigit) - nAddFrac; + if( nAddFrac==0 && nAddSig==0 ) return; + p->a = sqlite3_realloc64(p->a, nDigit+1); + if( p->a==0 ){ + p->oom = 1; + return; + } + if( nAddSig ){ + memmove(p->a+nAddSig, p->a, p->nDigit); + memset(p->a, 0, nAddSig); + p->nDigit += nAddSig; + } + if( nAddFrac ){ + memset(p->a+p->nDigit, 0, nAddFrac); + p->nDigit += nAddFrac; + p->nFrac += nAddFrac; + } +} + +/* +** Add the value pB into pA. +** +** Both pA and pB might become denormalized by this routine. +*/ +static void decimal_add(Decimal *pA, Decimal *pB){ + int nSig, nFrac, nDigit; + int i, rc; + if( pA==0 ){ + return; + } + if( pA->oom || pB==0 || pB->oom ){ + pA->oom = 1; + return; + } + if( pA->isNull || pB->isNull ){ + pA->isNull = 1; + return; + } + nSig = pA->nDigit - pA->nFrac; + if( nSig && pA->a[0]==0 ) nSig--; + if( nSignDigit-pB->nFrac ){ + nSig = pB->nDigit - pB->nFrac; + } + nFrac = pA->nFrac; + if( nFracnFrac ) nFrac = pB->nFrac; + nDigit = nSig + nFrac + 1; + decimal_expand(pA, nDigit, nFrac); + decimal_expand(pB, nDigit, nFrac); + if( pA->oom || pB->oom ){ + pA->oom = 1; + }else{ + if( pA->sign==pB->sign ){ + int carry = 0; + for(i=nDigit-1; i>=0; i--){ + int x = pA->a[i] + pB->a[i] + carry; + if( x>=10 ){ + carry = 1; + pA->a[i] = x - 10; + }else{ + carry = 0; + pA->a[i] = x; + } + } + }else{ + signed char *aA, *aB; + int borrow = 0; + rc = memcmp(pA->a, pB->a, nDigit); + if( rc<0 ){ + aA = pB->a; + aB = pA->a; + pA->sign = !pA->sign; + }else{ + aA = pA->a; + aB = pB->a; + } + for(i=nDigit-1; i>=0; i--){ + int x = aA[i] - aB[i] - borrow; + if( x<0 ){ + pA->a[i] = x+10; + borrow = 1; + }else{ + pA->a[i] = x; + borrow = 0; + } + } + } + } +} + +/* +** Compare text in decimal order. +*/ +static int decimalCollFunc( + void *notUsed, + int nKey1, const void *pKey1, + int nKey2, const void *pKey2 +){ + const unsigned char *zA = (const unsigned char*)pKey1; + const unsigned char *zB = (const unsigned char*)pKey2; + Decimal *pA = decimal_new(0, 0, nKey1, zA); + Decimal *pB = decimal_new(0, 0, nKey2, zB); + int rc; + UNUSED_PARAMETER(notUsed); + if( pA==0 || pB==0 ){ + rc = 0; + }else{ + rc = decimal_cmp(pA, pB); + } + decimal_free(pA); + decimal_free(pB); + return rc; +} + + +/* +** SQL Function: decimal_add(X, Y) +** decimal_sub(X, Y) +** +** Return the sum or difference of X and Y. +*/ +static void decimalAddFunc( + sqlite3_context *context, + int argc, + sqlite3_value **argv +){ + Decimal *pA = decimal_new(context, argv[0], 0, 0); + Decimal *pB = decimal_new(context, argv[1], 0, 0); + UNUSED_PARAMETER(argc); + decimal_add(pA, pB); + decimal_result(context, pA); + decimal_free(pA); + decimal_free(pB); +} +static void decimalSubFunc( + sqlite3_context *context, + int argc, + sqlite3_value **argv +){ + Decimal *pA = decimal_new(context, argv[0], 0, 0); + Decimal *pB = decimal_new(context, argv[1], 0, 0); + UNUSED_PARAMETER(argc); + if( pB ){ + pB->sign = !pB->sign; + decimal_add(pA, pB); + decimal_result(context, pA); + } + decimal_free(pA); + decimal_free(pB); +} + +/* Aggregate funcion: decimal_sum(X) +** +** Works like sum() except that it uses decimal arithmetic for unlimited +** precision. +*/ +static void decimalSumStep( + sqlite3_context *context, + int argc, + sqlite3_value **argv +){ + Decimal *p; + Decimal *pArg; + UNUSED_PARAMETER(argc); + p = sqlite3_aggregate_context(context, sizeof(*p)); + if( p==0 ) return; + if( !p->isInit ){ + p->isInit = 1; + p->a = sqlite3_malloc(2); + if( p->a==0 ){ + p->oom = 1; + }else{ + p->a[0] = 0; + } + p->nDigit = 1; + p->nFrac = 0; + } + if( sqlite3_value_type(argv[0])==SQLITE_NULL ) return; + pArg = decimal_new(context, argv[0], 0, 0); + decimal_add(p, pArg); + decimal_free(pArg); +} +static void decimalSumInverse( + sqlite3_context *context, + int argc, + sqlite3_value **argv +){ + Decimal *p; + Decimal *pArg; + UNUSED_PARAMETER(argc); + p = sqlite3_aggregate_context(context, sizeof(*p)); + if( p==0 ) return; + if( sqlite3_value_type(argv[0])==SQLITE_NULL ) return; + pArg = decimal_new(context, argv[0], 0, 0); + if( pArg ) pArg->sign = !pArg->sign; + decimal_add(p, pArg); + decimal_free(pArg); +} +static void decimalSumValue(sqlite3_context *context){ + Decimal *p = sqlite3_aggregate_context(context, 0); + if( p==0 ) return; + decimal_result(context, p); +} +static void decimalSumFinalize(sqlite3_context *context){ + Decimal *p = sqlite3_aggregate_context(context, 0); + if( p==0 ) return; + decimal_result(context, p); + decimal_clear(p); +} + +/* +** SQL Function: decimal_mul(X, Y) +** +** Return the product of X and Y. +** +** All significant digits after the decimal point are retained. +** Trailing zeros after the decimal point are omitted as long as +** the number of digits after the decimal point is no less than +** either the number of digits in either input. +*/ +static void decimalMulFunc( + sqlite3_context *context, + int argc, + sqlite3_value **argv +){ + Decimal *pA = decimal_new(context, argv[0], 0, 0); + Decimal *pB = decimal_new(context, argv[1], 0, 0); + signed char *acc = 0; + int i, j, k; + int minFrac; + UNUSED_PARAMETER(argc); + if( pA==0 || pA->oom || pA->isNull + || pB==0 || pB->oom || pB->isNull + ){ + goto mul_end; + } + acc = sqlite3_malloc64( pA->nDigit + pB->nDigit + 2 ); + if( acc==0 ){ + sqlite3_result_error_nomem(context); + goto mul_end; + } + memset(acc, 0, pA->nDigit + pB->nDigit + 2); + minFrac = pA->nFrac; + if( pB->nFracnFrac; + for(i=pA->nDigit-1; i>=0; i--){ + signed char f = pA->a[i]; + int carry = 0, x; + for(j=pB->nDigit-1, k=i+j+3; j>=0; j--, k--){ + x = acc[k] + f*pB->a[j] + carry; + acc[k] = x%10; + carry = x/10; + } + x = acc[k] + carry; + acc[k] = x%10; + acc[k-1] += x/10; + } + sqlite3_free(pA->a); + pA->a = acc; + acc = 0; + pA->nDigit += pB->nDigit + 2; + pA->nFrac += pB->nFrac; + pA->sign ^= pB->sign; + while( pA->nFrac>minFrac && pA->a[pA->nDigit-1]==0 ){ + pA->nFrac--; + pA->nDigit--; + } + decimal_result(context, pA); + +mul_end: + sqlite3_free(acc); + decimal_free(pA); + decimal_free(pB); +} + +#ifdef _WIN32 + +#endif +int sqlite3_decimal_init( + sqlite3 *db, + char **pzErrMsg, + const sqlite3_api_routines *pApi +){ + int rc = SQLITE_OK; + static const struct { + const char *zFuncName; + int nArg; + void (*xFunc)(sqlite3_context*,int,sqlite3_value**); + } aFunc[] = { + { "decimal", 1, decimalFunc }, + { "decimal_cmp", 2, decimalCmpFunc }, + { "decimal_add", 2, decimalAddFunc }, + { "decimal_sub", 2, decimalSubFunc }, + { "decimal_mul", 2, decimalMulFunc }, + }; + unsigned int i; + (void)pzErrMsg; /* Unused parameter */ + + SQLITE_EXTENSION_INIT2(pApi); + + for(i=0; i 'ieee754(2,0)' +** ieee754(45.25) -> 'ieee754(181,-2)' +** ieee754(2, 0) -> 2.0 +** ieee754(181, -2) -> 45.25 +** +** Two additional functions break apart the one-argument ieee754() +** result into separate integer values: +** +** ieee754_mantissa(45.25) -> 181 +** ieee754_exponent(45.25) -> -2 +** +** These functions convert binary64 numbers into blobs and back again. +** +** ieee754_from_blob(x'3ff0000000000000') -> 1.0 +** ieee754_to_blob(1.0) -> x'3ff0000000000000' +** +** In all single-argument functions, if the argument is an 8-byte blob +** then that blob is interpreted as a big-endian binary64 value. +** +** +** EXACT DECIMAL REPRESENTATION OF BINARY64 VALUES +** ----------------------------------------------- +** +** This extension in combination with the separate 'decimal' extension +** can be used to compute the exact decimal representation of binary64 +** values. To begin, first compute a table of exponent values: +** +** CREATE TABLE pow2(x INTEGER PRIMARY KEY, v TEXT); +** WITH RECURSIVE c(x,v) AS ( +** VALUES(0,'1') +** UNION ALL +** SELECT x+1, decimal_mul(v,'2') FROM c WHERE x+1<=971 +** ) INSERT INTO pow2(x,v) SELECT x, v FROM c; +** WITH RECURSIVE c(x,v) AS ( +** VALUES(-1,'0.5') +** UNION ALL +** SELECT x-1, decimal_mul(v,'0.5') FROM c WHERE x-1>=-1075 +** ) INSERT INTO pow2(x,v) SELECT x, v FROM c; +** +** Then, to compute the exact decimal representation of a floating +** point value (the value 47.49 is used in the example) do: +** +** WITH c(n) AS (VALUES(47.49)) +** ---------------^^^^^---- Replace with whatever you want +** SELECT decimal_mul(ieee754_mantissa(c.n),pow2.v) +** FROM pow2, c WHERE pow2.x=ieee754_exponent(c.n); +** +** Here is a query to show various boundry values for the binary64 +** number format: +** +** WITH c(name,bin) AS (VALUES +** ('minimum positive value', x'0000000000000001'), +** ('maximum subnormal value', x'000fffffffffffff'), +** ('mininum positive nornal value', x'0010000000000000'), +** ('maximum value', x'7fefffffffffffff')) +** SELECT c.name, decimal_mul(ieee754_mantissa(c.bin),pow2.v) +** FROM pow2, c WHERE pow2.x=ieee754_exponent(c.bin); +** +*/ +/* #include "sqlite3ext.h" */ +SQLITE_EXTENSION_INIT1 +#include +#include + +/* Mark a function parameter as unused, to suppress nuisance compiler +** warnings. */ +#ifndef UNUSED_PARAMETER +# define UNUSED_PARAMETER(X) (void)(X) +#endif + +/* +** Implementation of the ieee754() function +*/ +static void ieee754func( + sqlite3_context *context, + int argc, + sqlite3_value **argv +){ + if( argc==1 ){ + sqlite3_int64 m, a; + double r; + int e; + int isNeg; + char zResult[100]; + assert( sizeof(m)==sizeof(r) ); + if( sqlite3_value_type(argv[0])==SQLITE_BLOB + && sqlite3_value_bytes(argv[0])==sizeof(r) + ){ + const unsigned char *x = sqlite3_value_blob(argv[0]); + unsigned int i; + sqlite3_uint64 v = 0; + for(i=0; i>52; + m = a & ((((sqlite3_int64)1)<<52)-1); + if( e==0 ){ + m <<= 1; + }else{ + m |= ((sqlite3_int64)1)<<52; + } + while( e<1075 && m>0 && (m&1)==0 ){ + m >>= 1; + e++; + } + if( isNeg ) m = -m; + } + switch( *(int*)sqlite3_user_data(context) ){ + case 0: + sqlite3_snprintf(sizeof(zResult), zResult, "ieee754(%lld,%d)", + m, e-1075); + sqlite3_result_text(context, zResult, -1, SQLITE_TRANSIENT); + break; + case 1: + sqlite3_result_int64(context, m); + break; + case 2: + sqlite3_result_int(context, e-1075); + break; + } + }else{ + sqlite3_int64 m, e, a; + double r; + int isNeg = 0; + m = sqlite3_value_int64(argv[0]); + e = sqlite3_value_int64(argv[1]); + + /* Limit the range of e. Ticket 22dea1cfdb9151e4 2021-03-02 */ + if( e>10000 ){ + e = 10000; + }else if( e<-10000 ){ + e = -10000; + } + + if( m<0 ){ + isNeg = 1; + m = -m; + if( m<0 ) return; + }else if( m==0 && e>-1000 && e<1000 ){ + sqlite3_result_double(context, 0.0); + return; + } + while( (m>>32)&0xffe00000 ){ + m >>= 1; + e++; + } + while( m!=0 && ((m>>32)&0xfff00000)==0 ){ + m <<= 1; + e--; + } + e += 1075; + if( e<=0 ){ + /* Subnormal */ + m >>= 1-e; + e = 0; + }else if( e>0x7ff ){ + e = 0x7ff; + } + a = m & ((((sqlite3_int64)1)<<52)-1); + a |= e<<52; + if( isNeg ) a |= ((sqlite3_uint64)1)<<63; + memcpy(&r, &a, sizeof(r)); + sqlite3_result_double(context, r); + } +} + +/* +** Functions to convert between blobs and floats. +*/ +static void ieee754func_from_blob( + sqlite3_context *context, + int argc, + sqlite3_value **argv +){ + UNUSED_PARAMETER(argc); + if( sqlite3_value_type(argv[0])==SQLITE_BLOB + && sqlite3_value_bytes(argv[0])==sizeof(double) + ){ + double r; + const unsigned char *x = sqlite3_value_blob(argv[0]); + unsigned int i; + sqlite3_uint64 v = 0; + for(i=0; i>= 8; + } + sqlite3_result_blob(context, a, sizeof(r), SQLITE_TRANSIENT); + } +} + + +#ifdef _WIN32 + +#endif +int sqlite3_ieee_init( + sqlite3 *db, + char **pzErrMsg, + const sqlite3_api_routines *pApi +){ + static const struct { + char *zFName; + int nArg; + int iAux; + void (*xFunc)(sqlite3_context*,int,sqlite3_value**); + } aFunc[] = { + { "ieee754", 1, 0, ieee754func }, + { "ieee754", 2, 0, ieee754func }, + { "ieee754_mantissa", 1, 1, ieee754func }, + { "ieee754_exponent", 1, 2, ieee754func }, + { "ieee754_to_blob", 1, 0, ieee754func_to_blob }, + { "ieee754_from_blob", 1, 0, ieee754func_from_blob }, + + }; + unsigned int i; + int rc = SQLITE_OK; + SQLITE_EXTENSION_INIT2(pApi); + (void)pzErrMsg; /* Unused parameter */ + for(i=0; i