]> git.ipfire.org Git - thirdparty/sqlite.git/commitdiff
Support database file names longer than 260 characters using the new 'win32-longpath...
authormistachkin <mistachkin@noemail.net>
Wed, 28 Aug 2013 05:49:39 +0000 (05:49 +0000)
committermistachkin <mistachkin@noemail.net>
Wed, 28 Aug 2013 05:49:39 +0000 (05:49 +0000)
FossilOrigin-Name: 37e85e444cde18f061049437980b965d4485f43c

manifest
manifest.uuid
src/os_win.c
src/test1.c
test/win32longpath.test [new file with mode: 0644]

index dc3a924ab1753d92ce0850a9fb54462d2dde2cf5..c81c0d5c3e1b98ae8279be1502e5e4406de9550b 100644 (file)
--- a/manifest
+++ b/manifest
@@ -1,5 +1,5 @@
-C Make\snames\sof\sprivate\sfunctions\sin\sthe\sWin32\sVFS\sconsistent.\s\sFix\scomment\stypo\sin\sWin32\smutex\simplementation.
-D 2013-08-28T02:37:29.527
+C Support\sdatabase\sfile\snames\slonger\sthan\s260\scharacters\susing\sthe\snew\s'win32-longpath'\sVFS\svariant.
+D 2013-08-28T05:49:39.789
 F Makefile.arm-wince-mingw32ce-gcc d6df77f1f48d690bd73162294bbba7f59507c72f
 F Makefile.in 5e41da95d92656a5004b03d3576e8b226858a28e
 F Makefile.linux-gcc 91d710bdc4998cb015f39edf3cb314ec4f4d7e23
@@ -203,7 +203,7 @@ F src/os.c b4ad71336fd96f97776f75587cd9e8218288f5be
 F src/os.h 4a46270a64e9193af4a0aaa3bc2c66dc07c29b3f
 F src/os_common.h 92815ed65f805560b66166e3583470ff94478f04
 F src/os_unix.c c27a14a05061e4e690bd3949dc0246bda35e399d
-F src/os_win.c 0b9ab09dd3ccf8933f888c542a9b11b0bfa62659
+F src/os_win.c 26d752736dff0c7e4e384ab65b353cce1e7e19c5
 F src/pager.c 2aa4444ffe86e9282d03bc349a4a5e49bd77c0e8
 F src/pager.h f094af9f6ececfaa8a1e93876905a4f34233fb0c
 F src/parse.y 27c6b4138497d6f8360ba7847da6ed48033f957f
@@ -226,7 +226,7 @@ F src/sqliteLimit.h 164b0e6749d31e0daa1a4589a169d31c0dec7b3d
 F src/status.c 7ac05a5c7017d0b9f0b4bcd701228b784f987158
 F src/table.c 2cd62736f845d82200acfa1287e33feb3c15d62e
 F src/tclsqlite.c 659dad8ef30b54831306a244b43e37af4725a444
-F src/test1.c 870fc648a48cb6d6808393174f7ebe82b8c840fa
+F src/test1.c 031d00dbf1b48378e6d47af1a0f728c767320b96
 F src/test2.c 7355101c085304b90024f2261e056cdff13c6c35
 F src/test3.c 1c0e5d6f080b8e33c1ce8b3078e7013fdbcd560c
 F src/test4.c 9b32d22f5f150abe23c1830e2057c4037c45b3df
@@ -1057,6 +1057,7 @@ F test/whereF.test 136a7301512d72a08a272806c8767066311b7bc1
 F test/wherelimit.test 5e9fd41e79bb2b2d588ed999d641d9c965619b31
 F test/wild001.test bca33f499866f04c24510d74baf1e578d4e44b1c
 F test/win32lock.test 7a6bd73a5dcdee39b5bb93e92395e1773a194361
+F test/win32longpath.test 3018b1199270cb91194fc8cd8413912b6950577e
 F test/zeroblob.test caaecfb4f908f7bc086ed238668049f96774d688
 F test/zerodamage.test 209d7ed441f44cc5299e4ebffbef06fd5aabfefd
 F tool/build-all-msvc.bat c55f64ca200308fb5fa5c1ee751ea95a13977b5a x
