瀏覽代碼

dpkg-maintscript-helper: Fix symlink_to_dir and dir_to_symlink commands

- Always run postinst code regardless of prior-version, as the package
  might have been never configured before.
- Be more strict when checking the expected state of paths.
- Rename subcommand shell code to check-files-ownership.
- Change dir_to_symlink switch code to use a staging empty directory,
  to avoid dpkg removing files from other packages, when removing the
  package old files during upgrade.
- Bump minimal version in man page to 1.17.5.

Closes: #731730
Guillem Jover 12 年之前
父節點
當前提交
5177119c68
共有 3 個文件被更改,包括 106 次插入30 次删除
  1. 10 0
      debian/changelog
  2. 12 6
      man/dpkg-maintscript-helper.1
  3. 84 24
      scripts/dpkg-maintscript-helper.sh

+ 10 - 0
debian/changelog

@@ -9,6 +9,16 @@ dpkg (1.17.5) UNRELEASED; urgency=low
   * Fix segfault in update-alternatives when adding or renaming slaves for
   * Fix segfault in update-alternatives when adding or renaming slaves for
     an existing alternative. Regression introduced in dpkg 1.17.2.
     an existing alternative. Regression introduced in dpkg 1.17.2.
     Closes: #731710
     Closes: #731710
+  * Fix dpkg-maintscript-helper symlink_to_dir and dir_to_symlink commands:
+    - Always run postinst code regardless of prior-version, as the package
+      might have been never configured before.
+    - Be more strict when checking the expected state of paths.
+    - Rename subcommand shell code to check-files-ownership.
+    - Change dir_to_symlink switch code to use a staging empty directory,
+      to avoid dpkg removing files from other packages, when removing the
+      package old files during upgrade.
+    - Bump minimal version in man page to 1.17.5.
+    Closes: #731730
 
 
   [ Updated dpkg translations ]
   [ Updated dpkg translations ]
   * German (Sven Joachim).
   * German (Sven Joachim).

+ 12 - 6
man/dpkg-maintscript-helper.1

@@ -15,7 +15,7 @@
 .\" You should have received a copy of the GNU General Public License
 .\" You should have received a copy of the GNU General Public License
 .\" along with this program.  If not, see <https://www.gnu.org/licenses/>.
 .\" along with this program.  If not, see <https://www.gnu.org/licenses/>.
 .
 .
-.TH dpkg\-maintscript\-helper 1 "2012-05-04" "Debian Project" "dpkg suite"
+.TH dpkg\-maintscript\-helper 1 "2013-12-11" "Debian Project" "dpkg suite"
 .SH NAME
 .SH NAME
 dpkg\-maintscript\-helper \- works around known dpkg limitations in maintainer scripts
 dpkg\-maintscript\-helper \- works around known dpkg limitations in maintainer scripts
 .
 .
@@ -197,9 +197,15 @@ of the new symlink at \fIpathname\fP.
 Current implementation: the \fBpreinst\fP checks if the directory
 Current implementation: the \fBpreinst\fP checks if the directory
 exists, does not contain conffiles, pathnames owned by other packages,
 exists, does not contain conffiles, pathnames owned by other packages,
 or locally created pathnames, if not then it's left in place, otherwise
 or locally created pathnames, if not then it's left in place, otherwise
