iostreams/test/large_file_test.cpp

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;
}