@@ -1105,7 +1106,7 @@ F tool/warnings-clang.sh f6aa929dc20ef1f856af04a730772f59283631d4
 F tool/warnings.sh fbc018d67fd7395f440c28f33ef0f94420226381
 F tool/wherecosttest.c f407dc4c79786982a475261866a161cd007947ae
 F tool/win/sqlite.vsix 97894c2790eda7b5bce3cc79cb2a8ec2fde9b3ac
-P fc98092f4bd42d64059024f09547904c1d70a517
-R 1438e04cfc27e16b0dae733cf906e3e9
+P c3b82c5bf97cfb35544c5b1fbcdf7b9c4827d1cf
+R e20bd90758596867c642a3a14073401d
 U mistachkin
-Z 1716564dd5627ab9e624a1f87aa67863
+Z e8ceaa194e29622b0b6b8c77bb0db1ea
index 38eda4434525fbb17a0569a700429a66b13a9336..6cdcbf38360acc09e816382dfdf6d14cd6e0e686 100644 (file)
@@ -1 +1 @@
-c3b82c5bf97cfb35544c5b1fbcdf7b9c4827d1cf
\ No newline at end of file
+37e85e444cde18f061049437980b965d4485f43c
\ No newline at end of file
index ff5575ddb53f36782c982dbc7a0216f3f589da4c..89e64aa431d7c88ec0748b6059a5988496e8f405 100644 (file)
@@ -2871,7 +2871,7 @@ static void winModeBit(winFile *pFile, unsigned char mask, int *pArg){
 }
 
 /* Forward declaration */
-static int winGetTempname(int nBuf, char *zBuf);
+static int winGetTempname(sqlite3_vfs *, char **);
 #if SQLITE_MAX_MMAP_SIZE>0
 static int winMapfile(winFile*, sqlite3_int64);
 #endif
