Skip to content
Snippets Groups Projects
Verified Commit 7e5df6a5 authored by Levente Polyak's avatar Levente Polyak :rocket:
Browse files

fix(db-functions): fix locking issue that overwrote the same fd


Calling `acquire_fd` in a subshell doesn't populate the global lockfd
associate array properly. This resulted in the reuse of the same fd
multiple times, effectively only holding a single latest lock and
releasing any previous.

Fix the issue by avoiding a global variable that causes issues in
subshell calls by using the file descriptor table directly. Instead
of storing file descriptors, the lookup call iterates through all fds
and checks their handle. In case a file is not yet opened, allocate the
next free fd between 4 and 1023.

Operating directly on the file descriptor table has the nice side effect
that we avoid reusing descriptors by accident in case they have been
opened for none locking purpose within the statically defined range.

Reported-by: Felix Yan's avatarFelix Yan <felixonmars@archlinux.org>
Co-authored-by: Felix Yan's avatarFelix Yan <felixonmars@archlinux.org>
Signed-off-by: Levente Polyak's avatarLevente Polyak <anthraxx@archlinux.org>
parent 725e7846
No related branches found
No related tags found
No related merge requests found
......@@ -79,35 +79,36 @@ stat_done() {
printf "${BOLD}done${ALL_OFF}\n" >&2
}
declare -A lockfd=()
readonly lockfd_start=20
if [[ -z "${LOCK_DIR}" ]]; then
die "No configuration provided where to store locks in LOCK_DIR"
fi
acquire_fd() {
local handle="${1}"
local last key next fd
local fd fd_handle
# check if already acquired
if [[ "${lockfd["${handle}"]+exists}" ]]; then
echo "${lockfd["${handle}"]}"
return
fi
# store the resolved path
handle=$(realpath -- "${handle}")
# determine last used fd
last=${lockfd_start}
for key in "${!lockfd[@]}"; do
fd="${lockfd[${key}]}"
if (( fd > last )); then
last=${fd}
# try to find open fd for handle
for fd in /dev/fd/*; do
fd_handle=$(realpath -- "${fd}")
if [[ ${handle} -ef ${fd_handle} ]]; then
fd=$(basename -- "${fd}")
printf "%s" "${fd}"
return 0
fi
done
# assign next fd
next=$(( last + 1 ))
lockfd["${handle}"]=${next}
echo "${next}"
# return first unused fd
for fd in $(seq 4 1023); do
if [[ ! -f /dev/fd/${fd} ]]; then
printf "%s" "${fd}"
return 0
fi
done
return 1
}
##
......@@ -116,24 +117,32 @@ acquire_fd() {
acquire_lock() {
local lock_mode=$1
local handle=$2
local fd
local ret
local fd message
# acquire fd from handle
fd=$(acquire_fd "${handle}")
if ! fd=$(acquire_fd "${handle}"); then
error "Failed to acquire free fd for locking"
return 1
fi
# assign busy message
message=("${@:3}")
if (( ! ${#message[@]} )); then
message=("Locking %s" "${handle}")
fi
# Only reopen the FD if it wasn't handed to us
if ! [[ "/dev/fd/${fd}" -ef "${handle}" ]]; then
mkdir -p -- "$(dirname -- "${handle}")"
eval "exec ${fd}>"'"${handle}"'
eval "exec ${fd}>>"'"${handle}"'
fi
# Acquire lock via flock on the fd
if ! flock "${lock_mode}" --nonblock "${fd}"; then
stat_busy "${@:3}"
stat_busy "${message[@]}"
if ! flock "${lock_mode}" "${fd}"; then
ret=$?
error "failed to lock %s: %s" "${handle}" "${ret}"
return ${ret}
error "Failed to acquire lock on %s" "${handle}"
return 1
fi
stat_done
fi
......
0% Loading or .
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment