diff options
author | mark@chromium.org <mark@chromium.org@0039d316-1c4b-4281-b951-d872f2087c98> | 2009-11-02 23:29:39 +0000 |
---|---|---|
committer | mark@chromium.org <mark@chromium.org@0039d316-1c4b-4281-b951-d872f2087c98> | 2009-11-02 23:29:39 +0000 |
commit | fbb700c3b97898a128e30d503d80ff07202bc244 (patch) | |
tree | 2ee6988ba56efc10e8432cd77fb8cae6d2ebc9e9 /chrome/tools | |
parent | 30738548d3e61e7441518e256dfacb4c0b428128 (diff) | |
download | chromium_src-fbb700c3b97898a128e30d503d80ff07202bc244.zip chromium_src-fbb700c3b97898a128e30d503d80ff07202bc244.tar.gz chromium_src-fbb700c3b97898a128e30d503d80ff07202bc244.tar.bz2 |
rsync isn't resilient about updating symbolic link times, so catch a case that
might cause trouble and work around it.
BUG=26181
TEST=keystone_install_test.sh
It should now be possible to update Chrome when you have write permission on
all of the directories but none of the files or symbolic links on disk.
Review URL: http://codereview.chromium.org/341072
git-svn-id: svn://svn.chromium.org/chrome/trunk/src@30768 0039d316-1c4b-4281-b951-d872f2087c98
Diffstat (limited to 'chrome/tools')
-rwxr-xr-x | chrome/tools/build/mac/keystone_install.sh | 145 |
1 files changed, 143 insertions, 2 deletions
diff --git a/chrome/tools/build/mac/keystone_install.sh b/chrome/tools/build/mac/keystone_install.sh index a7352b6..4f3e9b7 100755 --- a/chrome/tools/build/mac/keystone_install.sh +++ b/chrome/tools/build/mac/keystone_install.sh @@ -22,6 +22,99 @@ set -e +# Returns 0 (true) if the parameter exists, is a symbolic link, and appears +# writeable on the basis of its POSIX permissions. This is used to determine +# writeability like test's -w primary, but -w resolves symbolic links and this +# function does not. +function is_writeable_symlink() { + SYMLINK=${1} + LINKMODE=$(stat -f %Sp "${SYMLINK}" 2> /dev/null || true) + if [ -z "${LINKMODE}" ] || [ "${LINKMODE:0:1}" != "l" ] ; then + return 1 + fi + LINKUSER=$(stat -f %u "${SYMLINK}" 2> /dev/null || true) + LINKGROUP=$(stat -f %g "${SYMLINK}" 2> /dev/null || true) + if [ -z "${LINKUSER}" ] || [ -z "${LINKGROUP}" ] ; then + return 1 + fi + + # If the users match, check the owner-write bit. + if [ ${EUID} -eq ${LINKUSER} ] ; then + if [ "${LINKMODE:2:1}" = "w" ] ; then + return 0 + fi + return 1 + fi + + # If the file's group matches any of the groups that this process is a + # member of, check the group-write bit. + GROUPMATCH= + for group in ${GROUPS[@]} ; do + if [ ${group} -eq ${LINKGROUP} ] ; then + GROUPMATCH=1 + break + fi + done + if [ -n "${GROUPMATCH}" ] ; then + if [ "${LINKMODE:5:1}" = "w" ] ; then + return 0 + fi + return 1 + fi + + # Check the other-write bit. + if [ "${LINKMODE:8:1}" = "w" ] ; then + return 0 + fi + return 1 +} + +# If SYMLINK exists and is a symbolic link, but is not writeable according to +# is_writeable_symlink, this function attempts to replace it with a new +# writeable symbolic link. If FROM does not exist, is not a symbolic link, or +# is already writeable, this function does nothing. This function always +# returns 0 (true). +function ensure_writeable_symlink() { + SYMLINK=${1} + if [ -L "${SYMLINK}" ] && ! is_writeable_symlink "${SYMLINK}" ; then + # If ${SYMLINK} refers to a directory, doing this naively might result in + # the new link being placed in that directory, instead of replacing the + # existing link. ln -fhs is supposed to handle this case, but it does so + # by unlinking (removing) the existing symbolic link before creating a new + # one. That leaves a small window during which the symbolic link is not + # present on disk at all. + # + # To avoid that possibility, a new symbolic link is created in a temporary + # location and then swapped into place with mv. An extra temporary + # directory is used to convince mv to replace the symbolic link: again, if + # the existing link refers to a directory, "mv newlink oldlink" will + # actually leave oldlink alone and place newlink into the directory. + # "mv newlink dirname(oldlink)" works as expected, but in order to replace + # oldlink, newlink must have the same basename, hence the temporary + # directory. + + TARGET=$(readlink "${SYMLINK}" 2> /dev/null || true) + if [ -z "${TARGET}" ] ; then + return 0 + fi + + SYMLINKDIR=$(dirname "${SYMLINK}") + TEMPLINKDIR="${SYMLINKDIR}/.symlink_temp.${$}.${RANDOM}" + TEMPLINK="${TEMPLINKDIR}/$(basename "${SYMLINK}")" + + # Don't bail out here if this fails. Something else will probably fail. + # Let it, it'll probably be easier to understand that failure than this + # one. + (mkdir "${TEMPLINKDIR}" && \ + ln -fhs "${TARGET}" "${TEMPLINK}" && \ + chmod -h 755 "${TEMPLINK}" && \ + mv -f "${TEMPLINK}" "${SYMLINKDIR}") || true + rm -rf "${TEMPLINKDIR}" + fi + + return 0 +} + # The argument should be the disk image path. Make sure it exists. if [ $# -lt 1 ] || [ ! -d "${1}" ]; then exit 10 @@ -78,17 +171,65 @@ if [ "${DEST}" -nt "${SRC}" ] ; then NEEDS_TOUCH=1 fi +# In some very weird and rare cases, it is possible to wind up with a user +# installation that contains symbolic links that the user does not have write +# permission over. More on how that might happen later. +# +# If a weird and rare case like this is observed, rsync will exit with an +# error when attempting to update the times on these symbolic links. rsync +# may not be intelligent enough to try creating a new symbolic link in these +# cases, but this script can be. +# +# This fix-up is not necessary when running as root, because root will always +# be able to write everything needed. +# +# The problem occurs when an administrative user first drag-installs the +# application to /Applications, resulting in the program's user being set to +# the user's own ID. If, subsequently, a .pkg package is installed over that, +# the existing directory ownership will be preserved, but file ownership will +# be changed to whateer is specified by the package, typically root. This +# applies to symbolic links as well. On a subsequent update, rsync will +# be able to copy the new files into place, because the user still has +# permission to write to the directories. If the symbolic link targets are +# not changing, though, rsync will not replace them, and they will remain +# owned by root. The user will not have permission to update the time on +# the symbolic links, resulting in an rsync error. +if [ ${EUID} -ne 0 ] ; then + # This step isn't critical. + set +e + + # Reset ${IFS} to deal with spaces in the for loop by not breaking the + # list up when they're encountered. + IFS_OLD="${IFS}" + IFS=$(printf '\n\t') + + # Only consider symbolic links in ${SRC}. If there are any other links in + # ${DEST} not present in ${SRC}, rsync will delete them as needed later. + LINKS=$(cd "${SRC}" && find . -type l) + + for link in ${LINKS} ; do + # ${link} is relative to ${SRC}. Prepending ${DEST} looks for the same + # link already on disk. + DESTLINK="${DEST}/${link}" + ensure_writeable_symlink "${DESTLINK}" + done + + # Go back to how things were. + IFS="${IFS_OLD}" + set -e +fi + # Don't use rsync -a, because -a expands to -rlptgoD. -g and -o copy owners # and groups, respectively, from the source, and that is undesirable in this # case. -D copies devices and special files; copying devices only works # when running as root, so for consistency between privileged and unprivileged # operation, this option is omitted as well. -# -c, --checksum skip based on checksum, not mod-time & size +# -I, --ignore-times don't skip files that match in size and mod-time # -l, --links copy symlinks as symlinks # -r, --recursive recurse into directories # -p, --perms preserve permissions # -t, --times preserve times -RSYNC_FLAGS="-clprt" +RSYNC_FLAGS="-Ilprt" # By copying to ${DEST}, the existing application name will be preserved, even # if the user has renamed the application on disk. Respecting the user's |