diff --git a/SMSubProcess.cpp b/SMSubProcess.cpp new file mode 100644 index 0000000..40cea37 --- /dev/null +++ b/SMSubProcess.cpp @@ -0,0 +1,80 @@ +/***************************************************************************** +* smsub Server Manager Subprocess +* Copyright (C) 2020 Syping +* +* Redistribution and use in source and binary forms, with or without modification, +* are permitted provided that the following conditions are met: +* +* 1. Redistributions of source code must retain the above copyright notice, +* this list of conditions and the following disclaimer. +* +* 2. Redistributions in binary form must reproduce the above copyright notice, +* this list of conditions and the following disclaimer in the documentation +* and/or other materials provided with the distribution. +* +* This software is provided as-is, no warranties are given to you, we are not +* responsible for anything with use of the software, you are self responsible. +*****************************************************************************/ + +#include +#include +#include "SMSubProcess.h" +#include "smsub.h" + +SMSubProcess::SMSubProcess(const QString &executable, const QStringList &arguments, const QString &workingDirectory) +{ + process.setProgram(executable); + process.setArguments(arguments); + process.setWorkingDirectory(workingDirectory); + process.setProcessChannelMode(QProcess::MergedChannels); + + QObject::connect(&process, SIGNAL(readyRead()), this, SLOT(readyRead())); + QObject::connect(&process, SIGNAL(finished(int)), this, SLOT(processExit(int))); + QObject::connect(&process, SIGNAL(errorOccurred(QProcess::ProcessError)), this, SLOT(processError(QProcess::ProcessError))); +} + +void SMSubProcess::start() +{ + process.start(QIODevice::ReadWrite); +} + +void SMSubProcess::readyRead() +{ + while (process.canReadLine()) { + const QByteArray readData = process.readLine().trimmed(); + emit outputWritten(readData + '\n'); + } +} + +void SMSubProcess::processExit(int exitCode) +{ + emit processStopped(); + QCoreApplication::exit(exitCode); +} + +void SMSubProcess::processError(QProcess::ProcessError error) +{ + if (likely(error == QProcess::FailedToStart)) { + QTextStream(stderr) << "Process failed to start!" << endl; + QCoreApplication::exit(1); + } + else if (error == QProcess::UnknownError) { + QTextStream(stderr) << "Unknown error occurred!" << endl; + QCoreApplication::exit(1); + } +} + +void SMSubProcess::killProcess() +{ + process.kill(); +} + +void SMSubProcess::stopProcess() +{ + process.terminate(); +} + +void SMSubProcess::writeInput(const QByteArray &input) +{ + process.write(input); +} diff --git a/SMSubProcess.h b/SMSubProcess.h new file mode 100644 index 0000000..83289f5 --- /dev/null +++ b/SMSubProcess.h @@ -0,0 +1,51 @@ +/***************************************************************************** +* smsub Server Manager Subprocess +* Copyright (C) 2020 Syping +* +* Redistribution and use in source and binary forms, with or without modification, +* are permitted provided that the following conditions are met: +* +* 1. Redistributions of source code must retain the above copyright notice, +* this list of conditions and the following disclaimer. +* +* 2. Redistributions in binary form must reproduce the above copyright notice, +* this list of conditions and the following disclaimer in the documentation +* and/or other materials provided with the distribution. +* +* This software is provided as-is, no warranties are given to you, we are not +* responsible for anything with use of the software, you are self responsible. +*****************************************************************************/ + +#ifndef SMSUBPROCESS_H +#define SMSUBPROCESS_H + +#include +#include + +class SMSubProcess : public QObject +{ + Q_OBJECT +public: + SMSubProcess(const QString &executable, const QStringList &arguments, const QString& workingDirectory); + void start(); + +private: + QProcess process; + +public slots: + void killProcess(); + void stopProcess(); + void writeInput(const QByteArray &input); + +private slots: + void readyRead(); + void processExit(int exitCode); + void processError(QProcess::ProcessError error); + +signals: + void outputWritten(const QByteArray &output); + void processStopped(); + +}; + +#endif // SMSUBPROCESS_H diff --git a/SMSubServer.cpp b/SMSubServer.cpp new file mode 100644 index 0000000..5a51ceb --- /dev/null +++ b/SMSubServer.cpp @@ -0,0 +1,83 @@ +/***************************************************************************** +* smsub Server Manager Subprocess +* Copyright (C) 2020 Syping +* +* Redistribution and use in source and binary forms, with or without modification, +* are permitted provided that the following conditions are met: +* +* 1. Redistributions of source code must retain the above copyright notice, +* this list of conditions and the following disclaimer. +* +* 2. Redistributions in binary form must reproduce the above copyright notice, +* this list of conditions and the following disclaimer in the documentation +* and/or other materials provided with the distribution. +* +* This software is provided as-is, no warranties are given to you, we are not +* responsible for anything with use of the software, you are self responsible. +*****************************************************************************/ + +#include "SMSubServer.h" +#include "smsub.h" + +SMSubServer::SMSubServer(const QString &socket) +{ + listen(socket); +} + +void SMSubServer::incomingConnection(quintptr socketDescriptor) +{ + QLocalSocket *localSocket = new QLocalSocket(); + localSocket->setSocketDescriptor(socketDescriptor); + QObject::connect(localSocket, SIGNAL(readyRead()), this, SLOT(readyRead())); + QObject::connect(localSocket, SIGNAL(disconnected()), this, SLOT(deleteSocket())); + sockets << localSocket; +} + +void SMSubServer::readyRead() +{ + QLocalSocket *socket = (QLocalSocket*)sender(); + while (socket->canReadLine()) { + const QByteArray readData = socket->readLine().trimmed(); + if (readData.startsWith("+log")) { + socket->setProperty("ReceiveLog", true); + } + else if (readData.startsWith("-log")) { + socket->setProperty("ReceiveLog", false); + } + else if (readData.startsWith("kill")) { + emit killRequested(); + } + else if (readData.startsWith("stop")) { + emit stopRequested(); + } + else if (readData.startsWith("wl")) { + emit inputWritten(readData.mid(3) + '\n'); + } + else if (readData.startsWith("w")) { + emit inputWritten(readData.mid(2)); + } + } +} + +void SMSubServer::deleteSocket() +{ + QLocalSocket *socket = (QLocalSocket*)sender(); + sockets.removeAll(socket); + socket->deleteLater(); +} + +void SMSubServer::writeOutput(const QByteArray &output) +{ + QVector::const_iterator it = sockets.constBegin(); + QVector::const_iterator end = sockets.constEnd(); + while (it != end) { + const QVariant variant = (*it)->property("ReceiveLog"); + if (unlikely(variant.type() == QVariant::Bool)) { + bool receiveLog = variant.toBool(); + if (likely(receiveLog)) { + (*it)->write(output); + } + } + it++; + } +} diff --git a/SMSubServer.h b/SMSubServer.h new file mode 100644 index 0000000..3fe6a5d --- /dev/null +++ b/SMSubServer.h @@ -0,0 +1,49 @@ +/***************************************************************************** +* smsub Server Manager Subprocess +* Copyright (C) 2020 Syping +* +* Redistribution and use in source and binary forms, with or without modification, +* are permitted provided that the following conditions are met: +* +* 1. Redistributions of source code must retain the above copyright notice, +* this list of conditions and the following disclaimer. +* +* 2. Redistributions in binary form must reproduce the above copyright notice, +* this list of conditions and the following disclaimer in the documentation +* and/or other materials provided with the distribution. +* +* This software is provided as-is, no warranties are given to you, we are not +* responsible for anything with use of the software, you are self responsible. +*****************************************************************************/ + +#ifndef SMSUBSERVER_H +#define SMSUBSERVER_H + +#include +#include +#include + +class SMSubServer : public QLocalServer +{ + Q_OBJECT +public: + SMSubServer(const QString &socket); + +public slots: + void writeOutput(const QByteArray &output); + +private slots: + void incomingConnection(quintptr socketDescriptor); + void deleteSocket(); + void readyRead(); + +private: + QVector sockets; + +signals: + void inputWritten(const QByteArray &input); + void killRequested(); + void stopRequested(); +}; + +#endif // SMSUBSERVER_H diff --git a/main.cpp b/main.cpp index 6165813..cf46227 100644 --- a/main.cpp +++ b/main.cpp @@ -12,13 +12,23 @@ * this list of conditions and the following disclaimer in the documentation * and/or other materials provided with the distribution. * -* 3. This software is provided as-is, no warranties are given, we are not +* This software is provided as-is, no warranties are given to you, we are not * responsible for anything with use of the software, you are self responsible. *****************************************************************************/ #include #include #include +#include +#include +#include +#include +#include +#include +#include +#include "SMSubProcess.h" +#include "SMSubServer.h" +#include "smsub.h" int main(int argc, char *argv[]) { @@ -33,5 +43,112 @@ int main(int argc, char *argv[]) QCommandLineOption processManifest("json", "JSON process manifest.", "json"); commandLineParser.addOption(processManifest); + QCommandLineOption processExecutable(QStringList() << "exec" << "executable", "Process executable to run.", "exec"); + commandLineParser.addOption(processExecutable); + + QCommandLineOption processArguments(QStringList() << "args" << "arguments", "Arguments given to process.", "args"); + commandLineParser.addOption(processArguments); + +#ifdef Q_OS_WIN + QCommandLineOption subprocessSocket(QStringList() << "sock" << "socket", "IPC socket used for communication.", "sock"); +#else + QCommandLineOption subprocessSocket(QStringList() << "sock" << "socket", "Unix socket used for communication.", "sock"); +#endif + commandLineParser.addOption(subprocessSocket); + + commandLineParser.process(a); + + if (unlikely(commandLineParser.isSet(processManifest) && commandLineParser.isSet(processExecutable))) { + QTextStream(stderr) << "You can't define a Process executable and a JSON process manifest at the same time!" << endl; + return 1; + } + + QString executable; + QString workingDirectory; + QStringList argumentList; + if (likely(commandLineParser.isSet(processManifest))) { + QFile manifestFile(commandLineParser.value(processManifest)); + if (likely(manifestFile.open(QIODevice::ReadOnly))) { + const QByteArray jsonData = manifestFile.readAll(); + QJsonDocument jsonDocument = QJsonDocument::fromJson(jsonData); + QJsonObject jsonObject = jsonDocument.object(); + + if (likely(jsonObject.contains("Executable"))) { + const QJsonValue jsonExecutable = jsonObject.value("Executable"); + if (unlikely(!jsonExecutable.isString())) { + QTextStream(stderr) << "Executable is not a string in manifest, aborting!" << endl; + manifestFile.close(); + return 1; + } + executable = jsonExecutable.toString(); + } + else { + QTextStream(stderr) << "Executable is not defined in manifest, aborting!" << endl; + manifestFile.close(); + return 1; + } + + if (likely(jsonObject.contains("WorkingDirectory"))) { + const QJsonValue jsonWorkingDirectory = jsonObject.value("WorkingDirectory"); + if (unlikely(!jsonWorkingDirectory.isString())) { + QTextStream(stderr) << "Working Directory is not a string in manifest, aborting!" << endl; + manifestFile.close(); + return 1; + } + workingDirectory = jsonWorkingDirectory.toString(); + } + else { + workingDirectory = QFileInfo(executable).absolutePath(); + } + + if (likely(jsonObject.contains("Arguments"))) { + const QJsonValue jsonArguments = jsonObject.value("Arguments"); + if (likely(jsonArguments.isArray())) { + const QJsonArray jsonArray = jsonArguments.toArray(); + QJsonArray::const_iterator it = jsonArray.constBegin(); + QJsonArray::const_iterator end = jsonArray.constEnd(); + while (it != end) { + argumentList << it->toString(); + } + } + else { + QTextStream(stderr) << "Arguments is not a array in manifest, aborting!" << endl; + manifestFile.close(); + return 1; + } + } + + manifestFile.close(); + } + } + else if (unlikely(commandLineParser.isSet(processArguments))) { + QTextStream(stderr) << "Arguments over command line are not supported yet!" << endl; + return 1; + } + + QString socket; + if (likely(commandLineParser.isSet(subprocessSocket))) { + socket = commandLineParser.value(subprocessSocket); + } + + SMSubServer subServer(socket); + if (unlikely(!subServer.isListening())) { +#ifdef Q_OS_WIN + QTextStream(stderr) << "Failed to start IPC socket!" << endl; +#else + QTextStream(stderr) << "Failed to start Unix socket!" << endl; +#endif + return 1; + } + + SMSubProcess subProcess(executable, argumentList, workingDirectory); + + QObject::connect(&subProcess, SIGNAL(outputWritten(QByteArray)), &subServer, SLOT(writeOutput(QByteArray))); + QObject::connect(&subServer, SIGNAL(inputWritten(QByteArray)), &subProcess, SLOT(writeInput(QByteArray))); + QObject::connect(&subServer, SIGNAL(killRequested()), &subProcess, SLOT(killProcess())); + QObject::connect(&subServer, SIGNAL(stopRequested()), &subProcess, SLOT(stopProcess())); + + subProcess.start(); + return a.exec(); } diff --git a/smsub.h b/smsub.h new file mode 100644 index 0000000..a3315d7 --- /dev/null +++ b/smsub.h @@ -0,0 +1,38 @@ +/***************************************************************************** +* smsub Server Manager Subprocess +* Copyright (C) 2020 Syping +* +* Redistribution and use in source and binary forms, with or without modification, +* are permitted provided that the following conditions are met: +* +* 1. Redistributions of source code must retain the above copyright notice, +* this list of conditions and the following disclaimer. +* +* 2. Redistributions in binary form must reproduce the above copyright notice, +* this list of conditions and the following disclaimer in the documentation +* and/or other materials provided with the distribution. +* +* This software is provided as-is, no warranties are given to you, we are not +* responsible for anything with use of the software, you are self responsible. +*****************************************************************************/ + +#ifndef SMSUB_H +#define SMSUB_H + +#ifndef SMSUB_WITHOUT_EXPECT +#ifndef likely +#define likely(x) __builtin_expect((x),1) +#endif +#ifndef unlikely +#define unlikely(x) __builtin_expect((x),0) +#endif +#else +#ifndef likely +#define likely(x) (x) +#endif +#ifndef unlikely +#define unlikely(x) (x) +#endif +#endif + +#endif // SMSUB_H diff --git a/smsub.pro b/smsub.pro index 269feab..cdf6410 100644 --- a/smsub.pro +++ b/smsub.pro @@ -1,10 +1,34 @@ +############################################################################### +# smsub Server Manager Subprocess +# Copyright (C) 2020 Syping +# +# Redistribution and use in source and binary forms, with or without modification, +# are permitted provided that the following conditions are met: +# +# 1. Redistributions of source code must retain the above copyright notice, +# this list of conditions and the following disclaimer. +# +# 2. Redistributions in binary form must reproduce the above copyright notice, +# this list of conditions and the following disclaimer in the documentation +# and/or other materials provided with the distribution. +# +# This software is provided as-is, no warranties are given to you, we are not +# responsible for anything with use of the software, you are self responsible. +############################################################################### + QT -= gui +QT += network CONFIG += c++11 console CONFIG -= app_bundle -SOURCES += \ - main.cpp +SOURCES += main.cpp \ + SMSubProcess.cpp \ + SMSubServer.cpp + +HEADERS += smsub.h \ + SMSubProcess.h \ + SMSubServer.h qnx: target.path = /tmp/$${TARGET}/bin else: unix:!android: target.path = /opt/$${TARGET}/bin