/*
* Copyright 1995, 1996 Perforce Software. All rights reserved.
*
* This file is part of Perforce - the FAST SCM System.
*/
# define NEED_FCNTL
# define NEED_FILE
# define NEED_STAT
# define NEED_UTIME
# define NEED_ERRNO
# define NEED_SLEEP
# define NEED_CHDIR
# include <stdhdrs.h>
# include <error.h>
# include <errornum.h>
# include <strbuf.h>
# include <debug.h>
# include <tunable.h>
# include <datetime.h>
# include <i18napi.h>
# include <charcvt.h>
# include <lockfile.h>
# include <largefile.h>
# include <share.h>
# include <mbstring.h>
# include "filesys.h"
# include "pathsys.h"
# include "fileio.h"
extern int global_umask;
# define utimbufL _utimbuf
# define DOUNICODE ( CharSetApi::isUnicode((CharSetApi::CharSet)GetCharSetPriv()) )
// The REPARSE_DATA_BUFFER is part of the "Windows Driver Kit" according to
// the MSDN docs, so for the time being we just copy the structure here:
//
// For MinGW builds, the mingw x86 version grouped the DDK into the
// winnt.h header. The newer mingw-w64 is more like Visual Studio
// in that you must include ddk/ntifs.h for the reparse structure.
// We defined the reparse structure only for OS_NT and OS_MINGWW64.
//
# if defined( OS_MINGWW64 ) == defined( OS_MINGW )
typedef struct _REPARSE_DATA_BUFFER {
ULONG ReparseTag;
USHORT ReparseDataLength;
USHORT Reserved;
union {
// if ReparseTag == IO_REPARSE_TAG_SYMLINK:
struct {
USHORT SubstituteNameOffset;
USHORT SubstituteNameLength;
USHORT PrintNameOffset;
USHORT PrintNameLength;
ULONG Flags;
WCHAR PathBuffer[1];
} SymbolicLinkReparseBuffer;
// if ReparseTag == IO_REPARSE_TAG_MOUNT_POINT:
struct {
USHORT SubstituteNameOffset;
USHORT SubstituteNameLength;
USHORT PrintNameOffset;
USHORT PrintNameLength;
WCHAR PathBuffer[1];
} MountPointReparseBuffer;
struct {
UCHAR DataBuffer[1];
} GenericReparseBuffer;
} ;
} REPARSE_DATA_BUFFER, *PREPARSE_DATA_BUFFER;
# endif
// We also include a handful of relevant magic constants from the device
// driver development kit here:
//
#ifndef FSCTL_GET_REPARSE_POINT
// define FSCTL_GET_REPARSE_POINT CTL_CODE(FILE_DEVICE_FILE_SYSTEM, 42, METHOD_BUFFERED, FILE_ANY_ACCESS)
# define FSCTL_GET_REPARSE_POINT 0x000900A8
# endif
# ifndef IO_REPARSE_TAG_SYMLINK
# define IO_REPARSE_TAG_SYMLINK 0xA000000CL
# endif
# ifndef S_ISDIR
# define S_ISDIR(m) (((m)&S_IFMT)==S_IFDIR)
# endif
# ifndef SYMBOLIC_LINK_FLAG_DIRECTORY
# define SYMBOLIC_LINK_FLAG_DIRECTORY 1
# endif
# ifndef MAXIMUM_REPARSE_DATA_BUFFER_SIZE
# define MAXIMUM_REPARSE_DATA_BUFFER_SIZE ( 16 * 1024 )
# endif
typedef BOOLEAN (WINAPI *CreateSymbolicLinkAProc)(LPCSTR,LPCSTR,DWORD);
typedef BOOLEAN (WINAPI *CreateSymbolicLinkWProc)(LPCWSTR,LPCWSTR,DWORD);
static CreateSymbolicLinkAProc CreateSymbolicLinkA_func = 0;
static CreateSymbolicLinkWProc CreateSymbolicLinkW_func = 0;
static int functionHandlesLoaded = 0;
// Handle the Unicode and LFN file name translation.
// Caller to nt_wname() must free the memory.
//
void
nt_free_wname( const wchar_t *wname )
{
delete [] (char *)wname;
}
const wchar_t *
nt_wname( StrPtr *fname, int lfn, int *newlen )
{
CharSetCvtUTF816 cvt;
wchar_t *wname;
StrBuf lfname;
const char *filename;
int namelen;
// We want one of these two long filename forms.
// lfn==1: \\?\c:\path
// lfn==2: \\?\UNC\host\share\path
// In the UNC case, fname will have two leading back slashes.
// Use an offset to eliminate one leading slash in fname.
//
if( lfn )
{
if( lfn == 2 )
lfname.Set( "\\\\?\\UNC" );
else
lfname.Set( "\\\\?\\" );
if( FileSys::IsRelative( *fname ) )
{
StrBuf cwd;
cwd.Alloc( _MAX_PATH );
_getcwd( cwd.Text(), cwd.Length() );
cwd.SetLength();
PathSys *p = PathSys::Create();
p->SetLocal( cwd, *fname );
lfname.Append( p->Text() );
delete p;
}
else
{
if( lfn == 2 )
lfname.Append( &(fname->Text()[1]) );
else
lfname.Append( fname->Text() );
}
for(int len=0 ; len < lfname.Length(); ++len )
{
if( lfname.Text()[len] == '/' )
lfname.Text()[len] = '\\';
}
filename = lfname.Text();
namelen = lfname.Length();
}
else
{
filename = fname->Text();
namelen = fname->Length();
}
// wname is allocated as a char *, use nt_free_wname when done.
//
if( newlen != NULL )
wname = (wchar_t *)cvt.CvtBuffer( filename, namelen, newlen );
else
wname = (wchar_t *)cvt.CvtBuffer( filename, namelen );
// No error structure, instead return a NULL.
if ( cvt.LastErr() != CharSetCvt::NONE )
{
// Right now we are not treating a conversion error as fatal.
// We do not set error codes which allows them to linger.
if( wname )
nt_free_wname( wname );
return NULL;
}
return wname;
}
int
nt_convtime( SYSTEMTIME *systime )
{
struct tm u_tm;
time_t t;
// Do the converstion twice. First time gets the TZ from
// the systime. Second time we have the correct TZ to
// produce the correct time_t.
//
u_tm.tm_sec = systime->wSecond;
u_tm.tm_min = systime->wMinute; u_tm.tm_hour = systime->wHour;
u_tm.tm_mday = systime->wDay;
u_tm.tm_mon = systime->wMonth - 1;
u_tm.tm_year = systime->wYear - 1900;
u_tm.tm_wday = 0;
u_tm.tm_yday = 0;
u_tm.tm_isdst = 0;
t = mktime( &u_tm );
u_tm.tm_sec = systime->wSecond;
u_tm.tm_min = systime->wMinute;
u_tm.tm_hour = systime->wHour;
u_tm.tm_mday = systime->wDay;
u_tm.tm_mon = systime->wMonth - 1;
u_tm.tm_year = systime->wYear - 1900;
u_tm.tm_wday = 0;
u_tm.tm_yday = 0;
t = mktime( &u_tm );
return t;
}
// This function is only for LFN support.
// This function does not handle wild cards, neither does the MS version.
// We do not fabricate c:/ or //host/share/
// This only handles absolute path names with a drive spec.
// Mostly taken from VS vc/crt/src/stat.c
//
// Limit this function to the VS2013 compiler.
//
int
nt_wstati64( const wchar_t *wname, struct statbL *sb )
{
# if (_MSC_VER >= 1800)
HANDLE findhandle;
WIN32_FIND_DATAW findbuf;
errno = 0;
findhandle = FindFirstFileExW (
wname,
FindExInfoStandard,
&findbuf,
FindExSearchNameMatch,
NULL, 0);
// File does not exist.
if( findhandle == INVALID_HANDLE_VALUE )
{
errno = ENOENT;
return -1;
}
FindClose( findhandle );
if( (findbuf.dwFileAttributes & FILE_ATTRIBUTE_REPARSE_POINT) &&
(findbuf.dwReserved0 == IO_REPARSE_TAG_SYMLINK) )
{
int fd = -1;
errno_t e;
int oflag = _O_RDONLY;
int ret=0;
if( findbuf.dwFileAttributes & FILE_ATTRIBUTE_DIRECTORY )
oflag |= _O_OBTAIN_DIR;
e = _wsopen_s( &fd, wname, oflag, _SH_DENYNO, 0 );
if( e != 0 || fd == -1 )
return -1;
ret = _fstati64( fd, sb );
close( fd );
return ret;
}
// Sort out file times.
//
SYSTEMTIME SysTime;
// Range testing on SystemTimeToTzSpecificLocalTime()
// Lowest date which will convert correctly
// quad=0x430e234000, highpart=0x43, lowpart=0xe234000
// quad=288000000000, highpart=67, lowpart=237191168
// Highest date which will convert correctly
// quad=0x7fff35f4f06c8000, highpart=0x7fff35f4, lowpart=0xf06c8000
// quad=9223149888000000000, highpart=2147431924, lowpart=4033642496
//
// Range testing on FileTimeToSystemTime()
// Lowest is quad=0
// Highest is quad=0x8000000000000000
//
// Both failure conditions return this error,
// WinAPI - ERROR_INVALID_PARAMETER
// CRT - EINVAL
if( findbuf.ftLastWriteTime.dwLowDateTime ||
findbuf.ftLastWriteTime.dwHighDateTime )
{
if( !FileTimeToSystemTime( &findbuf.ftLastWriteTime, &SysTime ) ||
!SystemTimeToTzSpecificLocalTime( NULL, &SysTime, &SysTime ) )
{
errno = EINVAL;
return -1;
}
sb->st_mtime = nt_convtime( &SysTime );
}
if( findbuf.ftLastAccessTime.dwLowDateTime ||
findbuf.ftLastAccessTime.dwHighDateTime )
{
if( !FileTimeToSystemTime( &findbuf.ftLastAccessTime, &SysTime ) ||
!SystemTimeToTzSpecificLocalTime( NULL, &SysTime, &SysTime ) )
{
errno = EINVAL;
return -1;
}
sb->st_atime = nt_convtime( &SysTime );
}
if( findbuf.ftCreationTime.dwLowDateTime ||
findbuf.ftCreationTime.dwHighDateTime )
{
if( !FileTimeToSystemTime( &findbuf.ftCreationTime, &SysTime ) ||
!SystemTimeToTzSpecificLocalTime( NULL, &SysTime, &SysTime ) )
{
errno = EINVAL;
return -1;
}
sb->st_ctime = nt_convtime( &SysTime );
}
// A=0, B=1, etc.
//
const wchar_t *p;
if( p = wcschr(wname, L':') )
sb->st_rdev = sb->st_dev = (_dev_t)(_mbctolower(*--p) - 0x61);
else
sb->st_rdev = sb->st_dev = 0;
// Sort out the Unix style file modes.
//
unsigned short uxmode = 0;
// Watch out, a directory can have FILE_ATTRIBUTE_ARCHIVE set.
//
if( findbuf.dwFileAttributes & FILE_ATTRIBUTE_DIRECTORY )
uxmode |= _S_IFDIR|_S_IEXEC;
else
if( findbuf.dwFileAttributes & FILE_ATTRIBUTE_NORMAL ||
findbuf.dwFileAttributes & FILE_ATTRIBUTE_ARCHIVE )
{
uxmode |= _S_IFREG;
}
if( findbuf.dwFileAttributes & FILE_ATTRIBUTE_READONLY )
uxmode |= _S_IREAD;
else
uxmode |= _S_IREAD|_S_IWRITE;
// The correct way to determine if a file is executable is to
// use Access Control Lists, ACLs. This is nasty stuff and you
// can dive in a bit by using the icacls command line tool.
// Apparently Cygwin does a better job with ACLs, hence job032715.
// The code below is basically a MS hack to simulate S_IEXEC.
//
if( p = wcsrchr(wname, L'.') )
{
if( _wcsicmp(p, L".exe") == 0 ||
_wcsicmp(p, L".cmd") == 0 ||
_wcsicmp(p, L".bat") == 0 ||
_wcsicmp(p, L".com") == 0 )
uxmode |= _S_IEXEC;
}
uxmode |= (uxmode & 0700) >> 3;
uxmode |= (uxmode & 0700) >> 6;
sb->st_mode = uxmode;
// You can use GetFileInformationByHandle() to get the hardlink
// count. We don't have a file handle here. We do the same
// thing as MS, just set st_nlink to 1.
//
sb->st_nlink = 1;
// 64bit file size.
//
sb->st_size = ((__int64)(findbuf.nFileSizeHigh)) * (0x100000000i64) +
(__int64)(findbuf.nFileSizeLow);
// Windows doesn't really have a uid or gid. Using ACLs it is
// possible to come up with these numbers. Although they will not
// be in the ranges as you have on Unix. So we do the smae thing
// as MS, and assign them to 0. You can get a file ID by using
// GetFileInformationByHandle(), it is a 64bit value.
//
sb->st_uid = sb->st_gid = sb->st_ino = 0;
return 0;
# else
return -1;
# endif
}
int
ntw_islink( StrPtr *fname, DWORD *dwFlags, int lfn )
{
DWORD fileAttributes;
const wchar_t *wname;
wname = nt_wname( fname, lfn, NULL );
if( !wname )
return -1;
fileAttributes = GetFileAttributesW( wname );
if( fileAttributes == INVALID_FILE_ATTRIBUTES )
{
nt_free_wname( wname );
return -1;
}
if( !(fileAttributes & FILE_ATTRIBUTE_REPARSE_POINT) )
{
nt_free_wname( wname );
return 0;
}
if( dwFlags )
{
*dwFlags = 0;
if( fileAttributes & FILE_ATTRIBUTE_DIRECTORY )
*dwFlags = SYMBOLIC_LINK_FLAG_DIRECTORY;
}
HANDLE fH;
WIN32_FIND_DATAW findFileDataW;
fH = FindFirstFileW( wname, &findFileDataW );
nt_free_wname( wname );
if( fH == INVALID_HANDLE_VALUE )
return -1;
FindClose( fH );
if( findFileDataW.dwReserved0 == IO_REPARSE_TAG_SYMLINK ||
findFileDataW.dwReserved0 == IO_REPARSE_TAG_MOUNT_POINT )
return 1;
return 0;
}
int
nt_islink( StrPtr *fname, DWORD *dwFlags, int dounicode, int lfn )
{
// Allow unicode to fall through.
if( dounicode || lfn )
{
int ret;
if( (ret = ntw_islink( fname, dwFlags, lfn )) >= 0 || lfn )
return ret;
}
DWORD fileAttributes = GetFileAttributes( fname->Text() );
if( fileAttributes == INVALID_FILE_ATTRIBUTES )
return -1;
if( dwFlags )
{
*dwFlags = 0;
if( fileAttributes & FILE_ATTRIBUTE_DIRECTORY )
*dwFlags = SYMBOLIC_LINK_FLAG_DIRECTORY;
}
if( fileAttributes & FILE_ATTRIBUTE_REPARSE_POINT )
{
WIN32_FIND_DATA findFileData;
HANDLE fH = FindFirstFile( fname->Text(), &findFileData );
if( fH == INVALID_HANDLE_VALUE )
return -1;
FindClose( fH );
if( findFileData.dwReserved0 == IO_REPARSE_TAG_SYMLINK ||
findFileData.dwReserved0 == IO_REPARSE_TAG_MOUNT_POINT )
return 1;
}
return 0;
}
// Open the file in Unicode mode, hand control back to nt_readlink().
// Return the file handle.
//
HANDLE
ntw_readlink( StrPtr *name, StrBuf &targetBuf, int lfn )
{
HANDLE fH;
const wchar_t *wname;
wname = nt_wname( name, lfn, NULL );
if( !wname )
return INVALID_HANDLE_VALUE;
fH = CreateFileW( wname,
GENERIC_READ, FILE_SHARE_READ,
0, OPEN_EXISTING,
(FILE_FLAG_BACKUP_SEMANTICS|
FILE_FLAG_OPEN_REPARSE_POINT),
0);
nt_free_wname( wname );
return fH;
}
// Reads what the symlink points to, puts the data into targetBuf.
// Returns the number of bytes read.
//
int
nt_readlink( StrPtr *name, StrBuf &targetBuf, int dounicode, int lfn )
{
HANDLE fH = INVALID_HANDLE_VALUE;
// Allow unicode to fall through.
if( dounicode || lfn )
{
fH = ntw_readlink( name, targetBuf, lfn );
if( fH == INVALID_HANDLE_VALUE && lfn )
return -1;
}
if( fH == INVALID_HANDLE_VALUE )
{
fH = CreateFile( name->Text(), GENERIC_READ, FILE_SHARE_READ,
0, OPEN_EXISTING,
(FILE_FLAG_BACKUP_SEMANTICS|FILE_FLAG_OPEN_REPARSE_POINT), 0);
}
if( fH == INVALID_HANDLE_VALUE )
return -1;
// If the extra memory allocated at the end of REPARSE_DATA_BUFFER
// is not large enough, the code ERROR_MORE_DATA is returned.
//
// The MS docs for DeviceIoControl() indicate that when the
// error code ERROR_MORE_DATA is returned, "Your application
// should call DeviceIoControl again with the same operation,
// specifying a new starting point".
//
// MS confirmed that this comment is not valid when using
// DeviceIoControl() for collecting the symlink target. The
// extra memory must be enough for the symlink target. Also
// DeviceIoControl() will not tell you the required buffer size.
// MS admits that the error code ERROR_INSUFFICIENT_BUFFER
// would have been a better return code.
//
// So we allocate the maximum allowed extra memory at the end
// of the REPARSE_DATA_BUFFER, 16k. This equates to a maximum
// of 4096 in length for a Windows symlink target. Testing
// symlink creation with a mixture of targets confirms this.
//
REPARSE_DATA_BUFFER *reparseBuffer;
// The REPARSE_DATA_BUFFER size and room for the symlink target.
DWORD struct_siz = sizeof(REPARSE_DATA_BUFFER) +
MAXIMUM_REPARSE_DATA_BUFFER_SIZE;
reparseBuffer = (REPARSE_DATA_BUFFER *) malloc( struct_siz );
reparseBuffer->ReparseDataLength = MAXIMUM_REPARSE_DATA_BUFFER_SIZE;
DWORD returnedLength;
DWORD result = DeviceIoControl( fH, FSCTL_GET_REPARSE_POINT, 0, 0,
reparseBuffer, struct_siz, &returnedLength, 0 );
CloseHandle( fH );
if( !result )
{
free( reparseBuffer );
return -1;
}
int len, off;
WCHAR *wp;
// This is low-level device driver and file system filter data
// structures, so we tread gently. By observation, the substitute
// name and the print name are similar, but the substitute name,
// particularly for junctions, seems to often point to the so-called
// "non-parsed string", which starts "\??\". I haven't found any
// docs about that magic string prefix, and have been successfully
// using the PrintName representation instead.
//
if( reparseBuffer->ReparseTag == IO_REPARSE_TAG_SYMLINK )
{
len = reparseBuffer->SymbolicLinkReparseBuffer.PrintNameLength;
off = reparseBuffer->SymbolicLinkReparseBuffer.PrintNameOffset;
wp = reparseBuffer->SymbolicLinkReparseBuffer.PathBuffer;
}
else if( reparseBuffer->ReparseTag == IO_REPARSE_TAG_MOUNT_POINT )
{
len = reparseBuffer->MountPointReparseBuffer.PrintNameLength;
off = reparseBuffer->MountPointReparseBuffer.PrintNameOffset;
wp = reparseBuffer->MountPointReparseBuffer.PathBuffer;
}
else
{
free( reparseBuffer );
return -1;
}
len = len / sizeof(WCHAR);
off = off / sizeof(WCHAR);
wp += off;
int retlen = len;
targetBuf.Alloc( len );
char *o = targetBuf.Text();
while( len-- )
{
char c = *wp++;
// Use forward slashes, storing in Unix format.
*o++ = c == '\\' ? '/' : c;
}
*o = 0;
targetBuf.SetLength();
free( reparseBuffer );
return retlen;
}
static int
ntw_open( StrPtr *fname, int flags, int mode, int lfn )
{
int newlen = 0;
const wchar_t *wname;
wname = nt_wname( fname, lfn, &newlen );
if( !wname )
return -1;
// Length check for unicode.
// If LFN and Unicode are grouped, this can be removed.
if( !lfn && newlen > ( MAX_PATH * 2 ) )
{
nt_free_wname( wname );
SetLastError( ERROR_BUFFER_OVERFLOW );
return -1;
}
int fd = _wopen( (const wchar_t *)wname, flags, mode );
nt_free_wname( wname );
return fd;
}
static int
nt_open( StrPtr *fname, int flags, int mode, int dounicode, int lfn )
{
# ifdef O_NOINHERIT
// All files on Windows are set to no inherit.
//
flags |= O_NOINHERIT;
# endif
// Allow unicode to fall through.
if( dounicode || lfn )
{
int fd;
if( (fd = ntw_open( fname, flags, mode, lfn ) ) >= 0 || lfn )
return fd;
}
if( fname->Length() > MAX_PATH )
{
SetLastError( ERROR_BUFFER_OVERFLOW );
return -1;
}
return ::open(fname->Text(), flags, mode);
}
static int
ntw_stat( StrPtr *fname, struct statbL *sb, int lfn )
{
int ret;
int newlen = 0;
const wchar_t *wname;
wname = nt_wname( fname, lfn, &newlen );
if ( !wname )
return -1;
// Length check for unicode.
// If LFN and Unicode are grouped, this can be removed.
if( !lfn && newlen > ( MAX_PATH * 2 ) )
{
nt_free_wname( wname );
SetLastError( ERROR_BUFFER_OVERFLOW );
return -1;
}
if( lfn )
ret = nt_wstati64( wname, sb ); // LFN
else
ret = _wstati64( wname, sb ); // Unicode
nt_free_wname( wname );
return ret;
}
static int
nt_stat( StrPtr *fname, struct statbL *sb, int dounicode, int lfn )
{
// Allow unicode to fall through.
if( dounicode || lfn )
{
int ret;
if( (ret = ntw_stat( fname, sb, lfn ) ) >= 0 || lfn )
return ret;
}
if( fname->Length() > MAX_PATH )
{
SetLastError( ERROR_BUFFER_OVERFLOW );
return -1;
}
return ::_stati64( fname->Text(), sb );
}
static int
ntw_unlink( StrPtr *fname, int lfn )
{
DWORD dwFlags;
const wchar_t *wname=NULL;
wname = nt_wname( fname, lfn, NULL );
if ( !wname )
return -1;
if( ntw_islink( fname, &dwFlags, lfn ) >= 0 )
{
if( dwFlags == SYMBOLIC_LINK_FLAG_DIRECTORY )
{
BOOL bRet = RemoveDirectoryW( wname );
nt_free_wname( wname );
return bRet ? 0 : -1;
}
else
{
int ret = _wunlink( wname );
nt_free_wname( wname );
return ret;
}
}
else
nt_free_wname( wname );
return -1;
}
static int
nt_unlink( StrPtr *fname, int dounicode, int lfn )
{
DWORD dwFlags;
// Allow unicode to fall through.
if( dounicode || lfn )
{
int ret;
if( (ret = ntw_unlink( fname, lfn )) >= 0 || lfn )
return ret;
}
// no error returned if directory is not removed.
if( nt_islink( fname, &dwFlags, dounicode, lfn ) > 0 &&
dwFlags == SYMBOLIC_LINK_FLAG_DIRECTORY &&
RemoveDirectory( fname->Text() ) )
return 0;
return ::_unlink( fname->Text() );
}
static HANDLE
nt_openDirOrFileHandleW( StrPtr *fname, DWORD flags, int lfn )
{
HANDLE fH;
const wchar_t *wname;
wname = nt_wname( fname, lfn, NULL );
if( !wname )
return INVALID_HANDLE_VALUE;
fH = CreateFileW( wname,
FILE_WRITE_ATTRIBUTES,
( FILE_SHARE_READ | FILE_SHARE_WRITE ),
NULL,
OPEN_EXISTING,
FILE_ATTRIBUTE_NORMAL,
NULL);
nt_free_wname( wname );
return fH;
}
static HANDLE
nt_openHandleW( StrPtr *fname, int lfn )
{
return nt_openDirOrFileHandleW( fname, FILE_ATTRIBUTE_NORMAL, lfn );
}
static HANDLE
nt_openDirHandleW( StrPtr *fname, int lfn )
{
return nt_openDirOrFileHandleW( fname,
( FILE_FLAG_BACKUP_SEMANTICS | FILE_ATTRIBUTE_NORMAL ), lfn );
}
static HANDLE
nt_openDirOrFileHandle( const char *fname, DWORD flags )
{
return CreateFile( fname,
FILE_WRITE_ATTRIBUTES,
( FILE_SHARE_READ | FILE_SHARE_WRITE ),
NULL,
OPEN_EXISTING,
flags,
NULL);
}
static HANDLE
nt_openHandle( const char *fname )
{
return nt_openDirOrFileHandle( fname, FILE_ATTRIBUTE_NORMAL );
}
static HANDLE
nt_openDirHandle( const char *fname )
{
return nt_openDirOrFileHandle( fname,
( FILE_FLAG_BACKUP_SEMANTICS | FILE_ATTRIBUTE_NORMAL ) );
}
// This code corrected a DST datetime problem, job039200
// As of Visual Studio 2013, the problem has been fixed.
// Keep this fix as we still build with older Visual Studios.
//
static int nt_convertToFileTime( time_t t32, FILETIME *ft)
{
SYSTEMTIME st;
struct tm *u_tm;
u_tm = ::gmtime( &t32 );
if( !u_tm )
return -1;
st.wMilliseconds = 0;
st.wDayOfWeek = 0;
st.wSecond = u_tm->tm_sec;
st.wMinute = u_tm->tm_min;
st.wHour = u_tm->tm_hour;
st.wDay = u_tm->tm_mday;
st.wMonth = u_tm->tm_mon + 1;
st.wYear = u_tm->tm_year + 1900;
SystemTimeToFileTime( &st, ft );
return 0;
}
static int
nt_setFileTimes( HANDLE hFile, time_t t32 )
{
FILETIME ft;
int result;
if( hFile == INVALID_HANDLE_VALUE || t32 == -1 ||
nt_convertToFileTime( t32, &ft ) )
return -1;
result = SetFileTime( hFile, (LPFILETIME)0, (LPFILETIME)0, &ft ) != 0
? 0 : -1 ;
CloseHandle( hFile );
return result;
}
static int
ntw_utime( StrPtr *fname, struct utimbufL *ut, int lfn )
{
const wchar_t *wname;
int ret;
wname = nt_wname( fname, lfn, NULL );
if( !wname )
return -1;
ret = nt_setFileTimes( nt_openHandleW( fname, lfn ), ut->modtime );
nt_free_wname( wname );
return ret;
}
static int
nt_utime( StrPtr *fname, struct utimbufL *ut, int dounicode, int lfn)
{
// Allow unicode to fall through.
if( dounicode || lfn )
{
int ret;
if ( (ret = ntw_utime( fname, ut, lfn ) ) >= 0 || lfn )
return ret;
}
return nt_setFileTimes( nt_openHandle( fname->Text() ), ut->modtime );
}
static int
ntw_chmod( StrPtr *fname, int m, int lfn )
{
const wchar_t *wname;
int ret;
wname = nt_wname( fname, lfn, NULL );
if( !wname )
return -1;
ret = _wchmod( (const wchar_t *)wname, m );
nt_free_wname( wname );
return ret;
}
static int
nt_chmod( StrPtr *fname, int m, int dounicode, int lfn )
{
// Allow unicode to fall through.
if( dounicode || lfn )
{
int ret;
if ((ret = ntw_chmod( fname, m, lfn )) >= 0 || lfn )
return ret;
}
return ::_chmod( fname->Text(), m );
}
static int
ntw_rename( StrPtr *fname, StrPtr *nname, int lfn )
{
const wchar_t *wname;
const wchar_t *wnname;
int ret;
wname = nt_wname( fname, lfn, NULL );
if( !wname )
return -1;
wnname = nt_wname( nname, lfn, NULL );
if( !wnname )
{
nt_free_wname( wname );
return -1;
}
ret = _wrename( wname, wnname );
nt_free_wname( wname );
nt_free_wname( wnname );
return ret;
}
static int
nt_rename( StrPtr *fname, StrPtr *nname, int dounicode, int lfn )
{
// Allow unicode to fall through.
if( dounicode || lfn )
{
int ret;
if( (ret=ntw_rename( fname, nname, lfn )) >= 0 || lfn )
return ret;
}
return ::rename( fname->Text(), nname->Text() );
}
// This code is only used on the client.
// Target must be absolute, can not chdir for LFN.
// Must call this function through nt_makelink().
int
ntw_makelink( StrBuf &target, StrPtr *name, DWORD dwFlags, int lfn )
{
int result = -1;
// For the symlink target we do not want a LFN path.
const wchar_t *wtarget = nt_wname( &target, 0, NULL );
if( !wtarget )
return -1;
const wchar_t *wname = nt_wname( name, lfn, NULL );
if( !wname )
{
nt_free_wname( wtarget );
return -1;
}
if( (*CreateSymbolicLinkW_func)( wname, wtarget, dwFlags ) )
result = 0;
nt_free_wname( wtarget );
nt_free_wname( wname );
return result;
}
// This code is only used on the client.
int
nt_makelink( StrBuf &target, StrPtr *name, int dounicode, int lfn )
{
int result = -1;
StrBuf n_tgt;
StrBuf abs_tgt;
if( !FileSys::SymlinksSupported() )
return result;
// Copy and normalize the target of the symlink for Windows.
char *symlink = target.Text();
n_tgt.Set( target.Text() );
char *p = n_tgt.Text();
while( *symlink )
{
if( *symlink == '/' ) *p = '\\';
p++;
symlink++;
}
*p = '\0';
// Create an absolute target for the stat().
if( FileSys::IsRelative( n_tgt ) )
{
PathSys *pth = PathSys::Create();
pth->Set( name->Text() );
pth->ToParent();
pth->SetLocal( StrRef( pth->Text() ), n_tgt );
abs_tgt.Set( pth->Text() );
}
else
abs_tgt.Set( n_tgt.Text() );
struct statbL sb;
DWORD dwFlags = 0;
// Try to stat the target of the symlink, directory or file.
// If the stat fails, we assume a file symlink.
if( nt_stat( &abs_tgt, &sb, dounicode, lfn ) >= 0 )
{
if( S_ISDIR( sb.st_mode ) )
dwFlags = SYMBOLIC_LINK_FLAG_DIRECTORY;
}
// Allow unicode to fall through.
// Using target maintains relative symlinks.
if( dounicode || lfn )
{
int ret;
if( (ret = ntw_makelink( n_tgt, name, dwFlags, lfn )) >= 0 || lfn )
return ret;
}
if( (*CreateSymbolicLinkA_func)(name->Text(), n_tgt.Text(), dwFlags) )
result = 0;
return result;
}
void
FileIO::Rename( FileSys *target, Error *e )
{
// On VMS and Novelle, the source must be writable (deletable,
// actually) for the rename() to unlink it. So we give it write
// perm first and then revert it to original perms after.
Chmod( FPM_RW, e );
// Don't unlink the target unless the source exists,
// as our rename isn't atomic (like on UNIX) and some
// stumblebum user may have removed the source file.
if( e->Test() )
return;
// Remember original perms of target so we can reset on failure.
FilePerm oldPerms =
( target->Stat() & FSF_WRITEABLE ) ? FPM_RW : FPM_RO;
// One customer (in Iceland) wanted this for IRIX as well.
// You need if you are you running NFS aginst NT as well
// if you are running on NT. Gag me!
//
// To support case-changing a file, rename needs to NOT
// unlink the file in this case, this is mainly client support.
const StrPtr *targetPath = target->Path();
if( ( Path()->Length() != targetPath->Length() ) ||
Path()->Compare( *targetPath ) )
{
target->Unlink( 0 ); // yeech - must not exist to rename
}
if( nt_rename( Path(), target->Path(), DOUNICODE, LFN ) < 0 )
{
// nasty hack coming up.
// one customer is suffering from a rename() problem
// that requires more diagnostics, so we will retry
// the rename() 10 times with 1 second interval and
// log any failure.
int ret = 0;
int renameMax = p4tunable.Get( P4TUNE_SYS_RENAME_MAX );
int renameWait = p4tunable.Get( P4TUNE_SYS_RENAME_WAIT );
for( int i=0; i < renameMax; ++i )
{
msleep( renameWait );
target->Unlink( 0 );
ret = nt_rename( Path(), target->Path(), DOUNICODE, LFN );
if( ret >= 0 )
break;
}
if( ret < 0 )
{
StrBuf b;
b << "failed to rename " << target->Name()
<< " after " << StrNum( renameMax ) << " attempts";
e->Sys( "rename", b.Text() );
// failed, restore original target perms.
target->Perms( oldPerms );
target->Chmod( e );
return;
}
}
// reset the target to our perms
target->Perms( perms );
target->Chmod( e );
// source file has been deleted, clear the flag
ClearDeleteOnClose();
}
/*
* FileIO::Unlink() - remove single file (error optional)
*/
void
FileIO::Unlink( Error *e )
{
int ret;
// yeech - must be writable to remove
if( *Name() )
nt_chmod( Path(), PERM_0666 & ~global_umask, DOUNICODE, LFN );
if( *Name() && nt_unlink( Path(), DOUNICODE, LFN ) < 0 && e )
e->Sys( "unlink", Name() );
}
// Caller must free the memory.
wchar_t *
FileIO::UnicodeName( StrBuf *fname, int lfn )
{
wchar_t *ret;
ret = (wchar_t *)nt_wname( fname, lfn, NULL );
if( !ret )
return NULL;
return ret;
}
void
FileIO::ChmodTime( int modTime, Error *e )
{
struct utimbufL t;
t.actime = 0; // This is ignored by nt_utime
t.modtime = DateTime::Localize( modTime );
if( nt_utime( Path(), &t, DOUNICODE, LFN ) < 0 )
e->Sys( "utime", Name() );
}
void
FileIO::Truncate( offL_t offset, Error *e )
{
// Don't bother if non-existent.
if( !( Stat() & FSF_EXISTS ) )
return;
int success = 1;
# ifdef HAVE_TRUNCATE
HANDLE hFile;
if( DOUNICODE || LFN )
{
const wchar_t *wname;
wname = nt_wname( Path(), LFN, NULL );
if( !wname )
{
e->Sys( "truncate", Name() );
return;
}
hFile = CreateFileW( wname, GENERIC_WRITE, FILE_SHARE_WRITE,
NULL, OPEN_EXISTING,
FILE_ATTRIBUTE_NORMAL, NULL );
nt_free_wname( wname );
}
else
{
hFile = CreateFile( Name(), GENERIC_WRITE, FILE_SHARE_WRITE,
NULL, OPEN_EXISTING,
FILE_ATTRIBUTE_NORMAL, NULL );
}
if (hFile == INVALID_HANDLE_VALUE)
{
e->Sys( "truncate", Name() );
return;
}
LARGE_INTEGER offset_li;
offset_li.QuadPart = offset;
success = SetFilePointerEx( hFile, offset_li, 0, FILE_BEGIN ) &&
SetEndOfFile( hFile );
CloseHandle( hFile );
# endif // HAVE_TRUNCATE
if( !success )
e->Sys( "truncate", Name() );
}
void
FileIO::Truncate( Error *e )
{
// Don't bother if non-existent.
if( !( Stat() & FSF_EXISTS ) )
return;
// Try truncate first; if that fails (as it will on secure NCR's),
// then open O_TRUNC.
int fd;
if( ( fd = nt_open( Path(), O_WRONLY|O_TRUNC, PERM_0666,
DOUNICODE, LFN ) ) >= 0 )
{
close( fd );
return;
}
e->Sys( "truncate", Name() );
}
/*
* FileIO::Stat() - return flags if file exists
*/
int
FileIO::Stat()
{
// Stat & check for missing, special
int flags = 0;
struct statbL sb;
StrBuf abs_tgt;
if( FileSys::SymlinksSupported() &&
nt_islink( Path(), NULL, DOUNICODE, LFN ) > 0 )
{
StrBuf linkTarget;
// The StrBuf allocation is done in nt_readlink().
if( nt_readlink( Path(), linkTarget, DOUNICODE, LFN ) < 0 )
return flags;
flags = FSF_SYMLINK;
// Create an absolute path for the symlink target.
if( FileSys::IsRelative( linkTarget ) )
{
PathSys *pth = PathSys::Create();
pth->Set( Name() );
pth->ToParent();
pth->SetLocal( StrRef( pth->Text() ), linkTarget );
abs_tgt.Set( pth->Text() );
}
else
abs_tgt.Set( Name() );
if( nt_stat( &abs_tgt, &sb, DOUNICODE, LFN ) >= 0 )
flags |= FSF_EXISTS;
return flags;
}
if( nt_stat( Path(), &sb, DOUNICODE, LFN ) < 0 )
return flags;
flags |= FSF_EXISTS;
if( sb.st_mode & S_IWUSR ) flags |= FSF_WRITEABLE;
if( sb.st_mode & S_IXUSR ) flags |= FSF_EXECUTABLE;
if( S_ISDIR( sb.st_mode ) ) flags |= FSF_DIRECTORY;
if( !S_ISREG( sb.st_mode ) ) flags |= FSF_SPECIAL;
if( !sb.st_size ) flags |= FSF_EMPTY;
return flags;
}
int
FileIO::GetOwner()
{
int uid = 0;
struct statbL sb;
StrBuf abs_tgt;
if( FileSys::SymlinksSupported() &&
nt_islink( Path(), NULL, DOUNICODE, LFN ) > 0 )
{
StrBuf linkTarget;
// The StrBuf allocation is done in nt_readlink().
if( nt_readlink( Path(), linkTarget, DOUNICODE, LFN ) < 0 )
return uid;
// Create an absolute path for the target.
if( FileSys::IsRelative( *Path() ) )
{
PathSys *pth = PathSys::Create();
pth->Set( Name() );
pth->ToParent();
pth->SetLocal( StrRef( pth->Text() ), linkTarget );
abs_tgt.Set( pth->Text() );
}
else
abs_tgt.Set( Name() );
if( nt_stat( &abs_tgt, &sb, DOUNICODE, LFN ) >= 0 )
uid = sb.st_uid;
return uid;
}
if( nt_stat( Path(), &sb, DOUNICODE, LFN ) >= 0 )
uid = sb.st_uid;
return uid;
}
bool
FileIO::HasOnlyPerm( FilePerm perms )
{
# ifdef false
/*
* This code does not work on windows since the
* windows does not handle the notion of group and world
* permissions in the same way unix does. Brent is looking
* into seeing if there is a way to assure security on
* the credentials directory and file. For now commented out.
*/
struct statbL sb;
int modeBits = 0;
if( nt_stat( Path(), &sb, DOUNICODE, LFN ) < 0 )
return false;
switch (perms)
{
case FPM_RO:
modeBits = PERM_0222;
break;
case FPM_RW:
modeBits = PERM_0666;
break;
case FPM_ROO:
modeBits = PERM_0400;
break;
case FPM_RXO:
modeBits = PERM_0500;
break;
case FPM_RWO:
modeBits = PERM_0600;
break;
case FPM_RWXO:
modeBits = PERM_0700;
break;
}
/*
* In this case we want an exact match of permissions
* We don't want to "and" to a mask, since we also want
* to verify that the other bits are off.
*/
if( (sb.st_mode & PERMSMASK) == modeBits )
return true;
return false;
# else
return true;
# endif //ifdef 0
}
# ifdef OS_MINGW
static int
nt_getLastModifiedTime( HANDLE hFile )
{
// Convert file timestamp to local time, then to time_t.
// This is because MINGW doesn't have _mkgmtime, but does have mktime.
SYSTEMTIME st;
SYSTEMTIME stUTC;
struct tm u_tm;
FILETIME cTime, aTime, mTime;
BOOL bRet;
if (hFile == INVALID_HANDLE_VALUE)
return -1;
// Avoid leaking the handle.
bRet = GetFileTime( hFile, &cTime, &aTime, &mTime );
CloseHandle( hFile );
if( !bRet )
return -1;
FileTimeToSystemTime( &mTime, &stUTC );
SystemTimeToTzSpecificLocalTime( NULL, &stUTC, &st );
u_tm.tm_sec = st.wSecond;
u_tm.tm_min = st.wMinute;
u_tm.tm_hour = st.wHour;
u_tm.tm_mday = st.wDay;
u_tm.tm_mon = st.wMonth - 1;
u_tm.tm_year = st.wYear - 1900;
u_tm.tm_wday = 0;
u_tm.tm_yday = 0;
u_tm.tm_isdst = 0;
return (int)( DateTime::Centralize( ::mktime( &u_tm ) ) );
}
# else
// This code corrected a DST datetime problem, job039200
// As of Visual Studio 2013, the problem has been fixed.
// We must keep this fix since we still build with older Visual Studios.
//
static int
nt_getLastModifiedTime( HANDLE hFile )
{
SYSTEMTIME st;
struct tm u_tm;
FILETIME cTime, aTime, mTime;
BOOL bRet;
if (hFile == INVALID_HANDLE_VALUE)
return -1;
// Avoid leaking the handle.
bRet = GetFileTime( hFile, &cTime, &aTime, &mTime );
CloseHandle( hFile );
if( !bRet )
return -1;
FileTimeToSystemTime( &mTime, &st );
u_tm.tm_sec = st.wSecond;
u_tm.tm_min = st.wMinute;
u_tm.tm_hour = st.wHour;
u_tm.tm_mday = st.wDay;
u_tm.tm_mon = st.wMonth - 1;
u_tm.tm_year = st.wYear - 1900;
u_tm.tm_wday = 0;
u_tm.tm_yday = 0;
u_tm.tm_isdst = 0;
return (int)( DateTime::Centralize( ::_mkgmtime( &u_tm ) ) );
}
# endif
int
FileIO::StatModTime()
{
HANDLE fH;
StrPtr *fname = Path();
if( DOUNICODE || LFN )
{
// nt_openHandleW() does the unicode filename translation.
if( nt_islink( fname, NULL, DOUNICODE, LFN ) > 0 )
fH = nt_openHandleW( fname, LFN );
else
fH = nt_openDirHandleW( fname, LFN );
if( fH != INVALID_HANDLE_VALUE )
return nt_getLastModifiedTime( fH );
// We know LFN can not fall through and succeed.
// Unicode case continues to fall through.
if( LFN )
return -1;
}
if( nt_islink( fname, NULL, DOUNICODE, LFN ) > 0 )
return nt_getLastModifiedTime( nt_openDirHandle( fname->Text() ) );
return nt_getLastModifiedTime( nt_openHandle( fname->Text() ) );
}
void
FileIO::Chmod( FilePerm perms, Error *e )
{
// Don't set perms on symlinks
if( ( GetType() & FST_MASK ) == FST_SYMLINK )
return;
// Permissions for readonly/readwrite, exec vs no exec
int bits = IsExec() ? PERM_0777 : PERM_0666;
switch( perms )
{
case FPM_RO: bits &= ~PERM_0222; break;
case FPM_ROO: bits &= ~PERM_0266; break;
case FPM_RWO: bits = PERM_0600; break; // for key file, set exactly to rwo
case FPM_RXO: bits = PERM_0500; break;
case FPM_RWXO: bits = PERM_0700; break;
}
if( nt_chmod( Path(), bits & ~global_umask, DOUNICODE, LFN ) >= 0 )
return;
// Can be called with e==0 to ignore error.
if( e )
e->Sys( "chmod", Name() );
}
void
FileIOBinary::Open( FileOpenMode mode, Error *e )
{
// Save mode for write, close
this->mode = mode;
// Get bits for (binary) open
int bits = openModes[ mode ].bflags;
// Handle exclusive open (must not already exist)
# ifdef O_EXCL
// Set O_EXCL to ensure we create the file when we open it.
if( GetType() & FST_M_EXCL )
bits |= O_EXCL;
# else
// No O_EXCL: we'll manually check if file already exists.
// Not atomic, but what can one do?
if( GetType() & FST_M_EXCL && Stat() & FSF_EXISTS )
{
e->Set( E_FAILED, "file exists" );
// if file is set delete on close unset that because we
// didn't create the file...
ClearDeleteOnClose();
return;
}
# endif
// open stdin/stdout or real file
if( Name()[0] == '-' && !Name()[1] )
{
// we do raw output: flush stdout
// for nice mixing of messages.
if( mode == FOM_WRITE )
fflush( stdout );
fd = openModes[ mode ].standard;
}
else if( (fd = nt_open( Path(), bits, PERM_0666, DOUNICODE, LFN )) <0 )
{
e->Sys( openModes[ mode ].modeName, Name() );
# ifdef O_EXCL
// if we failed to create the file probably due to the
// file already existing (O_EXCL)
// then unset delete on close because we didn't create it...
if( ( bits & (O_EXCL|O_CREAT) ) == (O_EXCL|O_CREAT) )
ClearDeleteOnClose();
# endif
}
if( e->Test() )
return;
// Do we need to preallocate (fragmentation ?)
offL_t sizeOffSet = GetSizeHint();
if( sizeOffSet )
{
FileIOBinary::Seek( sizeOffSet - (offL_t)1, e );
if( !e->Test() )
{
char endFile = 0;
FileIOBinary::Write( &endFile, 1, e );
FileIOBinary::Seek( (offL_t)0, e );
}
}
}
offL_t
FileIOBinary::GetSize()
{
struct _stati64 sb;
int ret;
if( nt_stat( Path(), &sb, DOUNICODE, LFN ) < 0 )
return -1;
return sb.st_size;
}
void
FileIOBinary::Seek( offL_t offset, Error *e )
{
if( _lseeki64( fd, offset, 0 ) == -1 && e )
e->Sys( "Seek", Name() );
tellpos = offset;
}
void
FileIOAppend::Open( FileOpenMode mode, Error *e )
{
// Save mode for write, close
this->mode = mode;
// open stdin/stdout or real file
if( Name()[0] == '-' && !Name()[1] )
{
fd = openModes[ mode ].standard;
}
else if( ( fd = nt_open( Path(), openModes[ mode ].aflags,
PERM_0666, DOUNICODE, LFN ) ) < 0 )
{
e->Sys( openModes[ mode ].modeName, Name() );
}
}
// Should work with unicode and LFN.
offL_t
FileIOAppend::GetSize()
{
offL_t s = 0;
if( !lockFile( fd, LOCKF_SH ) )
{
BY_HANDLE_FILE_INFORMATION bhfi;
if( GetFileInformationByHandle(
(HANDLE)_get_osfhandle( fd ), &bhfi ) )
s = ((offL_t)(bhfi.nFileSizeHigh)) * (0x100000000LL) +
(offL_t)(bhfi.nFileSizeLow);
lockFile( fd, LOCKF_UN );
}
else
s = FileIOBinary::GetSize();
return s;
}
void
FileIOAppend::Write( const char *buf, int len, Error *e )
{
// We do an unbuffered write here to guarantee the atomicity
// of the write. Stdio might break it up into chunks, whereas
// write() is supposed to keep it whole.
if( lockFile( fd, LOCKF_EX ) < 0 )
{
e->Sys( "lock", Name() );
return;
}
FileIOBinary::Write( buf, len, e );
if( lockFile( fd, LOCKF_UN ) < 0 )
{
e->Sys( "unlock", Name() );
return;
}
}
void
FileIOAppend::Rename( FileSys *target, Error *e )
{
// File may be open, so to rename we copy
// and truncate FileIOAppend files on NT.
Copy( target, FPM_RO, e );
if( e->Test() )
return;
Truncate( e );
}
// Initialize both multibyte and wide char operations.
int
FileSys::SymlinksSupported()
{
if( !functionHandlesLoaded)
{
functionHandlesLoaded = 1;
CreateSymbolicLinkA_func = (CreateSymbolicLinkAProc)
GetProcAddress(
GetModuleHandle("kernel32.dll"),
"CreateSymbolicLinkA");
CreateSymbolicLinkW_func = (CreateSymbolicLinkWProc)
GetProcAddress(
GetModuleHandle("kernel32.dll"),
"CreateSymbolicLinkW");
if( CreateSymbolicLinkA_func != 0 &&
CreateSymbolicLinkW_func != 0 )
{
const char *tempdir = getenv("TEMP");
if( !tempdir )
{
CreateSymbolicLinkA_func = 0;
CreateSymbolicLinkW_func = 0;
return 0;
}
StrBuf testLink;
StrBuf testTarget;
testLink << tempdir << "\\p4_test_symlink";
testTarget << tempdir << "\\p4_test_target";
nt_chmod( &testLink, PERM_0666 & ~global_umask, 0, 0 );
nt_unlink( &testLink, 0, 0 );
int result = nt_makelink( testTarget, &testLink, 0, 0 );
nt_unlink( &testLink, 0, 0 );
if( result < 0 )
{
CreateSymbolicLinkA_func = 0;
CreateSymbolicLinkW_func = 0;
}
}
}
return CreateSymbolicLinkA_func != 0;
}