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