summaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
-rw-r--r--doc/ota.qdoc18
-rw-r--r--examples/qml/basic/main.qml9
-rwxr-xr-xqt-ostree/build-ostree.sh1
-rw-r--r--qt-ostree/patches/deltas-Expose-the-filename-parameter.patch45
-rwxr-xr-xqt-ostree/qt-ostree65
-rw-r--r--src/imports/pluginmain.cpp22
-rw-r--r--src/lib/qotaclient.cpp50
-rw-r--r--src/lib/qotaclient.h2
-rw-r--r--src/lib/qotaclient_p.h1
-rw-r--r--src/lib/qotaclientasync.cpp102
-rw-r--r--src/lib/qotaclientasync_p.h6
11 files changed, 306 insertions, 15 deletions
diff --git a/doc/ota.qdoc b/doc/ota.qdoc
index cc48a0b..d4d31f9 100644
--- a/doc/ota.qdoc
+++ b/doc/ota.qdoc
@@ -109,6 +109,7 @@
\li Generating an update from the new version of your product's sysroot.
\li Delivering this update to a customer device via OTA.
\li Securing a delivery of an update.
+ \li Support for custom update delivery mechanisms.
\endlist
\section2 Installation
@@ -502,6 +503,23 @@
It is advised to use both GPG and TLS in hostile environments. To learn more about the
security topics from the above list, consult dedicated resources.
+ \section2 Offline Updates and Custom Delivery Mechanisms
+
+ Updating devices via OtaClient::update() requires a target device to be connected to the
+ Internet and this mechanism is limited to HTTP(S) only (OtaRepositoryConfig::url). An
+ alternative approach is to generate a self-contained update package. A self-contained
+ package support can be enabled by passing \c {--create-self-contained-package} to the
+ \c {qt-ostree} tool. This will generate a \c {WORKDIR/superblock} binary file. Generating
+ a self-contained update package is required when:
+ \list
+ \li A device has no network connection and is intended to be
+ updated via external media such as a USB drive, or
+ \li Some other protocol or proprietary mechanism is used to
+ deliver software to a device.
+ \endlist
+ As all APIs in the Qt OTA Update module, applying a self-contained update package is an
+ atomic process, and is done via OtaClient::applyOffline().
+
\section1 Layout of an OTA Enabled Sysroot
There are only two directories on a device for a safe storage of local files:
diff --git a/examples/qml/basic/main.qml b/examples/qml/basic/main.qml
index 85a9085..49a62c6 100644
--- a/examples/qml/basic/main.qml
+++ b/examples/qml/basic/main.qml
@@ -170,6 +170,14 @@ Window {
}
}
Button {
+ text: "Apply Package"
+ onClicked: {
+ if (!otaReady())
+ return;
+ OtaClient.applyOffline("/var/superblock")
+ }
+ }
+ Button {
visible: OtaClient.rollbackAvailable
text: "Rollback"
onClicked: {
@@ -255,6 +263,7 @@ Window {
if (success)
log("Update available: " + OtaClient.updateAvailable)
}
+ onApplyOfflineFinished: logWithCondition("Offline package apply", success)
onRollbackFinished: logWithCondition("Rollback", success)
onUpdateFinished: logWithCondition("Update", success)
onRepositoryConfigChanged: updateConfigView(config)
diff --git a/qt-ostree/build-ostree.sh b/qt-ostree/build-ostree.sh
index d01bf35..cfcf621 100755
--- a/qt-ostree/build-ostree.sh
+++ b/qt-ostree/build-ostree.sh
@@ -50,6 +50,7 @@ git am "${DIR}"/patches/Support-for-booting-without-initramfs.patch
git am "${DIR}"/patches/Allow-updating-files-in-the-boot-directory.patch
git am "${DIR}"/patches/u-boot-add-bootdir-to-the-generated-uEnv.txt.patch
git am "${DIR}"/patches/Create-firmware-convenience-symlinks.patch
+git am "${DIR}"/patches/deltas-Expose-the-filename-parameter.patch
./autogen.sh \
--with-soup \
diff --git a/qt-ostree/patches/deltas-Expose-the-filename-parameter.patch b/qt-ostree/patches/deltas-Expose-the-filename-parameter.patch
new file mode 100644
index 0000000..cb8fa8b
--- /dev/null
+++ b/qt-ostree/patches/deltas-Expose-the-filename-parameter.patch
@@ -0,0 +1,45 @@
+From 4537b1d2c89d3c9b24f0c08874ec6a1f720add0a Mon Sep 17 00:00:00 2001
+From: Gatis Paeglis <gatis.paeglis@qt.io>
+Date: Thu, 24 Nov 2016 15:59:36 +0100
+Subject: [PATCH] deltas: Expose the filename parameter
+
+The C API (ostree_repo_static_delta_generate) knows what to do
+with it, but this parameter was never exposed via command line
+tool.
+---
+ src/ostree/ot-builtin-static-delta.c | 5 +++++
+ 1 file changed, 5 insertions(+)
+
+diff --git a/src/ostree/ot-builtin-static-delta.c b/src/ostree/ot-builtin-static-delta.c
+index a1c220b..57799d4 100644
+--- a/src/ostree/ot-builtin-static-delta.c
++++ b/src/ostree/ot-builtin-static-delta.c
+@@ -33,6 +33,7 @@ static char *opt_min_fallback_size;
+ static char *opt_max_bsdiff_size;
+ static char *opt_max_chunk_size;
+ static char *opt_endianness;
++static char *opt_filename;
+ static gboolean opt_empty;
+ static gboolean opt_swap_endianness;
+ static gboolean opt_inline;
+@@ -71,6 +72,7 @@ static GOptionEntry generate_options[] = {
+ { "min-fallback-size", 0, 0, G_OPTION_ARG_STRING, &opt_min_fallback_size, "Minimum uncompressed size in megabytes for individual HTTP request", NULL},
+ { "max-bsdiff-size", 0, 0, G_OPTION_ARG_STRING, &opt_max_bsdiff_size, "Maximum size in megabytes to consider bsdiff compression for input files", NULL},
+ { "max-chunk-size", 0, 0, G_OPTION_ARG_STRING, &opt_max_chunk_size, "Maximum size of delta chunks in megabytes", NULL},
++ { "filename", 0, 0, G_OPTION_ARG_STRING, &opt_filename, "Sets where to store the delta. By default gets stored in the ostree repository", NULL},
+ { NULL }
+ };
+
+@@ -328,6 +330,9 @@ ot_static_delta_builtin_generate (int argc, char **argv, GCancellable *cancellab
+ if (opt_inline)
+ g_variant_builder_add (parambuilder, "{sv}",
+ "inline-parts", g_variant_new_boolean (TRUE));
++ if (opt_filename)
++ g_variant_builder_add (parambuilder, "{sv}",
++ "filename", g_variant_new_bytestring (opt_filename));
+
+ g_variant_builder_add (parambuilder, "{sv}", "verbose", g_variant_new_boolean (TRUE));
+ if (opt_endianness || opt_swap_endianness)
+--
+2.7.4
+
diff --git a/qt-ostree/qt-ostree b/qt-ostree/qt-ostree
index 7ab3709..318936a 100755
--- a/qt-ostree/qt-ostree
+++ b/qt-ostree/qt-ostree
@@ -63,6 +63,12 @@ FIRST_COMMIT=false
OSTREE_REPO=${WORKDIR}/ostree-repo
OSTREE_BRANCH=linux/qt
OSTREE_COMMIT_SUBJECT=""
+# STATIC DELTA
+STATIC_DELTA_ARGS=""
+SELF_CONTAINED_PACKAGE=false
+SUPERBLOCK=${WORKDIR}/superblock
+DELTA_FROM=${OSTREE_BRANCH}^
+DELTA_TO=${OSTREE_BRANCH}
# TLS
USE_CLIENT_TLS=false
SERVER_CERT=""
@@ -85,7 +91,7 @@ RECOVERY_PARTITION=false
RECOVERYFS_TYPE="ext2"
RECOVERYFS_OPT="-F -L recovery"
RECOVERYFS_SIZE="65536" # Recovery partition size [in KiB]
-ROOT_OVERHEAD_FACTOR="1.3" # Add 30% extra space in rootfs
+ROOT_OVERHEAD_FACTOR="3"
PARTITION_ALIGNMENT="4096" # Set alignment to 4MB [in KiB]
# LOG COLORS
COLOR_DEFAULT='\e[0m'
@@ -189,6 +195,19 @@ usage()
echo " exist, one is created in the specified location. If this argument is not"
echo " provided, a default repository is created in the working directory."
echo
+ echo "--create-self-contained-package"
+ echo
+ echo " Creates a self-contained (superblock) update package. This package is saved in the"
+ echo " current working directory."
+ echo
+ echo "--disable-bsdiff"
+ echo
+ echo " The bsdiff algorithm produces smaller updates by taking advantage of how executable"
+ echo " files change. Smaller updates mean a better download efficiency. Applying bsdiff deltas"
+ echo " on the client devices requires extra CPU and memory resources, as well as I/O requests."
+ echo " When generating an update for resource-constrained devices it might be desirable"
+ echo " to disable bsdiff."
+ echo
# NOTE: Disabling, as we don't really use them at the moment.
#echo "--ostree-branch os/branch-name Commits the generated update in the specified OSTree branch. A default branch is linux/qt."
@@ -347,6 +366,12 @@ parse_args()
OSTREE_REPO=$(realpath -ms ${2})
shift 1
;;
+ --create-self-contained-package)
+ SELF_CONTAINED_PACKAGE=true
+ ;;
+ --disable-bsdiff)
+ STATIC_DELTA_ARGS="--disable-bsdiff"
+ ;;
--uboot-env-file)
UBOOT_ENV_FILE=$(realpath -ms ${2})
shift 1
@@ -476,6 +501,13 @@ parse_args()
validation_error "Must specify both --tls-client-cert-path and --tls-client-key-path for TLS client authentication feature."
fi
+ if [ ! -d ${OSTREE_REPO}/objects ] ; then
+ FIRST_COMMIT=true
+ fi
+ if [[ $FIRST_COMMIT = true && $SELF_CONTAINED_PACKAGE = true ]] ; then
+ validation_error "Can not generate a self-contained package (--create-self-contained-package), when --ostree-repo points to a non-existing repository."
+ fi
+
if [ $INVALID_ARGS = true ] ; then
usage
qt_ostree_exit 1
@@ -732,12 +764,25 @@ convert_to_ostree_sysroot()
fi
}
+create_self_contained_package()
+{
+ qt_ostree_info "Generating a self-contained update package ..."
+ # Disable fallback objects, so all objects would be included in the generated
+ # delta and applying the delta would not require an Internet connection.
+ "${OSTREE}" --repo=${OSTREE_REPO} static-delta generate ${STATIC_DELTA_ARGS} \
+ --min-fallback-size=0 --inline --filename=${SUPERBLOCK}
+
+ if [ ! -e "${SUPERBLOCK}" ] ; then
+ qt_ostree_error "Something failed, ${SUPERBLOCK} does not exist"
+ fi
+ qt_ostree_info "Generated a self-contained update package: ${SUPERBLOCK}"
+}
+
commit_generated_tree()
{
# Commit the generated tree into OSTree repository.
- mkdir -p ${OSTREE_REPO}
- if [ ! -d ${OSTREE_REPO}/objects ] ; then
- FIRST_COMMIT=true
+ if [ $FIRST_COMMIT = true ] ; then
+ mkdir -p ${OSTREE_REPO}
qt_ostree_info "Initializing new OSTree repository in ${OSTREE_REPO}"
"${OSTREE}" --repo=${OSTREE_REPO} init --mode=archive-z2
fi
@@ -766,6 +811,18 @@ commit_generated_tree()
qt_ostree_exit 0
fi
+ STATIC_DELTA_ARGS="${STATIC_DELTA_ARGS} --from=${DELTA_FROM} --to=${DELTA_TO}"
+ if [ $SELF_CONTAINED_PACKAGE = true ] ; then
+ create_self_contained_package
+ fi
+ # Pulling static deltas via HTTP is on hold due to several UX issues: https://github.com/ostreedev/ostree/issues/475
+ # if [ $FIRST_COMMIT = false ] ; then
+ # qt_ostree_info "Generating static delta ..."
+ # "${OSTREE}" --repo=${OSTREE_REPO} static-delta generate ${STATIC_DELTA_ARGS}
+ # fi
+
+ "${OSTREE}" --repo=${OSTREE_REPO} summary -u ${GPG_ARGS}
+
qt_ostree_info "Checking the repository for consistency ..."
if [ $DEVELOPER = false ] ; then
"${OSTREE}" --repo=${OSTREE_REPO} fsck
diff --git a/src/imports/pluginmain.cpp b/src/imports/pluginmain.cpp
index 07df3ef..1e5a535 100644
--- a/src/imports/pluginmain.cpp
+++ b/src/imports/pluginmain.cpp
@@ -185,7 +185,7 @@ QT_BEGIN_NAMESPACE
\include qotaclient.cpp update-description
- \sa updateFinished(), fetchRemoteInfo, restartRequired
+ \sa updateFinished(), fetchRemoteInfo, restartRequired, setRepositoryConfig
*/
/*!
@@ -214,6 +214,24 @@ QT_BEGIN_NAMESPACE
*/
/*!
+ \qmlmethod bool OtaClient::applyOffline(string packagePath)
+
+ \include qotaclient.cpp apply-offline
+
+ This is a notifier signal for applyOffline(). The \a success argument
+ indicates whether the operation was successful.
+
+ \sa applyOfflineFinished()
+*/
+
+/*!
+ \qmlsignal OtaClient::applyOfflineFinished(bool success)
+
+ This is a notifier signal for rollback(). The \a success argument
+ indicates whether the operation was successful.
+*/
+
+/*!
\qmlproperty bool OtaClient::otaEnabled
\readonly
@@ -341,7 +359,7 @@ QT_BEGIN_NAMESPACE
*/
/*!
- \qmlmethod bool OtaClient::repositoryConfigsEqual()
+ \qmlmethod bool OtaClient::repositoryConfigsEqual(OtaRepositoryConfig a, OtaRepositoryConfig b)
\include qotaclient.cpp repository-configs-equal
*/
diff --git a/src/lib/qotaclient.cpp b/src/lib/qotaclient.cpp
index b6d705d..2ed86ba 100644
--- a/src/lib/qotaclient.cpp
+++ b/src/lib/qotaclient.cpp
@@ -168,6 +168,12 @@ void QOtaClientPrivate::rollbackFinished(const QString &defaultRev, bool success
emit q->rollbackFinished(success);
}
+void QOtaClientPrivate::applyOfflineFinished(bool success)
+{
+ Q_Q(QOtaClient);
+ emit q->applyOfflineFinished(success);
+}
+
void QOtaClientPrivate::statusStringChanged(const QString &status)
{
Q_Q(QOtaClient);
@@ -304,6 +310,13 @@ QString QOtaClientPrivate::revision(QueryTarget target) const
*/
/*!
+ \fn void QOtaClient::applyOfflineFinished(bool success)
+
+ This is a notifier signal for applyOffline(). The \a success argument
+ indicates whether the operation was successful.
+*/
+
+/*!
\fn void QOtaClient::remoteInfoChanged()
//! [remoteinfochanged-description]
Remote info can change when calling fetchRemoteInfo(). If OTA metadata on
@@ -384,6 +397,7 @@ QOtaClient::QOtaClient(QObject *parent) :
connect(async, &QOtaClientAsync::fetchRemoteInfoFinished, d, &QOtaClientPrivate::fetchRemoteInfoFinished);
connect(async, &QOtaClientAsync::updateFinished, d, &QOtaClientPrivate::updateFinished);
connect(async, &QOtaClientAsync::rollbackFinished, d, &QOtaClientPrivate::rollbackFinished);
+ connect(async, &QOtaClientAsync::applyOfflineFinished, d, &QOtaClientPrivate::applyOfflineFinished);
connect(async, &QOtaClientAsync::errorOccurred, d, &QOtaClientPrivate::errorOccurred);
connect(async, &QOtaClientAsync::statusStringChanged, d, &QOtaClientPrivate::statusStringChanged);
connect(async, &QOtaClientAsync::rollbackChanged, d, &QOtaClientPrivate::rollbackChanged);
@@ -426,7 +440,7 @@ bool QOtaClient::fetchRemoteInfo() const
holds whether the operation was started successfully.
//! [update-description]
- \sa updateFinished(), fetchRemoteInfo(), restartRequired
+ \sa updateFinished(), fetchRemoteInfo, restartRequired, setRepositoryConfig
*/
bool QOtaClient::update() const
{
@@ -456,11 +470,41 @@ bool QOtaClient::rollback() const
return true;
}
+
+/*!
+//! [apply-offline]
+ Applies a self-contained update package.
+
+ This method is asynchronous and returns immediately. The return value
+ holds whether the operation was started successfully. The \a packagePath
+ holds a path to the package file.
+
+//! [apply-offline]
+
+ \sa applyOfflineFinished()
+*/
+bool QOtaClient::applyOffline(const QString &packagePath)
+{
+ Q_D(QOtaClient);
+ if (!d->isReady())
+ return false;
+
+ QFileInfo package(packagePath);
+ if (!package.exists()) {
+ d->errorOccurred(QString(QStringLiteral("The package %1 does not exist"))
+ .arg(package.absoluteFilePath()));
+ return false;
+ }
+
+ d->m_otaAsync->applyOffline(package.absoluteFilePath());
+ return true;
+}
+
/*!
//! [remove-repository-config]
Remove a configuration file for the repository.
- The repository configuration is stored on a file system in \c {/etc/ostree/remotes.d/\*.conf}
+ The repository configuration is stored on a file system in \c {/etc/ostree/remotes.d/*.conf}
If the configuration file does not exist, this function returns \c true.
If the configuration file exists, this function returns \c true if the file
@@ -509,7 +553,7 @@ static inline bool pathExists(QOtaClientPrivate *d, const QString &path)
/*!
//! [set-repository-config]
Change the configuration for the repository. The repository configuration
- is stored on a file system in \c {/etc/ostree/remotes.d/\*.conf}
+ is stored on a file system in \c {/etc/ostree/remotes.d/*.conf}
Returns \c true if the configuration file is changed successfully;
otherwise returns \c false.
diff --git a/src/lib/qotaclient.h b/src/lib/qotaclient.h
index 6a560de..3f1a8fe 100644
--- a/src/lib/qotaclient.h
+++ b/src/lib/qotaclient.h
@@ -75,6 +75,7 @@ public:
Q_INVOKABLE bool fetchRemoteInfo() const;
Q_INVOKABLE bool update() const;
Q_INVOKABLE bool rollback() const;
+ Q_INVOKABLE bool applyOffline(const QString &packagePath);
Q_INVOKABLE bool setRepositoryConfig(QOtaRepositoryConfig *config);
Q_INVOKABLE QOtaRepositoryConfig *repositoryConfig() const;
@@ -110,6 +111,7 @@ Q_SIGNALS:
void fetchRemoteInfoFinished(bool success);
void updateFinished(bool success);
void rollbackFinished(bool success);
+ void applyOfflineFinished(bool success);
private:
Q_DISABLE_COPY(QOtaClient)
diff --git a/src/lib/qotaclient_p.h b/src/lib/qotaclient_p.h
index e93a4e5..cb5c4af 100644
--- a/src/lib/qotaclient_p.h
+++ b/src/lib/qotaclient_p.h
@@ -67,6 +67,7 @@ public:
void fetchRemoteInfoFinished(const QString &remoteRev, const QJsonDocument &remoteInfo, bool success);
void updateFinished(const QString &defaultRev, bool success);
void rollbackFinished(const QString &defaultRev, bool success);
+ void applyOfflineFinished(bool success);
void statusStringChanged(const QString &status);
void errorOccurred(const QString &error);
void rollbackChanged(const QString &rollbackRev, const QJsonDocument &rollbackInfo, int treeCount);
diff --git a/src/lib/qotaclientasync.cpp b/src/lib/qotaclientasync.cpp
index a040b1c..23c5ff9 100644
--- a/src/lib/qotaclientasync.cpp
+++ b/src/lib/qotaclientasync.cpp
@@ -37,14 +37,22 @@
QT_BEGIN_NAMESPACE
+#define OSTREE_STATIC_DELTA_META_ENTRY_FORMAT "(uayttay)"
+#define OSTREE_STATIC_DELTA_FALLBACK_FORMAT "(yaytt)"
+#define OSTREE_STATIC_DELTA_SUPERBLOCK_FORMAT "(a{sv}tayay" OSTREE_COMMIT_GVARIANT_STRING "aya" OSTREE_STATIC_DELTA_META_ENTRY_FORMAT "a" OSTREE_STATIC_DELTA_FALLBACK_FORMAT ")"
+
QOtaClientAsync::QOtaClientAsync() :
m_sysroot(ostree_sysroot_new(0))
{
+ GError *error = nullptr;
+ ostree_sysroot_get_repo (m_sysroot, &m_repo, 0, &error);
+ emitGError(error);
// async mapper
connect(this, &QOtaClientAsync::initialize, this, &QOtaClientAsync::_initialize);
connect(this, &QOtaClientAsync::fetchRemoteInfo, this, &QOtaClientAsync::_fetchRemoteInfo);
connect(this, &QOtaClientAsync::update, this, &QOtaClientAsync::_update);
connect(this, &QOtaClientAsync::rollback, this, &QOtaClientAsync::_rollback);
+ connect(this, &QOtaClientAsync::applyOffline, this, &QOtaClientAsync::_applyOffline);
}
QOtaClientAsync::~QOtaClientAsync()
@@ -146,7 +154,7 @@ bool QOtaClientAsync::multiprocessLock(const QString &method)
ostree_sysroot_lock (m_sysroot, &error);
if (emitGError(error))
return false;
- qCDebug(qota) << QTime::currentTime().toString() << " lock acquired";
+ qCDebug(qota) << QTime::currentTime().toString() << "lock acquired";
return true;
}
@@ -193,7 +201,7 @@ void QOtaClientAsync::_fetchRemoteInfo()
QString remoteRev;
QJsonDocument remoteInfo;
bool ok = true;
- ostree(QStringLiteral("ostree pull --commit-metadata-only qt-os linux/qt"), &ok);
+ ostree(QStringLiteral("ostree pull --commit-metadata-only --disable-static-deltas qt-os linux/qt"), &ok);
if (ok) ostree(QStringLiteral("ostree pull --subpath=/usr/etc/qt-ota.json qt-os linux/qt"), &ok);
if (ok) remoteRev = ostree(QStringLiteral("ostree rev-parse linux/qt"), &ok);
if (ok) remoteInfo = info(QOtaClientPrivate::QueryTarget::Remote, &ok, remoteRev);
@@ -201,22 +209,41 @@ void QOtaClientAsync::_fetchRemoteInfo()
multiprocessUnlock();
}
+bool QOtaClientAsync::deployCommit(const QString &commit)
+{
+ bool ok = true;
+ QString kernelArgs;
+ GError *error = nullptr;
+ g_autoptr(GFile) root = nullptr;
+ if (!ostree_repo_read_commit (m_repo, commit.toLatin1().constData(),
+ &root, nullptr, nullptr, &error)) {
+ emitGError(error);
+ return false;
+ }
+ g_autoptr(GFile) kargsInRev = g_file_resolve_relative_path (root, "/usr/lib/ostree-boot/kargs");
+ g_autoptr(GInputStream) in = (GInputStream*)g_file_read (kargsInRev, 0, 0);
+ if (in)
+ kernelArgs = ostree(QString(QStringLiteral("ostree cat %1 /usr/lib/ostree-boot/kargs")).arg(commit), &ok);
+
+ emit statusStringChanged(QStringLiteral("Deploying..."));
+ if (ok) ostree(QString(QStringLiteral("ostree admin deploy --karg-none %1 %2"))
+ .arg(kernelArgs).arg(commit), &ok, true);
+ return ok;
+}
+
void QOtaClientAsync::_update(const QString &updateToRev)
{
if (!multiprocessLock(QStringLiteral("_update")))
return;
bool ok = true;
QString defaultRev;
- QString kernelArgs;
GError *error = nullptr;
emit statusStringChanged(QStringLiteral("Checking for missing objects..."));
ostree(QString(QStringLiteral("ostree pull qt-os:%1")).arg(updateToRev), &ok, true);
multiprocessUnlock();
if (!ok) goto out;
- emit statusStringChanged(QStringLiteral("Deploying..."));
- kernelArgs = ostree(QString(QStringLiteral("ostree cat %1 /usr/lib/ostree-boot/kargs")).arg(updateToRev), &ok);
- if (ok) ostree(QString(QStringLiteral("ostree admin deploy --karg-none %1 linux/qt")).arg(kernelArgs), &ok, true);
+ ok = deployCommit(updateToRev);
if (!ok) goto out;
ostree_sysroot_load (m_sysroot, 0, &error);
@@ -311,4 +338,67 @@ void QOtaClientAsync::_rollback()
multiprocessUnlock();
}
+void QOtaClientAsync::_applyOffline(const QString &packagePath)
+{
+ bool success = false;
+ GError *error = nullptr;
+ g_autofree char *toCsum = nullptr;
+ g_autoptr(GBytes) bytes = nullptr;
+ g_autoptr(GVariant) deltaSuperblock = nullptr;
+ g_autoptr(GVariant) toCsumV = nullptr;
+ g_autoptr(GVariant) packageCommitV = nullptr;
+ g_autoptr(GVariant) currentCommitV = nullptr;
+ QString currentCommit;
+ guint64 currentTimestamp;
+ guint64 packageTimestamp;
+ bool ok = true;
+
+ // load delta superblock
+ GMappedFile *mfile = g_mapped_file_new (packagePath.toLatin1().data(), FALSE, &error);
+ if (!mfile)
+ goto out;
+ bytes = g_mapped_file_get_bytes (mfile);
+ g_mapped_file_unref (mfile);
+ deltaSuperblock = g_variant_new_from_bytes (G_VARIANT_TYPE (OSTREE_STATIC_DELTA_SUPERBLOCK_FORMAT),
+ bytes, FALSE);
+ g_variant_ref_sink (deltaSuperblock);
+
+ // get a timestamp of the commit object from the superblock
+ packageCommitV = g_variant_get_child_value (deltaSuperblock, 4);
+ if (!ostree_validate_structureof_commit (packageCommitV, &error))
+ goto out;
+ packageTimestamp = ostree_commit_get_timestamp (packageCommitV);
+ // get timestamp of the head commit from the repository
+ currentCommit = ostree(QStringLiteral("ostree rev-parse linux/qt"), &ok);
+ if (!ok || !ostree_repo_load_commit (m_repo, currentCommit.toLatin1().constData(),
+ &currentCommitV, nullptr, &error)) {
+ goto out;
+ }
+ currentTimestamp = ostree_commit_get_timestamp (currentCommitV);
+ qCDebug(qota) << "current timestamp:" << currentTimestamp;
+ qCDebug(qota) << "package timestamp:" << packageTimestamp;
+ if (packageTimestamp < currentTimestamp) {
+ emit errorOccurred(QString(QStringLiteral("Not allowed to downgrade - current timestamp: %1,"
+ " package timestamp: %2")).arg(currentTimestamp).arg(packageTimestamp));
+ goto out;
+ }
+
+ emit statusStringChanged(QStringLiteral("Applying the update package..."));
+ ostree(QString(QStringLiteral("ostree static-delta apply-offline %1")).arg(packagePath), &ok);
+ if (!ok) goto out;
+
+ toCsumV = g_variant_get_child_value (deltaSuperblock, 3);
+ if (!ostree_validate_structureof_csum_v (toCsumV, &error))
+ goto out;
+ toCsum = ostree_checksum_from_bytes_v (toCsumV);
+ ostree(QString(QStringLiteral("ostree reset qt-os:linux/qt %1")).arg(QLatin1String(toCsum)), &ok);
+ if (!ok || !deployCommit(QLatin1String(toCsum)))
+ goto out;
+
+ success = true;
+out:
+ emitGError(error);
+ emit applyOfflineFinished(success);
+}
+
QT_END_NAMESPACE
diff --git a/src/lib/qotaclientasync_p.h b/src/lib/qotaclientasync_p.h
index d9e1e28..248d5d7 100644
--- a/src/lib/qotaclientasync_p.h
+++ b/src/lib/qotaclientasync_p.h
@@ -39,6 +39,7 @@
QT_BEGIN_NAMESPACE
struct OstreeSysroot;
+struct OstreeRepo;
// from gerror.h
typedef struct _GError GError;
@@ -61,6 +62,8 @@ signals:
void updateFinished(const QString &defaultRev, bool success);
void rollback();
void rollbackFinished(const QString &defaultRev, bool success);
+ void applyOffline(const QString &packagePath);
+ void applyOfflineFinished(bool success);
void rollbackChanged(const QString &rollbackRev, const QJsonDocument &rollbackInfo, int treeCount);
void errorOccurred(const QString &error);
void statusStringChanged(const QString &status);
@@ -74,14 +77,17 @@ protected:
int rollbackIndex();
void resetRollbackState();
bool emitGError(GError *error);
+ bool deployCommit(const QString &commit);
void _initialize();
void _fetchRemoteInfo();
void _update(const QString &updateToRev);
void _rollback();
+ void _applyOffline(const QString &packagePath);
private:
OstreeSysroot *m_sysroot;
+ OstreeRepo *m_repo;
};
QT_END_NAMESPACE