#include "fbgui.h"
#include "sysinfo.h"
#include "downloadmanager.h"
#include "javascriptinterfacefbgui.h"
#include <log4cxx/logger.h>
#include "qlog4cxx.h"
using namespace log4cxx;
using namespace log4cxx::helpers;
LoggerPtr coreLogger(Logger::getLogger("fbgui.core"));
#include <iostream>
#include <QThread>
#include <QtWebKit>
//-------------------------------------------------------------------------------------------
/**
* A constructor.
*
* The constructor of the fbgui class. It initializes the main objects
* which are needed while the program is running.
* The appearance of the webView is here also defined.
*
* @see JavascriptInterface
* @see DownloadManager
*/
fbgui::fbgui() :
agui() {
}
fbgui::~fbgui() {
dmThread.quit();
}
/**
* init function.
*/
void fbgui::init() {
// start fbgui
LOG4CXX_DEBUG(coreLogger, "Initializing fbgui...");
setWindowTitle("fbgui");
if (sslSupport)
LOG4CXX_DEBUG(coreLogger, "SSL enabled.");
// initialize javascript interface
JavascriptInterfaceFBGUI* jsi = new JavascriptInterfaceFBGUI(
this->_webView->page()->mainFrame());
QObject::connect(jsi, SIGNAL(quitFbgui()), this, SLOT(close()));
QObject::connect(jsi, SIGNAL(shutDownClient()), this,
SLOT(shutdownSystem()));
QObject::connect(_webView->page()->mainFrame(), SIGNAL(
javaScriptWindowObjectCleared()), jsi, SLOT(attachToDOM()));
// initialize download manager
DownloadManager* dm = new DownloadManager();
QObject::connect(dm, SIGNAL(downloadInfo(const QString&, const double&)),
jsi, SLOT(downloadInfo(const QString&, const double&)));
QObject::connect(dm, SIGNAL(notify(const QString&)), jsi,
SLOT(notify(const QString&)));
QObject::connect(jsi, SIGNAL(requestFile(const QString&)), dm,
SLOT(downloadFile(const QString&)));
QObject::connect(dm,
SIGNAL(updateProgress(const int&, const double&, const QString&)), jsi,
SLOT(updateProgressBar(const int&, const double&, const QString&)));
QObject::connect(dm, SIGNAL(downloadQueueEmpty()), jsi, SLOT(
callbackOnFinished()));
QObject::connect(dm, SIGNAL(downloadQueueEmpty()), this, SLOT(loadSystem()));
// move download manager to its own thread
dm->moveToThread(&dmThread);
dmThread.start();
loadURL();
showFullScreen();
}
//-------------------------------------------------------------------------------------------
// Preparations for URL load
//-------------------------------------------------------------------------------------------
/**
* This method checks the existance of the host.
*
* This method checks if the host exists / can be found.
* The host is from the URL given through the configuration.
*/
bool fbgui::checkHost() const {
QHostInfo hostInfo = QHostInfo::fromName(baseURL.host());
if (hostInfo.error() != QHostInfo::NoError) {
LOG4CXX_DEBUG(coreLogger, "Lookup of " << baseURL.host() << "failed.");
LOG4CXX_DEBUG(coreLogger, "Host can not be reached.");
return false;
} else {
LOG4CXX_DEBUG(coreLogger,
"Lookup of " << baseURL.host() << " succeeded.");
return true;
}
}
//-------------------------------------------------------------------------------------------
/**
* This method tries loads the URL.
*
* This method loads the main screen via an POST request. If also disconnects the watcher
* of the file, (Watcher is set in the fbgui::watchForTrigger() method).
* and generates the POST data body.
*
* @see fbgui::watchForTrigger()
* @see fbgui::generatePOSTData()
*/
void fbgui::loadURL() {
if (checkHost()) {
LOG4CXX_DEBUG(coreLogger,
"Loading URL: " << baseURL.toString() << " ...");
// Generate POST identification data needed by PBS.
QByteArray postData = generatePOSTData();
// Generate a Network Request Object
QNetworkRequest req(baseURL);
// show cursor again since user is about to interact.
req.setHeader(QNetworkRequest::ContentTypeHeader,
"application/x-www-form-urlencoded");
QObject::connect(_webView, SIGNAL(loadFinished(bool)), this,
SLOT(loadURLDone(bool)));
_webView->load(req, QNetworkAccessManager::PostOperation, postData);
}
// TODO: error page if no host.
}
void fbgui::loadURLDone(bool success) {
// done contains the success of the loading: false / true
if (!success) {
LOG4CXX_DEBUG(coreLogger,
"Loading failed. URL: " << _webView->url().toString());
LOG4CXX_DEBUG(coreLogger, "You can quit with CTRL + X ...");
// TODO handle failure properly...
} else {
LOG4CXX_DEBUG(coreLogger, "Loaded URL: " << _webView->url().toString());
}
}
//-------------------------------------------------------------------------------------------
/**
* This method generates the POST data body.
*
* This method generates the POST data body. The body contains the
* MAC address, an hardwarehash and a specific serial number.
* The hardwarehash is a MD5 hash over the MAC address and the
* mainboard serial number.
* The specific serial number is set at the creation of the usb boot stick.
* This file has to be present on the directory specified in
* the configuration for this to work.
*
* @see SysInfo::getMACAddress()
* @see SysInfo::getMainboardSerial()
*/
QByteArray fbgui::generatePOSTData() {
LOG4CXX_DEBUG(coreLogger, "Generating POST data...");
// use MAC address as base data
SysInfo si;
QByteArray data;
QByteArray macAdress(si.getInfo("mac").toUtf8());
if (!macAdress.isEmpty())
data.append(macAdress);
else {
LOG4CXX_DEBUG(coreLogger, "No readable MAC Address.");
}
// append mainboard serial to the mac address for more unique hardwarehash
QByteArray mbserial(si.getInfo("mbserial").toUtf8());
if (!mbserial.isEmpty())
data.append(mbserial);
else {
LOG4CXX_DEBUG(coreLogger,
"Mainboard serial was empty. Not appending to base hash data.");
}LOG4CXX_DEBUG(coreLogger, "[post] Hashing: " << data);
// generate MD5 hash of data
QByteArray hash = QCryptographicHash::hash(data, QCryptographicHash::Md5);
LOG4CXX_DEBUG(coreLogger, "[post] MD5 Hash: " << hash.toHex());
// fetch serial number from usb
QByteArray serial;
QFile file(serialLocation);
if (!file.open(QIODevice::ReadOnly)) {
LOG4CXX_DEBUG(coreLogger, "[post] No such file: " << file.fileName());
}
// everything ok, read data
serial = file.readAll();
file.close();
serial.chop(1); // chop EOF
LOG4CXX_DEBUG(coreLogger, "[post] Serial number is: " << serial);
if (gInterfaceName.isEmpty())
gInterfaceName = "eth0";
// construct final byte array
QByteArray postData("mac=");
postData.append(si.getInfo("mac"));
postData.append("&hardwarehash=" + hash.toHex());
postData.append("&serialnumber=" + serial);
LOG4CXX_DEBUG(coreLogger, "[post] POST data: " << postData);
return postData;
}
//-------------------------------------------------------------------------------------------
// Preparing Kernel Switch per kexec (initiating Stage 3)
//-------------------------------------------------------------------------------------------
void fbgui::loadSystem() {
//show loading system page.
//_webView->disconnect(this);
//QObject::connect(_webView, SIGNAL(loadFinished(bool)), this, SLOT(prepareKexec()));
_webView->load(QUrl("qrc:/html/loadsystem.html"));
QTimer::singleShot(1000, this, SLOT(prepareKexec()));
}
//-------------------------------------------------------------------------------------------
/**
* This method prepares kexec.
*
* The kernel command line file that should have been downloaded from the Preboot-Server
* and the ip config file (created by udhcpc) are merged into the final completed KCL.
*
* A process is then started to load the kernel, initramfs and kcl into kexec.
* The process tries to execute kexec -l with these parameters.
*
* If this succeeds, runKexec() is called
*
* @see fbgui::runKexec()
*
*/
void fbgui::prepareKexec() {
LOG4CXX_DEBUG(coreLogger, "Preparing kexec ...");
// try to read KCL file that was downloaded.
QFile file(downloadPath + "/kcl");
if (!file.open(QIODevice::ReadOnly)) {
LOG4CXX_DEBUG(coreLogger, "No such file: " << file.fileName());
}
// everything ok, read data.
QString kcl = file.readAll();
file.close();
LOG4CXX_DEBUG(coreLogger, "KCL from PBS: " << kcl);
// try to read ipconfig file generated by udhcpc.
file.setFileName("/tmp/ip_config_fbgui");
if (!file.open(QIODevice::ReadOnly)) {
LOG4CXX_DEBUG(coreLogger, "No such file: " << file.fileName());
}
// everything ok, read data.
QString ipConfig = file.readAll();
file.close();
LOG4CXX_DEBUG(coreLogger, "IP config: " << ipConfig);
// append ipConfig
kcl.append(" ip=");
kcl.append(ipConfig);
LOG4CXX_DEBUG(coreLogger, "Complete KCL: " << kcl);
// load the kernel + initramfs + append of kcl into kexec.
QProcess *process = new QProcess(this);
QString cmdline = "kexec -l " + downloadPath.toUtf8() + "/kernel --initrd="
+ downloadPath.toUtf8() + "/initramfs --append=\"" + kcl.toUtf8()
+ "\"";
LOG4CXX_DEBUG(coreLogger, "kexec cmdline: " << cmdline);
process->start(cmdline);
bool ret = process->waitForFinished();
if (!ret) {
LOG4CXX_DEBUG(coreLogger, "Failed to execute: " << cmdline);
LOG4CXX_DEBUG(coreLogger, "Exiting in 5 seconds...");
QTimer::singleShot(5000, this, SLOT(close()));
} else {
LOG4CXX_DEBUG(coreLogger, "Kexec load was successfull.");
if (debugMode < 0) {
// if process successfully finished, try to run kexec -e
runKexec();
} else {
LOG4CXX_DEBUG(coreLogger,
"Skipping execution of kexec - open debug shell.");
LOG4CXX_DEBUG(coreLogger,
"To start the system execute \"kexec -e\" in your shell.");
close();
}
}
}
//-------------------------------------------------------------------------------------------
/**
* This method tries to execute: kexec -e
*
* This method tries to execute: kexec -e
*
*/
void fbgui::runKexec() {
QProcess *process = new QProcess(this);
process->start("kexec -e");
if (!process->waitForStarted()) {
LOG4CXX_DEBUG(coreLogger, "Failed to execute: kexec -e");
LOG4CXX_DEBUG(coreLogger, "Exiting in 5 seconds...");
QTimer::singleShot(5000, this, SLOT(close()));
//TODO: Handle failure properly...
}
}