1 /*
2 * Copyright 2015-2021 The Regents of the University of California
3 * All rights reserved.
4 *
5 * This file is part of Spoofer.
6 *
7 * Spoofer is free software: you can redistribute it and/or modify
8 * it under the terms of the GNU General Public License as published by
9 * the Free Software Foundation, either version 3 of the License, or
10 * (at your option) any later version.
11 *
12 * Spoofer is distributed in the hope that it will be useful,
13 * but WITHOUT ANY WARRANTY; without even the implied warranty of
14 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
15 * GNU General Public License for more details.
16 *
17 * You should have received a copy of the GNU General Public License
18 * along with Spoofer. If not, see <http://www.gnu.org/licenses/>.
19 */
20
21 #include "spoof_qt.h"
22 #include <QTimer>
23 #include <QDir>
24 #include <QCommandLineParser>
25 #include "../../config.h"
26 #include "InputReader.h"
27 #include "FileTailThread.h"
28 #include "app.h"
29 static const char cvsid[] ATR_USED = "$Id: app.cpp,v 1.73 2021/04/28 17:39:08 kkeys Exp $";
30
31 const QString App::help = QSL(
32 "run - run the prober (and update the schedule)\n"
33 "abort - stop a running prober process\n"
34 "pause - prevent scheduled prober runs\n"
35 "resume - resume scheduled prober runs\n"
36 "shutdown - shutdown the scheduler process\n"
37 "upgrade - upgrade Spoofer, if available\n"
38 "set - display all settings\n"
39 "set <name> <value> - change a setting\n"
40 "help - display this help\n"
41 "quit - exit the manager CLI\n");
42
App(int & argc,char ** argv)43 App::App(int &argc, char **argv) :
44 QCoreApplication(argc, argv), SpooferUI(),
45 inReader(), command()
46 { }
47
~App()48 App::~App() {
49 qDebug() << "CLI: App dtor";
50 // avoid "QThread: Destroyed while thread is still running"
51 if (fileTail) fileTail->wait(1000);
52 if (inReader) inReader->wait(1000);
53 qDebug() << "CLI: App dtor done";
54 }
55
parseCommandLine(QCommandLineParser & clp)56 bool App::parseCommandLine(QCommandLineParser &clp)
57 {
58 clp.addPositionalArgument(QSL("command"), QSL(
59 "Execute a single spoofer scheduler command. If <command> is omitted "
60 "on the command line, the cli will run in interactive mode.\n") %
61 App::help,
62 QSL("[command]"));
63 if (!SpooferBase::parseCommandLine(clp, QSL("Spoofer scheduler command line interface")))
64 return false;
65
66 QStringList args = clp.positionalArguments();
67 if (!args.isEmpty())
68 command = args.join(QSL(" "));
69
70 return true;
71 }
72
73 // Do extra initialization before QCoreApplication::exec().
exec()74 int App::exec()
75 {
76 QTimer::singleShot(1, this, SLOT(initEvents()));
77 return QCoreApplication::exec();
78 }
79
connectScheduler(bool privileged)80 bool App::connectScheduler(bool privileged)
81 {
82 connect(scheduler, &QLocalSocket::connected, this, &App::schedConnected);
83 connect(scheduler, &QLocalSocket::disconnected,
84 this, &App::schedDisconnected);
85 connect(scheduler, QLOCALSOCKET_ERROR_OCCURRED, this, &App::schedError);
86 connect(scheduler, &QLocalSocket::readyRead, this, &App::readScheduler);
87
88 return connectToScheduler(privileged);
89 }
90
91 // Initialize things that expect event loop to be running (including anything
92 // that might trigger App::exit()).
initEvents()93 void App::initEvents()
94 {
95 scheduler = new QLocalSocket(this);
96 if (command.isEmpty()) {
97 inReader = new InputReader(this);
98 connect(inReader, &InputReader::dataReady, this, &App::execCmd);
99 connect(inReader, &InputReader::finished, this, &App::quit); // EOF
100 connect(this, &App::aboutToQuit, inReader, &InputReader::abort);
101 inReader->start();
102 }
103
104 if (!connectScheduler(true)) {
105 if (command.isEmpty()) this->exit(1);
106 else execCmd(new std::string(command.toStdString()));
107 }
108 }
109
schedConnected()110 void App::schedConnected()
111 {
112 spout << "Connected to scheduler." << Qt_endl;
113 connect(this, &App::aboutToQuit, scheduler, &QLocalSocket::close);
114 schedulerPaused = false; // until told otherwise
115 if (fileTail) // stale
116 fileTail->requestInterruption();
117 if (!command.isEmpty()) {
118 execCmd(new std::string(command.toStdString()));
119 }
120 }
121
schedDisconnected()122 void App::schedDisconnected()
123 {
124 spout << "Scheduler disconnected" << Qt_endl;
125 disconnect(this, nullptr, scheduler, nullptr);
126 disconnect(scheduler, nullptr, nullptr, nullptr);
127 this->exit(1);
128 }
129
schedError()130 void App::schedError()
131 {
132 spout << "Scheduler error: " << scheduler->errorString() << Qt_endl;
133 scheduler->abort();
134 scheduler->close();
135 scheduler->deleteLater();
136 scheduler = new QLocalSocket(this);
137 #ifndef EVERYONE_IS_PRIVILEGED
138 if (connectionIsPrivileged && connectScheduler(false))
139 return;
140 #endif
141 disconnect(this, nullptr, scheduler, nullptr);
142 disconnect(scheduler, nullptr, nullptr, nullptr);
143 this->exit(1);
144 }
145
startFileTail(QString logname)146 void App::startFileTail(QString logname)
147 {
148 fileTail = new FileTailThread(logname, this);
149 connect(fileTail, &FileTailThread::dataReady,
150 this, &App::handleProberText);
151 connect(fileTail, &FileTailThread::finished,
152 this, &App::finishProber);
153 connect(this, &App::aboutToQuit,
154 fileTail, &FileTailThread::abort);
155 fileTail->start();
156 }
157
sendCmd(std::string * str)158 void App::sendCmd(std::string *str)
159 {
160 qDebug() << "CLI: send:" << str->c_str();
161 if (scheduler->state() == QLocalSocket::ConnectedState) {
162 scheduler->write(str->c_str(), static_cast<qint64>(str->size()));
163 } else {
164 spout << "Warning: Not connected to scheduler." << Qt_endl;
165 doneCmd(1);
166 }
167 }
168
execCmd(std::string * str)169 void App::execCmd(std::string *str)
170 {
171 qDebug() << "execCmd:" << str->c_str();
172 if (*str == "quit" || *str == "exit") {
173 qDebug() << "CLI: quit";
174 this->quit();
175 } else if (*str == "help" || *str == "?") {
176 spout << App::help << Qt_endl;
177 doneCmd(0);
178 } else if (*str == "set") {
179 for (auto m : Config::members) {
180 spout << "# " << m->key << ": " << m->desc;
181 if (m->required) spout << " (REQUIRED)";
182 spout << Qt_endl;
183 if (!m->isSet()) spout << "# ";
184 spout << "set " << m->key << " " <<
185 m->variant().toString() << Qt_endl << Qt_endl;
186 }
187 doneCmd(0);
188 } else if (str->size() > 0) {
189 sendCmd(str);
190 }
191 delete str;
192 }
193
promptForUpgrade()194 void App::promptForUpgrade()
195 {
196 spout << Qt_endl << "New Spoofer version " << upgradeInfo->vstr << " is available." <<
197 #ifndef UPGRADE_WITHOUT_DOWNLOAD
198 Qt_endl << QSL("URL: ") << upgradeInfo->file <<
199 #endif
200 #ifdef UPGRADE_CMD
201 Qt_endl << QSL("command: ") << QSL(UPGRADE_CMD) <<
202 #endif
203 Qt_endl;
204 if (!upgradeInfo->warning.isNull())
205 spout << upgradeInfo->warning << Qt_endl;
206 if (upgradeInfo->autoTime >= 0) {
207 spout << "Spoofer will upgrade automatically in " << upgradeInfo->autoTime << " seconds." << Qt_endl;
208 spout << "Type \"upgrade\" to upgrade immediately or \"cancel\" to cancel." << Qt_endl;
209 } else {
210 spout << "Type \"upgrade\" to upgrade." << Qt_endl;
211 }
212 }
213
214