fileio.cc #1

  • //
  • guest/
  • perforce_software/
  • p4/
  • 2014-2/
  • sys/
  • fileio.cc
  • View
  • Commits
  • Open Download .zip Download (15 KB)
/*
 * Copyright 1995, 2003 Perforce Software.  All rights reserved.
 *
 * This file is part of Perforce - the FAST SCM System.
 */

/*
 * fileio -- FileIO, FileIOBinary, FileIOAppend methods
 *
 * Hacked to hell for OS implementations.
 */

# define NEED_FLOCK
# define NEED_FSYNC
# define NEED_FCNTL
# define NEED_FILE
# define NEED_STAT
# define NEED_UTIME
# define NEED_ERRNO
# define NEED_READLINK

# include <stdhdrs.h>

# include <error.h>
# include <errornum.h>
# include <debug.h>
# include <tunable.h>
# include <msgsupp.h>
# include <strbuf.h>
# include <datetime.h>
# include <i18napi.h>
# include <charcvt.h>
# include <largefile.h>
# include <lockfile.h>
# include <md5.h>

# include <msgos.h>

# include "filesys.h"
# include "fileio.h"

// We need to know the user's umask so that Chmod() doesn't give
// away permissions beyond the umask.

int global_umask = -1;

# ifndef O_BINARY
# define O_BINARY 0
# endif

const FileIOBinary::OpenMode FileIOBinary::openModes[3] = {
	"open for read",
		O_RDONLY|O_BINARY,
		O_RDONLY|O_BINARY,
		0,
	"open for write",
		O_WRONLY|O_CREAT|O_TRUNC|O_BINARY,
		O_WRONLY|O_CREAT|O_APPEND,
		1,
	"open for read/write",
		O_RDWR|O_CREAT|O_BINARY,
		O_RDWR|O_CREAT|O_APPEND,
		1
} ;

FileIO::FileIO()
{
	// Get the umask if we don't know it already.

	if( global_umask < 0 )
	{
	    global_umask = umask( 0 );
	    (void)umask( global_umask );
	}
}

# if !defined( OS_VMS ) && !defined( OS_NT )

void
FileIO::Rename( FileSys *target, Error *e )
{
	// yeech - must not exist to rename

# if defined(OS_OS2) || defined(OS_AS400)
	target->Unlink( 0 );
# endif

# if defined( OS_MACOSX ) || defined( OS_DARWIN )
	// On mac, deal with a possible immutable *uchg) flag

	struct stat sb;

	if( stat( Name(), &sb ) >= 0 && ( sb.st_flags & UF_IMMUTABLE ) )
	    chflags( Name(), sb.st_flags & ~UF_IMMUTABLE );
	if( stat( target->Name(), &sb ) >= 0 && ( sb.st_flags & UF_IMMUTABLE ) )
	    chflags( target->Name(), sb.st_flags & ~UF_IMMUTABLE );
# endif
	// Run on all other UNIX variants, including Mac OS X

	if( rename( Name(), target->Name() ) < 0 )
	{
	    e->Sys( "rename", target->Name() );
	    return;
	}

	// source file has been deleted,  clear the flag

	ClearDeleteOnClose();
}

void
FileIO::ChmodTime( int modTime, Error *e )
{
# ifdef HAVE_UTIME
	struct utimbuf t;
	DateTime now;

	now.SetNow();
	t.actime = DateTime::Localize( now.Value() );
	t.modtime = DateTime::Localize( modTime );

	if( utime( Name(), &t ) < 0 )
	    e->Sys( "utime", Name() );
# endif // HAVE_UTIME
}

# endif // !OS_VMS && !OS_NT


void
FileIO::ChmodTime( Error *e )
{
	if( modTime )
	    ChmodTime( modTime, e );
}

# if !defined( OS_NT )

void
FileIO::Truncate( offL_t offset, Error *e )
{
	// Don't bother if non-existent.

	if( !( Stat() & FSF_EXISTS ) )
	    return;

# ifdef HAVE_TRUNCATE
	if( truncate( Name(), offset ) >= 0 )
	    return;
# endif // HAVE_TRUNCATE

	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.

# ifdef HAVE_TRUNCATE
	if( truncate( Name(), 0 ) >= 0 )
	    return;
# endif // HAVE_TRUNCATE

	int fd;

# if !defined ( OS_MACOSX )
	if( ( fd = openL( Name(), O_WRONLY|O_TRUNC, PERM_0666 ) ) >= 0 )
# else
	if( ( fd = openL( Name(), O_WRONLY|O_TRUNC ) ) >= 0 )
# endif // OS_MACOSX
	{
	    close( fd );
	    return;
	}

	e->Sys( "truncate", Name() );
}

