diff options
Diffstat (limited to 'src/pal/src/file/filetime.cpp')
-rw-r--r-- | src/pal/src/file/filetime.cpp | 788 |
1 files changed, 788 insertions, 0 deletions
diff --git a/src/pal/src/file/filetime.cpp b/src/pal/src/file/filetime.cpp new file mode 100644 index 0000000000..a8666b0dff --- /dev/null +++ b/src/pal/src/file/filetime.cpp @@ -0,0 +1,788 @@ +// Licensed to the .NET Foundation under one or more agreements. +// The .NET Foundation licenses this file to you under the MIT license. +// See the LICENSE file in the project root for more information. + +/*++ + + + +Module Name: + + filetime.cpp + +Abstract: + + Implementation of the file WIN API related to file time. + +Notes: + +One very important thing to note is that on BSD systems, the stat structure +stores nanoseconds for the time-related fields. This is implemented by +replacing the time_t fields st_atime, st_mtime, and st_ctime by timespec +structures, instead named st_atimespec, st_mtimespec, and st_ctimespec. + +However, if _POSIX_SOURCE is defined, the fields are time_t values and use +their POSIX names. For compatibility purposes, when _POSIX_SOURCE is NOT +defined, the time-related fields are defined in sys/stat.h as: + +#ifndef _POSIX_SOURCE +#define st_atime st_atimespec.tv_sec +#define st_mtime st_mtimespec.tv_sec +#define st_ctime st_ctimespec.tv_sec +#endif + +Furthermore, if _POSIX_SOURCE is defined, the structure still has +additional fields for nanoseconds, named st_atimensec, st_mtimensec, and +st_ctimensec. + +In the PAL, there is a configure check to see if the system supports +nanoseconds for the time-related fields. This source file also sets macros +so that STAT_ATIME_NSEC etc. will always refer to the appropriate field +if it exists, and are defined as 0 otherwise. + +-- + +Also note that there is no analog to "creation time" on Unix systems. +Instead, we use the inode change time, which is set to the current time +whenever mtime changes or when chmod, chown, etc. syscalls modify the +file status. + + + +--*/ + +#include "pal/corunix.hpp" +#include "pal/dbgmsg.h" +#include "pal/filetime.h" +#include "pal/thread.hpp" +#include "pal/file.hpp" + +#include <sys/types.h> +#include <sys/stat.h> +#include <utime.h> +#include <time.h> + +#if HAVE_SYS_TIME_H +#include <sys/time.h> +#endif // HAVE_SYS_TIME_H + +using namespace CorUnix; + +SET_DEFAULT_DEBUG_CHANNEL(FILE); + +// In safemath.h, Template SafeInt uses macro _ASSERTE, which need to use variable +// defdbgchan defined by SET_DEFAULT_DEBUG_CHANNEL. Therefore, the include statement +// should be placed after the SET_DEFAULT_DEBUG_CHANNEL(FILE) +#include <safemath.h> + +/* Magic number explanation: + + To 1970: + Both epochs are Gregorian. 1970 - 1601 = 369. Assuming a leap + year every four years, 369 / 4 = 92. However, 1700, 1800, and 1900 + were NOT leap years, so 89 leap years, 280 non-leap years. + 89 * 366 + 280 * 365 = 134744 days between epochs. Of course + 60 * 60 * 24 = 86400 seconds per day, so 134744 * 86400 = + 11644473600 = SECS_BETWEEN_1601_AND_1970_EPOCHS. + + To 2001: + Again, both epochs are Gregorian. 2001 - 1601 = 400. Assuming a leap + year every four years, 400 / 4 = 100. However, 1700, 1800, and 1900 + were NOT leap years (2000 was because it was divisible by 400), so + 97 leap years, 303 non-leap years. + 97 * 366 + 303 * 365 = 146097 days between epochs. 146097 * 86400 = + 12622780800 = SECS_BETWEEN_1601_AND_2001_EPOCHS. + + This result is also confirmed in the MSDN documentation on how + to convert a time_t value to a win32 FILETIME. +*/ +static const __int64 SECS_BETWEEN_1601_AND_1970_EPOCHS = 11644473600LL; +static const __int64 SECS_TO_100NS = 10000000; /* 10^7 */ + +#ifdef __APPLE__ +static const __int64 SECS_BETWEEN_1601_AND_2001_EPOCHS = 12622780800LL; +#endif // __APPLE__ + +/*++ +Function: + CompareFileTime + +See MSDN doc. +--*/ +LONG +PALAPI +CompareFileTime( + IN CONST FILETIME *lpFileTime1, + IN CONST FILETIME *lpFileTime2) +{ + __int64 First; + __int64 Second; + + long Ret; + + PERF_ENTRY(CompareFileTime); + ENTRY("CompareFileTime(lpFileTime1=%p lpFileTime2=%p)\n", + lpFileTime1, lpFileTime2); + + First = ((__int64)lpFileTime1->dwHighDateTime << 32) + + lpFileTime1->dwLowDateTime; + Second = ((__int64)lpFileTime2->dwHighDateTime << 32) + + lpFileTime2->dwLowDateTime; + + if ( First < Second ) + { + Ret = -1; + } + else if ( First > Second ) + { + Ret = 1; + } + else + { + Ret = 0; + } + + LOGEXIT("CompareFileTime returns LONG %ld\n", Ret); + PERF_EXIT(CompareFileTime); + return Ret; +} + + + +/*++ +Function: + SetFileTime + +Notes: This function will drop one digit (radix 10) of precision from +the supplied times, since Unix can set to the microsecond (at most, i.e. +if the futimes() function is available). + +As noted in the file header, there is no analog to "creation time" on Unix +systems, so the lpCreationTime argument to this function will always be +ignored, and the inode change time will be set to the current time. +--*/ +BOOL +PALAPI +SetFileTime( + IN HANDLE hFile, + IN CONST FILETIME *lpCreationTime, + IN CONST FILETIME *lpLastAccessTime, + IN CONST FILETIME *lpLastWriteTime) +{ + CPalThread *pThread; + PAL_ERROR palError = NO_ERROR; + const UINT64 MAX_FILETIMEVALUE = 0x8000000000000000LL; + + PERF_ENTRY(SetFileTime); + ENTRY("SetFileTime(hFile=%p, lpCreationTime=%p, lpLastAccessTime=%p, " + "lpLastWriteTime=%p)\n", hFile, lpCreationTime, lpLastAccessTime, + lpLastWriteTime); + + pThread = InternalGetCurrentThread(); + + /* validate filetime values */ + if ( (lpCreationTime && (((UINT64)lpCreationTime->dwHighDateTime << 32) + + lpCreationTime->dwLowDateTime >= MAX_FILETIMEVALUE)) || + (lpLastAccessTime && (((UINT64)lpLastAccessTime->dwHighDateTime << 32) + + lpLastAccessTime->dwLowDateTime >= MAX_FILETIMEVALUE)) || + (lpLastWriteTime && (((UINT64)lpLastWriteTime->dwHighDateTime << 32) + + lpLastWriteTime->dwLowDateTime >= MAX_FILETIMEVALUE))) + { + pThread->SetLastError(ERROR_INVALID_HANDLE); + return FALSE; + } + + palError = InternalSetFileTime( + pThread, + hFile, + lpCreationTime, + lpLastAccessTime, + lpLastWriteTime + ); + + if (NO_ERROR != palError) + { + pThread->SetLastError(palError); + } + + LOGEXIT("SetFileTime returns BOOL %s\n", NO_ERROR == palError ? "TRUE":"FALSE"); + PERF_EXIT(SetFileTime); + return NO_ERROR == palError; +} + +PAL_ERROR +CorUnix::InternalSetFileTime( + CPalThread *pThread, + IN HANDLE hFile, + IN CONST FILETIME *lpCreationTime, + IN CONST FILETIME *lpLastAccessTime, + IN CONST FILETIME *lpLastWriteTime) +{ + PAL_ERROR palError = NO_ERROR; + IPalObject *pFileObject = NULL; + CFileProcessLocalData *pLocalData = NULL; + IDataLock *pLocalDataLock = NULL; + struct timeval Times[2]; + int fd; + long nsec; + struct stat stat_buf; + + if (INVALID_HANDLE_VALUE == hFile) + { + ERROR( "Invalid file handle\n" ); + palError = ERROR_INVALID_HANDLE; + goto InternalSetFileTimeExit; + } + + palError = g_pObjectManager->ReferenceObjectByHandle( + pThread, + hFile, + &aotFile, + GENERIC_READ, + &pFileObject + ); + + if (NO_ERROR != palError) + { + goto InternalSetFileTimeExit; + } + + palError = pFileObject->GetProcessLocalData( + pThread, + ReadLock, + &pLocalDataLock, + reinterpret_cast<void**>(&pLocalData) + ); + + if (NO_ERROR != palError) + { + goto InternalSetFileTimeExit; + } + + if (lpCreationTime) + { + palError = ERROR_NOT_SUPPORTED; + goto InternalSetFileTimeExit; + } + + if( !lpLastAccessTime && !lpLastWriteTime ) + { + // if both pointers are NULL, the function simply returns. + goto InternalSetFileTimeExit; + } + else if( !lpLastAccessTime || !lpLastWriteTime ) + { + // if either pointer is NULL, fstat will need to be called. + fd = pLocalData->unix_fd; + if ( fd == -1 ) + { + TRACE("pLocalData = [%p], fd = %d\n", pLocalData, fd); + palError = ERROR_INVALID_HANDLE; + goto InternalSetFileTimeExit; + } + + if ( fstat(fd, &stat_buf) != 0 ) + { + TRACE("fstat failed on file descriptor %d\n", fd); + palError = FILEGetLastErrorFromErrno(); + goto InternalSetFileTimeExit; + } + } + + if (lpLastAccessTime) + { + Times[0].tv_sec = FILEFileTimeToUnixTime( *lpLastAccessTime, &nsec ); + Times[0].tv_usec = nsec / 1000; /* convert to microseconds */ + } + else + { + Times[0].tv_sec = stat_buf.st_atime; + Times[0].tv_usec = ST_ATIME_NSEC(&stat_buf) / 1000; + } + + if (lpLastWriteTime) + { + Times[1].tv_sec = FILEFileTimeToUnixTime( *lpLastWriteTime, &nsec ); + Times[1].tv_usec = nsec / 1000; /* convert to microseconds */ + } + else + { + Times[1].tv_sec = stat_buf.st_mtime; + Times[1].tv_usec = ST_MTIME_NSEC(&stat_buf) / 1000; + } + + TRACE("Setting atime = [%ld.%ld], mtime = [%ld.%ld]\n", + Times[0].tv_sec, Times[0].tv_usec, + Times[1].tv_sec, Times[1].tv_usec); + +#if HAVE_FUTIMES + if ( futimes(pLocalData->unix_fd, Times) != 0 ) +#elif HAVE_UTIMES + if ( utimes(pLocalData->unix_filename, Times) != 0 ) +#else + #error Operating system not supported +#endif + { + palError = FILEGetLastErrorFromErrno(); + } + +InternalSetFileTimeExit: + if (NULL != pLocalDataLock) + { + pLocalDataLock->ReleaseLock(pThread, FALSE); + } + + if (NULL != pFileObject) + { + pFileObject->ReleaseReference(pThread); + } + + return palError; +} + + +/*++ +Function: + GetFileTime + +Notes: As noted at the top of this file, there is no analog to "creation +time" on Unix systems, so the inode change time is used instead. Also, Win32 +LastAccessTime is updated after a write operation, but it is not on Unix. +To be consistent with Win32, this function returns the greater of mtime and +atime for LastAccessTime. +--*/ +BOOL +PALAPI +GetFileTime( + IN HANDLE hFile, + OUT LPFILETIME lpCreationTime, + OUT LPFILETIME lpLastAccessTime, + OUT LPFILETIME lpLastWriteTime) +{ + CPalThread *pThread; + PAL_ERROR palError = NO_ERROR; + + PERF_ENTRY(GetFileTime); + ENTRY("GetFileTime(hFile=%p, lpCreationTime=%p, lpLastAccessTime=%p, " + "lpLastWriteTime=%p)\n", + hFile, lpCreationTime, lpLastAccessTime, lpLastWriteTime); + + pThread = InternalGetCurrentThread(); + + palError = InternalGetFileTime( + pThread, + hFile, + lpCreationTime, + lpLastAccessTime, + lpLastWriteTime + ); + + if (NO_ERROR != palError) + { + pThread->SetLastError(palError); + } + + LOGEXIT("GetFileTime returns BOOL %s\n", NO_ERROR == palError ? "TRUE":"FALSE"); + PERF_EXIT(GetFileTime); + return NO_ERROR == palError; +} + +PAL_ERROR +CorUnix::InternalGetFileTime( + CPalThread *pThread, + IN HANDLE hFile, + OUT LPFILETIME lpCreationTime, + OUT LPFILETIME lpLastAccessTime, + OUT LPFILETIME lpLastWriteTime) +{ + PAL_ERROR palError = NO_ERROR; + IPalObject *pFileObject = NULL; + CFileProcessLocalData *pLocalData = NULL; + IDataLock *pLocalDataLock = NULL; + int Fd = -1; + + struct stat StatData; + + if (INVALID_HANDLE_VALUE == hFile) + { + ERROR( "Invalid file handle\n" ); + palError = ERROR_INVALID_HANDLE; + goto InternalGetFileTimeExit; + } + + palError = g_pObjectManager->ReferenceObjectByHandle( + pThread, + hFile, + &aotFile, + GENERIC_READ, + &pFileObject + ); + + if (NO_ERROR != palError) + { + goto InternalGetFileTimeExit; + } + + palError = pFileObject->GetProcessLocalData( + pThread, + ReadLock, + &pLocalDataLock, + reinterpret_cast<void**>(&pLocalData) + ); + + if (NO_ERROR != palError) + { + goto InternalGetFileTimeExit; + } + + Fd = pLocalData->unix_fd; + + if ( Fd == -1 ) + { + TRACE("pLocalData = [%p], Fd = %d\n", pLocalData, Fd); + palError = ERROR_INVALID_HANDLE; + goto InternalGetFileTimeExit; + } + + if ( fstat(Fd, &StatData) != 0 ) + { + TRACE("fstat failed on file descriptor %d\n", Fd); + palError = FILEGetLastErrorFromErrno(); + goto InternalGetFileTimeExit; + } + + if ( lpCreationTime ) + { + *lpCreationTime = FILEUnixTimeToFileTime(StatData.st_ctime, + ST_CTIME_NSEC(&StatData)); + } + if ( lpLastWriteTime ) + { + *lpLastWriteTime = FILEUnixTimeToFileTime(StatData.st_mtime, + ST_MTIME_NSEC(&StatData)); + } + if ( lpLastAccessTime ) + { + *lpLastAccessTime = FILEUnixTimeToFileTime(StatData.st_atime, + ST_ATIME_NSEC(&StatData)); + /* if Unix mtime is greater than atime, return mtime as the last + access time */ + if ( lpLastWriteTime && + CompareFileTime(lpLastAccessTime, lpLastWriteTime) < 0 ) + { + *lpLastAccessTime = *lpLastWriteTime; + } + } + +InternalGetFileTimeExit: + if (NULL != pLocalDataLock) + { + pLocalDataLock->ReleaseLock(pThread, FALSE); + } + + if (NULL != pFileObject) + { + pFileObject->ReleaseReference(pThread); + } + + return palError; +} + + + + + + +/*++ +Function: + GetSystemTimeAsFileTime + +See MSDN doc. +--*/ +VOID +PALAPI +GetSystemTimeAsFileTime( + OUT LPFILETIME lpSystemTimeAsFileTime) +{ + struct timeval Time; + + PERF_ENTRY(GetSystemTimeAsFileTime); + ENTRY("GetSystemTimeAsFileTime(lpSystemTimeAsFileTime=%p)\n", + lpSystemTimeAsFileTime); + + if ( gettimeofday( &Time, NULL ) != 0 ) + { + ASSERT("gettimeofday() failed"); + /* no way to indicate failure, so set time to zero */ + *lpSystemTimeAsFileTime = FILEUnixTimeToFileTime( 0, 0 ); + } + else + { + /* use (tv_usec * 1000) because 2nd arg is in nanoseconds */ + *lpSystemTimeAsFileTime = FILEUnixTimeToFileTime( Time.tv_sec, + Time.tv_usec * 1000 ); + } + + LOGEXIT("GetSystemTimeAsFileTime returns.\n"); + PERF_EXIT(GetSystemTimeAsFileTime); +} + + +#ifdef __APPLE__ +/*++ +Function: + FILECFAbsoluteTimeToFileTime + +Convert a CFAbsoluteTime value to a win32 FILETIME structure, as described +in MSDN documentation. CFAbsoluteTime is the number of seconds elapsed since +00:00 01 January 2001 UTC (Mac OS X epoch), while FILETIME represents a +64-bit number of 100-nanosecond intervals that have passed since 00:00 +01 January 1601 UTC (win32 epoch). +--*/ +FILETIME FILECFAbsoluteTimeToFileTime( CFAbsoluteTime sec ) +{ + __int64 Result; + FILETIME Ret; + + Result = ((__int64)sec + SECS_BETWEEN_1601_AND_2001_EPOCHS) * SECS_TO_100NS; + + Ret.dwLowDateTime = (DWORD)Result; + Ret.dwHighDateTime = (DWORD)(Result >> 32); + + TRACE("CFAbsoluteTime = [%9f] converts to Win32 FILETIME = [%#x:%#x]\n", + sec, Ret.dwHighDateTime, Ret.dwLowDateTime); + + return Ret; +} +#endif // __APPLE__ + + +/*++ +Function: + FILEUnixTimeToFileTime + +Convert a time_t value to a win32 FILETIME structure, as described in +MSDN documentation. time_t is the number of seconds elapsed since +00:00 01 January 1970 UTC (Unix epoch), while FILETIME represents a +64-bit number of 100-nanosecond intervals that have passed since 00:00 +01 January 1601 UTC (win32 epoch). +--*/ +FILETIME FILEUnixTimeToFileTime( time_t sec, long nsec ) +{ + __int64 Result; + FILETIME Ret; + + Result = ((__int64)sec + SECS_BETWEEN_1601_AND_1970_EPOCHS) * SECS_TO_100NS + + (nsec / 100); + + Ret.dwLowDateTime = (DWORD)Result; + Ret.dwHighDateTime = (DWORD)(Result >> 32); + + TRACE("Unix time = [%ld.%09ld] converts to Win32 FILETIME = [%#x:%#x]\n", + sec, nsec, Ret.dwHighDateTime, Ret.dwLowDateTime); + + return Ret; +} + + +/*++ +Function: + FILEFileTimeToUnixTime + +See FILEUnixTimeToFileTime above. + +This function takes a win32 FILETIME structures, returns the equivalent +time_t value, and, if the nsec parameter is non-null, also returns the +nanoseconds. + +NOTE: a 32-bit time_t is only capable of representing dates between +13 December 1901 and 19 January 2038. This function will calculate the +number of seconds (positive or negative) since the Unix epoch, however if +this value is outside of the range of 32-bit numbers, the result will be +truncated on systems with a 32-bit time_t. +--*/ +time_t FILEFileTimeToUnixTime( FILETIME FileTime, long *nsec ) +{ + __int64 UnixTime; + + /* get the full win32 value, in 100ns */ + UnixTime = ((__int64)FileTime.dwHighDateTime << 32) + + FileTime.dwLowDateTime; + + /* convert to the Unix epoch */ + UnixTime -= (SECS_BETWEEN_1601_AND_1970_EPOCHS * SECS_TO_100NS); + + TRACE("nsec=%p\n", nsec); + + if ( nsec ) + { + /* get the number of 100ns, convert to ns */ + *nsec = (UnixTime % SECS_TO_100NS) * 100; + } + + UnixTime /= SECS_TO_100NS; /* now convert to seconds */ + + if ( (time_t)UnixTime != UnixTime ) + { + WARN("Resulting value is too big for a time_t value\n"); + } + + TRACE("Win32 FILETIME = [%#x:%#x] converts to Unix time = [%ld.%09ld]\n", + FileTime.dwHighDateTime, FileTime.dwLowDateTime ,(long) UnixTime, + nsec?*nsec:0L); + + return (time_t)UnixTime; +} + + + +/** +Function + + FileTimeToSystemTime() + + Helper function for FileTimeToDosTime. + Converts the necessary file time attibutes to system time, for + easier manipulation in FileTimeToDosTime. + +--*/ +BOOL PALAPI FileTimeToSystemTime( CONST FILETIME * lpFileTime, + LPSYSTEMTIME lpSystemTime ) +{ + UINT64 FileTime = 0; + time_t UnixFileTime = 0; + struct tm * UnixSystemTime = 0; + + /* Combine the file time. */ + FileTime = lpFileTime->dwHighDateTime; + FileTime <<= 32; + FileTime |= (UINT)lpFileTime->dwLowDateTime; + bool isSafe = ClrSafeInt<UINT64>::subtraction( + FileTime, + SECS_BETWEEN_1601_AND_1970_EPOCHS * SECS_TO_100NS, + FileTime); + + if (isSafe == true) + { +#if HAVE_GMTIME_R + struct tm timeBuf; +#endif /* HAVE_GMTIME_R */ + /* Convert file time to unix time. */ + if (((INT64)FileTime) < 0) + { + UnixFileTime = -1 - ( ( -FileTime - 1 ) / 10000000 ); + } + else + { + UnixFileTime = FileTime / 10000000; + } + + /* Convert unix file time to Unix System time. */ +#if HAVE_GMTIME_R + UnixSystemTime = gmtime_r( &UnixFileTime, &timeBuf ); +#else /* HAVE_GMTIME_R */ + UnixSystemTime = gmtime( &UnixFileTime ); +#endif /* HAVE_GMTIME_R */ + + /* Convert unix system time to Windows system time. */ + lpSystemTime->wDay = UnixSystemTime->tm_mday; + + /* Unix time counts January as a 0, under Windows it is 1*/ + lpSystemTime->wMonth = UnixSystemTime->tm_mon + 1; + /* Unix time returns the year - 1900, Windows returns the current year*/ + lpSystemTime->wYear = UnixSystemTime->tm_year + 1900; + + lpSystemTime->wSecond = UnixSystemTime->tm_sec; + lpSystemTime->wMinute = UnixSystemTime->tm_min; + lpSystemTime->wHour = UnixSystemTime->tm_hour; + return TRUE; + } + else + { + ERROR( "The file time is to large.\n" ); + SetLastError(ERROR_INVALID_PARAMETER); + return FALSE; + } +} + + + +/** +Function: + FileTimeToDosDateTime + + Notes due to the difference between how BSD and Windows + calculates time, this function can only repersent dates between + 1980 and 2037. 2037 is the upperlimit for the BSD time functions( 1900 - + 2037 range ). + +See msdn for more details. +--*/ +BOOL +PALAPI +FileTimeToDosDateTime( + IN CONST FILETIME *lpFileTime, + OUT LPWORD lpFatDate, + OUT LPWORD lpFatTime ) +{ + BOOL bRetVal = FALSE; + + PERF_ENTRY(FileTimeToDosDateTime); + ENTRY( "FileTimeToDosDateTime( lpFileTime=%p, lpFatDate=%p, lpFatTime=%p )\n", + lpFileTime, lpFatDate, lpFatTime ); + + /* Sanity checks. */ + if ( !lpFileTime || !lpFatDate || !lpFatTime ) + { + ERROR( "Incorrect parameters.\n" ); + SetLastError( ERROR_INVALID_PARAMETER ); + } + else + { + /* Do conversion. */ + SYSTEMTIME SysTime; + if ( FileTimeToSystemTime( lpFileTime, &SysTime ) ) + { + if ( SysTime.wYear >= 1980 && SysTime.wYear <= 2037 ) + { + *lpFatDate = 0; + *lpFatTime = 0; + + *lpFatDate |= ( SysTime.wDay & 0x1F ); + *lpFatDate |= ( ( SysTime.wMonth & 0xF ) << 5 ); + *lpFatDate |= ( ( ( SysTime.wYear - 1980 ) & 0x7F ) << 9 ); + + if ( SysTime.wSecond % 2 == 0 ) + { + *lpFatTime |= ( ( SysTime.wSecond / 2 ) & 0x1F ); + } + else + { + *lpFatTime |= ( ( SysTime.wSecond / 2 + 1 ) & 0x1F ); + } + + *lpFatTime |= ( ( SysTime.wMinute & 0x3F ) << 5 ); + *lpFatTime |= ( ( SysTime.wHour & 0x1F ) << 11 ); + + bRetVal = TRUE; + } + else + { + ERROR( "The function can only repersent dates between 1/1/1980" + " and 12/31/2037\n" ); + SetLastError( ERROR_INVALID_PARAMETER ); + } + } + else + { + ERROR( "Unable to convert file time to system time.\n" ); + SetLastError( ERROR_INVALID_PARAMETER ); + bRetVal = FALSE; + } + } + + LOGEXIT( "returning BOOL %d\n", bRetVal ); + PERF_EXIT(FileTimeToDosDateTime); + return bRetVal; +} + |