diff options
-rw-r--r-- | doc/ota.qdoc | 18 | ||||
-rw-r--r-- | examples/qml/basic/main.qml | 9 | ||||
-rwxr-xr-x | qt-ostree/build-ostree.sh | 1 | ||||
-rw-r--r-- | qt-ostree/patches/deltas-Expose-the-filename-parameter.patch | 45 | ||||
-rwxr-xr-x | qt-ostree/qt-ostree | 65 | ||||
-rw-r--r-- | src/imports/pluginmain.cpp | 22 | ||||
-rw-r--r-- | src/lib/qotaclient.cpp | 50 | ||||
-rw-r--r-- | src/lib/qotaclient.h | 2 | ||||
-rw-r--r-- | src/lib/qotaclient_p.h | 1 | ||||
-rw-r--r-- | src/lib/qotaclientasync.cpp | 102 | ||||
-rw-r--r-- | src/lib/qotaclientasync_p.h | 6 |
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(), + ¤tCommitV, 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 |