1///////////////////////////////////////////////////////////////////////////////
2// Name:        tests/streams/largefile.cpp
3// Purpose:     Tests for large file support
4// Author:      Mike Wetherell
5// RCS-ID:      $Id: largefile.cpp 63301 2010-01-28 21:38:27Z MW $
6// Copyright:   (c) 2004 Mike Wetherell
7// Licence:     wxWidgets licence
8///////////////////////////////////////////////////////////////////////////////
9
10//
11// We're interested in what happens around offsets 0x7fffffff and 0xffffffff
12// so the test writes a small chunk of test data just before and just after
13// these offsets, then reads them back.
14//
15// The tests can be run with:
16//
17//  test --verbose largeFile
18//
19// On systems supporting sparse files they will also be registered in the
20// Streams subsuite so that they run by default.
21//
22
23// For compilers that support precompilation, includes "wx/wx.h".
24#include "testprec.h"
25
26#ifdef __BORLANDC__
27    #pragma hdrstop
28#endif
29
30// for all others, include the necessary headers
31#ifndef WX_PRECOMP
32    #include "wx/wx.h"
33#endif
34
35#include "wx/filename.h"
36#include "wx/wfstream.h"
37
38#ifdef __WXMSW__
39#include "wx/msw/wrapwin.h"
40#include <winioctl.h>
41#endif
42
43using std::auto_ptr;
44
45
46///////////////////////////////////////////////////////////////////////////////
47// Helpers
48
49bool IsFAT(const wxString& path);
50void MakeSparse(const wxString& path, int fd);
51
52
53///////////////////////////////////////////////////////////////////////////////
54// Base class for the test cases - common code
55
56class LargeFileTest : public CppUnit::TestCase
57{
58public:
59    LargeFileTest(std::string name) : CppUnit::TestCase(name) { }
60    virtual ~LargeFileTest() { }
61
62protected:
63    void runTest();
64
65    virtual wxInputStream *MakeInStream(const wxString& name) const = 0;
66    virtual wxOutputStream *MakeOutStream(const wxString& name) const = 0;
67    virtual bool HasLFS() const = 0;
68};
69
70void LargeFileTest::runTest()
71{
72    // self deleting temp file
73    struct TmpFile {
74        TmpFile() : m_name(wxFileName::CreateTempFileName(_T("wxlfs-"))) { }
75        ~TmpFile() { if (!m_name.empty()) wxRemoveFile(m_name); }
76        wxString m_name;
77    } tmpfile;
78
79    CPPUNIT_ASSERT(!tmpfile.m_name.empty());
80
81    bool haveLFS = true;
82    bool fourGigLimit = false;
83
84    if (!HasLFS()) {
85        haveLFS = false;
86        wxString n(getName().c_str(), *wxConvCurrent);
87        wxLogInfo(n + _T(": No large file support, testing up to 2GB only"));
88    }
89    else if (IsFAT(tmpfile.m_name)) {
90        fourGigLimit = true;
91        wxString n(getName().c_str(), *wxConvCurrent);
92        wxLogInfo(n + _T(": FAT volumes are limited to 4GB files"));
93    }
94
95    // size of the test blocks
96    const size_t size = 0x40;
97
98    // the test blocks
99    char upto2Gig[size];
100    char past2Gig[size];
101    char upto4Gig[size];
102    char past4Gig[size];
103    memset(upto2Gig, 'A', size);
104    memset(past2Gig, 'B', size);
105    memset(upto4Gig, 'X', size);
106    memset(past4Gig, 'Y', size);
107
108    wxFileOffset pos;
109
110    // write a large file
111    {
112        auto_ptr<wxOutputStream> out(MakeOutStream(tmpfile.m_name));
113
114        // write 'A's at [ 0x7fffffbf, 0x7fffffff [
115        pos = 0x7fffffff - size;
116        CPPUNIT_ASSERT(out->SeekO(pos) == pos);
117        CPPUNIT_ASSERT(out->Write(upto2Gig, size).LastWrite() == size);
118        pos += size;
119
120        if (haveLFS) {
121            // write 'B's at [ 0x7fffffff, 0x8000003f [
122            CPPUNIT_ASSERT(out->Write(past2Gig, size).LastWrite() == size);
123            pos += size;
124            CPPUNIT_ASSERT(out->TellO() == pos);
125
126            // write 'X's at [ 0xffffffbf, 0xffffffff [
127            pos = 0xffffffff - size;
128            CPPUNIT_ASSERT(out->SeekO(pos) == pos);
129            CPPUNIT_ASSERT(out->Write(upto4Gig, size).LastWrite() == size);
130            pos += size;
131
132            if (!fourGigLimit) {
133                // write 'Y's at [ 0xffffffff, 0x10000003f [
134                CPPUNIT_ASSERT(out->Write(past4Gig, size).LastWrite() == size);
135                pos += size;
136            }
137        }
138
139        // check the file seems to be the right length
140        CPPUNIT_ASSERT(out->TellO() == pos);
141        CPPUNIT_ASSERT(out->GetLength() == pos);
142    }
143
144    // read the large file back
145    {
146        auto_ptr<wxInputStream> in(MakeInStream(tmpfile.m_name));
147        char buf[size];
148
149        if (haveLFS) {
150            CPPUNIT_ASSERT(in->GetLength() == pos);
151            pos = 0xffffffff;
152
153            if (!fourGigLimit) {
154                CPPUNIT_ASSERT(in->GetLength() > pos);
155
156                // read back the 'Y's at [ 0xffffffff, 0x10000003f [
157                CPPUNIT_ASSERT(in->SeekI(pos) == pos);
158                CPPUNIT_ASSERT(in->Read(buf, size).LastRead() == size);
159                CPPUNIT_ASSERT(memcmp(buf, past4Gig, size) == 0);
160
161                CPPUNIT_ASSERT(in->TellI() == in->GetLength());
162            }
163
164            // read back the 'X's at [ 0xffffffbf, 0xffffffff [
165            pos -= size;
166            CPPUNIT_ASSERT(in->SeekI(pos) == pos);
167            CPPUNIT_ASSERT(in->Read(buf, size).LastRead() == size);
168            CPPUNIT_ASSERT(memcmp(buf, upto4Gig, size) == 0);
169            pos += size;
170            CPPUNIT_ASSERT(in->TellI() == pos);
171
172            // read back the 'B's at [ 0x7fffffff, 0x8000003f [
173            pos = 0x7fffffff;
174            CPPUNIT_ASSERT(in->SeekI(pos) == pos);
175            CPPUNIT_ASSERT(in->Read(buf, size).LastRead() == size);
176            CPPUNIT_ASSERT(memcmp(buf, past2Gig, size) == 0);
177        }
178        else {
179            CPPUNIT_ASSERT(in->GetLength() == 0x7fffffff);
180            pos = 0x7fffffff;
181        }
182
183        // read back the 'A's at [ 0x7fffffbf, 0x7fffffff [
184        pos -= size;
185        CPPUNIT_ASSERT(in->SeekI(pos) == pos);
186        CPPUNIT_ASSERT(in->Read(buf, size).LastRead() == size);
187        CPPUNIT_ASSERT(memcmp(buf, upto2Gig, size) == 0);
188        pos += size;
189        CPPUNIT_ASSERT(in->TellI() == pos);
190    }
191}
192
193
194///////////////////////////////////////////////////////////////////////////////
195// wxFile test case
196
197class LargeFileTest_wxFile : public LargeFileTest
198{
199public:
200    LargeFileTest_wxFile() : LargeFileTest("wxFile streams") { }
201
202protected:
203    wxInputStream *MakeInStream(const wxString& name) const;
204    wxOutputStream *MakeOutStream(const wxString& name) const;
205    bool HasLFS() const { return (wxFileOffset)0xffffffff > 0; }
206};
207
208wxInputStream *LargeFileTest_wxFile::MakeInStream(const wxString& name) const
209{
210    auto_ptr<wxFileInputStream> in(new wxFileInputStream(name));
211    CPPUNIT_ASSERT(in->Ok());
212    return in.release();
213}
214
215wxOutputStream *LargeFileTest_wxFile::MakeOutStream(const wxString& name) const
216{
217    wxFile file(name, wxFile::write);
218    CPPUNIT_ASSERT(file.IsOpened());
219    int fd = file.fd();
220    file.Detach();
221    MakeSparse(name, fd);
222    return new wxFileOutputStream(fd);
223}
224
225
226///////////////////////////////////////////////////////////////////////////////
227// wxFFile test case
228
229class LargeFileTest_wxFFile : public LargeFileTest
230{
231public:
232    LargeFileTest_wxFFile() : LargeFileTest("wxFFile streams") { }
233
234protected:
235    wxInputStream *MakeInStream(const wxString& name) const;
236    wxOutputStream *MakeOutStream(const wxString& name) const;
237    bool HasLFS() const;
238};
239
240wxInputStream *LargeFileTest_wxFFile::MakeInStream(const wxString& name) const
241{
242    auto_ptr<wxFFileInputStream> in(new wxFFileInputStream(name));
243    CPPUNIT_ASSERT(in->Ok());
244    return in.release();
245}
246
247wxOutputStream *LargeFileTest_wxFFile::MakeOutStream(const wxString& name) const
248{
249    wxFFile file(name, _T("w"));
250    CPPUNIT_ASSERT(file.IsOpened());
251    FILE *fp = file.fp();
252    file.Detach();
253    MakeSparse(name, fileno(fp));
254    return new wxFFileOutputStream(fp);
255}
256
257bool LargeFileTest_wxFFile::HasLFS() const
258{
259#ifdef wxHAS_LARGE_FFILES
260    return true;
261#else
262    return false;
263#endif
264}
265
266
267///////////////////////////////////////////////////////////////////////////////
268// The suite
269
270class largeFile : public CppUnit::TestSuite
271{
272public:
273    largeFile() : CppUnit::TestSuite("largeFile") { }
274
275    static CppUnit::Test *suite();
276};
277
278CppUnit::Test *largeFile::suite()
279{
280    largeFile *suite = new largeFile;
281
282    suite->addTest(new LargeFileTest_wxFile);
283    suite->addTest(new LargeFileTest_wxFFile);
284
285    return suite;
286}
287
288
289///////////////////////////////////////////////////////////////////////////////
290// Implement the helpers
291//
292// Ideally these tests will be part of the default suite so that regressions
293// are picked up. However this is only possible when sparse files are
294// supported otherwise the tests require too much disk space.
295
296#ifdef __WXMSW__
297
298#ifndef FILE_SUPPORTS_SPARSE_FILES
299#define FILE_SUPPORTS_SPARSE_FILES 0x00000040
300#endif
301
302#ifndef FSCTL_SET_SPARSE
303
304#   ifndef FILE_SPECIAL_ACCESS
305#       define FILE_SPECIAL_ACCESS FILE_ANY_ACCESS
306#   endif
307#   define FSCTL_SET_SPARSE CTL_CODE(FILE_DEVICE_FILE_SYSTEM, 49, \
308                                     METHOD_BUFFERED, FILE_SPECIAL_ACCESS)
309#endif
310
311static DWORD  volumeFlags;
312static wxChar volumeType[64];
313static bool   volumeInfoInit;
314
315void GetVolumeInfo(const wxString& path)
316{
317    // extract the volume 'C:\' or '\\tooter\share\' from the path
318    wxString vol;
319
320    if (path.substr(1, 2) == _T(":\\")) {
321        vol = path.substr(0, 3);
322    } else {
323        if (path.substr(0, 2) == _T("\\\\")) {
324            size_t i = path.find(_T('\\'), 2);
325
326            if (i != wxString::npos && i > 2) {
327                size_t j = path.find(_T('\\'), ++i);
328
329                if (j != i)
330                    vol = path.substr(0, j) + _T("\\");
331            }
332        }
333    }
334
335    // NULL means the current volume
336    const wxChar *pVol = vol.empty() ? NULL : vol.c_str();
337
338    if (!::GetVolumeInformation(pVol, NULL, 0, NULL, NULL,
339                                &volumeFlags,
340                                volumeType,
341                                WXSIZEOF(volumeType)))
342        wxLogSysError(_T("GetVolumeInformation() failed"));
343
344    volumeInfoInit = true;
345}
346
347bool IsFAT(const wxString& path)
348{
349    if (!volumeInfoInit)
350        GetVolumeInfo(path);
351    return wxString(volumeType).Upper().find(_T("FAT")) != wxString::npos;
352}
353
354void MakeSparse(const wxString& path, int fd)
355{
356    DWORD cb;
357
358    if (!volumeInfoInit)
359        GetVolumeInfo(path);
360
361    if ((volumeFlags & FILE_SUPPORTS_SPARSE_FILES) != 0)
362        if (!::DeviceIoControl((HANDLE)_get_osfhandle(fd),
363                               FSCTL_SET_SPARSE,
364                               NULL, 0, NULL, 0, &cb, NULL))
365            volumeFlags &= ~FILE_SUPPORTS_SPARSE_FILES;
366}
367
368// return the suite if sparse files are supported, otherwise return NULL
369//
370CppUnit::Test* GetlargeFileSuite()
371{
372    if (!volumeInfoInit) {
373        wxString path;
374        {
375            wxFile file;
376            path = wxFileName::CreateTempFileName(wxT("wxlfs-"), &file);
377            MakeSparse(path, file.fd());
378        }
379        wxRemoveFile(path);
380    }
381
382    if ((volumeFlags & FILE_SUPPORTS_SPARSE_FILES) != 0)
383        return largeFile::suite();
384    else
385        return NULL;
386}
387
388#else // __WXMSW__
389
390bool IsFAT(const wxString& WXUNUSED(path)) { return false; }
391void MakeSparse(const wxString& WXUNUSED(path), int WXUNUSED(fd)) { }
392
393// return the suite if sparse files are supported, otherwise return NULL
394//
395CppUnit::Test* GetlargeFileSuite()
396{
397    wxString path;
398    struct stat st1, st2;
399    memset(&st1, 0, sizeof(st1));
400    memset(&st2, 0, sizeof(st2));
401
402    {
403        wxFile file;
404        path = wxFileName::CreateTempFileName(wxT("wxlfs-"), &file);
405
406        fstat(file.fd(), &st1);
407        file.Seek(st1.st_blksize);
408        file.Write("x", 1);
409        fstat(file.fd(), &st1);
410
411        file.Seek(0);
412        file.Write("x", 1);
413        fstat(file.fd(), &st2);
414    }
415
416    wxRemoveFile(path);
417
418    if (st1.st_blocks != st2.st_blocks)
419        return largeFile::suite();
420    else
421        return NULL;
422}
423
424#endif // __WXMSW__
425
426CPPUNIT_TEST_SUITE_NAMED_REGISTRATION(largeFile, "largeFile");
427CPPUNIT_TEST_SUITE_NAMED_REGISTRATION(largeFile, "Streams.largeFile");
428