#include "ollie_kio.h" #include #include #include #include #include #include #include using namespace KIO; // Plugin factory — KIO loads this .so and calls the appropriate create method #if QT_VERSION_MAJOR >= 6 class OllieWorkerFactory : public KIO::WorkerFactory { Q_OBJECT Q_PLUGIN_METADATA(IID "org.kde.kio.worker.ollie" FILE "ollie.json") public: using WorkerFactory::WorkerFactory; std::unique_ptr createWorker(const QByteArray &pool, const QByteArray &app) override { return std::make_unique(pool, app); } }; #else class OllieWorkerFactory : public KIO::RealWorkerFactory { Q_OBJECT Q_PLUGIN_METADATA(IID "org.kde.kio.worker.ollie" FILE "ollie.json") public: using RealWorkerFactory::RealWorkerFactory; std::unique_ptr createWorker(const QByteArray &pool, const QByteArray &app) override { Q_UNUSED(pool); Q_UNUSED(app); return nullptr; // unused — KIO calls createRealWorker for RealWorkerFactory } std::unique_ptr createRealWorker(const QByteArray &pool, const QByteArray &app) override { return std::make_unique(pool, app); } }; #endif // kdemain entry point — KF5 KIO launches workers by calling this extern "C" Q_DECL_EXPORT int kdemain(int argc, char **argv) { QCoreApplication app(argc, argv); app.setApplicationName(QStringLiteral("kio_ollie")); if (argc != 4) { fprintf(stderr, "Usage: kio_ollie protocol pool_socket app_socket\n"); return -1; } OllieWorker worker(argv[2], argv[3]); worker.dispatchLoop(); return 0; } OllieWorker::OllieWorker(const QByteArray &pool, const QByteArray &app) : WorkerBase("ollie", pool, app) { } OllieWorker::~OllieWorker() { m_client.disconnect(); } bool OllieWorker::ensureConnected() { if (m_client.isConnected()) return true; return m_client.connectDefault(); } QString OllieWorker::ninepPath(const QUrl &url) const { QString path = url.path(); // Remove leading slash — 9P root is "" if (path.startsWith(QLatin1Char('/'))) path = path.mid(1); // Remove trailing slash if (path.endsWith(QLatin1Char('/')) && path.length() > 1) path.chop(1); return path; } bool OllieWorker::isDirectory(const QString &mode) const { // 9P mode: starts with 'd' for directory, // or numeric with Plan 9 DMDIR bit (0x80000000) if (mode.startsWith(QLatin1Char('d'))) return true; bool ok; uint modeVal = mode.toUInt(&ok, 0); if (ok && (modeVal & 0x80000000)) return true; return false; } mode_t OllieWorker::parseMode(const QString &modeStr) const { // Try numeric first bool ok; uint modeVal = modeStr.toUInt(&ok, 0); if (ok) { return static_cast(modeVal & 0777); } // Parse rwx string (e.g. "drwxr-xr-x") QString perms = modeStr; if (perms.startsWith(QLatin1Char('d'))) perms = perms.mid(1); mode_t m = 0; if (perms.length() >= 9) { if (perms[0] == QLatin1Char('r')) m |= 0400; if (perms[1] == QLatin1Char('w')) m |= 0200; if (perms[2] == QLatin1Char('x')) m |= 0100; if (perms[3] == QLatin1Char('r')) m |= 0040; if (perms[4] == QLatin1Char('w')) m |= 0020; if (perms[5] == QLatin1Char('x')) m |= 0010; if (perms[6] == QLatin1Char('r')) m |= 0004; if (perms[7] == QLatin1Char('w')) m |= 0002; if (perms[8] == QLatin1Char('x')) m |= 0001; } else { m = 0644; } return m; } KIO::UDSEntry OllieWorker::entryFromStat(const QString &path, const QString &name) { UDSEntry entry; auto st = m_client.stat(path); if (st.valid) { bool dir = isDirectory(st.mode); entry.fastInsert(UDSEntry::UDS_NAME, name.isEmpty() ? st.name : name); entry.fastInsert(UDSEntry::UDS_SIZE, dir ? 0 : st.size); entry.fastInsert(UDSEntry::UDS_MODIFICATION_TIME, st.mtime); entry.fastInsert(UDSEntry::UDS_ACCESS_TIME, st.mtime); mode_t perm = parseMode(st.mode); if (dir) { entry.fastInsert(UDSEntry::UDS_FILE_TYPE, S_IFDIR); entry.fastInsert(UDSEntry::UDS_ACCESS, perm | 0111); } else { entry.fastInsert(UDSEntry::UDS_FILE_TYPE, S_IFREG); entry.fastInsert(UDSEntry::UDS_ACCESS, perm); } if (!dir) { QMimeDatabase db; QMimeType mime = db.mimeTypeForFile(name.isEmpty() ? st.name : name, QMimeDatabase::MatchExtension); if (mime.isValid() && !mime.isDefault()) { entry.fastInsert(UDSEntry::UDS_MIME_TYPE, mime.name()); } else { entry.fastInsert(UDSEntry::UDS_MIME_TYPE, QStringLiteral("text/plain")); } } } else { entry.fastInsert(UDSEntry::UDS_NAME, name); } return entry; } WorkerResult OllieWorker::listDir(const QUrl &url) { if (!ensureConnected()) { return WorkerResult::fail(ERR_CANNOT_CONNECT, QStringLiteral("Cannot connect to Ollie 9P server: ") + m_client.lastError()); } QString path = ninepPath(url); QStringList entries = m_client.ls(path); if (entries.isEmpty() && !m_client.lastError().isEmpty()) { return WorkerResult::fail(ERR_CANNOT_ENTER_DIRECTORY, m_client.lastError()); } // Emit "." entry for the directory itself UDSEntry dot; dot.fastInsert(UDSEntry::UDS_NAME, QStringLiteral(".")); dot.fastInsert(UDSEntry::UDS_FILE_TYPE, S_IFDIR); dot.fastInsert(UDSEntry::UDS_ACCESS, 0755); listEntry(dot); for (const QString &name : entries) { QString childPath = path.isEmpty() ? name : (path + QLatin1Char('/') + name); UDSEntry entry = entryFromStat(childPath, name); listEntry(entry); } return WorkerResult::pass(); } WorkerResult OllieWorker::stat(const QUrl &url) { if (!ensureConnected()) { return WorkerResult::fail(ERR_CANNOT_CONNECT, QStringLiteral("Cannot connect to Ollie 9P server: ") + m_client.lastError()); } QString path = ninepPath(url); QString name = url.fileName(); if (name.isEmpty()) name = QStringLiteral("."); UDSEntry entry = entryFromStat(path, name); if (!entry.contains(UDSEntry::UDS_FILE_TYPE)) { if (path.isEmpty()) { // Root directory entry.fastInsert(UDSEntry::UDS_NAME, QStringLiteral(".")); entry.fastInsert(UDSEntry::UDS_FILE_TYPE, S_IFDIR); entry.fastInsert(UDSEntry::UDS_ACCESS, 0755); entry.fastInsert(UDSEntry::UDS_MIME_TYPE, QStringLiteral("inode/directory")); } else { return WorkerResult::fail(ERR_DOES_NOT_EXIST, m_client.lastError()); } } statEntry(entry); return WorkerResult::pass(); } WorkerResult OllieWorker::get(const QUrl &url) { if (!ensureConnected()) { return WorkerResult::fail(ERR_CANNOT_CONNECT, QStringLiteral("Cannot connect to Ollie 9P server: ") + m_client.lastError()); } QString path = ninepPath(url); QByteArray fileData = m_client.read(path); if (fileData.isNull() && !m_client.lastError().isEmpty()) { return WorkerResult::fail(ERR_CANNOT_READ, m_client.lastError()); } QMimeDatabase db; QMimeType mime = db.mimeTypeForFileNameAndData(url.fileName(), fileData); mimeType(mime.name()); totalSize(fileData.size()); data(fileData); return WorkerResult::pass(); } WorkerResult OllieWorker::put(const QUrl &url, int /*permissions*/, KIO::JobFlags /*flags*/) { if (!ensureConnected()) { return WorkerResult::fail(ERR_CANNOT_CONNECT, QStringLiteral("Cannot connect to Ollie 9P server: ") + m_client.lastError()); } // Read incoming data from the job QByteArray allData; int result; do { QByteArray buffer; dataReq(); result = readData(buffer); if (result > 0) allData.append(buffer); } while (result > 0); QString path = ninepPath(url); if (!m_client.write(path, allData)) { return WorkerResult::fail(ERR_CANNOT_WRITE, m_client.lastError()); } return WorkerResult::pass(); } WorkerResult OllieWorker::mkdir(const QUrl &url, int /*permissions*/) { if (!ensureConnected()) { return WorkerResult::fail(ERR_CANNOT_CONNECT, QStringLiteral("Cannot connect to Ollie 9P server: ") + m_client.lastError()); } QString path = ninepPath(url); if (!m_client.mkdir(path)) { return WorkerResult::fail(ERR_CANNOT_MKDIR, m_client.lastError()); } return WorkerResult::pass(); } WorkerResult OllieWorker::del(const QUrl &url, bool /*isFile*/) { if (!ensureConnected()) { return WorkerResult::fail(ERR_CANNOT_CONNECT, QStringLiteral("Cannot connect to Ollie 9P server: ") + m_client.lastError()); } QString path = ninepPath(url); if (!m_client.remove(path)) { return WorkerResult::fail(ERR_CANNOT_DELETE, m_client.lastError()); } return WorkerResult::pass(); } WorkerResult OllieWorker::rename(const QUrl &src, const QUrl &dest, KIO::JobFlags /*flags*/) { if (!ensureConnected()) { return WorkerResult::fail(ERR_CANNOT_CONNECT, QStringLiteral("Cannot connect to Ollie 9P server: ") + m_client.lastError()); } QString srcPath = ninepPath(src); QString newName = dest.fileName(); if (!m_client.rename(srcPath, newName)) { return WorkerResult::fail(ERR_CANNOT_RENAME, m_client.lastError()); } return WorkerResult::pass(); } #include "ollie_kio.moc"