# endif // !defined( OS_NT )

/*
 * FileIO::Stat() - return flags if file exists
 */

# ifndef OS_NT

int
FileIO::Stat()
{
	// Stat & check for missing, special

	int flags = 0;
	struct statbL sb;

# ifdef HAVE_SYMLINKS
	// With symlinks, we first stat the link
	// and if it is a link, then stat the actual file.
	// A symlink without an underlying file appears
	// as FSF_SYMLINK.  With an underlying file
	// as FSF_SYMLINK|FSF_EXISTS.

	if( lstatL( Name(), &sb ) < 0 )
	    return flags;

	if( S_ISLNK( sb.st_mode ) )
	    flags |= FSF_SYMLINK;

	if( S_ISLNK( sb.st_mode ) && statL( Name(), &sb ) < 0 )
	    return flags;
# else
	// No symlinks: just stat the file.

	if( statL( Name(), &sb ) < 0 )
	    return flags;
# endif

	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;

# if defined ( OS_DARWIN ) || defined( OS_MACOSX )
	// If the immutable bit is set, we can't write this file
	// Chmod() will unset the immutable bit if it needs to.
	//
	if( sb.st_flags & UF_IMMUTABLE ) flags &= ~FSF_WRITEABLE;
# endif

	return flags;
}

# endif

int
FileIO::Readlink( StrBuf &linkPath, Error *e )
{
# if defined( HAVE_SYMLINKS ) && !defined( OS_NT )
	// call will fail if this is not a symlink
	linkPath.Clear();
	char buf[SIZE_MEDSTR];
	ssize_t	namelen;
	if( (namelen = readlink( Name(), buf, sizeof(buf)-1 )) < 0 )
	{
	    e->Sys( "readlink", Name() );
	    return errno;
	}
	else
	{
	    buf[namelen] = '\0';
	    linkPath.Set( buf );
	}
	return 0;
# else
	/*
	 * After searching on the net for a equivalent
	 * to no avail, we will not "do windows".  This
	 * is used in the Clustered Server which is currently
	 * linux-only, caveat emptor.
	 */
	e->Set( MsgSupp::NotImplemented ) << "Windows";
	return EINVAL;
# endif
}

/*
 * Make a symlink (linux only)
 * NB: not atomic!
 */
int
FileIO::Writelink( const StrPtr &linkPath, Error *e )
{
# if defined( HAVE_SYMLINKS ) && !defined( OS_NT )
	if( ( Stat() & (FSF_EXISTS|FSF_SYMLINK) ) &&
	    unlink( Name() ) < 0 )
	{
	    e->Sys( "unlink", Name() );
	    return errno;
	}

	// call will fail if target exists
	if( symlink( linkPath.Text(), Name() ) < 0 )
	{
	    e->Sys( "symlink", linkPath.Text() );
	    return errno;
	}
	return 0;
# else
	/*
	 * After searching on the net for a equivalent
	 * to no avail, we will not "do windows".  This
	 * is used in the Clustered Server which is currently
	 * linux-only, caveat emptor.
	 */
	e->Set( MsgSupp::NotImplemented ) << "Windows";
	return EINVAL;
# endif
}

# if !defined( OS_NT )
int
FileIO::GetOwner()
{
	int uid = 0;
	struct statbL sb;

# ifdef HAVE_SYMLINKS
	// With symlinks, we first stat the link
	// and if it is a link, then stat the actual file.
	// A symlink without an underlying file appears
	// as FSF_SYMLINK.  With an underlying file
	// as FSF_SYMLINK|FSF_EXISTS.

	if( lstatL( Name(), &sb ) < 0 )
	    return uid;

	if( S_ISLNK( sb.st_mode ) && statL( Name(), &sb ) < 0 )
	    return uid;
# else
	// No symlinks: just stat the file.

	if( statL( Name(), &sb ) < 0 )
	    return uid;
# endif

	return sb.st_uid;
}
# endif

