utils: Introduce RamFile class for memfd
This class can be used to create an anonymous file, for instance to pass data between compositor and clients, through means of a file descriptor, as is done in various Wayland protocols, notably the keymap exchange. It also implements sealing the file, so that it can be shared between multiple clients without them being able to modify it. If supported, memfd_create is used, otherwise a `QTemporaryFile` is used. Signed-off-by: Victoria Fischer <victoria.fischer@mbition.io>master
parent
e5aeb674c0
commit
3646620430
@ -0,0 +1,71 @@
|
|||||||
|
/*
|
||||||
|
KWin - the KDE window manager
|
||||||
|
This file is part of the KDE project.
|
||||||
|
|
||||||
|
SPDX-FileCopyrightText: 2022 MBition GmbH
|
||||||
|
SPDX-FileContributor: Kai Uwe Broulik <kai_uwe.broulik@mbition.io>
|
||||||
|
|
||||||
|
SPDX-License-Identifier: GPL-2.0-or-later
|
||||||
|
*/
|
||||||
|
|
||||||
|
#include <config-kwin.h>
|
||||||
|
|
||||||
|
#include <sys/mman.h>
|
||||||
|
#include <unistd.h>
|
||||||
|
|
||||||
|
#include "utils/ramfile.h"
|
||||||
|
|
||||||
|
#include <QtTest>
|
||||||
|
|
||||||
|
using namespace KWin;
|
||||||
|
|
||||||
|
class TestUtils : public QObject
|
||||||
|
{
|
||||||
|
Q_OBJECT
|
||||||
|
private Q_SLOTS:
|
||||||
|
void testRamFile();
|
||||||
|
void testSealedRamFile();
|
||||||
|
};
|
||||||
|
|
||||||
|
static const QByteArray s_testByteArray = QByteArrayLiteral("Test Data \0\1\2\3");
|
||||||
|
static const char s_writeTestArray[] = "test";
|
||||||
|
|
||||||
|
void TestUtils::testRamFile()
|
||||||
|
{
|
||||||
|
KWin::RamFile file("test", s_testByteArray.constData(), s_testByteArray.size());
|
||||||
|
QVERIFY(file.isValid());
|
||||||
|
QCOMPARE(file.size(), s_testByteArray.size());
|
||||||
|
|
||||||
|
QVERIFY(file.fd() != -1);
|
||||||
|
|
||||||
|
char buf[20];
|
||||||
|
int num = read(file.fd(), buf, sizeof buf);
|
||||||
|
QCOMPARE(num, file.size());
|
||||||
|
|
||||||
|
QCOMPARE(qstrcmp(s_testByteArray.constData(), buf), 0);
|
||||||
|
}
|
||||||
|
|
||||||
|
void TestUtils::testSealedRamFile()
|
||||||
|
{
|
||||||
|
#if HAVE_MEMFD
|
||||||
|
KWin::RamFile file("test", s_testByteArray.constData(), s_testByteArray.size(), KWin::RamFile::Flag::SealWrite);
|
||||||
|
QVERIFY(file.isValid());
|
||||||
|
QVERIFY(file.effectiveFlags().testFlag(KWin::RamFile::Flag::SealWrite));
|
||||||
|
|
||||||
|
// Writing should not work.
|
||||||
|
auto written = write(file.fd(), s_writeTestArray, strlen(s_writeTestArray));
|
||||||
|
QCOMPARE(written, -1);
|
||||||
|
|
||||||
|
// Cannot use MAP_SHARED on sealed file descriptor.
|
||||||
|
void *data = mmap(nullptr, file.size(), PROT_READ, MAP_SHARED, file.fd(), 0);
|
||||||
|
QCOMPARE(data, MAP_FAILED);
|
||||||
|
|
||||||
|
data = mmap(nullptr, file.size(), PROT_READ, MAP_PRIVATE, file.fd(), 0);
|
||||||
|
QVERIFY(data != MAP_FAILED);
|
||||||
|
#else
|
||||||
|
QSKIP("Sealing requires memfd suport.");
|
||||||
|
#endif
|
||||||
|
}
|
||||||
|
|
||||||
|
QTEST_MAIN(TestUtils)
|
||||||
|
#include "test_utils.moc"
|
@ -0,0 +1,163 @@
|
|||||||
|
/*
|
||||||
|
KWin - the KDE window manager
|
||||||
|
This file is part of the KDE project.
|
||||||
|
|
||||||
|
SPDX-FileCopyrightText: 2022 MBition GmbH
|
||||||
|
SPDX-FileContributor: Kai Uwe Broulik <kai_uwe.broulik@mbition.io>
|
||||||
|
|
||||||
|
SPDX-License-Identifier: GPL-2.0-or-later
|
||||||
|
*/
|
||||||
|
|
||||||
|
#include "ramfile.h"
|
||||||
|
#include "common.h" // for logging
|
||||||
|
|
||||||
|
#include <QScopeGuard>
|
||||||
|
|
||||||
|
#include <fcntl.h>
|
||||||
|
#include <sys/mman.h>
|
||||||
|
#include <unistd.h>
|
||||||
|
#include <utility>
|
||||||
|
|
||||||
|
namespace KWin
|
||||||
|
{
|
||||||
|
|
||||||
|
RamFile::RamFile(const char *name, const void *inData, int size, RamFile::Flags flags)
|
||||||
|
: m_size(size)
|
||||||
|
, m_flags(flags)
|
||||||
|
{
|
||||||
|
auto guard = qScopeGuard([this] {
|
||||||
|
cleanup();
|
||||||
|
});
|
||||||
|
|
||||||
|
#if HAVE_MEMFD
|
||||||
|
m_fd = FileDescriptor(memfd_create(name, MFD_CLOEXEC | MFD_ALLOW_SEALING));
|
||||||
|
if (!m_fd.isValid()) {
|
||||||
|
qCWarning(KWIN_CORE).nospace() << name << ": Can't create memfd: " << strerror(errno);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (ftruncate(m_fd.get(), size) < 0) {
|
||||||
|
qCWarning(KWIN_CORE).nospace() << name << ": Failed to ftruncate memfd: " << strerror(errno);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
void *data = mmap(nullptr, size, PROT_READ | PROT_WRITE, MAP_SHARED, m_fd.get(), 0);
|
||||||
|
if (data == MAP_FAILED) {
|
||||||
|
qCWarning(KWIN_CORE).nospace() << name << ": mmap failed: " << strerror(errno);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
#else
|
||||||
|
m_tmp = std::make_unique<QTemporaryFile>();
|
||||||
|
if (!m_tmp->open()) {
|
||||||
|
qCWarning(KWIN_CORE).nospace() << name << ": Can't open temporary file";
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (unlink(m_tmp->fileName().toUtf8().constData()) != 0) {
|
||||||
|
qCWarning(KWIN_CORE).nospace() << name << ": Failed to remove temporary file from filesystem: " << strerror(errno);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!m_tmp->resize(size)) {
|
||||||
|
qCWarning(KWIN_CORE).nospace() << name << ": Failed to resize temporary file";
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
uchar *data = m_tmp->map(0, size);
|
||||||
|
if (!data) {
|
||||||
|
qCWarning(KWIN_CORE).nospace() << name << ": map failed";
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
#endif
|
||||||
|
|
||||||
|
memcpy(data, inData, size);
|
||||||
|
|
||||||
|
#if HAVE_MEMFD
|
||||||
|
munmap(data, size);
|
||||||
|
#else
|
||||||
|
m_tmp->unmap(data);
|
||||||
|
#endif
|
||||||
|
|
||||||
|
int seals = F_SEAL_SHRINK | F_SEAL_GROW | F_SEAL_SEAL;
|
||||||
|
if (flags.testFlag(RamFile::Flag::SealWrite)) {
|
||||||
|
seals |= F_SEAL_WRITE;
|
||||||
|
}
|
||||||
|
// This can fail for QTemporaryFile based on the underlying file system.
|
||||||
|
if (fcntl(fd(), F_ADD_SEALS, seals) != 0) {
|
||||||
|
qCDebug(KWIN_CORE).nospace() << name << ": Failed to seal RamFile: " << strerror(errno);
|
||||||
|
}
|
||||||
|
|
||||||
|
guard.dismiss();
|
||||||
|
}
|
||||||
|
|
||||||
|
RamFile::RamFile(RamFile &&other) Q_DECL_NOEXCEPT
|
||||||
|
: m_size(std::exchange(other.m_size, 0))
|
||||||
|
, m_flags(std::exchange(other.m_flags, RamFile::Flags{}))
|
||||||
|
#if HAVE_MEMFD
|
||||||
|
, m_fd(std::exchange(other.m_fd, KWin::FileDescriptor{}))
|
||||||
|
#else
|
||||||
|
, m_tmp(std::exchange(other.m_tmp, {}))
|
||||||
|
#endif
|
||||||
|
{
|
||||||
|
}
|
||||||
|
|
||||||
|
RamFile &RamFile::operator=(RamFile &&other) Q_DECL_NOEXCEPT
|
||||||
|
{
|
||||||
|
cleanup();
|
||||||
|
m_size = std::exchange(other.m_size, 0);
|
||||||
|
m_flags = std::exchange(other.m_flags, RamFile::Flags{});
|
||||||
|
#if HAVE_MEMFD
|
||||||
|
m_fd = std::exchange(other.m_fd, KWin::FileDescriptor{});
|
||||||
|
#else
|
||||||
|
m_tmp = std::exchange(other.m_tmp, {});
|
||||||
|
#endif
|
||||||
|
return *this;
|
||||||
|
}
|
||||||
|
|
||||||
|
RamFile::~RamFile()
|
||||||
|
{
|
||||||
|
cleanup();
|
||||||
|
}
|
||||||
|
|
||||||
|
void RamFile::cleanup()
|
||||||
|
{
|
||||||
|
#if HAVE_MEMFD
|
||||||
|
m_fd = KWin::FileDescriptor();
|
||||||
|
#else
|
||||||
|
m_tmp.reset();
|
||||||
|
#endif
|
||||||
|
}
|
||||||
|
|
||||||
|
bool RamFile::isValid() const
|
||||||
|
{
|
||||||
|
return fd() != -1;
|
||||||
|
}
|
||||||
|
|
||||||
|
RamFile::Flags RamFile::effectiveFlags() const
|
||||||
|
{
|
||||||
|
Flags flags = {};
|
||||||
|
|
||||||
|
const int seals = fcntl(fd(), F_GET_SEALS);
|
||||||
|
if (seals > 0) {
|
||||||
|
if (seals & F_SEAL_WRITE) {
|
||||||
|
flags.setFlag(Flag::SealWrite);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return flags;
|
||||||
|
}
|
||||||
|
|
||||||
|
int RamFile::fd() const
|
||||||
|
{
|
||||||
|
#if HAVE_MEMFD
|
||||||
|
return m_fd.get();
|
||||||
|
#else
|
||||||
|
return m_tmp->handle();
|
||||||
|
#endif
|
||||||
|
}
|
||||||
|
|
||||||
|
int RamFile::size() const
|
||||||
|
{
|
||||||
|
return m_size;
|
||||||
|
}
|
||||||
|
|
||||||
|
} // namespace KWin
|
@ -0,0 +1,117 @@
|
|||||||
|
/*
|
||||||
|
KWin - the KDE window manager
|
||||||
|
This file is part of the KDE project.
|
||||||
|
|
||||||
|
SPDX-FileCopyrightText: 2022 MBition GmbH
|
||||||
|
SPDX-FileContributor: Kai Uwe Broulik <kai_uwe.broulik@mbition.io>
|
||||||
|
|
||||||
|
SPDX-License-Identifier: GPL-2.0-or-later
|
||||||
|
*/
|
||||||
|
|
||||||
|
#pragma once
|
||||||
|
|
||||||
|
#include <config-kwin.h>
|
||||||
|
#include <kwin_export.h>
|
||||||
|
|
||||||
|
#if HAVE_MEMFD
|
||||||
|
#include "filedescriptor.h"
|
||||||
|
#else
|
||||||
|
#include <QTemporaryFile>
|
||||||
|
#include <memory>
|
||||||
|
#endif
|
||||||
|
|
||||||
|
#include <QFlag>
|
||||||
|
|
||||||
|
class QByteArray;
|
||||||
|
|
||||||
|
namespace KWin
|
||||||
|
{
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @brief Creates a file in memory.
|
||||||
|
*
|
||||||
|
* This is useful for passing larger data to clients,
|
||||||
|
* for example the xkeymap.
|
||||||
|
*
|
||||||
|
* If memfd is supported, it is used, otherwise
|
||||||
|
* a temporary file is created.
|
||||||
|
*
|
||||||
|
* @note It is advisable not to send the same file
|
||||||
|
* descriptor out to multiple clients unless it
|
||||||
|
* is sealed for writing. Check which flags actually
|
||||||
|
* apply before handing out the file descriptor.
|
||||||
|
*
|
||||||
|
* @sa effectiveFlags()
|
||||||
|
*/
|
||||||
|
class KWIN_EXPORT RamFile
|
||||||
|
{
|
||||||
|
public:
|
||||||
|
/**
|
||||||
|
* Flags to use when creating the file.
|
||||||
|
*
|
||||||
|
* @note Check with effectiveFlags() which flags
|
||||||
|
* actually apply after the file was created.
|
||||||
|
*/
|
||||||
|
enum class Flag {
|
||||||
|
SealWrite = 1 << 0, ///< Seal the file descriptor for writing.
|
||||||
|
};
|
||||||
|
Q_DECLARE_FLAGS(Flags, Flag)
|
||||||
|
|
||||||
|
RamFile() = default;
|
||||||
|
/**
|
||||||
|
* Create a file of given size with given data.
|
||||||
|
*
|
||||||
|
* @note You should call seal() after copying the data into the file.
|
||||||
|
*
|
||||||
|
* @param name The file name, useful for debugging.
|
||||||
|
* @param data The data to store in the file.
|
||||||
|
* @param size The size of the file.
|
||||||
|
* @param flags The flags to use when creating the file.
|
||||||
|
*/
|
||||||
|
RamFile(const char *name, const void *inData, int size, Flags flags = {});
|
||||||
|
|
||||||
|
RamFile(RamFile &&other) Q_DECL_NOEXCEPT;
|
||||||
|
RamFile &operator=(RamFile &&other) Q_DECL_NOEXCEPT;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Destroys the file.
|
||||||
|
*/
|
||||||
|
~RamFile();
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Whether this instance contains a valid file descriptor.
|
||||||
|
*/
|
||||||
|
bool isValid() const;
|
||||||
|
/**
|
||||||
|
* The flags that are effectively applied.
|
||||||
|
*
|
||||||
|
* For instance, even though SealWrite was passed in the constructor,
|
||||||
|
* it might not be supported.
|
||||||
|
*/
|
||||||
|
Flags effectiveFlags() const;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* The underlying file descriptor
|
||||||
|
*
|
||||||
|
* @return The fd, or -1 if there is none.
|
||||||
|
*/
|
||||||
|
int fd() const;
|
||||||
|
/**
|
||||||
|
* The size of the file
|
||||||
|
*/
|
||||||
|
int size() const;
|
||||||
|
|
||||||
|
private:
|
||||||
|
void cleanup();
|
||||||
|
|
||||||
|
int m_size = 0;
|
||||||
|
Flags m_flags = {};
|
||||||
|
|
||||||
|
#if HAVE_MEMFD
|
||||||
|
KWin::FileDescriptor m_fd;
|
||||||
|
#else
|
||||||
|
std::unique_ptr<QTemporaryFile> m_tmp;
|
||||||
|
#endif
|
||||||
|
};
|
||||||
|
|
||||||
|
} // namespace KWin
|
Loading…
Reference in New Issue