diff --git a/.gitignore b/.gitignore index c032d6af..c3545c78 100644 --- a/.gitignore +++ b/.gitignore @@ -1,6 +1,7 @@ build/ *.pro.* src/bin/patchmanager-daemon/adaptor.* +src/bin/patchmanager-daemon/patchmanager *.rpm *.so* diff --git a/rpm/patchmanager.spec b/rpm/patchmanager.spec index 6c790869..47924aa4 100644 --- a/rpm/patchmanager.spec +++ b/rpm/patchmanager.spec @@ -16,6 +16,8 @@ URL: https://github.com/sailfishos-patches/patchmanager Source0: %{name}-%{version}.tar.bz2 Requires: unzip Requires: patch +Requires: grep +Requires: sed Requires: sailfish-version >= 3.4.0 BuildRequires: pkgconfig(Qt5Core) BuildRequires: pkgconfig(Qt5DBus) @@ -168,6 +170,7 @@ systemctl-user daemon-reload %{_userunitdir}/lipstick.service.wants/lipstick-patchmanager.service %{_libdir}/libpreload%{name}.so %{_sysconfdir}/firejail/whitelist-common-%{name}.local +%config(noreplace) %{_sysconfdir}/%{name}/manglelist.conf %attr(0755,root,root) %{_libexecdir}/pm_apply %attr(0755,root,root) %{_libexecdir}/pm_unapply diff --git a/src/bin/patchmanager-daemon/dbus/org.SfietKonstantin.patchmanager.xml b/src/bin/patchmanager-daemon/dbus/org.SfietKonstantin.patchmanager.xml index 22fd0eed..5214172d 100644 --- a/src/bin/patchmanager-daemon/dbus/org.SfietKonstantin.patchmanager.xml +++ b/src/bin/patchmanager-daemon/dbus/org.SfietKonstantin.patchmanager.xml @@ -59,6 +59,9 @@ + + + diff --git a/src/bin/patchmanager-daemon/patchmanager b/src/bin/patchmanager-daemon/patchmanager new file mode 100755 index 00000000..e73549a0 Binary files /dev/null and b/src/bin/patchmanager-daemon/patchmanager differ diff --git a/src/bin/patchmanager-daemon/patchmanagerobject.cpp b/src/bin/patchmanager-daemon/patchmanagerobject.cpp index 24609db7..6afdf021 100644 --- a/src/bin/patchmanager-daemon/patchmanagerobject.cpp +++ b/src/bin/patchmanager-daemon/patchmanagerobject.cpp @@ -86,7 +86,7 @@ static const QString PATCHES_WORK_DIR_PREFIX = QStringLiteral("/tmp/patchmanager static const QString PATCHES_WORK_DIR = QStringLiteral("%1/%2").arg(PATCHES_WORK_DIR_PREFIX, "work"); static const QString PATCHES_ADDITIONAL_DIR = QStringLiteral("%1/%2").arg(PATCHES_WORK_DIR_PREFIX, "patches"); static const QString PATCH_FILE = QStringLiteral("patch.json"); - +static const QString MANGLE_CONFIG_FILE = QStringLiteral("/etc/patchmanager/manglelist.conf"); static const QString NAME_KEY = QStringLiteral("name"); static const QString DESCRIPTION_KEY = QStringLiteral("description"); static const QString CATEGORY_KEY = QStringLiteral("category"); @@ -288,6 +288,17 @@ void PatchManagerObject::setAppliedPatches(const QSet &patches) putSettings(QStringLiteral("applied"), QStringList(patches.toList())); } +QStringList PatchManagerObject::getMangleCandidates() +{ + if (m_mangleCandidates.empty()) { + qDebug() << Q_FUNC_INFO; + auto mangleCandidates = QSettings(MANGLE_CONFIG_FILE, QSettings::IniFormat).value("MANGLE_CANDIDATES", "").toString(); + m_mangleCandidates = mangleCandidates.split(' ', QString::SplitBehavior::SkipEmptyParts); + qDebug() << "Loaded mangle candidates:" << m_mangleCandidates; + } + return m_mangleCandidates; +} + void PatchManagerObject::getVersion() { qDebug() << Q_FUNC_INFO; @@ -1157,6 +1168,10 @@ bool PatchManagerObject::putSettings(const QString &name, const QVariant &value) QVariant old = m_settings->value(key); if (old != value) { m_settings->setValue(key ,value); + if (name == QStringLiteral("bitnessMangle")) { + qDebug() << Q_FUNC_INFO << "Changing bitness mangle refreshes patch list"; + refreshPatchList(); + } return true; } return false; @@ -1569,6 +1584,18 @@ void PatchManagerObject::doRefreshPatchList() { qDebug() << Q_FUNC_INFO; + // Create mangling replacement tokens. + QStringList toManglePaths{}, mangledPaths{}; + if(getSettings(QStringLiteral("bitnessMangle"), false).toBool()) { + toManglePaths = getMangleCandidates(); + mangledPaths = getMangleCandidates().replaceInStrings("/usr/lib/", "/usr/lib64/"); + if (Q_PROCESSOR_WORDSIZE == 4) { // 32 bit + std::swap(toManglePaths, mangledPaths); + } + } + qDebug() << Q_FUNC_INFO << "toManglePaths" << toManglePaths; + qDebug() << Q_FUNC_INFO << "mangledPaths" << mangledPaths; + // load applied patches m_appliedPatches = getAppliedPatches(); @@ -1590,6 +1617,14 @@ void PatchManagerObject::doRefreshPatchList() if (line.startsWith(QByteArrayLiteral("+++ "))) { const QString toPatch = QString::fromLatin1(line.split(' ')[1].split('\t')[0].split('\n')[0]); QString path = toPatch; + + for (int i = 0; i < toManglePaths.size(); i++) { + if (path.startsWith(toManglePaths[i])) { + qDebug() << Q_FUNC_INFO << "Editing path: " << path; + path.replace(toManglePaths[i], mangledPaths[i]); + } + } + while (!QFileInfo::exists(path) && path.count('/') > 1) { path = path.mid(path.indexOf('/', 1)); } @@ -1768,6 +1803,13 @@ bool PatchManagerObject::doPatch(const QString &patchName, bool apply, QString * QStringList arguments; arguments.append(patchName); + if (false == getSettings(QStringLiteral("bitnessMangle"), false).toBool()) { + QProcessEnvironment env = QProcessEnvironment::systemEnvironment(); + qDebug() << Q_FUNC_INFO << "DISABLE_MANGLING=true"; + env.insert("DISABLE_MANGLING", "true"); + process.setProcessEnvironment(env); + } + process.setArguments(arguments); qDebug() << Q_FUNC_INFO << "Starting:" << process.program() << process.arguments(); process.start(); diff --git a/src/bin/patchmanager-daemon/patchmanagerobject.h b/src/bin/patchmanager-daemon/patchmanagerobject.h index 246e029e..3a67e1a1 100644 --- a/src/bin/patchmanager-daemon/patchmanagerobject.h +++ b/src/bin/patchmanager-daemon/patchmanagerobject.h @@ -40,6 +40,7 @@ #include #include #include +#include #include #include @@ -180,6 +181,8 @@ private slots: void restartKeyboard(); void doRestartKeyboard(); + QStringList getMangleCandidates(); + private: void restartService(const QString &serviceName); @@ -237,6 +240,8 @@ private slots: QString m_osRelease; + QStringList m_mangleCandidates; + PatchManagerAdaptor *m_adaptor = nullptr; QNetworkAccessManager *m_nam = nullptr; diff --git a/src/etc/etc.pro b/src/etc/etc.pro index 6d59952f..009e1f98 100644 --- a/src/etc/etc.pro +++ b/src/etc/etc.pro @@ -1,6 +1,8 @@ TEMPLATE = aux +manglelist.files = manglelist.conf +manglelist.path = /etc/patchmanager firejail.files = whitelist-common-patchmanager.local firejail.path = /etc/firejail -INSTALLS += firejail +INSTALLS += firejail manglelist diff --git a/src/etc/manglelist.conf b/src/etc/manglelist.conf new file mode 100644 index 00000000..98c664c2 --- /dev/null +++ b/src/etc/manglelist.conf @@ -0,0 +1,9 @@ +# Settings file for 32/64 bit path mangling +# +# must conform to both shell format, and be parseable as QSettings(foo, QSettings::IniFormat) + +# list of candidate paths to attempt 32/64bit library path correction +# used by patchmanager-daemon and pm_apply.sh +# determined by a find /usr/lib -name "*.qml" and bug reports +# the list is given in "32bit" format, i.e. no lib64 +MANGLE_CANDIDATES="/usr/lib/qt5/qml /usr/lib/jolla-mediaplayer /usr/lib/maliit/plugins" diff --git a/src/qml/SettingsPage.qml b/src/qml/SettingsPage.qml index 2fe78105..3b7dab32 100644 --- a/src/qml/SettingsPage.qml +++ b/src/qml/SettingsPage.qml @@ -31,6 +31,28 @@ Page { onClicked: PatchManager.developerMode = !PatchManager.developerMode automaticCheck: false } + + TextSwitch { + id: fixBitSwitch + text: qsTranslate("", "Fix patches made for 32-bit or 64-bit only") + description: qsTranslate("", "Automatically fix lib or lib64 for select paths shown below.") + checked: PatchManager.bitnessMangle + onClicked: PatchManager.bitnessMangle = !PatchManager.bitnessMangle + automaticCheck: false + } + + TextArea { + // align to the right of TextSwitch indicator + anchors { + left: fixBitSwitch.left + leftMargin: fixBitSwitch.leftMargin + Theme.paddingLarge + } + color: Theme.secondaryColor + font.pixelSize: Theme.fontSizeSmall + readOnly: true + text: PatchManager.mangleCandidates.join("\n") + enabled: fixBitSwitch.checked + } } } } diff --git a/src/qml/patchmanager.cpp b/src/qml/patchmanager.cpp index 0d17c325..82648984 100644 --- a/src/qml/patchmanager.cpp +++ b/src/qml/patchmanager.cpp @@ -197,6 +197,17 @@ bool PatchManager::applyOnBoot() const return getSettingsSync(QStringLiteral("applyOnBoot"), false).toBool(); } +QStringList PatchManager::mangleCandidates() const +{ + QDBusPendingReply reply = m_interface->getMangleCandidates(); + reply.waitForFinished(); + if (reply.isFinished()) { + qDebug() << Q_FUNC_INFO << "mangleCandidates() dbus replied:" << reply.value(); + return reply.value(); + } + return QStringList(); +} + void PatchManager::setApplyOnBoot(bool applyOnBoot) { if (putSettingsSync(QStringLiteral("applyOnBoot"), applyOnBoot)) { @@ -204,6 +215,17 @@ void PatchManager::setApplyOnBoot(bool applyOnBoot) } } +bool PatchManager::bitnessMangle() const +{ + return getSettingsSync(QStringLiteral("bitnessMangle"), false).toBool(); +} + +void PatchManager::setBitnessMangle(bool bitnessMangle) +{ + if (putSettingsSync(QStringLiteral("bitnessMangle"), bitnessMangle)) { + emit bitnessMangleChanged(bitnessMangle); + } +} PatchManagerModel *PatchManager::installedModel() { return m_installedModel; diff --git a/src/qml/patchmanager.h b/src/qml/patchmanager.h index 7b0f1a06..cfb8f715 100644 --- a/src/qml/patchmanager.h +++ b/src/qml/patchmanager.h @@ -66,6 +66,8 @@ class PatchManager: public QObject Q_PROPERTY(QString serverMediaUrl READ serverMediaUrl CONSTANT) Q_PROPERTY(bool developerMode READ developerMode WRITE setDeveloperMode NOTIFY developerModeChanged) Q_PROPERTY(bool applyOnBoot READ applyOnBoot WRITE setApplyOnBoot NOTIFY applyOnBootChanged) + Q_PROPERTY(bool bitnessMangle READ bitnessMangle WRITE setBitnessMangle NOTIFY bitnessMangleChanged) + Q_PROPERTY(QStringList mangleCandidates READ mangleCandidates) Q_PROPERTY(PatchManagerModel *installedModel READ installedModel CONSTANT) Q_PROPERTY(QVariantMap updates READ getUpdates NOTIFY updatesChanged) Q_PROPERTY(QStringList updatesNames READ getUpdatesNames NOTIFY updatesChanged) @@ -83,6 +85,9 @@ class PatchManager: public QObject void setDeveloperMode(bool developerMode); bool applyOnBoot() const; void setApplyOnBoot(bool applyOnBoot); + bool bitnessMangle() const; + void setBitnessMangle(bool bitnessMangle); + QStringList mangleCandidates() const; PatchManagerModel *installedModel(); QString trCategory(const QString &category) const; QVariantMap getUpdates() const; @@ -156,6 +161,7 @@ public slots: void easterReceived(const QString & easterText); void developerModeChanged(bool developerMode); void applyOnBootChanged(bool applyOnBoot); + void bitnessMangleChanged(bool bitnessMangle); void updatesChanged(); void toggleServicesChanged(bool toggleServices); void failureChanged(bool failed); diff --git a/src/tools/pm_apply b/src/tools/pm_apply index 55c47093..9a7bf548 100644 --- a/src/tools/pm_apply +++ b/src/tools/pm_apply @@ -15,9 +15,18 @@ PM_LOG_FILE="$PM_VAR_DIR/patchmanager.log" PM_PATCH_BACKUP_ROOT_DIR="$PM_VAR_DIR/patches" PM_PATCH_BACKUP_DIR="$PM_PATCH_BACKUP_ROOT_DIR/$1" +SYS_BITNESS=$(/usr/bin/getconf LONG_BIT) + # Constants PATCH_NAME="unified_diff.patch" PATCH_PATH="$PATCH_DIR/$PATCH_NAME" +PATCH_EDITED_NAME="unified_diff_${SYS_BITNESS}bit.patch" + +# list of candidate paths to attempt 32/64bit library path correction +MANGLE_CANDIDATES="" +if [ -z "$DISABLE_MANGLING" ] && [ -r "etc/patchmanager/manglelist.conf" ] ; then + source /etc/patchmanager/manglelist.conf +fi ROOT_DIR="/tmp/patchmanager" @@ -36,6 +45,11 @@ log() { echo "$@" | tee -a "$PM_LOG_FILE" } +log "Mangle candidates" +log $MANGLE_CANDIDATES +log "Disable mangling" +log $DISABLE_MANGLING + failure() { log log "*** FAILED ***" @@ -68,6 +82,69 @@ test_if_applied() { fi } +# see issue #71: https://github.com/sailfishos-patches/patchmanager/issues/71 +mangle_libpath() { + if [ -f "$PATCH_PATH" ]; then + log + log "----------------------------------" + [ $SYS_BITNESS -eq 32 ] && log "Checking paths for 32->64bit conversion" + [ $SYS_BITNESS -eq 64 ] && log "Checking paths for 64->32bit conversion" + log "----------------------------------" + log + + found=0 + candidates="$MANGLE_CANDIDATES" + if [ $SYS_BITNESS -eq 32 ]; then + # first, convert the candidate list + # variable expansion ${foo/lib/lib64} would work on bash, but not POSIX/ash/busybox + candidates=$(printf '%s' "$MANGLE_CANDIDATES" | sed 's@/usr/lib/@/usr/lib64/@g' ) + fi + + for p in $candidates; do + totl_lines=$(grep -c "^+++" "$PATCH_PATH") + cand_lines=$(grep -c "^+++ $p" "$PATCH_PATH") + if [ $cand_lines -eq 0 ]; then + continue # nothing found, try next + else + found=$(( $found + $cand_lines )) + if [ $totl_lines -ne $cand_lines ]; then + # if there are several references in the patch file our mangling might do too much and cause the patch to fail. + log "WARNING: mixed patch, conversion might not work" + fi + fi + + patch_edited_path="$PM_PATCH_BACKUP_DIR"/"$PATCH_EDITED_NAME" + log "found: ${p}, replacing libpath references" + + mkdir -p "$PM_PATCH_BACKUP_DIR" + # prepare the pattern + pr="" + if [ $SYS_BITNESS -eq 32 ]; then + pr=$(printf '%s' "$p" | sed 's@/usr/lib64/@/usr/lib/@') + elif [ $SYS_BITNESS -eq 64 ]; then + pr=$(printf '%s' "$p" | sed 's@/usr/lib/@/usr/lib64/@') + fi + # doit + printf '#\n# patch converted to %sbit library paths from original by Patchmanager 3.1\n# Date: %s\n#\n' $SYS_BITNESS $(date -Iseconds) > "$patch_edited_path" + sed "s@^+++ $p@+++ $pr@;s@^--- $p@--- $pr@" "$PATCH_PATH" >> "$patch_edited_path" + if [ $? -ne 0 ]; then + failure + fi + log + log "OK, conversion produced: $patch_edited_path" + log + # set the patch to apply to the new one: + PATCH_PATH="$patch_edited_path" + return + done + if [ $found -eq 0 ]; then + log + log "OK, found nothing to convert" + log + fi + fi +} + verify_text_patch() { if [ -f "$PATCH_PATH" ]; then log @@ -157,6 +234,8 @@ fi # The main function that controls all the magic stuff # +mangle_libpath + test_if_applied verify_text_patch diff --git a/src/tools/pm_unapply b/src/tools/pm_unapply index 631777a3..d508dc0a 100644 --- a/src/tools/pm_unapply +++ b/src/tools/pm_unapply @@ -15,17 +15,23 @@ PM_LOG_FILE="$PM_VAR_DIR/patchmanager.log" PM_PATCH_BACKUP_ROOT_DIR="$PM_VAR_DIR/patches" PM_PATCH_BACKUP_DIR="$PM_PATCH_BACKUP_ROOT_DIR/$1" +SYS_BITNESS=$(/usr/bin/getconf LONG_BIT) + # Constants PATCH_NAME="unified_diff.patch" PATCH_PATH="$PATCH_DIR/$PATCH_NAME" PATCH_BACKUP="$PM_PATCH_BACKUP_DIR/$PATCH_NAME" +PATCH_EDITED_NAME="unified_diff_${SYS_BITNESS}bit.patch" +PATCH_EDITED_BACKUP="$PM_PATCH_BACKUP_DIR"/"$PATCH_EDITED_NAME" ROOT_DIR="/tmp/patchmanager" # Applications PATCH_EXEC="/usr/bin/patch" -if [ -f "$PATCH_BACKUP" ]; then +if [ -f "$PATCH_EDITED_BACKUP" ]; then + PATCH_FILE="$PATCH_EDITED_BACKUP" +elif [ -f "$PATCH_BACKUP" ]; then PATCH_FILE="$PATCH_BACKUP" else PATCH_FILE="$PATCH_PATH" @@ -111,6 +117,8 @@ log log "$(basename "$PATCH_DIR")" +log "Using PATCH_FILE=$PATCH_FILE" + if [ -f "$PATCH_FILE" ]; then log " contains text patch" fi