ADDED .fossil-settings/empty-dirs
Index: .fossil-settings/empty-dirs
==================================================================
--- /dev/null
+++ .fossil-settings/empty-dirs
@@ -0,0 +1,1 @@
+compat
ADDED .fossil-settings/ignore-glob
Index: .fossil-settings/ignore-glob
==================================================================
--- /dev/null
+++ .fossil-settings/ignore-glob
@@ -0,0 +1,1 @@
+compat/*
Index: Makefile.in
==================================================================
--- Makefile.in
+++ Makefile.in
@@ -992,11 +992,16 @@
# Source files that go into making shell.c
SHELL_SRC = \
$(TOP)/src/shell.c.in \
$(TOP)/ext/misc/shathree.c \
$(TOP)/ext/misc/fileio.c \
- $(TOP)/ext/misc/completion.c
+ $(TOP)/ext/misc/completion.c \
+ $(TOP)/ext/misc/sqlar.c \
+ $(TOP)/ext/expert/sqlite3expert.c \
+ $(TOP)/ext/expert/sqlite3expert.h \
+ $(TOP)/ext/misc/zipfile.c \
+ $(TOP)/src/test_windirent.c
shell.c: $(SHELL_SRC) $(TOP)/tool/mkshellc.tcl
$(TCLSH_CMD) $(TOP)/tool/mkshellc.tcl >shell.c
Index: Makefile.msc
==================================================================
--- Makefile.msc
+++ Makefile.msc
@@ -90,10 +90,33 @@
!IFNDEF SPLIT_AMALGAMATION
SPLIT_AMALGAMATION = 0
!ENDIF
# <>
+# Set this non-0 to have this makefile assume the Tcl shell executable
+# (tclsh*.exe) is available in the PATH. By default, this is disabled
+# for compatibility with older build environments. This setting only
+# applies if TCLSH_CMD is not set manually.
+#
+!IFNDEF USE_TCLSH_IN_PATH
+USE_TCLSH_IN_PATH = 0
+!ENDIF
+
+# Set this non-0 to use zlib, possibly compiling it from source code.
+#
+!IFNDEF USE_ZLIB
+USE_ZLIB = 0
+!ENDIF
+
+# Set this non-0 to build zlib from source code. This is enabled by
+# default and in that case it will be assumed that the ZLIBDIR macro
+# points to the top-level source code directory for zlib.
+#
+!IFNDEF BUILD_ZLIB
+BUILD_ZLIB = 1
+!ENDIF
+
# Set this non-0 to use the International Components for Unicode (ICU).
#
!IFNDEF USE_ICU
USE_ICU = 0
!ENDIF
@@ -610,10 +633,19 @@
!ELSE
SHELL_CORE_DEP =
!ENDIF
!ENDIF
+# <>
+# If zlib support is enabled, add the dependencies for it.
+#
+!IF $(USE_ZLIB)!=0 && $(BUILD_ZLIB)!=0
+SHELL_CORE_DEP = zlib $(SHELL_CORE_DEP)
+TESTFIXTURE_DEP = zlib $(TESTFIXTURE_DEP)
+!ENDIF
+# <>
+
# This is the core library that the shell executable should link with.
#
!IFNDEF SHELL_CORE_LIB
!IF $(DYNAMIC_SHELL)!=0 || $(FOR_WIN10)!=0
SHELL_CORE_LIB = $(SQLITE3LIB)
@@ -800,16 +832,20 @@
# non-stubs enabled programs using Tcl must link against. These variables
# (TCLINCDIR, TCLLIBDIR, and LIBTCL) may be overridden via the environment
# prior to running nmake in order to match the actual installed location and
# version on this machine.
#
+!IFNDEF TCLDIR
+TCLDIR = $(TOP)\compat\tcl
+!ENDIF
+
!IFNDEF TCLINCDIR
-TCLINCDIR = c:\tcl\include
+TCLINCDIR = $(TCLDIR)\include
!ENDIF
!IFNDEF TCLLIBDIR
-TCLLIBDIR = c:\tcl\lib
+TCLLIBDIR = $(TCLDIR)\lib
!ENDIF
!IFNDEF LIBTCL
LIBTCL = tcl86.lib
!ENDIF
@@ -817,24 +853,53 @@
!IFNDEF LIBTCLSTUB
LIBTCLSTUB = tclstub86.lib
!ENDIF
!IFNDEF LIBTCLPATH
-LIBTCLPATH = c:\tcl\bin
+LIBTCLPATH = $(TCLDIR)\bin
+!ENDIF
+
+# The locations of the zlib header and library files. These variables
+# (ZLIBINCDIR, ZLIBLIBDIR, and ZLIBLIB) may be overridden via the environment
+# prior to running nmake in order to match the actual installed (or source
+# code) location on this machine.
+#
+!IFNDEF ZLIBDIR
+ZLIBDIR = $(TOP)\compat\zlib
+!ENDIF
+
+!IFNDEF ZLIBINCDIR
+ZLIBINCDIR = $(ZLIBDIR)
+!ENDIF
+
+!IFNDEF ZLIBLIBDIR
+ZLIBLIBDIR = $(ZLIBDIR)
+!ENDIF
+
+!IFNDEF ZLIBLIB
+!IF $(DYNAMIC_SHELL)!=0
+ZLIBLIB = zdll.lib
+!ELSE
+ZLIBLIB = zlib.lib
+!ENDIF
!ENDIF
# The locations of the ICU header and library files. These variables
# (ICUINCDIR, ICULIBDIR, and LIBICU) may be overridden via the environment
# prior to running nmake in order to match the actual installed location on
# this machine.
#
+!IFNDEF ICUDIR
+ICUDIR = $(TOP)\compat\icu
+!ENDIF
+
!IFNDEF ICUINCDIR
-ICUINCDIR = c:\icu\include
+ICUINCDIR = $(ICUDIR)\include
!ENDIF
!IFNDEF ICULIBDIR
-ICULIBDIR = c:\icu\lib
+ICULIBDIR = $(ICUDIR)\lib
!ENDIF
!IFNDEF LIBICU
LIBICU = icuuc.lib icuin.lib
!ENDIF
@@ -843,11 +908,15 @@
# know the specific version we want to use. This variable (TCLSH_CMD) may be
# overridden via the environment prior to running nmake in order to select a
# specific Tcl shell to use.
#
!IFNDEF TCLSH_CMD
+!IF $(USE_TCLSH_IN_PATH)!=0 || !EXIST("$(TCLDIR)\bin\tclsh.exe")
TCLSH_CMD = tclsh
+!ELSE
+TCLSH_CMD = $(TCLDIR)\bin\tclsh.exe
+!ENDIF
!ENDIF
# <>
# Compiler options needed for programs that use the readline() library.
#
@@ -949,10 +1018,19 @@
TCC = $(TCC) -Zi
BCC = $(BCC) -Zi
!ENDIF
# <>
+# If zlib support is enabled, add the compiler options for it.
+#
+!IF $(USE_ZLIB)!=0
+TCC = $(TCC) -DSQLITE_HAVE_ZLIB=1
+RCC = $(RCC) -DSQLITE_HAVE_ZLIB=1
+TCC = $(TCC) -I$(ZLIBINCDIR)
+RCC = $(RCC) -I$(ZLIBINCDIR)
+!ENDIF
+
# If ICU support is enabled, add the compiler options for it.
#
!IF $(USE_ICU)!=0
TCC = $(TCC) -DSQLITE_ENABLE_ICU=1
RCC = $(RCC) -DSQLITE_ENABLE_ICU=1
@@ -1069,12 +1147,19 @@
# <>
# Start with the Tcl related linker options.
#
!IF $(NO_TCL)==0
-LTLIBPATHS = /LIBPATH:$(TCLLIBDIR)
-LTLIBS = $(LTLIBS) $(LIBTCL)
+TCLLIBPATHS = $(TCLLIBPATHS) /LIBPATH:$(TCLLIBDIR)
+TCLLIBS = $(TCLLIBS) $(LIBTCL)
+!ENDIF
+
+# If zlib support is enabled, add the linker options for it.
+#
+!IF $(USE_ZLIB)!=0
+LTLIBPATHS = $(LTLIBPATHS) /LIBPATH:$(ZLIBLIBDIR)
+LTLIBS = $(LTLIBS) $(ZLIBLIB)
!ENDIF
# If ICU support is enabled, add the linker options for it.
#
!IF $(USE_ICU)!=0
@@ -1421,10 +1506,16 @@
$(TOP)\ext\misc\series.c \
$(TOP)\ext\misc\spellfix.c \
$(TOP)\ext\misc\totype.c \
$(TOP)\ext\misc\unionvtab.c \
$(TOP)\ext\misc\wholenumber.c
+
+# If use of zlib is enabled, add the "zipfile.c" source file.
+#
+!IF $(USE_ZLIB)!=0
+TESTEXT = $(TESTEXT) $(TOP)\ext\misc\zipfile.c
+!ENDIF
# Source code to the library files needed by the test fixture
# (non-amalgamation)
#
TESTSRC2 = \
@@ -1543,11 +1634,19 @@
# <>
# This is the default Makefile target. The objects listed here
# are what get build when you type just "make" with no arguments.
#
-all: dll libsqlite3.lib shell $(ALL_TCL_TARGETS)
+core: dll libsqlite3.lib shell
+
+# Targets that require the Tcl library.
+#
+tcl: $(ALL_TCL_TARGETS)
+
+# This Makefile target builds all of the standard binaries.
+#
+all: core tcl
# Dynamic link library section.
#
dll: $(SQLITE3DLL)
@@ -1938,11 +2037,11 @@
tclsqlite-shell.lo: $(TOP)\src\tclsqlite.c $(HDR) $(SQLITE_TCL_DEP)
$(LTCOMPILE) $(NO_WARN) -DTCLSH -DBUILD_sqlite -I$(TCLINCDIR) -c $(TOP)\src\tclsqlite.c
tclsqlite3.exe: tclsqlite-shell.lo $(SQLITE3C) $(SQLITE3H) $(LIBRESOBJS)
- $(LTLINK) $(SQLITE3C) /link $(LDFLAGS) $(LTLINKOPTS) $(LTLIBPATHS) /OUT:$@ tclsqlite-shell.lo $(LIBRESOBJS) $(LTLIBS) $(TLIBS)
+ $(LTLINK) $(SQLITE3C) /link $(LDFLAGS) $(LTLINKOPTS) $(TCLLIBPATHS) $(LTLIBPATHS) /OUT:$@ tclsqlite-shell.lo $(LIBRESOBJS) $(TCLLIBS) $(LTLIBS) $(TLIBS)
# Rules to build opcodes.c and opcodes.h
#
opcodes.c: opcodes.h $(TOP)\tool\mkopcodec.tcl
$(TCLSH_CMD) $(TOP)\tool\mkopcodec.tcl opcodes.h > opcodes.c
@@ -1983,15 +2082,27 @@
# Source files that go into making shell.c
SHELL_SRC = \
$(TOP)\src\shell.c.in \
$(TOP)\ext\misc\shathree.c \
$(TOP)\ext\misc\fileio.c \
- $(TOP)\ext\misc\completion.c
+ $(TOP)\ext\misc\completion.c \
+ $(TOP)\ext\expert\sqlite3expert.c \
+ $(TOP)\ext\expert\sqlite3expert.h \
+ $(TOP)\src\test_windirent.c
+
+# If use of zlib is enabled, add the "zipfile.c" source file.
+#
+!IF $(USE_ZLIB)!=0
+SHELL_SRC = $(SHELL_SRC) $(TOP)\ext\misc\sqlar.c
+SHELL_SRC = $(SHELL_SRC) $(TOP)\ext\misc\zipfile.c
+!ENDIF
shell.c: $(SHELL_SRC) $(TOP)\tool\mkshellc.tcl
$(TCLSH_CMD) $(TOP)\tool\mkshellc.tcl > shell.c
+zlib:
+ pushd $(ZLIBDIR) && $(MAKE) /f win32\Makefile.msc clean $(ZLIBLIB) && popd
# Rules to build the extension objects.
#
icu.lo: $(TOP)\ext\icu\icu.c $(HDR) $(EXTHDR)
$(LTCOMPILE) $(CORE_COMPILE_OPTS) $(NO_WARN) -DSQLITE_CORE -c $(TOP)\ext\icu\icu.c
@@ -2165,15 +2276,15 @@
| $(TCLSH_CMD) $(TOP)\tool\replace.tcl exact "void (*freeProc)" "void (SQLITE_TCLAPI *freeProc)" \
| $(TCLSH_CMD) $(TOP)\tool\replace.tcl exact "Tcl_HashEntry *(*findProc)" "Tcl_HashEntry *(SQLITE_TCLAPI *findProc)" \
| $(TCLSH_CMD) $(TOP)\tool\replace.tcl exact "Tcl_HashEntry *(*createProc)" "Tcl_HashEntry *(SQLITE_TCLAPI *createProc)" >> $(SQLITETCLH)
!ENDIF
-testfixture.exe: $(TESTFIXTURE_SRC) $(SQLITE3H) $(LIBRESOBJS) $(HDR) $(SQLITE_TCL_DEP)
+testfixture.exe: $(TESTFIXTURE_SRC) $(TESTFIXTURE_DEP) $(SQLITE3H) $(LIBRESOBJS) $(HDR) $(SQLITE_TCL_DEP)
$(LTLINK) -DSQLITE_NO_SYNC=1 $(TESTFIXTURE_FLAGS) \
-DBUILD_sqlite -I$(TCLINCDIR) \
$(TESTFIXTURE_SRC) \
- /link $(LDFLAGS) $(LTLINKOPTS) $(LTLIBPATHS) $(LIBRESOBJS) $(LTLIBS) $(TLIBS)
+ /link $(LDFLAGS) $(LTLINKOPTS) $(TCLLIBPATHS) $(LTLIBPATHS) $(LIBRESOBJS) $(TCLLIBS) $(LTLIBS) $(TLIBS)
extensiontest: testfixture.exe testloadext.dll
@set PATH=$(LIBTCLPATH);$(PATH)
.\testfixture.exe $(TOP)\test\loadext.test $(TESTOPTS)
@@ -2219,11 +2330,11 @@
sqlite3_analyzer.c: $(SQLITE3C) $(SQLITE3H) $(TOP)\src\tclsqlite.c $(TOP)\tool\spaceanal.tcl $(TOP)\tool\mkccode.tcl $(TOP)\tool\sqlite3_analyzer.c.in $(SQLITE_TCL_DEP)
$(TCLSH_CMD) $(TOP)\tool\mkccode.tcl $(TOP)\tool\sqlite3_analyzer.c.in > $@
sqlite3_analyzer.exe: sqlite3_analyzer.c $(LIBRESOBJS)
$(LTLINK) $(NO_WARN) -DBUILD_sqlite -I$(TCLINCDIR) sqlite3_analyzer.c \
- /link $(LDFLAGS) $(LTLINKOPTS) $(LTLIBPATHS) $(LIBRESOBJS) $(LTLIBS) $(TLIBS)
+ /link $(LDFLAGS) $(LTLINKOPTS) $(TCLLIBPATHS) $(LTLIBPATHS) $(LIBRESOBJS) $(TCLLIBS) $(LTLIBS) $(TLIBS)
sqlite3_expert.exe: $(SQLITE3C) $(TOP)\ext\expert\sqlite3expert.h $(TOP)\ext\expert\sqlite3expert.c $(TOP)\ext\expert\expert.c
$(LTLINK) $(NO_WARN) $(TOP)\ext\expert\sqlite3expert.c $(TOP)\ext\expert\expert.c $(SQLITE3C) $(TLIBS)
CHECKER_DEPS =\
Index: autoconf/Makefile.msc
==================================================================
--- autoconf/Makefile.msc
+++ autoconf/Makefile.msc
@@ -558,10 +558,11 @@
SHELL_CORE_DEP = $(SQLITE3DLL)
!ELSE
SHELL_CORE_DEP =
!ENDIF
!ENDIF
+
# This is the core library that the shell executable should link with.
#
!IFNDEF SHELL_CORE_LIB
!IF $(DYNAMIC_SHELL)!=0 || $(FOR_WIN10)!=0
Index: ext/fts5/fts5_tcl.c
==================================================================
--- ext/fts5/fts5_tcl.c
+++ ext/fts5/fts5_tcl.c
@@ -431,11 +431,11 @@
}
CASE(15, "xGetAuxdataInt") {
int iVal;
int bClear;
if( Tcl_GetBooleanFromObj(interp, objv[2], &bClear) ) return TCL_ERROR;
- iVal = ((char*)p->pApi->xGetAuxdata(p->pFts, bClear) - (char*)0);
+ iVal = (int)((char*)p->pApi->xGetAuxdata(p->pFts, bClear) - (char*)0);
Tcl_SetObjResult(interp, Tcl_NewIntObj(iVal));
break;
}
CASE(16, "xPhraseForeach") {
Index: ext/misc/fileio.c
==================================================================
--- ext/misc/fileio.c
+++ ext/misc/fileio.c
@@ -9,15 +9,124 @@
** May you share freely, never taking more than you give.
**
******************************************************************************
**
** This SQLite extension implements SQL functions readfile() and
-** writefile().
+** 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
+#else
+# include "windows.h"
+# include
+# include
+# include "test_windirent.h"
+# define dirent DIRENT
+# define timespec TIMESPEC
+# define stat _stat
+# define mkdir(path,mode) _mkdir(path)
+# define lstat(path,buf) _stat(path,buf)
+#endif
+#include
+#include
+
+
+#define FSDIR_SCHEMA "(name,mode,mtime,data,path HIDDEN,dir HIDDEN)"
+
+/*
+** Set the result stored by context ctx to a blob containing the
+** contents of file zName.
+*/
+static void readFileContents(sqlite3_context *ctx, const char *zName){
+ FILE *in;
+ long nIn;
+ void *pBuf;
+
+ in = fopen(zName, "rb");
+ if( in==0 ) return;
+ fseek(in, 0, SEEK_END);
+ nIn = ftell(in);
+ rewind(in);
+ pBuf = sqlite3_malloc( nIn );
+ if( pBuf && 1==fread(pBuf, nIn, 1, in) ){
+ sqlite3_result_blob(ctx, pBuf, nIn, sqlite3_free);
+ }else{
+ 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.
@@ -26,57 +135,600 @@
sqlite3_context *context,
int argc,
sqlite3_value **argv
){
const char *zName;
- FILE *in;
- long nIn;
- void *pBuf;
-
(void)(argc); /* Unused parameter */
zName = (const char*)sqlite3_value_text(argv[0]);
if( zName==0 ) return;
- in = fopen(zName, "rb");
- if( in==0 ) return;
- fseek(in, 0, SEEK_END);
- nIn = ftell(in);
- rewind(in);
- pBuf = sqlite3_malloc( nIn );
- if( pBuf && 1==fread(pBuf, nIn, 1, in) ){
- sqlite3_result_blob(context, pBuf, nIn, sqlite3_free);
+ 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);
+}
+
+/*
+** 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 to (mode&0777).
+**
+** 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,
+ mode_t mode
+){
+ char *zCopy = sqlite3_mprintf("%s", zFile);
+ int rc = SQLITE_OK;
+
+ if( zCopy==0 ){
+ rc = SQLITE_NOMEM;
}else{
- sqlite3_free(pBuf);
+ 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) && !defined(WIN32)
+ 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
+ FILETIME lastAccess;
+ FILETIME lastWrite;
+ SYSTEMTIME currentTime;
+ LONGLONG intervals;
+ HANDLE hFile;
+ GetSystemTime(¤tTime);
+ SystemTimeToFileTime(¤tTime, &lastAccess);
+ intervals = Int32x32To64(mtime, 10000000) + 116444736000000000;
+ lastWrite.dwLowDateTime = (DWORD)intervals;
+ lastWrite.dwHighDateTime = intervals >> 32;
+ hFile = CreateFile(
+ zFile, FILE_WRITE_ATTRIBUTES, 0, NULL, OPEN_EXISTING,
+ FILE_FLAG_BACKUP_SEMANTICS, NULL
+ );
+ if( hFile!=INVALID_HANDLE_VALUE ){
+ BOOL bResult = SetFileTime(hFile, NULL, &lastAccess, &lastWrite);
+ CloseHandle(hFile);
+ return !bResult;
+ }else{
+ return 1;
+ }
+#endif
}
- fclose(in);
+
+ return 0;
}
/*
-** Implementation of the "writefile(X,Y)" SQL function. The argument Y
-** is written into file X. The number of bytes written is returned. Or
-** NULL is returned if something goes wrong, such as being unable to open
-** file X for writing.
+** 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
){
- FILE *out;
- const char *z;
- sqlite3_int64 rc;
const char *zFile;
+ mode_t mode = 0;
+ int res;
+ sqlite3_int64 mtime = -1;
- (void)(argc); /* Unused parameter */
+ 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;
- out = fopen(zFile, "wb");
- if( out==0 ) return;
- z = (const char*)sqlite3_value_blob(argv[1]);
- if( z==0 ){
- rc = 0;
+ 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, mode)==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);
+ }
+ }
+}
+
+#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;
+
+ 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));
+ }
+ *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;
+ 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);
+ pCur->aLvl = 0;
+ pCur->zPath = 0;
+ pCur->zBase = 0;
+ pCur->nBase = 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->aLvl);
+ 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;
+ int nByte = nNew*sizeof(FsdirLevel);
+ FsdirLevel *aNew = (FsdirLevel*)sqlite3_realloc(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( lstat(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 0: { /* name */
+ sqlite3_result_text(ctx, &pCur->zPath[pCur->nBase], -1, SQLITE_TRANSIENT);
+ break;
+ }
+
+ case 1: /* mode */
+ sqlite3_result_int64(ctx, pCur->sStat.st_mode);
+ break;
+
+ case 2: /* mtime */
+ sqlite3_result_int64(ctx, pCur->sStat.st_mtime);
+ break;
+
+ case 3: { /* 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;
+ int nBuf = 64;
+ int n;
+
+ while( 1 ){
+ n = readlink(pCur->zPath, aBuf, nBuf);
+ if( nzPath);
+ }
+ }
+ }
+ 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.
+*/
+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;
+
+ 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( lstat(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 bits in idxNum:
+**
+** (1) start = $value -- constraint exists
+** (2) stop = $value -- constraint exists
+** (4) step = $value -- constraint exists
+** (8) output in descending order
+*/
+static int fsdirBestIndex(
+ sqlite3_vtab *tab,
+ sqlite3_index_info *pIdxInfo
+){
+ int i; /* Loop over constraints */
+ int idx4 = -1;
+ int idx5 = -1;
+
+ const struct sqlite3_index_constraint *pConstraint;
+ pConstraint = pIdxInfo->aConstraint;
+ for(i=0; inConstraint; i++, pConstraint++){
+ if( pConstraint->usable==0 ) continue;
+ if( pConstraint->op!=SQLITE_INDEX_CONSTRAINT_EQ ) continue;
+ if( pConstraint->iColumn==4 ) idx4 = i;
+ if( pConstraint->iColumn==5 ) idx5 = i;
+ }
+
+ if( idx4<0 ){
+ pIdxInfo->idxNum = 0;
+ pIdxInfo->estimatedCost = (double)(((sqlite3_int64)1) << 50);
}else{
- rc = fwrite(z, 1, sqlite3_value_bytes(argv[1]), out);
+ pIdxInfo->aConstraintUsage[idx4].omit = 1;
+ pIdxInfo->aConstraintUsage[idx4].argvIndex = 1;
+ if( idx5>=0 ){
+ pIdxInfo->aConstraintUsage[idx5].omit = 1;
+ pIdxInfo->aConstraintUsage[idx5].argvIndex = 2;
+ pIdxInfo->idxNum = 2;
+ pIdxInfo->estimatedCost = 10.0;
+ }else{
+ pIdxInfo->idxNum = 1;
+ pIdxInfo->estimatedCost = 100.0;
+ }
}
- fclose(out);
- sqlite3_result_int64(context, rc);
+
+ 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 */
+ };
+
@@ -83,5 +735,11 @@
+ int rc = sqlite3_create_module(db, "fsdir", &fsdirModule, 0);
+ return rc;
+}
+#else /* SQLITE_OMIT_VIRTUALTABLE */
+# define fsdirRegister(x) SQLITE_OK
+#endif
#ifdef _WIN32
__declspec(dllexport)
#endif
int sqlite3_fileio_init(
@@ -93,10 +751,13 @@
SQLITE_EXTENSION_INIT2(pApi);
(void)pzErrMsg; /* Unused parameter */
rc = sqlite3_create_function(db, "readfile", 1, SQLITE_UTF8, 0,
readfileFunc, 0, 0);
if( rc==SQLITE_OK ){
- rc = sqlite3_create_function(db, "writefile", 2, SQLITE_UTF8, 0,
+ rc = sqlite3_create_function(db, "writefile", -1, SQLITE_UTF8, 0,
writefileFunc, 0, 0);
}
+ if( rc==SQLITE_OK ){
+ rc = fsdirRegister(db);
+ }
return rc;
}
ADDED ext/misc/sqlar.c
Index: ext/misc/sqlar.c
==================================================================
--- /dev/null
+++ ext/misc/sqlar.c
@@ -0,0 +1,113 @@
+/*
+** 2017-12-17
+**
+** 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.
+**
+******************************************************************************
+**
+** Utility functions sqlar_compress() and sqlar_uncompress(). Useful
+** for working with sqlar archives and used by the shell tool's built-in
+** sqlar support.
+*/
+#include "sqlite3ext.h"
+SQLITE_EXTENSION_INIT1
+#include
+
+/*
+** Implementation of the "sqlar_compress(X)" SQL function.
+**
+** If the type of X is SQLITE_BLOB, and compressing that blob using
+** zlib utility function compress() yields a smaller blob, return the
+** compressed blob. Otherwise, return a copy of X.
+*/
+static void sqlarCompressFunc(
+ sqlite3_context *context,
+ int argc,
+ sqlite3_value **argv
+){
+ assert( argc==1 );
+ if( sqlite3_value_type(argv[0])==SQLITE_BLOB ){
+ const Bytef *pData = sqlite3_value_blob(argv[0]);
+ uLong nData = sqlite3_value_bytes(argv[0]);
+ uLongf nOut = compressBound(nData);
+ Bytef *pOut;
+
+ pOut = (Bytef*)sqlite3_malloc(nOut);
+ if( pOut==0 ){
+ sqlite3_result_error_nomem(context);
+ return;
+ }else{
+ if( Z_OK!=compress(pOut, &nOut, pData, nData) ){
+ sqlite3_result_error(context, "error in compress()", -1);
+ }else if( nOut
+#include
+#include
+
+#include
+#include
+#include
+#if !defined(_WIN32) && !defined(WIN32)
+# include
+# include
+# include
+#else
+# include
+#endif
+#include
+#include
+
+#include
+
+#ifndef SQLITE_OMIT_VIRTUALTABLE
+
+#ifndef SQLITE_AMALGAMATION
+typedef sqlite3_int64 i64;
+typedef unsigned char u8;
+typedef unsigned short u16;
+typedef unsigned long u32;
+#define MIN(a,b) ((a)<(b) ? (a) : (b))
+#endif
+
+#define ZIPFILE_SCHEMA "CREATE TABLE y(" \
+ "name, /* Name of file in zip archive */" \
+ "mode, /* POSIX mode for file */" \
+ "mtime, /* Last modification time in seconds since epoch */" \
+ "sz, /* Size of object */" \
+ "data, /* Data stored in zip file (possibly compressed) */" \
+ "method, /* Compression method (integer) */" \
+ "f HIDDEN /* Name of zip file */" \
+");"
+
+#define ZIPFILE_F_COLUMN_IDX 6 /* Index of column "f" in the above */
+#define ZIPFILE_BUFFER_SIZE (64*1024)
+
+
+/*
+** Magic numbers used to read and write zip files.
+**
+** ZIPFILE_NEWENTRY_MADEBY:
+** Use this value for the "version-made-by" field in new zip file
+** entries. The upper byte indicates "unix", and the lower byte
+** indicates that the zip file matches pkzip specification 3.0.
+** This is what info-zip seems to do.
+**
+** ZIPFILE_NEWENTRY_REQUIRED:
+** Value for "version-required-to-extract" field of new entries.
+** Version 2.0 is required to support folders and deflate compression.
+**
+** ZIPFILE_NEWENTRY_FLAGS:
+** Value for "general-purpose-bit-flags" field of new entries. Bit
+** 11 means "utf-8 filename and comment".
+**
+** ZIPFILE_SIGNATURE_CDS:
+** First 4 bytes of a valid CDS record.
+**
+** ZIPFILE_SIGNATURE_LFH:
+** First 4 bytes of a valid LFH record.
+*/
+#define ZIPFILE_EXTRA_TIMESTAMP 0x5455
+#define ZIPFILE_NEWENTRY_MADEBY ((3<<8) + 30)
+#define ZIPFILE_NEWENTRY_REQUIRED 20
+#define ZIPFILE_NEWENTRY_FLAGS 0x800
+#define ZIPFILE_SIGNATURE_CDS 0x02014b50
+#define ZIPFILE_SIGNATURE_LFH 0x04034b50
+#define ZIPFILE_SIGNATURE_EOCD 0x06054b50
+#define ZIPFILE_LFH_FIXED_SZ 30
+
+/*
+** Set the error message contained in context ctx to the results of
+** vprintf(zFmt, ...).
+*/
+static void zipfileCtxErrorMsg(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);
+}
+
+
+/*
+*** 4.3.16 End of central directory record:
+***
+*** end of central dir signature 4 bytes (0x06054b50)
+*** number of this disk 2 bytes
+*** number of the disk with the
+*** start of the central directory 2 bytes
+*** total number of entries in the
+*** central directory on this disk 2 bytes
+*** total number of entries in
+*** the central directory 2 bytes
+*** size of the central directory 4 bytes
+*** offset of start of central
+*** directory with respect to
+*** the starting disk number 4 bytes
+*** .ZIP file comment length 2 bytes
+*** .ZIP file comment (variable size)
+*/
+typedef struct ZipfileEOCD ZipfileEOCD;
+struct ZipfileEOCD {
+ u16 iDisk;
+ u16 iFirstDisk;
+ u16 nEntry;
+ u16 nEntryTotal;
+ u32 nSize;
+ u32 iOffset;
+};
+
+/*
+*** 4.3.12 Central directory structure:
+***
+*** ...
+***
+*** central file header signature 4 bytes (0x02014b50)
+*** version made by 2 bytes
+*** version needed to extract 2 bytes
+*** general purpose bit flag 2 bytes
+*** compression method 2 bytes
+*** last mod file time 2 bytes
+*** last mod file date 2 bytes
+*** crc-32 4 bytes
+*** compressed size 4 bytes
+*** uncompressed size 4 bytes
+*** file name length 2 bytes
+*** extra field length 2 bytes
+*** file comment length 2 bytes
+*** disk number start 2 bytes
+*** internal file attributes 2 bytes
+*** external file attributes 4 bytes
+*** relative offset of local header 4 bytes
+*/
+typedef struct ZipfileCDS ZipfileCDS;
+struct ZipfileCDS {
+ u16 iVersionMadeBy;
+ u16 iVersionExtract;
+ u16 flags;
+ u16 iCompression;
+ u16 mTime;
+ u16 mDate;
+ u32 crc32;
+ u32 szCompressed;
+ u32 szUncompressed;
+ u16 nFile;
+ u16 nExtra;
+ u16 nComment;
+ u16 iDiskStart;
+ u16 iInternalAttr;
+ u32 iExternalAttr;
+ u32 iOffset;
+ char *zFile; /* Filename (sqlite3_malloc()) */
+};
+
+/*
+*** 4.3.7 Local file header:
+***
+*** local file header signature 4 bytes (0x04034b50)
+*** version needed to extract 2 bytes
+*** general purpose bit flag 2 bytes
+*** compression method 2 bytes
+*** last mod file time 2 bytes
+*** last mod file date 2 bytes
+*** crc-32 4 bytes
+*** compressed size 4 bytes
+*** uncompressed size 4 bytes
+*** file name length 2 bytes
+*** extra field length 2 bytes
+***
+*/
+typedef struct ZipfileLFH ZipfileLFH;
+struct ZipfileLFH {
+ u16 iVersionExtract;
+ u16 flags;
+ u16 iCompression;
+ u16 mTime;
+ u16 mDate;
+ u32 crc32;
+ u32 szCompressed;
+ u32 szUncompressed;
+ u16 nFile;
+ u16 nExtra;
+};
+
+typedef struct ZipfileEntry ZipfileEntry;
+struct ZipfileEntry {
+ char *zPath; /* Path of zipfile entry */
+ i64 iRowid; /* Rowid for this value if queried */
+ u8 *aCdsEntry; /* Buffer containing entire CDS entry */
+ int nCdsEntry; /* Size of buffer aCdsEntry[] in bytes */
+ int bDeleted; /* True if entry has been deleted */
+ ZipfileEntry *pNext; /* Next element in in-memory CDS */
+};
+
+/*
+** Cursor type for recursively iterating through a directory structure.
+*/
+typedef struct ZipfileCsr ZipfileCsr;
+struct ZipfileCsr {
+ sqlite3_vtab_cursor base; /* Base class - must be first */
+ int bEof; /* True when at EOF */
+
+ /* Used outside of write transactions */
+ FILE *pFile; /* Zip file */
+ i64 iNextOff; /* Offset of next record in central directory */
+ ZipfileEOCD eocd; /* Parse of central directory record */
+
+ /* Used inside write transactions */
+ ZipfileEntry *pCurrent;
+
+ ZipfileCDS cds; /* Central Directory Structure */
+ ZipfileLFH lfh; /* Local File Header for current entry */
+ i64 iDataOff; /* Offset in zipfile to data */
+ u32 mTime; /* Extended mtime value */
+ int flags; /* Flags byte (see below for bits) */
+ ZipfileCsr *pCsrNext; /* Next cursor on same virtual table */
+};
+
+/*
+** Values for ZipfileCsr.flags.
+*/
+#define ZIPFILE_MTIME_VALID 0x0001
+
+typedef struct ZipfileTab ZipfileTab;
+struct ZipfileTab {
+ sqlite3_vtab base; /* Base class - must be first */
+ char *zFile; /* Zip file this table accesses (may be NULL) */
+ u8 *aBuffer; /* Temporary buffer used for various tasks */
+
+ /* The following are used by write transactions only */
+ ZipfileCsr *pCsrList; /* List of cursors */
+ ZipfileEntry *pFirstEntry; /* Linked list of all files (if pWriteFd!=0) */
+ ZipfileEntry *pLastEntry; /* Last element in pFirstEntry list */
+ FILE *pWriteFd; /* File handle open on zip archive */
+ i64 szCurrent; /* Current size of zip archive */
+ i64 szOrig; /* Size of archive at start of transaction */
+};
+
+static void zipfileDequote(char *zIn){
+ char q = zIn[0];
+ if( q=='"' || q=='\'' || q=='`' || q=='[' ){
+ char c;
+ int iIn = 1;
+ int iOut = 0;
+ if( q=='[' ) q = ']';
+ while( (c = zIn[iIn++]) ){
+ if( c==q ){
+ if( zIn[iIn++]!=q ) break;
+ }
+ zIn[iOut++] = c;
+ }
+ zIn[iOut] = '\0';
+ }
+}
+
+/*
+** Construct a new ZipfileTab virtual table object.
+**
+** argv[0] -> module name ("zipfile")
+** argv[1] -> database name
+** argv[2] -> table name
+** argv[...] -> "column name" and other module argument fields.
+*/
+static int zipfileConnect(
+ sqlite3 *db,
+ void *pAux,
+ int argc, const char *const*argv,
+ sqlite3_vtab **ppVtab,
+ char **pzErr
+){
+ int nByte = sizeof(ZipfileTab) + ZIPFILE_BUFFER_SIZE;
+ int nFile = 0;
+ const char *zFile = 0;
+ ZipfileTab *pNew = 0;
+ int rc;
+
+ if( argc>3 ){
+ zFile = argv[3];
+ nFile = (int)strlen(zFile)+1;
+ }
+
+ rc = sqlite3_declare_vtab(db, ZIPFILE_SCHEMA);
+ if( rc==SQLITE_OK ){
+ pNew = (ZipfileTab*)sqlite3_malloc(nByte+nFile);
+ if( pNew==0 ) return SQLITE_NOMEM;
+ memset(pNew, 0, nByte+nFile);
+ pNew->aBuffer = (u8*)&pNew[1];
+ if( zFile ){
+ pNew->zFile = (char*)&pNew->aBuffer[ZIPFILE_BUFFER_SIZE];
+ memcpy(pNew->zFile, zFile, nFile);
+ zipfileDequote(pNew->zFile);
+ }
+ }
+ *ppVtab = (sqlite3_vtab*)pNew;
+ return rc;
+}
+
+/*
+** This method is the destructor for zipfile vtab objects.
+*/
+static int zipfileDisconnect(sqlite3_vtab *pVtab){
+ sqlite3_free(pVtab);
+ return SQLITE_OK;
+}
+
+/*
+** Constructor for a new ZipfileCsr object.
+*/
+static int zipfileOpen(sqlite3_vtab *p, sqlite3_vtab_cursor **ppCsr){
+ ZipfileCsr *pCsr;
+ pCsr = sqlite3_malloc(sizeof(*pCsr));
+ *ppCsr = (sqlite3_vtab_cursor*)pCsr;
+ if( pCsr==0 ){
+ return SQLITE_NOMEM;
+ }
+ memset(pCsr, 0, sizeof(*pCsr));
+ return SQLITE_OK;
+}
+
+/*
+** Reset a cursor back to the state it was in when first returned
+** by zipfileOpen().
+*/
+static void zipfileResetCursor(ZipfileCsr *pCsr){
+ ZipfileTab *pTab = (ZipfileTab*)(pCsr->base.pVtab);
+ ZipfileCsr **pp;
+
+ /* Remove this cursor from the ZipfileTab.pCsrList list. */
+ for(pp=&pTab->pCsrList; *pp; pp=&((*pp)->pCsrNext)){
+ if( *pp==pCsr ) *pp = pCsr->pCsrNext;
+ }
+
+ sqlite3_free(pCsr->cds.zFile);
+ pCsr->cds.zFile = 0;
+ pCsr->bEof = 0;
+ if( pCsr->pFile ){
+ fclose(pCsr->pFile);
+ pCsr->pFile = 0;
+ }
+}
+
+/*
+** Destructor for an ZipfileCsr.
+*/
+static int zipfileClose(sqlite3_vtab_cursor *cur){
+ ZipfileCsr *pCsr = (ZipfileCsr*)cur;
+ zipfileResetCursor(pCsr);
+ sqlite3_free(pCsr);
+ return SQLITE_OK;
+}
+
+/*
+** Set the error message for the virtual table associated with cursor
+** pCsr to the results of vprintf(zFmt, ...).
+*/
+static void zipfileSetErrmsg(ZipfileCsr *pCsr, const char *zFmt, ...){
+ va_list ap;
+ va_start(ap, zFmt);
+ pCsr->base.pVtab->zErrMsg = sqlite3_vmprintf(zFmt, ap);
+ va_end(ap);
+}
+
+static int zipfileReadData(
+ FILE *pFile, /* Read from this file */
+ u8 *aRead, /* Read into this buffer */
+ int nRead, /* Number of bytes to read */
+ i64 iOff, /* Offset to read from */
+ char **pzErrmsg /* OUT: Error message (from sqlite3_malloc) */
+){
+ size_t n;
+ fseek(pFile, (long)iOff, SEEK_SET);
+ n = fread(aRead, 1, nRead, pFile);
+ if( (int)n!=nRead ){
+ *pzErrmsg = sqlite3_mprintf("error in fread()");
+ return SQLITE_ERROR;
+ }
+ return SQLITE_OK;
+}
+
+static int zipfileAppendData(
+ ZipfileTab *pTab,
+ const u8 *aWrite,
+ int nWrite
+){
+ size_t n;
+ fseek(pTab->pWriteFd, (long)pTab->szCurrent, SEEK_SET);
+ n = fwrite(aWrite, 1, nWrite, pTab->pWriteFd);
+ if( (int)n!=nWrite ){
+ pTab->base.zErrMsg = sqlite3_mprintf("error in fwrite()");
+ return SQLITE_ERROR;
+ }
+ pTab->szCurrent += nWrite;
+ return SQLITE_OK;
+}
+
+static u16 zipfileGetU16(const u8 *aBuf){
+ return (aBuf[1] << 8) + aBuf[0];
+}
+static u32 zipfileGetU32(const u8 *aBuf){
+ return ((u32)(aBuf[3]) << 24)
+ + ((u32)(aBuf[2]) << 16)
+ + ((u32)(aBuf[1]) << 8)
+ + ((u32)(aBuf[0]) << 0);
+}
+
+static void zipfilePutU16(u8 *aBuf, u16 val){
+ aBuf[0] = val & 0xFF;
+ aBuf[1] = (val>>8) & 0xFF;
+}
+static void zipfilePutU32(u8 *aBuf, u32 val){
+ aBuf[0] = val & 0xFF;
+ aBuf[1] = (val>>8) & 0xFF;
+ aBuf[2] = (val>>16) & 0xFF;
+ aBuf[3] = (val>>24) & 0xFF;
+}
+
+#define zipfileRead32(aBuf) ( aBuf+=4, zipfileGetU32(aBuf-4) )
+#define zipfileRead16(aBuf) ( aBuf+=2, zipfileGetU16(aBuf-2) )
+
+#define zipfileWrite32(aBuf,val) { zipfilePutU32(aBuf,val); aBuf+=4; }
+#define zipfileWrite16(aBuf,val) { zipfilePutU16(aBuf,val); aBuf+=2; }
+
+static u8* zipfileCsrBuffer(ZipfileCsr *pCsr){
+ return ((ZipfileTab*)(pCsr->base.pVtab))->aBuffer;
+}
+
+/*
+** Magic numbers used to read CDS records.
+*/
+#define ZIPFILE_CDS_FIXED_SZ 46
+#define ZIPFILE_CDS_NFILE_OFF 28
+
+static int zipfileReadCDS(ZipfileCsr *pCsr){
+ char **pzErr = &pCsr->base.pVtab->zErrMsg;
+ u8 *aRead;
+ int rc = SQLITE_OK;
+
+ sqlite3_free(pCsr->cds.zFile);
+ pCsr->cds.zFile = 0;
+
+ if( pCsr->pCurrent==0 ){
+ aRead = zipfileCsrBuffer(pCsr);
+ rc = zipfileReadData(
+ pCsr->pFile, aRead, ZIPFILE_CDS_FIXED_SZ, pCsr->iNextOff, pzErr
+ );
+ }else{
+ aRead = pCsr->pCurrent->aCdsEntry;
+ }
+
+ if( rc==SQLITE_OK ){
+ u32 sig = zipfileRead32(aRead);
+ if( sig!=ZIPFILE_SIGNATURE_CDS ){
+ assert( pCsr->pCurrent==0 );
+ zipfileSetErrmsg(pCsr,"failed to read CDS at offset %lld",pCsr->iNextOff);
+ rc = SQLITE_ERROR;
+ }else{
+ int nRead;
+ pCsr->cds.iVersionMadeBy = zipfileRead16(aRead);
+ pCsr->cds.iVersionExtract = zipfileRead16(aRead);
+ pCsr->cds.flags = zipfileRead16(aRead);
+ pCsr->cds.iCompression = zipfileRead16(aRead);
+ pCsr->cds.mTime = zipfileRead16(aRead);
+ pCsr->cds.mDate = zipfileRead16(aRead);
+ pCsr->cds.crc32 = zipfileRead32(aRead);
+ pCsr->cds.szCompressed = zipfileRead32(aRead);
+ pCsr->cds.szUncompressed = zipfileRead32(aRead);
+ assert( pCsr->pCurrent
+ || aRead==zipfileCsrBuffer(pCsr)+ZIPFILE_CDS_NFILE_OFF
+ );
+ pCsr->cds.nFile = zipfileRead16(aRead);
+ pCsr->cds.nExtra = zipfileRead16(aRead);
+ pCsr->cds.nComment = zipfileRead16(aRead);
+ pCsr->cds.iDiskStart = zipfileRead16(aRead);
+ pCsr->cds.iInternalAttr = zipfileRead16(aRead);
+ pCsr->cds.iExternalAttr = zipfileRead32(aRead);
+ pCsr->cds.iOffset = zipfileRead32(aRead);
+ assert( pCsr->pCurrent
+ || aRead==zipfileCsrBuffer(pCsr)+ZIPFILE_CDS_FIXED_SZ
+ );
+
+ if( pCsr->pCurrent==0 ){
+ nRead = pCsr->cds.nFile + pCsr->cds.nExtra;
+ aRead = zipfileCsrBuffer(pCsr);
+ pCsr->iNextOff += ZIPFILE_CDS_FIXED_SZ;
+ rc = zipfileReadData(pCsr->pFile, aRead, nRead, pCsr->iNextOff, pzErr);
+ }
+
+ if( rc==SQLITE_OK ){
+ pCsr->cds.zFile = sqlite3_mprintf("%.*s", (int)pCsr->cds.nFile, aRead);
+ pCsr->iNextOff += pCsr->cds.nFile;
+ pCsr->iNextOff += pCsr->cds.nExtra;
+ pCsr->iNextOff += pCsr->cds.nComment;
+ }
+
+ /* Scan the cds.nExtra bytes of "extra" fields for any that can
+ ** be interpreted. The general format of an extra field is:
+ **
+ ** Header ID 2 bytes
+ ** Data Size 2 bytes
+ ** Data N bytes
+ **
+ */
+ if( rc==SQLITE_OK ){
+ u8 *p = &aRead[pCsr->cds.nFile];
+ u8 *pEnd = &p[pCsr->cds.nExtra];
+
+ while( p modtime is present */
+ pCsr->mTime = zipfileGetU32(&p[1]);
+ pCsr->flags |= ZIPFILE_MTIME_VALID;
+ }
+ break;
+ }
+ }
+
+ p += nByte;
+ }
+ }
+ }
+ }
+
+ return rc;
+}
+
+static FILE *zipfileGetFd(ZipfileCsr *pCsr){
+ if( pCsr->pFile ) return pCsr->pFile;
+ return ((ZipfileTab*)(pCsr->base.pVtab))->pWriteFd;
+}
+
+static int zipfileReadLFH(ZipfileCsr *pCsr){
+ FILE *pFile = zipfileGetFd(pCsr);
+ char **pzErr = &pCsr->base.pVtab->zErrMsg;
+ static const int szFix = ZIPFILE_LFH_FIXED_SZ;
+ u8 *aRead = zipfileCsrBuffer(pCsr);
+ int rc;
+
+ rc = zipfileReadData(pFile, aRead, szFix, pCsr->cds.iOffset, pzErr);
+ if( rc==SQLITE_OK ){
+ u32 sig = zipfileRead32(aRead);
+ if( sig!=ZIPFILE_SIGNATURE_LFH ){
+ zipfileSetErrmsg(pCsr, "failed to read LFH at offset %d",
+ (int)pCsr->cds.iOffset
+ );
+ rc = SQLITE_ERROR;
+ }else{
+ pCsr->lfh.iVersionExtract = zipfileRead16(aRead);
+ pCsr->lfh.flags = zipfileRead16(aRead);
+ pCsr->lfh.iCompression = zipfileRead16(aRead);
+ pCsr->lfh.mTime = zipfileRead16(aRead);
+ pCsr->lfh.mDate = zipfileRead16(aRead);
+ pCsr->lfh.crc32 = zipfileRead32(aRead);
+ pCsr->lfh.szCompressed = zipfileRead32(aRead);
+ pCsr->lfh.szUncompressed = zipfileRead32(aRead);
+ pCsr->lfh.nFile = zipfileRead16(aRead);
+ pCsr->lfh.nExtra = zipfileRead16(aRead);
+ assert( aRead==zipfileCsrBuffer(pCsr)+szFix );
+ pCsr->iDataOff = pCsr->cds.iOffset+szFix+pCsr->lfh.nFile+pCsr->lfh.nExtra;
+ }
+ }
+
+ return rc;
+}
+
+
+/*
+** Advance an ZipfileCsr to its next row of output.
+*/
+static int zipfileNext(sqlite3_vtab_cursor *cur){
+ ZipfileCsr *pCsr = (ZipfileCsr*)cur;
+ int rc = SQLITE_OK;
+ pCsr->flags = 0;
+
+ if( pCsr->pCurrent==0 ){
+ i64 iEof = pCsr->eocd.iOffset + pCsr->eocd.nSize;
+ if( pCsr->iNextOff>=iEof ){
+ pCsr->bEof = 1;
+ }
+ }else{
+ assert( pCsr->pFile==0 );
+ do {
+ pCsr->pCurrent = pCsr->pCurrent->pNext;
+ }while( pCsr->pCurrent && pCsr->pCurrent->bDeleted );
+ if( pCsr->pCurrent==0 ){
+ pCsr->bEof = 1;
+ }
+ }
+
+ if( pCsr->bEof==0 ){
+ rc = zipfileReadCDS(pCsr);
+ if( rc==SQLITE_OK ){
+ rc = zipfileReadLFH(pCsr);
+ }
+ }
+
+ return rc;
+}
+
+/*
+** "Standard" MS-DOS time format:
+**
+** File modification time:
+** Bits 00-04: seconds divided by 2
+** Bits 05-10: minute
+** Bits 11-15: hour
+** File modification date:
+** Bits 00-04: day
+** Bits 05-08: month (1-12)
+** Bits 09-15: years from 1980
+*/
+static time_t zipfileMtime(ZipfileCsr *pCsr){
+ struct tm t;
+ memset(&t, 0, sizeof(t));
+ t.tm_sec = (pCsr->cds.mTime & 0x1F)*2;
+ t.tm_min = (pCsr->cds.mTime >> 5) & 0x2F;
+ t.tm_hour = (pCsr->cds.mTime >> 11) & 0x1F;
+
+ t.tm_mday = (pCsr->cds.mDate & 0x1F);
+ t.tm_mon = ((pCsr->cds.mDate >> 5) & 0x0F) - 1;
+ t.tm_year = 80 + ((pCsr->cds.mDate >> 9) & 0x7F);
+
+ return mktime(&t);
+}
+
+static void zipfileMtimeToDos(ZipfileCDS *pCds, u32 mTime){
+ time_t t = (time_t)mTime;
+ struct tm res;
+
+#if !defined(_WIN32) && !defined(WIN32)
+ localtime_r(&t, &res);
+#else
+ memcpy(&res, localtime(&t), sizeof(struct tm));
+#endif
+
+ pCds->mTime = (u16)(
+ (res.tm_sec / 2) +
+ (res.tm_min << 5) +
+ (res.tm_hour << 11));
+
+ pCds->mDate = (u16)(
+ (res.tm_mday-1) +
+ ((res.tm_mon+1) << 5) +
+ ((res.tm_year-80) << 9));
+}
+
+/*
+** Return values of columns for the row at which the series_cursor
+** is currently pointing.
+*/
+static int zipfileColumn(
+ sqlite3_vtab_cursor *cur, /* The cursor */
+ sqlite3_context *ctx, /* First argument to sqlite3_result_...() */
+ int i /* Which column to return */
+){
+ ZipfileCsr *pCsr = (ZipfileCsr*)cur;
+ int rc = SQLITE_OK;
+ switch( i ){
+ case 0: /* name */
+ sqlite3_result_text(ctx, pCsr->cds.zFile, -1, SQLITE_TRANSIENT);
+ break;
+ case 1: /* mode */
+ /* TODO: Whether or not the following is correct surely depends on
+ ** the platform on which the archive was created. */
+ sqlite3_result_int(ctx, pCsr->cds.iExternalAttr >> 16);
+ break;
+ case 2: { /* mtime */
+ if( pCsr->flags & ZIPFILE_MTIME_VALID ){
+ sqlite3_result_int64(ctx, pCsr->mTime);
+ }else{
+ sqlite3_result_int64(ctx, zipfileMtime(pCsr));
+ }
+ break;
+ }
+ case 3: { /* sz */
+ sqlite3_result_int64(ctx, pCsr->cds.szUncompressed);
+ break;
+ }
+ case 4: { /* data */
+ int sz = pCsr->cds.szCompressed;
+ if( sz>0 ){
+ u8 *aBuf = sqlite3_malloc(sz);
+ if( aBuf==0 ){
+ rc = SQLITE_NOMEM;
+ }else{
+ FILE *pFile = zipfileGetFd(pCsr);
+ rc = zipfileReadData(pFile, aBuf, sz, pCsr->iDataOff,
+ &pCsr->base.pVtab->zErrMsg
+ );
+ }
+ if( rc==SQLITE_OK ){
+ sqlite3_result_blob(ctx, aBuf, sz, SQLITE_TRANSIENT);
+ sqlite3_free(aBuf);
+ }
+ }
+ break;
+ }
+ case 5: /* method */
+ sqlite3_result_int(ctx, pCsr->cds.iCompression);
+ break;
+ }
+
+ return SQLITE_OK;
+}
+
+/*
+** Return the rowid for the current row.
+*/
+static int zipfileRowid(sqlite3_vtab_cursor *cur, sqlite_int64 *pRowid){
+ ZipfileCsr *pCsr = (ZipfileCsr*)cur;
+ if( pCsr->pCurrent ){
+ *pRowid = pCsr->pCurrent->iRowid;
+ }else{
+ *pRowid = pCsr->cds.iOffset;
+ }
+ return SQLITE_OK;
+}
+
+/*
+** Return TRUE if the cursor has been moved off of the last
+** row of output.
+*/
+static int zipfileEof(sqlite3_vtab_cursor *cur){
+ ZipfileCsr *pCsr = (ZipfileCsr*)cur;
+ return pCsr->bEof;
+}
+
+/*
+*/
+static int zipfileReadEOCD(
+ ZipfileTab *pTab, /* Return errors here */
+ FILE *pFile, /* Read from this file */
+ ZipfileEOCD *pEOCD /* Object to populate */
+){
+ u8 *aRead = pTab->aBuffer; /* Temporary buffer */
+ i64 szFile; /* Total size of file in bytes */
+ int nRead; /* Bytes to read from file */
+ i64 iOff; /* Offset to read from */
+ int rc;
+
+ fseek(pFile, 0, SEEK_END);
+ szFile = (i64)ftell(pFile);
+ if( szFile==0 ){
+ return SQLITE_EMPTY;
+ }
+ nRead = (int)(MIN(szFile, ZIPFILE_BUFFER_SIZE));
+ iOff = szFile - nRead;
+
+ rc = zipfileReadData(pFile, aRead, nRead, iOff, &pTab->base.zErrMsg);
+ if( rc==SQLITE_OK ){
+ int i;
+
+ /* Scan backwards looking for the signature bytes */
+ for(i=nRead-20; i>=0; i--){
+ if( aRead[i]==0x50 && aRead[i+1]==0x4b
+ && aRead[i+2]==0x05 && aRead[i+3]==0x06
+ ){
+ break;
+ }
+ }
+ if( i<0 ){
+ pTab->base.zErrMsg = sqlite3_mprintf(
+ "cannot find end of central directory record"
+ );
+ return SQLITE_ERROR;
+ }
+
+ aRead += i+4;
+ pEOCD->iDisk = zipfileRead16(aRead);
+ pEOCD->iFirstDisk = zipfileRead16(aRead);
+ pEOCD->nEntry = zipfileRead16(aRead);
+ pEOCD->nEntryTotal = zipfileRead16(aRead);
+ pEOCD->nSize = zipfileRead32(aRead);
+ pEOCD->iOffset = zipfileRead32(aRead);
+
+#if 0
+ printf("iDisk=%d iFirstDisk=%d nEntry=%d "
+ "nEntryTotal=%d nSize=%d iOffset=%d",
+ (int)pEOCD->iDisk, (int)pEOCD->iFirstDisk, (int)pEOCD->nEntry,
+ (int)pEOCD->nEntryTotal, (int)pEOCD->nSize, (int)pEOCD->iOffset
+ );
+#endif
+ }
+
+ return SQLITE_OK;
+}
+
+/*
+** xFilter callback.
+*/
+static int zipfileFilter(
+ sqlite3_vtab_cursor *cur,
+ int idxNum, const char *idxStr,
+ int argc, sqlite3_value **argv
+){
+ ZipfileTab *pTab = (ZipfileTab*)cur->pVtab;
+ ZipfileCsr *pCsr = (ZipfileCsr*)cur;
+ const char *zFile; /* Zip file to scan */
+ int rc = SQLITE_OK; /* Return Code */
+
+ zipfileResetCursor(pCsr);
+
+ if( pTab->zFile ){
+ zFile = pTab->zFile;
+ }else if( idxNum==0 ){
+ /* Error. This is an eponymous virtual table and the user has not
+ ** supplied a file name. */
+ zipfileSetErrmsg(pCsr, "table function zipfile() requires an argument");
+ return SQLITE_ERROR;
+ }else{
+ zFile = (const char*)sqlite3_value_text(argv[0]);
+ }
+
+ if( pTab->pWriteFd==0 ){
+ pCsr->pFile = fopen(zFile, "rb");
+ if( pCsr->pFile==0 ){
+ zipfileSetErrmsg(pCsr, "cannot open file: %s", zFile);
+ rc = SQLITE_ERROR;
+ }else{
+ rc = zipfileReadEOCD(pTab, pCsr->pFile, &pCsr->eocd);
+ if( rc==SQLITE_OK ){
+ pCsr->iNextOff = pCsr->eocd.iOffset;
+ rc = zipfileNext(cur);
+ }else if( rc==SQLITE_EMPTY ){
+ rc = SQLITE_OK;
+ pCsr->bEof = 1;
+ }
+ }
+ }else{
+ ZipfileEntry e;
+ memset(&e, 0, sizeof(e));
+ e.pNext = pTab->pFirstEntry;
+ pCsr->pCurrent = &e;
+ rc = zipfileNext(cur);
+ assert( pCsr->pCurrent!=&e );
+ }
+
+ return rc;
+}
+
+/*
+** xBestIndex callback.
+*/
+static int zipfileBestIndex(
+ sqlite3_vtab *tab,
+ sqlite3_index_info *pIdxInfo
+){
+ int i;
+
+ for(i=0; inConstraint; i++){
+ const struct sqlite3_index_constraint *pCons = &pIdxInfo->aConstraint[i];
+ if( pCons->usable==0 ) continue;
+ if( pCons->op!=SQLITE_INDEX_CONSTRAINT_EQ ) continue;
+ if( pCons->iColumn!=ZIPFILE_F_COLUMN_IDX ) continue;
+ break;
+ }
+
+ if( inConstraint ){
+ pIdxInfo->aConstraintUsage[i].argvIndex = 1;
+ pIdxInfo->aConstraintUsage[i].omit = 1;
+ pIdxInfo->estimatedCost = 1000.0;
+ pIdxInfo->idxNum = 1;
+ }else{
+ pIdxInfo->estimatedCost = (double)(((sqlite3_int64)1) << 50);
+ pIdxInfo->idxNum = 0;
+ }
+
+ return SQLITE_OK;
+}
+
+/*
+** Add object pNew to the end of the linked list that begins at
+** ZipfileTab.pFirstEntry and ends with pLastEntry.
+*/
+static void zipfileAddEntry(ZipfileTab *pTab, ZipfileEntry *pNew){
+ assert( (pTab->pFirstEntry==0)==(pTab->pLastEntry==0) );
+ assert( pNew->pNext==0 );
+ if( pTab->pFirstEntry==0 ){
+ pNew->iRowid = 1;
+ pTab->pFirstEntry = pTab->pLastEntry = pNew;
+ }else{
+ assert( pTab->pLastEntry->pNext==0 );
+ pNew->iRowid = pTab->pLastEntry->iRowid+1;
+ pTab->pLastEntry->pNext = pNew;
+ pTab->pLastEntry = pNew;
+ }
+}
+
+static int zipfileLoadDirectory(ZipfileTab *pTab){
+ ZipfileEOCD eocd;
+ int rc;
+
+ rc = zipfileReadEOCD(pTab, pTab->pWriteFd, &eocd);
+ if( rc==SQLITE_OK ){
+ int i;
+ int iOff = 0;
+ u8 *aBuf = sqlite3_malloc(eocd.nSize);
+ if( aBuf==0 ){
+ rc = SQLITE_NOMEM;
+ }else{
+ rc = zipfileReadData(
+ pTab->pWriteFd, aBuf, eocd.nSize, eocd.iOffset, &pTab->base.zErrMsg
+ );
+ }
+
+ for(i=0; rc==SQLITE_OK && izPath = (char*)&pNew[1];
+ memcpy(pNew->zPath, &aBuf[ZIPFILE_CDS_FIXED_SZ], nFile);
+ pNew->zPath[nFile] = '\0';
+ pNew->aCdsEntry = (u8*)&pNew->zPath[nFile+1];
+ pNew->nCdsEntry = ZIPFILE_CDS_FIXED_SZ+nFile+nExtra+nComment;
+ memcpy(pNew->aCdsEntry, aRec, pNew->nCdsEntry);
+ zipfileAddEntry(pTab, pNew);
+ }
+
+ iOff += ZIPFILE_CDS_FIXED_SZ+nFile+nExtra+nComment;
+ }
+
+ sqlite3_free(aBuf);
+ }else if( rc==SQLITE_EMPTY ){
+ rc = SQLITE_OK;
+ }
+
+ return rc;
+}
+
+static ZipfileEntry *zipfileNewEntry(
+ ZipfileCDS *pCds, /* Values for fixed size part of CDS */
+ const char *zPath, /* Path for new entry */
+ int nPath, /* strlen(zPath) */
+ u32 mTime /* Modification time (or 0) */
+){
+ u8 *aWrite;
+ ZipfileEntry *pNew;
+ pCds->nFile = (u16)nPath;
+ pCds->nExtra = mTime ? 9 : 0;
+ pNew = (ZipfileEntry*)sqlite3_malloc(
+ sizeof(ZipfileEntry) +
+ nPath+1 +
+ ZIPFILE_CDS_FIXED_SZ + nPath + pCds->nExtra
+ );
+
+ if( pNew ){
+ memset(pNew, 0, sizeof(ZipfileEntry));
+ pNew->zPath = (char*)&pNew[1];
+ pNew->aCdsEntry = (u8*)&pNew->zPath[nPath+1];
+ pNew->nCdsEntry = ZIPFILE_CDS_FIXED_SZ + nPath + pCds->nExtra;
+ memcpy(pNew->zPath, zPath, nPath+1);
+
+ aWrite = pNew->aCdsEntry;
+ zipfileWrite32(aWrite, ZIPFILE_SIGNATURE_CDS);
+ zipfileWrite16(aWrite, pCds->iVersionMadeBy);
+ zipfileWrite16(aWrite, pCds->iVersionExtract);
+ zipfileWrite16(aWrite, pCds->flags);
+ zipfileWrite16(aWrite, pCds->iCompression);
+ zipfileWrite16(aWrite, pCds->mTime);
+ zipfileWrite16(aWrite, pCds->mDate);
+ zipfileWrite32(aWrite, pCds->crc32);
+ zipfileWrite32(aWrite, pCds->szCompressed);
+ zipfileWrite32(aWrite, pCds->szUncompressed);
+ zipfileWrite16(aWrite, pCds->nFile);
+ zipfileWrite16(aWrite, pCds->nExtra);
+ zipfileWrite16(aWrite, pCds->nComment); assert( pCds->nComment==0 );
+ zipfileWrite16(aWrite, pCds->iDiskStart);
+ zipfileWrite16(aWrite, pCds->iInternalAttr);
+ zipfileWrite32(aWrite, pCds->iExternalAttr);
+ zipfileWrite32(aWrite, pCds->iOffset);
+ assert( aWrite==&pNew->aCdsEntry[ZIPFILE_CDS_FIXED_SZ] );
+ memcpy(aWrite, zPath, nPath);
+ if( pCds->nExtra ){
+ aWrite += nPath;
+ zipfileWrite16(aWrite, ZIPFILE_EXTRA_TIMESTAMP);
+ zipfileWrite16(aWrite, 5);
+ *aWrite++ = 0x01;
+ zipfileWrite32(aWrite, mTime);
+ }
+ }
+
+ return pNew;
+}
+
+static int zipfileAppendEntry(
+ ZipfileTab *pTab,
+ ZipfileCDS *pCds,
+ const char *zPath, /* Path for new entry */
+ int nPath, /* strlen(zPath) */
+ const u8 *pData,
+ int nData,
+ u32 mTime
+){
+ u8 *aBuf = pTab->aBuffer;
+ int rc;
+
+ zipfileWrite32(aBuf, ZIPFILE_SIGNATURE_LFH);
+ zipfileWrite16(aBuf, pCds->iVersionExtract);
+ zipfileWrite16(aBuf, pCds->flags);
+ zipfileWrite16(aBuf, pCds->iCompression);
+ zipfileWrite16(aBuf, pCds->mTime);
+ zipfileWrite16(aBuf, pCds->mDate);
+ zipfileWrite32(aBuf, pCds->crc32);
+ zipfileWrite32(aBuf, pCds->szCompressed);
+ zipfileWrite32(aBuf, pCds->szUncompressed);
+ zipfileWrite16(aBuf, (u16)nPath);
+ zipfileWrite16(aBuf, pCds->nExtra);
+ assert( aBuf==&pTab->aBuffer[ZIPFILE_LFH_FIXED_SZ] );
+ rc = zipfileAppendData(pTab, pTab->aBuffer, (int)(aBuf - pTab->aBuffer));
+ if( rc==SQLITE_OK ){
+ rc = zipfileAppendData(pTab, (const u8*)zPath, nPath);
+ }
+
+ if( rc==SQLITE_OK && pCds->nExtra ){
+ aBuf = pTab->aBuffer;
+ zipfileWrite16(aBuf, ZIPFILE_EXTRA_TIMESTAMP);
+ zipfileWrite16(aBuf, 5);
+ *aBuf++ = 0x01;
+ zipfileWrite32(aBuf, mTime);
+ rc = zipfileAppendData(pTab, pTab->aBuffer, 9);
+ }
+
+ if( rc==SQLITE_OK ){
+ rc = zipfileAppendData(pTab, pData, nData);
+ }
+
+ return rc;
+}
+
+static int zipfileGetMode(ZipfileTab *pTab, sqlite3_value *pVal, int *pMode){
+ const char *z = (const char*)sqlite3_value_text(pVal);
+ int mode = 0;
+ if( z==0 || (z[0]>=0 && z[0]<=9) ){
+ mode = sqlite3_value_int(pVal);
+ }else{
+ const char zTemplate[11] = "-rwxrwxrwx";
+ int i;
+ if( strlen(z)!=10 ) goto parse_error;
+ switch( z[0] ){
+ case '-': mode |= S_IFREG; break;
+ case 'd': mode |= S_IFDIR; break;
+#if !defined(_WIN32) && !defined(WIN32)
+ case 'l': mode |= S_IFLNK; break;
+#endif
+ default: goto parse_error;
+ }
+ for(i=1; i<10; i++){
+ if( z[i]==zTemplate[i] ) mode |= 1 << (9-i);
+ else if( z[i]!='-' ) goto parse_error;
+ }
+ }
+ *pMode = mode;
+ return SQLITE_OK;
+
+ parse_error:
+ pTab->base.zErrMsg = sqlite3_mprintf("zipfile: parse error in mode: %s", z);
+ return SQLITE_ERROR;
+}
+
+/*
+** xUpdate method.
+*/
+static int zipfileUpdate(
+ sqlite3_vtab *pVtab,
+ int nVal,
+ sqlite3_value **apVal,
+ sqlite_int64 *pRowid
+){
+ ZipfileTab *pTab = (ZipfileTab*)pVtab;
+ int rc = SQLITE_OK; /* Return Code */
+ ZipfileEntry *pNew = 0; /* New in-memory CDS entry */
+
+ int mode; /* Mode for new entry */
+ i64 mTime; /* Modification time for new entry */
+ i64 sz; /* Uncompressed size */
+ const char *zPath; /* Path for new entry */
+ int nPath; /* strlen(zPath) */
+ const u8 *pData; /* Pointer to buffer containing content */
+ int nData; /* Size of pData buffer in bytes */
+ int iMethod = 0; /* Compression method for new entry */
+ u8 *pFree = 0; /* Free this */
+ ZipfileCDS cds; /* New Central Directory Structure entry */
+
+ assert( pTab->zFile );
+ assert( pTab->pWriteFd );
+
+ if( sqlite3_value_type(apVal[0])!=SQLITE_NULL ){
+ i64 iDelete = sqlite3_value_int64(apVal[0]);
+ ZipfileEntry *p;
+ for(p=pTab->pFirstEntry; p; p=p->pNext){
+ if( p->iRowid==iDelete ){
+ p->bDeleted = 1;
+ break;
+ }
+ }
+ if( nVal==1 ) return SQLITE_OK;
+ }
+
+ zPath = (const char*)sqlite3_value_text(apVal[2]);
+ nPath = (int)strlen(zPath);
+ rc = zipfileGetMode(pTab, apVal[3], &mode);
+ if( rc!=SQLITE_OK ) return rc;
+ mTime = sqlite3_value_int64(apVal[4]);
+ sz = sqlite3_value_int(apVal[5]);
+ pData = sqlite3_value_blob(apVal[6]);
+ nData = sqlite3_value_bytes(apVal[6]);
+
+ /* If a NULL value is inserted into the 'method' column, do automatic
+ ** compression. */
+ if( nData>0 && sqlite3_value_type(apVal[7])==SQLITE_NULL ){
+ pFree = (u8*)sqlite3_malloc(nData);
+ if( pFree==0 ){
+ rc = SQLITE_NOMEM;
+ }else{
+ int res;
+ z_stream str;
+ memset(&str, 0, sizeof(str));
+ str.next_in = (z_const Bytef*)pData;
+ str.avail_in = nData;
+ str.next_out = pFree;
+ str.avail_out = nData;
+ deflateInit2(&str, 9, Z_DEFLATED, -15, 8, Z_DEFAULT_STRATEGY);
+ res = deflate(&str, Z_FINISH);
+ if( res==Z_STREAM_END ){
+ pData = pFree;
+ nData = str.total_out;
+ iMethod = 8;
+ }else if( res!=Z_OK ){
+ pTab->base.zErrMsg = sqlite3_mprintf("zipfile: deflate() error");
+ rc = SQLITE_ERROR;
+ }
+ deflateEnd(&str);
+ }
+ }else{
+ iMethod = sqlite3_value_int(apVal[7]);
+ if( iMethod<0 || iMethod>65535 ){
+ pTab->base.zErrMsg = sqlite3_mprintf(
+ "zipfile: invalid compression method: %d", iMethod
+ );
+ rc = SQLITE_ERROR;
+ }
+ }
+
+ if( rc==SQLITE_OK ){
+ /* Create the new CDS record. */
+ memset(&cds, 0, sizeof(cds));
+ cds.iVersionMadeBy = ZIPFILE_NEWENTRY_MADEBY;
+ cds.iVersionExtract = ZIPFILE_NEWENTRY_REQUIRED;
+ cds.flags = ZIPFILE_NEWENTRY_FLAGS;
+ cds.iCompression = (u16)iMethod;
+ zipfileMtimeToDos(&cds, (u32)mTime);
+ cds.crc32 = crc32(0, pData, nData);
+ cds.szCompressed = nData;
+ cds.szUncompressed = (u32)sz;
+ cds.iExternalAttr = (mode<<16);
+ cds.iOffset = (u32)pTab->szCurrent;
+ pNew = zipfileNewEntry(&cds, zPath, nPath, (u32)mTime);
+ if( pNew==0 ){
+ rc = SQLITE_NOMEM;
+ }else{
+ zipfileAddEntry(pTab, pNew);
+ }
+ }
+
+ /* Append the new header+file to the archive */
+ if( rc==SQLITE_OK ){
+ rc = zipfileAppendEntry(pTab, &cds, zPath, nPath, pData, nData, (u32)mTime);
+ }
+
+ sqlite3_free(pFree);
+ return rc;
+}
+
+static int zipfileAppendEOCD(ZipfileTab *pTab, ZipfileEOCD *p){
+ u8 *aBuf = pTab->aBuffer;
+
+ zipfileWrite32(aBuf, ZIPFILE_SIGNATURE_EOCD);
+ zipfileWrite16(aBuf, p->iDisk);
+ zipfileWrite16(aBuf, p->iFirstDisk);
+ zipfileWrite16(aBuf, p->nEntry);
+ zipfileWrite16(aBuf, p->nEntryTotal);
+ zipfileWrite32(aBuf, p->nSize);
+ zipfileWrite32(aBuf, p->iOffset);
+ zipfileWrite16(aBuf, 0); /* Size of trailing comment in bytes*/
+
+ assert( (aBuf-pTab->aBuffer)==22 );
+ return zipfileAppendData(pTab, pTab->aBuffer, (int)(aBuf - pTab->aBuffer));
+}
+
+static void zipfileCleanupTransaction(ZipfileTab *pTab){
+ ZipfileEntry *pEntry;
+ ZipfileEntry *pNext;
+
+ for(pEntry=pTab->pFirstEntry; pEntry; pEntry=pNext){
+ pNext = pEntry->pNext;
+ sqlite3_free(pEntry);
+ }
+ pTab->pFirstEntry = 0;
+ pTab->pLastEntry = 0;
+ fclose(pTab->pWriteFd);
+ pTab->pWriteFd = 0;
+ pTab->szCurrent = 0;
+ pTab->szOrig = 0;
+}
+
+static int zipfileBegin(sqlite3_vtab *pVtab){
+ ZipfileTab *pTab = (ZipfileTab*)pVtab;
+ int rc = SQLITE_OK;
+
+ assert( pTab->pWriteFd==0 );
+
+ /* This table is only writable if a default archive path was specified
+ ** as part of the CREATE VIRTUAL TABLE statement. */
+ if( pTab->zFile==0 ){
+ pTab->base.zErrMsg = sqlite3_mprintf(
+ "zipfile: writing requires a default archive"
+ );
+ return SQLITE_ERROR;
+ }
+
+ /* Open a write fd on the file. Also load the entire central directory
+ ** structure into memory. During the transaction any new file data is
+ ** appended to the archive file, but the central directory is accumulated
+ ** in main-memory until the transaction is committed. */
+ pTab->pWriteFd = fopen(pTab->zFile, "ab+");
+ if( pTab->pWriteFd==0 ){
+ pTab->base.zErrMsg = sqlite3_mprintf(
+ "zipfile: failed to open file %s for writing", pTab->zFile
+ );
+ rc = SQLITE_ERROR;
+ }else{
+ fseek(pTab->pWriteFd, 0, SEEK_END);
+ pTab->szCurrent = pTab->szOrig = (i64)ftell(pTab->pWriteFd);
+ rc = zipfileLoadDirectory(pTab);
+ }
+
+ if( rc!=SQLITE_OK ){
+ zipfileCleanupTransaction(pTab);
+ }
+
+ return rc;
+}
+
+static int zipfileCommit(sqlite3_vtab *pVtab){
+ ZipfileTab *pTab = (ZipfileTab*)pVtab;
+ int rc = SQLITE_OK;
+ if( pTab->pWriteFd ){
+ i64 iOffset = pTab->szCurrent;
+ ZipfileEntry *p;
+ ZipfileEOCD eocd;
+ int nEntry = 0;
+
+ /* Write out all undeleted entries */
+ for(p=pTab->pFirstEntry; rc==SQLITE_OK && p; p=p->pNext){
+ if( p->bDeleted ) continue;
+ rc = zipfileAppendData(pTab, p->aCdsEntry, p->nCdsEntry);
+ nEntry++;
+ }
+
+ /* Write out the EOCD record */
+ eocd.iDisk = 0;
+ eocd.iFirstDisk = 0;
+ eocd.nEntry = (u16)nEntry;
+ eocd.nEntryTotal = (u16)nEntry;
+ eocd.nSize = (u32)(pTab->szCurrent - iOffset);
+ eocd.iOffset = (u32)iOffset;
+ rc = zipfileAppendEOCD(pTab, &eocd);
+
+ zipfileCleanupTransaction(pTab);
+ }
+ return rc;
+}
+
+static int zipfileRollback(sqlite3_vtab *pVtab){
+ return zipfileCommit(pVtab);
+}
+
+/*
+** Register the "zipfile" virtual table.
+*/
+static int zipfileRegister(sqlite3 *db){
+ static sqlite3_module zipfileModule = {
+ 1, /* iVersion */
+ zipfileConnect, /* xCreate */
+ zipfileConnect, /* xConnect */
+ zipfileBestIndex, /* xBestIndex */
+ zipfileDisconnect, /* xDisconnect */
+ zipfileDisconnect, /* xDestroy */
+ zipfileOpen, /* xOpen - open a cursor */
+ zipfileClose, /* xClose - close a cursor */
+ zipfileFilter, /* xFilter - configure scan constraints */
+ zipfileNext, /* xNext - advance a cursor */
+ zipfileEof, /* xEof - check for end of scan */
+ zipfileColumn, /* xColumn - read data */
+ zipfileRowid, /* xRowid - read data */
+ zipfileUpdate, /* xUpdate */
+ zipfileBegin, /* xBegin */
+ 0, /* xSync */
+ zipfileCommit, /* xCommit */
+ zipfileRollback, /* xRollback */
+ 0, /* xFindMethod */
+ 0, /* xRename */
+ };
+
+ int rc = sqlite3_create_module(db, "zipfile" , &zipfileModule, 0);
+ return rc;
+}
+#else /* SQLITE_OMIT_VIRTUALTABLE */
+# define zipfileRegister(x) SQLITE_OK
+#endif
+
+/*
+** zipfile_uncompress(DATA, SZ, METHOD)
+*/
+static void zipfileUncompressFunc(
+ sqlite3_context *context,
+ int argc,
+ sqlite3_value **argv
+){
+ int iMethod;
+
+ iMethod = sqlite3_value_int(argv[2]);
+ if( iMethod==0 ){
+ sqlite3_result_value(context, argv[0]);
+ }else if( iMethod==8 ){
+ Byte *res;
+ int sz = sqlite3_value_int(argv[1]);
+ z_stream str;
+ memset(&str, 0, sizeof(str));
+ str.next_in = (Byte*)sqlite3_value_blob(argv[0]);
+ str.avail_in = sqlite3_value_bytes(argv[0]);
+ res = str.next_out = (Byte*)sqlite3_malloc(sz);
+ if( res==0 ){
+ sqlite3_result_error_nomem(context);
+ }else{
+ int err;
+ str.avail_out = sz;
+
+ err = inflateInit2(&str, -15);
+ if( err!=Z_OK ){
+ zipfileCtxErrorMsg(context, "inflateInit2() failed (%d)", err);
+ }else{
+ err = inflate(&str, Z_NO_FLUSH);
+ if( err!=Z_STREAM_END ){
+ zipfileCtxErrorMsg(context, "inflate() failed (%d)", err);
+ }else{
+ sqlite3_result_blob(context, res, sz, SQLITE_TRANSIENT);
+ }
+ }
+ sqlite3_free(res);
+ inflateEnd(&str);
+ }
+ }else{
+ zipfileCtxErrorMsg(context, "unrecognized compression method: %d", iMethod);
+ }
+}
+
+#ifdef _WIN32
+__declspec(dllexport)
+#endif
+int sqlite3_zipfile_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, "zipfile_uncompress", 3,
+ SQLITE_UTF8, 0, zipfileUncompressFunc, 0, 0
+ );
+ if( rc!=SQLITE_OK ) return rc;
+ return zipfileRegister(db);
+}
+
Index: main.mk
==================================================================
--- main.mk
+++ main.mk
@@ -368,10 +368,11 @@
$(TOP)/ext/misc/spellfix.c \
$(TOP)/ext/misc/totype.c \
$(TOP)/ext/misc/unionvtab.c \
$(TOP)/ext/misc/wholenumber.c \
$(TOP)/ext/misc/vfslog.c \
+ $(TOP)/ext/misc/zipfile.c \
$(TOP)/ext/fts5/fts5_tcl.c \
$(TOP)/ext/fts5/fts5_test_mi.c \
$(TOP)/ext/fts5/fts5_test_tok.c
@@ -693,12 +694,15 @@
SHELL_SRC = \
$(TOP)/src/shell.c.in \
$(TOP)/ext/misc/shathree.c \
$(TOP)/ext/misc/fileio.c \
$(TOP)/ext/misc/completion.c \
+ $(TOP)/ext/misc/sqlar.c \
$(TOP)/ext/expert/sqlite3expert.c \
- $(TOP)/ext/expert/sqlite3expert.h
+ $(TOP)/ext/expert/sqlite3expert.h \
+ $(TOP)/ext/misc/zipfile.c \
+ $(TOP)/src/test_windirent.c
shell.c: $(SHELL_SRC) $(TOP)/tool/mkshellc.tcl
tclsh $(TOP)/tool/mkshellc.tcl >shell.c
Index: src/shell.c.in
==================================================================
--- src/shell.c.in
+++ src/shell.c.in
@@ -71,12 +71,13 @@
# include
# if !defined(__RTP__) && !defined(_WRS_KERNEL)
# include
# endif
# include
-# include
#endif
+#include
+#include
#if HAVE_READLINE
# include
# include
#endif
@@ -872,13 +873,22 @@
** work here in the middle of this regular program.
*/
#define SQLITE_EXTENSION_INIT1
#define SQLITE_EXTENSION_INIT2(X) (void)(X)
+#if defined(_WIN32) || defined(WIN32)
+INCLUDE test_windirent.c
+#define dirent DIRENT
+#define timespec TIMESPEC
+#endif
INCLUDE ../ext/misc/shathree.c
INCLUDE ../ext/misc/fileio.c
INCLUDE ../ext/misc/completion.c
+#ifdef SQLITE_HAVE_ZLIB
+INCLUDE ../ext/misc/zipfile.c
+INCLUDE ../ext/misc/sqlar.c
+#endif
INCLUDE ../ext/expert/sqlite3expert.h
INCLUDE ../ext/expert/sqlite3expert.c
#if defined(SQLITE_ENABLE_SESSION)
/*
@@ -1943,11 +1953,11 @@
{ "write_bytes: ", "Bytes written to storage:" },
{ "cancelled_write_bytes: ", "Cancelled write bytes:" },
};
int i;
for(i=0; idb, 1);
#endif
sqlite3_fileio_init(p->db, 0, 0);
sqlite3_shathree_init(p->db, 0, 0);
sqlite3_completion_init(p->db, 0, 0);
+#ifdef SQLITE_HAVE_ZLIB
+ sqlite3_zipfile_init(p->db, 0, 0);
+ sqlite3_sqlar_init(p->db, 0, 0);
+#endif
sqlite3_create_function(p->db, "shell_add_schema", 3, SQLITE_UTF8, 0,
shellAddSchemaName, 0, 0);
sqlite3_create_function(p->db, "shell_module_schema", 1, SQLITE_UTF8, 0,
shellModuleSchema, 0, 0);
}
@@ -3118,11 +3132,11 @@
#elif HAVE_LINENOISE
/*
** Linenoise completion callback
*/
static void linenoise_completion(const char *zLine, linenoiseCompletions *lc){
- int nLine = (int)strlen(zLine);
+ int nLine = strlen30(zLine);
int i, iStart;
sqlite3_stmt *pStmt = 0;
char *zSql;
char zBuf[1000];
@@ -3344,11 +3358,11 @@
FILE *f = (FILE*)pArg;
UNUSED_PARAMETER(mType);
UNUSED_PARAMETER(pP);
if( f ){
const char *z = (const char*)pX;
- int i = (int)strlen(z);
+ int i = strlen30(z);
while( i>0 && z[i-1]==';' ){ i--; }
utf8_printf(f, "%.*s;\n", i, z);
}
return 0;
}
@@ -3533,11 +3547,11 @@
sqlite3_stmt *pInsert = 0;
char *zQuery = 0;
char *zInsert = 0;
int rc;
int i, j, n;
- int nTable = (int)strlen(zTable);
+ int nTable = strlen30(zTable);
int k = 0;
int cnt = 0;
const int spinRate = 10000;
zQuery = sqlite3_mprintf("SELECT * FROM \"%w\"", zTable);
@@ -3554,11 +3568,11 @@
raw_printf(stderr, "out of memory\n");
goto end_data_xfer;
}
sqlite3_snprintf(200+nTable,zInsert,
"INSERT OR IGNORE INTO \"%s\" VALUES(?", zTable);
- i = (int)strlen(zInsert);
+ i = strlen30(zInsert);
for(j=1; j1 && sqlite3_strnicmp("-verbose", azArg[i], n)==0 ){
bVerbose = 1;
}
else if( n>1 && sqlite3_strnicmp("-groupbyparent", azArg[i], n)==0 ){
bGroupByParent = 1;
@@ -4239,20 +4253,759 @@
ShellState *pState, /* Current shell tool state */
char **azArg, /* Array of arguments passed to dot command */
int nArg /* Number of entries in azArg[] */
){
int n;
- n = (nArg>=2 ? (int)strlen(azArg[1]) : 0);
+ n = (nArg>=2 ? strlen30(azArg[1]) : 0);
if( n<1 || sqlite3_strnicmp(azArg[1], "fkey-indexes", n) ) goto usage;
return lintFkeyIndexes(pState, azArg, nArg);
usage:
raw_printf(stderr, "Usage %s sub-command ?switches...?\n", azArg[0]);
raw_printf(stderr, "Where sub-commands are:\n");
raw_printf(stderr, " fkey-indexes\n");
return SQLITE_ERROR;
}
+
+static void shellPrepare(
+ sqlite3 *db,
+ int *pRc,
+ const char *zSql,
+ sqlite3_stmt **ppStmt
+){
+ *ppStmt = 0;
+ if( *pRc==SQLITE_OK ){
+ int rc = sqlite3_prepare_v2(db, zSql, -1, ppStmt, 0);
+ if( rc!=SQLITE_OK ){
+ raw_printf(stderr, "sql error: %s (%d)\n",
+ sqlite3_errmsg(db), sqlite3_errcode(db)
+ );
+ *pRc = rc;
+ }
+ }
+}
+
+static void shellPreparePrintf(
+ sqlite3 *db,
+ int *pRc,
+ sqlite3_stmt **ppStmt,
+ const char *zFmt,
+ ...
+){
+ *ppStmt = 0;
+ if( *pRc==SQLITE_OK ){
+ va_list ap;
+ char *z;
+ va_start(ap, zFmt);
+ z = sqlite3_vmprintf(zFmt, ap);
+ if( z==0 ){
+ *pRc = SQLITE_NOMEM;
+ }else{
+ shellPrepare(db, pRc, z, ppStmt);
+ sqlite3_free(z);
+ }
+ }
+}
+
+static void shellFinalize(
+ int *pRc,
+ sqlite3_stmt *pStmt
+){
+ if( pStmt ){
+ sqlite3 *db = sqlite3_db_handle(pStmt);
+ int rc = sqlite3_finalize(pStmt);
+ if( *pRc==SQLITE_OK ){
+ if( rc!=SQLITE_OK ){
+ raw_printf(stderr, "SQL error: %s\n", sqlite3_errmsg(db));
+ }
+ *pRc = rc;
+ }
+ }
+}
+
+static void shellReset(
+ int *pRc,
+ sqlite3_stmt *pStmt
+){
+ int rc = sqlite3_reset(pStmt);
+ if( *pRc==SQLITE_OK ){
+ if( rc!=SQLITE_OK ){
+ sqlite3 *db = sqlite3_db_handle(pStmt);
+ raw_printf(stderr, "SQL error: %s\n", sqlite3_errmsg(db));
+ }
+ *pRc = rc;
+ }
+}
+
+/*
+** Structure representing a single ".ar" command.
+*/
+typedef struct ArCommand ArCommand;
+struct ArCommand {
+ int eCmd; /* An AR_CMD_* value */
+ const char *zFile; /* --file argument, or NULL */
+ const char *zDir; /* --directory argument, or NULL */
+ int bVerbose; /* True if --verbose */
+ int bZip; /* True if --zip */
+ int nArg; /* Number of command arguments */
+ char **azArg; /* Array of command arguments */
+};
+
+/*
+** Print a usage message for the .ar command to stderr and return SQLITE_ERROR.
+*/
+static int arUsage(FILE *f){
+ raw_printf(f,
+"\n"
+"Usage: .ar [OPTION...] [FILE...]\n"
+"The .ar command manages sqlar archives.\n"
+"\n"
+"Examples:\n"
+" .ar -cf archive.sar foo bar # Create archive.sar from files foo and bar\n"
+" .ar -tf archive.sar # List members of archive.sar\n"
+" .ar -xvf archive.sar # Verbosely extract files from archive.sar\n"
+"\n"
+"Each command line must feature exactly one command option:\n"
+" -c, --create Create a new archive\n"
+" -u, --update Update or add files to an existing archive\n"
+" -t, --list List contents of archive\n"
+" -x, --extract Extract files from archive\n"
+"\n"
+"And zero or more optional options:\n"
+" -v, --verbose Print each filename as it is processed\n"
+" -f FILE, --file FILE Operate on archive FILE (default is current db)\n"
+" -C DIR, --directory DIR Change to directory DIR to read/extract files\n"
+"\n"
+"See also: http://sqlite.org/cli.html#sqlar_archive_support\n"
+"\n"
+);
+ return SQLITE_ERROR;
+}
+
+/*
+** Print an error message for the .ar command to stderr and return
+** SQLITE_ERROR.
+*/
+static int arErrorMsg(const char *zFmt, ...){
+ va_list ap;
+ char *z;
+ va_start(ap, zFmt);
+ z = sqlite3_vmprintf(zFmt, ap);
+ va_end(ap);
+ raw_printf(stderr, "Error: %s (try \".ar --help\")\n", z);
+ sqlite3_free(z);
+ return SQLITE_ERROR;
+}
+
+/*
+** Values for ArCommand.eCmd.
+*/
+#define AR_CMD_CREATE 1
+#define AR_CMD_EXTRACT 2
+#define AR_CMD_LIST 3
+#define AR_CMD_UPDATE 4
+#define AR_CMD_HELP 5
+
+/*
+** Other (non-command) switches.
+*/
+#define AR_SWITCH_VERBOSE 6
+#define AR_SWITCH_FILE 7
+#define AR_SWITCH_DIRECTORY 8
+#define AR_SWITCH_ZIP 9
+
+static int arProcessSwitch(ArCommand *pAr, int eSwitch, const char *zArg){
+ switch( eSwitch ){
+ case AR_CMD_CREATE:
+ case AR_CMD_EXTRACT:
+ case AR_CMD_LIST:
+ case AR_CMD_UPDATE:
+ case AR_CMD_HELP:
+ if( pAr->eCmd ){
+ return arErrorMsg("multiple command options");
+ }
+ pAr->eCmd = eSwitch;
+ break;
+
+ case AR_SWITCH_VERBOSE:
+ pAr->bVerbose = 1;
+ break;
+ case AR_SWITCH_ZIP:
+ pAr->bZip = 1;
+ break;
+
+ case AR_SWITCH_FILE:
+ pAr->zFile = zArg;
+ break;
+ case AR_SWITCH_DIRECTORY:
+ pAr->zDir = zArg;
+ break;
+ }
+
+ return SQLITE_OK;
+}
+
+/*
+** Parse the command line for an ".ar" command. The results are written into
+** structure (*pAr). SQLITE_OK is returned if the command line is parsed
+** successfully, otherwise an error message is written to stderr and
+** SQLITE_ERROR returned.
+*/
+static int arParseCommand(
+ char **azArg, /* Array of arguments passed to dot command */
+ int nArg, /* Number of entries in azArg[] */
+ ArCommand *pAr /* Populate this object */
+){
+ struct ArSwitch {
+ char cShort;
+ const char *zLong;
+ int eSwitch;
+ int bArg;
+ } aSwitch[] = {
+ { 'c', "create", AR_CMD_CREATE, 0 },
+ { 'x', "extract", AR_CMD_EXTRACT, 0 },
+ { 't', "list", AR_CMD_LIST, 0 },
+ { 'u', "update", AR_CMD_UPDATE, 0 },
+ { 'h', "help", AR_CMD_HELP, 0 },
+ { 'v', "verbose", AR_SWITCH_VERBOSE, 0 },
+ { 'f', "file", AR_SWITCH_FILE, 1 },
+ { 'C', "directory", AR_SWITCH_DIRECTORY, 1 },
+ { 'z', "zip", AR_SWITCH_ZIP, 0 }
+ };
+ int nSwitch = sizeof(aSwitch) / sizeof(struct ArSwitch);
+ struct ArSwitch *pEnd = &aSwitch[nSwitch];
+
+ if( nArg<=1 ){
+ return arUsage(stderr);
+ }else{
+ char *z = azArg[1];
+ memset(pAr, 0, sizeof(ArCommand));
+
+ if( z[0]!='-' ){
+ /* Traditional style [tar] invocation */
+ int i;
+ int iArg = 2;
+ for(i=0; z[i]; i++){
+ const char *zArg = 0;
+ struct ArSwitch *pOpt;
+ for(pOpt=&aSwitch[0]; pOptcShort ) break;
+ }
+ if( pOpt==pEnd ){
+ return arErrorMsg("unrecognized option: %c", z[i]);
+ }
+ if( pOpt->bArg ){
+ if( iArg>=nArg ){
+ return arErrorMsg("option requires an argument: %c",z[i]);
+ }
+ zArg = azArg[iArg++];
+ }
+ if( arProcessSwitch(pAr, pOpt->eSwitch, zArg) ) return SQLITE_ERROR;
+ }
+ pAr->nArg = nArg-iArg;
+ if( pAr->nArg>0 ){
+ pAr->azArg = &azArg[iArg];
+ }
+ }else{
+ /* Non-traditional invocation */
+ int iArg;
+ for(iArg=1; iArgazArg = &azArg[iArg];
+ pAr->nArg = nArg-iArg;
+ break;
+ }
+ n = strlen30(z);
+
+ if( z[1]!='-' ){
+ int i;
+ /* One or more short options */
+ for(i=1; icShort ) break;
+ }
+ if( pOpt==pEnd ){
+ return arErrorMsg("unrecognized option: %c\n", z[i]);
+ }
+ if( pOpt->bArg ){
+ if( i<(n-1) ){
+ zArg = &z[i+1];
+ i = n;
+ }else{
+ if( iArg>=(nArg-1) ){
+ return arErrorMsg("option requires an argument: %c\n",z[i]);
+ }
+ zArg = azArg[++iArg];
+ }
+ }
+ if( arProcessSwitch(pAr, pOpt->eSwitch, zArg) ) return SQLITE_ERROR;
+ }
+ }else if( z[2]=='\0' ){
+ /* A -- option, indicating that all remaining command line words
+ ** are command arguments. */
+ pAr->azArg = &azArg[iArg+1];
+ pAr->nArg = nArg-iArg-1;
+ break;
+ }else{
+ /* A long option */
+ const char *zArg = 0; /* Argument for option, if any */
+ struct ArSwitch *pMatch = 0; /* Matching option */
+ struct ArSwitch *pOpt; /* Iterator */
+ for(pOpt=&aSwitch[0]; pOptzLong;
+ if( (n-2)<=strlen30(zLong) && 0==memcmp(&z[2], zLong, n-2) ){
+ if( pMatch ){
+ return arErrorMsg("ambiguous option: %s",z);
+ }else{
+ pMatch = pOpt;
+ }
+ }
+ }
+
+ if( pMatch==0 ){
+ return arErrorMsg("unrecognized option: %s", z);
+ }
+ if( pMatch->bArg ){
+ if( iArg>=(nArg-1) ){
+ return arErrorMsg("option requires an argument: %s", z);
+ }
+ zArg = azArg[++iArg];
+ }
+ if( arProcessSwitch(pAr, pMatch->eSwitch, zArg) ) return SQLITE_ERROR;
+ }
+ }
+ }
+ }
+
+ return SQLITE_OK;
+}
+
+/*
+** This function assumes that all arguments within the ArCommand.azArg[]
+** array refer to archive members, as for the --extract or --list commands.
+** It checks that each of them are present. If any specified file is not
+** present in the archive, an error is printed to stderr and an error
+** code returned. Otherwise, if all specified arguments are present in
+** the archive, SQLITE_OK is returned.
+**
+** This function strips any trailing '/' characters from each argument.
+** This is consistent with the way the [tar] command seems to work on
+** Linux.
+*/
+static int arCheckEntries(sqlite3 *db, ArCommand *pAr){
+ int rc = SQLITE_OK;
+ if( pAr->nArg ){
+ int i;
+ sqlite3_stmt *pTest = 0;
+
+ shellPreparePrintf(db, &rc, &pTest, "SELECT name FROM %s WHERE name=?1",
+ pAr->bZip ? "zipfile(?2)" : "sqlar"
+ );
+ if( rc==SQLITE_OK && pAr->bZip ){
+ sqlite3_bind_text(pTest, 2, pAr->zFile, -1, SQLITE_TRANSIENT);
+ }
+ for(i=0; inArg && rc==SQLITE_OK; i++){
+ char *z = pAr->azArg[i];
+ int n = strlen30(z);
+ int bOk = 0;
+ while( n>0 && z[n-1]=='/' ) n--;
+ z[n] = '\0';
+ sqlite3_bind_text(pTest, 1, z, -1, SQLITE_STATIC);
+ if( SQLITE_ROW==sqlite3_step(pTest) ){
+ bOk = 1;
+ }
+ shellReset(&rc, pTest);
+ if( rc==SQLITE_OK && bOk==0 ){
+ raw_printf(stderr, "not found in archive: %s\n", z);
+ rc = SQLITE_ERROR;
+ }
+ }
+ shellFinalize(&rc, pTest);
+ }
+
+ return rc;
+}
+
+/*
+** Format a WHERE clause that can be used against the "sqlar" table to
+** identify all archive members that match the command arguments held
+** in (*pAr). Leave this WHERE clause in (*pzWhere) before returning.
+** The caller is responsible for eventually calling sqlite3_free() on
+** any non-NULL (*pzWhere) value.
+*/
+static void arWhereClause(
+ int *pRc,
+ ArCommand *pAr,
+ char **pzWhere /* OUT: New WHERE clause */
+){
+ char *zWhere = 0;
+ if( *pRc==SQLITE_OK ){
+ if( pAr->nArg==0 ){
+ zWhere = sqlite3_mprintf("1");
+ }else{
+ int i;
+ const char *zSep = "";
+ for(i=0; inArg; i++){
+ const char *z = pAr->azArg[i];
+ zWhere = sqlite3_mprintf(
+ "%z%s name = '%q' OR name BETWEEN '%q/' AND '%q0'",
+ zWhere, zSep, z, z, z
+ );
+ if( zWhere==0 ){
+ *pRc = SQLITE_NOMEM;
+ break;
+ }
+ zSep = " OR ";
+ }
+ }
+ }
+ *pzWhere = zWhere;
+}
+
+/*
+** Argument zMode must point to a buffer at least 11 bytes in size. This
+** function populates this buffer with the string interpretation of
+** the unix file mode passed as the second argument (e.g. "drwxr-xr-x").
+*/
+static void shellModeToString(char *zMode, int mode){
+ int i;
+
+ /* Magic numbers copied from [man 2 stat] */
+ if( mode & 0040000 ){
+ zMode[0] = 'd';
+ }else if( (mode & 0120000)==0120000 ){
+ zMode[0] = 'l';
+ }else{
+ zMode[0] = '-';
+ }
+
+ for(i=0; i<3; i++){
+ int m = (mode >> ((2-i)*3));
+ char *a = &zMode[1 + i*3];
+ a[0] = (m & 0x4) ? 'r' : '-';
+ a[1] = (m & 0x2) ? 'w' : '-';
+ a[2] = (m & 0x1) ? 'x' : '-';
+ }
+ zMode[10] = '\0';
+}
+
+/*
+** Implementation of .ar "lisT" command.
+*/
+static int arListCommand(ShellState *p, sqlite3 *db, ArCommand *pAr){
+ const char *zSql = "SELECT %s FROM %s WHERE %s";
+ const char *zTbl = (pAr->bZip ? "zipfile(?)" : "sqlar");
+ const char *azCols[] = {
+ "name",
+ "mode, sz, datetime(mtime, 'unixepoch'), name"
+ };
+
+ char *zWhere = 0;
+ sqlite3_stmt *pSql = 0;
+ int rc;
+
+ rc = arCheckEntries(db, pAr);
+ arWhereClause(&rc, pAr, &zWhere);
+
+ shellPreparePrintf(db, &rc, &pSql, zSql, azCols[pAr->bVerbose], zTbl, zWhere);
+ if( rc==SQLITE_OK && pAr->bZip ){
+ sqlite3_bind_text(pSql, 1, pAr->zFile, -1, SQLITE_TRANSIENT);
+ }
+ while( rc==SQLITE_OK && SQLITE_ROW==sqlite3_step(pSql) ){
+ if( pAr->bVerbose ){
+ char zMode[11];
+ shellModeToString(zMode, sqlite3_column_int(pSql, 0));
+
+ raw_printf(p->out, "%s % 10d %s %s\n", zMode,
+ sqlite3_column_int(pSql, 1),
+ sqlite3_column_text(pSql, 2),
+ sqlite3_column_text(pSql, 3)
+ );
+ }else{
+ raw_printf(p->out, "%s\n", sqlite3_column_text(pSql, 0));
+ }
+ }
+
+ shellFinalize(&rc, pSql);
+ return rc;
+}
+
+
+/*
+** Implementation of .ar "eXtract" command.
+*/
+static int arExtractCommand(ShellState *p, sqlite3 *db, ArCommand *pAr){
+ const char *zSql1 =
+ "SELECT "
+ " :1 || name, "
+ " writefile(?1 || name, %s, mode, mtime) "
+ "FROM %s WHERE (%s) AND (data IS NULL OR ?2 = 0)";
+
+ const char *azExtraArg[] = {
+ "sqlar_uncompress(data, sz)",
+ "zipfile_uncompress(data, sz, method)"
+ };
+ const char *azSource[] = {
+ "sqlar", "zipfile(?3)"
+ };
+
+
+
+ sqlite3_stmt *pSql = 0;
+ int rc = SQLITE_OK;
+ char *zDir = 0;
+ char *zWhere = 0;
+ int i;
+
+ /* If arguments are specified, check that they actually exist within
+ ** the archive before proceeding. And formulate a WHERE clause to
+ ** match them. */
+ rc = arCheckEntries(db, pAr);
+ arWhereClause(&rc, pAr, &zWhere);
+
+ if( rc==SQLITE_OK ){
+ if( pAr->zDir ){
+ zDir = sqlite3_mprintf("%s/", pAr->zDir);
+ }else{
+ zDir = sqlite3_mprintf("");
+ }
+ if( zDir==0 ) rc = SQLITE_NOMEM;
+ }
+
+ shellPreparePrintf(db, &rc, &pSql, zSql1,
+ azExtraArg[pAr->bZip], azSource[pAr->bZip], zWhere
+ );
+
+ if( rc==SQLITE_OK ){
+ sqlite3_bind_text(pSql, 1, zDir, -1, SQLITE_STATIC);
+ if( pAr->bZip ){
+ sqlite3_bind_text(pSql, 3, pAr->zFile, -1, SQLITE_STATIC);
+ }
+
+ /* Run the SELECT statement twice. The first time, writefile() is called
+ ** for all archive members that should be extracted. The second time,
+ ** only for the directories. This is because the timestamps for
+ ** extracted directories must be reset after they are populated (as
+ ** populating them changes the timestamp). */
+ for(i=0; i<2; i++){
+ sqlite3_bind_int(pSql, 2, i);
+ while( rc==SQLITE_OK && SQLITE_ROW==sqlite3_step(pSql) ){
+ if( i==0 && pAr->bVerbose ){
+ raw_printf(p->out, "%s\n", sqlite3_column_text(pSql, 0));
+ }
+ }
+ shellReset(&rc, pSql);
+ }
+ shellFinalize(&rc, pSql);
+ }
+
+ sqlite3_free(zDir);
+ sqlite3_free(zWhere);
+ return rc;
+}
+
+
+/*
+** Implementation of .ar "create" and "update" commands.
+**
+** Create the "sqlar" table in the database if it does not already exist.
+** Then add each file in the azFile[] array to the archive. Directories
+** are added recursively. If argument bVerbose is non-zero, a message is
+** printed on stdout for each file archived.
+**
+** The create command is the same as update, except that it drops
+** any existing "sqlar" table before beginning.
+*/
+static int arCreateUpdate(
+ ShellState *p, /* Shell state pointer */
+ sqlite3 *db,
+ ArCommand *pAr, /* Command arguments and options */
+ int bUpdate
+){
+ const char *zSql = "SELECT name, mode, mtime, data FROM fsdir(?, ?)";
+ const char *zCreate =
+ "CREATE TABLE IF NOT EXISTS sqlar("
+ "name TEXT PRIMARY KEY, -- name of the file\n"
+ "mode INT, -- access permissions\n"
+ "mtime INT, -- last modification time\n"
+ "sz INT, -- original file size\n"
+ "data BLOB -- compressed content\n"
+ ")";
+ const char *zDrop = "DROP TABLE IF EXISTS sqlar";
+ const char *zInsert = "REPLACE INTO sqlar VALUES(?,?,?,?,sqlar_compress(?))";
+
+ sqlite3_stmt *pStmt = 0; /* Directory traverser */
+ sqlite3_stmt *pInsert = 0; /* Compilation of zInsert */
+ int i; /* For iterating through azFile[] */
+ int rc; /* Return code */
+
+ assert( pAr->bZip==0 );
+
+ rc = sqlite3_exec(db, "SAVEPOINT ar;", 0, 0, 0);
+ if( rc!=SQLITE_OK ) return rc;
+
+ if( bUpdate==0 ){
+ rc = sqlite3_exec(db, zDrop, 0, 0, 0);
+ if( rc!=SQLITE_OK ) return rc;
+ }
+
+ rc = sqlite3_exec(db, zCreate, 0, 0, 0);
+ shellPrepare(db, &rc, zInsert, &pInsert);
+ shellPrepare(db, &rc, zSql, &pStmt);
+ sqlite3_bind_text(pStmt, 2, pAr->zDir, -1, SQLITE_STATIC);
+
+ for(i=0; inArg && rc==SQLITE_OK; i++){
+ sqlite3_bind_text(pStmt, 1, pAr->azArg[i], -1, SQLITE_STATIC);
+ while( rc==SQLITE_OK && SQLITE_ROW==sqlite3_step(pStmt) ){
+ int sz;
+ const char *zName = (const char*)sqlite3_column_text(pStmt, 0);
+ int mode = sqlite3_column_int(pStmt, 1);
+ unsigned int mtime = sqlite3_column_int(pStmt, 2);
+
+ if( pAr->bVerbose ){
+ raw_printf(p->out, "%s\n", zName);
+ }
+
+ sqlite3_bind_text(pInsert, 1, zName, -1, SQLITE_STATIC);
+ sqlite3_bind_int(pInsert, 2, mode);
+ sqlite3_bind_int64(pInsert, 3, (sqlite3_int64)mtime);
+
+ if( S_ISDIR(mode) ){
+ sz = 0;
+ sqlite3_bind_null(pInsert, 5);
+ }else{
+ sqlite3_bind_value(pInsert, 5, sqlite3_column_value(pStmt, 3));
+ if( S_ISLNK(mode) ){
+ sz = -1;
+ }else{
+ sz = sqlite3_column_bytes(pStmt, 3);
+ }
+ }
+
+ sqlite3_bind_int(pInsert, 4, sz);
+ sqlite3_step(pInsert);
+ rc = sqlite3_reset(pInsert);
+ }
+ shellReset(&rc, pStmt);
+ }
+
+ if( rc!=SQLITE_OK ){
+ sqlite3_exec(db, "ROLLBACK TO ar; RELEASE ar;", 0, 0, 0);
+ }else{
+ rc = sqlite3_exec(db, "RELEASE ar;", 0, 0, 0);
+ }
+ shellFinalize(&rc, pStmt);
+ shellFinalize(&rc, pInsert);
+ return rc;
+}
+
+/*
+** Implementation of .ar "Create" command.
+**
+** Create the "sqlar" table in the database if it does not already exist.
+** Then add each file in the azFile[] array to the archive. Directories
+** are added recursively. If argument bVerbose is non-zero, a message is
+** printed on stdout for each file archived.
+*/
+static int arCreateCommand(
+ ShellState *p, /* Shell state pointer */
+ sqlite3 *db,
+ ArCommand *pAr /* Command arguments and options */
+){
+ return arCreateUpdate(p, db, pAr, 0);
+}
+
+/*
+** Implementation of .ar "Update" command.
+*/
+static int arUpdateCmd(ShellState *p, sqlite3 *db, ArCommand *pAr){
+ return arCreateUpdate(p, db, pAr, 1);
+}
+
+
+/*
+** Implementation of ".ar" dot command.
+*/
+static int arDotCommand(
+ ShellState *pState, /* Current shell tool state */
+ char **azArg, /* Array of arguments passed to dot command */
+ int nArg /* Number of entries in azArg[] */
+){
+ ArCommand cmd;
+ int rc;
+ rc = arParseCommand(azArg, nArg, &cmd);
+ if( rc==SQLITE_OK ){
+ sqlite3 *db = 0; /* Database handle to use as archive */
+
+ if( cmd.bZip ){
+ if( cmd.zFile==0 ){
+ raw_printf(stderr, "zip format requires a --file switch\n");
+ return SQLITE_ERROR;
+ }else
+ if( cmd.eCmd==AR_CMD_CREATE || cmd.eCmd==AR_CMD_UPDATE ){
+ raw_printf(stderr, "zip archives are read-only\n");
+ return SQLITE_ERROR;
+ }
+ db = pState->db;
+ }else if( cmd.zFile ){
+ int flags;
+ if( cmd.eCmd==AR_CMD_CREATE || cmd.eCmd==AR_CMD_UPDATE ){
+ flags = SQLITE_OPEN_READWRITE|SQLITE_OPEN_CREATE;
+ }else{
+ flags = SQLITE_OPEN_READONLY;
+ }
+ rc = sqlite3_open_v2(cmd.zFile, &db, flags, 0);
+ if( rc!=SQLITE_OK ){
+ raw_printf(stderr, "cannot open file: %s (%s)\n",
+ cmd.zFile, sqlite3_errmsg(db)
+ );
+ sqlite3_close(db);
+ return rc;
+ }
+ sqlite3_fileio_init(db, 0, 0);
+#ifdef SQLITE_HAVE_ZLIB
+ sqlite3_sqlar_init(db, 0, 0);
+#endif
+ }else{
+ db = pState->db;
+ }
+
+ switch( cmd.eCmd ){
+ case AR_CMD_CREATE:
+ rc = arCreateCommand(pState, db, &cmd);
+ break;
+
+ case AR_CMD_EXTRACT:
+ rc = arExtractCommand(pState, db, &cmd);
+ break;
+
+ case AR_CMD_LIST:
+ rc = arListCommand(pState, db, &cmd);
+ break;
+
+ case AR_CMD_HELP:
+ arUsage(pState->out);
+ break;
+
+ default:
+ assert( cmd.eCmd==AR_CMD_UPDATE );
+ rc = arUpdateCmd(pState, db, &cmd);
+ break;
+ }
+
+ if( cmd.zFile ){
+ sqlite3_close(db);
+ }
+ }
+
+ return rc;
+}
/*
** Implementation of ".expert" dot command.
*/
static int expertDotCommand(
@@ -4270,11 +5023,11 @@
for(i=1; rc==SQLITE_OK && i=2 && 0==strncmp(z, "-verbose", n) ){
pState->expert.bVerbose = 1;
}
else if( n>=2 && 0==strncmp(z, "-sample", n) ){
if( i==(nArg-1) ){
@@ -4306,11 +5059,10 @@
}
}
return rc;
}
-
/*
** If an input line begins with "." then invoke this routine to
** process that line.
@@ -4371,10 +5123,17 @@
}else{
sqlite3_set_authorizer(p->db, 0, 0);
}
}else
#endif
+
+#ifndef SQLITE_OMIT_VIRTUALTABLE
+ if( c=='a' && strncmp(azArg[0], "ar", n)==0 ){
+ open_db(p, 0);
+ rc = arDotCommand(p, azArg, nArg);
+ }else
+#endif
if( (c=='b' && n>=3 && strncmp(azArg[0], "backup", n)==0)
|| (c=='s' && n>=3 && strncmp(azArg[0], "save", n)==0)
){
const char *zDestFile = 0;
@@ -5147,11 +5906,11 @@
}
}else
if( c=='m' && strncmp(azArg[0], "mode", n)==0 ){
const char *zMode = nArg>=2 ? azArg[1] : "";
- int n2 = (int)strlen(zMode);
+ int n2 = strlen30(zMode);
int c2 = zMode[0];
if( c2=='l' && n2>2 && strncmp(azArg[1],"lines",n2)==0 ){
p->mode = MODE_Line;
sqlite3_snprintf(sizeof(p->rowSeparator), p->rowSeparator, SEP_Row);
}else if( c2=='c' && strncmp(azArg[1],"columns",n2)==0 ){
@@ -6386,12 +7145,11 @@
if( nArg!=4 ){
raw_printf(stderr, "Usage: .user login USER PASSWORD\n");
rc = 1;
goto meta_command_exit;
}
- rc = sqlite3_user_authenticate(p->db, azArg[2], azArg[3],
- (int)strlen(azArg[3]));
+ rc = sqlite3_user_authenticate(p->db, azArg[2], azArg[3], strlen30(azArg[3]));
if( rc ){
utf8_printf(stderr, "Authentication failed for user %s\n", azArg[2]);
rc = 1;
}
}else if( strcmp(azArg[1],"add")==0 ){
@@ -6398,12 +7156,11 @@
if( nArg!=5 ){
raw_printf(stderr, "Usage: .user add USER PASSWORD ISADMIN\n");
rc = 1;
goto meta_command_exit;
}
- rc = sqlite3_user_add(p->db, azArg[2],
- azArg[3], (int)strlen(azArg[3]),
+ rc = sqlite3_user_add(p->db, azArg[2], azArg[3], strlen30(azArg[3]),
booleanValue(azArg[4]));
if( rc ){
raw_printf(stderr, "User-Add failed: %d\n", rc);
rc = 1;
}
@@ -6411,12 +7168,11 @@
if( nArg!=5 ){
raw_printf(stderr, "Usage: .user edit USER PASSWORD ISADMIN\n");
rc = 1;
goto meta_command_exit;
}
- rc = sqlite3_user_change(p->db, azArg[2],
- azArg[3], (int)strlen(azArg[3]),
+ rc = sqlite3_user_change(p->db, azArg[2], azArg[3], strlen30(azArg[3]),
booleanValue(azArg[4]));
if( rc ){
raw_printf(stderr, "User-Edit failed: %d\n", rc);
rc = 1;
}
Index: src/test1.c
==================================================================
--- src/test1.c
+++ src/test1.c
@@ -6958,10 +6958,13 @@
extern int sqlite3_series_init(sqlite3*,char**,const sqlite3_api_routines*);
extern int sqlite3_spellfix_init(sqlite3*,char**,const sqlite3_api_routines*);
extern int sqlite3_totype_init(sqlite3*,char**,const sqlite3_api_routines*);
extern int sqlite3_wholenumber_init(sqlite3*,char**,const sqlite3_api_routines*);
extern int sqlite3_unionvtab_init(sqlite3*,char**,const sqlite3_api_routines*);
+#ifdef SQLITE_HAVE_ZLIB
+ extern int sqlite3_zipfile_init(sqlite3*,char**,const sqlite3_api_routines*);
+#endif
static const struct {
const char *zExtName;
int (*pInit)(sqlite3*,char**,const sqlite3_api_routines*);
} aExtension[] = {
{ "amatch", sqlite3_amatch_init },
@@ -6979,10 +6982,13 @@
{ "series", sqlite3_series_init },
{ "spellfix", sqlite3_spellfix_init },
{ "totype", sqlite3_totype_init },
{ "unionvtab", sqlite3_unionvtab_init },
{ "wholenumber", sqlite3_wholenumber_init },
+#ifdef SQLITE_HAVE_ZLIB
+ { "zipfile", sqlite3_zipfile_init },
+#endif
};
sqlite3 *db;
const char *zName;
int i, j, rc;
char *zErrMsg = 0;
Index: src/test_windirent.h
==================================================================
--- src/test_windirent.h
+++ src/test_windirent.h
@@ -35,10 +35,37 @@
#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.
*/
@@ -73,26 +100,31 @@
/*
** We need to provide the necessary structures and related types.
*/
+#ifndef DIRENT_DEFINED
+#define DIRENT_DEFINED
typedef struct DIRENT DIRENT;
-typedef struct DIR DIR;
typedef DIRENT *LPDIRENT;
-typedef DIR *LPDIR;
-
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
ADDED test/shell8.test
Index: test/shell8.test
==================================================================
--- /dev/null
+++ test/shell8.test
@@ -0,0 +1,168 @@
+# 2017 December 9
+#
+# 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.
+#
+#***********************************************************************
+#
+# Test the shell tool ".ar" command.
+#
+
+set testdir [file dirname $argv0]
+source $testdir/tester.tcl
+set testprefix shell8
+set CLI [test_find_cli]
+
+proc populate_dir {dirname spec} {
+ # First delete the current tree, if one exists.
+ file delete -force $dirname
+
+ # Recreate the root of the new tree.
+ file mkdir $dirname
+
+ # Add each file to the new tree.
+ foreach {f d} $spec {
+ set path [file join $dirname $f]
+ file mkdir [file dirname $path]
+ set fd [open $path w]
+ puts -nonewline $fd $d
+ close $fd
+ }
+}
+
+proc dir_to_list {dirname {n -1}} {
+ if {$n<0} {set n [llength [file split $dirname]]}
+
+ set res [list]
+ foreach f [glob -nocomplain $dirname/*] {
+ set mtime [file mtime $f]
+ if {$::tcl_platform(platform)!="windows"} {
+ set perm [file attributes $f -perm]
+ } else {
+ set perm 0
+ }
+ set relpath [file join {*}[lrange [file split $f] $n end]]
+ lappend res
+ if {[file isdirectory $f]} {
+ lappend res [list $relpath / $mtime $perm]
+ lappend res {*}[dir_to_list $f]
+ } else {
+ set fd [open $f]
+ set data [read $fd]
+ close $fd
+ lappend res [list $relpath $data $mtime $perm]
+ }
+ }
+ lsort $res
+}
+
+proc dir_compare {d1 d2} {
+ set l1 [dir_to_list $d1]
+ set l2 [dir_to_list $d1]
+ string compare $l1 $l2
+}
+
+foreach {tn tcl} {
+ 1 {
+ set c1 ".ar c ar1"
+ set x1 ".ar x"
+
+ set c2 ".ar cC ar1 ."
+ set x2 ".ar Cx ar3"
+
+ set c3 ".ar cCf ar1 test_xyz.db ."
+ set x3 ".ar Cfx ar3 test_xyz.db"
+ }
+
+ 2 {
+ set c1 ".ar -c ar1"
+ set x1 ".ar -x"
+
+ set c2 ".ar -cC ar1 ."
+ set x2 ".ar -xC ar3"
+
+ set c3 ".ar -cCar1 -ftest_xyz.db ."
+ set x3 ".ar -x -C ar3 -f test_xyz.db"
+ }
+
+ 3 {
+ set c1 ".ar --create ar1"
+ set x1 ".ar --extract"
+
+ set c2 ".ar --directory ar1 --create ."
+ set x2 ".ar --extract --dir ar3"
+
+ set c3 ".ar --creat --dir ar1 --file test_xyz.db ."
+ set x3 ".ar --e --d ar3 --f test_xyz.db"
+ }
+
+ 4 {
+ set c1 ".ar --cr ar1"
+ set x1 ".ar --e"
+
+ set c2 ".ar -C ar1 -c ."
+ set x2 ".ar -x -C ar3"
+
+ set c3 ".ar -c --directory ar1 --file test_xyz.db ."
+ set x3 ".ar -x --directory ar3 --file test_xyz.db"
+ }
+} {
+ eval $tcl
+
+ # Populate directory "ar1" with some files.
+ #
+ populate_dir ar1 {
+ file1 "abcd"
+ file2 "efgh"
+ dir1/file3 "ijkl"
+ }
+ set expected [dir_to_list ar1]
+
+ do_test 1.$tn.1 {
+ catchcmd test_ar.db $c1
+ file delete -force ar1
+ catchcmd test_ar.db $x1
+ dir_to_list ar1
+ } $expected
+
+ do_test 1.$tn.2 {
+ file delete -force ar3
+ catchcmd test_ar.db $c2
+ catchcmd test_ar.db $x2
+ dir_to_list ar3
+ } $expected
+
+ do_test 1.$tn.3 {
+ file delete -force ar3
+ file delete -force test_xyz.db
+ catchcmd ":memory:" $c3
+ catchcmd ":memory:" $x3
+ dir_to_list ar3
+ } $expected
+
+ # This is a repeat of test 1.$tn.1, except that there is a 2 second
+ # pause between creating the archive and extracting its contents.
+ # This is to test that timestamps are set correctly.
+ #
+ # Because it is slow, only do this for $tn==1.
+ if {$tn==1} {
+ do_test 1.$tn.1 {
+ catchcmd test_ar.db $c1
+ file delete -force ar1
+ after 2000
+ catchcmd test_ar.db $x1
+ dir_to_list ar1
+ } $expected
+ }
+}
+
+finish_test
+
+
+
+finish_test
+
ADDED test/zipfile.test
Index: test/zipfile.test
==================================================================
--- /dev/null
+++ test/zipfile.test
@@ -0,0 +1,77 @@
+# 2017 December 9
+#
+# 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.
+#
+#***********************************************************************
+#
+
+set testdir [file dirname $argv0]
+source $testdir/tester.tcl
+set testprefix zipfile
+
+load_static_extension db zipfile
+
+forcedelete test.zip
+do_execsql_test 1.0 {
+ CREATE VIRTUAL TABLE temp.zz USING zipfile('test.zip');
+ PRAGMA table_info(zz);
+} {
+ 0 name {} 0 {} 0
+ 1 mode {} 0 {} 0
+ 2 mtime {} 0 {} 0
+ 3 sz {} 0 {} 0
+ 4 data {} 0 {} 0
+ 5 method {} 0 {} 0
+}
+
+do_execsql_test 1.1.1 {
+ INSERT INTO zz VALUES('f.txt', '-rw-r--r--', 1000000000, 5, 'abcde', 0);
+}
+do_execsql_test 1.1.2 {
+ INSERT INTO zz VALUES('g.txt', '-rw-r--r--', 1000000002, 5, '12345', 0);
+}
+
+do_execsql_test 1.2 {
+ SELECT name, mtime, data FROM zipfile('test.zip')
+} {
+ f.txt 1000000000 abcde
+ g.txt 1000000002 12345
+}
+
+do_execsql_test 1.3 {
+ INSERT INTO zz VALUES('h.txt',
+ '-rw-r--r--', 1000000004, 20, 'aaaaaaaaaabbbbbbbbbb', NULL
+ );
+}
+
+do_execsql_test 1.4 {
+ SELECT name, mtime, zipfile_uncompress(data, sz, method), method
+ FROM zipfile('test.zip');
+} {
+ f.txt 1000000000 abcde 0
+ g.txt 1000000002 12345 0
+ h.txt 1000000004 aaaaaaaaaabbbbbbbbbb 8
+}
+
+do_execsql_test 1.5.1 {
+ BEGIN;
+ INSERT INTO zz VALUES('i.txt', '-rw-r--r--', 1000000006, 5, 'zxcvb', 0);
+ SELECT name FROM zz;
+ COMMIT;
+} {f.txt g.txt h.txt i.txt}
+do_execsql_test 1.5.2 {
+ SELECT name FROM zz;
+} {f.txt g.txt h.txt i.txt}
+
+do_execsql_test 1.6.0 {
+ DELETE FROM zz WHERE name='g.txt';
+ SELECT name FROM zz;
+} {f.txt h.txt i.txt}
+
+finish_test
+