Make wxFile::ReadAll() work for unseekable files too

Calling this function with an unseekable file, such as any file under
/sys on Linux systems, would previously just hang as the loop condition
was never satisfied when length was -1.

Fix this by checking for this case and using a different approach by
extending the buffer we read the data into as we go instead of
preallocating it all at once.

See #9965.
This commit is contained in:
Vadim Zeitlin
2017-12-15 18:34:43 +01:00
parent ebcba7a385
commit a30bee473e
4 changed files with 86 additions and 12 deletions

View File

@@ -106,6 +106,7 @@ All:
- Add UTF-8 support to wxZipOutputStream (Tobias Taschner).
- Update all bundled 3rd party libraries to their latest versions.
- Use unique prefix for all zlib symbols to avoid link conflicts.
- Make wxFile::ReadAll() work for unseekable files too.
All (GUI):

View File

@@ -377,6 +377,8 @@ public:
/**
Reads the entire contents of the file into a string.
Since wxWidgets 3.1.1 this method also works for unseekable files.
@param str
Non-@NULL pointer to a string to read data into.
@param conv

View File

@@ -261,24 +261,71 @@ bool wxFile::ReadAll(wxString *str, const wxMBConv& conv)
{
wxCHECK_MSG( str, false, wxS("Output string must be non-NULL") );
static const ssize_t READSIZE = 4096;
wxCharBuffer buf;
ssize_t length = Length();
wxCHECK_MSG( (wxFileOffset)length == Length(), false, wxT("huge file not supported") );
wxCharBuffer buf(length);
char* p = buf.data();
for ( ;; )
if ( length != -1 )
{
static const ssize_t READSIZE = 4096;
wxCHECK_MSG( (wxFileOffset)length == Length(), false, wxT("huge file not supported") );
ssize_t nread = Read(p, length > READSIZE ? READSIZE : length);
if ( nread == wxInvalidOffset )
if ( !buf.extend(length) )
return false;
p += nread;
if ( length <= nread )
break;
char* p = buf.data();
for ( ;; )
{
ssize_t nread = Read(p, length > READSIZE ? READSIZE : length);
if ( nread == wxInvalidOffset )
return false;
length -= nread;
if ( nread == 0 )
{
// We have reached EOF before reading the entire length of the
// file. This can happen for some special files (e.g. those
// under /sys on Linux systems) or even for regular files if
// another process has truncated the file since we started
// reading it, so deal with it gracefully.
buf.shrink(p - buf.data());
break;
}
p += nread;
length -= nread;
if ( !length )
{
// Notice that we don't keep reading after getting the expected
// number of bytes, even though in principle a situation
// similar to the one described above, with another process
// extending the file since we started to read it, is possible.
// But returning just the data that was in the file when we
// originally started reading it isn't really wrong in this
// case, so keep things simple and just do it like this.
break;
}
}
}
else // File is not seekable
{
for ( ;; )
{
const size_t len = buf.length();
if ( !buf.extend(len + READSIZE) )
return false;
ssize_t nread = Read(buf.data() + len, READSIZE);
if ( nread == wxInvalidOffset )
return false;
if ( nread < READSIZE )
{
// We have reached EOF.
buf.shrink(len + nread);
break;
}
}
}
str->assign(buf, conv);

View File

@@ -139,4 +139,28 @@ void FileTestCase::TempFile()
CPPUNIT_ASSERT( wxRemoveFile(wxT("test2")) );
}
#ifdef __LINUX__
// Check that GetSize() works correctly for special files.
TEST_CASE("wxFile::Special", "[file][linux][special-file]")
{
// This file is not seekable and has 0 size, but can still be read.
wxFile fileProc("/proc/diskstats");
CHECK( fileProc.IsOpened() );
wxString s;
CHECK( fileProc.ReadAll(&s) );
CHECK( !s.empty() );
// All files in /sys seem to have size of 4KiB currently, even if they
// don't have that much data in them.
wxFile fileSys("/sys/power/state");
CHECK( fileSys.IsOpened() );
CHECK( fileSys.ReadAll(&s) );
CHECK( !s.empty() );
CHECK( s.length() < 4096 );
}
#endif // __LINUX__
#endif // wxUSE_FILE