# if !defined( OS_NT )
bool
FileIO::HasOnlyPerm( FilePerm perms )
{
	struct statbL sb;
	mode_t modeBits = 0;

	if( statL( Name(), &sb ) < 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;
}
# endif // not defined OS_NT

# if !defined( OS_NT )
int
FileIO::StatModTime()
{
	struct statbL sb;

	if( statL( Name(), &sb ) < 0 )
	    return 0;

	return (int)( DateTime::Centralize( sb.st_mtime ) );
}

# endif

/*
 * FileIO::Unlink() - remove single file (error optional)
 */

# if !defined( OS_VMS ) && !defined( OS_NT )

void
FileIO::Unlink( Error *e )
{

# if defined(OS_OS2) || defined(OS_DARWIN) || defined(OS_MACOSX)

	// yeech - must be writable to remove
	// even on Darwin because the uchg flag might be set
	// but only for real files, otherwise the chmod
	// will propagate to the target file.

	Chmod( FPM_RW, 0 );

# endif

	if( *Name() && unlink( Name() ) < 0 && e )
	    e->Sys( "unlink", Name() );
}

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( chmod( Name(), bits & ~global_umask ) >= 0 )
	    return;

# if defined( OS_MACOSX ) || defined( OS_DARWIN )

	// On mac, try unlocking the immutable bit and chmoding again.

	struct stat sb;

	if( stat( Name(), &sb ) >= 0 &&
	    chflags( Name(), sb.st_flags & ~UF_IMMUTABLE ) >= 0 &&
	    chmod( Name(), bits & ~global_umask ) >= 0 )
		return;
# endif

	// Can be called with e==0 to ignore error.

	if( e )
	    e->Sys( "chmod", Name() );
}

# endif /* !OS_VMS && !OS_NT */

/*
 * FileIoText Support
 * FileIOBinary support
 */

FileIOBinary::~FileIOBinary()
{
	Cleanup();
}

# if !defined( OS_NT )

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 = openL( Name(), bits, PERM_0666 ) ) < 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
	}

}

# endif

void
FileIOBinary::Close( Error *e )
{
	if( fd < 2 )
	    return;

	if( ( GetType() & FST_M_SYNC ) )
	    Fsync( e );

# ifdef OS_LINUX
	if( cacheHint && p4tunable.Get( P4TUNE_FILESYS_CACHEHINT ) )
	  (void) posix_fadvise64( fd, 0, 0, POSIX_FADV_DONTNEED );
# endif

	if( close( fd ) < 0 )
	    e->Sys( "close", Name() );

	fd = -1;

	if( mode == FOM_WRITE && modTime )
	    ChmodTime( modTime, e );

	if( mode == FOM_WRITE )
	    Chmod( perms, e );
}

void
FileIOBinary::Fsync( Error *e )
{
# ifdef HAVE_FSYNC
	if( fd >= 0 && fsync( fd ) < 0 )
	    e->Sys( "fsync", Name() );
# endif
}

void
FileIOBinary::Write( const char *buf, int len, Error *e )
{
	// Raw, unbuffered write

	int l;

	if( ( l = write( fd, buf, len ) ) < 0 )
	    e->Sys( "write", Name() );
	else
	    tellpos += l;

	if( checksum && l > 0 )
	    checksum->Update( StrRef( buf, l ) );
}

int
FileIOBinary::Read( char *buf, int len, Error *e )
{
	// Raw, unbuffered read

	int l;

	if( ( l = read( fd, buf, len ) ) < 0 )
	    e->Sys( "read", Name() );
	else
	    tellpos += l;

	return l;
}

# if !defined( OS_NT )

offL_t
FileIOBinary::GetSize()
{
	struct statbL sb;

	if( fd >= 0 && fstatL( fd, &sb ) < 0 )
	    return -1;
	if( fd < 0 && statL( Name(), &sb ) < 0 )
	    return -1;

	return sb.st_size;
}

void
FileIOBinary::Seek( offL_t offset, Error *e )
{
	if( lseekL( fd, offset, 0 ) == -1 )
	    e->Sys( "seek", Name() );
	tellpos = offset;
}

# endif

offL_t
FileIOBinary::Tell()
{
	return tellpos;
}

