diff --git a/.cmake.conf b/.cmake.conf new file mode 100644 index 0000000..16ecb3d --- /dev/null +++ b/.cmake.conf @@ -0,0 +1 @@ +add_compile_definitions(APPCONTROLLER_VERSION="1.0.0") diff --git a/CMakeLists.txt b/CMakeLists.txt new file mode 100644 index 0000000..7c96b7c --- /dev/null +++ b/CMakeLists.txt @@ -0,0 +1,26 @@ +cmake_minimum_required(VERSION 3.16) +include(.cmake.conf) + +project(appcontroller LANGUAGES CXX) + +set(CMAKE_CXX_STANDARD 17) +set(CMAKE_CXX_STANDARD_REQUIRED ON) + +set(CMAKE_AUTOMOC ON) + +find_package(Qt6 COMPONENTS Network) + +qt6_add_executable(appcontroller + main.cpp + process.cpp + portlist.cpp + perfprocesshandler.cpp +) + +target_link_libraries(appcontroller PUBLIC + Qt::Network +) + +install(TARGETS appcontroller + RUNTIME DESTINATION bin +) diff --git a/appcontroller.pro b/appcontroller.pro deleted file mode 100644 index 97b208e..0000000 --- a/appcontroller.pro +++ /dev/null @@ -1,40 +0,0 @@ -QT-=gui -QT+=network -HEADERS=\ - process.h \ - portlist.h \ - perfprocesshandler.h - -SOURCES=\ - main.cpp \ - process.cpp \ - portlist.cpp \ - perfprocesshandler.cpp - -android { - target.path = $$[INSTALL_ROOT]/system/bin -} else { - target.path = $$[INSTALL_ROOT]/usr/bin -} -INSTALLS+=target - -# Find out git hash -exists(.git) { - unix:system(which git):HAS_GIT=TRUE - win32:system(where git.exe):HAS_GIT=TRUE - contains(HAS_GIT, TRUE) { - GIT_HASH=$$system(git log -1 --format=%H) - !system(git diff-index --quiet HEAD): GIT_HASH="$$GIT_HASH-dirty" - GIT_VERSION=$$system(git describe --tags --exact-match) - isEmpty(GIT_VERSION) : GIT_VERSION="unknown" - } -} else { - GIT_HASH="unknown" - GIT_VERSION="unknown" -} - -isEmpty(GIT_VERSION) : error("No suitable tag found") -isEmpty(GIT_HASH) : error("No hash available") - -DEFINES+="GIT_HASH=\\\"$$GIT_HASH\\\"" -DEFINES+="GIT_VERSION=\\\"$$GIT_VERSION\\\"" diff --git a/main.cpp b/main.cpp index fb52564..6f05471 100644 --- a/main.cpp +++ b/main.cpp @@ -1,25 +1,30 @@ -/**************************************************************************** +/****************************************************************************** ** -** Copyright (C) 2014 Digia Plc -** All rights reserved. -** For any questions to Digia, please use contact form at http://www.qt.io +** Copyright (C) 2016 The Qt Company Ltd. +** Contact: http://www.qt.io/licensing/ ** -** This file is part of Qt Enterprise Embedded. +** This file is part of Qt for Device Creation. ** -** Licensees holding valid Qt Enterprise licenses may use this file in -** accordance with the Qt Enterprise License Agreement provided with the +** $QT_BEGIN_LICENSE:COMM$ +** +** Commercial License Usage +** Licensees holding valid commercial Qt licenses may use this file in +** accordance with the commercial license agreement provided with the ** Software or, alternatively, in accordance with the terms contained in -** a written agreement between you and Digia. +** a written agreement between you and The Qt Company. For licensing terms +** and conditions see http://www.qt.io/terms-conditions. For further +** information use the contact form at http://www.qt.io/contact-us. ** -** If you have questions regarding the use of this file, please use -** contact form at http://www.qt.io +** $QT_END_LICENSE$ ** -****************************************************************************/ +******************************************************************************/ #include "process.h" #include "portlist.h" #include "perfprocesshandler.h" #include +#include +#include #include #include #include @@ -34,12 +39,9 @@ #include #define PID_FILE "/data/user/.appcontroller" +#define FEATURES "restart perf eglresize qmldebugservices explicitdebugports" -#ifdef Q_OS_ANDROID - #define B2QT_PREFIX "/data/user/b2qt" -#else - #define B2QT_PREFIX "/usr/bin/b2qt" -#endif +#define B2QT_PREFIX "/usr/bin/b2qt" static int serverSocket = -1; @@ -47,20 +49,33 @@ static const char socketPath[] = "#Boot2Qt_appcontroller"; static void usage() { - printf("appcontroller [--debug-gdb] [--debug-qml] [--port-range ] [--stop] [--launch] [--show-platfrom] [--make-default] [--remove-default] [--print-debug] [--version] [--detach] [executable] [arguments]\n" + printf("appcontroller [--debug-gdb] [--debug-gdb-port ]" + " [--debug-qml] [--debug-qml-port ] [--qml-debug-services ]" + " [--profile-perf ] [--port-range ] [--stop] [--launch] [--show-platfrom]" + " [--make-default] [--no-wrapper] [--remove-default] [--print-debug] [--version] [--detach]" + " [executable] [arguments]\n" "\n" - "--port-range Port range to use for debugging connections\n" - "--debug-gdb Start GDB debugging\n" - "--debug-qml Start QML debugging\n" - "--stop Stop already running application\n" - "--launch Start application without stopping already running application\n" - "--show-platform Show platform information\n" - "--make-default Make this application the default on boot\n" - "--remove-default Restore the default application\n" - "--print-debug Print debug messages to stdout on Android\n" - "--version Print version information\n" - "--detach Start application as usual, then go into background\n" - "--help, -h, -help Show this help\n" + "--port-range Port range to use for debugging connections\n" + "--debug-gdb Start GDB debugging\n" + "--debug-gdb-port Port to be used for GDB debugging\n" + "--debug-qml Start QML debugging or profiling\n" + "--debug-qml-port Port to be used for QML debugging\n" + "--qml-debug-services Specify services to use for QML debugging/profiling\n" + "--profile-perf Start perf profiling\n" + "--stop Stop already running application\n" + "--stop-for-restart Stop already running application and prepare to\n" + " restart it\n" + "--launch Start application without stopping already running\n" + " application\n" + "--show-platform Show platform information\n" + "--make-default Make this application the default on boot\n" + "--no-wrapper Start the application without the configured wrapper command\n" + "--remove-default Restore the default application\n" + "--version Print version information\n" + "--detach Start application as usual, then go into background\n" + "--restart Restart the current running application or an\n" + " application stopped with --stop-for-restart\n" + "--help, -h, -help Show this help\n" ); } @@ -72,28 +87,39 @@ static void setupAddressStruct(struct sockaddr_un &address) address.sun_path[0] = 0; } -static int connectSocket() +static int connectSocket(const QByteArray &command) { - int create_socket; - struct sockaddr_un address; + int fd = 0; + struct sockaddr_un address; - if ((create_socket = socket(AF_UNIX, SOCK_STREAM, 0)) < 0) { - perror("Could not create socket"); - return -1; - } + if ((fd = socket(AF_UNIX, SOCK_STREAM, 0)) < 0) { + perror("Could not create socket"); + return -1; + } - if (fcntl(create_socket, F_SETFD, FD_CLOEXEC) == -1) { - perror("Unable to set CLOEXEC"); - } + if (fcntl(fd, F_SETFD, FD_CLOEXEC) == -1) + perror("Unable to set CLOEXEC"); - setupAddressStruct(address); + setupAddressStruct(address); - if (connect(create_socket, (struct sockaddr *) &address, sizeof (address)) != 0) { - perror("Could not connect"); - return -1; - } - close(create_socket); - return 0; + if (connect(fd, (struct sockaddr *) &address, sizeof (address)) != 0) { + perror("Could not connect"); + return -1; + } + + QLocalSocket localSocket; + if (!localSocket.setSocketDescriptor(fd)) { + fprintf(stderr, "Unable to initialize local socket from descriptor.\n"); + close(fd); + return -1; + } + + if (localSocket.write(command) != command.size()) { + fprintf(stderr, "Could not send command"); + return -1; + } + localSocket.waitForBytesWritten(); + return 0; } static int createServerSocket() @@ -121,7 +147,7 @@ static int createServerSocket() return -1; } - if (connectSocket() != 0) { + if (connectSocket("stop") != 0) { fprintf(stderr, "Failed to connect to process\n"); } @@ -142,7 +168,17 @@ static int createServerSocket() static void stop() { - connectSocket(); + connectSocket("stop"); +} + +static void restart() +{ + connectSocket("restart"); +} + +static void stopForRestart() +{ + connectSocket("stopForRestart"); } static int openServer(QTcpServer *s, Utils::PortList &range) @@ -160,21 +196,37 @@ static int findFirstFreePort(Utils::PortList &range) return openServer(&s, range); } -static Config parseConfigFile() +bool readEnvs(Config *config, const QString &envFile) { - Config config; - config.base = config.platform = QLatin1String("unknown"); - config.debugInterface = Config::LocalDebugInterface; + QFile f(envFile); + if (!f.open(QFile::ReadOnly)) { + fprintf(stderr, "Could not read environment file: %s\n", qPrintable(envFile)); + return false; + } -#ifdef Q_OS_ANDROID - QFile f("/system/bin/appcontroller.conf"); -#else - QFile f("/etc/appcontroller.conf"); -#endif + while (!f.atEnd()) { + const auto line = f.readLine().simplified(); + if (!line.startsWith("#")) { + const auto index = line.indexOf('='); + const auto key = line.left(index); + auto value = line.mid(index + 1); + if (value.startsWith('"') && value.endsWith('"')) + value = value.mid(1, value.length() - 2); + if (!key.isEmpty()) + config->env[key] = value; + } + } + f.close(); + return true; +} + +bool parseConfigFile(Config *config, const QString &fileName) +{ + QFile f(fileName); if (!f.open(QFile::ReadOnly)) { - fprintf(stderr, "Could not read config file.\n"); - return config; + fprintf(stderr, "Could not read config file: %s\n", qPrintable(fileName)); + return false; } while (!f.atEnd()) { @@ -185,37 +237,84 @@ static Config parseConfigFile() if (index < 2) { // ignore } else - config.env[sub.left(index)] = sub.mid(index+1); + config->env[sub.left(index)] = sub.mid(index+1); } else if (line.startsWith("append=")) { - config.args += line.mid(7).simplified(); + config->args += line.mid(7).simplified(); } else if (line.startsWith("base=")) { - config.base = line.mid(5).simplified(); + config->base = line.mid(5).simplified(); } else if (line.startsWith("platform=")) { - config.platform = line.mid(9).simplified(); + config->platform = line.mid(9).simplified(); } else if (line.startsWith("debugInterface=")) { const QString value = line.mid(15).simplified(); if (value == "local") - config.debugInterface = Config::LocalDebugInterface; + config->debugInterface = Config::LocalDebugInterface; else if (value == "public") - config.debugInterface = Config::PublicDebugInterface; + config->debugInterface = Config::PublicDebugInterface; else qWarning() << "Unkonwn value for debuginterface:" << value; + } else if (line.startsWith("environmentFile=")) { + QString envFile = line.mid(16).simplified(); + if (!envFile.isEmpty()) + readEnvs(config, envFile); + } else if (line.startsWith("user=")) { + config->user = line.mid(5).simplified(); + } else if (line.startsWith("group=")) { + config->group = line.mid(6).simplified(); + } else if (line.startsWith("wrapperCmd=")) { + config->wrapperCmd = line.mid(11).simplified(); + } else if (line.startsWith("clearEnv=")) { + config->clearEnv.append(line.mid(9).simplified()); } + } f.close(); - return config; + return true; +} + +bool parseConfigFileDirectory(Config *config, const QString &dirName) +{ + QDir d(dirName); + if (d.exists()) { + foreach (const QString &fileName, d.entryList(QDir::Files)) { + const QString file(d.absoluteFilePath(fileName)); + + if (!parseConfigFile(config, file)) { + fprintf(stderr, "Failed to parse config file: %s\n", qPrintable(file)); + return false; + } + } + } + return true; } static bool removeDefault() { - if (QFile::exists(B2QT_PREFIX)) { + QFileInfo fi(B2QT_PREFIX); + + if (fi.isSymLink()) { if (!QFile::remove(B2QT_PREFIX)) { fprintf(stderr, "Could not remove default application.\n"); return false; } sync(); + return true; } - return true; + + if (fi.isDir()) { + fprintf(stderr, "Could not remove default application because '" B2QT_PREFIX "' is a directory. It should be a symlink.\n"); + return false; + } + + if (fi.isFile()) { + fprintf(stderr, "Could not remove default application because '" B2QT_PREFIX "' is a file. It should be a symlink.\n"); + return false; + } + + if (!fi.exists()) { + return true; + } + + return false; } static bool makeDefault(const QString &filepath) @@ -271,8 +370,10 @@ int main(int argc, char **argv) QStringList defaultArgs; quint16 gdbDebugPort = 0; + quint16 qmlDebugPort = 0; bool useGDB = false; bool useQML = false; + QString qmlDebugServices; QStringList perfParams; bool fireAndForget = false; bool detach = false; @@ -283,7 +384,9 @@ int main(int argc, char **argv) return 1; } - Config config = parseConfigFile(); + Config config; + if (!parseConfigFile(&config, "/etc/appcontroller.conf")) + fprintf(stderr, "Failed to parse config file.\n"); while (!args.isEmpty()) { const QString arg(args.takeFirst()); @@ -302,14 +405,41 @@ int main(int argc, char **argv) useGDB = true; setpgid(0,0); // must be called before setsid() setsid(); + } else if (arg == "--debug-gdb-port") { + if (args.isEmpty()) { + fprintf(stderr, "--debug-gdb-port requires a port number\n"); + return 1; + } + gdbDebugPort = args.takeFirst().toUInt(); + if (gdbDebugPort < 1) { + fprintf(stderr, "--debug-gdb-port has invalid port number\n"); + return 1; + } } else if (arg == "--debug-qml") { useQML = true; + } else if (arg == "--debug-qml-port") { + if (args.isEmpty()) { + fprintf(stderr, "--debug-qml-port requires a port number\n"); + return 1; + } + qmlDebugPort = args.takeFirst().toUInt(); + if (qmlDebugPort < 1) { + fprintf(stderr, "--debug-qml-port has invalid port number\n"); + return 1; + } + } else if (arg == "--qml-debug-services") { + if (args.isEmpty()) { + fprintf(stderr, "--qml-debug-services requires a list of comma-separated service " + "names.\n"); + return 1; + } + qmlDebugServices = args.takeFirst(); } else if (arg == "--profile-perf") { if (args.isEmpty()) { - fprintf(stderr, "--profile-perf requires comma-separated list of parameters that " - "get passed to \"perf record\". Arguments \"-o -\" are " - "automatically appended to capture the output as stream. " - "Escape commas by doubling them."); + fprintf(stderr, "--profile-perf requires comma-separated list of parameters that\n" + "get passed to \"perf record\". Arguments \"-o -\" are\n" + "automatically appended to capture the output as stream.\n" + "Escape commas by doubling them.\n"); return 1; } perfParams = extractPerfParams(args.takeFirst()); @@ -331,18 +461,24 @@ int main(int argc, char **argv) if (!makeDefault(args.takeFirst())) return 1; return 0; + } else if (arg == "--no-wrapper") { + config.wrapperCmd.clear(); } else if (arg == "--remove-default") { if (removeDefault()) return 0; else return 1; - } else if (arg == "--print-debug") { - config.flags |= Config::PrintDebugMessages; } else if (arg == "--version") { - printf("Appcontroller version: " GIT_VERSION "\nGit revision: " GIT_HASH "\n"); + printf("Appcontroller version: " APPCONTROLLER_VERSION "\nFeatures: " FEATURES "\n"); return 0; } else if (arg == "--detach") { detach = true; + } else if (arg == "--restart") { + restart(); + return 0; + } else if (arg == "--stop-for-restart") { + stopForRestart(); + return 0; } else if (arg == "--help" || arg == "-help" || arg == "-h") { usage(); return 0; @@ -357,17 +493,25 @@ int main(int argc, char **argv) return 1; } - if ((useGDB || useQML) && !range.hasMore()) { - fprintf(stderr, "--port-range is mandatory\n"); + if (useGDB && !(gdbDebugPort || range.hasMore())) { + fprintf(stderr, "--debug-gdb requires --port-range or --debug-gdb-port\n"); + return 1; + } + + if (useQML && !(qmlDebugPort || range.hasMore())) { + fprintf(stderr, "--debug-qml requires --port-range or --debug-qml-port\n"); return 1; } if (detach && (useGDB || useQML)) { - fprintf(stderr, "Detached debugging not possible. --detach and one of --useGDB, --useQML must not be used together.\n"); + fprintf(stderr, "Detached debugging not possible. --detach and one of --debug-gdb, --debug-qml must not be used together.\n"); return 1; } - if (useGDB) { + if (gdbDebugPort && !useGDB) + gdbDebugPort = 0; + + if (useGDB && !gdbDebugPort) { int port = findFirstFreePort(range); if (port < 0) { fprintf(stderr, "Could not find an unused port in range\n"); @@ -376,13 +520,18 @@ int main(int argc, char **argv) gdbDebugPort = port; } if (useQML) { - int port = findFirstFreePort(range); - if (port < 0) { - fprintf(stderr, "Could not find an unused port in range\n"); - return 1; + if (!qmlDebugPort) { + int port = findFirstFreePort(range); + if (port < 0) { + fprintf(stderr, "Could not find an unused port in range\n"); + return 1; + } + qmlDebugPort = port; } - defaultArgs.push_front("-qmljsdebugger=port:" + QString::number(port) + ",block"); - printf("QML Debugger: Going to wait for connection on port %d...\n", port); + defaultArgs.push_front("-qmljsdebugger=port:" + QString::number(qmlDebugPort) + ",block" + + (qmlDebugServices.isEmpty() ? + "" : (",services:" + qmlDebugServices))); + printf("QML Debugger: Going to wait for connection on port %d...\n", qmlDebugPort); } defaultArgs.push_front(args.takeFirst()); @@ -415,7 +564,8 @@ int main(int argc, char **argv) } setsid(); - chdir("/"); + if (chdir("/") != 0) + return -1; signal(SIGHUP, SIG_IGN); // child @@ -447,7 +597,7 @@ int main(int argc, char **argv) QStringList allArgs; allArgs << QLatin1String("perf") << QLatin1String("record") << perfParams << QLatin1String("-o") << QLatin1String("-") - << QLatin1String("--") << defaultArgs.join(QLatin1Char(' ')); + << QLatin1String("--") << defaultArgs; PerfProcessHandler *server = new PerfProcessHandler(&process, allArgs); int port = openServer(server->server(), range); diff --git a/perfprocesshandler.cpp b/perfprocesshandler.cpp index 9b609ab..2f6000b 100644 --- a/perfprocesshandler.cpp +++ b/perfprocesshandler.cpp @@ -1,20 +1,23 @@ -/**************************************************************************** +/****************************************************************************** ** -** Copyright (C) 2015 The Qt Company Ltd -** All rights reserved. -** For any questions to The Qt Company, please use contact form at http://www.qt.io/contact-us +** Copyright (C) 2016 The Qt Company Ltd. +** Contact: http://www.qt.io/licensing/ ** -** This file is part of QtEnterprise Embedded. +** This file is part of Qt for Device Creation. ** -** Licensees holding valid Qt Enterprise licenses may use this file in -** accordance with the Qt Enterprise License Agreement provided with the +** $QT_BEGIN_LICENSE:COMM$ +** +** Commercial License Usage +** Licensees holding valid commercial Qt licenses may use this file in +** accordance with the commercial license agreement provided with the ** Software or, alternatively, in accordance with the terms contained in -** a written agreement between you and The Qt Company. +** a written agreement between you and The Qt Company. For licensing terms +** and conditions see http://www.qt.io/terms-conditions. For further +** information use the contact form at http://www.qt.io/contact-us. ** -** If you have questions regarding the use of this file, please use -** contact form at http://www.qt.io/contact-us +** $QT_END_LICENSE$ ** -****************************************************************************/ +******************************************************************************/ #include "perfprocesshandler.h" #include diff --git a/perfprocesshandler.h b/perfprocesshandler.h index f10e8a6..b7867ce 100644 --- a/perfprocesshandler.h +++ b/perfprocesshandler.h @@ -1,20 +1,23 @@ -/**************************************************************************** +/****************************************************************************** ** -** Copyright (C) 2015 The Qt Company Ltd -** All rights reserved. -** For any questions to The Qt Company, please use contact form at http://www.qt.io/contact-us +** Copyright (C) 2016 The Qt Company Ltd. +** Contact: http://www.qt.io/licensing/ ** -** This file is part of QtEnterprise Embedded. +** This file is part of Qt for Device Creation. ** -** Licensees holding valid Qt Enterprise licenses may use this file in -** accordance with the Qt Enterprise License Agreement provided with the +** $QT_BEGIN_LICENSE:COMM$ +** +** Commercial License Usage +** Licensees holding valid commercial Qt licenses may use this file in +** accordance with the commercial license agreement provided with the ** Software or, alternatively, in accordance with the terms contained in -** a written agreement between you and The Qt Company. +** a written agreement between you and The Qt Company. For licensing terms +** and conditions see http://www.qt.io/terms-conditions. For further +** information use the contact form at http://www.qt.io/contact-us. ** -** If you have questions regarding the use of this file, please use -** contact form at http://www.qt.io/contact-us +** $QT_END_LICENSE$ ** -****************************************************************************/ +******************************************************************************/ #ifndef PERFPROCESSHANDLER_H #define PERFPROCESSHANDLER_H diff --git a/portlist.cpp b/portlist.cpp index a93814e..6a3a0de 100644 --- a/portlist.cpp +++ b/portlist.cpp @@ -1,20 +1,23 @@ -/**************************************************************************** +/****************************************************************************** ** -** Copyright (C) 2014 Digia Plc -** All rights reserved. -** For any questions to Digia, please use contact form at http://www.qt.io +** Copyright (C) 2016 The Qt Company Ltd. +** Contact: http://www.qt.io/licensing/ ** -** This file is part of Qt Enterprise Embedded. +** This file is part of Qt for Device Creation. ** -** Licensees holding valid Qt Enterprise licenses may use this file in -** accordance with the Qt Enterprise License Agreement provided with the +** $QT_BEGIN_LICENSE:COMM$ +** +** Commercial License Usage +** Licensees holding valid commercial Qt licenses may use this file in +** accordance with the commercial license agreement provided with the ** Software or, alternatively, in accordance with the terms contained in -** a written agreement between you and Digia. +** a written agreement between you and The Qt Company. For licensing terms +** and conditions see http://www.qt.io/terms-conditions. For further +** information use the contact form at http://www.qt.io/contact-us. ** -** If you have questions regarding the use of this file, please use -** contact form at http://www.qt.io +** $QT_END_LICENSE$ ** -****************************************************************************/ +******************************************************************************/ #include "portlist.h" diff --git a/portlist.h b/portlist.h index 171194c..5ad0954 100644 --- a/portlist.h +++ b/portlist.h @@ -1,20 +1,23 @@ -/**************************************************************************** +/****************************************************************************** ** -** Copyright (C) 2014 Digia Plc -** All rights reserved. -** For any questions to Digia, please use contact form at http://www.qt.io +** Copyright (C) 2016 The Qt Company Ltd. +** Contact: http://www.qt.io/licensing/ ** -** This file is part of Qt Enterprise Embedded. +** This file is part of Qt for Device Creation. ** -** Licensees holding valid Qt Enterprise licenses may use this file in -** accordance with the Qt Enterprise License Agreement provided with the +** $QT_BEGIN_LICENSE:COMM$ +** +** Commercial License Usage +** Licensees holding valid commercial Qt licenses may use this file in +** accordance with the commercial license agreement provided with the ** Software or, alternatively, in accordance with the terms contained in -** a written agreement between you and Digia. +** a written agreement between you and The Qt Company. For licensing terms +** and conditions see http://www.qt.io/terms-conditions. For further +** information use the contact form at http://www.qt.io/contact-us. ** -** If you have questions regarding the use of this file, please use -** contact form at http://www.qt.io +** $QT_END_LICENSE$ ** -****************************************************************************/ +******************************************************************************/ #ifndef PORTLIST_H #define PORTLIST_H diff --git a/process.cpp b/process.cpp index 1fe811b..c6fbe2e 100644 --- a/process.cpp +++ b/process.cpp @@ -1,34 +1,42 @@ -/**************************************************************************** +/****************************************************************************** ** -** Copyright (C) 2014 Digia Plc -** All rights reserved. -** For any questions to Digia, please use contact form at http://www.qt.io +** Copyright (C) 2016 The Qt Company Ltd. +** Contact: http://www.qt.io/licensing/ ** -** This file is part of Qt Enterprise Embedded. +** This file is part of Qt for Device Creation. ** -** Licensees holding valid Qt Enterprise licenses may use this file in -** accordance with the Qt Enterprise License Agreement provided with the +** $QT_BEGIN_LICENSE:COMM$ +** +** Commercial License Usage +** Licensees holding valid commercial Qt licenses may use this file in +** accordance with the commercial license agreement provided with the ** Software or, alternatively, in accordance with the terms contained in -** a written agreement between you and Digia. +** a written agreement between you and The Qt Company. For licensing terms +** and conditions see http://www.qt.io/terms-conditions. For further +** information use the contact form at http://www.qt.io/contact-us. ** -** If you have questions regarding the use of this file, please use -** contact form at http://www.qt.io +** $QT_END_LICENSE$ ** -****************************************************************************/ +******************************************************************************/ #include "process.h" #include #include #include #include +#include #include #include #include #include #include #include +#include #include +#include +#include +bool parseConfigFileDirectory(Config *config, const QString &dirName); static int pipefd[2]; static void signalhandler(int) @@ -89,13 +97,13 @@ Process::Process() , mDebuggee(0) , mDebug(false) , mStdoutFd(1) + , mBeingRestarted(false) { mProcess->setProcessChannelMode(QProcess::SeparateChannels); connect(mProcess, &QProcess::readyReadStandardError, this, &Process::readyReadStandardError); connect(mProcess, &QProcess::readyReadStandardOutput, this, &Process::readyReadStandardOutput); connect(mProcess, (void (QProcess::*)(int, QProcess::ExitStatus))&QProcess::finished, this, &Process::finished); - connect(mProcess, (void (QProcess::*)(QProcess::ProcessError))&QProcess::error, this, &Process::error); - connect(mProcess, (void (QProcess::*)(int, QProcess::ExitStatus))&QProcess::finished, qApp, &QCoreApplication::quit); + connect(mProcess, (void (QProcess::*)(QProcess::ProcessError))&QProcess::errorOccurred, this, &Process::errorOccurred); if (pipe2(pipefd, O_CLOEXEC) != 0) qWarning("Could not create pipe"); @@ -129,8 +137,9 @@ void Process::forwardProcessOutput(qintptr fd, const QByteArray &data) fd_set inputFdSet; FD_ZERO(&inputFdSet); FD_SET(pipefd[0], &inputFdSet); - if (select(qMax(fd, pipefd[0]) + 1, &inputFdSet, &outputFdSet, NULL, NULL) > 0 && - !FD_ISSET(pipefd[0], &inputFdSet)) + if (select(qMax(fd, static_cast(pipefd[0])) + 1, + &inputFdSet, &outputFdSet, NULL, NULL) > 0 && + !FD_ISSET(pipefd[0], &inputFdSet)) continue; // else fprintf below will output the appropriate errno } @@ -141,12 +150,8 @@ void Process::forwardProcessOutput(qintptr fd, const QByteArray &data) size -= written; constData += written; } - - if (mConfig.flags.testFlag(Config::PrintDebugMessages)) - qDebug() << data; } - void Process::readyReadStandardOutput() { forwardProcessOutput(mStdoutFd, mProcess->readAllStandardOutput()); @@ -170,7 +175,7 @@ void Process::setDebug() mDebug = true; } -void Process::error(QProcess::ProcessError error) +void Process::errorOccurred(QProcess::ProcessError error) { switch (error) { case QProcess::FailedToStart: @@ -193,7 +198,8 @@ void Process::error(QProcess::ProcessError error) printf("Unknown error\n"); break; } - qApp->quit(); + if (!mBeingRestarted) + qApp->quit(); } void Process::finished(int exitCode, QProcess::ExitStatus exitStatus) @@ -202,46 +208,126 @@ void Process::finished(int exitCode, QProcess::ExitStatus exitStatus) printf("Process exited with exit code %d\n", exitCode); else printf("Process stopped\n"); + if (!mBeingRestarted) { + qDebug() << "quit"; + qApp->quit(); + } } -void Process::startup(QStringList args) +void Process::startup() { -#ifdef Q_OS_ANDROID - QProcessEnvironment pe = interactiveProcessEnvironment(); -#else QProcessEnvironment pe = QProcessEnvironment::systemEnvironment(); -#endif + QStringList args = mStartupArguments; + mBeingRestarted = false; + + Config actualConfig = mConfig; + + // Parse temporary config files + // This needs to be done on every startup because those files are expected to change. + parseConfigFileDirectory(&actualConfig, "/var/lib/b2qt/appcontroller.conf.d"); + parseConfigFileDirectory(&actualConfig, "/tmp/b2qt/appcontroller.conf.d"); + + if (!actualConfig.user.isEmpty()) { + mProcess->setChildProcessModifier([this, actualConfig]() { + if (getuid() != 0) { + fprintf(stderr, "Not running as root, cannot change user\n"); + return; + } + + struct passwd *p; + if ((p = getpwnam(actualConfig.user.toLatin1().constData())) == nullptr) { + fprintf(stderr, "Failed to get user\n"); + return; + } + + if (setgroups(0, nullptr) == -1) + fprintf(stderr, "Failed to clear groups\n"); + + gid_t gid = -1; + QString groupName; + + if (!actualConfig.group.isEmpty()) { + struct group *g; + if ((g = getgrnam(actualConfig.group.toLatin1().constData())) == nullptr) { + fprintf(stderr, "Failed to get group\n"); + return; + } + gid = g->gr_gid; + groupName = g->gr_name; + } else { + struct group *g; + if ((g = getgrgid(p->pw_gid)) == nullptr) { + fprintf(stderr, "Failed to get group\n"); + return; + } + gid = g->gr_gid; + groupName = QString::fromLocal8Bit(g->gr_name); + } + + if (initgroups(actualConfig.user.toLatin1().constData(), gid) == -1) + fprintf(stderr, "Failed to set groups\n"); + + if (setgid(gid) == -1) + fprintf(stderr, "Failed to change group\n"); + + qDebug() << "Changed group to" << groupName << "gid" << gid; + + if (setuid(p->pw_uid) == -1) + printf("Failed to change user\n"); - foreach (const QString &key, mConfig.env.keys()) { + qDebug() << "Changed user to" << p->pw_name << "uid" << p->pw_uid; + + // Useful for debugging + // ::system("id"); + // ::system("env"); + }); + } + + foreach (const QString &key, actualConfig.clearEnv) { + pe.remove(key); + } + + foreach (const QString &key, actualConfig.env.keys()) { if (!pe.contains(key)) { - qDebug() << key << mConfig.env.value(key); - pe.insert(key, mConfig.env.value(key)); + qDebug() << key << actualConfig.env.value(key); + pe.insert(key, actualConfig.env.value(key)); } } - if (!mConfig.base.isEmpty()) - pe.insert(QLatin1String("B2QT_BASE"), mConfig.base); - if (!mConfig.platform.isEmpty()) - pe.insert(QLatin1String("B2QT_PLATFORM"), mConfig.platform); + if (!actualConfig.base.isEmpty()) + pe.insert(QLatin1String("B2QT_BASE"), actualConfig.base); + if (!actualConfig.platform.isEmpty()) + pe.insert(QLatin1String("B2QT_PLATFORM"), actualConfig.platform); - args.append(mConfig.args); + args.append(actualConfig.args); mProcess->setProcessEnvironment(pe); - mBinary = args.first(); - args.removeFirst(); + if (actualConfig.wrapperCmd.isEmpty()) { + mBinary = args.first(); + args.removeFirst(); + } else { + QStringList wrapper = actualConfig.wrapperCmd.split(" "); + mBinary = wrapper.first(); + wrapper.removeFirst(); + args = wrapper + args; + } + qDebug() << mBinary << args; + mProcess->start(mBinary, args); } void Process::start(const QStringList &args) { - startup(args); + mStartupArguments = args; + startup(); } void Process::stop() { if (mProcess->state() == QProcess::QProcess::NotRunning) { printf("No process running\n"); - qApp->exit(); + if (!mBeingRestarted) + qApp->exit(); return; } @@ -250,97 +336,73 @@ void Process::stop() if (kill(mDebuggee, SIGKILL) != 0) perror("Could not kill debugee"); } - if (kill(-getpid(), SIGTERM) != 0) - perror("Could not kill process group"); mProcess->terminate(); if (!mProcess->waitForFinished()) mProcess->kill(); } -void Process::incomingConnection(int i) +void Process::stopForRestart() { - accept(i, NULL, NULL); + printf("Stopping application for restart\n"); + mBeingRestarted = true; stop(); } -void Process::setSocketNotifier(QSocketNotifier *s) -{ - connect(s, &QSocketNotifier::activated, this, &Process::incomingConnection); -} - -void Process::setConfig(const Config &config) -{ - mConfig = config; -} - -void Process::setStdoutFd(qintptr stdoutFd) +void Process::restart() { - mStdoutFd = stdoutFd; + printf("Restarting application\n"); + mBeingRestarted = true; + stop(); + startup(); } -QProcessEnvironment Process::interactiveProcessEnvironment() const +void Process::incomingConnection(int i) { - QProcessEnvironment env; - - QProcess process; - process.start("sh"); - if (!process.waitForStarted(3000)) { - printf("Could not start shell.\n"); - return env; + int fd = accept(i, NULL, NULL); + if (fd < 0 ) { + perror("Could not accept connection"); + stop(); + return; } - process.write("source /system/etc/mkshrc\n"); - process.write("export -p\n"); - process.closeWriteChannel(); - - printf("waiting for process to finish\n"); - if (!process.waitForFinished(1000)) { - printf("did not finish: terminate\n"); - process.terminate(); - if (!process.waitForFinished(1000)) { - printf("did not terminate: kill\n"); - process.kill(); - if (!process.waitForFinished(1000)) { - printf("Could not stop process.\n"); - } - } + QLocalSocket localSocket; + if (!localSocket.setSocketDescriptor(fd)) { + fprintf(stderr, "Could not initialize local socket from descriptor.\n"); + close(fd); + stop(); + return; } - QList list = process.readAllStandardOutput().split('\n'); - if (list.isEmpty()) - printf("Failed to read environment output\n"); - - foreach (QByteArray entry, list) { - if (entry.startsWith("export ")) { - entry = entry.mid(7); - } else if (entry.startsWith("declare -x ")) { - entry = entry.mid(11); - } else { - continue; - } + if (!localSocket.waitForReadyRead()) { + fprintf(stderr, "No command received.\n"); + stop(); // default + return; + } - QByteArray key; - QByteArray value; - int index = entry.indexOf('='); + QByteArray command = localSocket.readAll(); - if (index > 0) { - key = entry.left(index); - value = entry.mid(index + 1); - } else { - key = entry; - // value is empty - } + if (command == "stop") + stop(); + else if (command == "restart") + restart(); + else if (command == "stopForRestart") + stopForRestart(); + else + stop(); +} - // Remove simple escaping. - // This is not complete. - if (value.startsWith('\'') and value.endsWith('\'')) - value = value.mid(1, value.size()-2); - else if (value.startsWith('"') and value.endsWith('"')) - value = value.mid(1, value.size()-2); +void Process::setSocketNotifier(QSocketNotifier *s) +{ + connect(s, &QSocketNotifier::activated, this, &Process::incomingConnection); +} - env.insert(key, value); - } +void Process::setConfig(const Config &config) +{ + mConfig = config; +} - return env; +void Process::setStdoutFd(qintptr stdoutFd) +{ + mStdoutFd = stdoutFd; } diff --git a/process.h b/process.h index bf67d96..17754ac 100644 --- a/process.h +++ b/process.h @@ -1,20 +1,23 @@ -/**************************************************************************** +/****************************************************************************** ** -** Copyright (C) 2014 Digia Plc -** All rights reserved. -** For any questions to Digia, please use contact form at http://www.qt.io +** Copyright (C) 2016 The Qt Company Ltd. +** Contact: http://www.qt.io/licensing/ ** -** This file is part of Qt Enterprise Embedded. +** This file is part of Qt for Device Creation. ** -** Licensees holding valid Qt Enterprise licenses may use this file in -** accordance with the Qt Enterprise License Agreement provided with the +** $QT_BEGIN_LICENSE:COMM$ +** +** Commercial License Usage +** Licensees holding valid commercial Qt licenses may use this file in +** accordance with the commercial license agreement provided with the ** Software or, alternatively, in accordance with the terms contained in -** a written agreement between you and Digia. +** a written agreement between you and The Qt Company. For licensing terms +** and conditions see http://www.qt.io/terms-conditions. For further +** information use the contact form at http://www.qt.io/contact-us. ** -** If you have questions regarding the use of this file, please use -** contact form at http://www.qt.io +** $QT_END_LICENSE$ ** -****************************************************************************/ +******************************************************************************/ #ifndef PROCESS_H #define PROCESS_H @@ -27,24 +30,22 @@ class QSocketNotifier; struct Config { - enum Flag { - PrintDebugMessages = 0x01 - }; - Q_DECLARE_FLAGS(Flags, Flag) - enum DebugInterface{ LocalDebugInterface, PublicDebugInterface }; - Config() : flags(0) { } + Config() : platform("unknown"), debugInterface(LocalDebugInterface) { } QString base; QString platform; QMap env; + QStringList clearEnv; QStringList args; - Flags flags; DebugInterface debugInterface; + QString user; + QString group; + QString wrapperCmd; }; class Process : public QObject @@ -60,22 +61,25 @@ class Process : public QObject void setStdoutFd(qintptr stdoutFd); public slots: void stop(); + void stopForRestart(); + void restart(); private slots: void readyReadStandardError(); void readyReadStandardOutput(); void finished(int, QProcess::ExitStatus); - void error(QProcess::ProcessError); + void errorOccurred(QProcess::ProcessError); void incomingConnection(int); private: void forwardProcessOutput(qintptr fd, const QByteArray &data); - void startup(QStringList); - QProcessEnvironment interactiveProcessEnvironment() const; + void startup(); QProcess *mProcess; int mDebuggee; bool mDebug; Config mConfig; QString mBinary; qintptr mStdoutFd; + QStringList mStartupArguments; + bool mBeingRestarted; }; #endif // PROCESS_H