Add installer

This commit is contained in:
driftingsun 2020-12-16 06:02:22 +03:00
parent c9bc8aa8c1
commit a2a5cafc5f
73 changed files with 4354 additions and 488 deletions

1
service/src/QtService Normal file
View file

@ -0,0 +1 @@
#include "qtservice.h"

View file

@ -0,0 +1 @@
#include "qtservice.h"

View file

@ -0,0 +1 @@
#include "qtservice.h"

1129
service/src/qtservice.cpp Normal file

File diff suppressed because it is too large Load diff

192
service/src/qtservice.h Normal file
View file

@ -0,0 +1,192 @@
/****************************************************************************
**
** Copyright (C) 2013 Digia Plc and/or its subsidiary(-ies).
** Contact: http://www.qt-project.org/legal
**
** This file is part of the Qt Solutions component.
**
** $QT_BEGIN_LICENSE:BSD$
** You may use this file under the terms of the BSD license as follows:
**
** "Redistribution and use in source and binary forms, with or without
** modification, are permitted provided that the following conditions are
** met:
** * Redistributions of source code must retain the above copyright
** notice, this list of conditions and the following disclaimer.
** * 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.
** * Neither the name of Digia Plc and its Subsidiary(-ies) nor the names
** of its contributors may be used to endorse or promote products derived
** from this software without specific prior written permission.
**
**
** THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
** "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
** LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
** A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
** OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
** SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
** LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
** DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
** THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
** (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
** OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE."
**
** $QT_END_LICENSE$
**
****************************************************************************/
#ifndef QTSERVICE_H
#define QTSERVICE_H
#include <QCoreApplication>
#if defined(Q_OS_WIN)
# if !defined(QT_QTSERVICE_EXPORT) && !defined(QT_QTSERVICE_IMPORT)
# define QT_QTSERVICE_EXPORT
# elif defined(QT_QTSERVICE_IMPORT)
# if defined(QT_QTSERVICE_EXPORT)
# undef QT_QTSERVICE_EXPORT
# endif
# define QT_QTSERVICE_EXPORT __declspec(dllimport)
# elif defined(QT_QTSERVICE_EXPORT)
# undef QT_QTSERVICE_EXPORT
# define QT_QTSERVICE_EXPORT __declspec(dllexport)
# endif
#else
# define QT_QTSERVICE_EXPORT
#endif
class QStringList;
class QtServiceControllerPrivate;
class QT_QTSERVICE_EXPORT QtServiceController
{
Q_DECLARE_PRIVATE(QtServiceController)
public:
enum StartupType
{
AutoStartup = 0, ManualStartup
};
QtServiceController(const QString &name);
virtual ~QtServiceController();
bool isInstalled() const;
bool isRunning() const;
QString serviceName() const;
QString serviceDescription() const;
StartupType startupType() const;
QString serviceFilePath() const;
static bool install(const QString &serviceFilePath, const QString &account = QString(),
const QString &password = QString());
bool uninstall();
bool start(const QStringList &arguments);
bool start();
bool stop();
bool pause();
bool resume();
bool sendCommand(int code);
private:
QtServiceControllerPrivate *d_ptr;
};
class QtServiceBasePrivate;
class QT_QTSERVICE_EXPORT QtServiceBase
{
Q_DECLARE_PRIVATE(QtServiceBase)
public:
enum MessageType
{
Success = 0, Error, Warning, Information
};
enum ServiceFlag
{
Default = 0x00,
CanBeSuspended = 0x01,
CannotBeStopped = 0x02,
NeedsStopOnShutdown = 0x04
};
Q_DECLARE_FLAGS(ServiceFlags, ServiceFlag)
QtServiceBase(int argc, char **argv, const QString &name);
virtual ~QtServiceBase();
QString serviceName() const;
QString serviceDescription() const;
void setServiceDescription(const QString &description);
QtServiceController::StartupType startupType() const;
void setStartupType(QtServiceController::StartupType startupType);
ServiceFlags serviceFlags() const;
void setServiceFlags(ServiceFlags flags);
int exec();
void logMessage(const QString &message, MessageType type = Success,
int id = 0, uint category = 0, const QByteArray &data = QByteArray());
static QtServiceBase *instance();
protected:
virtual void start() = 0;
virtual void stop();
virtual void pause();
virtual void resume();
virtual void processCommand(int code);
virtual void createApplication(int &argc, char **argv) = 0;
virtual int executeApplication() = 0;
private:
friend class QtServiceSysPrivate;
QtServiceBasePrivate *d_ptr;
};
template <typename Application>
class QtService : public QtServiceBase
{
public:
QtService(int argc, char **argv, const QString &name)
: QtServiceBase(argc, argv, name), app(0)
{ }
~QtService()
{
}
protected:
Application *application() const
{ return app; }
virtual void createApplication(int &argc, char **argv)
{
app = new Application(argc, argv);
QCoreApplication *a = app;
Q_UNUSED(a);
}
virtual int executeApplication()
{ return Application::exec(); }
private:
Application *app;
};
Q_DECLARE_OPERATORS_FOR_FLAGS(QtServiceBase::ServiceFlags)
#endif // QTSERVICE_H

21
service/src/qtservice.pri Normal file
View file

@ -0,0 +1,21 @@
include(../common.pri)
INCLUDEPATH += $$PWD
DEPENDPATH += $$PWD
!win32:QT += network
win32:LIBS += -luser32
qtservice-uselib:!qtservice-buildlib {
LIBS += -L$$QTSERVICE_LIBDIR -l$$QTSERVICE_LIBNAME
} else {
HEADERS += $$PWD/qtservice.h \
$$PWD/qtservice_p.h
SOURCES += $$PWD/qtservice.cpp
win32:SOURCES += $$PWD/qtservice_win.cpp
unix:HEADERS += $$PWD/qtunixsocket.h $$PWD/qtunixserversocket.h
unix:SOURCES += $$PWD/qtservice_unix.cpp $$PWD/qtunixsocket.cpp $$PWD/qtunixserversocket.cpp
}
win32 {
qtservice-buildlib:shared:DEFINES += QT_QTSERVICE_EXPORT
else:qtservice-uselib:DEFINES += QT_QTSERVICE_IMPORT
}

87
service/src/qtservice_p.h Normal file
View file

@ -0,0 +1,87 @@
/****************************************************************************
**
** Copyright (C) 2013 Digia Plc and/or its subsidiary(-ies).
** Contact: http://www.qt-project.org/legal
**
** This file is part of the Qt Solutions component.
**
** $QT_BEGIN_LICENSE:BSD$
** You may use this file under the terms of the BSD license as follows:
**
** "Redistribution and use in source and binary forms, with or without
** modification, are permitted provided that the following conditions are
** met:
** * Redistributions of source code must retain the above copyright
** notice, this list of conditions and the following disclaimer.
** * 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.
** * Neither the name of Digia Plc and its Subsidiary(-ies) nor the names
** of its contributors may be used to endorse or promote products derived
** from this software without specific prior written permission.
**
**
** THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
** "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
** LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
** A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
** OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
** SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
** LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
** DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
** THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
** (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
** OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE."
**
** $QT_END_LICENSE$
**
****************************************************************************/
#ifndef QTSERVICE_P_H
#define QTSERVICE_P_H
#include <QStringList>
#include "qtservice.h"
class QtServiceControllerPrivate
{
Q_DECLARE_PUBLIC(QtServiceController)
public:
QString serviceName;
QtServiceController *q_ptr;
};
class QtServiceBasePrivate
{
Q_DECLARE_PUBLIC(QtServiceBase)
public:
QtServiceBasePrivate(const QString &name);
~QtServiceBasePrivate();
QtServiceBase *q_ptr;
QString serviceDescription;
QtServiceController::StartupType startupType;
QtServiceBase::ServiceFlags serviceFlags;
QStringList args;
static class QtServiceBase *instance;
QtServiceController controller;
void startService();
int run(bool asService, const QStringList &argList);
bool install(const QString &account, const QString &password);
bool start();
QString filePath() const;
bool sysInit();
void sysSetPath();
void sysCleanup();
class QtServiceSysPrivate *sysd;
};
#endif