-it's renamed to \fIpathname\fP\fB.dpkg\-backup\fP. On configuration,
-the \fBpostinst\fP removes \fIpathname\fP\fB.dpkg\-backup\fP if
-\fIpathname\fP\fB.dpkg\-backup\fP is still a directory. On
+it's renamed to \fIpathname\fP\fB.dpkg\-backup\fP, and an empty staging
+directory named \fIpathname\fP is created, marked with a file so that
+dpkg can track it. On configuration, the \fBpostinst\fP finishes the
+switch if \fIpathname\fP\fB.dpkg\-backup\fP is still a directory and
+\fIpathname\fP is the staging directory; it removes the staging directory
+mark file, moves the newly created files inside the staging directory
+to the symlink target \fInew-target\fP/, replaces the now empty staging
+directory \fIpathname\fP with a symlink to \fInew-target\fP, and
+removes \fIpathname\fP\fB.dpkg\-backup\fP. On
 abort\-upgrade/abort\-install, the \fBpostrm\fP renames
 abort\-upgrade/abort\-install, the \fBpostrm\fP renames
 \fIpathname\fP\fB.dpkg\-backup\fP back to \fIpathname\fP if required.
 \fIpathname\fP\fB.dpkg\-backup\fP back to \fIpathname\fP if required.
 .
 .
@@ -210,9 +216,9 @@ using it unconditionally requires a pre-dependency to ensure that the
 required version of \fBdpkg\fP has been unpacked before. The required version
 required version of \fBdpkg\fP has been unpacked before. The required version
 depends on the command used, for \fBrm_conffile\fP and \fBmv_conffile\fP
 depends on the command used, for \fBrm_conffile\fP and \fBmv_conffile\fP
 it is 1.15.7.2, for \fBsymlink_to_dir\fP and \fBdir_to_symlink\fP
 it is 1.15.7.2, for \fBsymlink_to_dir\fP and \fBdir_to_symlink\fP
-it is 1.17.2:
+it is 1.17.5:
 .P
 .P
-    \fBPre\-Depends:\fP dpkg (>= 1.17.2)
+    \fBPre\-Depends:\fP dpkg (>= 1.17.5)
 .P
 .P
 But in many cases the operation done by the program is not critical for
 But in many cases the operation done by the program is not critical for
 the package, and instead of using a pre-dependency we can call the
 the package, and instead of using a pre-dependency we can call the

+ 84 - 24
scripts/dpkg-maintscript-helper.sh

@@ -1,6 +1,6 @@
 #!/bin/sh
 #!/bin/sh
 #
 #
-# Copyright © 2007,2011-2012 Guillem Jover <guillem@debian.org>
+# Copyright © 2007,2011-2013 Guillem Jover <guillem@debian.org>
 # Copyright © 2010 Raphaël Hertzog <hertzog@debian.org>
 # Copyright © 2010 Raphaël Hertzog <hertzog@debian.org>
 # Copyright © 2008 Joey Hess <joeyh@debian.org>
 # Copyright © 2008 Joey Hess <joeyh@debian.org>
 # Copyright © 2005 Scott James Remnant (original implementation on www.dpkg.org)
 # Copyright © 2005 Scott James Remnant (original implementation on www.dpkg.org)
@@ -272,8 +272,13 @@ symlink_to_dir() {
 		fi
 		fi
 		;;
 		;;
 	postinst)
 	postinst)
+		# We cannot bail depending on the version, as here we only
+		# know what was the last configured version, and we might
+		# have been unpacked, then upgraded with an unpack and thus
+		# never been configured before.
 		if [ "$1" = "configure" ] && [ -h "${SYMLINK}.dpkg-backup" ] &&
 		if [ "$1" = "configure" ] && [ -h "${SYMLINK}.dpkg-backup" ] &&
-		    dpkg --compare-versions "$2" le-nl "$LASTVERSION"; then
+		   [ "$(readlink -f ${SYMLINK}.dpkg-backup)" = "$SYMLINK_TARGET" ]
+		then
 			rm -f "${SYMLINK}.dpkg-backup"
 			rm -f "${SYMLINK}.dpkg-backup"
 		fi
 		fi
 		;;
 		;;
