Index: src/os_win.c ================================================================== --- src/os_win.c +++ src/os_win.c @@ -66,10 +66,18 @@ /* ** Define the required Windows SDK version constants if they are not ** already available. */ +#ifndef _WIN32_WINNT_VISTA +# define _WIN32_WINNT_VISTA 0x0600 +#endif + +#ifndef _WIN32_WINNT_WIN8 +# define _WIN32_WINNT_WIN8 0x0602 +#endif + #ifndef NTDDI_WIN8 # define NTDDI_WIN8 0x06020000 #endif #ifndef NTDDI_WINBLUE @@ -444,25 +452,35 @@ const sqlite3_mem_methods *sqlite3MemGetWin32(void); #endif /* SQLITE_WIN32_MALLOC */ /* -** The following variable is (normally) set once and never changes -** thereafter. It records whether the operating system is Win9x -** or WinNT. -** -** 0: Operating system unknown. -** 1: Operating system is Win9x. -** 2: Operating system is WinNT. -** -** In order to facilitate testing on a WinNT system, the test fixture +** The following variables are (normally) set once and never change +** thereafter. They record the major and minor OS version and whether +** the operating system type is Win9x or WinNT. +** +** sqlite3_os_type = 0: Operating system type is unknown. +** sqlite3_os_type = 1: Operating system type is Win9x. +** sqlite3_os_type = 2: Operating system type is WinNT. +** +** For the major and minor versions, the following are the "well-known" +** operating system releases (i.e. the ones that are tested for): +** +** sqlite3_os_major.sqlite3_os_minor = 6.0 = Windows Vista +** sqlite3_os_major.sqlite3_os_minor = 6.2 = Windows 8 +** +** In order to facilitate testing on WinNT systems, the test fixture ** can manually set this value to 1 to emulate Win98 behavior. */ #ifdef SQLITE_TEST LONG SQLITE_WIN32_VOLATILE sqlite3_os_type = 0; +LONG SQLITE_WIN32_VOLATILE sqlite3_os_major = 0; +LONG SQLITE_WIN32_VOLATILE sqlite3_os_minor = 0; #else static LONG SQLITE_WIN32_VOLATILE sqlite3_os_type = 0; +static LONG SQLITE_WIN32_VOLATILE sqlite3_os_major = 0; +static LONG SQLITE_WIN32_VOLATILE sqlite3_os_minor = 0; #endif #ifndef SYSCALL # define SYSCALL sqlite3_syscall_ptr #endif @@ -1008,11 +1026,12 @@ #endif #define osSetFilePointerEx ((BOOL(WINAPI*)(HANDLE,LARGE_INTEGER, \ PLARGE_INTEGER,DWORD))aSyscall[65].pCurrent) -#if SQLITE_OS_WINRT +#if SQLITE_OS_WINRT || (defined(SQLITE_WIN32_WIN8_SECTOR_SIZE) && \ + defined(_WIN32_WINNT) && _WIN32_WINNT >= _WIN32_WINNT_WIN8) { "GetFileInformationByHandleEx", (SYSCALL)GetFileInformationByHandleEx, 0 }, #else { "GetFileInformationByHandleEx", (SYSCALL)0, 0 }, #endif @@ -1131,10 +1150,43 @@ { "FlushViewOfFile", (SYSCALL)0, 0 }, #endif #define osFlushViewOfFile \ ((BOOL(WINAPI*)(LPCVOID,SIZE_T))aSyscall[79].pCurrent) + +#if defined(SQLITE_WIN32_VISTA_SECTOR_SIZE) && defined(_WIN32_WINNT) && \ + _WIN32_WINNT >= _WIN32_WINNT_VISTA + { "DeviceIoControl", (SYSCALL)DeviceIoControl, 0 }, +#else + { "DeviceIoControl", (SYSCALL)0, 0 }, +#endif + +#define osDeviceIoControl ((BOOL(WINAPI*)( \ + HANDLE,DWORD,LPVOID,DWORD,LPVOID,DWORD,LPVOID, \ + LPOVERLAPPED))aSyscall[80].pCurrent) + +#if defined(SQLITE_WIN32_VISTA_SECTOR_SIZE) && defined(_WIN32_WINNT) && \ + _WIN32_WINNT >= _WIN32_WINNT_VISTA + { "GetVolumeInformationByHandleW", (SYSCALL)GetVolumeInformationByHandleW, 0 }, +#else + { "GetVolumeInformationByHandleW", (SYSCALL)0, 0 }, +#endif + +#define osGetVolumeInformationByHandleW ((BOOL(WINAPI*)( \ + HANDLE,LPWSTR,DWORD,LPDWORD,LPDWORD,LPDWORD,LPWSTR, \ + DWORD))aSyscall[81].pCurrent) + +#if defined(SQLITE_WIN32_VISTA_SECTOR_SIZE) && defined(_WIN32_WINNT) && \ + _WIN32_WINNT >= _WIN32_WINNT_VISTA + { "GetVolumeInformationW", (SYSCALL)GetVolumeInformationW, 0 }, +#else + { "GetVolumeInformationW", (SYSCALL)0, 0 }, +#endif + +#define osGetVolumeInformationW ((BOOL(WINAPI*)( \ + LPCWSTR,LPWSTR,DWORD,LPDWORD,LPDWORD,LPDWORD,LPWSTR, \ + DWORD))aSyscall[82].pCurrent) }; /* End of the overrideable system calls */ /* ** This is the xSetSystemCall() method of sqlite3_vfs for all of the @@ -1401,10 +1453,67 @@ # define osIsNT() (0) #else # define osIsNT() ((sqlite3_os_type==2) || sqlite3_win32_is_nt()) #endif +/* +** This following version checking macros should evaluate to non-zero only +** when running on Windows Vista (or higher) or Windows 8 (or higher). +*/ + +#if !SQLITE_WIN32_GETVERSIONEX +# define osIsVistaPlus() (1) +# define osIsWin8Plus() (1) +#elif SQLITE_OS_WINCE +# define osIsVistaPlus() (0) +# define osIsWin8Plus() (0) +#elif SQLITE_OS_WINRT +# define osIsVistaPlus() (1) +# define osIsWin8Plus() (1) +#else +# define osIsVistaPlus() (winGetVersionEx() && ((sqlite3_os_major>6) || \ + ((sqlite3_os_major==6) && (sqlite3_os_minor>=0)))) +# define osIsWin8Plus() (winGetVersionEx() && ((sqlite3_os_major>6) || \ + ((sqlite3_os_major==6) && (sqlite3_os_minor>=2)))) +#endif + +/* +** This function populates the Windows version information needed by this +** module. Use of the GetVersionExA or GetVersionExW function is required. +** The return value will be non-zero if version information was queried. +*/ +#if SQLITE_WIN32_GETVERSIONEX +static int winGetVersionEx(){ + if( osInterlockedCompareExchange(&sqlite3_os_type, 0, 0)==0 ){ +#if defined(SQLITE_WIN32_HAS_ANSI) + OSVERSIONINFOA sInfo; + sInfo.dwOSVersionInfoSize = sizeof(sInfo); + osGetVersionExA(&sInfo); + osInterlockedCompareExchange(&sqlite3_os_type, + (sInfo.dwPlatformId == VER_PLATFORM_WIN32_NT) ? 2 : 1, 0); + osInterlockedCompareExchange(&sqlite3_os_major, + (LONG)sInfo.dwMajorVersion, 0); + osInterlockedCompareExchange(&sqlite3_os_minor, + (LONG)sInfo.dwMinorVersion, 0); + return 1; +#elif defined(SQLITE_WIN32_HAS_WIDE) + OSVERSIONINFOW sInfo; + sInfo.dwOSVersionInfoSize = sizeof(sInfo); + osGetVersionExW(&sInfo); + osInterlockedCompareExchange(&sqlite3_os_type, + (sInfo.dwPlatformId == VER_PLATFORM_WIN32_NT) ? 2 : 1, 0); + osInterlockedCompareExchange(&sqlite3_os_major, + (LONG)sInfo.dwMajorVersion, 0); + osInterlockedCompareExchange(&sqlite3_os_minor, + (LONG)sInfo.dwMinorVersion, 0); + return 1; +#endif + } + return 0; +} +#endif + /* ** This function determines if the machine is running a version of Windows ** based on the NT kernel. */ int sqlite3_win32_is_nt(void){ @@ -1413,25 +1522,11 @@ ** NOTE: The WinRT sub-platform is always assumed to be based on the NT ** kernel. */ return 1; #elif SQLITE_WIN32_GETVERSIONEX - if( osInterlockedCompareExchange(&sqlite3_os_type, 0, 0)==0 ){ -#if defined(SQLITE_WIN32_HAS_ANSI) - OSVERSIONINFOA sInfo; - sInfo.dwOSVersionInfoSize = sizeof(sInfo); - osGetVersionExA(&sInfo); - osInterlockedCompareExchange(&sqlite3_os_type, - (sInfo.dwPlatformId == VER_PLATFORM_WIN32_NT) ? 2 : 1, 0); -#elif defined(SQLITE_WIN32_HAS_WIDE) - OSVERSIONINFOW sInfo; - sInfo.dwOSVersionInfoSize = sizeof(sInfo); - osGetVersionExW(&sInfo); - osInterlockedCompareExchange(&sqlite3_os_type, - (sInfo.dwPlatformId == VER_PLATFORM_WIN32_NT) ? 2 : 1, 0); -#endif - } + winGetVersionEx(); return osInterlockedCompareExchange(&sqlite3_os_type, 2, 2)==2; #elif SQLITE_TEST return osInterlockedCompareExchange(&sqlite3_os_type, 2, 2)==2; #else /* @@ -3536,10 +3631,50 @@ } OSTRACE(("FCNTL file=%p, rc=SQLITE_NOTFOUND\n", pFile->h)); return SQLITE_NOTFOUND; } +/* +** Same-volume determination for versions of Windows prior to Windows 8 +** is untested, undocumented, and unsupported. All such code is omitted +** unless the SQLITE_WIN32_VISTA_SECTOR_SIZE define is set at compile-time, +** and that compile-time option is off by default and undocumented. The +** following code is for reference only. +*/ + +#if defined(SQLITE_WIN32_VISTA_SECTOR_SIZE) && \ + defined(_WIN32_WINNT) && _WIN32_WINNT >= _WIN32_WINNT_VISTA +/* +** This function attempts to determine if the specified file resides on the +** same volume as the corresponding root directory. If not, the specified +** file may be impacted by a hard link, symbolic link, or reparse point (e.g. +** junction). +** +** This function may return false even when the file is on the same volume +** as the corresponding root directory. This function may return true only +** when there is no doubt that the specified file is on the same volume as +** the corresponding root directory associated with the volume. +*/ +static BOOL winIsOnSameVolume(winFile *pFile){ + WCHAR zRoot[] = L"_:\\\0"; /* underscore will be drive letter */ + DWORD dwFileVolumeSerialNumber = 0; + DWORD dwRootVolumeSerialNumber = 0; + + if ( !osGetVolumeInformationByHandleW(pFile->h, NULL, 0, + &dwFileVolumeSerialNumber, NULL, + NULL, NULL, 0) ){ + return FALSE; + } + zRoot[0] = (WCHAR)pFile->zPath[0]; /* 'A' to 'Z' only, upper/lower case */ + if( !osGetVolumeInformationW(zRoot, NULL, 0, &dwRootVolumeSerialNumber, + NULL, NULL, NULL, 0) ){ + return FALSE; + } + return (dwFileVolumeSerialNumber == dwRootVolumeSerialNumber); +} +#endif + /* ** Return the sector size in bytes of the underlying block device for ** the specified file. This is almost always 512 bytes, but may be ** larger for some devices. ** @@ -3547,10 +3682,84 @@ ** if two files are created in the same file-system directory (i.e. ** a database and its journal file) that the sector size will be the ** same for both. */ static int winSectorSize(sqlite3_file *id){ +#if defined(SQLITE_WIN32_WIN8_SECTOR_SIZE) && \ + defined(_WIN32_WINNT) && _WIN32_WINNT >= _WIN32_WINNT_WIN8 + if( osIsWin8Plus() ){ + winFile *pFile = (winFile*)id; + FILE_STORAGE_INFO info; + memset(&info, 0, sizeof(FILE_STORAGE_INFO)); + if( osGetFileInformationByHandleEx(pFile->h, FileStorageInfo, + &info, sizeof(info)) ){ + ULONG size = info.FileSystemEffectivePhysicalBytesPerSectorForAtomicity; + OSTRACE(("SECTOR file=%p, size=%lu\n", pFile->h, size)); + if( size>0 && size<=2147483647 ){ + return (int)size; + } + }else{ + pFile->lastErrno = osGetLastError(); + winLogError(SQLITE_IOERR_FSTAT, pFile->lastErrno, + "winSectorSize1", pFile->zPath); + } + } +#endif + +/* +** Sector-size determination for versions of Windows prior to Windows 8 +** is untested, undocumented, and unsupported. All such code is omitted +** unless the SQLITE_WIN32_VISTA_SECTOR_SIZE define is set at compile-time, +** and that compile-time option is off by default and undocumented. The +** following code is for reference only. +*/ + +#if defined(SQLITE_WIN32_VISTA_SECTOR_SIZE) && \ + defined(_WIN32_WINNT) && _WIN32_WINNT >= _WIN32_WINNT_VISTA + if( osIsVistaPlus() ){ + winFile *pFile = (winFile*)id; + if( winIsDriveLetterAndColon(pFile->zPath) && winIsOnSameVolume(pFile) ){ + WCHAR zDisk[] = L"\\\\.\\_:\0"; /* underscore will be drive letter */ + HANDLE hDisk; + zDisk[4] = (WCHAR)pFile->zPath[0]; /* 'A' to 'Z' only, upper/lower case */ + assert( (zDisk[4]>=L'A' && zDisk[4]<=L'Z') + || (zDisk[4]>=L'a' && zDisk[4]<=L'z') + ); + hDisk = osCreateFileW(zDisk, 0, FILE_SHARE_READ | FILE_SHARE_WRITE, NULL, + OPEN_EXISTING, FILE_ATTRIBUTE_NORMAL, NULL); + if( hDisk!=NULL ){ + STORAGE_PROPERTY_QUERY query; + STORAGE_ACCESS_ALIGNMENT_DESCRIPTOR alignment; + DWORD bytes = 0; + memset(&query, 0, sizeof(STORAGE_PROPERTY_QUERY)); + memset(&alignment, 0, sizeof(STORAGE_ACCESS_ALIGNMENT_DESCRIPTOR)); + query.QueryType = PropertyStandardQuery; + query.PropertyId = StorageAccessAlignmentProperty; + if( osDeviceIoControl(hDisk, IOCTL_STORAGE_QUERY_PROPERTY, &query, + sizeof(STORAGE_PROPERTY_QUERY), &alignment, + sizeof(STORAGE_ACCESS_ALIGNMENT_DESCRIPTOR), + &bytes, NULL) ){ + DWORD size = alignment.BytesPerPhysicalSector; + OSTRACE(("SECTOR file=%p, size=%lu\n", pFile->h, size)); + if( size>0 && size<=2147483647 ){ + return (int)size; + } + }else{ + pFile->lastErrno = osGetLastError(); + winLogError(SQLITE_IOERR_FSTAT, pFile->lastErrno, + "winSectorSize2", pFile->zPath); + } + osCloseHandle(hDisk); + }else{ + pFile->lastErrno = osGetLastError(); + winLogError(SQLITE_IOERR_FSTAT, pFile->lastErrno, + "winSectorSize3", pFile->zPath); + } + } + } +#endif + (void)id; return SQLITE_DEFAULT_SECTOR_SIZE; } /* @@ -5921,11 +6130,11 @@ }; #endif /* Double-check that the aSyscall[] array has been constructed ** correctly. See ticket [bb3a86e890c8e96ab] */ - assert( ArraySize(aSyscall)==80 ); + assert( ArraySize(aSyscall)==83 ); /* get memory map allocation granularity */ memset(&winSysInfo, 0, sizeof(SYSTEM_INFO)); #if SQLITE_OS_WINRT osGetNativeSystemInfo(&winSysInfo);