View file

@ -0,0 +1,482 @@
/****************************************************************************
**
** Copyright (C) 2013 Digia Plc and/or its subsidiary(-ies).
** Contact: http://www.qt-project.org/legal
**
** This file is part of the Qt Solutions component.
**
** $QT_BEGIN_LICENSE:BSD$
** You may use this file under the terms of the BSD license as follows:
**
** "Redistribution and use in source and binary forms, with or without
** modification, are permitted provided that the following conditions are
** met:
** * Redistributions of source code must retain the above copyright
** notice, this list of conditions and the following disclaimer.
** * 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.
** * Neither the name of Digia Plc and its Subsidiary(-ies) nor the names
** of its contributors may be used to endorse or promote products derived
** from this software without specific prior written permission.
**
**
** THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
** "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
** LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
** A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
** OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
** SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
** LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
** DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
** THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
** (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
** OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE."
**
** $QT_END_LICENSE$
**
****************************************************************************/
#include "qtservice.h"
#include "qtservice_p.h"
#include "qtunixsocket.h"
#include "qtunixserversocket.h"
#include <QCoreApplication>
#include <QStringList>
#include <QFile>
#include <QTimer>
#include <QDir>
#include <pwd.h>
#include <fcntl.h>
#include <unistd.h>
#include <stdlib.h>
#include <string.h>
#include <syslog.h>
#include <signal.h>
#include <sys/stat.h>
#include <QMap>
#include <QSettings>
#include <QProcess>
static QString encodeName(const QString &name, bool allowUpper = false)
{
QString n = name.toLower();
QString legal = QLatin1String("abcdefghijklmnopqrstuvwxyz1234567890");
if (allowUpper)
legal += QLatin1String("ABCDEFGHIJKLMNOPQRSTUVWXYZ");
int pos = 0;
while (pos < n.size()) {
if (legal.indexOf(n[pos]) == -1)
n.remove(pos, 1);
else
++pos;
}
return n;
}
static QString login()
{
QString l;
uid_t uid = getuid();
passwd *pw = getpwuid(uid);
if (pw)
l = QString(pw->pw_name);
return l;
}
static QString socketPath(const QString &serviceName)
{
QString sn = encodeName(serviceName);
return QString(QLatin1String("/var/tmp/") + sn + QLatin1String(".") + login());
}
static bool sendCmd(const QString &serviceName, const QString &cmd)
{
bool retValue = false;
QtUnixSocket sock;
if (sock.connectTo(socketPath(serviceName))) {
sock.write(QString(cmd+"\r\n").toLatin1().constData());
sock.flush();
sock.waitForReadyRead(-1);
QString reply = sock.readAll();
if (reply == QLatin1String("true"))
retValue = true;
sock.close();
}
return retValue;
}
static QString absPath(const QString &path)
{
QString ret;
if (path[0] != QChar('/')) { // Not an absolute path
int slashpos;
if ((slashpos = path.lastIndexOf('/')) != -1) { // Relative path
QDir dir = QDir::current();
dir.cd(path.left(slashpos));
ret = dir.absolutePath();
} else { // Need to search $PATH
char *envPath = ::getenv("PATH");
if (envPath) {
QStringList envPaths = QString::fromLocal8Bit(envPath).split(':');
for (int i = 0; i < envPaths.size(); ++i) {
if (QFile::exists(envPaths.at(i) + QLatin1String("/") + QString(path))) {
QDir dir(envPaths.at(i));
ret = dir.absolutePath();
break;
}
}
}
}
} else {
QFileInfo fi(path);
ret = fi.absolutePath();
}
return ret;
}
QString QtServiceBasePrivate::filePath() const
{
QString ret;
if (args.isEmpty())
return ret;
QFileInfo fi(args[0]);
QDir dir(absPath(args[0]));
return dir.absoluteFilePath(fi.fileName());
}
QString QtServiceController::serviceDescription() const
{
QSettings settings(QSettings::SystemScope, "QtSoftware");
settings.beginGroup("services");
settings.beginGroup(serviceName());
QString desc = settings.value("description").toString();
settings.endGroup();
settings.endGroup();
return desc;
}
QtServiceController::StartupType QtServiceController::startupType() const
{
QSettings settings(QSettings::SystemScope, "QtSoftware");
settings.beginGroup("services");
settings.beginGroup(serviceName());
StartupType startupType = (StartupType)settings.value("startupType").toInt();
settings.endGroup();
settings.endGroup();
return startupType;
}
QString QtServiceController::serviceFilePath() const
{
QSettings settings(QSettings::SystemScope, "QtSoftware");
settings.beginGroup("services");
settings.beginGroup(serviceName());
QString path = settings.value("path").toString();
settings.endGroup();
settings.endGroup();
return path;
}
bool QtServiceController::uninstall()
{
QSettings settings(QSettings::SystemScope, "QtSoftware");
settings.beginGroup("services");
settings.remove(serviceName());
settings.endGroup();
settings.sync();
QSettings::Status ret = settings.status();
if (ret == QSettings::AccessError) {
fprintf(stderr, "Cannot uninstall \"%s\". Cannot write to: %s. Check permissions.\n",
serviceName().toLatin1().constData(),
settings.fileName().toLatin1().constData());
}
return (ret == QSettings::NoError);
}
bool QtServiceController::start(const QStringList &arguments)
{
if (!isInstalled())
return false;
if (isRunning())
return false;
return QProcess::startDetached(serviceFilePath(), arguments);
}
bool QtServiceController::stop()
{
return sendCmd(serviceName(), QLatin1String("terminate"));
}
bool QtServiceController::pause()
{
return sendCmd(serviceName(), QLatin1String("pause"));
}
bool QtServiceController::resume()
{
return sendCmd(serviceName(), QLatin1String("resume"));
}
bool QtServiceController::sendCommand(int code)
{
return sendCmd(serviceName(), QString(QLatin1String("num:") + QString::number(code)));
}
bool QtServiceController::isInstalled() const
{
QSettings settings(QSettings::SystemScope, "QtSoftware");
settings.beginGroup("services");
QStringList list = settings.childGroups();
settings.endGroup();
QStringListIterator it(list);
while (it.hasNext()) {
if (it.next() == serviceName())
return true;
}
return false;
}
bool QtServiceController::isRunning() const
{
QtUnixSocket sock;
if (sock.connectTo(socketPath(serviceName())))
return true;
return false;
}
///////////////////////////////////
class QtServiceSysPrivate : public QtUnixServerSocket
{
Q_OBJECT
public:
QtServiceSysPrivate();
~QtServiceSysPrivate();
char *ident;
QtServiceBase::ServiceFlags serviceFlags;
protected:
#if QT_VERSION >= 0x050000
void incomingConnection(qintptr socketDescriptor);
#else
void incomingConnection(int socketDescriptor);
#endif
private slots:
void slotReady();
void slotClosed();
private:
QString getCommand(const QTcpSocket *socket);
QMap<const QTcpSocket *, QString> cache;
};
QtServiceSysPrivate::QtServiceSysPrivate()
: QtUnixServerSocket(), ident(0), serviceFlags(0)
{
}
QtServiceSysPrivate::~QtServiceSysPrivate()
{
if (ident)
delete[] ident;
}
#if QT_VERSION >= 0x050000
void QtServiceSysPrivate::incomingConnection(qintptr socketDescriptor)
#else
void QtServiceSysPrivate::incomingConnection(int socketDescriptor)
#endif
{
QTcpSocket *s = new QTcpSocket(this);
s->setSocketDescriptor(socketDescriptor);
connect(s, SIGNAL(readyRead()), this, SLOT(slotReady()));
connect(s, SIGNAL(disconnected()), this, SLOT(slotClosed()));
}
void QtServiceSysPrivate::slotReady()
{
QTcpSocket *s = (QTcpSocket *)sender();
cache[s] += QString(s->readAll());
QString cmd = getCommand(s);
while (!cmd.isEmpty()) {
bool retValue = false;
if (cmd == QLatin1String("terminate")) {
if (!(serviceFlags & QtServiceBase::CannotBeStopped)) {
QtServiceBase::instance()->stop();
QCoreApplication::instance()->quit();
retValue = true;
}
} else if (cmd == QLatin1String("pause")) {
if (serviceFlags & QtServiceBase::CanBeSuspended) {
QtServiceBase::instance()->pause();
retValue = true;
}
} else if (cmd == QLatin1String("resume")) {
if (serviceFlags & QtServiceBase::CanBeSuspended) {
QtServiceBase::instance()->resume();
retValue = true;
}
} else if (cmd == QLatin1String("alive")) {
retValue = true;
} else if (cmd.length() > 4 && cmd.left(4) == QLatin1String("num:")) {
cmd = cmd.mid(4);
QtServiceBase::instance()->processCommand(cmd.toInt());
retValue = true;
}
QString retString;
if (retValue)
retString = QLatin1String("true");
else
retString = QLatin1String("false");
s->write(retString.toLatin1().constData());
s->flush();
cmd = getCommand(s);
}
}
void QtServiceSysPrivate::slotClosed()
{
QTcpSocket *s = (QTcpSocket *)sender();
s->deleteLater();
}
QString QtServiceSysPrivate::getCommand(const QTcpSocket *socket)
{
int pos = cache[socket].indexOf("\r\n");
if (pos >= 0) {
QString ret = cache[socket].left(pos);
cache[socket].remove(0, pos+2);
return ret;
}
return "";
}
#include "qtservice_unix.moc"
bool QtServiceBasePrivate::sysInit()
{
sysd = new QtServiceSysPrivate;
sysd->serviceFlags = serviceFlags;
// Restrict permissions on files that are created by the service
::umask(027);
return true;
}
void QtServiceBasePrivate::sysSetPath()
{
if (sysd)
sysd->setPath(socketPath(controller.serviceName()));
}
void QtServiceBasePrivate::sysCleanup()
{
if (sysd) {
sysd->close();
delete sysd;
sysd = 0;
}
}
bool QtServiceBasePrivate::start()
{
if (sendCmd(controller.serviceName(), "alive")) {
// Already running
return false;
}
// Could just call controller.start() here, but that would fail if
// we're not installed. We do not want to strictly require installation.
::setenv("QTSERVICE_RUN", "1", 1); // Tell the detached process it's it
return QProcess::startDetached(filePath(), args.mid(1), "/");
}
bool QtServiceBasePrivate::install(const QString &account, const QString &password)
{
Q_UNUSED(account)
Q_UNUSED(password)
QSettings settings(QSettings::SystemScope, "QtSoftware");
settings.beginGroup("services");
settings.beginGroup(controller.serviceName());
settings.setValue("path", filePath());
settings.setValue("description", serviceDescription);
settings.setValue("automaticStartup", startupType);
settings.endGroup();
settings.endGroup();
settings.sync();
QSettings::Status ret = settings.status();
if (ret == QSettings::AccessError) {
fprintf(stderr, "Cannot install \"%s\". Cannot write to: %s. Check permissions.\n",
controller.serviceName().toLatin1().constData(),
settings.fileName().toLatin1().constData());
}
return (ret == QSettings::NoError);
}
void QtServiceBase::logMessage(const QString &message, QtServiceBase::MessageType type,
int, uint, const QByteArray &)
{
if (!d_ptr->sysd)
return;
int st;
switch(type) {
case QtServiceBase::Error:
st = LOG_ERR;
break;
case QtServiceBase::Warning:
st = LOG_WARNING;
break;
default:
st = LOG_INFO;
}
if (!d_ptr->sysd->ident) {
QString tmp = encodeName(serviceName(), true);
int len = tmp.toLocal8Bit().size();
d_ptr->sysd->ident = new char[len+1];
d_ptr->sysd->ident[len] = '\0';
::memcpy(d_ptr->sysd->ident, tmp.toLocal8Bit().constData(), len);
}
openlog(d_ptr->sysd->ident, LOG_PID, LOG_DAEMON);
foreach(QString line, message.split('\n'))
syslog(st, "%s", line.toLocal8Bit().constData());
closelog();
}
void QtServiceBase::setServiceFlags(QtServiceBase::ServiceFlags flags)
{
if (d_ptr->serviceFlags == flags)
return;
d_ptr->serviceFlags = flags;
if (d_ptr->sysd)
d_ptr->sysd->serviceFlags = flags;
}