@@ -283,7 +288,8 @@ symlink_to_dir() {
 		fi
 		fi
 		if [ "$1" = "abort-install" -o "$1" = "abort-upgrade" ] &&
 		if [ "$1" = "abort-install" -o "$1" = "abort-upgrade" ] &&
 		   [ -n "$2" ] &&
 		   [ -n "$2" ] &&
-		   [ -h "${SYMLINK}.dpkg-backup" ] && [ ! -e "$SYMLINK" ] &&
+		   [ ! -e "$SYMLINK" ] && [ -h "${SYMLINK}.dpkg-backup" ] &&
+		   [ "$(readlink -f ${SYMLINK}.dpkg-backup)" = "$SYMLINK_TARGET" ] &&
 		   dpkg --compare-versions "$2" le-nl "$LASTVERSION"; then
 		   dpkg --compare-versions "$2" le-nl "$LASTVERSION"; then
 			echo "Restoring backup of $SYMLINK ..."
 			echo "Restoring backup of $SYMLINK ..."
 			mv "${SYMLINK}.dpkg-backup" "$SYMLINK"
 			mv "${SYMLINK}.dpkg-backup" "$SYMLINK"
@@ -333,21 +339,22 @@ dir_to_symlink() {
 	case "$DPKG_MAINTSCRIPT_NAME" in
 	case "$DPKG_MAINTSCRIPT_NAME" in
 	preinst)
 	preinst)
 		if [ "$1" = "install" -o "$1" = "upgrade" ] &&
 		if [ "$1" = "install" -o "$1" = "upgrade" ] &&
-		   [ -n "$2" ] && [ -d "$PATHNAME" ] &&
+		   [ -n "$2" ] &&
+		   [ ! -h "$PATHNAME" ] && [ -d "$PATHNAME" ] &&
 		   dpkg --compare-versions "$2" le-nl "$LASTVERSION"; then
 		   dpkg --compare-versions "$2" le-nl "$LASTVERSION"; then
 			prepare_dir_to_symlink "$PACKAGE" "$PATHNAME"
 			prepare_dir_to_symlink "$PACKAGE" "$PATHNAME"
 		fi
 		fi
 		;;
 		;;
 	postinst)
 	postinst)
+		# We cannot bail depending on the version, as here we only
+		# know what was the last configured version, and we might
+		# have been unpacked, then upgraded with an unpack and thus
+		# never been configured before.
 		if [ "$1" = "configure" ] &&
 		if [ "$1" = "configure" ] &&
-		   [ -d "${PATHNAME}.dpkg-backup" ] && [ -h "$PATHNAME" ] &&
-		   [ "$(readlink -f $PATHNAME)" = "$SYMLINK_TARGET" ] &&
-		    dpkg --compare-versions "$2" le-nl "$LASTVERSION"; then
-			# By now, dpkg will have updated the symlink to point
-			# to the new location, but we are left behind the old
-			# files owned by this package in the backup directory,
-			# just remove it.
-			rm -rf "${PATHNAME}.dpkg-backup"
+		   [ -d "${PATHNAME}.dpkg-backup" ] &&
+		   [ ! -h "$PATHNAME" ] && [ -d "$PATHNAME" ] &&
+		   [ -f "$PATHNAME/.dpkg-staging-dir" ]; then
+			finish_dir_to_symlink "$PATHNAME" "$SYMLINK_TARGET"
 		fi
 		fi
 		;;
 		;;
 	postrm)
 	postrm)
@@ -356,12 +363,13 @@ dir_to_symlink() {
 		fi
 		fi
 		if [ "$1" = "abort-install" -o "$1" = "abort-upgrade" ] &&
 		if [ "$1" = "abort-install" -o "$1" = "abort-upgrade" ] &&
 		   [ -n "$2" ] &&
 		   [ -n "$2" ] &&
-		   [ -d "${PATHNAME}.dpkg-backup" ] && [ -h "$PATHNAME" ] &&
-		   [ "$(readlink -f $PATHNAME)" = "$SYMLINK_TARGET" ] &&
+		   [ -d "${PATHNAME}.dpkg-backup" ] &&
+		   [ \( ! -h "$PATHNAME" -a -d "$PATHNAME" -a \
+		        -f "$PATHNAME/.dpkg-staging-dir" \) -o \
+		     \( -h "$PATHNAME" -a \
+		        "$(readlink -f $PATHNAME)" = "$SYMLINK_TARGET" \) ] &&
 		   dpkg --compare-versions "$2" le-nl "$LASTVERSION"; then
 		   dpkg --compare-versions "$2" le-nl "$LASTVERSION"; then
-			echo "Restoring backup of $PATHNAME ..."
-			rm -f "$PATHNAME"
-			mv "${PATHNAME}.dpkg-backup" "$PATHNAME"
+			abort_dir_to_symlink "$PATHNAME"
 		fi
 		fi
 		;;
 		;;
 	*)
 	*)
