/* * 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 "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: // # ifndef 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; // Just bulks up the structure so I can allocate it on the stack struct { UCHAR DataBuffer[MAX_PATH * 4]; } 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 typedef BOOLEAN (WINAPI *CreateSymbolicLinkProc)(LPCSTR,LPCSTR,DWORD); static CreateSymbolicLinkProc CreateSymbolicLink_func = 0; static int functionHandlesLoaded = 0; int nt_makelink( const char *target, const char *name ) { int result = -1; if( !FileSys::SymlinksSupported() ) return result; char tgt[MAX_PATH]; char *p = tgt; while( *target ) { *p = *target == '/' ? '\\' : *target; p++; target++; } *p = '\0'; // Since the target may be relative, cd to the source's directory char curDir[1024]; _getcwd( curDir, sizeof( curDir ) ); PathSys *pth = PathSys::Create(); pth->Set( StrRef( name ) ); pth->ToParent(); _chdir( pth->Text() ); struct stat sb; DWORD dwFlags = 0; if( stat( tgt, &sb ) >= 0 ) { if( S_ISDIR( sb.st_mode ) ) dwFlags = SYMBOLIC_LINK_FLAG_DIRECTORY; } if( (*CreateSymbolicLink_func)( name, tgt, (DWORD)dwFlags ) ) result = 0; _chdir( curDir ); delete pth; return result; } int nt_islink( const char *fname ) { DWORD fileAttributes = GetFileAttributes( fname ); if( fileAttributes == INVALID_FILE_ATTRIBUTES ) return -1; if( fileAttributes & FILE_ATTRIBUTE_REPARSE_POINT ) { WIN32_FIND_DATA findFileData; HANDLE fH = FindFirstFile( fname, &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; } // Reads what the symlink points to, puts the data into targetBuf. // Returns the number of bytes read. // int nt_readlink( const char *name, char *targetBuf, int bufSize ) { HANDLE fH = CreateFile( name, 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; REPARSE_DATA_BUFFER reparseBuffer; DWORD returnedLength; DWORD result = DeviceIoControl( fH, FSCTL_GET_REPARSE_POINT, 0, 0, &reparseBuffer, sizeof(reparseBuffer), &returnedLength, 0 ); CloseHandle( fH ); if( !result ) 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 { return -1; } len = len / sizeof(WCHAR); off = off / sizeof(WCHAR); wp += off; int retlen = len; char *o = targetBuf; while( len-- ) { char c = *wp++; *o++ = c == '\\' ? '/' : c; } *o = 0; return retlen; } static int nt_open( const char *fname, int flags, int mode, int dounicode ) { # ifdef O_NOINHERIT // All files on Windows are set to no inherit. // flags |= O_NOINHERIT; # endif if ( dounicode ) { CharSetCvtUTF816 cvt; int newlen = 0; const char *wname = cvt.FastCvt( fname, strlen( fname ), &newlen ); if( newlen > ( MAX_PATH * 2 ) ) { SetLastError( ERROR_BUFFER_OVERFLOW ); return -1; } if ( cvt.LastErr() == CharSetCvt::NONE ) { return _wopen( (const wchar_t *)wname, flags, mode ); } } if( strlen( fname ) > MAX_PATH ) { SetLastError( ERROR_BUFFER_OVERFLOW ); return -1; } return ::open(fname, flags, mode); } static int nt_stat( const char *fname, struct statbL *sb, int dounicode ) { int ret; if ( dounicode ) { CharSetCvtUTF816 cvt; int newlen = 0; const char *wname = cvt.FastCvt( fname, strlen( fname ), &newlen ); if( newlen > ( MAX_PATH * 2 ) ) { SetLastError( ERROR_BUFFER_OVERFLOW ); return -1; } if ( cvt.LastErr() == CharSetCvt::NONE ) { ret = _wstati64( (const wchar_t *)wname, sb ); if ( ret >= 0 || errno != ENOENT ) return ret; } } if( strlen( fname ) > MAX_PATH ) { SetLastError( ERROR_BUFFER_OVERFLOW ); return -1; } return ::_stati64( fname, sb ); } static int nt_unlink( const char *fname, int dounicode ) { int ret; if ( dounicode ) { CharSetCvtUTF816 cvt; const char *wname = cvt.FastCvt( fname, strlen( fname ) ); if ( cvt.LastErr() == CharSetCvt::NONE ) { ret = _wunlink( (const wchar_t *)wname ); if ( ret >= 0 || errno != ENOENT ) return ret; } } if( nt_islink( fname ) > 0 && RemoveDirectory( fname ) ) return 0; return ::_unlink( fname ); } static HANDLE nt_openHandleW( const wchar_t *wname) { return CreateFileW( wname, FILE_WRITE_ATTRIBUTES, ( FILE_SHARE_READ | FILE_SHARE_WRITE ), NULL, OPEN_EXISTING, FILE_ATTRIBUTE_NORMAL, NULL); } 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 ) ); } 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 nt_utime( const char *fname, struct utimbufL *ut, int dounicode ) { if ( dounicode ) { CharSetCvtUTF816 cvt; const char *wname = cvt.FastCvt( fname, strlen( fname ) ); if ( cvt.LastErr() == CharSetCvt::NONE ) { return nt_setFileTimes( nt_openHandleW( (const wchar_t *)wname ), ut->modtime ); } } return nt_setFileTimes( nt_openHandle( fname ), ut->modtime ); } static int nt_chmod( const char *fname, int m, int dounicode ) { int ret; if ( dounicode ) { CharSetCvtUTF816 cvt; const char *wname = cvt.FastCvt( fname, strlen( fname ) ); if ( cvt.LastErr() == CharSetCvt::NONE ) { ret = _wchmod( (const wchar_t *)wname, m ); if ( ret >= 0 || errno != ENOENT ) return ret; } } return ::_chmod( fname, m ); } static int nt_rename( const char *fname, const char *nname, int dounicode ) { int ret; if ( dounicode ) { CharSetCvtUTF816 cvt; const char *wname = cvt.FastCvt( fname, strlen( fname ) ); if ( cvt.LastErr() == CharSetCvt::NONE ) { wchar_t *wnname = FileIO::UnicodeName( nname, strlen( nname ) ); if ( wnname ) { ret = _wrename( (const wchar_t *)wname, wnname ); delete [] (char *)wnname; if ( ret >= 0 || errno != ENOENT ) return ret; } } } return ::rename( fname, nname ); } 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 = 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( Name(), target->Name(), DOUNICODE ) < 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; 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( Name(), target->Name(), DOUNICODE ); 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 ) { // yeech - must be writable to remove if( *Name() ) nt_chmod( Name(), PERM_0666 & ~global_umask, DOUNICODE ); if( *Name() && nt_unlink( Name(), DOUNICODE ) < 0 && e ) e->Sys( "unlink", Name() ); } wchar_t * FileIO::UnicodeName( const char* fname, int flen ) { wchar_t *ret; CharSetCvtUTF816 cvt; ret = (wchar_t *) cvt.CvtBuffer( fname, flen ); if ( cvt.LastErr() != CharSetCvt::NONE ) { delete [] (char *)ret; ret = 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( Name(), &t, DOUNICODE ) < 0 ) e->Sys( "utime", 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( Name(), O_WRONLY|O_TRUNC, PERM_0666, DOUNICODE ) ) >= 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; if( FileSys::SymlinksSupported() && nt_islink( Name() ) > 0 ) { char linkTarget[ MAX_PATH ]; if( nt_readlink( Name(), linkTarget, sizeof( linkTarget ) ) < 0 ) return flags; flags = FSF_SYMLINK; if( nt_stat( linkTarget, &sb, DOUNICODE ) >= 0 ) flags |= FSF_EXISTS; return flags; } if( nt_stat( Name(), &sb, DOUNICODE ) < 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; if( FileSys::SymlinksSupported() && nt_islink( Name() ) > 0 ) { char linkTarget[ MAX_PATH ]; if( nt_readlink( Name(), linkTarget, sizeof( linkTarget ) ) < 0 ) return uid; if( nt_stat( linkTarget, &sb, DOUNICODE ) >= 0 ) uid = sb.st_uid; return uid; } if( nt_stat( Name(), &sb, DOUNICODE ) >= 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( Name(), &sb, DOUNICODE ) < 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; if (hFile == INVALID_HANDLE_VALUE) return -1; if( !GetFileTime( hFile, &cTime, &aTime, &mTime ) ) return -1; CloseHandle( hFile ); 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 static int nt_getLastModifiedTime( HANDLE hFile ) { SYSTEMTIME st; struct tm u_tm; FILETIME cTime, aTime, mTime; if (hFile == INVALID_HANDLE_VALUE) return -1; if( !GetFileTime( hFile, &cTime, &aTime, &mTime ) ) return -1; CloseHandle( hFile ); 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() { const char *fname = Name(); if ( DOUNICODE ) { CharSetCvtUTF816 cvt; const char *wname = cvt.FastCvt( fname, strlen( fname ) ); if ( cvt.LastErr() == CharSetCvt::NONE ) return nt_getLastModifiedTime( nt_openHandleW( (const wchar_t *)wname ) ); } if( nt_islink( fname ) > 0 ) return nt_getLastModifiedTime( nt_openDirHandle( fname ) ); return nt_getLastModifiedTime( nt_openHandle( fname ) ); } 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; } if( nt_chmod( Name(), bits & ~global_umask, DOUNICODE ) >= 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( Name(), bits, PERM_0666, DOUNICODE ) ) < 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; if ( DOUNICODE ) { CharSetCvtUTF816 cvt; int newlen = 0; const char *wname = cvt.FastCvt( Name(), strlen(Name()), &newlen ); if ( cvt.LastErr() == CharSetCvt::NONE ) { if( _wstati64( (const wchar_t *)wname, &sb ) < 0 ) return -1; } if( newlen > ( MAX_PATH * 2 ) ) { SetLastError( ERROR_BUFFER_OVERFLOW ); return -1; } return sb.st_size; } if( _stati64( Name(), &sb ) < 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( Name(), openModes[ mode ].aflags, PERM_0666, DOUNICODE ) ) < 0 ) { e->Sys( openModes[ mode ].modeName, Name() ); } } 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 ); } int FileSys::SymlinksSupported() { if( !functionHandlesLoaded) { functionHandlesLoaded = 1; CreateSymbolicLink_func = (CreateSymbolicLinkProc) GetProcAddress( GetModuleHandle("kernel32.dll"), "CreateSymbolicLinkA"); if( CreateSymbolicLink_func != 0 ) { const char *tempdir = getenv("TEMP"); if( !tempdir ) { CreateSymbolicLink_func = 0; return 0; } StrBuf testLink; StrBuf testTarget; testLink << tempdir << "\\p4_test_symlink"; testTarget << tempdir << "\\p4_test_target"; nt_chmod( testLink.Text(), PERM_0666 & ~global_umask, 0 ); nt_unlink( testLink.Text(), 0 ); int result = nt_makelink( testTarget.Text(), testLink.Text() ); nt_unlink( testLink.Text(), 0 ); if( result < 0 ) CreateSymbolicLink_func = 0; } } return CreateSymbolicLink_func != 0; }
# | Change | User | Description | Committed | |
---|---|---|---|---|---|
#1 | 14945 | Newtopian |
Merging //guest/perforce_software/p4/... to //guest/Newtopian/p4/... |
||
//guest/perforce_software/p4/2014.1/sys/fileiont.cc | |||||
#1 | 12188 | Matt Attaway | Move 'main' p4 into a release specific directory in prep for new releases | ||
//guest/perforce_software/p4/sys/fileiont.cc | |||||
#1 | 9129 | Matt Attaway | Initial commit of the 2014.1 p4/p4api source code |