View file

@ -0,0 +1,952 @@
/****************************************************************************
**
** Copyright (C) 2013 Digia Plc and/or its subsidiary(-ies).
** Contact: http://www.qt-project.org/legal
**
** This file is part of the Qt Solutions component.
**
** $QT_BEGIN_LICENSE:BSD$
** You may use this file under the terms of the BSD license as follows:
**
** "Redistribution and use in source and binary forms, with or without
** modification, are permitted provided that the following conditions are
** met:
** * Redistributions of source code must retain the above copyright
** notice, this list of conditions and the following disclaimer.
** * 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.
** * Neither the name of Digia Plc and its Subsidiary(-ies) nor the names
** of its contributors may be used to endorse or promote products derived
** from this software without specific prior written permission.
**
**
** THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
** "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
** LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
** A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
** OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
** SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
** LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
** DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
** THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
** (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
** OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE."
**
** $QT_END_LICENSE$
**
****************************************************************************/
#include "qtservice.h"
#include "qtservice_p.h"
#include <QCoreApplication>
#include <QDateTime>
#include <QFile>
#include <QLibrary>
#include <QMutex>
#include <QSemaphore>
#include <QProcess>
#include <QSettings>
#include <QTextStream>
#include <qt_windows.h>
#include <QWaitCondition>
#include <QAbstractEventDispatcher>
#include <QVector>
#include <QThread>
#if QT_VERSION >= 0x050000
# include <QAbstractNativeEventFilter>
#endif
#include <stdio.h>
#if defined(QTSERVICE_DEBUG)
#include <QDebug>
#endif
typedef SERVICE_STATUS_HANDLE(WINAPI*PRegisterServiceCtrlHandler)(const wchar_t*,LPHANDLER_FUNCTION);
static PRegisterServiceCtrlHandler pRegisterServiceCtrlHandler = 0;
typedef BOOL(WINAPI*PSetServiceStatus)(SERVICE_STATUS_HANDLE,LPSERVICE_STATUS);
static PSetServiceStatus pSetServiceStatus = 0;
typedef BOOL(WINAPI*PChangeServiceConfig2)(SC_HANDLE,DWORD,LPVOID);
static PChangeServiceConfig2 pChangeServiceConfig2 = 0;
typedef BOOL(WINAPI*PCloseServiceHandle)(SC_HANDLE);
static PCloseServiceHandle pCloseServiceHandle = 0;
typedef SC_HANDLE(WINAPI*PCreateService)(SC_HANDLE,LPCTSTR,LPCTSTR,DWORD,DWORD,DWORD,DWORD,LPCTSTR,LPCTSTR,LPDWORD,LPCTSTR,LPCTSTR,LPCTSTR);
static PCreateService pCreateService = 0;
typedef SC_HANDLE(WINAPI*POpenSCManager)(LPCTSTR,LPCTSTR,DWORD);
static POpenSCManager pOpenSCManager = 0;
typedef BOOL(WINAPI*PDeleteService)(SC_HANDLE);
static PDeleteService pDeleteService = 0;
typedef SC_HANDLE(WINAPI*POpenService)(SC_HANDLE,LPCTSTR,DWORD);
static POpenService pOpenService = 0;
typedef BOOL(WINAPI*PQueryServiceStatus)(SC_HANDLE,LPSERVICE_STATUS);
static PQueryServiceStatus pQueryServiceStatus = 0;
typedef BOOL(WINAPI*PStartServiceCtrlDispatcher)(CONST SERVICE_TABLE_ENTRY*);
static PStartServiceCtrlDispatcher pStartServiceCtrlDispatcher = 0;
typedef BOOL(WINAPI*PStartService)(SC_HANDLE,DWORD,const wchar_t**);
static PStartService pStartService = 0;
typedef BOOL(WINAPI*PControlService)(SC_HANDLE,DWORD,LPSERVICE_STATUS);
static PControlService pControlService = 0;
typedef HANDLE(WINAPI*PDeregisterEventSource)(HANDLE);
static PDeregisterEventSource pDeregisterEventSource = 0;
typedef BOOL(WINAPI*PReportEvent)(HANDLE,WORD,WORD,DWORD,PSID,WORD,DWORD,LPCTSTR*,LPVOID);
static PReportEvent pReportEvent = 0;
typedef HANDLE(WINAPI*PRegisterEventSource)(LPCTSTR,LPCTSTR);
static PRegisterEventSource pRegisterEventSource = 0;
typedef DWORD(WINAPI*PRegisterServiceProcess)(DWORD,DWORD);
static PRegisterServiceProcess pRegisterServiceProcess = 0;
typedef BOOL(WINAPI*PQueryServiceConfig)(SC_HANDLE,LPQUERY_SERVICE_CONFIG,DWORD,LPDWORD);
static PQueryServiceConfig pQueryServiceConfig = 0;
typedef BOOL(WINAPI*PQueryServiceConfig2)(SC_HANDLE,DWORD,LPBYTE,DWORD,LPDWORD);
static PQueryServiceConfig2 pQueryServiceConfig2 = 0;
#define RESOLVE(name) p##name = (P##name)lib.resolve(#name);
#define RESOLVEA(name) p##name = (P##name)lib.resolve(#name"A");
#define RESOLVEW(name) p##name = (P##name)lib.resolve(#name"W");
static bool winServiceInit()
{
if (!pOpenSCManager) {
QLibrary lib("advapi32");
// only resolve unicode versions
RESOLVEW(RegisterServiceCtrlHandler);
RESOLVE(SetServiceStatus);
RESOLVEW(ChangeServiceConfig2);
RESOLVE(CloseServiceHandle);
RESOLVEW(CreateService);
RESOLVEW(OpenSCManager);
RESOLVE(DeleteService);
RESOLVEW(OpenService);
RESOLVE(QueryServiceStatus);
RESOLVEW(StartServiceCtrlDispatcher);
RESOLVEW(StartService); // need only Ansi version
RESOLVE(ControlService);
RESOLVE(DeregisterEventSource);
RESOLVEW(ReportEvent);
RESOLVEW(RegisterEventSource);
RESOLVEW(QueryServiceConfig);
RESOLVEW(QueryServiceConfig2);
}
return pOpenSCManager != 0;
}
bool QtServiceController::isInstalled() const
{
Q_D(const QtServiceController);
bool result = false;
if (!winServiceInit())
return result;
// Open the Service Control Manager
SC_HANDLE hSCM = pOpenSCManager(0, 0, 0);
if (hSCM) {
// Try to open the service
SC_HANDLE hService = pOpenService(hSCM, (wchar_t*)d->serviceName.utf16(),
SERVICE_QUERY_CONFIG);
if (hService) {
result = true;
pCloseServiceHandle(hService);
}
pCloseServiceHandle(hSCM);
}
return result;
}
bool QtServiceController::isRunning() const
{
Q_D(const QtServiceController);
bool result = false;
if (!winServiceInit())
return result;
// Open the Service Control Manager
SC_HANDLE hSCM = pOpenSCManager(0, 0, 0);
if (hSCM) {
// Try to open the service
SC_HANDLE hService = pOpenService(hSCM, (wchar_t *)d->serviceName.utf16(),
SERVICE_QUERY_STATUS);
if (hService) {
SERVICE_STATUS info;
int res = pQueryServiceStatus(hService, &info);
if (res)
result = info.dwCurrentState != SERVICE_STOPPED;
pCloseServiceHandle(hService);
}
pCloseServiceHandle(hSCM);
}
return result;
}
QString QtServiceController::serviceFilePath() const
{
Q_D(const QtServiceController);
QString result;
if (!winServiceInit())
return result;
// Open the Service Control Manager
SC_HANDLE hSCM = pOpenSCManager(0, 0, 0);
if (hSCM) {
// Try to open the service
SC_HANDLE hService = pOpenService(hSCM, (wchar_t *)d->serviceName.utf16(),
SERVICE_QUERY_CONFIG);
if (hService) {
DWORD sizeNeeded = 0;
char data[8 * 1024];
if (pQueryServiceConfig(hService, (LPQUERY_SERVICE_CONFIG)data, 8 * 1024, &sizeNeeded)) {
LPQUERY_SERVICE_CONFIG config = (LPQUERY_SERVICE_CONFIG)data;
result = QString::fromUtf16((const ushort*)config->lpBinaryPathName);
}
pCloseServiceHandle(hService);
}
pCloseServiceHandle(hSCM);
}
return result;
}
QString QtServiceController::serviceDescription() const
{
Q_D(const QtServiceController);
QString result;
if (!winServiceInit())
return result;
// Open the Service Control Manager
SC_HANDLE hSCM = pOpenSCManager(0, 0, 0);
if (hSCM) {
// Try to open the service
SC_HANDLE hService = pOpenService(hSCM, (wchar_t *)d->serviceName.utf16(),
SERVICE_QUERY_CONFIG);
if (hService) {
DWORD dwBytesNeeded;
char data[8 * 1024];
if (pQueryServiceConfig2(
hService,
SERVICE_CONFIG_DESCRIPTION,
(unsigned char *)data,
8096,
&dwBytesNeeded)) {
LPSERVICE_DESCRIPTION desc = (LPSERVICE_DESCRIPTION)data;
if (desc->lpDescription)
result = QString::fromUtf16((const ushort*)desc->lpDescription);
}
pCloseServiceHandle(hService);
}
pCloseServiceHandle(hSCM);
}
return result;
}
QtServiceController::StartupType QtServiceController::startupType() const
{
Q_D(const QtServiceController);
StartupType result = ManualStartup;
if (!winServiceInit())
return result;
// Open the Service Control Manager
SC_HANDLE hSCM = pOpenSCManager(0, 0, 0);
if (hSCM) {
// Try to open the service
SC_HANDLE hService = pOpenService(hSCM, (wchar_t *)d->serviceName.utf16(),
SERVICE_QUERY_CONFIG);
if (hService) {
DWORD sizeNeeded = 0;
char data[8 * 1024];
if (pQueryServiceConfig(hService, (QUERY_SERVICE_CONFIG *)data, 8 * 1024, &sizeNeeded)) {
QUERY_SERVICE_CONFIG *config = (QUERY_SERVICE_CONFIG *)data;
result = config->dwStartType == SERVICE_DEMAND_START ? ManualStartup : AutoStartup;
}
pCloseServiceHandle(hService);
}
pCloseServiceHandle(hSCM);
}
return result;
}
bool QtServiceController::uninstall()
{
Q_D(QtServiceController);
bool result = false;
if (!winServiceInit())
return result;
// Open the Service Control Manager
SC_HANDLE hSCM = pOpenSCManager(0, 0, SC_MANAGER_ALL_ACCESS);
if (hSCM) {
// Try to open the service
SC_HANDLE hService = pOpenService(hSCM, (wchar_t *)d->serviceName.utf16(), DELETE);
if (hService) {
if (pDeleteService(hService))
result = true;
pCloseServiceHandle(hService);
}
pCloseServiceHandle(hSCM);
}
return result;
}
bool QtServiceController::start(const QStringList &args)
{
Q_D(QtServiceController);
bool result = false;
if (!winServiceInit())
return result;
// Open the Service Control Manager
SC_HANDLE hSCM = pOpenSCManager(0, 0, SC_MANAGER_CONNECT);
if (hSCM) {
// Try to open the service
SC_HANDLE hService = pOpenService(hSCM, (wchar_t *)d->serviceName.utf16(), SERVICE_START);
if (hService) {
QVector<const wchar_t *> argv(args.size());
for (int i = 0; i < args.size(); ++i)
argv[i] = (const wchar_t*)args.at(i).utf16();
if (pStartService(hService, args.size(), argv.data()))
result = true;
pCloseServiceHandle(hService);
}
pCloseServiceHandle(hSCM);
}
return result;
}
bool QtServiceController::stop()
{
Q_D(QtServiceController);
bool result = false;
if (!winServiceInit())
return result;
SC_HANDLE hSCM = pOpenSCManager(0, 0, SC_MANAGER_CONNECT);
if (hSCM) {
SC_HANDLE hService = pOpenService(hSCM, (wchar_t *)d->serviceName.utf16(), SERVICE_STOP|SERVICE_QUERY_STATUS);
if (hService) {
SERVICE_STATUS status;
if (pControlService(hService, SERVICE_CONTROL_STOP, &status)) {
bool stopped = status.dwCurrentState == SERVICE_STOPPED;
int i = 0;
while(!stopped && i < 10) {
Sleep(200);
if (!pQueryServiceStatus(hService, &status))
break;
stopped = status.dwCurrentState == SERVICE_STOPPED;
++i;
}
result = stopped;
} else {
qErrnoWarning(GetLastError(), "stopping");
}
pCloseServiceHandle(hService);
}
pCloseServiceHandle(hSCM);
}
return result;
}
bool QtServiceController::pause()
{
Q_D(QtServiceController);
bool result = false;
if (!winServiceInit())
return result;
SC_HANDLE hSCM = pOpenSCManager(0, 0, SC_MANAGER_CONNECT);
if (hSCM) {
SC_HANDLE hService = pOpenService(hSCM, (wchar_t *)d->serviceName.utf16(),
SERVICE_PAUSE_CONTINUE);
if (hService) {
SERVICE_STATUS status;
if (pControlService(hService, SERVICE_CONTROL_PAUSE, &status))
result = true;
pCloseServiceHandle(hService);
}
pCloseServiceHandle(hSCM);
}
return result;
}
bool QtServiceController::resume()
{
Q_D(QtServiceController);
bool result = false;
if (!winServiceInit())
return result;
SC_HANDLE hSCM = pOpenSCManager(0, 0, SC_MANAGER_CONNECT);
if (hSCM) {
SC_HANDLE hService = pOpenService(hSCM, (wchar_t *)d->serviceName.utf16(),
SERVICE_PAUSE_CONTINUE);
if (hService) {
SERVICE_STATUS status;
if (pControlService(hService, SERVICE_CONTROL_CONTINUE, &status))
result = true;
pCloseServiceHandle(hService);
}
pCloseServiceHandle(hSCM);
}
return result;
}
bool QtServiceController::sendCommand(int code)
{
Q_D(QtServiceController);
bool result = false;
if (!winServiceInit())
return result;
if (code < 0 || code > 127 || !isRunning())
return result;
SC_HANDLE hSCM = pOpenSCManager(0, 0, SC_MANAGER_CONNECT);
if (hSCM) {
SC_HANDLE hService = pOpenService(hSCM, (wchar_t *)d->serviceName.utf16(),
SERVICE_USER_DEFINED_CONTROL);
if (hService) {
SERVICE_STATUS status;
if (pControlService(hService, 128 + code, &status))
result = true;
pCloseServiceHandle(hService);
}
pCloseServiceHandle(hSCM);
}
return result;
}
#if defined(QTSERVICE_DEBUG)
# if QT_VERSION >= 0x050000
extern void qtServiceLogDebug(QtMsgType type, const QMessageLogContext &context, const QString &msg);
# else
extern void qtServiceLogDebug(QtMsgType type, const char* msg);
# endif
#endif
void QtServiceBase::logMessage(const QString &message, MessageType type,
int id, uint category, const QByteArray &data)
{
#if defined(QTSERVICE_DEBUG)
QByteArray dbgMsg("[LOGGED ");
switch (type) {
case Error: dbgMsg += "Error] " ; break;
case Warning: dbgMsg += "Warning] "; break;
case Success: dbgMsg += "Success] "; break;
case Information: //fall through
default: dbgMsg += "Information] "; break;
}
# if QT_VERSION >= 0x050000
qtServiceLogDebug((QtMsgType)-1, QMessageLogContext(), QLatin1String(dbgMsg) + message);
# else
qtServiceLogDebug((QtMsgType)-1, (dbgMsg + message.toAscii()).constData());
# endif
#endif
Q_D(QtServiceBase);
if (!winServiceInit())
return;
WORD wType;
switch (type) {
case Error: wType = EVENTLOG_ERROR_TYPE; break;
case Warning: wType = EVENTLOG_WARNING_TYPE; break;
case Information: wType = EVENTLOG_INFORMATION_TYPE; break;
default: wType = EVENTLOG_SUCCESS; break;
}
HANDLE h = pRegisterEventSource(0, (wchar_t *)d->controller.serviceName().utf16());
if (h) {
const wchar_t *msg = (wchar_t*)message.utf16();
const char *bindata = data.size() ? data.constData() : 0;
pReportEvent(h, wType, category, id, 0, 1, data.size(),(const wchar_t **)&msg,
const_cast<char *>(bindata));
pDeregisterEventSource(h);
}
}
class QtServiceControllerHandler : public QObject
{
Q_OBJECT
public:
QtServiceControllerHandler(QtServiceSysPrivate *sys);
protected:
void customEvent(QEvent *e);
private:
QtServiceSysPrivate *d_sys;
};
class QtServiceSysPrivate
{
public:
enum {
QTSERVICE_STARTUP = 256
};
QtServiceSysPrivate();
void setStatus( DWORD dwState );
void setServiceFlags(QtServiceBase::ServiceFlags flags);
DWORD serviceFlags(QtServiceBase::ServiceFlags flags) const;
inline bool available() const;
static void WINAPI serviceMain( DWORD dwArgc, wchar_t** lpszArgv );
static void WINAPI handler( DWORD dwOpcode );
SERVICE_STATUS status;
SERVICE_STATUS_HANDLE serviceStatus;
QStringList serviceArgs;
static QtServiceSysPrivate *instance;
#if QT_VERSION < 0x050000
static QCoreApplication::EventFilter nextFilter;
#endif
QWaitCondition condition;
QMutex mutex;
QSemaphore startSemaphore;
QSemaphore startSemaphore2;
QtServiceControllerHandler *controllerHandler;
void handleCustomEvent(QEvent *e);
};
QtServiceControllerHandler::QtServiceControllerHandler(QtServiceSysPrivate *sys)
: QObject(), d_sys(sys)
{
}
void QtServiceControllerHandler::customEvent(QEvent *e)
{
d_sys->handleCustomEvent(e);
}
QtServiceSysPrivate *QtServiceSysPrivate::instance = 0;
#if QT_VERSION < 0x050000
QCoreApplication::EventFilter QtServiceSysPrivate::nextFilter = 0;
#endif
QtServiceSysPrivate::QtServiceSysPrivate()
{
instance = this;
}
inline bool QtServiceSysPrivate::available() const
{
return 0 != pOpenSCManager;
}
void WINAPI QtServiceSysPrivate::serviceMain(DWORD dwArgc, wchar_t** lpszArgv)
{
if (!instance || !QtServiceBase::instance())
return;
// Windows spins off a random thread to call this function on
// startup, so here we just signal to the QApplication event loop
// in the main thread to go ahead with start()'ing the service.
for (DWORD i = 0; i < dwArgc; i++)
instance->serviceArgs.append(QString::fromUtf16((unsigned short*)lpszArgv[i]));
instance->startSemaphore.release(); // let the qapp creation start
instance->startSemaphore2.acquire(); // wait until its done
// Register the control request handler
instance->serviceStatus = pRegisterServiceCtrlHandler((TCHAR*)QtServiceBase::instance()->serviceName().utf16(), handler);
if (!instance->serviceStatus) // cannot happen - something is utterly wrong
return;
handler(QTSERVICE_STARTUP); // Signal startup to the application -
// causes QtServiceBase::start() to be called in the main thread
// The MSDN doc says that this thread should just exit - the service is
// running in the main thread (here, via callbacks in the handler thread).
}
// The handler() is called from the thread that called
// StartServiceCtrlDispatcher, i.e. our HandlerThread, and
// not from the main thread that runs the event loop, so we
// have to post an event to ourselves, and use a QWaitCondition
// and a QMutex to synchronize.
void QtServiceSysPrivate::handleCustomEvent(QEvent *e)
{
int code = e->type() - QEvent::User;
switch(code) {
case QTSERVICE_STARTUP: // Startup
QtServiceBase::instance()->start();
break;
case SERVICE_CONTROL_STOP:
QtServiceBase::instance()->stop();
QCoreApplication::instance()->quit();
break;
case SERVICE_CONTROL_PAUSE:
QtServiceBase::instance()->pause();
break;
case SERVICE_CONTROL_CONTINUE:
QtServiceBase::instance()->resume();
break;
default:
if (code >= 128 && code <= 255)
QtServiceBase::instance()->processCommand(code - 128);
break;
}
mutex.lock();
condition.wakeAll();
mutex.unlock();
}
void WINAPI QtServiceSysPrivate::handler( DWORD code )
{
if (!instance)
return;
instance->mutex.lock();
switch (code) {
case QTSERVICE_STARTUP: // QtService startup (called from WinMain when started)
instance->setStatus(SERVICE_START_PENDING);
QCoreApplication::postEvent(instance->controllerHandler, new QEvent(QEvent::Type(QEvent::User + code)));
instance->condition.wait(&instance->mutex);
instance->setStatus(SERVICE_RUNNING);
break;
case SERVICE_CONTROL_STOP: // 1
instance->setStatus(SERVICE_STOP_PENDING);
QCoreApplication::postEvent(instance->controllerHandler, new QEvent(QEvent::Type(QEvent::User + code)));
instance->condition.wait(&instance->mutex);
// status will be reported as stopped by start() when qapp::exec returns
break;
case SERVICE_CONTROL_PAUSE: // 2
instance->setStatus(SERVICE_PAUSE_PENDING);
QCoreApplication::postEvent(instance->controllerHandler, new QEvent(QEvent::Type(QEvent::User + code)));
instance->condition.wait(&instance->mutex);
instance->setStatus(SERVICE_PAUSED);
break;
case SERVICE_CONTROL_CONTINUE: // 3
instance->setStatus(SERVICE_CONTINUE_PENDING);
QCoreApplication::postEvent(instance->controllerHandler, new QEvent(QEvent::Type(QEvent::User + code)));
instance->condition.wait(&instance->mutex);
instance->setStatus(SERVICE_RUNNING);
break;
case SERVICE_CONTROL_INTERROGATE: // 4
break;
case SERVICE_CONTROL_SHUTDOWN: // 5
// Don't waste time with reporting stop pending, just do it
QCoreApplication::postEvent(instance->controllerHandler, new QEvent(QEvent::Type(QEvent::User + SERVICE_CONTROL_STOP)));
instance->condition.wait(&instance->mutex);
// status will be reported as stopped by start() when qapp::exec returns
break;
default:
if ( code >= 128 && code <= 255 ) {
QCoreApplication::postEvent(instance->controllerHandler, new QEvent(QEvent::Type(QEvent::User + code)));
instance->condition.wait(&instance->mutex);
}
break;
}
instance->mutex.unlock();
// Report current status
if (instance->available() && instance->status.dwCurrentState != SERVICE_STOPPED)
pSetServiceStatus(instance->serviceStatus, &instance->status);
}
void QtServiceSysPrivate::setStatus(DWORD state)
{
if (!available())
return;
status.dwCurrentState = state;
pSetServiceStatus(serviceStatus, &status);
}
void QtServiceSysPrivate::setServiceFlags(QtServiceBase::ServiceFlags flags)
{
if (!available())
return;
status.dwControlsAccepted = serviceFlags(flags);
pSetServiceStatus(serviceStatus, &status);
}
DWORD QtServiceSysPrivate::serviceFlags(QtServiceBase::ServiceFlags flags) const
{
DWORD control = 0;
if (flags & QtServiceBase::CanBeSuspended)
control |= SERVICE_ACCEPT_PAUSE_CONTINUE;
if (!(flags & QtServiceBase::CannotBeStopped))
control |= SERVICE_ACCEPT_STOP;
if (flags & QtServiceBase::NeedsStopOnShutdown)
control |= SERVICE_ACCEPT_SHUTDOWN;
return control;
}
#include "qtservice_win.moc"
class HandlerThread : public QThread
{
public:
HandlerThread()
: success(true), console(false), QThread()
{}
bool calledOk() { return success; }
bool runningAsConsole() { return console; }
protected:
bool success, console;
void run()
{
SERVICE_TABLE_ENTRYW st [2];
st[0].lpServiceName = (wchar_t*)QtServiceBase::instance()->serviceName().utf16();
st[0].lpServiceProc = QtServiceSysPrivate::serviceMain;
st[1].lpServiceName = 0;
st[1].lpServiceProc = 0;
success = (pStartServiceCtrlDispatcher(st) != 0); // should block
if (!success) {
if (GetLastError() == ERROR_FAILED_SERVICE_CONTROLLER_CONNECT) {
// Means we're started from console, not from service mgr
// start() will ask the mgr to start another instance of us as a service instead
console = true;
}
else {
QtServiceBase::instance()->logMessage(QString("The Service failed to start [%1]").arg(qt_error_string(GetLastError())), QtServiceBase::Error);
}
QtServiceSysPrivate::instance->startSemaphore.release(); // let start() continue, since serviceMain won't be doing it
}
}
};
/*
Ignore WM_ENDSESSION system events, since they make the Qt kernel quit
*/
#if QT_VERSION >= 0x050000
class QtServiceAppEventFilter : public QAbstractNativeEventFilter
{
public:
QtServiceAppEventFilter() {}
bool nativeEventFilter(const QByteArray &eventType, void *message, long *result);
};
bool QtServiceAppEventFilter::nativeEventFilter(const QByteArray &, void *message, long *result)
{
MSG *winMessage = (MSG*)message;
if (winMessage->message == WM_ENDSESSION && (winMessage->lParam & ENDSESSION_LOGOFF)) {
*result = TRUE;
return true;
}
return false;
}
Q_GLOBAL_STATIC(QtServiceAppEventFilter, qtServiceAppEventFilter)
#else
bool myEventFilter(void* message, long* result)
{
MSG* msg = reinterpret_cast<MSG*>(message);
if (!msg || (msg->message != WM_ENDSESSION) || !(msg->lParam & ENDSESSION_LOGOFF))
return QtServiceSysPrivate::nextFilter ? QtServiceSysPrivate::nextFilter(message, result) : false;
if (QtServiceSysPrivate::nextFilter)
QtServiceSysPrivate::nextFilter(message, result);
if (result)
*result = TRUE;
return true;
}
#endif
/* There are three ways we can be started:
- By a service controller (e.g. the Services control panel), with
no (service-specific) arguments. ServiceBase::exec() will then call
start() below, and the service will start.
- From the console, but with no (service-specific) arguments. This
means we should ask a controller to start the service (i.e. another
instance of this executable), and then just terminate. We discover
this case (as different from the above) by the fact that
StartServiceCtrlDispatcher will return an error, instead of blocking.
- From the console, with -e(xec) argument. ServiceBase::exec() will
then call ServiceBasePrivate::exec(), which calls
ServiceBasePrivate::run(), which runs the application as a normal
program.
*/
bool QtServiceBasePrivate::start()
{
sysInit();
if (!winServiceInit())
return false;
// Since StartServiceCtrlDispatcher() blocks waiting for service
// control events, we need to call it in another thread, so that
// the main thread can run the QApplication event loop.
HandlerThread* ht = new HandlerThread();
ht->start();
QtServiceSysPrivate* sys = QtServiceSysPrivate::instance;
// Wait until service args have been received by serviceMain.
// If Windows doesn't call serviceMain (or
// StartServiceControlDispatcher doesn't return an error) within
// a timeout of 20 secs, something is very wrong; give up
if (!sys->startSemaphore.tryAcquire(1, 20000))
return false;
if (!ht->calledOk()) {
if (ht->runningAsConsole())
return controller.start(args.mid(1));
else
return false;
}
int argc = sys->serviceArgs.size();
QVector<char *> argv(argc);
QList<QByteArray> argvData;
for (int i = 0; i < argc; ++i)
argvData.append(sys->serviceArgs.at(i).toLocal8Bit());
for (int i = 0; i < argc; ++i)
argv[i] = argvData[i].data();
q_ptr->createApplication(argc, argv.data());
QCoreApplication *app = QCoreApplication::instance();
if (!app)
return false;
#if QT_VERSION >= 0x050000
QAbstractEventDispatcher::instance()->installNativeEventFilter(qtServiceAppEventFilter());
#else
QtServiceSysPrivate::nextFilter = app->setEventFilter(myEventFilter);
#endif
sys->controllerHandler = new QtServiceControllerHandler(sys);
sys->startSemaphore2.release(); // let serviceMain continue (and end)
sys->status.dwWin32ExitCode = q_ptr->executeApplication();
sys->setStatus(SERVICE_STOPPED);
if (ht->isRunning())
ht->wait(1000); // let the handler thread finish
delete sys->controllerHandler;
sys->controllerHandler = 0;
if (ht->isFinished())
delete ht;
delete app;
sysCleanup();
return true;
}
bool QtServiceBasePrivate::install(const QString &account, const QString &password)
{
bool result = false;
if (!winServiceInit())
return result;
// Open the Service Control Manager
SC_HANDLE hSCM = pOpenSCManager(0, 0, SC_MANAGER_ALL_ACCESS);
if (hSCM) {
QString acc = account;
DWORD dwStartType = startupType == QtServiceController::AutoStartup ? SERVICE_AUTO_START : SERVICE_DEMAND_START;
DWORD dwServiceType = SERVICE_WIN32_OWN_PROCESS;
wchar_t *act = 0;
wchar_t *pwd = 0;
if (!acc.isEmpty()) {
// The act string must contain a string of the format "Domain\UserName",
// so if only a username was specified without a domain, default to the local machine domain.
if (!acc.contains(QChar('\\'))) {
acc.prepend(QLatin1String(".\\"));
}
if (!acc.endsWith(QLatin1String("\\LocalSystem")))
act = (wchar_t*)acc.utf16();
}
if (!password.isEmpty() && act) {
pwd = (wchar_t*)password.utf16();
}
// Only set INTERACTIVE if act is LocalSystem. (and act should be 0 if it is LocalSystem).
if (!act) dwServiceType |= SERVICE_INTERACTIVE_PROCESS;
// Create the service
SC_HANDLE hService = pCreateService(hSCM, (wchar_t *)controller.serviceName().utf16(),
(wchar_t *)controller.serviceName().utf16(),
SERVICE_ALL_ACCESS,
dwServiceType, // QObject::inherits ( const char * className ) for no inter active ????
dwStartType, SERVICE_ERROR_NORMAL, (wchar_t *)filePath().utf16(),
0, 0, 0,
act, pwd);
if (hService) {
result = true;
if (!serviceDescription.isEmpty()) {
SERVICE_DESCRIPTION sdesc;
sdesc.lpDescription = (wchar_t *)serviceDescription.utf16();
pChangeServiceConfig2(hService, SERVICE_CONFIG_DESCRIPTION, &sdesc);
}
pCloseServiceHandle(hService);
}
pCloseServiceHandle(hSCM);
}
return result;
}
QString QtServiceBasePrivate::filePath() const
{
wchar_t path[_MAX_PATH];
::GetModuleFileNameW( 0, path, sizeof(path) );
return QString::fromUtf16((unsigned short*)path);
}
bool QtServiceBasePrivate::sysInit()
{
sysd = new QtServiceSysPrivate();
sysd->serviceStatus = 0;
sysd->status.dwServiceType = SERVICE_WIN32_OWN_PROCESS|SERVICE_INTERACTIVE_PROCESS;
sysd->status.dwCurrentState = SERVICE_STOPPED;
sysd->status.dwControlsAccepted = sysd->serviceFlags(serviceFlags);
sysd->status.dwWin32ExitCode = NO_ERROR;
sysd->status.dwServiceSpecificExitCode = 0;
sysd->status.dwCheckPoint = 0;
sysd->status.dwWaitHint = 0;
return true;
}
void QtServiceBasePrivate::sysSetPath()
{
}
void QtServiceBasePrivate::sysCleanup()
{
if (sysd) {
delete sysd;
sysd = 0;
}
}
void QtServiceBase::setServiceFlags(QtServiceBase::ServiceFlags flags)
{
if (d_ptr->serviceFlags == flags)
return;
d_ptr->serviceFlags = flags;
if (d_ptr->sysd)
d_ptr->sysd->setServiceFlags(flags);
}

