diff options
Diffstat (limited to 'zen/file_io.cpp')
-rw-r--r-- | zen/file_io.cpp | 217 |
1 files changed, 217 insertions, 0 deletions
diff --git a/zen/file_io.cpp b/zen/file_io.cpp new file mode 100644 index 00000000..6b3a5214 --- /dev/null +++ b/zen/file_io.cpp @@ -0,0 +1,217 @@ +// ************************************************************************** +// * This file is part of the FreeFileSync project. It is distributed under * +// * GNU General Public License: http://www.gnu.org/licenses/gpl.html * +// * Copyright (C) 2008-2011 ZenJu (zhnmju123 AT gmx.de) * +// ************************************************************************** + +#include "file_io.h" + +#ifdef FFS_WIN +#include "long_path_prefix.h" +#elif defined FFS_LINUX +#include <cerrno> +#endif + +using namespace zen; + + +FileInput::FileInput(FileHandle handle, const Zstring& filename) : + eofReached(false), + fileHandle(handle), + filename_(filename) {} + + +FileInput::FileInput(const Zstring& filename) : //throw FileError, ErrorNotExisting + eofReached(false), + filename_(filename) +{ +#ifdef FFS_WIN + fileHandle = ::CreateFile(zen::applyLongPathPrefix(filename).c_str(), + GENERIC_READ, + FILE_SHARE_READ | FILE_SHARE_WRITE | FILE_SHARE_DELETE, //all shared modes are required to read open files that are shared by other applications + 0, + OPEN_EXISTING, + FILE_FLAG_SEQUENTIAL_SCAN, + /* possible values: (Reference http://msdn.microsoft.com/en-us/library/aa363858(VS.85).aspx#caching_behavior) + FILE_FLAG_NO_BUFFERING + FILE_FLAG_RANDOM_ACCESS + FILE_FLAG_SEQUENTIAL_SCAN + + tests on Win7 x64 show that FILE_FLAG_SEQUENTIAL_SCAN provides best performance for binary comparison in all cases: + - comparing different physical disks (DVD <-> HDD and HDD <-> HDD) + - even on same physical disk! (HDD <-> HDD) + - independent from client buffer size! + + tests on XP show that FILE_FLAG_SEQUENTIAL_SCAN provides best performance for binary comparison when + - comparing different physical disks (DVD <-> HDD) + + while FILE_FLAG_RANDOM_ACCESS offers best performance for + - same physical disk (HDD <-> HDD) + + Problem: bad XP implementation of prefetch makes flag FILE_FLAG_SEQUENTIAL_SCAN effectively load two files at once from one drive + swapping every 64 kB (or similar). File access times explode! + => For XP it is critical to use FILE_FLAG_RANDOM_ACCESS (to disable prefetch) if reading two files on same disk and + FILE_FLAG_SEQUENTIAL_SCAN when reading from different disk (e.g. massive performance improvement compared to random access for DVD <-> HDD!) + => there is no compromise that satisfies all cases! (on XP) + + for FFS most comparisons are probably between different disks => let's use FILE_FLAG_SEQUENTIAL_SCAN + */ + NULL); + if (fileHandle == INVALID_HANDLE_VALUE) + { + const DWORD lastError = ::GetLastError(); + + std::wstring errorMessage = _("Error opening file:") + "\n\"" + filename_ + "\"" + "\n\n" + zen::getLastErrorFormatted(lastError); + + if (lastError == ERROR_FILE_NOT_FOUND || + lastError == ERROR_PATH_NOT_FOUND || + lastError == ERROR_BAD_NETPATH || + lastError == ERROR_NETNAME_DELETED) + throw ErrorNotExisting(errorMessage); + + throw FileError(errorMessage); + } + +#elif defined FFS_LINUX + fileHandle = ::fopen(filename.c_str(), "r,type=record,noseek"); //utilize UTF-8 filename + if (fileHandle == NULL) + { + const int lastError = errno; + + std::wstring errorMessage = _("Error opening file:") + "\n\"" + filename_ + "\"" + "\n\n" + zen::getLastErrorFormatted(lastError); + + if (lastError == ENOENT) + throw ErrorNotExisting(errorMessage); + + throw FileError(errorMessage); + } +#endif +} + + +FileInput::~FileInput() +{ +#ifdef FFS_WIN + ::CloseHandle(fileHandle); +#elif defined FFS_LINUX + ::fclose(fileHandle); //NEVER allow passing NULL to fclose! -> crash!; fileHandle != NULL in this context! +#endif +} + + +size_t FileInput::read(void* buffer, size_t bytesToRead) //returns actual number of bytes read; throw (FileError) +{ +#ifdef FFS_WIN + DWORD bytesRead = 0; + if (!::ReadFile(fileHandle, //__in HANDLE hFile, + buffer, //__out LPVOID lpBuffer, + static_cast<DWORD>(bytesToRead), //__in DWORD nNumberOfBytesToRead, + &bytesRead, //__out_opt LPDWORD lpNumberOfBytesRead, + NULL)) //__inout_opt LPOVERLAPPED lpOverlapped +#elif defined FFS_LINUX + const size_t bytesRead = ::fread(buffer, 1, bytesToRead, fileHandle); + if (::ferror(fileHandle) != 0) +#endif + throw FileError(_("Error reading file:") + "\n\"" + filename_ + "\"" + "\n\n" + zen::getLastErrorFormatted() + " (r)"); + +#ifdef FFS_WIN + if (bytesRead < bytesToRead) //falsify only! +#elif defined FFS_LINUX + if (::feof(fileHandle) != 0) +#endif + eofReached = true; + + if (bytesRead > bytesToRead) + throw FileError(_("Error reading file:") + "\n\"" + filename_ + "\"" + "\n\n" + "buffer overflow"); + + return bytesRead; +} + + +bool FileInput::eof() //end of file reached +{ + return eofReached; +} + + +FileOutput::FileOutput(FileHandle handle, const Zstring& filename) : fileHandle(handle), filename_(filename) {} + + +FileOutput::FileOutput(const Zstring& filename, AccessFlag access) : //throw FileError, ErrorTargetPathMissing, ErrorTargetExisting + filename_(filename) +{ +#ifdef FFS_WIN + fileHandle = ::CreateFile(zen::applyLongPathPrefix(filename).c_str(), + GENERIC_READ | GENERIC_WRITE, + /* http://msdn.microsoft.com/en-us/library/aa363858(v=vs.85).aspx + quote: When an application creates a file across a network, it is better + to use GENERIC_READ | GENERIC_WRITE for dwDesiredAccess than to use GENERIC_WRITE alone. + The resulting code is faster, because the redirector can use the cache manager and send fewer SMBs with more data. + This combination also avoids an issue where writing to a file across a network can occasionally return ERROR_ACCESS_DENIED. */ + FILE_SHARE_READ | FILE_SHARE_DELETE, //note: FILE_SHARE_DELETE is required to rename file while handle is open! + 0, + access == ACC_OVERWRITE ? CREATE_ALWAYS : CREATE_NEW, + FILE_ATTRIBUTE_NORMAL | FILE_FLAG_SEQUENTIAL_SCAN, + NULL); + if (fileHandle == INVALID_HANDLE_VALUE) + { + const DWORD lastError = ::GetLastError(); + std::wstring errorMessage = _("Error writing file:") + "\n\"" + filename_ + "\"" + "\n\n" + zen::getLastErrorFormatted(lastError); + + if (lastError == ERROR_FILE_EXISTS) + throw ErrorTargetExisting(errorMessage); + + if (lastError == ERROR_PATH_NOT_FOUND) + throw ErrorTargetPathMissing(errorMessage); + + throw FileError(errorMessage); + } + +#elif defined FFS_LINUX + fileHandle = ::fopen(filename.c_str(), + //GNU extension: https://www.securecoding.cert.org/confluence/display/cplusplus/FIO03-CPP.+Do+not+make+assumptions+about+fopen()+and+file+creation + access == ACC_OVERWRITE ? "w,type=record,noseek" : "wx,type=record,noseek"); + if (fileHandle == NULL) + { + const int lastError = errno; + std::wstring errorMessage = _("Error writing file:") + "\n\"" + filename_ + "\"" + "\n\n" + zen::getLastErrorFormatted(lastError); + if (lastError == EEXIST) + throw ErrorTargetExisting(errorMessage); + + if (lastError == ENOENT) + throw ErrorTargetPathMissing(errorMessage); + + throw FileError(errorMessage); + } +#endif +} + + +FileOutput::~FileOutput() +{ +#ifdef FFS_WIN + ::CloseHandle(fileHandle); +#elif defined FFS_LINUX + ::fclose(fileHandle); //NEVER allow passing NULL to fclose! -> crash! +#endif +} + + +void FileOutput::write(const void* buffer, size_t bytesToWrite) //throw FileError +{ +#ifdef FFS_WIN + DWORD bytesWritten = 0; + if (!::WriteFile(fileHandle, //__in HANDLE hFile, + buffer, //__out LPVOID lpBuffer, + static_cast<DWORD>(bytesToWrite), //__in DWORD nNumberOfBytesToWrite, + &bytesWritten, //__out_opt LPDWORD lpNumberOfBytesWritten, + NULL)) //__inout_opt LPOVERLAPPED lpOverlapped +#elif defined FFS_LINUX + const size_t bytesWritten = ::fwrite(buffer, 1, bytesToWrite, fileHandle); + if (::ferror(fileHandle) != 0) +#endif + throw FileError(_("Error writing file:") + "\n\"" + filename_ + "\"" + "\n\n" + zen::getLastErrorFormatted() + " (w)"); //w -> distinguish from fopen error message! + + if (bytesWritten != bytesToWrite) //must be fulfilled for synchronous writes! + throw FileError(_("Error writing file:") + "\n\"" + filename_ + "\"" + "\n\n" + "incomplete write"); +} |