summaryrefslogtreecommitdiffstats
path: root/src
diff options
context:
space:
mode:
authorRainer Keller <rainer.keller@digia.com>2014-09-04 10:38:02 +0200
committerRainer Keller <rainer.keller@digia.com>2014-09-04 15:09:20 +0300
commitfa2d78c6088be96e755d97b1a72343753abec8d1 (patch)
tree589a1c1a06db570a17235a7699dffc4012241c2f /src
parent3a4fb56b69957ada6e59a669ea38df948794dbd0 (diff)
Add tools related for update
b2qt-update-application is for doing the actual update. b2qt-update-util is for initiating and finishing the update. Change-Id: Ide1c1ba307d9346bacf83c507816feecefe87a16 Reviewed-by: Eirik Aavitsland <eirik.aavitsland@digia.com>
Diffstat (limited to 'src')
-rw-r--r--src/b2qt-update-application/.gitignore4
-rw-r--r--src/b2qt-update-application/b2qt-update-application.pro14
-rw-r--r--src/b2qt-update-application/filewrapper.cpp53
-rw-r--r--src/b2qt-update-application/filewrapper.h40
-rw-r--r--src/b2qt-update-application/main.cpp203
-rw-r--r--src/b2qt-update-application/tar.cpp309
-rw-r--r--src/b2qt-update-application/tar.h99
-rw-r--r--src/b2qt-update-application/update.cpp202
-rw-r--r--src/b2qt-update-application/update.h50
-rw-r--r--src/b2qt-update-util/.gitignore4
-rw-r--r--src/b2qt-update-util/b2qt-update-util.pro2
-rw-r--r--src/b2qt-update-util/main.cpp138
-rw-r--r--src/src.pro1
13 files changed, 1119 insertions, 0 deletions
diff --git a/src/b2qt-update-application/.gitignore b/src/b2qt-update-application/.gitignore
new file mode 100644
index 0000000..d2a78c0
--- /dev/null
+++ b/src/b2qt-update-application/.gitignore
@@ -0,0 +1,4 @@
+Makefile
+b2qt-update-application
+moc_*.cpp
+*.o
diff --git a/src/b2qt-update-application/b2qt-update-application.pro b/src/b2qt-update-application/b2qt-update-application.pro
new file mode 100644
index 0000000..5fc557b
--- /dev/null
+++ b/src/b2qt-update-application/b2qt-update-application.pro
@@ -0,0 +1,14 @@
+CONFIG += c++11
+QT = core network
+SOURCES += \
+ main.cpp \
+ tar.cpp \
+ filewrapper.cpp \
+ update.cpp
+
+HEADERS += \
+ tar.h \
+ filewrapper.h \
+ update.h
+
+LIBS += -lcrypto
diff --git a/src/b2qt-update-application/filewrapper.cpp b/src/b2qt-update-application/filewrapper.cpp
new file mode 100644
index 0000000..6b4a759
--- /dev/null
+++ b/src/b2qt-update-application/filewrapper.cpp
@@ -0,0 +1,53 @@
+/****************************************************************************
+**
+** Copyright (C) 2014 Digia Plc
+** All rights reserved.
+** For any questions to Digia, please use the contact form at
+** http://qt.digia.com/
+**
+** This file is part of Qt Enterprise Embedded.
+**
+** Licensees holding valid Qt Enterprise licenses may use this file in
+** accordance with the Qt Enterprise License Agreement provided with the
+** Software or, alternatively, in accordance with the terms contained in
+** a written agreement between you and Digia.
+**
+** If you have questions regarding the use of this file, please use
+** the contact form at http://qt.digia.com/
+**
+****************************************************************************/
+
+#include "filewrapper.h"
+#include <QDebug>
+#include <QTimer>
+
+FileWrapper::FileWrapper(const QString &fileName, QObject *parent)
+ : QFile(fileName, parent)
+ , mTimer(new QTimer(this))
+{
+ mTimer->setInterval(50);
+ connect(mTimer, &QTimer::timeout, this, &FileWrapper::emitReadyRead);
+}
+
+FileWrapper::~FileWrapper()
+{
+}
+
+bool FileWrapper::open(OpenMode mode)
+{
+ bool rc = QFile::open(mode);
+ if (rc) {
+ mTimer->start();
+ }
+ return rc;
+}
+
+void FileWrapper::emitReadyRead()
+{
+ if (!atEnd()) {
+ emit readyRead();
+ } else {
+ mTimer->stop();
+ close();
+ }
+}
diff --git a/src/b2qt-update-application/filewrapper.h b/src/b2qt-update-application/filewrapper.h
new file mode 100644
index 0000000..fc8a977
--- /dev/null
+++ b/src/b2qt-update-application/filewrapper.h
@@ -0,0 +1,40 @@
+/****************************************************************************
+**
+** Copyright (C) 2014 Digia Plc
+** All rights reserved.
+** For any questions to Digia, please use the contact form at
+** http://qt.digia.com/
+**
+** This file is part of Qt Enterprise Embedded.
+**
+** Licensees holding valid Qt Enterprise licenses may use this file in
+** accordance with the Qt Enterprise License Agreement provided with the
+** Software or, alternatively, in accordance with the terms contained in
+** a written agreement between you and Digia.
+**
+** If you have questions regarding the use of this file, please use
+** the contact form at http://qt.digia.com/
+**
+****************************************************************************/
+
+#ifndef FILEWRAPPER_H
+#define FILEWRAPPER_H
+
+#include <QFile>
+class QTimer;
+
+class FileWrapper : public QFile
+{
+ Q_OBJECT
+
+public:
+ FileWrapper(const QString &fileName, QObject *parent = 0);
+ virtual ~FileWrapper();
+ bool open(OpenMode mode);
+
+private:
+ void emitReadyRead();
+ QTimer *mTimer;
+};
+
+#endif // FILEWRAPPER_H
diff --git a/src/b2qt-update-application/main.cpp b/src/b2qt-update-application/main.cpp
new file mode 100644
index 0000000..157af4f
--- /dev/null
+++ b/src/b2qt-update-application/main.cpp
@@ -0,0 +1,203 @@
+/****************************************************************************
+**
+** Copyright (C) 2014 Digia Plc
+** All rights reserved.
+** For any questions to Digia, please use the contact form at
+** http://qt.digia.com/
+**
+** This file is part of Qt Enterprise Embedded.
+**
+** Licensees holding valid Qt Enterprise licenses may use this file in
+** accordance with the Qt Enterprise License Agreement provided with the
+** Software or, alternatively, in accordance with the terms contained in
+** a written agreement between you and Digia.
+**
+** If you have questions regarding the use of this file, please use
+** the contact form at http://qt.digia.com/
+**
+****************************************************************************/
+
+#include <QCoreApplication>
+#include <QProcess>
+#include <QDebug>
+#include <QFile>
+#include <QDir>
+#include <unistd.h>
+#include <sys/reboot.h>
+#include <QNetworkAccessManager>
+#include <QUrl>
+#include <QNetworkRequest>
+#include <QNetworkReply>
+#include "update.h"
+#include "filewrapper.h"
+
+#define UPDATE_MOUNTPOINT "/mnt/update"
+
+QStringList mounts;
+
+void cleanup()
+{
+ foreach (const QString &m, mounts) {
+ QProcess::execute("umount", QStringList() << m);
+ }
+ mounts.clear();
+}
+
+void error(const QString &message)
+{
+ fprintf(stderr, "%s\n", message.toLocal8Bit().constData());
+ cleanup();
+ exit(1);
+}
+
+bool execute(const QString &binary, const QStringList &arguments)
+{
+ int rc = QProcess::execute(binary, arguments);
+ if (rc != 0)
+ error("Failed to execute command '" + binary + " " + arguments.join(" "));
+ return rc == 0;
+}
+
+bool mount(const QString &device, const QString &mountpoint, const QString &arguments = QString())
+{
+ QStringList tmp;
+ if (!arguments.isEmpty())
+ tmp << "-o" << arguments;
+
+ if (!execute("mount", QStringList() << tmp << device << mountpoint))
+ return false;
+
+ if (!arguments.contains("remount"))
+ mounts << mountpoint;
+ return true;
+}
+
+QByteArray readAll(const QString &fileName)
+{
+ QFile f(fileName);
+ if (!f.open(QFile::ReadOnly)) {
+ qWarning() << "Could not read" << fileName;
+ return QByteArray();
+ }
+
+ return f.readAll();
+}
+
+bool writeAll(const QString &fileName, const QByteArray &content)
+{
+ QFile f(fileName);
+ if (!f.open(QFile::WriteOnly)) {
+ qWarning() << "Could not write" << fileName;
+ return false;
+ }
+ if (f.write(content) != content.size()) {
+ qWarning() << "write size mismatch";
+ return false;
+ }
+ f.close();
+ return true;
+}
+
+QStringList find_usb_storage()
+{
+ QStringList rc;
+ QDir d("/sys/dev/block");
+
+ foreach (QString s, d.entryList()) {
+ QFileInfo fi(d.absoluteFilePath(s));
+ QString path = fi.canonicalFilePath();
+ if (path.contains("/usb"))
+ rc += path.mid(path.lastIndexOf('/'));
+ }
+ return rc;
+}
+
+int main(int argc, char **argv)
+{
+ QCoreApplication app(argc, argv);
+
+ mount(QString(), "/", "remount,rw");
+ execute("mkdir", QStringList() << "-p" << "/mnt/boot");
+ execute("mkdir", QStringList() << "-p" << "/mnt/root");
+ execute("mkdir", QStringList() << "-p" << UPDATE_MOUNTPOINT);
+ mount("/dev/mmcblk0p1", "/mnt/boot", "ro");
+
+ QByteArray update_state = readAll("/mnt/boot/update/state").trimmed();
+ if (update_state.isEmpty())
+ qFatal("Update state is empty");
+
+ if (update_state == "v")
+ qFatal("Update state is 'valid', this should not happen");
+ else if (update_state == "t")
+ qDebug() << "Update state is 'testing', this should not happen";
+ else if (update_state == "u")
+ qDebug() << "Update state is 'update'";
+ else
+ qDebug() << "Unknown update state:" << update_state;
+
+ QStringList usbsticks = find_usb_storage();
+
+ qDebug() << "Found USB storage devices:" << usbsticks;
+
+ bool update_found = false;
+ foreach (QString s, usbsticks) {
+ if (QProcess::execute("mount", QStringList() << "-o" << "ro" << "/dev/" + s << UPDATE_MOUNTPOINT) == 0) {
+ mounts << UPDATE_MOUNTPOINT;
+ qDebug() << "mount successful";
+
+ if (QFile::exists(UPDATE_MOUNTPOINT "/b2qt-update")) {
+ qDebug() << "update found";
+ update_found = true;
+ break;
+ }
+ execute("umount", QStringList() << UPDATE_MOUNTPOINT);
+ mounts.removeAt(mounts.lastIndexOf(UPDATE_MOUNTPOINT));
+ qDebug() << "no update found";
+ } else {
+ qDebug() << "mount of" << s << "failed";
+ }
+ }
+
+ Update update;
+
+ if (update_found) {
+ FileWrapper *fw = new FileWrapper(UPDATE_MOUNTPOINT "/b2qt-update", &update);
+ if (!fw->open(QFile::ReadOnly))
+ qFatal("Failed to open update");
+ qDebug() << "Starting update from USB";
+ update.setDevice(fw);
+ } else {
+ QByteArray update_source = readAll("/mnt/boot/update/source").trimmed();
+ if (update_source.isEmpty()) {
+ execute("umount", QStringList() << "/mnt/boot"); // FIXME
+ mounts.removeAt(mounts.lastIndexOf("/mnt/boot"));
+ qFatal("Update source is empty");
+ }
+
+ execute("udhcpc", QStringList() << "-i" << "eth0");
+
+ QNetworkAccessManager *manager = new QNetworkAccessManager(0);
+ QNetworkReply *reply = manager->get(QNetworkRequest(QUrl(update_source)));
+ reply->setReadBufferSize(2000000); // 2 MB
+
+ QObject::connect(reply, (void (QNetworkReply::*)(QNetworkReply::NetworkError))&QNetworkReply::error,
+ [](QNetworkReply::NetworkError e){qDebug() << "network error" << e;});
+ QObject::connect(reply, &QNetworkReply::sslErrors,
+ [](QList<QSslError>){ qDebug() << "ssl errors";});
+
+ qDebug() << "Starting update from Internet" << update_source;
+ update.setDevice(reply);
+ }
+
+ app.exec();
+
+ qDebug() << "unmount";
+ cleanup();
+
+ qDebug() << "sync";
+ sync();
+ qDebug() << "reboot; waiting 2 seconds";
+ sleep(2);
+ reboot(RB_AUTOBOOT);
+ return 0;
+}
diff --git a/src/b2qt-update-application/tar.cpp b/src/b2qt-update-application/tar.cpp
new file mode 100644
index 0000000..c0369ff
--- /dev/null
+++ b/src/b2qt-update-application/tar.cpp
@@ -0,0 +1,309 @@
+/****************************************************************************
+**
+** Copyright (C) 2014 Digia Plc
+** All rights reserved.
+** For any questions to Digia, please use the contact form at
+** http://qt.digia.com/
+**
+** This file is part of Qt Enterprise Embedded.
+**
+** Licensees holding valid Qt Enterprise licenses may use this file in
+** accordance with the Qt Enterprise License Agreement provided with the
+** Software or, alternatively, in accordance with the terms contained in
+** a written agreement between you and Digia.
+**
+** If you have questions regarding the use of this file, please use
+** the contact form at http://qt.digia.com/
+**
+****************************************************************************/
+
+#include "tar.h"
+#include <QProcess>
+#include <QDebug>
+#include <openssl/x509.h>
+#include <openssl/pem.h>
+
+static bool isZeroed(const char *ptr, unsigned int size)
+{
+ while (size) {
+ if (*ptr) {
+ return false;
+ }
+ ++ptr;
+ --size;
+ }
+ return true;
+}
+
+Tar::Tar(QIODevice *source)
+ : QObject(source)
+ , mSource(source)
+ , mSize(0)
+ , mRemainingSize(0)
+ , mRemainingFileBytes(0)
+ , mProcess(0)
+ , mState(WaitForHeader)
+ , x509(0)
+ , mdctx(0)
+{
+ // Initialize table with all digests in order to look them up by string later
+ OpenSSL_add_all_digests();
+
+ connect(source, &QIODevice::readyRead, this, &Tar::dataIncoming);
+ connect(source, &QIODevice::aboutToClose, this, &Tar::aboutToClose);
+}
+
+Tar::~Tar()
+{
+ if (x509)
+ X509_free(x509);
+ x509 = 0;
+ if (mdctx)
+ EVP_MD_CTX_destroy(mdctx);
+ mdctx = 0;
+}
+
+const QByteArray &Tar::currentContent() const
+{
+ return mContent;
+}
+
+unsigned long Tar::currentSize() const
+{
+ return mSize;
+}
+
+QString Tar::currentFileName() const
+{
+ if (mHeader.name[sizeof(mHeader.name)-1])
+ return QString::fromLatin1(mHeader.name, sizeof(mHeader.name));
+ else
+ return QString::fromLatin1(mHeader.name);
+}
+
+void Tar::dataIncoming()
+{
+ while (mSource->bytesAvailable() > 512) {
+
+ if (mState == WaitForHeader) {
+ if (mSource->bytesAvailable() < 512)
+ return;
+ if (mSource->read((char*)&mHeader, sizeof(mHeader)) != sizeof(mHeader))
+ qFatal("Tar read error");
+ if (isZeroed((char*)&mHeader, sizeof(mHeader))) {
+ qDebug() << "Zero Tar block";
+ qDebug() << "Bytes left" << mSource->bytesAvailable();
+ mSource->close();
+ return;
+ }
+ if (mHeader.name[0] == 0)
+ qFatal("Header starts with 0");
+
+ mSize = QString::fromLatin1(mHeader.size, sizeof(mHeader.size)-1).toULongLong(0, 8);
+ mRemainingFileBytes = mSize;
+ mRemainingSize = mSize;
+ if (mSize % 512) // padding to 512 byte block
+ mRemainingSize += 512 - (mSize % 512);
+
+ if (mSize == 0) {
+ qFatal("Size of file is 0");
+ }
+
+ mState = WaitForDecision;
+ qDebug() << "Started file" << currentFileName() << mSize;
+ if (x509)
+ setupMDContext();
+ emit startingFile(currentFileName());
+ } else if (mState == WaitForDecision) {
+ return;
+ } else if (mState == Extract) {
+ QByteArray ba;
+
+ quint64 bytesToHandle = mRemainingSize;
+
+ if (mSource->bytesAvailable() < mRemainingSize)
+ bytesToHandle = mSource->bytesAvailable();
+ if (bytesToHandle > 60000)
+ bytesToHandle = 60000;
+ ba = mSource->read(bytesToHandle);
+
+ updateMDContext(ba);
+ mProcess->write(ba);
+ mProcess->waitForBytesWritten();
+ mRemainingSize -= ba.size();
+ // do not return
+ } else if (mState == Receive) {
+ QByteArray ba;
+
+ if (mSource->bytesAvailable() > mRemainingSize)
+ ba = mSource->read(mRemainingSize);
+ else
+ ba = mSource->readAll();
+ updateMDContext(ba);
+ mContent += ba;
+ mRemainingSize -= ba.size();
+ if (mContent.size() > mSize)
+ mContent.resize(mSize);
+ // do not return
+ } else if (mState == Skip) {
+ QByteArray ba;
+
+ if (mSource->bytesAvailable() > mRemainingSize) {
+ ba = mSource->read(mRemainingSize);
+ } else {
+ ba = mSource->readAll();
+ }
+ updateMDContext(ba);
+ mRemainingSize -= ba.size();
+ // drop data
+ // do not return
+ } else if (mState == WaitForContinue) {
+ return;
+ } else {
+ qFatal("Unknown tar state");
+ }
+
+ if (mState != WaitForContinue && mState != WaitForHeader && mRemainingSize == 0) {
+ if (mState == Extract) {
+ qDebug() << "End tar process";
+ // please tar
+ // it needs 2 zeroed blocks
+ char buffer[sizeof(mHeader)];
+ memset(buffer, 0, sizeof(buffer));
+ mProcess->write(buffer, sizeof(buffer));
+ mProcess->write(buffer, sizeof(buffer));
+ mProcess->waitForBytesWritten();
+ mProcess->closeWriteChannel();
+ mProcess->waitForFinished(-1);
+ mProcess->deleteLater();
+ mProcess = 0;
+ }
+ mState = WaitForContinue;
+
+ emit(endingFile(currentFileName()));
+ }
+
+ }
+}
+
+void Tar::extractContent(const QString &targetDir)
+{
+ if (mState == WaitForDecision) {
+ qDebug() << Q_FUNC_INFO;
+ mState = Extract;
+ delete mProcess;
+ mProcess = new QProcess(this);
+ mProcess->setProcessChannelMode(QProcess::ForwardedChannels);
+ mProcess->start("tar", QStringList() << "xvf" << "-" << "-C" << targetDir);
+
+ // if we want to extract a file directly, the header needs to be passed to tar
+ if (!currentFileName().endsWith(".tar"))
+ mProcess->write((const char*)&mHeader, sizeof(mHeader));
+ }
+}
+
+void Tar::receiveContent()
+{
+ if (mState == WaitForDecision)
+ mState = Receive;
+}
+
+void Tar::skipContent()
+{
+ if (mState == WaitForDecision)
+ mState = Skip;
+}
+
+void Tar::continueContent()
+{
+ if (mState == WaitForContinue) {
+ mState = WaitForHeader;
+ mSize = 0;
+ mContent.clear();
+ delete mProcess;
+ mProcess = 0;
+ } else
+ qDebug() << "Called" << Q_FUNC_INFO << "in wrong state";
+}
+
+void Tar::aboutToClose()
+{
+ if (mState != WaitForHeader) {
+ qWarning() << "unexpected close";
+ }
+ emit finished();
+}
+
+bool Tar::setVerificationData(const QString &certificateFileName, const QString &mdType)
+{
+ if (x509)
+ qFatal("Setting key twice");
+
+ // Load key from file
+ BIO *i = BIO_new(BIO_s_file());
+ BIO *o = BIO_new_fp(stdout,BIO_NOCLOSE);
+
+ if ((BIO_read_filename(i, certificateFileName.toLatin1().constData()) <= 0) ||
+ ((x509 = PEM_read_bio_X509_AUX(i, NULL, NULL, NULL)) == NULL)) {
+ qWarning() << "Invalid certificate, failed to read file" << certificateFileName;
+ return false;
+ }
+ // Showing used certificate
+ X509_print_ex(o, x509, XN_FLAG_COMPAT, X509_FLAG_COMPAT);
+ BIO_free(i);
+ BIO_free(o);
+ qDebug() << "Using verification key:" << certificateFileName;
+
+ md = EVP_get_digestbyname(mdType.toLatin1().constData());
+ if (md == NULL)
+ qFatal("Digest not found");
+ return true;
+}
+
+void Tar::setupMDContext()
+{
+ if (mdctx)
+ EVP_MD_CTX_destroy(mdctx);
+ if (!(mdctx = EVP_MD_CTX_create()))
+ qFatal("EVP_MD_CTX_create failed");
+
+ EVP_PKEY *key = X509_get_pubkey(x509);
+ if (key == NULL) {
+ qFatal("X509_get_pubkey failed");
+ }
+
+ if (md == NULL)
+ qFatal("No digest set");
+
+ if (1 != EVP_DigestVerifyInit(mdctx, NULL, md, NULL, key))
+ qFatal("EVP_DigestVerifyInit failed");
+}
+
+void Tar::updateMDContext(QByteArray data)
+{
+ if (mRemainingFileBytes == 0)
+ qFatal("Trying to update MD context when no more bytes are expected");
+
+ if (mRemainingFileBytes < data.size())
+ data = data.left(mRemainingFileBytes);
+ mRemainingFileBytes -= data.size();
+
+ if (!mdctx)
+ return;
+
+ if (1 != EVP_DigestVerifyUpdate(mdctx, data.constData(), data.size()))
+ qFatal("EVP_DigestVerifyUpdate failed");
+}
+
+bool Tar::checkSignature(const QByteArray &signature)
+{
+ return 1 == EVP_DigestVerifyFinal(mdctx, (unsigned char*)signature.constData(), signature.size());
+}
+
+bool Tar::verifyCurrentContent(const QByteArray &signature)
+{
+ setupMDContext();
+ if (1 != EVP_DigestVerifyUpdate(mdctx, mContent.constData(), mContent.size()))
+ qFatal("EVP_DigestVerifyUpdate failed");
+ return checkSignature(signature);
+}
diff --git a/src/b2qt-update-application/tar.h b/src/b2qt-update-application/tar.h
new file mode 100644
index 0000000..e3ca1d2
--- /dev/null
+++ b/src/b2qt-update-application/tar.h
@@ -0,0 +1,99 @@
+/****************************************************************************
+**
+** Copyright (C) 2014 Digia Plc
+** All rights reserved.
+** For any questions to Digia, please use the contact form at
+** http://qt.digia.com/
+**
+** This file is part of Qt Enterprise Embedded.
+**
+** Licensees holding valid Qt Enterprise licenses may use this file in
+** accordance with the Qt Enterprise License Agreement provided with the
+** Software or, alternatively, in accordance with the terms contained in
+** a written agreement between you and Digia.
+**
+** If you have questions regarding the use of this file, please use
+** the contact form at http://qt.digia.com/
+**
+****************************************************************************/
+
+#ifndef TAR_H
+#define TAR_H
+
+#include <QObject>
+#include <openssl/x509.h>
+class QIODevice;
+class QProcess;
+#define USTAR_HEADER_SIZE 512
+
+struct ustar_header
+{
+char name[100]; /* File name. Null-terminated if room. */
+char mode[8]; /* Permissions as octal string. */
+char uid[8]; /* User ID as octal string. */
+char gid[8]; /* Group ID as octal string. */
+char size[12]; /* File size in bytes as octal string. */
+char mtime[12]; /* Modification time in seconds from Jan 1, 1970, as octal string. */
+char chksum[8]; /* Sum of octets in header as octal string. */
+char typeflag; /* An enum ustar_type value. */
+char linkname[100]; /* Name of link target. Null-terminated if room. */
+char magic[6]; /* "ustar\0" */
+char version[2]; /* "00" */
+char uname[32]; /* User name, always null-terminated. */
+char gname[32]; /* Group name, always null-terminated. */
+char devmajor[8]; /* Device major number as octal string. */
+char devminor[8]; /* Device minor number as octal string. */
+char prefix[155]; /* Prefix to file name. Null-terminated if room. */
+char padding[12]; /* Pad to 512 bytes. */
+} __attribute__((packed));
+
+class Tar : public QObject
+{
+ Q_OBJECT
+
+public:
+ Tar(QIODevice *source);
+ virtual ~Tar();
+
+ const QByteArray &currentContent() const;
+ unsigned long currentSize() const;
+ QString currentFileName() const;
+
+ enum State { WaitForHeader, WaitForDecision, Extract, Receive, Skip, WaitForContinue};
+ bool setVerificationData(const QString &certificateFileName, const QString &md);
+ bool checkSignature(const QByteArray &signature);
+ bool verifyCurrentContent(const QByteArray &signature);
+
+signals:
+ void startingFile(const QString &name);
+ void endingFile(const QString &name);
+ void finished();
+
+public slots:
+ void extractContent(const QString &targetDir);
+ void receiveContent();
+ void skipContent();
+ void dataIncoming();
+ void continueContent();
+
+private slots:
+ void aboutToClose();
+
+private:
+ void setupMDContext();
+ void updateMDContext(QByteArray);
+
+ QIODevice *mSource;
+ qint64 mSize;
+ qint64 mRemainingSize; // File Size + Padding
+ qint64 mRemainingFileBytes; // File Size without Padding
+ ustar_header mHeader;
+ QByteArray mContent;
+ QProcess *mProcess;
+ State mState;
+ X509 *x509;
+ EVP_MD_CTX *mdctx;
+ const EVP_MD *md;
+};
+
+#endif // TAR_H
diff --git a/src/b2qt-update-application/update.cpp b/src/b2qt-update-application/update.cpp
new file mode 100644
index 0000000..79fea64
--- /dev/null
+++ b/src/b2qt-update-application/update.cpp
@@ -0,0 +1,202 @@
+/****************************************************************************
+**
+** Copyright (C) 2014 Digia Plc
+** All rights reserved.
+** For any questions to Digia, please use the contact form at
+** http://qt.digia.com/
+**
+** This file is part of Qt Enterprise Embedded.
+**
+** Licensees holding valid Qt Enterprise licenses may use this file in
+** accordance with the Qt Enterprise License Agreement provided with the
+** Software or, alternatively, in accordance with the terms contained in
+** a written agreement between you and Digia.
+**
+** If you have questions regarding the use of this file, please use
+** the contact form at http://qt.digia.com/
+**
+****************************************************************************/
+
+#include "update.h"
+#include "tar.h"
+#include <QDebug>
+#include <QCoreApplication>
+#include <QProcess>
+#include <QFile>
+
+extern QStringList mounts;
+bool writeAll(const QString &fileName, const QByteArray &content);
+static QStringList allowedFiles;
+QByteArray readAll(const QString &fileName);
+bool execute(const QString &binary, const QStringList &arguments);
+bool mount(const QString &device, const QString &mountpoint, const QString &arguments = QString());
+
+QMap<QString,QString> parseMetaInfo(const QByteArray &data)
+{
+ QMap<QString,QString> rc;
+ QList<QByteArray> list = data.split('\n');
+ foreach (const QByteArray &ba, list) {
+ if (ba.isEmpty())
+ continue;
+ int i = ba.indexOf(':');
+ if (i < 0 || i == ba.size())
+ qFatal("Invalid meta.info content: No colon found or no data after colon");
+
+ QString key = ba.left(i);
+ QString value = ba.mid(i+1).trimmed();
+
+ if (rc.contains(key))
+ qFatal("Invalid meta.info content: Same key given twice");
+ rc.insert(key, value);
+ }
+ if (rc.isEmpty())
+ qFatal("Invalid meta.info content: No keys given");
+ return rc;
+}
+
+Update::Update(QObject *parent)
+ : QObject(parent)
+ , mTar(0)
+ , mSource(0)
+{
+ allowedFiles << "key.info" << "meta.info" << "keys.tar" << "uImage" << "rootfs.tar";
+}
+
+Update::~Update()
+{
+}
+
+//# openssl dgst -sha256 -sign priv.pem file.txt > file.txt.sig
+//# openssl dgst -sha256 -verify public.pem -signature file.txt.sig file.txt
+
+void Update::setDevice(QIODevice *source)
+{
+ if (!source) {
+ qFatal("Source device is NULL");
+ return;
+ }
+
+ if (mSource) {
+ qFatal("Source already set");
+ return;
+ }
+
+ mSource = source;
+ mTar = new Tar(mSource);
+
+ connect(mTar, &Tar::startingFile, this, &Update::tarStartingFile);
+ connect(mTar, &Tar::endingFile, this, &Update::tarEndingFile);
+ connect(mTar, &Tar::finished, this, &Update::tarFinished);
+}
+
+void Update::tarStartingFile(const QString &name)
+{
+ if (name == "key.info") {
+ mTar->receiveContent();
+ } else if (name.endsWith(".sig")) {
+ mTar->receiveContent();
+ mCurrentFile = name;
+ mCurrentFile.chop(4);
+
+ bool found = false;
+ while (!allowedFiles.isEmpty()) {
+ QString n = allowedFiles.takeFirst();
+ if (n == mCurrentFile) {
+ found = true;
+ break;
+ }
+ }
+
+ if (!found)
+ qFatal("File not allowed");
+
+ } else if (mCurrentFile != name) {
+ qFatal("Invalid file order");
+ } else {
+ if (mCurrentFile == "meta.info") {
+ mTar->receiveContent();
+ } else if (mCurrentFile == "uImage") {
+ mount(QString(), "/mnt/boot", "remount,rw");
+ mTar->extractContent("/mnt/boot");
+ } else if (mCurrentFile == "rootfs.tar") {
+ qDebug() << "Formatting rootfs";
+ execute("mkfs." + mMetaInfo["filesystemType"], QStringList() << mMetaInfo["rootDevice"]);
+ mount(mMetaInfo["rootDevice"], "/mnt/root");
+ mTar->extractContent("/mnt/root");
+ } else if (mCurrentFile == "keys.tar") {
+ mount(QString(), "/mnt/boot", "remount,rw");
+ execute("rm", QStringList() << "-r" << "/mnt/boot/update/keys/");
+ execute("mkdir", QStringList() << "/mnt/boot/update/keys/");
+ mTar->extractContent("/mnt/boot/update/keys");
+ } else {
+ qFatal("Unexpected file in update data");
+ }
+ }
+}
+
+void Update::tarEndingFile(const QString &name)
+{
+ if (name == "meta.info") {
+ QMap<QString,QString> map = parseMetaInfo(mTar->currentContent());
+
+ // Some rough sanity checks
+ if (!map.contains("version"))
+ qFatal("No version information found" );
+ if (map["version"] != "1")
+ qFatal("Invalid update version");
+ if (!map.contains("platform") || map["platform"].isEmpty())
+ qFatal("Platform information missing");
+ if (map["platform"] != readAll("/mnt/boot/update/platform").trimmed())
+ qFatal("Invalid platform information");
+ else
+ qDebug() << "Platform matches";
+ if (!map.contains("rootDevice") || map["rootDevice"].isEmpty())
+ qFatal("Root device information missing");
+ if (!QFile::exists(map["rootDevice"]))
+ qFatal("Root device not found");
+ if (!map.contains("key") || map["key"].isEmpty() || map["key"].contains('/'))
+ qFatal("Invalid or no key fingerprint");
+ if (!map.contains("digest") || map["digest"].isEmpty())
+ qFatal("Invalid digest type");
+ if (!map.contains("filesystemType") || map["filesystemType"].isEmpty() || map["filesystemType"].contains(' ') || map["filesystemType"].contains('/'))
+ qFatal("Invalid filesystem type");
+
+ mMetaInfo = map;
+
+ if (!mTar->setVerificationData("/mnt/boot/update/keys/" + mMetaInfo["key"], mMetaInfo["digest"]))
+ qFatal("Loading of certificate failed");
+
+ // Now verify the meta.info itself
+ if (mTar->verifyCurrentContent(mCurrentSignature)) {
+ qDebug() << "meta.info VERIFIED OK";
+ } else {
+ qDebug() << "meta.info VERIFICATION FAILED";
+ qFatal("VERIFICATION FAILED");
+ return;
+ }
+
+ } else if (name.endsWith(".sig")) {
+ mCurrentSignature = mTar->currentContent();
+ qDebug() << "Received signature with" << mCurrentSignature.size() << "bytes.";
+ } else {
+ if (mTar->checkSignature(mCurrentSignature)) {
+ qDebug() << "VERIFIED OK";
+ // FIXME: do something here
+ } else {
+ qDebug() << "VERIFICATION FAILED";
+ qFatal("VERIFICATION FAILED");
+ }
+ }
+
+ mTar->continueContent();
+}
+
+void Update::tarFinished()
+{
+ qDebug() << "Update finished";
+ mount(QString(), "/mnt/boot", "remount,rw");
+ if (!writeAll("/mnt/boot/update/state", "t"))
+ qFatal("Could not set state to testing");
+ mount(QString(), "/mnt/boot", "remount,ro");
+ qApp->exit();
+}
diff --git a/src/b2qt-update-application/update.h b/src/b2qt-update-application/update.h
new file mode 100644
index 0000000..830be56
--- /dev/null
+++ b/src/b2qt-update-application/update.h
@@ -0,0 +1,50 @@
+/****************************************************************************
+**
+** Copyright (C) 2014 Digia Plc
+** All rights reserved.
+** For any questions to Digia, please use the contact form at
+** http://qt.digia.com/
+**
+** This file is part of Qt Enterprise Embedded.
+**
+** Licensees holding valid Qt Enterprise licenses may use this file in
+** accordance with the Qt Enterprise License Agreement provided with the
+** Software or, alternatively, in accordance with the terms contained in
+** a written agreement between you and Digia.
+**
+** If you have questions regarding the use of this file, please use
+** the contact form at http://qt.digia.com/
+**
+****************************************************************************/
+
+#ifndef UPDATE_H
+#define UPDATE_H
+
+#include <QObject>
+#include <QMap>
+class Tar;
+class QIODevice;
+
+class Update : public QObject
+{
+ Q_OBJECT
+
+public:
+ Update(QObject *parent = 0);
+ virtual ~Update();
+ void setDevice(QIODevice *source);
+
+private slots:
+ void tarStartingFile(const QString &name);
+ void tarEndingFile(const QString &name);
+ void tarFinished();
+
+private:
+ Tar *mTar;
+ QIODevice *mSource;
+ QString mCurrentFile;
+ QByteArray mCurrentSignature;
+ QMap<QString, QString> mMetaInfo;
+};
+
+#endif // UPDATE_H
diff --git a/src/b2qt-update-util/.gitignore b/src/b2qt-update-util/.gitignore
new file mode 100644
index 0000000..67f0098
--- /dev/null
+++ b/src/b2qt-update-util/.gitignore
@@ -0,0 +1,4 @@
+Makefile
+b2qt-update-util
+*.o
+moc_*.cpp
diff --git a/src/b2qt-update-util/b2qt-update-util.pro b/src/b2qt-update-util/b2qt-update-util.pro
new file mode 100644
index 0000000..87397b4
--- /dev/null
+++ b/src/b2qt-update-util/b2qt-update-util.pro
@@ -0,0 +1,2 @@
+QT = core
+SOURCES = main.cpp
diff --git a/src/b2qt-update-util/main.cpp b/src/b2qt-update-util/main.cpp
new file mode 100644
index 0000000..ae26dc5
--- /dev/null
+++ b/src/b2qt-update-util/main.cpp
@@ -0,0 +1,138 @@
+/****************************************************************************
+**
+** Copyright (C) 2014 Digia Plc
+** All rights reserved.
+** For any questions to Digia, please use the contact form at
+** http://qt.digia.com/
+**
+** This file is part of Qt Enterprise Embedded.
+**
+** Licensees holding valid Qt Enterprise licenses may use this file in
+** accordance with the Qt Enterprise License Agreement provided with the
+** Software or, alternatively, in accordance with the terms contained in
+** a written agreement between you and Digia.
+**
+** If you have questions regarding the use of this file, please use
+** the contact form at http://qt.digia.com/
+**
+****************************************************************************/
+
+#include <QCoreApplication>
+#include <sys/reboot.h>
+#include <QFile>
+#include <QDebug>
+#include <unistd.h>
+#include <QStringList>
+#include <QProcess>
+
+bool mount_boot()
+{
+ return QProcess::execute("mount", QStringList() << "/dev/mmcblk0p1" << "/boot") == 0;
+}
+
+bool umount_boot()
+{
+ return QProcess::execute("umount", QStringList() << "/boot") == 0;
+}
+
+bool finish_update()
+{
+ if (!mount_boot()) {
+ qWarning() << "Could not mount /boot";
+ return false;
+ }
+ QFile f("/boot/update/state");
+ if (!f.open(QFile::WriteOnly)) {
+ qWarning() << "Could not open file for writing";
+ return false;
+ }
+ if (f.write("v") != 1) {
+ qWarning() << "Write error";
+ return false;
+ }
+ fsync(f.handle());
+ f.close();
+ if (!umount_boot()) {
+ qWarning() << "Could not unmount /boot";
+ return false;
+ }
+ return true;
+}
+
+int start_update(const QString &source)
+{
+ if (!mount_boot()) {
+ qWarning() << "Could not mount /boot";
+ return false;
+ }
+ {
+ QFile f("/boot/update/source");
+ if (!f.open(QFile::WriteOnly)) {
+ qWarning() << "Could not open file for writing";
+ return false;
+ }
+ QByteArray ba = source.toUtf8();
+
+ if (f.write(ba) != ba.size()) {
+ qWarning() << "Write error";
+ return false;
+ }
+ fsync(f.handle());
+ f.close();
+ }
+
+ {
+ QFile f("/boot/update/state");
+ if (!f.open(QFile::WriteOnly)) {
+ qWarning() << "Could not open file for writing";
+ return false;
+ }
+ if (f.write("u") != 1) {
+ qWarning() << "Write error";
+ return false;
+ }
+ fsync(f.handle());
+ f.close();
+ }
+ if (!umount_boot()) {
+ qWarning() << "Could not unmount /boot";
+ return false;
+ }
+ reboot(RB_AUTOBOOT);
+ return true;
+}
+
+void usage()
+{
+ fprintf(stderr,
+ "b2qt-update [start|finish] [...]\n"
+ " start: For internet update provide a http URL as parameter\n"
+ " finish: An update\n"
+ );
+}
+
+int main(int argc, char **argv)
+{
+ QCoreApplication app(argc, argv);
+ QStringList args = app.arguments();
+
+ args.removeFirst();
+ if (args.size() == 0) {
+ usage();
+ return 1;
+ }
+
+ QString arg = args.takeFirst();
+
+ if (arg == "finish")
+ return finish_update();
+ else if (arg == "start") {
+ if (args.size() == 0)
+ return start_update(QString());
+ else
+ return start_update(args.takeFirst());
+ } else {
+ usage();
+ return 1;
+ }
+}
diff --git a/src/src.pro b/src/src.pro
index fe2f463..e8ecbaf 100644
--- a/src/src.pro
+++ b/src/src.pro
@@ -5,6 +5,7 @@ SUBDIRS += \
imports \
doc \
plugins \
+ b2qt-update-util
android: SUBDIRS += doppelganger qt_hw_init qconnectivity