View file

@ -0,0 +1,92 @@
/****************************************************************************
**
** Copyright (C) 2013 Digia Plc and/or its subsidiary(-ies).
** Contact: http://www.qt-project.org/legal
**
** This file is part of the Qt Solutions component.
**
** $QT_BEGIN_LICENSE:BSD$
** You may use this file under the terms of the BSD license as follows:
**
** "Redistribution and use in source and binary forms, with or without
** modification, are permitted provided that the following conditions are
** met:
** * Redistributions of source code must retain the above copyright
** notice, this list of conditions and the following disclaimer.
** * 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.
** * Neither the name of Digia Plc and its Subsidiary(-ies) nor the names
** of its contributors may be used to endorse or promote products derived
** from this software without specific prior written permission.
**
**
** THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
** "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
** LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
** A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
** OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
** SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
** LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
** DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
** THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
** (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
** OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE."
**
** $QT_END_LICENSE$
**
****************************************************************************/
#include "qtunixserversocket.h"
#include <sys/types.h>
#include <sys/un.h>
#include <sys/socket.h>
#include <string.h>
#include <unistd.h>
#ifndef SUN_LEN
#define SUN_LEN(ptr) ((size_t)(((struct sockaddr_un *) 0)->sun_path) \
+strlen ((ptr)->sun_path))
#endif
QtUnixServerSocket::QtUnixServerSocket(const QString &path, QObject *parent)
: QTcpServer(parent)
{
setPath(path);
}
QtUnixServerSocket::QtUnixServerSocket(QObject *parent)
: QTcpServer(parent)
{
}
void QtUnixServerSocket::setPath(const QString &path)
{
path_.clear();
int sock = ::socket(PF_UNIX, SOCK_STREAM, 0);
if (sock != -1) {
struct sockaddr_un addr;
::memset(&addr, 0, sizeof(struct sockaddr_un));
addr.sun_family = AF_UNIX;
::unlink(path.toLatin1().constData()); // ### This might need to be changed
unsigned int pathlen = strlen(path.toLatin1().constData());
if (pathlen > sizeof(addr.sun_path)) pathlen = sizeof(addr.sun_path);
::memcpy(addr.sun_path, path.toLatin1().constData(), pathlen);
if ((::bind(sock, (struct sockaddr *)&addr, SUN_LEN(&addr)) != -1) &&
(::listen(sock, 5) != -1)) {
setSocketDescriptor(sock);
path_ = path;
}
}
}
void QtUnixServerSocket::close()
{
QTcpServer::close();
if (!path_.isEmpty()) {
::unlink(path_.toLatin1().constData());
path_.clear();
}
}