@@ -391,17 +399,69 @@ prepare_dir_to_symlink()
 			return 1
 			return 1
 		fi
 		fi
 		return 0
 		return 0
-	' subcommand "$PACKAGE" || \
+	' check-files-ownership "$PACKAGE" || \
 		error "directory '$PATHNAME' contains files not owned by" \
 		error "directory '$PATHNAME' contains files not owned by" \
 		      "package $PACKAGE, cannot switch to symlink"
 		      "package $PACKAGE, cannot switch to symlink"
 
 
-	# Move the directory aside and make a temporary symlink to reduce the
-	# time the contents are not available. dpkg will not be able to remove
-	# the old files from the backup directory after unpack, because it
-	# will have updated the symlink to point to the new location already,
-	# we'll remove them ourselves later on.
+	# At this point, we know that the directory either contains no files,
+	# or only non-conffiles owned by the package.
+	#
+	# To do the switch we cannot simply replace it with the final symlink
+	# just yet, because dpkg needs to remove any file present in the old
+	# package that have disappeared in the new one, and we do not want to
+	# lose files resolving to the same pathname in the symlink target.
+	#
+	# We cannot replace the directory with a staging symlink either,
+	# because dpkg will update symlinks to their new target.
+	#
+	# So we need to create a staging directory, to avoid removing files
+	# from other packages, and to trap any new files in the directory
+	# to move them to their correct place later on.
 	mv -f "$PATHNAME" "${PATHNAME}.dpkg-backup"
 	mv -f "$PATHNAME" "${PATHNAME}.dpkg-backup"
-	ln -s "${PATHNAME}.dpkg-backup" "$PATHNAME"
+	mkdir "$PATHNAME"
+
+	# Mark it as a staging directory, so that we can track things.
+	touch "$PATHNAME/.dpkg-staging-dir"
+}
+
+finish_dir_to_symlink()
+{
+	local PATHNAME="$1"
+	local SYMLINK_TARGET="$2"
+
+	# Move the contents of the staging directory to the symlink target,
+	# as those are all new files installed between this package being
+	# unpacked and configured.
+	rm "$PATHNAME/.dpkg-staging-dir"
+	find "$PATHNAME" -mindepth 1 -print0 | \
+		xargs -0 -i% mv -f "%" "$SYMLINK_TARGET/"
+
+	# Remove the staging directory.
+	rmdir "$PATHNAME"
+
+	# Do the actual switch.
+	ln -s "$SYMLINK_TARGET" "$PATHNAME"
+
+	# We are left behind the old files owned by this package in the backup
+	# directory, just remove it.
+	rm -rf "${PATHNAME}.dpkg-backup"
+}
+
+abort_dir_to_symlink()
+{
+	local PATHNAME="$1"
+
+	echo "Restoring backup of $PATHNAME ..."
+	if [ -h "$PATHNAME" ]; then
+		rm -f "$PATHNAME"
+	else
+		# The staging directory must be empty, as no other package
+		# should have been unpacked inbetween.
+		rm -f "$PATHNAME/.dpkg-staging-dir"
+		rmdir "$PATHNAME"
+	fi
+
+	mv "${PATHNAME}.dpkg-backup" "$PATHNAME"
 }
 }
 
 
 # Common functions
 # Common functions