From 47d80ded722151bdbea10d1b670c4b1a2262fb95 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Derzsi=20D=C3=A1niel?= Date: Fri, 29 Nov 2024 18:33:22 +0200 Subject: [PATCH 1/3] libalpm: Ignore database locks if the process that locked the database no longer exists It's quite common to get the following message after aborting a pacman command, often after system restarts: :: Synchronizing package databases... error: failed to synchronize all databases (unable to lock database) Right now, the locking mechanism is relatively rudimentary: if db.lck exists, then the database is considered locked. If something happens to the pacman process and it cannot remove the db.lck, it will be locked indefinitely, or until somebody Googles "pacman unable to lock database" and removes the lock. This commit makes db.lck keep track of the process that locked it. If subsequent pacman processes cannot find the process that originally locked the database, the database will be considered unlocked. --- lib/libalpm/handle.c | 54 ++++++++++++++++++++++++++++++++++++++++++-- 1 file changed, 52 insertions(+), 2 deletions(-) diff --git a/lib/libalpm/handle.c b/lib/libalpm/handle.c index e2f919f6..4d42119d 100644 --- a/lib/libalpm/handle.c +++ b/lib/libalpm/handle.c @@ -21,6 +21,7 @@ */ #include +#include #include #include #include @@ -135,10 +136,59 @@ int _alpm_handle_lock(alpm_handle_t *handle) FREE(dir); do { - handle->lockfd = open(handle->lockfile, O_WRONLY | O_CREAT | O_EXCL | O_CLOEXEC, 0000); + handle->lockfd = open(handle->lockfile, O_RDWR | O_CREAT | O_CLOEXEC, 0000); } while(handle->lockfd == -1 && errno == EINTR); - return (handle->lockfd >= 0 ? 0 : -1); + if(handle->lockfd < 0) { + /* failed to open lock for writing */ + return -1; + } + + /* lock opened */ + /* check the pid */ + pid_t pid = getpid(); + char buf[12]; + ssize_t bytesread = read(handle->lockfd, buf, sizeof(buf) - 1); + + buf[bytesread] = '\0'; + pid_t lockedpid; + + if(sscanf(buf, "%d", &lockedpid) == 1 && kill(lockedpid, 0) == 0 && errno != ESRCH) { + /* could not grab lock */ + /* old pid is still active */ + close(handle->lockfd); + handle->lockfd = -1; + return -1; + } + + /* write pid to lock */ + /* first allocate string for pid */ + char *pidstr = NULL; + int len = asprintf(&pidstr, "%d\n", pid); + + if(len == -1) { + _alpm_log(handle, ALPM_LOG_ERROR, "alloc failure: could not allocate pid"); + close(handle->lockfd); + unlink(handle->lockfile); + handle->lockfd = -1; + return -1; + } + + /* finally write pid to lock */ + int writtenbytes = write(handle->lockfd, pidstr, len); + + /* cleanup */ + FREE(pidstr); + + if(writtenbytes != len) { + _alpm_log(handle, ALPM_LOG_ERROR, "could not write pid to lockfile"); + close(handle->lockfd); + unlink(handle->lockfile); + handle->lockfd = -1; + return -1; + } + + return 0; } int SYMEXPORT alpm_unlock(alpm_handle_t *handle) From 52996a37276bc1f63ddd2ad3164477b3b78fbf0a Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Derzsi=20D=C3=A1niel?= Date: Fri, 29 Nov 2024 18:34:01 +0200 Subject: [PATCH 2/3] scripts: Ignore database locks if the process that locked the database no longer exists in makepkg and pacman-db-upgrade --- scripts/libmakepkg/util/lock.sh.in | 31 +++++++++++++++++++++++++++++ scripts/libmakepkg/util/meson.build | 1 + scripts/makepkg.sh.in | 11 ++++------ scripts/pacman-db-upgrade.sh.in | 14 ++++++++----- 4 files changed, 45 insertions(+), 12 deletions(-) create mode 100644 scripts/libmakepkg/util/lock.sh.in diff --git a/scripts/libmakepkg/util/lock.sh.in b/scripts/libmakepkg/util/lock.sh.in new file mode 100644 index 00000000..b7a9437d --- /dev/null +++ b/scripts/libmakepkg/util/lock.sh.in @@ -0,0 +1,31 @@ +#!/bin/bash +# +# lock.sh - functions to handle pacman locking +# +# Copyright (c) 2024 Pacman Development Team +# +# This program is free software; you can redistribute it and/or modify +# it under the terms of the GNU General Public License as published by +# the Free Software Foundation; either version 2 of the License, or +# (at your option) any later version. +# +# This program is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# GNU General Public License for more details. +# +# You should have received a copy of the GNU General Public License +# along with this program. If not, see . +# + +is_pacman_locked() { + local lockfile="$(pacman-conf DBPath)/db.lck" + if [[ -f $lockfile ]]; then + local pid=$(<"$lockfile") + # Check if the pid is valid and the process is still running + if [[ -n $pid && $pid =~ ^[0-9]+$ && -d /proc/$pid ]]; then + return 0 + fi + fi + return 1 +} \ No newline at end of file diff --git a/scripts/libmakepkg/util/meson.build b/scripts/libmakepkg/util/meson.build index a160fd06..38c02ff0 100644 --- a/scripts/libmakepkg/util/meson.build +++ b/scripts/libmakepkg/util/meson.build @@ -5,6 +5,7 @@ sources = [ 'config.sh.in', 'dirsize.sh.in', 'error.sh.in', + 'lock.sh.in', 'message.sh.in', 'option.sh.in', 'parseopts.sh.in', diff --git a/scripts/makepkg.sh.in b/scripts/makepkg.sh.in index 75df3650..a44d59c2 100644 --- a/scripts/makepkg.sh.in +++ b/scripts/makepkg.sh.in @@ -247,14 +247,11 @@ run_pacman() { else cmd=(su root -c "$cmdescape") fi - local lockfile="$(pacman-conf DBPath)/db.lck" - while [[ -f $lockfile ]]; do - local timer=0 + local timer=0 + while is_pacman_locked && (( timer < 10 )); do + (( ++timer )) msg "$(gettext "Pacman is currently in use, please wait...")" - while [[ -f $lockfile ]] && (( timer < 10 )); do - (( ++timer )) - sleep 3 - done + sleep 3 done fi "${cmd[@]}" diff --git a/scripts/pacman-db-upgrade.sh.in b/scripts/pacman-db-upgrade.sh.in index 6942cede..363060c7 100644 --- a/scripts/pacman-db-upgrade.sh.in +++ b/scripts/pacman-db-upgrade.sh.in @@ -30,6 +30,7 @@ declare -r myver='@PACKAGE_VERSION@' MAKEPKG_LIBRARY=${MAKEPKG_LIBRARY:-'@libmakepkgdir@'} # Import libmakepkg +source "$MAKEPKG_LIBRARY"/util/lock.sh source "$MAKEPKG_LIBRARY"/util/message.sh source "$MAKEPKG_LIBRARY"/util/parseopts.sh @@ -132,15 +133,18 @@ fi # strip any trailing slash from our dbroot dbroot="${dbroot%/}" + +# make sure pacman isn't running +if is_pacman_locked; then + die "$(gettext "Pacman lock file was found. Cannot run while pacman is running.")" +fi + # form the path to our lockfile location lockfile="${dbroot}/db.lck" -# make sure pacman isn't running -if [[ -f $lockfile ]]; then - die "$(gettext "Pacman lock file was found. Cannot run while pacman is running.")" -fi # do not let pacman run while we do this -touch "$lockfile" +# write our pid to the lockfile +echo $$ > "$lockfile" if [[ -f "${dbroot}"/local/ALPM_DB_VERSION ]]; then db_version=$(cat "${dbroot}"/local/ALPM_DB_VERSION) From 32de51318dfdf30e6af5415a8c48520a332bd094 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Derzsi=20D=C3=A1niel?= Date: Fri, 29 Nov 2024 18:34:19 +0200 Subject: [PATCH 3/3] test: Ignore database locks if the process that locked the database no longer exists in pmtest --- test/pacman/pmtest.py | 2 +- test/pacman/util.py | 12 ++++++++++++ 2 files changed, 13 insertions(+), 1 deletion(-) diff --git a/test/pacman/pmtest.py b/test/pacman/pmtest.py index f5f033fa..7cfc3b1d 100644 --- a/test/pacman/pmtest.py +++ b/test/pacman/pmtest.py @@ -232,7 +232,7 @@ class pmtest(object): return files def run(self, pacman): - if os.path.isfile(util.PM_LOCK): + if util.ispacmanlocked(): tap.bail("\tERROR: another pacman session is on-going -- skipping") return diff --git a/test/pacman/util.py b/test/pacman/util.py index ee844812..e5de4b81 100644 --- a/test/pacman/util.py +++ b/test/pacman/util.py @@ -184,3 +184,15 @@ def mkdir(path): elif os.path.isfile(path): raise OSError("'%s' already exists and is not a directory" % path) os.makedirs(path, 0o755) + +# +# Locking +# +def ispacmanlocked(): + if not os.path.exists(PM_LOCK): + return False + + with open(PM_LOCK, 'r') as f: + pid = f.read().strip() + + return pid.isdigit() and os.path.exists(f'/proc/{pid}')