[pacman-dev] [PATCH 2/2] repo-add: ensure database and signature files are always viewed in whole

Allan McRae allan at archlinux.org
Sat Nov 19 21:56:23 EST 2011


On 16/11/11 14:16, Dan McGee wrote:
> This addresses a short but sweet race condition currently existing in
> repo-add and repo-remove. We do the smart thing and zip the database to
> a location in a temporary directory and not over the original database
> directly. However, we then proceed to move this file directly from the
> temporary directory to our final location, which is more than likely a
> cross-filesystem move (/tmp on tmpfs) and thus non-atomic.
>
> Instead, zip the file to the same directory, prefixing the filename with
> '.tmp.'. We then move the file into place. This move is guaranteed to be
> atomic, so any reader of the database file will get either the old
> version, the new version, or ENOENT.
>
> We also perform a hardlink if possible instead of a move when shifting
> the old database out of the way to '.old'; this ensures there is no
> chance of a database file not existing during the whole process.
>
> Only one small race condition should now be present- when the database
> has been fully moved into place and the signature has not, you may see a
> mismatch. There seems to be no good way to address this, and it existed
> before this patch.
>
> A final note- if someone had locked-down permissions on the directory
> that the database files are in (e.g., could only write to foo.db.tar.gz,
> foo.db, foo.db.tar.gz.old, foo.db.old, and the lock file), this would
> break.
>
> Signed-off-by: Dan McGee<dan at archlinux.org>

Minor correction below.  Otherwise,

Signed-off-by: Allan


> ---
>   scripts/repo-add.sh.in |   38 ++++++++++++++++++++++++++------------
>   1 files changed, 26 insertions(+), 12 deletions(-)
>
> diff --git a/scripts/repo-add.sh.in b/scripts/repo-add.sh.in
> index 280c024..42c7334 100644
> --- a/scripts/repo-add.sh.in
> +++ b/scripts/repo-add.sh.in
> @@ -640,37 +640,51 @@ if (( success )); then
>   	msg "$(gettext "Creating updated database file '%s'")" "$REPO_DB_FILE"
>
>   	TAR_OPT=$(verify_repo_extension "$REPO_DB_FILE")
> +	# $LOCKFILE is already guaranteed to be absolute so this is safe
> +	dirname=${LOCKFILE%/*}
>   	filename=${REPO_DB_FILE##*/}
> +	# this ensures we create it on the same filesystem, making moves atomic
> +	tempname="$dirname/.tmp.$filename"
>
>   	pushd "$tmpdir/tree">/dev/null
>   	if ( shopt -s nullglob; files=(*); (( ${#files[*]} )) ); then
> -		bsdtar -c${TAR_OPT}f "$tmpdir/$filename" *
> +		bsdtar -c${TAR_OPT}f "$tempname" *
>   	else
>   		# we have no packages remaining? zip up some emptyness
>   		warning "$(gettext "No packages remain, creating empty database.")"
> -		bsdtar -c${TAR_OPT}f "$tmpdir/$filename" -T /dev/null
> +		bsdtar -c${TAR_OPT}f "$tempname" -T /dev/null
>   	fi
>   	popd>/dev/null
>
> -	create_signature "$tmpdir/$filename"
> +	create_signature "$tempname"
>
> -	[[ -f $REPO_DB_FILE ]]&&  mv -f "$REPO_DB_FILE" "${REPO_DB_FILE}.old"
> +	# hardlink or move the previous version of the database and signature to .old
> +	# extension as a backup measure
> +	if [[ -f $REPO_DB_FILE ]]; then
> +		ln -f "$REPO_DB_FILE" "$REPO_DB_FILE.old" 2>/dev/null || \
> +			mv -f "$REPO_DB_FILE" "$REPO_DB_FILE.old"
> +	fi
>   	if [[ -f $REPO_DB_FILE.sig ]]; then
> -		mv -f "$REPO_DB_FILE.sig" "$REPO_DB_FILE.old.sig"
> +		ln -f "$REPO_DB_FILE.sig" "$REPO_DB_FILE.old.sig" 2>/dev/null || \
> +			mv -f "$REPO_DB_FILE.sig" "$REPO_DB_FILE.old.sig"
>   	else
>   		rm -f "$REPO_DB_FILE.old.sig"
>   	fi
> -	[[ -f $tmpdir/$filename ]]&&  mv "$tmpdir/$filename" "$REPO_DB_FILE"
> -	[[ -f $tmpdir/$filename.sig ]]&&  mv "$tmpdir/$filename.sig" "$REPO_DB_FILE.sig"
> +
> +	# rotate the newly-created database and signature into place
> +	mv "$tempname" "$REPO_DB_FILE"
> +	if [[ -f .sig ]]; then

[[ -f $tempname.sig ]]


> +		mv "$tempname.sig" "$REPO_DB_FILE.sig"
> +	fi
> +
>   	dblink="${REPO_DB_FILE%.tar*}"
> -	target=${REPO_DB_FILE##*/}
>   	rm -f "$dblink" "$dblink.sig"
> -	ln -s "$target" "$dblink" 2>/dev/null || \
> -		ln "$target" "$dblink" 2>/dev/null || \
> +	ln -s "$filename" "$dblink" 2>/dev/null || \
> +		ln "$filename" "$dblink" 2>/dev/null || \
>   		cp "$REPO_DB_FILE" "$dblink"
>   	if [[ -f "$REPO_DB_FILE.sig" ]]; then
> -		ln -s "$target.sig" "$dblink.sig" 2>/dev/null || \
> -			ln "$target.sig" "$dblink.sig" 2>/dev/null || \
> +		ln -s "$filename.sig" "$dblink.sig" 2>/dev/null || \
> +			ln "$filename.sig" "$dblink.sig" 2>/dev/null || \
>   			cp "$REPO_DB_FILE.sig" "$dblink.sig"
>   	fi
>   else



More information about the pacman-dev mailing list