zbackup/file.cc

451 lines
8.9 KiB
C++

// Copyright (c) 2012-2014 Konstantin Isakov <ikm@zbackup.org> and ZBackup contributors, see CONTRIBUTORS
// Part of ZBackup. Licensed under GNU GPLv2 or later + OpenSSL, see LICENSE
#include <limits.h>
#include <sys/stat.h>
#include <unistd.h>
#include <cerrno>
#include <cstring>
#if defined( __APPLE__ ) || defined( __OpenBSD__ ) || defined(__FreeBSD__) || defined(__CYGWIN__)
#include <sys/socket.h>
#else
#include <sys/sendfile.h>
#endif
#include <sys/types.h>
#include <fcntl.h>
#include "file.hh"
#include "utils.hh"
enum
{
// We employ a writing buffer to considerably speed up file operations when
// they consists of many small writes. The default size for the buffer is 64k
WriteBufferSize = 65536
};
bool File::exists( char const * filename ) throw()
{
#ifdef __WIN32
struct _stat buf;
return _stat( filename, &buf ) == 0;
#else
struct stat buf;
// EOVERFLOW rationale: if the file is too large, it still does exist
return stat( filename, &buf ) == 0 || errno == EOVERFLOW;
#endif
}
void File::erase( std::string const & filename ) throw( exCantErase )
{
if ( remove( filename.c_str() ) != 0 )
throw exCantErase( filename );
}
void File::rename( std::string const & from,
std::string const & to ) throw( exCantRename,
exCantErase )
{
int res = 0;
res = ::rename( from.c_str(), to.c_str() );
if ( 0 != res )
{
if ( EXDEV == errno )
{
int read_fd;
int write_fd;
struct stat stat_buf;
off_t offset = 0;
/* Open the input file. */
read_fd = ::open( from.c_str(), O_RDONLY );
/* Stat the input file to obtain its size. */
if ( fstat( read_fd, &stat_buf ) != 0 )
throw exCantRename( from + " to " + to );
/* Open the output file for writing, with the same permissions as the
source file. */
write_fd = ::open( to.c_str(), O_WRONLY | O_CREAT, stat_buf.st_mode );
/* Blast the bytes from one file to the other. */
#if defined( __APPLE__ )
if ( -1 == sendfile( write_fd, read_fd, offset, &stat_buf.st_size, NULL, 0 ) )
throw exCantRename( from + " to " + to );
#elif defined( __OpenBSD__ ) || defined(__FreeBSD__) || defined(__CYGWIN__)
size_t BUFSIZE = 4096, size;
char buf[BUFSIZE];
while ( ( size = ::read( read_fd, buf, BUFSIZE ) ) != -1 && size != 0 )
::write( write_fd, buf, size );
if ( size == -1 )
throw exCantRename( from + " to " + to );
#else
if ( -1 == sendfile( write_fd, read_fd, &offset, stat_buf.st_size ) )
throw exCantRename( from + " to " + to );
#endif
/* Close up. */
::close( read_fd );
::close( write_fd );
File::erase ( from );
}
else
throw exCantRename( from + " to " + to );
}
}
void File::open( char const * filename, OpenMode mode ) throw( exCantOpen )
{
char const * m;
switch( mode )
{
case Update:
m = "r+b";
break;
case WriteOnly:
m = "wb";
break;
default:
m = "rb";
}
f = fopen( filename, m );
if ( !f )
throw exCantOpen( std::string( filename ) + ": " + strerror( errno ) );
}
void File::open( int fd, OpenMode mode ) throw( exCantOpen )
{
char const * m;
switch( mode )
{
case Update:
m = "r+b";
break;
case WriteOnly:
m = "wb";
break;
default:
m = "rb";
}
f = fdopen( fd, m );
if ( !f )
throw exCantOpen( "fd#" + Utils::numberToString( fd ) + ": " + strerror( errno ) );
}
File::File( char const * filename, OpenMode mode ) throw( exCantOpen ):
writeBuffer( 0 )
{
open( filename, mode );
}
File::File( std::string const & filename, OpenMode mode )
throw( exCantOpen ): writeBuffer( 0 )
{
open( filename.c_str(), mode );
}
File::File( int fd, OpenMode mode )
throw( exCantOpen ): writeBuffer( 0 )
{
open( fd, mode );
}
void File::read( void * buf, size_t size ) throw( exReadError, exWriteError )
{
if ( !size )
return;
if ( writeBuffer )
flushWriteBuffer();
size_t result = fread( buf, size, 1, f );
if ( result != 1 )
{
if ( !ferror( f ) )
throw exShortRead();
else
throw exReadErrorDetailed( f );
}
}
size_t File::readRecords( void * buf, size_t size, size_t count ) throw( exWriteError )
{
if ( writeBuffer )
flushWriteBuffer();
return fread( buf, size, count, f );
}
void File::write( void const * buf, size_t size ) throw( exWriteError )
{
if ( !size )
return;
if ( size >= WriteBufferSize )
{
// If the write is large, there's not much point in buffering
flushWriteBuffer();
size_t result = fwrite( buf, size, 1, f );
if ( result != 1 )
throw exWriteError();
return;
}
if ( !writeBuffer )
{
// Allocate the writing buffer since we don't have any yet
writeBuffer = new char[ WriteBufferSize ];
writeBufferLeft = WriteBufferSize;
}
size_t toAdd = size < writeBufferLeft ? size : writeBufferLeft;
memcpy( writeBuffer + ( WriteBufferSize - writeBufferLeft ),
buf, toAdd );
size -= toAdd;
writeBufferLeft -= toAdd;
if ( !writeBufferLeft ) // Out of buffer? Flush it
{
flushWriteBuffer();
if ( size ) // Something's still left? Add to buffer
{
memcpy( writeBuffer, (char const *)buf + toAdd, size );
writeBufferLeft -= size;
}
}
}
size_t File::writeRecords( void const * buf, size_t size, size_t count )
throw( exWriteError )
{
flushWriteBuffer();
return fwrite( buf, size, count, f );
}
char * File::gets( char * s, int size, bool stripNl )
throw( exWriteError )
{
if ( writeBuffer )
flushWriteBuffer();
char * result = fgets( s, size, f );
if ( result && stripNl )
{
size_t len = strlen( result );
char * last = result + len;
while( len-- )
{
--last;
if ( *last == '\n' || *last == '\r' )
*last = 0;
else
break;
}
}
return result;
}
std::string File::gets( bool stripNl ) throw( exReadError, exWriteError )
{
char buf[ 1024 ];
if ( !gets( buf, sizeof( buf ), stripNl ) )
{
if ( !ferror( f ) )
throw exShortRead();
else
throw exReadErrorDetailed( f );
}
return std::string( buf );
}
void File::seek( long offset ) throw( exSeekError, exWriteError )
{
if ( writeBuffer )
flushWriteBuffer();
if ( fseek( f, offset, SEEK_SET ) != 0 )
throw exSeekError();
}
void File::seekCur( long offset ) throw( exSeekError, exWriteError )
{
if ( writeBuffer )
flushWriteBuffer();
if ( fseek( f, offset, SEEK_CUR ) != 0 )
throw exSeekError();
}
void File::seekEnd( long offset ) throw( exSeekError, exWriteError )
{
if ( writeBuffer )
flushWriteBuffer();
if ( fseek( f, offset, SEEK_END ) != 0 )
throw exSeekError();
}
void File::rewind() throw( exSeekError, exWriteError )
{
seek( 0 );
}
size_t File::tell() throw( exSeekError )
{
long result = ftell( f );
if ( result == -1 )
throw exSeekError();
if ( writeBuffer )
result += ( WriteBufferSize - writeBufferLeft );
return ( size_t ) result;
}
size_t File::size() throw( exSeekError, exWriteError )
{
size_t cur = tell();
seekEnd( 0 );
size_t result = tell();
seek( cur );
return result;
}
bool File::eof() throw( exWriteError )
{
if ( writeBuffer )
flushWriteBuffer();
return feof( f );
}
int File::error() throw( exReadError )
{
int result = ferror( f );
return result;
}
FILE * File::file() throw( exWriteError )
{
flushWriteBuffer();
return f;
}
FILE * File::release() throw( exWriteError )
{
releaseWriteBuffer();
FILE * c = f;
f = 0;
return c;
}
void File::close() throw( exWriteError )
{
fclose( release() );
}
File::~File() throw()
{
if ( f )
{
try
{
releaseWriteBuffer();
}
catch( exWriteError & )
{
}
fclose( f );
}
}
void File::flushWriteBuffer() throw( exWriteError )
{
if ( writeBuffer && writeBufferLeft != WriteBufferSize )
{
size_t result = fwrite( writeBuffer, WriteBufferSize - writeBufferLeft, 1, f );
if ( result != 1 )
throw exWriteError();
writeBufferLeft = WriteBufferSize;
}
}
void File::releaseWriteBuffer() throw( exWriteError )
{
flushWriteBuffer();
if ( writeBuffer )
{
delete [] writeBuffer;
writeBuffer = 0;
}
}
File::exReadErrorDetailed::exReadErrorDetailed( int fd )
{
buildDescription( fd );
}
File::exReadErrorDetailed::exReadErrorDetailed( FILE * f )
{
buildDescription( fileno( f ) );
}
void File::exReadErrorDetailed::buildDescription( int fd )
{
description = "Error reading from file ";
char path[ PATH_MAX ];
char procFdLink[ 48 ];
sprintf( procFdLink, "/proc/self/fd/%d", fd );
int pathChars = readlink( procFdLink, path, sizeof( path ) );
if ( pathChars < 0 )
description += "(unknown)";
else
description.append( path, pathChars );
}
const char * File::exReadErrorDetailed::what() const throw()
{
return description.c_str();
}
File::exReadErrorDetailed::~exReadErrorDetailed() throw ()
{
}