/*
 * FileIOAppend support
 *
 * On UNIX, FileIOAppend::Write() and Rename() obey a special
 * protocol to provide for atomic rotating of files that may
 * be open by active processes.
 *
 * Rename() opens & locks the file, renames it, and then changes
 * the permission on the renamed file to read-only.
 *
 * Write() locks and then fstats the file, to see if it is still
 * writeable.  If not, it assumes it has been renamed, so it closes
 * and reopens the original file (which should always be writeable),
 * and then relocks it.  It can then write and unlock.
 *
 * There is the possibility that rapid fire Rename() calls may get
 * between Write()'s reopen for write and its lock.  In this odd case,
 * we retry 10 times.
 */

FileIOAppend::~FileIOAppend()
{
	// Need to set mode to read so it doesn't try to chmod the
	// file in the destructor

	mode = FOM_READ;
}

# if !defined( OS_NT )

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 = openL( Name(), openModes[ mode ].aflags, PERM_0666 ) ) < 0 )
	{
	    e->Sys( openModes[ mode ].modeName, Name() );
	    ClearDeleteOnClose();
	}

	// Clear send/receive buffers

	rcv = snd = 0;
}

offL_t
FileIOAppend::GetSize()
{
	offL_t s = 0;

	if( !lockFile( fd, LOCKF_SH ) )
	{
	    s = FileIOBinary::GetSize();

	    lockFile( fd, LOCKF_UN );
	}
	else
	    s = FileIOBinary::GetSize();

	return s;
}

void
FileIOAppend::Write( const char *buf, int len, Error *e )
{
	// Lock and check for writeability.
	// If made read-only by Rename(), close and reopen.
	// If file keeps becomming read-only, give up after
	// trying a while.

	int tries = 10;

	while( --tries )
	{
	    struct statbL sb;

	    if( lockFile( fd, LOCKF_EX ) < 0 )
	    {
		e->Sys( "lock", Name() );
		return;
	    }

	    if( fstatL( fd, &sb ) < 0 )
	    {
		e->Sys( "fstat", Name() );
		return;
	    }

	    if( sb.st_mode & S_IWUSR )
		break;

	    if( close( fd ) < 0 )
	    {
		e->Sys( "close", Name() );
		return;
	    }

	    Open( mode, e );

	    if( e->Test() )
		return;
	}

	if( !tries )
	{
	    e->Set( E_FAILED, "Tired of waiting for %file% to be writeable." )
		<< 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 )
{
	// Open, lock for write, rename, chmod to RO, and close

	Open( FOM_WRITE, e );

	if( e->Test() )
	    return;

	if( lockFile( fd, LOCKF_EX ) < 0 )
	{
	    e->Sys( "lock", Name() );
	    return;
	}

	if( rename( Name(), target->Name() ) < 0 )
	{
	    // Uh oh -- we can't rename.  Maybe cross device?
	    // Do the dumb way: copy/truncate.

	    mode = FOM_READ;

	    Close( e );
	    Copy( target, FPM_RO, e );

	    if( e->Test() )
		return;

	    Truncate( e );
	    return;
	}

	target->Chmod( FPM_RO, e );

	// Need to set mode to read so it doesn't try to chmod the
	// file in the destructor

	mode = FOM_READ;

	if( e->Test() )
	    return;

	struct statbL sb;

	if( fstatL( fd, &sb ) < 0 )
	{
	    e->Sys( "fstat", Name() );
	    return;
	}

	if( sb.st_mode & S_IWUSR )
	{
	    P4INT64 bigINode = sb.st_ino;
	    P4INT64 bigMode  = sb.st_mode;
	    e->Set( MsgOs::ChmodBetrayal ) <<
	            Name() << target->Name() <<
	            StrNum( bigMode ) << StrNum( bigINode );
	    return;
	}

	Close( e );
}

# endif

void
FileIOAppend::Close( Error *e )
{
	mode = FOM_READ;

	FileIOBuffer::Close( e );
}

# Change User Description Committed
#1 15903 Matt Attaway Everything should be happy now between the Workshop and the depot paths
//guest/perforce_software/p4/2014_2/sys/fileio.cc
#1 15901 Matt Attaway Clean up code to fit modern Workshop naming standards
//guest/perforce_software/p4/2014.2/sys/fileio.cc
#1 12189 Matt Attaway Initial (and much belated) drop of 2014.2 p4 source code