tst_QFile: update virtualFile() to check if readLine() reads all

We know that all lines from /proc/<PID>/maps end in a newline, so we
trim exactly that one byte, then we put it all back together with
.join('\n') to check if we've read the entire file.

Linux virtual files are usually served in 4 kB increments; tst_qfile's
maps file is about 16000 bytes for me, just short of the QIODevice buffer:

[pid 414315] read(5, "55c6afe04000-55c6afe11000 r--p 0"..., 16384) = 4049
[pid 414315] read(5, "7f215fd25000-7f215fd26000 r--p 0"..., 12335) = 4038
[pid 414315] read(5, "7f2160800000-7f21608c7000 r--p 0"..., 8297) = 4072
[pid 414315] read(5, "7f216119f000-7f21611a0000 rw-p 0"..., 4225) = 3994

It is not a coincidence that the reads are at line boundaries, though
it's not a guarantee from the kernel.

We appear to have accidentally fixed the QEMU emulation bug by reading
another process' /proc/<PID>/maps (hypothesis: QMU emulates the target
system in /proc/self, hiding itself in maps, but makes no translation
for other process map files.

Change-Id: Ifbf974a4d10745b099b1fffd1777ac919b12dc90
Reviewed-by: Qt CI Bot <qt_ci_bot@qt-project.org>
Reviewed-by: Fabian Kosmale <fabian.kosmale@qt.io>
This commit is contained in:
Thiago Macieira 2023-08-02 13:58:46 -07:00
parent 9d4579c1cd
commit 7ca82250e0

View File

@ -52,7 +52,9 @@ QT_END_NAMESPACE
#ifdef Q_OS_DARWIN
# include <sys/mount.h>
#elif defined(Q_OS_LINUX)
# include <sys/eventfd.h>
# include <sys/vfs.h>
# include <sys/wait.h>
#elif defined(Q_OS_FREEBSD)
# include <sys/param.h>
# include <sys/mount.h>
@ -2613,8 +2615,29 @@ void tst_QFile::writeToReadOnlyFile()
// This platform have 0-sized virtual files
void tst_QFile::virtualFile()
{
// test if QFile works with virtual files
QString fname = "/proc/self/maps";
// We need to test a large-ish /proc file on Linux, one that is usually
// over 4 kB (because the kernel writes in chunks of that), has a
// cross-platform file format, and is definitely readable. The best
// candidate and the one we can verify anything in is /proc/<PID>/maps.
// However, our act of reading may change the map because we allocate
// memory, so we fork() here so we have a frozen snapshot of the file.
int efd = eventfd(0, EFD_CLOEXEC);
pid_t pid = fork();
if (pid == 0) {
// child
uint64_t val;
eventfd_read(efd, &val);
_exit(0);
}
QVERIFY2(pid > 0, "fork failed: " + qt_error_string().toLocal8Bit());
auto waitForChild = qScopeGuard([=] {
eventfd_write(efd, 1);
close(efd);
waitpid(pid, nullptr, 0);
});
QString fname = u"/proc/%1/maps"_s.arg(pid);
// consistency check
QFileInfo fi(fname);
@ -2625,11 +2648,7 @@ void tst_QFile::virtualFile()
// open the file
QFile f(fname);
QVERIFY2(f.open(QIODevice::ReadOnly), msgOpenFailed(f).constData());
if (QTestPrivate::isRunningArmOnX86())
QEXPECT_FAIL("","QEMU does not read /proc/self/maps size correctly", Continue);
QCOMPARE(f.size(), Q_INT64_C(0));
if (QTestPrivate::isRunningArmOnX86())
QEXPECT_FAIL("","QEMU does not read /proc/self/maps size correctly", Continue);
QVERIFY(f.atEnd());
// read data
@ -2637,18 +2656,27 @@ void tst_QFile::virtualFile()
QCOMPARE(data.size(), 16);
QCOMPARE(f.pos(), Q_INT64_C(16));
// seeking
QVERIFY(f.seek(1));
QCOMPARE(f.pos(), Q_INT64_C(1));
QVERIFY(f.seek(0));
QCOMPARE(f.pos(), Q_INT64_C(0));
// line-reading
data = f.readLine();
QVERIFY(!data.isEmpty());
QList<QByteArray> lines;
for (data = f.readLine(); !data.isEmpty(); data = f.readLine()) {
// chop the newline -- not using .trimmed() so cut exactly one byte
data.chop(1);
lines += std::move(data);
}
// read all:
QVERIFY(f.seek(0));
data = f.readAll();
QVERIFY(f.pos() != 0);
QVERIFY(!data.isEmpty());
// seeking
QVERIFY(f.seek(1));
QCOMPARE(f.pos(), Q_INT64_C(1));
QCOMPARE(data, lines.join('\n') + '\n');
}
#endif // defined(Q_OS_LINUX) || defined(Q_OS_AIX) || defined(Q_OS_FREEBSD) || defined(Q_OS_NETBSD)