447 lines
13 KiB
C++
447 lines
13 KiB
C++
/*
|
|
* Distributed under the Boost Software License, Version 1.0.(See accompanying
|
|
* file LICENSE_1_0.txt or copy at http://www.boost.org/LICENSE_1_0.txt.)
|
|
*
|
|
* See http://www.boost.org/libs/iostreams for documentation.
|
|
*
|
|
* Tests seeking with a file_descriptor using large file offsets.
|
|
*
|
|
* File: libs/iostreams/test/large_file_test.cpp
|
|
* Date: Tue Dec 25 21:34:47 MST 2007
|
|
* Copyright: 2007-2008 CodeRage, LLC
|
|
* Author: Jonathan Turkanis
|
|
* Contact: turkanis at coderage dot com
|
|
*/
|
|
|
|
#include <cstdio> // SEEK_SET, etc.
|
|
#include <ctime>
|
|
#include <string>
|
|
#include <boost/config.hpp> // BOOST_STRINGIZE
|
|
#include <boost/detail/workaround.hpp>
|
|
#include <boost/iostreams/detail/config/rtl.hpp>
|
|
#include <boost/iostreams/detail/config/windows_posix.hpp>
|
|
#include <boost/iostreams/detail/execute.hpp>
|
|
#include <boost/iostreams/detail/ios.hpp>
|
|
#include <boost/iostreams/device/file_descriptor.hpp>
|
|
#include <boost/iostreams/device/mapped_file.hpp>
|
|
#include <boost/iostreams/positioning.hpp>
|
|
#include <boost/lexical_cast.hpp>
|
|
#include <boost/test/test_tools.hpp>
|
|
#include <boost/test/unit_test.hpp>
|
|
#include <iostream>
|
|
|
|
// OS-specific headers for low-level i/o.
|
|
|
|
#include <fcntl.h> // file opening flags.
|
|
#include <sys/stat.h> // file access permissions.
|
|
#ifdef BOOST_IOSTREAMS_WINDOWS
|
|
# include <io.h> // low-level file i/o.
|
|
# define WINDOWS_LEAN_AND_MEAN
|
|
# include <windows.h>
|
|
# ifndef INVALID_SET_FILE_POINTER
|
|
# define INVALID_SET_FILE_POINTER ((DWORD)-1)
|
|
# endif
|
|
#else
|
|
# include <sys/types.h> // mode_t.
|
|
# include <unistd.h> // low-level file i/o.
|
|
#endif
|
|
|
|
using namespace std;
|
|
using namespace boost;
|
|
using namespace boost::iostreams;
|
|
using boost::unit_test::test_suite;
|
|
|
|
//------------------Definition of constants-----------------------------------//
|
|
|
|
const stream_offset gigabyte = 1073741824;
|
|
const stream_offset file_size = // Some compilers complain about "8589934593"
|
|
gigabyte * static_cast<stream_offset>(8) + static_cast<stream_offset>(1);
|
|
const int offset_list[] =
|
|
{ 0, 1, 2, 3, 4, 5, 6, 7, 8, 7, 6, 5, 4, 3, 2, 1, // Seek by 1GB
|
|
0, 2, 1, 3, 2, 4, 3, 5, 4, 6, 5, 7, 6, 8, // Seek by 2GB
|
|
6, 7, 5, 6, 4, 5, 3, 4, 2, 3, 1, 2,
|
|
0, 3, 1, 4, 2, 5, 3, 6, 4, 7, 5, 8, // Seek by 3GB
|
|
5, 7, 4, 6, 3, 5, 2, 4, 1,
|
|
0, 4, 1, 5, 2, 6, 3, 7, 4, 8, // Seek by 4GB
|
|
4, 7, 3, 6, 2, 5, 1, 4,
|
|
0, 5, 1, 6, 2, 7, 3, 8, 3, 7, 2, 6, 1, 5, // Seek by 5GB
|
|
0, 6, 1, 7, 2, 8, 2, 7, 1, 6, // Seek by 6GB
|
|
0, 7, 1, 8, 1, 7, // Seek by 7GB
|
|
0, 8, 0 }; // Seek by 8GB
|
|
const int offset_list_length = sizeof(offset_list) / sizeof(int);
|
|
#ifdef LARGE_FILE_TEMP
|
|
# define BOOST_FILE_NAME BOOST_STRINGIZE(LARGE_FILE_TEMP)
|
|
# define BOOST_KEEP_FILE false
|
|
#else
|
|
# define BOOST_FILE_NAME BOOST_STRINGIZE(LARGE_FILE_KEEP)
|
|
# define BOOST_KEEP_FILE true
|
|
#endif
|
|
|
|
//------------------Definition of remove_large_file---------------------------//
|
|
|
|
// Removes the large file
|
|
void remove_large_file()
|
|
{
|
|
#ifdef BOOST_IOSTREAMS_WINDOWS
|
|
DeleteFile(TEXT(BOOST_FILE_NAME));
|
|
#else
|
|
unlink(BOOST_FILE_NAME);
|
|
#endif
|
|
}
|
|
|
|
//------------------Definition of large_file_exists---------------------------//
|
|
|
|
// Returns true if the large file exists, has the correct size, and has been
|
|
// modified since the last commit affecting this source file; if the file exists
|
|
// but is invalid, deletes the file.
|
|
bool large_file_exists()
|
|
{
|
|
// Last mod date
|
|
time_t last_mod;
|
|
|
|
#ifdef BOOST_IOSTREAMS_WINDOWS
|
|
|
|
// Check existence
|
|
WIN32_FIND_DATA info;
|
|
HANDLE hnd = FindFirstFile(TEXT(BOOST_FILE_NAME), &info);
|
|
if (hnd == INVALID_HANDLE_VALUE)
|
|
return false;
|
|
|
|
// Check size
|
|
FindClose(hnd);
|
|
stream_offset size =
|
|
(static_cast<stream_offset>(info.nFileSizeHigh) << 32) +
|
|
static_cast<stream_offset>(info.nFileSizeLow);
|
|
if (size != file_size) {
|
|
remove_large_file();
|
|
return false;
|
|
}
|
|
|
|
// Fetch last mod date
|
|
SYSTEMTIME stime;
|
|
if (!FileTimeToSystemTime(&info.ftLastWriteTime, &stime)) {
|
|
remove_large_file();
|
|
return false;
|
|
}
|
|
tm ctime;
|
|
ctime.tm_year = stime.wYear - 1900;
|
|
ctime.tm_mon = stime.wMonth - 1;
|
|
ctime.tm_mday = stime.wDay;
|
|
ctime.tm_hour = stime.wHour;
|
|
ctime.tm_min = stime.wMinute;
|
|
ctime.tm_sec = stime.wSecond;
|
|
ctime.tm_isdst = 0;
|
|
last_mod = mktime(&ctime);
|
|
|
|
#else
|
|
|
|
// Check existence
|
|
struct BOOST_IOSTREAMS_FD_STAT info;
|
|
if (BOOST_IOSTREAMS_FD_STAT(BOOST_FILE_NAME, &info))
|
|
return false;
|
|
|
|
// Check size
|
|
if (info.st_size != file_size) {
|
|
remove_large_file();
|
|
return false;
|
|
}
|
|
|
|
// Fetch last mod date
|
|
last_mod = info.st_mtime;
|
|
|
|
#endif
|
|
|
|
// Fetch last mod date of this file ("large_file_test.cpp")
|
|
string timestamp =
|
|
"$Date$";
|
|
if (timestamp.size() != 53) { // Length of auto-generated SVN timestamp
|
|
remove_large_file();
|
|
return false;
|
|
}
|
|
tm commit;
|
|
try {
|
|
commit.tm_year = lexical_cast<int>(timestamp.substr(7, 4)) - 1900;
|
|
commit.tm_mon = lexical_cast<int>(timestamp.substr(12, 2)) - 1;
|
|
commit.tm_mday = lexical_cast<int>(timestamp.substr(15, 2));
|
|
commit.tm_hour = lexical_cast<int>(timestamp.substr(18, 2));
|
|
commit.tm_min = lexical_cast<int>(timestamp.substr(21, 2));
|
|
commit.tm_sec = lexical_cast<int>(timestamp.substr(24, 2));
|
|
} catch (const bad_lexical_cast&) {
|
|
remove_large_file();
|
|
return false;
|
|
}
|
|
|
|
// If last commit was two days or more before file timestamp, existing
|
|
// file is okay; otherwise, it must be regenerated (the two-day window
|
|
// compensates for time zone differences)
|
|
return difftime(last_mod, mktime(&commit)) >= 60 * 60 * 48;
|
|
}
|
|
|
|
//------------------Definition of map_large_file------------------------------//
|
|
|
|
// Initializes the large file by mapping it in small segments. This is an
|
|
// optimization for Win32; the straightforward implementation using WriteFile
|
|
// and SetFilePointer (see the Borland workaround below) is painfully slow.
|
|
bool map_large_file()
|
|
{
|
|
for (stream_offset z = 0; z <= 8; ++z) {
|
|
try {
|
|
mapped_file_params params;
|
|
params.path = BOOST_FILE_NAME;
|
|
params.offset = z * gigabyte;
|
|
params.length = 1;
|
|
params.mode = BOOST_IOS::out;
|
|
mapped_file file(params);
|
|
file.begin()[0] = static_cast<char>(z + 1);
|
|
} catch (const std::exception&) {
|
|
remove_large_file();
|
|
return false;
|
|
}
|
|
}
|
|
return true;
|
|
}
|
|
|
|
//------------------Definition of create_large_file---------------------------//
|
|
|
|
// Creates and initializes the large file if it does not already exist. The file
|
|
// looks like this:
|
|
//
|
|
// 0 1GB 2GB 3GB 4GB 5GB 6GB 7GB 8GB
|
|
// +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
|
|
// 1.....2.....3.....4.....5.....6.....7.....8.....9
|
|
// +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
|
|
//
|
|
// where the characters 1-9 appear at offsets that are multiples of 1GB and the
|
|
// dots represent uninitialized data.
|
|
bool create_large_file()
|
|
{
|
|
// If file exists, has correct size, and is recent, we're done
|
|
if (BOOST_KEEP_FILE && large_file_exists())
|
|
return true;
|
|
|
|
#ifdef BOOST_IOSTREAMS_WINDOWS
|
|
|
|
// Create file
|
|
HANDLE hnd =
|
|
CreateFile(
|
|
TEXT(BOOST_FILE_NAME),
|
|
GENERIC_WRITE,
|
|
0,
|
|
NULL,
|
|
CREATE_ALWAYS,
|
|
FILE_ATTRIBUTE_NORMAL,
|
|
NULL
|
|
);
|
|
if (!hnd)
|
|
return false;
|
|
|
|
// Set file pointer
|
|
LONG off_low = static_cast<LONG>(file_size & 0xffffffff);
|
|
LONG off_high = static_cast<LONG>(file_size >> 32);
|
|
if ( SetFilePointer(hnd, off_low, &off_high, FILE_BEGIN) ==
|
|
INVALID_SET_FILE_POINTER &&
|
|
GetLastError() != NO_ERROR )
|
|
{
|
|
CloseHandle(hnd);
|
|
remove_large_file();
|
|
return false;
|
|
}
|
|
|
|
// Set file size
|
|
if (!SetEndOfFile(hnd)) {
|
|
CloseHandle(hnd);
|
|
remove_large_file();
|
|
return false;
|
|
}
|
|
|
|
# if !defined(__BORLANDC__) || __BORLANDC__ < 0x582 || __BORLANDC__ >= 0x592
|
|
|
|
// Close handle; all further access is via mapped_file
|
|
CloseHandle(hnd);
|
|
|
|
// Initialize file data
|
|
return map_large_file();
|
|
|
|
# else // Borland >= 5.8.2 and Borland < 5.9.2
|
|
|
|
// Initialize file data (very slow, even though only 9 writes are required)
|
|
for (stream_offset z = 0; z <= 8; ++z) {
|
|
|
|
// Seek
|
|
LONG off_low = static_cast<LONG>((z * gigabyte) & 0xffffffff); // == 0
|
|
LONG off_high = static_cast<LONG>((z * gigabyte) >> 32);
|
|
if ( SetFilePointer(hnd, off_low, &off_high, FILE_BEGIN) ==
|
|
INVALID_SET_FILE_POINTER &&
|
|
GetLastError() != NO_ERROR )
|
|
{
|
|
CloseHandle(hnd);
|
|
remove_large_file();
|
|
return false;
|
|
}
|
|
|
|
// Write a character
|
|
char buf[1] = { z + 1 };
|
|
DWORD result;
|
|
BOOL success = WriteFile(hnd, buf, 1, &result, NULL);
|
|
if (!success || result != 1) {
|
|
CloseHandle(hnd);
|
|
remove_large_file();
|
|
return false;
|
|
}
|
|
}
|
|
|
|
// Close file
|
|
CloseHandle(hnd);
|
|
return true;
|
|
|
|
# endif // Borland workaround
|
|
#else // #ifdef BOOST_IOSTREAMS_WINDOWS
|
|
|
|
// Create file
|
|
int oflag = O_WRONLY | O_CREAT;
|
|
#ifdef _LARGEFILE64_SOURCE
|
|
oflag |= O_LARGEFILE;
|
|
#endif
|
|
mode_t pmode =
|
|
S_IRUSR | S_IWUSR |
|
|
S_IRGRP | S_IWGRP |
|
|
S_IROTH | S_IWOTH;
|
|
int fd = BOOST_IOSTREAMS_FD_OPEN(BOOST_FILE_NAME, oflag, pmode);
|
|
if (fd == -1)
|
|
return false;
|
|
|
|
// Set file size
|
|
if (BOOST_IOSTREAMS_FD_TRUNCATE(fd, file_size)) {
|
|
BOOST_IOSTREAMS_FD_CLOSE(fd);
|
|
return false;
|
|
}
|
|
|
|
# ifndef __CYGWIN__
|
|
|
|
// Initialize file data
|
|
for (int z = 0; z <= 8; ++z) {
|
|
|
|
// Seek
|
|
BOOST_IOSTREAMS_FD_OFFSET off =
|
|
BOOST_IOSTREAMS_FD_SEEK(
|
|
fd,
|
|
static_cast<BOOST_IOSTREAMS_FD_OFFSET>(z * gigabyte),
|
|
SEEK_SET
|
|
);
|
|
if (off == -1) {
|
|
BOOST_IOSTREAMS_FD_CLOSE(fd);
|
|
return false;
|
|
}
|
|
|
|
// Write a character
|
|
char buf[1] = { z + 1 };
|
|
if (BOOST_IOSTREAMS_FD_WRITE(fd, buf, 1) == -1) {
|
|
BOOST_IOSTREAMS_FD_CLOSE(fd);
|
|
return false;
|
|
}
|
|
}
|
|
|
|
// Close file
|
|
BOOST_IOSTREAMS_FD_CLOSE(fd);
|
|
return true;
|
|
|
|
# else // Cygwin
|
|
|
|
// Close descriptor; all further access is via mapped_file
|
|
BOOST_IOSTREAMS_FD_CLOSE(fd);
|
|
|
|
// Initialize file data
|
|
return map_large_file();
|
|
|
|
# endif
|
|
#endif // #ifdef BOOST_IOSTREAMS_WINDOWS
|
|
}
|
|
|
|
//------------------Definition of large_file----------------------------------//
|
|
|
|
// RAII utility
|
|
class large_file {
|
|
public:
|
|
large_file() { exists_ = create_large_file(); }
|
|
~large_file() { if (!BOOST_KEEP_FILE) remove_large_file(); }
|
|
bool exists() const { return exists_; }
|
|
const char* path() const { return BOOST_FILE_NAME; }
|
|
private:
|
|
bool exists_;
|
|
};
|
|
|
|
//------------------Definition of check_character-----------------------------//
|
|
|
|
// Verify that the given file contains the given character at the current
|
|
// position
|
|
bool check_character(file_descriptor_source& file, char value)
|
|
{
|
|
char buf[1];
|
|
std::streamsize amt;
|
|
BOOST_CHECK_NO_THROW(amt = file.read(buf, 1));
|
|
BOOST_CHECK_MESSAGE(amt == 1, "failed reading character");
|
|
BOOST_CHECK_NO_THROW(file.seek(-1, BOOST_IOS::cur));
|
|
return buf[0] == value;
|
|
}
|
|
|
|
//------------------Definition of large_file_test-----------------------------//
|
|
|
|
void large_file_test()
|
|
{
|
|
BOOST_REQUIRE_MESSAGE(
|
|
sizeof(stream_offset) >= 8,
|
|
"large offsets not supported"
|
|
);
|
|
|
|
// Prepare file and file descriptor
|
|
large_file large;
|
|
file_descriptor_source file;
|
|
BOOST_REQUIRE_MESSAGE(
|
|
large.exists(), "failed creating file \"" << BOOST_FILE_NAME << '"'
|
|
);
|
|
BOOST_CHECK_NO_THROW(file.open(large.path(), BOOST_IOS::binary));
|
|
|
|
// Test seeking using ios_base::beg
|
|
for (int z = 0; z < offset_list_length; ++z) {
|
|
char value = offset_list[z] + 1;
|
|
stream_offset off =
|
|
static_cast<stream_offset>(offset_list[z]) * gigabyte;
|
|
BOOST_CHECK_NO_THROW(file.seek(off, BOOST_IOS::beg));
|
|
BOOST_CHECK_MESSAGE(
|
|
check_character(file, value),
|
|
"failed validating seek"
|
|
);
|
|
}
|
|
|
|
// Test seeking using ios_base::end
|
|
for (int z = 0; z < offset_list_length; ++z) {
|
|
char value = offset_list[z] + 1;
|
|
stream_offset off =
|
|
-static_cast<stream_offset>(8 - offset_list[z]) * gigabyte - 1;
|
|
BOOST_CHECK_NO_THROW(file.seek(off, BOOST_IOS::end));
|
|
BOOST_CHECK_MESSAGE(
|
|
check_character(file, value),
|
|
"failed validating seek"
|
|
);
|
|
}
|
|
|
|
// Test seeking using ios_base::cur
|
|
for (int next, cur = 0, z = 0; z < offset_list_length; ++z, cur = next) {
|
|
next = offset_list[z];
|
|
char value = offset_list[z] + 1;
|
|
stream_offset off = static_cast<stream_offset>(next - cur) * gigabyte;
|
|
BOOST_CHECK_NO_THROW(file.seek(off, BOOST_IOS::cur));
|
|
BOOST_CHECK_MESSAGE(
|
|
check_character(file, value),
|
|
"failed validating seek"
|
|
);
|
|
}
|
|
}
|
|
|
|
test_suite* init_unit_test_suite(int, char* [])
|
|
{
|
|
test_suite* test = BOOST_TEST_SUITE("execute test");
|
|
test->add(BOOST_TEST_CASE(&large_file_test));
|
|
return test;
|
|
}
|