View file

@ -0,0 +1,61 @@
/****************************************************************************
**
** Copyright (C) 2013 Digia Plc and/or its subsidiary(-ies).
** Contact: http://www.qt-project.org/legal
**
** This file is part of the Qt Solutions component.
**
** $QT_BEGIN_LICENSE:BSD$
** You may use this file under the terms of the BSD license as follows:
**
** "Redistribution and use in source and binary forms, with or without
** modification, are permitted provided that the following conditions are
** met:
** * Redistributions of source code must retain the above copyright
** notice, this list of conditions and the following disclaimer.
** * 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.
** * Neither the name of Digia Plc and its Subsidiary(-ies) nor the names
** of its contributors may be used to endorse or promote products derived
** from this software without specific prior written permission.
**
**
** THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
** "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
** LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
** A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
** OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
** SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
** LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
** DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
** THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
** (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
** OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE."
**
** $QT_END_LICENSE$
**
****************************************************************************/
#ifndef QTUNIXSERVERSOCKET_H
#define QTUNIXSERVERSOCKET_H
#include <QTcpServer>
class QtUnixServerSocket : public QTcpServer
{
Q_OBJECT
public:
QtUnixServerSocket(const QString &path, QObject *parent = 0);
QtUnixServerSocket(QObject *parent = 0);
void setPath(const QString &path);
void close();
private:
QString path_;
};
#endif