@@ -2947,13 +2947,13 @@ static int winFileControl(sqlite3_file *id, int op, void *pArg){
       return SQLITE_OK;
     }
     case SQLITE_FCNTL_TEMPFILENAME: {
-      char *zTFile = sqlite3MallocZero( pFile->pVfs->mxPathname );
-      if( zTFile ){
-        winGetTempname(pFile->pVfs->mxPathname, zTFile);
+      char *zTFile = 0;
+      int rc = winGetTempname(pFile->pVfs, &zTFile);
+      if( rc==SQLITE_OK ){
         *(char**)pArg = zTFile;
       }
-      OSTRACE(("FCNTL file=%p, rc=SQLITE_OK\n", pFile->h));
-      return SQLITE_OK;
+      OSTRACE(("FCNTL file=%p, rc=%d\n", pFile->h, rc));
+      return rc;
     }
 #if SQLITE_MAX_MMAP_SIZE>0
     case SQLITE_FCNTL_MMAP_SIZE: {
@@ -3930,17 +3930,29 @@ static void *winConvertUtf8Filename(const char *zFilename){
 }
 
 /*
-** Create a temporary file name in zBuf.  zBuf must be big enough to
-** hold at pVfs->mxPathname characters.
+** This function returns non-zero if the specified UTF-8 string buffer
+** ends with a directory separator character.
+*/
+static int winEndsInDirSep(char *zBuf){
+  if( zBuf ){
+    int nLen = sqlite3Strlen30(zBuf);
+    return nLen>0 && winIsDirSep(zBuf[nLen-1]);
+  }
+  return 0;
+}
+
+/*
+** Create a temporary file name and store the resulting pointer into pzBuf.
+** The pointer returned in pzBuf must be freed via sqlite3_free().
 */
-static int winGetTempname(int nBuf, char *zBuf){
+static int winGetTempname(sqlite3_vfs *pVfs, char **pzBuf){
   static char zChars[] =
     "abcdefghijklmnopqrstuvwxyz"
     "ABCDEFGHIJKLMNOPQRSTUVWXYZ"
     "0123456789";
   size_t i, j;
-  int nTempPath;
-  char zTempPath[SQLITE_WIN32_MAX_PATH_BYTES+2];
+  int nBuf, nLen;
+  char *zBuf;
 
   /* It's odd to simulate an io-error here, but really this is just
   ** using the io-error infrastructure to test that SQLite handles this
@@ -3948,23 +3960,49 @@ static int winGetTempname(int nBuf, char *zBuf){
   */
   SimulateIOError( return SQLITE_IOERR );
 
+  /* Allocate a temporary buffer to store the fully qualified file
+  ** name for the temporary file.  If this fails, we cannot continue.
+  */
+  nBuf = pVfs->mxPathname;
+  zBuf = sqlite3MallocZero( nBuf+2 );
+  if( !zBuf ){
+    OSTRACE(("TEMP-FILENAME rc=SQLITE_IOERR_NOMEM\n"));
+    return SQLITE_IOERR_NOMEM;
+  }
+
+  /* Figure out the effective temporary directory.  First, check if one
+  ** has been explicitly set by the application; otherwise, use the one
+  ** configured by the operating system.
+  */
+  assert( nBuf>30 );
   if( sqlite3_temp_directory ){
-    sqlite3_snprintf(SQLITE_WIN32_MAX_PATH_BYTES-30, zTempPath, "%s",
-                     sqlite3_temp_directory);
+    sqlite3_snprintf(nBuf-30, zBuf, "%s%s", sqlite3_temp_directory,
+                     winEndsInDirSep(sqlite3_temp_directory) ? "" :
+                     winGetDirDep());
   }
 #if !SQLITE_OS_WINRT
   else if( osIsNT() ){
     char *zMulti;
-    WCHAR zWidePath[SQLITE_WIN32_MAX_PATH_CHARS];
-    if( osGetTempPathW(SQLITE_WIN32_MAX_PATH_CHARS-30, zWidePath)==0 ){
+    LPWSTR zWidePath = sqlite3MallocZero( nBuf*sizeof(WCHAR) );
+    if( !zWidePath ){
+      sqlite3_free(zBuf);
+      OSTRACE(("TEMP-FILENAME rc=SQLITE_IOERR_NOMEM\n"));
+      return SQLITE_IOERR_NOMEM;
+    }
+    if( osGetTempPathW(nBuf, zWidePath)==0 ){
+      sqlite3_free(zWidePath);
+      sqlite3_free(zBuf);
       OSTRACE(("TEMP-FILENAME rc=SQLITE_IOERR_GETTEMPPATH\n"));
       return SQLITE_IOERR_GETTEMPPATH;
     }
     zMulti = winUnicodeToUtf8(zWidePath);
     if( zMulti ){
-      sqlite3_snprintf(SQLITE_WIN32_MAX_PATH_BYTES-30, zTempPath, "%s", zMulti);
+      sqlite3_snprintf(nBuf-30, zBuf, "%s", zMulti);
       sqlite3_free(zMulti);
+      sqlite3_free(zWidePath);
     }else{
+      sqlite3_free(zWidePath);
+      sqlite3_free(zBuf);
       OSTRACE(("TEMP-FILENAME rc=SQLITE_IOERR_NOMEM\n"));
       return SQLITE_IOERR_NOMEM;
     }
@@ -3972,55 +4010,43 @@ static int winGetTempname(int nBuf, char *zBuf){
 #ifdef SQLITE_WIN32_HAS_ANSI
   else{
     char *zUtf8;
-    char zMbcsPath[SQLITE_WIN32_MAX_PATH_BYTES];
-    if( osGetTempPathA(SQLITE_WIN32_MAX_PATH_BYTES-30, zMbcsPath)==0 ){
+    char *zMbcsPath = sqlite3MallocZero( nBuf );
+    if( !zMbcsPath ){
+      sqlite3_free(zBuf);
+      OSTRACE(("TEMP-FILENAME rc=SQLITE_IOERR_NOMEM\n"));
+      return SQLITE_IOERR_NOMEM;
+    }
+    if( osGetTempPathA(nBuf, zMbcsPath)==0 ){
+      sqlite3_free(zBuf);
       OSTRACE(("TEMP-FILENAME rc=SQLITE_IOERR_GETTEMPPATH\n"));
       return SQLITE_IOERR_GETTEMPPATH;
     }
     zUtf8 = sqlite3_win32_mbcs_to_utf8(zMbcsPath);
     if( zUtf8 ){
-      sqlite3_snprintf(SQLITE_WIN32_MAX_PATH_BYTES-30, zTempPath, "%s", zUtf8);
+      sqlite3_snprintf(nBuf-30, zBuf, "%s", zUtf8);
       sqlite3_free(zUtf8);
     }else{
+      sqlite3_free(zBuf);
       OSTRACE(("TEMP-FILENAME rc=SQLITE_IOERR_NOMEM\n"));
       return SQLITE_IOERR_NOMEM;
     }
   }
-#else
-  else{
-    /*
-    ** Compiled without ANSI support and the current operating system
-    ** is not Windows NT; therefore, just zero the temporary buffer.
-    */
-    memset(zTempPath, 0, SQLITE_WIN32_MAX_PATH_BYTES+2);
-  }
 #endif /* SQLITE_WIN32_HAS_ANSI */
-#else
-  else{
-    /*
-    ** Compiled for WinRT and the sqlite3_temp_directory is not set;
-    ** therefore, just zero the temporary buffer.
-    */
-    memset(zTempPath, 0, SQLITE_WIN32_MAX_PATH_BYTES+2);
-  }
 #endif /* !SQLITE_OS_WINRT */
 
   /* Check that the output buffer is large enough for the temporary file 
   ** name. If it is not, return SQLITE_ERROR.
   */
-  nTempPath = sqlite3Strlen30(zTempPath);
+  nLen = sqlite3Strlen30(zBuf);
 
-  if( (nTempPath + sqlite3Strlen30(SQLITE_TEMP_FILE_PREFIX) + 18) >= nBuf ){
+  if( (nLen + sqlite3Strlen30(SQLITE_TEMP_FILE_PREFIX) + 18) >= nBuf ){
+    sqlite3_free(zBuf);
     OSTRACE(("TEMP-FILENAME rc=SQLITE_ERROR\n"));
     return SQLITE_ERROR;
   }
 
-  for(i=nTempPath; i>0 && winIsDirSep(zTempPath[i-1]); i--){}
-  zTempPath[i] = 0;
+  sqlite3_snprintf(nBuf-18-nLen, zBuf+nLen, SQLITE_TEMP_FILE_PREFIX);
 
-  sqlite3_snprintf(nBuf-18, zBuf, (nTempPath > 0) ?
-                       "%s%s" SQLITE_TEMP_FILE_PREFIX : SQLITE_TEMP_FILE_PREFIX,
-                   zTempPath, winGetDirDep());
   j = sqlite3Strlen30(zBuf);
   sqlite3_randomness(15, &zBuf[j]);
   for(i=0; i<15; i++, j++){
@@ -4028,6 +4054,7 @@ static int winGetTempname(int nBuf, char *zBuf){
   }
   zBuf[j] = 0;
   zBuf[j+1] = 0;
+  *pzBuf = zBuf;
 
   OSTRACE(("TEMP-FILENAME name=%s, rc=SQLITE_OK\n", zBuf));
   return SQLITE_OK;
@@ -4066,7 +4093,7 @@ static int winIsDir(const void *zConverted){
 ** Open a file.
 */
 static int winOpen(
-  sqlite3_vfs *pVfs,        /* Not used */
+  sqlite3_vfs *pVfs,        /* Used to get maximum path name length */
   const char *zName,        /* Name of the file (UTF-8) */
   sqlite3_file *id,         /* Write the SQLite file handle here */
   int flags,                /* Open mode flags */
@@ -4089,7 +4116,7 @@ static int winOpen(
   /* If argument zPath is a NULL pointer, this function is required to open
   ** a temporary file. Use this buffer to store the file name in.
   */
-  char zTmpname[SQLITE_WIN32_MAX_PATH_BYTES+2]; /* Buffer for temp filename */
+  char *zTmpname = 0; /* For temporary filename, if necessary. */
 
   int rc = SQLITE_OK;            /* Function Return Code */
 #if !defined(NDEBUG) || SQLITE_OS_WINCE
@@ -4155,7 +4182,7 @@ static int winOpen(
   */
   if( !zUtf8Name ){
     assert( isDelete && !isOpenJournal );
-    rc = winGetTempname(SQLITE_WIN32_MAX_PATH_BYTES+2, zTmpname);
+    rc = winGetTempname(pVfs, &zTmpname);
     if( rc!=SQLITE_OK ){
       OSTRACE(("OPEN name=%s, rc=%s", zUtf8Name, sqlite3ErrName(rc)));
       return rc;
@@ -4173,12 +4200,14 @@ static int winOpen(
   /* Convert the filename to the system encoding. */
   zConverted = winConvertUtf8Filename(zUtf8Name);
   if( zConverted==0 ){
+    sqlite3_free(zTmpname);
     OSTRACE(("OPEN name=%s, rc=SQLITE_IOERR_NOMEM", zUtf8Name));
     return SQLITE_IOERR_NOMEM;
   }
 
   if( winIsDir(zConverted) ){
     sqlite3_free(zConverted);
+    sqlite3_free(zTmpname);
     OSTRACE(("OPEN name=%s, rc=SQLITE_CANTOPEN_ISDIR", zUtf8Name));
     return SQLITE_CANTOPEN_ISDIR;
   }
@@ -4277,6 +4306,7 @@ static int winOpen(
     pFile->lastErrno = lastErrno;
     winLogError(SQLITE_CANTOPEN, pFile->lastErrno, "winOpen", zUtf8Name);
     sqlite3_free(zConverted);
+    sqlite3_free(zTmpname);
     if( isReadWrite && !isExclusive ){
       return winOpen(pVfs, zName, id, 
          ((flags|SQLITE_OPEN_READONLY) &
@@ -4305,6 +4335,7 @@ static int winOpen(
   ){
     osCloseHandle(h);
     sqlite3_free(zConverted);
+    sqlite3_free(zTmpname);
     OSTRACE(("OPEN-CE-LOCK name=%s, rc=%s\n", zName, sqlite3ErrName(rc)));
     return rc;
   }
@@ -4314,6 +4345,7 @@ static int winOpen(
 #endif
   {
     sqlite3_free(zConverted);
+    sqlite3_free(zTmpname);
   }
 
   pFile->pMethod = &winIoMethod;
@@ -4587,7 +4619,6 @@ static int winFullPathname(
 #if defined(__CYGWIN__)
   SimulateIOError( return SQLITE_ERROR );
   UNUSED_PARAMETER(nFull);
-  assert( pVfs->mxPathname>=SQLITE_WIN32_MAX_PATH_BYTES );
   assert( nFull>=pVfs->mxPathname );
   if ( sqlite3_data_directory && !winIsVerbatimPathname(zRelative) ){
     /*
@@ -4596,15 +4627,21 @@ static int winFullPathname(
     **       for converting the relative path name to an absolute
     **       one by prepending the data directory and a slash.
     */
-    char zOut[SQLITE_WIN32_MAX_PATH_BYTES+1];
+    char *zOut = sqlite3MallocZero( pVfs->mxPathname+1 );
+    if( !zOut ){
+      winLogError(SQLITE_IOERR_NOMEM, 0, "winFullPathname", zRelative);
+      return SQLITE_IOERR_NOMEM;
+    }
     if( cygwin_conv_path(CCP_POSIX_TO_WIN_A|CCP_RELATIVE, zRelative, zOut,
-                         SQLITE_WIN32_MAX_PATH_BYTES+1)<0 ){
+                         pVfs->mxPathname+1)<0 ){
       winLogError(SQLITE_CANTOPEN_FULLPATH, (DWORD)errno, "cygwin_conv_path",
                   zRelative);
+      sqlite3_free(zOut);
       return SQLITE_CANTOPEN_FULLPATH;
     }
     sqlite3_snprintf(MIN(nFull, pVfs->mxPathname), zFull, "%s%s%s",
                      sqlite3_data_directory, winGetDirDep(), zOut);
+    sqlite3_free(zOut);
   }else{
     if( cygwin_conv_path(CCP_POSIX_TO_WIN_A, zRelative, zFull, nFull)<0 ){
       winLogError(SQLITE_CANTOPEN_FULLPATH, (DWORD)errno, "cygwin_conv_path",
@@ -4973,6 +5010,32 @@ int sqlite3_os_init(void){
     winGetSystemCall,    /* xGetSystemCall */
     winNextSystemCall,   /* xNextSystemCall */
   };
+#if defined(SQLITE_WIN32_HAS_WIDE)
+  static sqlite3_vfs winLongPathVfs = {
+    3,                   /* iVersion */
+    sizeof(winFile),     /* szOsFile */
+    SQLITE_WINNT_MAX_PATH_BYTES, /* mxPathname */
+    0,                   /* pNext */
+    "win32-longpath",    /* zName */
+    0,                   /* pAppData */
+    winOpen,             /* xOpen */
+    winDelete,           /* xDelete */
+    winAccess,           /* xAccess */
+    winFullPathname,     /* xFullPathname */
+    winDlOpen,           /* xDlOpen */
+    winDlError,          /* xDlError */
+    winDlSym,            /* xDlSym */
+    winDlClose,          /* xDlClose */
+    winRandomness,       /* xRandomness */
+    winSleep,            /* xSleep */
+    winCurrentTime,      /* xCurrentTime */
+    winGetLastError,     /* xGetLastError */
+    winCurrentTimeInt64, /* xCurrentTimeInt64 */
+    winSetSystemCall,    /* xSetSystemCall */
+    winGetSystemCall,    /* xGetSystemCall */
+    winNextSystemCall,   /* xNextSystemCall */
+  };
+#endif
 
   /* Double-check that the aSyscall[] array has been constructed
   ** correctly.  See ticket [bb3a86e890c8e96ab] */
@@ -4989,6 +5052,11 @@ int sqlite3_os_init(void){
   assert( winSysInfo.dwPageSize>0 );
 
   sqlite3_vfs_register(&winVfs, 1);
+
+#if defined(SQLITE_WIN32_HAS_WIDE)
+  sqlite3_vfs_register(&winLongPathVfs, 0);
+#endif
+
   return SQLITE_OK; 
 }
 
index 9c38b11a6dbfb24aa7be6c6501f12756069808f7..6727d48899d55fa4a64d2d419729225d9a5d1aa3 100644 (file)
@@ -5934,6 +5934,145 @@ static int win32_file_lock(
   CloseHandle(ev);
   return TCL_OK;
 }
+
+/*
+**      exists_win32_path PATH
+**
+** Returns non-zero if the specified path exists, whose fully qualified name
+** may exceed 248 characters if it is prefixed with "\\?\".
+*/
+static int win32_exists_path(
+  void *clientData,
+  Tcl_Interp *interp,
+  int objc,
+  Tcl_Obj *CONST objv[]
+){
+  if( objc!=2 ){
+    Tcl_WrongNumArgs(interp, 1, objv, "PATH");
+    return TCL_ERROR;
+  }
+  Tcl_SetObjResult(interp, Tcl_NewBooleanObj(
+      GetFileAttributesW( Tcl_GetUnicode(objv[1]))!=INVALID_FILE_ATTRIBUTES ));
+  return TCL_OK;
+}
+
+/*
+**      find_win32_file PATTERN
+**
+** Returns a list of entries in a directory that match the specified pattern,
+** whose fully qualified name may exceed 248 characters if it is prefixed with
+** "\\?\".
+*/
+static int win32_find_file(
+  void *clientData,
+  Tcl_Interp *interp,
+  int objc,
+  Tcl_Obj *CONST objv[]
+){
+  HANDLE hFindFile = INVALID_HANDLE_VALUE;
+  WIN32_FIND_DATAW findData;
+  Tcl_Obj *listObj;
+  DWORD lastErrno;
+  if( objc!=2 ){
+    Tcl_WrongNumArgs(interp, 1, objv, "PATTERN");
+    return TCL_ERROR;
+  }
+  hFindFile = FindFirstFileW(Tcl_GetUnicode(objv[1]), &findData);
+  if( hFindFile==INVALID_HANDLE_VALUE ){
+    Tcl_SetObjResult(interp, Tcl_NewWideIntObj(GetLastError()));
+    return TCL_ERROR;
+  }
+  listObj = Tcl_NewObj();
+  Tcl_IncrRefCount(listObj);
+  do {
+    Tcl_ListObjAppendElement(interp, listObj, Tcl_NewUnicodeObj(
+        findData.cFileName, -1));
+    Tcl_ListObjAppendElement(interp, listObj, Tcl_NewWideIntObj(
+        findData.dwFileAttributes));
+  } while( FindNextFileW(hFindFile, &findData) );
+  lastErrno = GetLastError();
+  if( lastErrno!=NO_ERROR && lastErrno!=ERROR_NO_MORE_FILES ){
+    FindClose(hFindFile);
+    Tcl_DecrRefCount(listObj);
+    Tcl_SetObjResult(interp, Tcl_NewWideIntObj(GetLastError()));
+    return TCL_ERROR;
+  }
+  FindClose(hFindFile);
+  Tcl_SetObjResult(interp, listObj);
+  return TCL_OK;
+}
+
+/*
+**      delete_win32_file FILENAME
+**
+** Deletes the specified file, whose fully qualified name may exceed 248
+** characters if it is prefixed with "\\?\".
+*/
+static int win32_delete_file(
+  void *clientData,
+  Tcl_Interp *interp,
+  int objc,
+  Tcl_Obj *CONST objv[]
+){
+  if( objc!=2 ){
+    Tcl_WrongNumArgs(interp, 1, objv, "FILENAME");
+    return TCL_ERROR;
+  }
+  if( !DeleteFileW(Tcl_GetUnicode(objv[1])) ){
+    Tcl_SetObjResult(interp, Tcl_NewWideIntObj(GetLastError()));
+    return TCL_ERROR;
+  }
+  Tcl_ResetResult(interp);
+  return TCL_OK;
+}
+
+/*
+**      make_win32_dir DIRECTORY
+**
+** Creates the specified directory, whose fully qualified name may exceed 248
+** characters if it is prefixed with "\\?\".
+*/
+static int win32_mkdir(
+  void *clientData,
+  Tcl_Interp *interp,
+  int objc,
+  Tcl_Obj *CONST objv[]
+){
+  if( objc!=2 ){
+    Tcl_WrongNumArgs(interp, 1, objv, "DIRECTORY");
+    return TCL_ERROR;
+  }
+  if( !CreateDirectoryW(Tcl_GetUnicode(objv[1]), NULL) ){
+    Tcl_SetObjResult(interp, Tcl_NewWideIntObj(GetLastError()));
+    return TCL_ERROR;
+  }
+  Tcl_ResetResult(interp);
+  return TCL_OK;
+}
+
+/*
+**      remove_win32_dir DIRECTORY
+**
+** Removes the specified directory, whose fully qualified name may exceed 248
+** characters if it is prefixed with "\\?\".
+*/
+static int win32_rmdir(
+  void *clientData,
+  Tcl_Interp *interp,
+  int objc,
+  Tcl_Obj *CONST objv[]
+){
+  if( objc!=2 ){
+    Tcl_WrongNumArgs(interp, 1, objv, "DIRECTORY");
+    return TCL_ERROR;
+  }
+  if( !RemoveDirectoryW(Tcl_GetUnicode(objv[1])) ){
+    Tcl_SetObjResult(interp, Tcl_NewWideIntObj(GetLastError()));
+    return TCL_ERROR;
+  }
+  Tcl_ResetResult(interp);
+  return TCL_OK;
+}
 #endif
 
 
@@ -6193,6 +6332,11 @@ int Sqlitetest1_Init(Tcl_Interp *interp){
      { "optimization_control",          optimization_control,0},
 #if SQLITE_OS_WIN
      { "lock_win32_file",               win32_file_lock,    0 },
+     { "exists_win32_path",             win32_exists_path,  0 },
+     { "find_win32_file",               win32_find_file,    0 },
+     { "delete_win32_file",             win32_delete_file,  0 },
+     { "make_win32_dir",                win32_mkdir,        0 },
+     { "remove_win32_dir",              win32_rmdir,        0 },
 #endif
      { "tcl_objproc",                   runAsObjProc,       0 },
 
diff --git a/test/win32longpath.test b/test/win32longpath.test
new file mode 100644 (file)
index 0000000..bd6eb70
--- /dev/null
@@ -0,0 +1,160 @@
+# 2013 August 27
+#
+# The author disclaims copyright to this source code.  In place of
+# a legal notice, here is a blessing:
+#
+#    May you do good and not evil.
+#    May you find forgiveness for yourself and forgive others.
+#    May you share freely, never taking more than you give.
+#
+#***********************************************************************
+# This file implements regression tests for SQLite library.  The
+# focus of this script is testing the file name handling provided
+# by the "win32-longpath" VFS.
+#
+
+if {$tcl_platform(platform)!="windows"} return
+
+set testdir [file dirname $argv0]
+source $testdir/tester.tcl
+set testprefix win32longpath
+
+proc do_remove_win32_dir {args} {
+  set nRetry [getFileRetries]     ;# Maximum number of retries.
+  set nDelay [getFileRetryDelay]  ;# Delay in ms before retrying.
+
+  foreach dirName $args {
+    # On windows, sometimes even a [remove_win32_dir] can fail just after
+    # a directory is emptied. The cause is usually "tag-alongs" - programs
+    # like anti-virus software, automatic backup tools and various explorer
+    # extensions that keep a file open a little longer than we expect,
+    # causing the delete to fail.
+    #
+    # The solution is to wait a short amount of time before retrying the
+    # removal.
+    #
+    if {$nRetry > 0} {
+      for {set i 0} {$i < $nRetry} {incr i} {
+        set rc [catch {
+          remove_win32_dir $dirName
+        } msg]
+        if {$rc == 0} break
+        if {$nDelay > 0} { after $nDelay }
+      }
+      if {$rc} { error $msg }
+    } else {
+      remove_win32_dir $dirName
+    }
+  }
+}
+
+proc do_delete_win32_file {args} {
+  set nRetry [getFileRetries]     ;# Maximum number of retries.
+  set nDelay [getFileRetryDelay]  ;# Delay in ms before retrying.
+
+  foreach fileName $args {
+    # On windows, sometimes even a [delete_win32_file] can fail just after
+    # a file is closed. The cause is usually "tag-alongs" - programs like
+    # anti-virus software, automatic backup tools and various explorer
+    # extensions that keep a file open a little longer than we expect,
+    # causing the delete to fail.
+    #
+    # The solution is to wait a short amount of time before retrying the
+    # delete.
+    #
+    if {$nRetry > 0} {
+      for {set i 0} {$i < $nRetry} {incr i} {
+        set rc [catch {
+          delete_win32_file $fileName
+        } msg]
+        if {$rc == 0} break
+        if {$nDelay > 0} { after $nDelay }
+      }
+      if {$rc} { error $msg }
+    } else {
+      delete_win32_file $fileName
+    }
+  }
+}
+
+db close
+set path [file nativename [get_pwd]]
+sqlite3 db [file join $path test.db] -vfs win32-longpath
+
+do_test 1.1 {
+  db eval {
+    BEGIN EXCLUSIVE;
+    CREATE TABLE t1(x);
+    INSERT INTO t1 VALUES(1);
+    INSERT INTO t1 VALUES(2);
+    INSERT INTO t1 VALUES(3);
+    INSERT INTO t1 VALUES(4);
+    SELECT x FROM t1 ORDER BY x;
+    COMMIT;
+  }
+} {1 2 3 4}
+
+set longPath(1) \\\\?\\$path\\[pid]
+make_win32_dir $longPath(1)
+
+set longPath(2) $longPath(1)\\[string repeat X 255]
+make_win32_dir $longPath(2)
+
+set longPath(3) $longPath(2)\\[string repeat Y 255]
+make_win32_dir $longPath(3)
+
+set fileName $longPath(3)\\test.db
+
+do_test 1.2 {
+  list [catch {sqlite3 db2 [string range $fileName 4 end]} msg] $msg
+} {1 {unable to open database file}}
+
+sqlite3 db3 $fileName -vfs win32-longpath
+
+do_test 1.3 {
+  db3 eval {
+    PRAGMA journal_mode = WAL;
+  }
+} {wal}
+
+do_test 1.4 {
+  db3 eval {
+    BEGIN EXCLUSIVE;
+    CREATE TABLE t1(x);
+    INSERT INTO t1 VALUES(5);
+    INSERT INTO t1 VALUES(6);
+    INSERT INTO t1 VALUES(7);
+    INSERT INTO t1 VALUES(8);
+    SELECT x FROM t1 ORDER BY x;
+    COMMIT;
+  }
+} {5 6 7 8}
+
+db3 close
+# puts "  Database exists \{[exists_win32_path $fileName]\}"
+
+sqlite3 db3 $fileName -vfs win32-longpath
+
+do_test 1.5 {
+  db3 eval {
+    BEGIN EXCLUSIVE;
+    INSERT INTO t1 VALUES(9);
+    INSERT INTO t1 VALUES(10);
+    INSERT INTO t1 VALUES(11);
+    INSERT INTO t1 VALUES(12);
+    SELECT x FROM t1 ORDER BY x;
+    COMMIT;
+  }
+} {5 6 7 8 9 10 11 12}
+
+db3 close
+# puts "  Database exists \{[exists_win32_path $fileName]\}"
+
+do_delete_win32_file $fileName
+# puts "  Files remaining \{[find_win32_file $longPath(3)\\*]\}"
+
+do_remove_win32_dir $longPath(3)
+do_remove_win32_dir $longPath(2)
+do_remove_win32_dir $longPath(1)
+
+finish_test