| Index: chrome/installer/mac/keystone_install.sh
|
| ===================================================================
|
| --- chrome/installer/mac/keystone_install.sh (revision 49726)
|
| +++ chrome/installer/mac/keystone_install.sh (working copy)
|
| @@ -23,12 +23,19 @@
|
| # 3 Basic sanity check destination failure (e.g. ticket points to nothing)
|
| # 4 Update driven by user ticket when a system ticket is also present
|
| # 5 Could not prepare existing installed version to receive update
|
| -# 6 rsync failed (could not assure presence of Versions directory)
|
| +# 6 Patch sanity check failure
|
| # 7 rsync failed (could not copy new versioned directory to Versions)
|
| # 8 rsync failed (could not update outer .app bundle)
|
| # 9 Could not get the version, update URL, or channel after update
|
| # 10 Updated application does not have the version number from the update
|
| # 11 ksadmin failure
|
| +# 12 dirpatcher failed for versioned directory
|
| +# 13 dirpatcher failed for outer .app bundle
|
| +#
|
| +# The following exit codes are not used by this script, but can be used to
|
| +# convey special meaning to Keystone:
|
| +# 66 (unused) success, request reboot
|
| +# 77 (unused) try installation again later
|
|
|
| set -eu
|
|
|
| @@ -74,6 +81,41 @@
|
| fi
|
| }
|
|
|
| +declare g_temp_dir
|
| +cleanup() {
|
| + local status=${?}
|
| +
|
| + trap - EXIT
|
| + trap '' HUP INT QUIT TERM
|
| +
|
| + if [[ ${status} -ge 128 ]]; then
|
| + err "Caught signal $((${status} - 128))"
|
| + fi
|
| +
|
| + if [[ -n "${g_temp_dir}" ]]; then
|
| + rm -rf "${g_temp_dir}"
|
| + fi
|
| +
|
| + exit ${status}
|
| +}
|
| +
|
| +ensure_temp_dir() {
|
| + if [[ -z "${g_temp_dir}" ]]; then
|
| + # Choose a template that won't be a dot directory. Make it safe by
|
| + # removing leading hyphens, too.
|
| + local template="${ME}"
|
| + if [[ "${template}" =~ ^[-.]+(.*)$ ]]; then
|
| + template="${BASH_REMATCH[1]}"
|
| + fi
|
| + if [[ -z "${template}" ]]; then
|
| + template="keystone_install"
|
| + fi
|
| +
|
| + g_temp_dir="$(mktemp -d -t "${template}")"
|
| + note "g_temp_dir = ${g_temp_dir}"
|
| + fi
|
| +}
|
| +
|
| # Returns 0 (true) if |symlink| exists, is a symbolic link, and appears
|
| # writable on the basis of its POSIX permissions. This is used to determine
|
| # writability like test's -w primary, but -w resolves symbolic links and this
|
| @@ -184,6 +226,57 @@
|
| return 0
|
| }
|
|
|
| +# ensure_writable_symlinks_recursive calls ensure_writable_symlink for every
|
| +# symbolic link in |directory|, recursivley.
|
| +#
|
| +# 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.
|
| +#
|
| +# 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.
|
| +ensure_writable_symlinks_recursive() {
|
| + local directory="${1}"
|
| +
|
| + # This fix-up is not necessary when running as root, because root will
|
| + # always be able to write everything needed.
|
| + if [[ ${EUID} -eq 0 ]]; then
|
| + return 0
|
| + fi
|
| +
|
| + # This step isn't critical.
|
| + local set_e=
|
| + if [[ "${-}" =~ e ]]; then
|
| + set_e="y"
|
| + set +e
|
| + fi
|
| +
|
| + # Use find -print0 with read -d $'\0' to handle even the weirdest paths.
|
| + local symlink
|
| + while IFS= read -r -d $'\0' symlink; do
|
| + ensure_writable_symlink "${symlink}"
|
| + done < <(find "${directory}" -type l -print0)
|
| +
|
| + # Go back to how things were.
|
| + if [[ -n "${set_e}" ]]; then
|
| + set -e
|
| + fi
|
| +}
|
| +
|
| # Prints the version of ksadmin, as reported by ksadmin --ksadmin-version, to
|
| # stdout. This function operates with "static" variables: it will only check
|
| # the ksadmin version once per script run. If ksadmin is old enough to not
|
| @@ -208,11 +301,11 @@
|
| # Keystone version. |check_version| should be a string of the form
|
| # "major.minor.micro.build". Returns 1 (false) if either |check_version| or
|
| # the Keystone version do not match this format.
|
| -readonly VER_RE="^([0-9]+)\\.([0-9]+)\\.([0-9]+)\\.([0-9]+)\$"
|
| +readonly KSADMIN_VERSION_RE="^([0-9]+)\\.([0-9]+)\\.([0-9]+)\\.([0-9]+)\$"
|
| is_ksadmin_version_ge() {
|
| local check_version="${1}"
|
|
|
| - if ! [[ "${check_version}" =~ ${VER_RE} ]]; then
|
| + if ! [[ "${check_version}" =~ ${KSADMIN_VERSION_RE} ]]; then
|
| return 1
|
| fi
|
|
|
| @@ -224,7 +317,7 @@
|
| local ksadmin_version
|
| ksadmin_version="$(ksadmin_version)"
|
|
|
| - if ! [[ "${ksadmin_version}" =~ ${VER_RE} ]]; then
|
| + if ! [[ "${ksadmin_version}" =~ ${KSADMIN_VERSION_RE} ]]; then
|
| return 1
|
| fi
|
|
|
| @@ -294,10 +387,13 @@
|
| # Early steps are critical. Don't continue past any failure.
|
| set -e
|
|
|
| + trap cleanup EXIT HUP INT QUIT TERM
|
| +
|
| readonly PRODUCT_NAME="Google Chrome"
|
| readonly APP_DIR="${PRODUCT_NAME}.app"
|
| readonly FRAMEWORK_NAME="${PRODUCT_NAME} Framework"
|
| readonly FRAMEWORK_DIR="${FRAMEWORK_NAME}.framework"
|
| + readonly PATCH_DIR=".patch"
|
| readonly CONTENTS_DIR="Contents"
|
| readonly APP_PLIST="${CONTENTS_DIR}/Info"
|
| readonly VERSIONS_DIR="${CONTENTS_DIR}/Versions"
|
| @@ -348,51 +444,144 @@
|
| exit 2
|
| fi
|
|
|
| - # The update to install.
|
| - local update_app="${update_dmg_mount_point}/${APP_DIR}"
|
| - note "update_app = ${update_app}"
|
| -
|
| - # Make sure that there's something to copy from, and that it's an absolute
|
| - # path.
|
| - if [[ "${update_app:0:1}" != "/" ]] ||
|
| - ! [[ -d "${update_app}" ]]; then
|
| - err "update_app must be an absolute path to a directory"
|
| + local patch_dir="${update_dmg_mount_point}/${PATCH_DIR}"
|
| + if [[ "${patch_dir:0:1}" != "/" ]]; then
|
| + note "patch_dir = ${patch_dir}"
|
| + err "patch_dir must be an absolute path"
|
| exit 2
|
| fi
|
|
|
| - # Get some information about the update.
|
| - note "reading update values"
|
| -
|
| - local update_app_plist="${update_app}/${APP_PLIST}"
|
| - note "update_app_plist = ${update_app_plist}"
|
| - local update_version_app
|
| - if ! update_version_app="$(defaults read "${update_app_plist}" \
|
| - "${APP_VERSION_KEY}")" ||
|
| - [[ -z "${update_version_app}" ]]; then
|
| - err "couldn't determine update_version_app"
|
| + # Figure out if this is an ordinary installation disk image being used as a
|
| + # full update, or a patch. A patch will have a .patch directory at the root
|
| + # of the disk image containing information about the update, tools to apply
|
| + # it, and the update contents.
|
| + local is_patch=
|
| + local dirpatcher=
|
| + if [[ -d "${patch_dir}" ]]; then
|
| + # patch_dir exists and is a directory - this is a patch update.
|
| + is_patch="y"
|
| + dirpatcher="${patch_dir}/dirpatcher.sh"
|
| + if ! [[ -x "${dirpatcher}" ]]; then
|
| + err "couldn't locate dirpatcher"
|
| + exit 6
|
| + fi
|
| + elif [[ -e "${patch_dir}" ]]; then
|
| + # patch_dir exists, but is not a directory - what's that mean?
|
| + note "patch_dir = ${patch_dir}"
|
| + err "patch_dir must be a directory"
|
| exit 2
|
| + else
|
| + # patch_dir does not exist - this is a full "installer."
|
| + patch_dir=
|
| fi
|
| - note "update_version_app = ${update_version_app}"
|
| + note "patch_dir = ${patch_dir}"
|
| + note "is_patch = ${is_patch}"
|
| + note "dirpatcher = ${dirpatcher}"
|
|
|
| - local update_ks_plist="${update_app_plist}"
|
| - note "update_ks_plist = ${update_ks_plist}"
|
| - local update_version_ks
|
| - if ! update_version_ks="$(defaults read "${update_ks_plist}" \
|
| - "${KS_VERSION_KEY}")" ||
|
| - [[ -z "${update_version_ks}" ]]; then
|
| - err "couldn't determine update_version_ks"
|
| - exit 2
|
| - fi
|
| - note "update_version_ks = ${update_version_ks}"
|
| + # The update to install.
|
|
|
| - local product_id
|
| - if ! product_id="$(defaults read "${update_ks_plist}" \
|
| - "${KS_PRODUCT_KEY}")" ||
|
| - [[ -z "${product_id}" ]]; then
|
| - err "couldn't determine product_id"
|
| - exit 2
|
| + # update_app is the path to the new version of the .app. It will only be
|
| + # set at this point for a non-patch update. It is not yet set for a patch
|
| + # update because no such directory exists yet; it will be set later when
|
| + # dirpatcher creates it.
|
| + local update_app=
|
| +
|
| + # update_version_app_old, patch_app_dir, and patch_versioned_dir will only
|
| + # be set for patch updates.
|
| + local update_version_app_old=
|
| + local patch_app_dir=
|
| + local patch_versioned_dir=
|
| +
|
| + local update_version_app update_version_ks product_id
|
| + if [[ -z "${is_patch}" ]]; then
|
| + update_app="${update_dmg_mount_point}/${APP_DIR}"
|
| + note "update_app = ${update_app}"
|
| +
|
| + # Make sure that there's something to copy from, and that it's an absolute
|
| + # path.
|
| + if [[ "${update_app:0:1}" != "/" ]] ||
|
| + ! [[ -d "${update_app}" ]]; then
|
| + err "update_app must be an absolute path to a directory"
|
| + exit 2
|
| + fi
|
| +
|
| + # Get some information about the update.
|
| + note "reading update values"
|
| +
|
| + local update_app_plist="${update_app}/${APP_PLIST}"
|
| + note "update_app_plist = ${update_app_plist}"
|
| + if ! update_version_app="$(defaults read "${update_app_plist}" \
|
| + "${APP_VERSION_KEY}")" ||
|
| + [[ -z "${update_version_app}" ]]; then
|
| + err "couldn't determine update_version_app"
|
| + exit 2
|
| + fi
|
| + note "update_version_app = ${update_version_app}"
|
| +
|
| + local update_ks_plist="${update_app_plist}"
|
| + note "update_ks_plist = ${update_ks_plist}"
|
| + if ! update_version_ks="$(defaults read "${update_ks_plist}" \
|
| + "${KS_VERSION_KEY}")" ||
|
| + [[ -z "${update_version_ks}" ]]; then
|
| + err "couldn't determine update_version_ks"
|
| + exit 2
|
| + fi
|
| + note "update_version_ks = ${update_version_ks}"
|
| +
|
| + if ! product_id="$(defaults read "${update_ks_plist}" \
|
| + "${KS_PRODUCT_KEY}")" ||
|
| + [[ -z "${product_id}" ]]; then
|
| + err "couldn't determine product_id"
|
| + exit 2
|
| + fi
|
| + note "product_id = ${product_id}"
|
| + else # [[ -n "${is_patch}" ]]
|
| + # Get some information about the update.
|
| + note "reading update values"
|
| +
|
| + if ! update_version_app_old=$(<"${patch_dir}/old_app_version") ||
|
| + [[ -z "${update_version_app_old}" ]]; then
|
| + err "couldn't determine update_version_app_old"
|
| + exit 2
|
| + fi
|
| + note "update_version_app_old = ${update_version_app_old}"
|
| +
|
| + if ! update_version_app=$(<"${patch_dir}/new_app_version") ||
|
| + [[ -z "${update_version_app}" ]]; then
|
| + err "couldn't determine update_version_app"
|
| + exit 2
|
| + fi
|
| + note "update_version_app = ${update_version_app}"
|
| +
|
| + if ! update_version_ks=$(<"${patch_dir}/new_ks_version") ||
|
| + [[ -z "${update_version_ks}" ]]; then
|
| + err "couldn't determine update_version_ks"
|
| + exit 2
|
| + fi
|
| + note "update_version_ks = ${update_version_ks}"
|
| +
|
| + if ! product_id=$(<"${patch_dir}/ks_product") ||
|
| + [[ -z "${product_id}" ]]; then
|
| + err "couldn't determine product_id"
|
| + exit 2
|
| + fi
|
| + note "product_id = ${product_id}"
|
| +
|
| + patch_app_dir="${patch_dir}/application.dirpatch"
|
| + if ! [[ -d "${patch_app_dir}" ]]; then
|
| + err "couldn't locate patch_app_dir"
|
| + exit 6
|
| + fi
|
| + note "patch_app_dir = ${patch_app_dir}"
|
| +
|
| + patch_versioned_dir=\
|
| +"${patch_dir}/version_${update_version_app_old}_${update_version_app}.dirpatch"
|
| + if ! [[ -d "${patch_versioned_dir}" ]]; then
|
| + err "couldn't locate patch_versioned_dir"
|
| + exit 6
|
| + fi
|
| + note "patch_versioned_dir = ${patch_versioned_dir}"
|
| fi
|
| - note "product_id = ${product_id}"
|
|
|
| # ksadmin is required. Keystone should have set a ${PATH} that includes it.
|
| # Check that here, so that more useful feedback can be offered in the
|
| @@ -479,6 +668,22 @@
|
| "${APP_VERSION_KEY}" || true)"
|
| note "old_version_app = ${old_version_app}"
|
|
|
| + # old_version_app is not required, because it won't be present in skeleton
|
| + # bootstrap installations, which just have an empty .app directory. Only
|
| + # require it when doing a patch update, and use it to validate that the
|
| + # patch applies to the old installed version. By definition, skeleton
|
| + # bootstraps can't be installed with patch udpates. They require the full
|
| + # application on the disk image.
|
| + if [[ -n "${is_patch}" ]]; then
|
| + if [[ -z "${old_version_app}" ]]; then
|
| + err "old_version_app required for patch"
|
| + exit 6
|
| + elif [[ "${old_version_app}" != "${update_version_app_old}" ]]; then
|
| + err "this patch does not apply to the installed version"
|
| + exit 6
|
| + fi
|
| + fi
|
| +
|
| local installed_versions_dir="${installed_app}/${VERSIONS_DIR}"
|
| note "installed_versions_dir = ${installed_versions_dir}"
|
|
|
| @@ -500,119 +705,147 @@
|
| true)"
|
| note "old_brand = ${old_brand}"
|
|
|
| - # See if the timestamp of what's currently on disk is newer than the
|
| - # update's outer .app's timestamp. rsync will copy the update's timestamp
|
| - # over, but if that timestamp isn't as recent as what's already on disk, the
|
| - # .app will need to be touched.
|
| - local needs_touch=
|
| - if [[ "${installed_app}" -nt "${update_app}" ]]; then
|
| - needs_touch="y"
|
| - fi
|
| - note "needs_touch = ${needs_touch}"
|
| + ensure_writable_symlinks_recursive "${installed_app}"
|
|
|
| - # 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
|
| - note "fixing installed symbolic links"
|
| -
|
| - # Only consider symbolic links in ${update_app}. If there are any other
|
| - # links in ${installed_app} not present in ${update_app}, rsync will
|
| - # delete them as needed later. Use find -print0 with read -d $'\0' to
|
| - # handle even the weirdest paths.
|
| - local update_link
|
| - while IFS= read -r -d $'\0' update_link; do
|
| - # ${update_link} is relative to ${update_app}. Prepending
|
| - # ${installed_app} looks for the same link already on disk.
|
| - local installed_link="${installed_app}/${update_link}"
|
| - note "ensure_writable_symlink ${installed_link}"
|
| - ensure_writable_symlink "${installed_link}"
|
| - done < <(cd "${update_app}" && find . -type l -print0)
|
| -
|
| - # Go back to how things were.
|
| - set -e
|
| - fi
|
| -
|
| # By copying to ${installed_app}, the existing application name will be
|
| # preserved, if the user has renamed the application on disk. Respecting
|
| # the user's changes is friendly.
|
|
|
| # Make sure that ${installed_versions_dir} exists, so that it can receive
|
| # the versioned directory. It may not exist if updating from an older
|
| - # version that did not use the versioned layout on disk. An rsync that
|
| - # excludes all contents is used to bring the permissions over from
|
| - # ${update_versions_dir}, otherwise, this directory would be the only one in
|
| - # the entire update exempt from getting its permissions copied over. A
|
| - # simple mkdir wouldn't copy mode bits. This is done even if
|
| - # ${installed_versions_dir} already does exist to ensure that the mode bits
|
| - # come from the update.
|
| + # version that did not use the versioned layout on disk. Later, during the
|
| + # rsync to copy the applciation directory, the mode bits and timestamp on
|
| + # ${installed_versions_dir} will be set to conform to whatever is present in
|
| + # the update.
|
| #
|
| # ${installed_app} is guaranteed to exist at this point, but
|
| # ${installed_app}/${CONTENTS_DIR} may not if things are severely broken or
|
| # if this update is actually an initial installation from a Keystone
|
| # skeleton bootstrap. The mkdir creates ${installed_app}/${CONTENTS_DIR} if
|
| # it doesn't exist; its mode bits will be fixed up in a subsequent rsync.
|
| - note "creating CONTENTS_DIR"
|
| - if ! mkdir -p "${installed_app}/${CONTENTS_DIR}"; then
|
| - err "mkdir of CONTENTS_DIR failed"
|
| + note "creating installed_versions_dir"
|
| + if ! mkdir -p "${installed_versions_dir}"; then
|
| + err "mkdir of installed_versions_dir failed"
|
| exit 5
|
| fi
|
|
|
| - local update_versions_dir="${update_app}/${VERSIONS_DIR}"
|
| - note "update_versions_dir = ${update_versions_dir}"
|
| + local new_versioned_dir
|
| + new_versioned_dir="${installed_versions_dir}/${update_version_app}"
|
| + note "new_versioned_dir = ${new_versioned_dir}"
|
|
|
| - note "rsyncing VERSIONS_DIR"
|
| - if ! rsync ${RSYNC_FLAGS} --exclude "*" "${update_versions_dir}/" \
|
| - "${installed_versions_dir}"; then
|
| - err "rsync of VERSIONS_DIR failed, status ${PIPESTATUS[0]}"
|
| - exit 6
|
| + # If there's an entry at ${new_versioned_dir} but it's not a directory
|
| + # (or it's a symbolic link, whether or not it points to a directory), rsync
|
| + # won't get rid of it. It's never correct to have a non-directory in place
|
| + # of the versioned directory, so toss out whatever's there. Don't treat
|
| + # this as a critical step: if removal fails, operation can still proceed to
|
| + # to the dirpatcher or rsync, which will likely fail.
|
| + if [[ -e "${new_versioned_dir}" ]] &&
|
| + ([[ -L "${new_versioned_dir}" ]] ||
|
| + ! [[ -d "${new_versioned_dir}" ]]); then
|
| + note "removing non-directory in place of versioned directory"
|
| + rm -f "${new_versioned_dir}" 2> /dev/null || true
|
| fi
|
|
|
| + local update_versioned_dir
|
| + if [[ -z "${is_patch}" ]]; then
|
| + update_versioned_dir="${update_app}/${VERSIONS_DIR}/${update_version_app}"
|
| + note "update_versioned_dir = ${update_versioned_dir}"
|
| + else # [[ -n "${is_patch}" ]]
|
| + # dirpatcher won't patch into a directory that already exists. Doing so
|
| + # would be a bad idea, anyway. If ${new_versioned_dir} already exists,
|
| + # it may be something left over from a previous failed or incomplete
|
| + # update attempt, or it may be the live versioned directory if this is a
|
| + # same-version update intended only to change channels. Since there's no
|
| + # way to tell, this case is handled by having dirpatcher produce the new
|
| + # versioned directory in a temporary location and then having rsync copy
|
| + # it into place as an ${update_versioned_dir}, the same as in a non-patch
|
| + # update. If ${new_versioned_dir} doesn't exist, dirpatcher can place the
|
| + # new versioned directory at that location directly.
|
| + local versioned_dir_target
|
| + if ! [[ -e "${new_versioned_dir}" ]]; then
|
| + versioned_dir_target="${new_versioned_dir}"
|
| + note "versioned_dir_target = ${versioned_dir_target}"
|
| + else
|
| + ensure_temp_dir
|
| + versioned_dir_target="${g_temp_dir}/${update_version_app}"
|
| + note "versioned_dir_target = ${versioned_dir_target}"
|
| + update_versioned_dir="${versioned_dir_target}"
|
| + note "update_versioned_dir = ${update_versioned_dir}"
|
| + fi
|
| +
|
| + note "dirpatching versioned directory"
|
| + if ! "${dirpatcher}" "${old_versioned_dir}" \
|
| + "${patch_versioned_dir}" \
|
| + "${versioned_dir_target}"; then
|
| + err "dirpatcher of versioned directory failed, status ${PIPESTATUS[0]}"
|
| + exit 12
|
| + fi
|
| + fi
|
| +
|
| # Copy the versioned directory. The new versioned directory should have a
|
| # different name than any existing one, so this won't harm anything already
|
| # present in ${installed_versions_dir}, including the versioned directory
|
| # being used by any running processes. If this step is interrupted, there
|
| # will be an incomplete versioned directory left behind, but it won't
|
| # won't interfere with anything, and it will be replaced or removed during a
|
| - # future update attempt. Note that in certain cases, same-version updates
|
| - # are distributed to move users between channels; when this happens, the
|
| - # contents of the versioned directories are identical and rsync will not
|
| - # render the versioned directory unusable even for an instant.
|
| - local update_versioned_dir new_versioned_dir
|
| - update_versioned_dir="${update_versions_dir}/${update_version_app}"
|
| - note "update_versioned_dir = ${update_versioned_dir}"
|
| - new_versioned_dir="${installed_versions_dir}/${update_version_app}"
|
| - note "new_versioned_dir = ${new_versioned_dir}"
|
| + # future update attempt.
|
| + #
|
| + # In certain cases, same-version updates are distributed to move users
|
| + # between channels; when this happens, the contents of the versioned
|
| + # directories are identical and rsync will not render the versioned
|
| + # directory unusable even for an instant.
|
| + #
|
| + # ${update_versioned_dir} may be empty during a patch update (${is_patch})
|
| + # if the dirpatcher above was able to write it into place directly. In
|
| + # that event, dirpatcher guarantees that ${new_versioned_dir} is already in
|
| + # place.
|
| + if [[ -n "${update_versioned_dir}" ]]; then
|
| + note "rsyncing versioned directory"
|
| + if ! rsync ${RSYNC_FLAGS} --delete-before "${update_versioned_dir}/" \
|
| + "${new_versioned_dir}"; then
|
| + err "rsync of versioned directory failed, status ${PIPESTATUS[0]}"
|
| + exit 7
|
| + fi
|
| + fi
|
|
|
| - note "rsyncing versioned directory"
|
| - if ! rsync ${RSYNC_FLAGS} --delete-before "${update_versioned_dir}/" \
|
| - "${new_versioned_dir}"; then
|
| - err "rsync of versioned directory failed, status ${PIPESTATUS[0]}"
|
| - exit 7
|
| + if [[ -n "${is_patch}" ]]; then
|
| + # If the versioned directory was prepared in a temporary directory and
|
| + # then rsynced into place, remove the temporary copy now that it's no
|
| + # longer needed.
|
| + if [[ -n "${update_versioned_dir}" ]]; then
|
| + rm -rf "${update_versioned_dir}" 2> /dev/null || true
|
| + update_versioned_dir=
|
| + note "update_versioned_dir = ${update_versioned_dir}"
|
| + fi
|
| +
|
| + # Prepare ${update_app}. This always needs to be done in a temporary
|
| + # location because dirpatcher won't write to a directory that already
|
| + # exists, and ${installed_app} needs to be used as input to dirpatcher
|
| + # in any event. The new application will be rsynced into place once
|
| + # dirpatcher creates it.
|
| + ensure_temp_dir
|
| + update_app="${g_temp_dir}/${APP_DIR}"
|
| + note "update_app = ${update_app}"
|
| +
|
| + note "dirpatching app directory"
|
| + if ! "${dirpatcher}" "${installed_app}" \
|
| + "${patch_app_dir}" \
|
| + "${update_app}"; then
|
| + err "dirpatcher of app directory failed, status ${PIPESTATUS[0]}"
|
| + exit 13
|
| + fi
|
| fi
|
|
|
| + # See if the timestamp of what's currently on disk is newer than the
|
| + # update's outer .app's timestamp. rsync will copy the update's timestamp
|
| + # over, but if that timestamp isn't as recent as what's already on disk, the
|
| + # .app will need to be touched.
|
| + local needs_touch=
|
| + if [[ "${installed_app}" -nt "${update_app}" ]]; then
|
| + needs_touch="y"
|
| + fi
|
| + note "needs_touch = ${needs_touch}"
|
| +
|
| # Copy the unversioned files into place, leaving everything in
|
| # ${installed_versions_dir} alone. If this step is interrupted, the
|
| # application will at least remain in a usable state, although it may not
|
| @@ -621,9 +854,11 @@
|
| # critical point is when the main executable is replaced. There isn't very
|
| # much to copy in this step, because most of the application is in the
|
| # versioned directory. This step only accounts for around 50 files, most of
|
| - # which are small localized InfoPlist.strings files.
|
| + # which are small localized InfoPlist.strings files. Note that
|
| + # ${VERSIONS_DIR} is included to copy its mode bits and timestamp, but its
|
| + # contents are excluded, having already been installed above.
|
| note "rsyncing app directory"
|
| - if ! rsync ${RSYNC_FLAGS} --delete-after --exclude "/${VERSIONS_DIR}" \
|
| + if ! rsync ${RSYNC_FLAGS} --delete-after --exclude "/${VERSIONS_DIR}/*" \
|
| "${update_app}/" "${installed_app}"; then
|
| err "rsync of app directory failed, status ${PIPESTATUS[0]}"
|
| exit 8
|
| @@ -631,6 +866,20 @@
|
|
|
| note "rsyncs complete"
|
|
|
| + if [[ -n "${is_patch}" ]]; then
|
| + # update_app has been rsynced into place and is no longer needed.
|
| + rm -rf "${update_app}" 2> /dev/null || true
|
| + update_app=
|
| + note "update_app = ${update_app}"
|
| + fi
|
| +
|
| + if [[ -n "${g_temp_dir}" ]]; then
|
| + # The temporary directory, if any, is no longer needed.
|
| + rm -rf "${g_temp_dir}" 2> /dev/null || true
|
| + g_temp_dir=
|
| + note "g_temp_dir = ${g_temp_dir}"
|
| + fi
|
| +
|
| # If necessary, touch the outermost .app so that it appears to the outside
|
| # world that something was done to the bundle. This will cause
|
| # LaunchServices to invalidate the information it has cached about the
|
| @@ -967,6 +1216,9 @@
|
|
|
| # Great success!
|
| note "done!"
|
| +
|
| + trap - EXIT
|
| +
|
| return 0
|
| }
|
|
|
|
|