1 /*
2  * Distributed under the Boost Software License, Version 1.0.(See accompanying
3  * file LICENSE_1_0.txt or copy at http://www.boost.org/LICENSE_1_0.txt.)
4  *
5  * See http://www.boost.org/libs/iostreams for documentation.
6  *
7  * Tests seeking with a file_descriptor using large file offsets.
8  *
9  * File:        libs/iostreams/test/large_file_test.cpp
10  * Date:        Tue Dec 25 21:34:47 MST 2007
11  * Copyright:   2007-2008 CodeRage, LLC
12  * Author:      Jonathan Turkanis
13  * Contact:     turkanis at coderage dot com
14  */
15 
16 #include <cstdio>            // SEEK_SET, etc.
17 #include <ctime>
18 #include <string>
19 #include <boost/config.hpp>  // BOOST_STRINGIZE
20 #include <boost/detail/workaround.hpp>
21 #include <boost/iostreams/detail/config/rtl.hpp>
22 #include <boost/iostreams/detail/config/windows_posix.hpp>
23 #include <boost/iostreams/detail/execute.hpp>
24 #include <boost/iostreams/detail/ios.hpp>
25 #include <boost/iostreams/device/file_descriptor.hpp>
26 #include <boost/iostreams/device/mapped_file.hpp>
27 #include <boost/iostreams/positioning.hpp>
28 #include <boost/lexical_cast.hpp>
29 #include <boost/test/test_tools.hpp>
30 #include <boost/test/unit_test.hpp>
31 #include <iostream>
32 
33     // OS-specific headers for low-level i/o.
34 
35 #include <fcntl.h>       // file opening flags.
36 #include <sys/stat.h>    // file access permissions.
37 #ifdef BOOST_IOSTREAMS_WINDOWS
38 # include <io.h>         // low-level file i/o.
39 # define WINDOWS_LEAN_AND_MEAN
40 # include <windows.h>
41 # ifndef INVALID_SET_FILE_POINTER
42 #  define INVALID_SET_FILE_POINTER ((DWORD)-1)
43 # endif
44 #else
45 # include <sys/types.h>  // mode_t.
46 # include <unistd.h>     // low-level file i/o.
47 #endif
48 
49 using namespace std;
50 using namespace boost;
51 using namespace boost::iostreams;
52 using boost::unit_test::test_suite;
53 
54 //------------------Definition of constants-----------------------------------//
55 
56 const stream_offset gigabyte = 1073741824;
57 const stream_offset file_size = // Some compilers complain about "8589934593"
58     gigabyte * static_cast<stream_offset>(8) + static_cast<stream_offset>(1);
59 const int offset_list[] =
60     { 0, 1, 2, 3, 4, 5, 6, 7, 8, 7, 6, 5, 4, 3, 2, 1, // Seek by 1GB
61       0, 2, 1, 3, 2, 4, 3, 5, 4, 6, 5, 7, 6, 8,       // Seek by 2GB
62          6, 7, 5, 6, 4, 5, 3, 4, 2, 3, 1, 2,
63       0, 3, 1, 4, 2, 5, 3, 6, 4, 7, 5, 8,             // Seek by 3GB
64          5, 7, 4, 6, 3, 5, 2, 4, 1,
65       0, 4, 1, 5, 2, 6, 3, 7, 4, 8,                   // Seek by 4GB
66          4, 7, 3, 6, 2, 5, 1, 4,
67       0, 5, 1, 6, 2, 7, 3, 8, 3, 7, 2, 6, 1, 5,       // Seek by 5GB
68       0, 6, 1, 7, 2, 8, 2, 7, 1, 6,                   // Seek by 6GB
69       0, 7, 1, 8, 1, 7,                               // Seek by 7GB
70       0, 8, 0 };                                      // Seek by 8GB
71 const int offset_list_length = sizeof(offset_list) / sizeof(int);
72 #ifdef LARGE_FILE_TEMP
73 # define BOOST_FILE_NAME BOOST_STRINGIZE(LARGE_FILE_TEMP)
74 # define BOOST_KEEP_FILE false
75 #else
76 # define BOOST_FILE_NAME BOOST_STRINGIZE(LARGE_FILE_KEEP)
77 # define BOOST_KEEP_FILE true
78 #endif
79 
80 //------------------Definition of remove_large_file---------------------------//
81 
82 // Removes the large file
remove_large_file()83 void remove_large_file()
84 {
85 #ifdef BOOST_IOSTREAMS_WINDOWS
86     DeleteFile(TEXT(BOOST_FILE_NAME));
87 #else
88     unlink(BOOST_FILE_NAME);
89 #endif
90 }
91 
92 //------------------Definition of large_file_exists---------------------------//
93 
94 // Returns true if the large file exists, has the correct size, and has been
95 // modified since the last commit affecting this source file; if the file exists
96 // but is invalid, deletes the file.
large_file_exists()97 bool large_file_exists()
98 {
99     // Last mod date
100     time_t last_mod;
101 
102 #ifdef BOOST_IOSTREAMS_WINDOWS
103 
104     // Check existence
105     WIN32_FIND_DATA info;
106     HANDLE hnd = FindFirstFile(TEXT(BOOST_FILE_NAME), &info);
107     if (hnd == INVALID_HANDLE_VALUE)
108         return false;
109 
110     // Check size
111     FindClose(hnd);
112     stream_offset size =
113         (static_cast<stream_offset>(info.nFileSizeHigh) << 32) +
114         static_cast<stream_offset>(info.nFileSizeLow);
115     if (size != file_size) {
116         remove_large_file();
117         return false;
118     }
119 
120     // Fetch last mod date
121     SYSTEMTIME stime;
122     if (!FileTimeToSystemTime(&info.ftLastWriteTime, &stime)) {
123         remove_large_file();
124         return false;
125     }
126     tm ctime;
127     ctime.tm_year = stime.wYear - 1900;
128     ctime.tm_mon = stime.wMonth - 1;
129     ctime.tm_mday = stime.wDay;
130     ctime.tm_hour = stime.wHour;
131     ctime.tm_min = stime.wMinute;
132     ctime.tm_sec = stime.wSecond;
133     ctime.tm_isdst = 0;
134     last_mod = mktime(&ctime);
135 
136 #else
137 
138     // Check existence
139     struct BOOST_IOSTREAMS_FD_STAT info;
140     if (BOOST_IOSTREAMS_FD_STAT(BOOST_FILE_NAME, &info))
141         return false;
142 
143     // Check size
144     if (info.st_size != file_size) {
145         remove_large_file();
146         return false;
147     }
148 
149     // Fetch last mod date
150     last_mod = info.st_mtime;
151 
152 #endif
153 
154     // Fetch last mod date of this file ("large_file_test.cpp")
155     string timestamp =
156         "$Date$";
157     if (timestamp.size() != 53) { // Length of auto-generated SVN timestamp
158         remove_large_file();
159         return false;
160     }
161     tm commit;
162     try {
163         commit.tm_year = lexical_cast<int>(timestamp.substr(7, 4)) - 1900;
164         commit.tm_mon = lexical_cast<int>(timestamp.substr(12, 2)) - 1;
165         commit.tm_mday = lexical_cast<int>(timestamp.substr(15, 2));
166         commit.tm_hour = lexical_cast<int>(timestamp.substr(18, 2));
167         commit.tm_min = lexical_cast<int>(timestamp.substr(21, 2));
168         commit.tm_sec = lexical_cast<int>(timestamp.substr(24, 2));
169     } catch (const bad_lexical_cast&) {
170         remove_large_file();
171         return false;
172     }
173 
174     // If last commit was two days or more before file timestamp, existing
175     // file is okay; otherwise, it must be regenerated (the two-day window
176     // compensates for time zone differences)
177     return difftime(last_mod, mktime(&commit)) >= 60 * 60 * 48;
178 }
179 
180 //------------------Definition of map_large_file------------------------------//
181 
182 // Initializes the large file by mapping it in small segments. This is an
183 // optimization for Win32; the straightforward implementation using WriteFile
184 // and SetFilePointer (see the Borland workaround below) is painfully slow.
map_large_file()185 bool map_large_file()
186 {
187     for (stream_offset z = 0; z <= 8; ++z) {
188         try {
189             mapped_file_params params;
190             params.path = BOOST_FILE_NAME;
191             params.offset = z * gigabyte;
192             params.length = 1;
193             params.mode = BOOST_IOS::out;
194             mapped_file file(params);
195             file.begin()[0] = static_cast<char>(z + 1);
196         } catch (const std::exception&) {
197             remove_large_file();
198             return false;
199         }
200     }
201     return true;
202 }
203 
204 //------------------Definition of create_large_file---------------------------//
205 
206 // Creates and initializes the large file if it does not already exist. The file
207 // looks like this:
208 //
209 //     0     1GB   2GB   3GB   4GB   5GB   6GB   7GB   8GB
210 //     +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
211 //     1.....2.....3.....4.....5.....6.....7.....8.....9
212 //     +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
213 //
214 // where the characters 1-9 appear at offsets that are multiples of 1GB and the
215 // dots represent uninitialized data.
create_large_file()216 bool create_large_file()
217 {
218     // If file exists, has correct size, and is recent, we're done
219     if (BOOST_KEEP_FILE && large_file_exists())
220         return true;
221 
222 #ifdef BOOST_IOSTREAMS_WINDOWS
223 
224     // Create file
225     HANDLE hnd =
226         CreateFile(
227             TEXT(BOOST_FILE_NAME),
228             GENERIC_WRITE,
229             0,
230             NULL,
231             CREATE_ALWAYS,
232             FILE_ATTRIBUTE_NORMAL,
233             NULL
234         );
235     if (!hnd)
236         return false;
237 
238     // Set file pointer
239     LONG off_low = static_cast<LONG>(file_size & 0xffffffff);
240     LONG off_high = static_cast<LONG>(file_size >> 32);
241     if ( SetFilePointer(hnd, off_low, &off_high, FILE_BEGIN) ==
242             INVALID_SET_FILE_POINTER &&
243          GetLastError() != NO_ERROR )
244     {
245         CloseHandle(hnd);
246         remove_large_file();
247         return false;
248     }
249 
250     // Set file size
251     if (!SetEndOfFile(hnd)) {
252         CloseHandle(hnd);
253         remove_large_file();
254         return false;
255     }
256 
257 # if !defined(__BORLANDC__) || __BORLANDC__ < 0x582 || __BORLANDC__ >= 0x592
258 
259     // Close handle; all further access is via mapped_file
260     CloseHandle(hnd);
261 
262     // Initialize file data
263     return map_large_file();
264 
265 # else // Borland >= 5.8.2 and Borland < 5.9.2
266 
267     // Initialize file data (very slow, even though only 9 writes are required)
268     for (stream_offset z = 0; z <= 8; ++z) {
269 
270         // Seek
271         LONG off_low = static_cast<LONG>((z * gigabyte) & 0xffffffff); // == 0
272         LONG off_high = static_cast<LONG>((z * gigabyte) >> 32);
273         if ( SetFilePointer(hnd, off_low, &off_high, FILE_BEGIN) ==
274                 INVALID_SET_FILE_POINTER &&
275              GetLastError() != NO_ERROR )
276         {
277             CloseHandle(hnd);
278             remove_large_file();
279             return false;
280         }
281 
282         // Write a character
283         char buf[1] = { z + 1 };
284         DWORD result;
285         BOOL success = WriteFile(hnd, buf, 1, &result, NULL);
286         if (!success || result != 1) {
287             CloseHandle(hnd);
288             remove_large_file();
289             return false;
290         }
291     }
292 
293     // Close file
294     CloseHandle(hnd);
295     return true;
296 
297 # endif // Borland workaround
298 #else // #ifdef BOOST_IOSTREAMS_WINDOWS
299 
300     // Create file
301     int oflag = O_WRONLY | O_CREAT;
302     #ifdef _LARGEFILE64_SOURCE
303         oflag |= O_LARGEFILE;
304     #endif
305     mode_t pmode =
306         S_IRUSR | S_IWUSR |
307         S_IRGRP | S_IWGRP |
308         S_IROTH | S_IWOTH;
309     int fd = BOOST_IOSTREAMS_FD_OPEN(BOOST_FILE_NAME, oflag, pmode);
310     if (fd == -1)
311         return false;
312 
313     // Set file size
314     if (BOOST_IOSTREAMS_FD_TRUNCATE(fd, file_size)) {
315         BOOST_IOSTREAMS_FD_CLOSE(fd);
316         return false;
317     }
318 
319 # ifndef __CYGWIN__
320 
321     // Initialize file data
322     for (int z = 0; z <= 8; ++z) {
323 
324         // Seek
325         BOOST_IOSTREAMS_FD_OFFSET off =
326             BOOST_IOSTREAMS_FD_SEEK(
327                 fd,
328                 static_cast<BOOST_IOSTREAMS_FD_OFFSET>(z * gigabyte),
329                 SEEK_SET
330             );
331         if (off == -1) {
332             BOOST_IOSTREAMS_FD_CLOSE(fd);
333             return false;
334         }
335 
336         // Write a character
337         char buf[1] = { z + 1 };
338         if (BOOST_IOSTREAMS_FD_WRITE(fd, buf, 1) == -1) {
339             BOOST_IOSTREAMS_FD_CLOSE(fd);
340             return false;
341         }
342     }
343 
344     // Close file
345     BOOST_IOSTREAMS_FD_CLOSE(fd);
346     return true;
347 
348 # else // Cygwin
349 
350     // Close descriptor; all further access is via mapped_file
351     BOOST_IOSTREAMS_FD_CLOSE(fd);
352 
353     // Initialize file data
354     return map_large_file();
355 
356 # endif
357 #endif // #ifdef BOOST_IOSTREAMS_WINDOWS
358 }
359 
360 //------------------Definition of large_file----------------------------------//
361 
362 // RAII utility
363 class large_file {
364 public:
large_file()365     large_file() { exists_ = create_large_file(); }
~large_file()366     ~large_file() { if (!BOOST_KEEP_FILE) remove_large_file(); }
exists() const367     bool exists() const { return exists_; }
path() const368     const char* path() const { return BOOST_FILE_NAME; }
369 private:
370     bool exists_;
371 };
372 
373 //------------------Definition of check_character-----------------------------//
374 
375 // Verify that the given file contains the given character at the current
376 // position
check_character(file_descriptor_source & file,char value)377 bool check_character(file_descriptor_source& file, char value)
378 {
379     char             buf[1];
380     std::streamsize  amt;
381     BOOST_CHECK_NO_THROW(amt = file.read(buf, 1));
382     BOOST_CHECK_MESSAGE(amt == 1, "failed reading character");
383     BOOST_CHECK_NO_THROW(file.seek(-1, BOOST_IOS::cur));
384     return buf[0] == value;
385 }
386 
387 //------------------Definition of large_file_test-----------------------------//
388 
large_file_test()389 void large_file_test()
390 {
391     BOOST_REQUIRE_MESSAGE(
392         sizeof(stream_offset) >= 8,
393         "large offsets not supported"
394     );
395 
396     // Prepare file and file descriptor
397     large_file              large;
398     file_descriptor_source  file;
399     BOOST_REQUIRE_MESSAGE(
400         large.exists(), "failed creating file \"" << BOOST_FILE_NAME << '"'
401     );
402     BOOST_CHECK_NO_THROW(file.open(large.path(), BOOST_IOS::binary));
403 
404     // Test seeking using ios_base::beg
405     for (int z = 0; z < offset_list_length; ++z) {
406         char value = offset_list[z] + 1;
407         stream_offset off =
408             static_cast<stream_offset>(offset_list[z]) * gigabyte;
409         BOOST_CHECK_NO_THROW(file.seek(off, BOOST_IOS::beg));
410         BOOST_CHECK_MESSAGE(
411             check_character(file, value),
412             "failed validating seek"
413         );
414     }
415 
416     // Test seeking using ios_base::end
417     for (int z = 0; z < offset_list_length; ++z) {
418         char value = offset_list[z] + 1;
419         stream_offset off =
420             -static_cast<stream_offset>(8 - offset_list[z]) * gigabyte - 1;
421         BOOST_CHECK_NO_THROW(file.seek(off, BOOST_IOS::end));
422         BOOST_CHECK_MESSAGE(
423             check_character(file, value),
424             "failed validating seek"
425         );
426     }
427 
428     // Test seeking using ios_base::cur
429     for (int next, cur = 0, z = 0; z < offset_list_length; ++z, cur = next) {
430         next = offset_list[z];
431         char value = offset_list[z] + 1;
432         stream_offset off = static_cast<stream_offset>(next - cur) * gigabyte;
433         BOOST_CHECK_NO_THROW(file.seek(off, BOOST_IOS::cur));
434         BOOST_CHECK_MESSAGE(
435             check_character(file, value),
436             "failed validating seek"
437         );
438     }
439 }
440 
init_unit_test_suite(int,char * [])441 test_suite* init_unit_test_suite(int, char* [])
442 {
443     test_suite* test = BOOST_TEST_SUITE("execute test");
444     test->add(BOOST_TEST_CASE(&large_file_test));
445     return test;
446 }
447