/* * 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 // SEEK_SET, etc. #include #include #include // BOOST_STRINGIZE #include #include #include #include #include #include #include #include #include #include #include #include // OS-specific headers for low-level i/o. #include // file opening flags. #include // file access permissions. #ifdef BOOST_IOSTREAMS_WINDOWS # include // low-level file i/o. # define WINDOWS_LEAN_AND_MEAN # include # ifndef INVALID_SET_FILE_POINTER # define INVALID_SET_FILE_POINTER ((DWORD)-1) # endif #else # include // mode_t. # include // 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(8) + static_cast(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(info.nFileSizeHigh) << 32) + static_cast(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(timestamp.substr(7, 4)) - 1900; commit.tm_mon = lexical_cast(timestamp.substr(12, 2)) - 1; commit.tm_mday = lexical_cast(timestamp.substr(15, 2)); commit.tm_hour = lexical_cast(timestamp.substr(18, 2)); commit.tm_min = lexical_cast(timestamp.substr(21, 2)); commit.tm_sec = lexical_cast(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(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(file_size & 0xffffffff); LONG off_high = static_cast(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((z * gigabyte) & 0xffffffff); // == 0 LONG off_high = static_cast((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(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(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(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(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; }