View file

@ -0,0 +1,78 @@
/****************************************************************************
**
** Copyright (C) 2013 Digia Plc and/or its subsidiary(-ies).
** Contact: http://www.qt-project.org/legal
**
** This file is part of the Qt Solutions component.
**
** $QT_BEGIN_LICENSE:BSD$
** You may use this file under the terms of the BSD license as follows:
**
** "Redistribution and use in source and binary forms, with or without
** modification, are permitted provided that the following conditions are
** met:
** * Redistributions of source code must retain the above copyright
** notice, this list of conditions and the following disclaimer.
** * 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.
** * Neither the name of Digia Plc and its Subsidiary(-ies) nor the names
** of its contributors may be used to endorse or promote products derived
** from this software without specific prior written permission.
**
**
** THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
** "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
** LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
** A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
** OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
** SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
** LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
** DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
** THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
** (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
** OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE."
**
** $QT_END_LICENSE$
**
****************************************************************************/
#include "qtunixsocket.h"
#include <sys/types.h>
#include <sys/un.h>
#include <sys/socket.h>
#include <string.h>
#include <unistd.h>
#ifndef SUN_LEN
#define SUN_LEN(ptr) ((size_t)(((struct sockaddr_un *) 0)->sun_path) \
+strlen ((ptr)->sun_path))
#endif
QtUnixSocket::QtUnixSocket(QObject *parent)
: QTcpSocket(parent)
{
}
bool QtUnixSocket::connectTo(const QString &path)
{
bool ret = false;
int sock = ::socket(PF_UNIX, SOCK_STREAM, 0);
if (sock != -1) {
struct sockaddr_un addr;
::memset(&addr, 0, sizeof(struct sockaddr_un));
addr.sun_family = AF_UNIX;
size_t pathlen = strlen(path.toLatin1().constData());
pathlen = qMin(pathlen, sizeof(addr.sun_path));
::memcpy(addr.sun_path, path.toLatin1().constData(), pathlen);
int err = ::connect(sock, (struct sockaddr *)&addr, SUN_LEN(&addr));
if (err != -1) {
setSocketDescriptor(sock);
ret = true;
} else {
::close(sock);
}
}
return ret;
}

View file

@ -0,0 +1,55 @@
/****************************************************************************
**
** Copyright (C) 2013 Digia Plc and/or its subsidiary(-ies).
** Contact: http://www.qt-project.org/legal
**
** This file is part of the Qt Solutions component.
**
** $QT_BEGIN_LICENSE:BSD$
** You may use this file under the terms of the BSD license as follows:
**
** "Redistribution and use in source and binary forms, with or without
** modification, are permitted provided that the following conditions are
** met:
** * Redistributions of source code must retain the above copyright
** notice, this list of conditions and the following disclaimer.
** * 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.
** * Neither the name of Digia Plc and its Subsidiary(-ies) nor the names
** of its contributors may be used to endorse or promote products derived
** from this software without specific prior written permission.
**
**
** THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
** "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
** LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
** A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
** OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
** SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
** LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
** DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
** THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
** (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
** OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE."
**
** $QT_END_LICENSE$
**
****************************************************************************/
#ifndef QTUNIXSOCKET_H
#define QTUNIXSOCKET_H
#include <QTcpSocket>
class QtUnixSocket : public QTcpSocket
{
Q_OBJECT
public:
QtUnixSocket(QObject *parent = 0);
bool connectTo(const QString &path);
};
#endif