15821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)#!/bin/bash -p
25821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)
35821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)# Copyright (c) 2012 The Chromium Authors. All rights reserved.
45821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)# Use of this source code is governed by a BSD-style license that can be
55821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)# found in the LICENSE file.
65821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)
75821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)# usage: keystone_install.sh update_dmg_mount_point
85821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)#
95821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)# Called by the Keystone system to update the installed application with a new
105821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)# version from a disk image.
115821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)#
125821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)# Environment variables:
135821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)# GOOGLE_CHROME_UPDATER_DEBUG
145821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)#   When set to a non-empty value, additional information about this script's
155821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)#   actions will be logged to stderr.  The same debugging information will
165821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)#   also be enabled when "Library/Google/Google Chrome Updater Debug" in the
175821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)#   root directory or in ${HOME} exists.
185821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)#
195821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)# Exit codes:
205821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)#  0  Happiness
215821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)#  1  Unknown failure
225821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)#  2  Basic sanity check source failure (e.g. no app on disk image)
235821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)#  3  Basic sanity check destination failure (e.g. ticket points to nothing)
245821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)#  4  Update driven by user ticket when a system ticket is also present
255821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)#  5  Could not prepare existing installed version to receive update
265821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)#  6  Patch sanity check failure
275821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)#  7  rsync failed (could not copy new versioned directory to Versions)
285821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)#  8  rsync failed (could not update outer .app bundle)
295821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)#  9  Could not get the version, update URL, or channel after update
305821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)# 10  Updated application does not have the version number from the update
315821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)# 11  ksadmin failure
325821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)# 12  dirpatcher failed for versioned directory
335821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)# 13  dirpatcher failed for outer .app bundle
345821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)#
35f60fc993c7b081abf77ce2ffc7fcca1142c8cb01Torne (Richard Coles)# The following exit codes can be used to convey special meaning to Keystone.
36f60fc993c7b081abf77ce2ffc7fcca1142c8cb01Torne (Richard Coles)# KeystoneRegistration will present these codes to Chrome as "success."
375821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)# 66  (unused) success, request reboot
385821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)# 77  (unused) try installation again later
395821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)
405821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)set -eu
415821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)
425821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)# http://b/2290916: Keystone runs the installation with a restrictive PATH
435821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)# that only includes the directory containing ksadmin, /bin, and /usr/bin.  It
445821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)# does not include /sbin or /usr/sbin.  This script uses lsof, which is in
455821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)# /usr/sbin, and it's conceivable that it might want to use other tools in an
465821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)# sbin directory.  Adjust the path accordingly.
475821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)export PATH="${PATH}:/sbin:/usr/sbin"
485821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)
495821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)# Environment sanitization.  Clear environment variables that might impact the
505821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)# interpreter's operation.  The |bash -p| invocation on the #! line takes the
515821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)# bite out of BASH_ENV, ENV, and SHELLOPTS (among other features), but
525821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)# clearing them here ensures that they won't impact any shell scripts used as
535821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)# utility programs. SHELLOPTS is read-only and can't be unset, only
545821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)# unexported.
555821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)unset BASH_ENV CDPATH ENV GLOBIGNORE IFS POSIXLY_CORRECT
565821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)export -n SHELLOPTS
575821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)
585821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)set -o pipefail
595821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)shopt -s nullglob
605821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)
615821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)ME="$(basename "${0}")"
625821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)readonly ME
635821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)
64f60fc993c7b081abf77ce2ffc7fcca1142c8cb01Torne (Richard Coles)readonly KS_CHANNEL_KEY="KSChannelID"
65f60fc993c7b081abf77ce2ffc7fcca1142c8cb01Torne (Richard Coles)
665821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)# Workaround for http://code.google.com/p/chromium/issues/detail?id=83180#c3
675821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)# In bash 4.0, "declare VAR" no longer initializes VAR if not already set.
685821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles): ${GOOGLE_CHROME_UPDATER_DEBUG:=}
695821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)err() {
705821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)  local error="${1}"
715821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)
725821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)  local id=
735821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)  if [[ -n "${GOOGLE_CHROME_UPDATER_DEBUG}" ]]; then
745821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)    id=": ${$} $(date "+%Y-%m-%d %H:%M:%S %z")"
755821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)  fi
765821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)
775821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)  echo "${ME}${id}: ${error}" >& 2
785821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)}
795821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)
805821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)note() {
815821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)  local message="${1}"
825821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)
835821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)  if [[ -n "${GOOGLE_CHROME_UPDATER_DEBUG}" ]]; then
845821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)    err "${message}"
855821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)  fi
865821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)}
875821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)
885821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)g_temp_dir=
895821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)cleanup() {
905821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)  local status=${?}
915821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)
925821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)  trap - EXIT
935821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)  trap '' HUP INT QUIT TERM
945821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)
955821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)  if [[ ${status} -ge 128 ]]; then
965821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)    err "Caught signal $((${status} - 128))"
975821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)  fi
985821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)
995821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)  if [[ -n "${g_temp_dir}" ]]; then
1005821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)    rm -rf "${g_temp_dir}"
1015821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)  fi
1025821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)
1035821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)  exit ${status}
1045821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)}
1055821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)
1065821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)ensure_temp_dir() {
1075821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)  if [[ -z "${g_temp_dir}" ]]; then
1085821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)    # Choose a template that won't be a dot directory.  Make it safe by
1095821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)    # removing leading hyphens, too.
1105821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)    local template="${ME}"
1115821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)    if [[ "${template}" =~ ^[-.]+(.*)$ ]]; then
1125821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)      template="${BASH_REMATCH[1]}"
1135821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)    fi
1145821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)    if [[ -z "${template}" ]]; then
1155821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)      template="keystone_install"
1165821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)    fi
1175821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)
1185821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)    g_temp_dir="$(mktemp -d -t "${template}")"
1195821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)    note "g_temp_dir = ${g_temp_dir}"
1205821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)  fi
1215821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)}
1225821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)
1235821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)# Returns 0 (true) if |symlink| exists, is a symbolic link, and appears
1245821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)# writable on the basis of its POSIX permissions.  This is used to determine
1255821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)# writability like test's -w primary, but -w resolves symbolic links and this
1265821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)# function does not.
1275821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)is_writable_symlink() {
1285821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)  local symlink="${1}"
1295821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)
1305821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)  local link_mode
1315821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)  link_mode="$(stat -f %Sp "${symlink}" 2> /dev/null || true)"
1325821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)  if [[ -z "${link_mode}" ]] || [[ "${link_mode:0:1}" != "l" ]]; then
1335821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)    return 1
1345821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)  fi
1355821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)
1365821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)  local link_user link_group
1375821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)  link_user="$(stat -f %u "${symlink}" 2> /dev/null || true)"
1385821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)  link_group="$(stat -f %g "${symlink}" 2> /dev/null || true)"
1395821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)  if [[ -z "${link_user}" ]] || [[ -z "${link_group}" ]]; then
1405821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)    return 1
1415821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)  fi
1425821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)
1435821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)  # If the users match, check the owner-write bit.
1445821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)  if [[ ${EUID} -eq "${link_user}" ]]; then
1455821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)    if [[ "${link_mode:2:1}" = "w" ]]; then
1465821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)      return 0
1475821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)    fi
1485821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)    return 1
1495821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)  fi
1505821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)
1515821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)  # If the file's group matches any of the groups that this process is a
1525821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)  # member of, check the group-write bit.
1535821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)  local group_match=
1545821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)  local group
1555821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)  for group in "${GROUPS[@]}"; do
1565821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)    if [[ "${group}" -eq "${link_group}" ]]; then
1575821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)      group_match="y"
1585821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)      break
1595821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)    fi
1605821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)  done
1615821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)  if [[ -n "${group_match}" ]]; then
1625821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)    if [[ "${link_mode:5:1}" = "w" ]]; then
1635821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)      return 0
1645821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)    fi
1655821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)    return 1
1665821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)  fi
1675821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)
1685821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)  # Check the other-write bit.
1695821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)  if [[ "${link_mode:8:1}" = "w" ]]; then
1705821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)    return 0
1715821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)  fi
1725821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)
1735821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)  return 1
1745821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)}
1755821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)
1765821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)# If |symlink| exists and is a symbolic link, but is not writable according to
1775821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)# is_writable_symlink, this function attempts to replace it with a new
1785821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)# writable symbolic link.  If |symlink| does not exist, is not a symbolic
1795821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)# link, or is already writable, this function does nothing.  This function
1805821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)# always returns 0 (true).
1815821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)ensure_writable_symlink() {
1825821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)  local symlink="${1}"
1835821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)
1845821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)  if [[ -L "${symlink}" ]] && ! is_writable_symlink "${symlink}"; then
1855821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)    # If ${symlink} refers to a directory, doing this naively might result in
1865821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)    # the new link being placed in that directory, instead of replacing the
1875821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)    # existing link.  ln -fhs is supposed to handle this case, but it does so
1885821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)    # by unlinking (removing) the existing symbolic link before creating a new
1895821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)    # one.  That leaves a small window during which the symbolic link is not
1905821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)    # present on disk at all.
1915821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)    #
1925821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)    # To avoid that possibility, a new symbolic link is created in a temporary
1935821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)    # location and then swapped into place with mv.  An extra temporary
1945821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)    # directory is used to convince mv to replace the symbolic link: again, if
1955821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)    # the existing link refers to a directory, "mv newlink oldlink" will
1965821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)    # actually leave oldlink alone and place newlink into the directory.
1975821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)    # "mv newlink dirname(oldlink)" works as expected, but in order to replace
1985821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)    # oldlink, newlink must have the same basename, hence the temporary
1995821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)    # directory.
2005821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)
2015821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)    local target
2025821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)    target="$(readlink "${symlink}" 2> /dev/null || true)"
2035821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)    if [[ -z "${target}" ]]; then
2045821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)      return 0
2055821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)    fi
2065821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)
2075821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)    # Error handling strategy: if anything fails, such as the mktemp, ln,
2085821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)    # chmod, or mv, ignore the failure and return 0 (success), leaving the
2095821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)    # existing state with the non-writable symbolic link intact.  Failures
2105821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)    # in this function will be difficult to understand and diagnose, and a
2115821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)    # non-writable symbolic link is not necessarily fatal.  If something else
2125821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)    # requires a writable symbolic link, allowing it to fail when a symbolic
2135821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)    # link is not writable is easier to understand than bailing out of the
2145821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)    # script on failure here.
2155821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)
2165821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)    local symlink_dir temp_link_dir temp_link
2175821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)    symlink_dir="$(dirname "${symlink}")"
2185821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)    temp_link_dir="$(mktemp -d "${symlink_dir}/.symlink_temp.XXXXXX" || true)"
2195821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)    if [[ -z "${temp_link_dir}" ]]; then
2205821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)      return 0
2215821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)    fi
2225821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)    temp_link="${temp_link_dir}/$(basename "${symlink}")"
2235821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)
2245821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)    (ln -fhs "${target}" "${temp_link}" && \
2255821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)        chmod -h 755 "${temp_link}" && \
2265821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)        mv -f "${temp_link}" "${symlink_dir}/") || true
2275821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)    rm -rf "${temp_link_dir}"
2285821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)  fi
2295821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)
2305821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)  return 0
2315821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)}
2325821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)
2335821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)# ensure_writable_symlinks_recursive calls ensure_writable_symlink for every
2345821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)# symbolic link in |directory|, recursively.
2355821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)#
2365821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)# In some very weird and rare cases, it is possible to wind up with a user
2375821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)# installation that contains symbolic links that the user does not have write
2385821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)# permission over.  More on how that might happen later.
2395821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)#
2405821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)# If a weird and rare case like this is observed, rsync will exit with an
2415821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)# error when attempting to update the times on these symbolic links.  rsync
2425821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)# may not be intelligent enough to try creating a new symbolic link in these
2435821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)# cases, but this script can be.
2445821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)#
2455821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)# The problem occurs when an administrative user first drag-installs the
2465821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)# application to /Applications, resulting in the program's user being set to
2475821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)# the user's own ID.  If, subsequently, a .pkg package is installed over that,
2485821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)# the existing directory ownership will be preserved, but file ownership will
2495821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)# be changed to whatever is specified by the package, typically root.  This
2505821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)# applies to symbolic links as well.  On a subsequent update, rsync will be
2515821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)# able to copy the new files into place, because the user still has permission
2525821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)# to write to the directories.  If the symbolic link targets are not changing,
2535821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)# though, rsync will not replace them, and they will remain owned by root.
2545821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)# The user will not have permission to update the time on the symbolic links,
2555821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)# resulting in an rsync error.
2565821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)ensure_writable_symlinks_recursive() {
2575821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)  local directory="${1}"
2585821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)
2595821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)  # This fix-up is not necessary when running as root, because root will
2605821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)  # always be able to write everything needed.
2615821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)  if [[ ${EUID} -eq 0 ]]; then
2625821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)    return 0
2635821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)  fi
2645821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)
2655821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)  # This step isn't critical.
2665821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)  local set_e=
2675821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)  if [[ "${-}" =~ e ]]; then
2685821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)    set_e="y"
2695821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)    set +e
2705821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)  fi
2715821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)
2725821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)  # Use find -print0 with read -d $'\0' to handle even the weirdest paths.
2735821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)  local symlink
2745821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)  while IFS= read -r -d $'\0' symlink; do
2755821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)    ensure_writable_symlink "${symlink}"
2765821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)  done < <(find "${directory}" -type l -print0)
2775821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)
2785821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)  # Go back to how things were.
2795821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)  if [[ -n "${set_e}" ]]; then
2805821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)    set -e
2815821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)  fi
2825821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)}
2835821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)
2845821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)# is_version_ge accepts two version numbers, left and right, and performs a
2855821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)# piecewise comparison determining the result of left >= right, returning true
2865821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)# (0) if left >= right, and false (1) if left < right. If left or right are
2875821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)# missing components relative to the other, the missing components are assumed
2885821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)# to be 0, such that 10.6 == 10.6.0.
2895821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)is_version_ge() {
2905821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)  local left="${1}"
2915821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)  local right="${2}"
2925821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)
2935821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)  local -a left_array right_array
2945821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)  IFS=. left_array=(${left})
2955821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)  IFS=. right_array=(${right})
2965821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)
2975821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)  local left_count=${#left_array[@]}
2985821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)  local right_count=${#right_array[@]}
2995821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)  local count=${left_count}
3005821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)  if [[ ${right_count} -lt ${count} ]]; then
3015821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)    count=${right_count}
3025821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)  fi
3035821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)
3045821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)  # Compare the components piecewise, as long as there are corresponding
3055821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)  # components on each side. If left_element and right_element are unequal,
3065821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)  # a comparison can be made.
3075821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)  local index=0
3085821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)  while [[ ${index} -lt ${count} ]]; do
3095821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)    local left_element="${left_array[${index}]}"
3105821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)    local right_element="${right_array[${index}]}"
3115821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)    if [[ ${left_element} -gt ${right_element} ]]; then
3125821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)      return 0
3135821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)    elif [[ ${left_element} -lt ${right_element} ]]; then
3145821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)      return 1
3155821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)    fi
3165821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)    ((++index))
3175821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)  done
3185821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)
3195821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)  # If there are more components on the left than on the right, continue
3205821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)  # comparing, assuming 0 for each of the missing components on the right.
3215821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)  while [[ ${index} -lt ${left_count} ]]; do
3225821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)    local left_element="${left_array[${index}]}"
3235821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)    if [[ ${left_element} -gt 0 ]]; then
3245821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)      return 0
3255821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)    fi
3265821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)    ((++index))
3275821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)  done
3285821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)
3295821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)  # If there are more components on the right than on the left, continue
3305821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)  # comparing, assuming 0 for each of the missing components on the left.
3315821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)  while [[ ${index} -lt ${right_count} ]]; do
3325821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)    local right_element="${right_array[${index}]}"
3335821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)    if [[ ${right_element} -gt 0 ]]; then
3345821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)      return 1
3355821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)    fi
3365821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)    ((++index))
3375821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)  done
3385821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)
3395821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)  # Upon reaching this point, the two version numbers are semantically equal.
3405821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)  return 0
3415821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)}
3425821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)
3435821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)# Prints the OS version, as reported by sw_vers -productVersion, to stdout.
3445821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)# This function operates with "static" variables: it will only check the OS
3455821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)# version once per script run.
3465821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)g_checked_os_version=
3475821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)g_os_version=
3485821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)os_version() {
3495821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)  if [[ -z "${g_checked_os_version}" ]]; then
3505821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)    g_checked_os_version="y"
3515821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)    g_os_version="$(sw_vers -productVersion)"
3525821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)    note "g_os_version = ${g_os_version}"
3535821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)  fi
3545821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)  echo "${g_os_version}"
3555821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)  return 0
3565821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)}
3575821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)
3585821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)# Compares the running OS version against a supplied version number,
3595821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)# |check_version|, and returns 0 (true) if the running OS version is greater
3605821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)# than or equal to |check_version| according to a piece-wise comparison.
3615821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)# Returns 1 (false) if the running OS version number cannot be determined or
3625821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)# if |check_version| is greater than the running OS version. |check_version|
3635821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)# should be a string of the form "major.minor" or "major.minor.micro".
3645821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)is_os_version_ge() {
3655821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)  local check_version="${1}"
3665821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)
3675821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)  local os_version="$(os_version)"
3685821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)  is_version_ge "${os_version}" "${check_version}"
3695821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)
3705821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)  # The return value of is_version_ge is used as this function's return value.
3715821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)}
3725821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)
3735821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)# Returns 0 (true) if xattr supports -r for recursive operation.
3745821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)os_xattr_supports_r() {
3755821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)  # xattr -r is supported in Mac OS X 10.6.
3765821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)  is_os_version_ge 10.6
3775821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)
3785821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)  # The return value of is_os_version_ge is used as this function's return
3795821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)  # value.
3805821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)}
3815821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)
3825821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)# Prints the version of ksadmin, as reported by ksadmin --ksadmin-version, to
3835821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)# stdout.  This function operates with "static" variables: it will only check
3845821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)# the ksadmin version once per script run.  If ksadmin is old enough to not
3855821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)# support --ksadmin-version, or another error occurs, this function prints an
3865821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)# empty string.
3875821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)g_checked_ksadmin_version=
3885821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)g_ksadmin_version=
3895821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)ksadmin_version() {
3905821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)  if [[ -z "${g_checked_ksadmin_version}" ]]; then
3915821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)    g_checked_ksadmin_version="y"
3925821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)    g_ksadmin_version="$(ksadmin --ksadmin-version || true)"
3935821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)    note "g_ksadmin_version = ${g_ksadmin_version}"
3945821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)  fi
3955821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)  echo "${g_ksadmin_version}"
3965821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)  return 0
3975821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)}
3985821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)
3995821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)# Compares the installed ksadmin version against a supplied version number,
4005821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)# |check_version|, and returns 0 (true) if the installed Keystone version is
4015821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)# greater than or equal to |check_version| according to a piece-wise
4025821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)# comparison.  Returns 1 (false) if the installed Keystone version number
4035821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)# cannot be determined or if |check_version| is greater than the installed
4045821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)# Keystone version.  |check_version| should be a string of the form
4055821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)# "major.minor.micro.build".
4065821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)is_ksadmin_version_ge() {
4075821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)  local check_version="${1}"
4085821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)
4095821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)  local ksadmin_version="$(ksadmin_version)"
4105821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)  is_version_ge "${ksadmin_version}" "${check_version}"
4115821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)
4125821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)  # The return value of is_version_ge is used as this function's return value.
4135821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)}
4145821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)
4155821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)# Returns 0 (true) if ksadmin supports --tag.
4165821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)ksadmin_supports_tag() {
4175821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)  local ksadmin_version
4185821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)
4195821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)  ksadmin_version="$(ksadmin_version)"
4205821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)  if [[ -n "${ksadmin_version}" ]]; then
4215821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)    # A ksadmin that recognizes --ksadmin-version and provides a version
4225821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)    # number is new enough to recognize --tag.
4235821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)    return 0
4245821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)  fi
4255821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)
4265821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)  return 1
4275821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)}
4285821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)
4295821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)# Returns 0 (true) if ksadmin supports --tag-path and --tag-key.
4305821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)ksadmin_supports_tagpath_tagkey() {
4315821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)  # --tag-path and --tag-key were introduced in Keystone 1.0.7.1306.
4325821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)  is_ksadmin_version_ge 1.0.7.1306
4335821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)
4345821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)  # The return value of is_ksadmin_version_ge is used as this function's
4355821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)  # return value.
4365821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)}
4375821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)
4385821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)# Returns 0 (true) if ksadmin supports --brand-path and --brand-key.
4395821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)ksadmin_supports_brandpath_brandkey() {
4405821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)  # --brand-path and --brand-key were introduced in Keystone 1.0.8.1620.
4415821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)  is_ksadmin_version_ge 1.0.8.1620
4425821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)
4435821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)  # The return value of is_ksadmin_version_ge is used as this function's
4445821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)  # return value.
4455821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)}
4465821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)
4475821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)# Returns 0 (true) if ksadmin supports --version-path and --version-key.
4485821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)ksadmin_supports_versionpath_versionkey() {
4495821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)  # --version-path and --version-key were introduced in Keystone 1.0.9.2318.
4505821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)  is_ksadmin_version_ge 1.0.9.2318
4515821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)
4525821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)  # The return value of is_ksadmin_version_ge is used as this function's
4535821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)  # return value.
4545821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)}
4555821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)
456f60fc993c7b081abf77ce2ffc7fcca1142c8cb01Torne (Richard Coles)has_32_bit_only_cpu() {
457f60fc993c7b081abf77ce2ffc7fcca1142c8cb01Torne (Richard Coles)  local cpu_64_bit_capable="$(sysctl -n hw.cpu64bit_capable 2>/dev/null)"
458f60fc993c7b081abf77ce2ffc7fcca1142c8cb01Torne (Richard Coles)  [[ -z "${cpu_64_bit_capable}" || "${cpu_64_bit_capable}" -eq 0 ]]
459f60fc993c7b081abf77ce2ffc7fcca1142c8cb01Torne (Richard Coles)
460f60fc993c7b081abf77ce2ffc7fcca1142c8cb01Torne (Richard Coles)  # The return value of the comparison is used as this function's return
461f60fc993c7b081abf77ce2ffc7fcca1142c8cb01Torne (Richard Coles)  # value.
462f60fc993c7b081abf77ce2ffc7fcca1142c8cb01Torne (Richard Coles)}
463f60fc993c7b081abf77ce2ffc7fcca1142c8cb01Torne (Richard Coles)
4645821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)# Runs "defaults read" to obtain the value of a key in a property list. As
4655821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)# with "defaults read", an absolute path to a plist is supplied, without the
4665821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)# ".plist" extension.
4675821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)#
4685821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)# As of Mac OS X 10.8, defaults (and NSUserDefaults and CFPreferences)
4695821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)# normally communicates with cfprefsd to read and write plists. Changes to a
4705821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)# plist file aren't necessarily reflected immediately via this API family when
4715821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)# not made through this API family, because cfprefsd may return cached data
4725821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)# from a former on-disk version of a plist file instead of reading the current
4735821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)# version from disk. The old behavior can be restored by setting the
4745821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)# __CFPREFERENCES_AVOID_DAEMON environment variable, although extreme care
4755821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)# should be used because portions of the system that use this API family
4765821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)# normally and thus use cfprefsd and its cache will become unsynchronized with
4775821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)# the on-disk state.
4785821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)#
4795821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)# This function is provided to set __CFPREFERENCES_AVOID_DAEMON when calling
4805821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)# "defaults read" and thus avoid cfprefsd and its on-disk cache, and is
4815821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)# intended only to be used to read values from Info.plist files, which are not
4825821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)# preferences. The use of "defaults" for this purpose has always been
4835821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)# questionable, but there's no better option to interact with plists from
4845821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)# shell scripts. Definitely don't use infoplist_read to read preference
4855821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)# plists.
4865821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)#
4875821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)# This function exists because the update process delivers new copies of
4885821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)# Info.plist files to the disk behind cfprefsd's back, and if cfprefsd becomes
4895821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)# aware of the original version of the file for any reason (such as this
4905821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)# script reading values from it via "defaults read"), the new version of the
4915821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)# file will not be immediately effective or visible via cfprefsd after the
4925821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)# update is applied.
4935821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)infoplist_read() {
4945821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)  __CFPREFERENCES_AVOID_DAEMON=1 defaults read "${@}"
4955821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)}
4965821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)
497f60fc993c7b081abf77ce2ffc7fcca1142c8cb01Torne (Richard Coles)# When a patch update fails because the old installed copy doesn't match the
498f60fc993c7b081abf77ce2ffc7fcca1142c8cb01Torne (Richard Coles)# expected state, mark_failed_patch_update updates the Keystone ticket by
499f60fc993c7b081abf77ce2ffc7fcca1142c8cb01Torne (Richard Coles)# adding "-full" to the tag. The server will see this on a subsequent update
500f60fc993c7b081abf77ce2ffc7fcca1142c8cb01Torne (Richard Coles)# attempt and will provide a full update (as opposed to a patch) to the
501f60fc993c7b081abf77ce2ffc7fcca1142c8cb01Torne (Richard Coles)# client.
502f60fc993c7b081abf77ce2ffc7fcca1142c8cb01Torne (Richard Coles)#
503f60fc993c7b081abf77ce2ffc7fcca1142c8cb01Torne (Richard Coles)# Even if mark_failed_patch_update fails to modify the tag, the user will
504f60fc993c7b081abf77ce2ffc7fcca1142c8cb01Torne (Richard Coles)# eventually be updated. Patch updates are only provided for successive
505f60fc993c7b081abf77ce2ffc7fcca1142c8cb01Torne (Richard Coles)# releases on a particular channel, to update version o to version o+1. If a
506f60fc993c7b081abf77ce2ffc7fcca1142c8cb01Torne (Richard Coles)# patch update fails in this case, eventually version o+2 will be released,
507f60fc993c7b081abf77ce2ffc7fcca1142c8cb01Torne (Richard Coles)# and no patch update will exist to update o to o+2, so the server will
508f60fc993c7b081abf77ce2ffc7fcca1142c8cb01Torne (Richard Coles)# provide a full update package.
509f60fc993c7b081abf77ce2ffc7fcca1142c8cb01Torne (Richard Coles)mark_failed_patch_update() {
510f60fc993c7b081abf77ce2ffc7fcca1142c8cb01Torne (Richard Coles)  local product_id="${1}"
511f60fc993c7b081abf77ce2ffc7fcca1142c8cb01Torne (Richard Coles)  local want_full_installer_path="${2}"
512f60fc993c7b081abf77ce2ffc7fcca1142c8cb01Torne (Richard Coles)  local old_ks_plist="${3}"
513f60fc993c7b081abf77ce2ffc7fcca1142c8cb01Torne (Richard Coles)  local old_version_app="${4}"
514f60fc993c7b081abf77ce2ffc7fcca1142c8cb01Torne (Richard Coles)  local system_ticket="${5}"
515f60fc993c7b081abf77ce2ffc7fcca1142c8cb01Torne (Richard Coles)
516f60fc993c7b081abf77ce2ffc7fcca1142c8cb01Torne (Richard Coles)  # This step isn't critical.
517f60fc993c7b081abf77ce2ffc7fcca1142c8cb01Torne (Richard Coles)  local set_e=
518f60fc993c7b081abf77ce2ffc7fcca1142c8cb01Torne (Richard Coles)  if [[ "${-}" =~ e ]]; then
519f60fc993c7b081abf77ce2ffc7fcca1142c8cb01Torne (Richard Coles)    set_e="y"
520f60fc993c7b081abf77ce2ffc7fcca1142c8cb01Torne (Richard Coles)    set +e
521f60fc993c7b081abf77ce2ffc7fcca1142c8cb01Torne (Richard Coles)  fi
522f60fc993c7b081abf77ce2ffc7fcca1142c8cb01Torne (Richard Coles)
523f60fc993c7b081abf77ce2ffc7fcca1142c8cb01Torne (Richard Coles)  note "marking failed patch update"
524f60fc993c7b081abf77ce2ffc7fcca1142c8cb01Torne (Richard Coles)
525f60fc993c7b081abf77ce2ffc7fcca1142c8cb01Torne (Richard Coles)  local channel
526f60fc993c7b081abf77ce2ffc7fcca1142c8cb01Torne (Richard Coles)  channel="$(infoplist_read "${old_ks_plist}" "${KS_CHANNEL_KEY}" 2> /dev/null)"
527f60fc993c7b081abf77ce2ffc7fcca1142c8cb01Torne (Richard Coles)
528f60fc993c7b081abf77ce2ffc7fcca1142c8cb01Torne (Richard Coles)  local tag="${channel}"
529f60fc993c7b081abf77ce2ffc7fcca1142c8cb01Torne (Richard Coles)  local tag_key="${KS_CHANNEL_KEY}"
530f60fc993c7b081abf77ce2ffc7fcca1142c8cb01Torne (Richard Coles)  if has_32_bit_only_cpu; then
531f60fc993c7b081abf77ce2ffc7fcca1142c8cb01Torne (Richard Coles)    tag="${tag}-32bit"
532f60fc993c7b081abf77ce2ffc7fcca1142c8cb01Torne (Richard Coles)    tag_key="${tag_key}-32bit"
533f60fc993c7b081abf77ce2ffc7fcca1142c8cb01Torne (Richard Coles)  fi
534f60fc993c7b081abf77ce2ffc7fcca1142c8cb01Torne (Richard Coles)
535f60fc993c7b081abf77ce2ffc7fcca1142c8cb01Torne (Richard Coles)  tag="${tag}-full"
536f60fc993c7b081abf77ce2ffc7fcca1142c8cb01Torne (Richard Coles)  tag_key="${tag_key}-full"
537f60fc993c7b081abf77ce2ffc7fcca1142c8cb01Torne (Richard Coles)
538f60fc993c7b081abf77ce2ffc7fcca1142c8cb01Torne (Richard Coles)  note "tag = ${tag}"
539f60fc993c7b081abf77ce2ffc7fcca1142c8cb01Torne (Richard Coles)  note "tag_key = ${tag_key}"
540f60fc993c7b081abf77ce2ffc7fcca1142c8cb01Torne (Richard Coles)
541f60fc993c7b081abf77ce2ffc7fcca1142c8cb01Torne (Richard Coles)  # ${old_ks_plist}, used for --tag-path, is the Info.plist for the old
542f60fc993c7b081abf77ce2ffc7fcca1142c8cb01Torne (Richard Coles)  # version of Chrome. It may not contain the keys for the "-full" tag suffix.
543f60fc993c7b081abf77ce2ffc7fcca1142c8cb01Torne (Richard Coles)  # If it doesn't, just bail out without marking the patch update as failed.
544f60fc993c7b081abf77ce2ffc7fcca1142c8cb01Torne (Richard Coles)  local read_tag="$(infoplist_read "${old_ks_plist}" "${tag_key}" 2> /dev/null)"
545f60fc993c7b081abf77ce2ffc7fcca1142c8cb01Torne (Richard Coles)  note "read_tag = ${read_tag}"
546f60fc993c7b081abf77ce2ffc7fcca1142c8cb01Torne (Richard Coles)  if [[ -z "${read_tag}" ]]; then
547f60fc993c7b081abf77ce2ffc7fcca1142c8cb01Torne (Richard Coles)    note "couldn't mark failed patch update"
548f60fc993c7b081abf77ce2ffc7fcca1142c8cb01Torne (Richard Coles)    if [[ -n "${set_e}" ]]; then
549f60fc993c7b081abf77ce2ffc7fcca1142c8cb01Torne (Richard Coles)      set -e
550f60fc993c7b081abf77ce2ffc7fcca1142c8cb01Torne (Richard Coles)    fi
551f60fc993c7b081abf77ce2ffc7fcca1142c8cb01Torne (Richard Coles)    return 0
552f60fc993c7b081abf77ce2ffc7fcca1142c8cb01Torne (Richard Coles)  fi
553f60fc993c7b081abf77ce2ffc7fcca1142c8cb01Torne (Richard Coles)
554f60fc993c7b081abf77ce2ffc7fcca1142c8cb01Torne (Richard Coles)  # Chrome can't easily read its Keystone ticket prior to registration, and
555f60fc993c7b081abf77ce2ffc7fcca1142c8cb01Torne (Richard Coles)  # when Chrome registers with Keystone, it obliterates old tag values in its
556f60fc993c7b081abf77ce2ffc7fcca1142c8cb01Torne (Richard Coles)  # ticket. Therefore, an alternative mechanism is provided to signal to
557f60fc993c7b081abf77ce2ffc7fcca1142c8cb01Torne (Richard Coles)  # Chrome that a full installer is desired. If the .want_full_installer file
558f60fc993c7b081abf77ce2ffc7fcca1142c8cb01Torne (Richard Coles)  # is present and it contains Chrome's current version number, Chrome will
559f60fc993c7b081abf77ce2ffc7fcca1142c8cb01Torne (Richard Coles)  # include "-full" in its tag when it registers with Keystone. This allows
560f60fc993c7b081abf77ce2ffc7fcca1142c8cb01Torne (Richard Coles)  # "-full" to persist in the tag even after Chrome is relaunched, which on a
561f60fc993c7b081abf77ce2ffc7fcca1142c8cb01Torne (Richard Coles)  # user ticket, triggers a re-registration.
562f60fc993c7b081abf77ce2ffc7fcca1142c8cb01Torne (Richard Coles)  #
563f60fc993c7b081abf77ce2ffc7fcca1142c8cb01Torne (Richard Coles)  # .want_full_installer is placed immediately inside the .app bundle as a
564f60fc993c7b081abf77ce2ffc7fcca1142c8cb01Torne (Richard Coles)  # sibling to the Contents directory. In this location, it's outside of the
565f60fc993c7b081abf77ce2ffc7fcca1142c8cb01Torne (Richard Coles)  # view of the code signing and code signature verification machinery. This
566f60fc993c7b081abf77ce2ffc7fcca1142c8cb01Torne (Richard Coles)  # file can safely be added, modified, and removed without affecting the
567f60fc993c7b081abf77ce2ffc7fcca1142c8cb01Torne (Richard Coles)  # signature.
568f60fc993c7b081abf77ce2ffc7fcca1142c8cb01Torne (Richard Coles)  rm -f "${want_full_installer_path}" 2> /dev/null
569f60fc993c7b081abf77ce2ffc7fcca1142c8cb01Torne (Richard Coles)  echo "${old_version_app}" > "${want_full_installer_path}"
570f60fc993c7b081abf77ce2ffc7fcca1142c8cb01Torne (Richard Coles)
571f60fc993c7b081abf77ce2ffc7fcca1142c8cb01Torne (Richard Coles)  # See the comment below in the "setting permissions" section for an
572f60fc993c7b081abf77ce2ffc7fcca1142c8cb01Torne (Richard Coles)  # explanation of the groups and modes selected here.
573f60fc993c7b081abf77ce2ffc7fcca1142c8cb01Torne (Richard Coles)  local chmod_mode="644"
574f60fc993c7b081abf77ce2ffc7fcca1142c8cb01Torne (Richard Coles)  if [[ -z "${system_ticket}" ]] &&
575f60fc993c7b081abf77ce2ffc7fcca1142c8cb01Torne (Richard Coles)     [[ "${want_full_installer_path:0:14}" = "/Applications/" ]] &&
576f60fc993c7b081abf77ce2ffc7fcca1142c8cb01Torne (Richard Coles)     chgrp admin "${want_full_installer_path}" 2> /dev/null; then
577f60fc993c7b081abf77ce2ffc7fcca1142c8cb01Torne (Richard Coles)    chmod_mode="664"
578f60fc993c7b081abf77ce2ffc7fcca1142c8cb01Torne (Richard Coles)  fi
579f60fc993c7b081abf77ce2ffc7fcca1142c8cb01Torne (Richard Coles)  note "chmod_mode = ${chmod_mode}"
580f60fc993c7b081abf77ce2ffc7fcca1142c8cb01Torne (Richard Coles)  chmod "${chmod_mode}" "${want_full_installer_path}" 2> /dev/null
581f60fc993c7b081abf77ce2ffc7fcca1142c8cb01Torne (Richard Coles)
582f60fc993c7b081abf77ce2ffc7fcca1142c8cb01Torne (Richard Coles)  local old_ks_plist_path="${old_ks_plist}.plist"
583f60fc993c7b081abf77ce2ffc7fcca1142c8cb01Torne (Richard Coles)
584f60fc993c7b081abf77ce2ffc7fcca1142c8cb01Torne (Richard Coles)  # Using ksadmin without --register only updates specified values in the
585f60fc993c7b081abf77ce2ffc7fcca1142c8cb01Torne (Richard Coles)  # ticket, without changing other existing values.
586f60fc993c7b081abf77ce2ffc7fcca1142c8cb01Torne (Richard Coles)  local ksadmin_args=(
587f60fc993c7b081abf77ce2ffc7fcca1142c8cb01Torne (Richard Coles)    --productid "${product_id}"
588f60fc993c7b081abf77ce2ffc7fcca1142c8cb01Torne (Richard Coles)  )
589f60fc993c7b081abf77ce2ffc7fcca1142c8cb01Torne (Richard Coles)
590f60fc993c7b081abf77ce2ffc7fcca1142c8cb01Torne (Richard Coles)  if ksadmin_supports_tag; then
591f60fc993c7b081abf77ce2ffc7fcca1142c8cb01Torne (Richard Coles)    ksadmin_args+=(
592f60fc993c7b081abf77ce2ffc7fcca1142c8cb01Torne (Richard Coles)      --tag "${tag}"
593f60fc993c7b081abf77ce2ffc7fcca1142c8cb01Torne (Richard Coles)    )
594f60fc993c7b081abf77ce2ffc7fcca1142c8cb01Torne (Richard Coles)  fi
595f60fc993c7b081abf77ce2ffc7fcca1142c8cb01Torne (Richard Coles)
596f60fc993c7b081abf77ce2ffc7fcca1142c8cb01Torne (Richard Coles)  if ksadmin_supports_tagpath_tagkey; then
597f60fc993c7b081abf77ce2ffc7fcca1142c8cb01Torne (Richard Coles)    ksadmin_args+=(
598f60fc993c7b081abf77ce2ffc7fcca1142c8cb01Torne (Richard Coles)      --tag-path "${old_ks_plist_path}"
599f60fc993c7b081abf77ce2ffc7fcca1142c8cb01Torne (Richard Coles)      --tag-key "${tag_key}"
600f60fc993c7b081abf77ce2ffc7fcca1142c8cb01Torne (Richard Coles)    )
601f60fc993c7b081abf77ce2ffc7fcca1142c8cb01Torne (Richard Coles)  fi
602f60fc993c7b081abf77ce2ffc7fcca1142c8cb01Torne (Richard Coles)
603f60fc993c7b081abf77ce2ffc7fcca1142c8cb01Torne (Richard Coles)  note "ksadmin_args = ${ksadmin_args[*]}"
604f60fc993c7b081abf77ce2ffc7fcca1142c8cb01Torne (Richard Coles)
605f60fc993c7b081abf77ce2ffc7fcca1142c8cb01Torne (Richard Coles)  if ! ksadmin "${ksadmin_args[@]}"; then
606f60fc993c7b081abf77ce2ffc7fcca1142c8cb01Torne (Richard Coles)    err "ksadmin failed to mark failed patch update"
607f60fc993c7b081abf77ce2ffc7fcca1142c8cb01Torne (Richard Coles)  else
608f60fc993c7b081abf77ce2ffc7fcca1142c8cb01Torne (Richard Coles)    note "marked failed patch update"
609f60fc993c7b081abf77ce2ffc7fcca1142c8cb01Torne (Richard Coles)  fi
610f60fc993c7b081abf77ce2ffc7fcca1142c8cb01Torne (Richard Coles)
611f60fc993c7b081abf77ce2ffc7fcca1142c8cb01Torne (Richard Coles)  # Go back to how things were.
612f60fc993c7b081abf77ce2ffc7fcca1142c8cb01Torne (Richard Coles)  if [[ -n "${set_e}" ]]; then
613f60fc993c7b081abf77ce2ffc7fcca1142c8cb01Torne (Richard Coles)    set -e
614f60fc993c7b081abf77ce2ffc7fcca1142c8cb01Torne (Richard Coles)  fi
615f60fc993c7b081abf77ce2ffc7fcca1142c8cb01Torne (Richard Coles)}
616f60fc993c7b081abf77ce2ffc7fcca1142c8cb01Torne (Richard Coles)
6175821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)usage() {
6185821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)  echo "usage: ${ME} update_dmg_mount_point" >& 2
6195821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)}
6205821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)
6215821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)main() {
6225821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)  local update_dmg_mount_point="${1}"
6235821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)
6245821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)  # Early steps are critical.  Don't continue past any failure.
6255821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)  set -e
6265821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)
6275821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)  trap cleanup EXIT HUP INT QUIT TERM
6285821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)
6295821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)  readonly PRODUCT_NAME="Google Chrome"
6305821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)  readonly APP_DIR="${PRODUCT_NAME}.app"
6315821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)  readonly ALTERNATE_APP_DIR="${PRODUCT_NAME} Canary.app"
6325821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)  readonly FRAMEWORK_NAME="${PRODUCT_NAME} Framework"
6335821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)  readonly FRAMEWORK_DIR="${FRAMEWORK_NAME}.framework"
6345821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)  readonly PATCH_DIR=".patch"
6355821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)  readonly CONTENTS_DIR="Contents"
6365821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)  readonly APP_PLIST="${CONTENTS_DIR}/Info"
6375821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)  readonly VERSIONS_DIR="${CONTENTS_DIR}/Versions"
6385821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)  readonly UNROOTED_BRAND_PLIST="Library/Google/Google Chrome Brand"
6395821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)  readonly UNROOTED_DEBUG_FILE="Library/Google/Google Chrome Updater Debug"
6405821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)
6415821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)  readonly APP_VERSION_KEY="CFBundleShortVersionString"
6425821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)  readonly APP_BUNDLEID_KEY="CFBundleIdentifier"
6435821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)  readonly KS_VERSION_KEY="KSVersion"
6445821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)  readonly KS_PRODUCT_KEY="KSProductID"
6455821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)  readonly KS_URL_KEY="KSUpdateURL"
6465821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)  readonly KS_BRAND_KEY="KSBrandID"
6475821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)
6485821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)  readonly QUARANTINE_ATTR="com.apple.quarantine"
6495821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)  readonly KEYCHAIN_REAUTHORIZE_DIR=".keychain_reauthorize"
6505821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)
6515821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)  # Don't use rsync -a, because -a expands to -rlptgoD.  -g and -o copy owners
6525821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)  # and groups, respectively, from the source, and that is undesirable in this
6535821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)  # case.  -D copies devices and special files; copying devices only works
6545821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)  # when running as root, so for consistency between privileged and
6555821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)  # unprivileged operation, this option is omitted as well.
6565821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)  #  -I, --ignore-times  don't skip files that match in size and mod-time
6575821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)  #  -l, --links         copy symlinks as symlinks
6585821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)  #  -r, --recursive     recurse into directories
6595821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)  #  -p, --perms         preserve permissions
6605821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)  #  -t, --times         preserve times
6615821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)  readonly RSYNC_FLAGS="-Ilprt"
6625821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)
6635821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)  # It's difficult to get GOOGLE_CHROME_UPDATER_DEBUG set in the environment
6645821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)  # when this script is called from Keystone.  If a "debug file" exists in
6655821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)  # either the root directory or the home directory of the user who owns the
6665821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)  # ticket, turn on verbosity.  This may aid debugging.
6675821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)  if [[ -e "/${UNROOTED_DEBUG_FILE}" ]] ||
6685821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)     [[ -e ~/"${UNROOTED_DEBUG_FILE}" ]]; then
6695821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)    export GOOGLE_CHROME_UPDATER_DEBUG="y"
6705821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)  fi
6715821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)
6725821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)  note "update_dmg_mount_point = ${update_dmg_mount_point}"
6735821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)
6745821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)  # The argument should be the disk image path.  Make sure it exists and that
6755821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)  # it's an absolute path.
6765821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)  note "checking update"
6775821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)
6785821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)  if [[ -z "${update_dmg_mount_point}" ]] ||
6795821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)     [[ "${update_dmg_mount_point:0:1}" != "/" ]] ||
6805821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)     ! [[ -d "${update_dmg_mount_point}" ]]; then
6815821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)    err "update_dmg_mount_point must be an absolute path to a directory"
6825821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)    usage
6835821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)    exit 2
6845821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)  fi
6855821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)
6865821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)  local patch_dir="${update_dmg_mount_point}/${PATCH_DIR}"
6875821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)  if [[ "${patch_dir:0:1}" != "/" ]]; then
6885821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)    note "patch_dir = ${patch_dir}"
6895821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)    err "patch_dir must be an absolute path"
6905821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)    exit 2
6915821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)  fi
6925821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)
6935821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)  # Figure out if this is an ordinary installation disk image being used as a
6945821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)  # full update, or a patch.  A patch will have a .patch directory at the root
6955821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)  # of the disk image containing information about the update, tools to apply
6965821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)  # it, and the update contents.
6975821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)  local is_patch=
6985821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)  local dirpatcher=
6995821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)  if [[ -d "${patch_dir}" ]]; then
7005821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)    # patch_dir exists and is a directory - this is a patch update.
7015821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)    is_patch="y"
7025821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)    dirpatcher="${patch_dir}/dirpatcher.sh"
7035821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)    if ! [[ -x "${dirpatcher}" ]]; then
7045821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)      err "couldn't locate dirpatcher"
7055821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)      exit 6
7065821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)    fi
7075821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)  elif [[ -e "${patch_dir}" ]]; then
7085821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)    # patch_dir exists, but is not a directory - what's that mean?
7095821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)    note "patch_dir = ${patch_dir}"
7105821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)    err "patch_dir must be a directory"
7115821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)    exit 2
7125821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)  else
7135821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)    # patch_dir does not exist - this is a full "installer."
7145821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)    patch_dir=
7155821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)  fi
7165821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)  note "patch_dir = ${patch_dir}"
7175821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)  note "is_patch = ${is_patch}"
7185821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)  note "dirpatcher = ${dirpatcher}"
7195821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)
7205821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)  # The update to install.
7215821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)
7225821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)  # update_app is the path to the new version of the .app.  It will only be
7235821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)  # set at this point for a non-patch update.  It is not yet set for a patch
7245821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)  # update because no such directory exists yet; it will be set later when
7255821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)  # dirpatcher creates it.
7265821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)  local update_app=
7275821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)
7285821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)  # update_version_app_old, patch_app_dir, and patch_versioned_dir will only
7295821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)  # be set for patch updates.
7305821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)  local update_version_app_old=
7315821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)  local patch_app_dir=
7325821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)  local patch_versioned_dir=
7335821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)
7345821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)  local update_version_app update_version_ks product_id
7355821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)  if [[ -z "${is_patch}" ]]; then
7365821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)    update_app="${update_dmg_mount_point}/${APP_DIR}"
7375821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)    note "update_app = ${update_app}"
7385821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)
7395821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)    # Make sure that it's an absolute path.
7405821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)    if [[ "${update_app:0:1}" != "/" ]]; then
7415821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)      err "update_app must be an absolute path"
7425821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)      exit 2
7435821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)    fi
7445821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)
7455821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)    # Make sure there's something to copy from.
7465821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)    if ! [[ -d "${update_app}" ]]; then
7475821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)      update_app="${update_dmg_mount_point}/${ALTERNATE_APP_DIR}"
7485821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)      note "update_app = ${update_app}"
7495821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)
7505821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)      if [[ "${update_app:0:1}" != "/" ]]; then
7515821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)        err "update_app (alternate) must be an absolute path"
7525821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)        exit 2
7535821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)      fi
7545821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)
7555821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)      if ! [[ -d "${update_app}" ]]; then
7565821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)        err "update_app must be a directory"
7575821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)        exit 2
7585821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)      fi
7595821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)    fi
7605821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)
7615821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)    # Get some information about the update.
7625821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)    note "reading update values"
7635821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)
7645821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)    local update_app_plist="${update_app}/${APP_PLIST}"
7655821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)    note "update_app_plist = ${update_app_plist}"
7665821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)    if ! update_version_app="$(infoplist_read "${update_app_plist}" \
7675821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)                                              "${APP_VERSION_KEY}")" ||
7685821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)       [[ -z "${update_version_app}" ]]; then
7695821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)      err "couldn't determine update_version_app"
7705821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)      exit 2
7715821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)    fi
7725821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)    note "update_version_app = ${update_version_app}"
7735821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)
7745821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)    local update_ks_plist="${update_app_plist}"
7755821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)    note "update_ks_plist = ${update_ks_plist}"
7765821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)    if ! update_version_ks="$(infoplist_read "${update_ks_plist}" \
7775821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)                                             "${KS_VERSION_KEY}")" ||
7785821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)       [[ -z "${update_version_ks}" ]]; then
7795821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)      err "couldn't determine update_version_ks"
7805821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)      exit 2
7815821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)    fi
7825821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)    note "update_version_ks = ${update_version_ks}"
7835821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)
7845821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)    if ! product_id="$(infoplist_read "${update_ks_plist}" \
7855821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)                                      "${KS_PRODUCT_KEY}")" ||
7865821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)       [[ -z "${product_id}" ]]; then
7875821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)      err "couldn't determine product_id"
7885821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)      exit 2
7895821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)    fi
7905821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)    note "product_id = ${product_id}"
7915821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)  else  # [[ -n "${is_patch}" ]]
7925821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)    # Get some information about the update.
7935821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)    note "reading update values"
7945821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)
7955821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)    if ! update_version_app_old=$(<"${patch_dir}/old_app_version") ||
7965821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)       [[ -z "${update_version_app_old}" ]]; then
7975821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)      err "couldn't determine update_version_app_old"
7985821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)      exit 2
7995821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)    fi
8005821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)    note "update_version_app_old = ${update_version_app_old}"
8015821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)
8025821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)    if ! update_version_app=$(<"${patch_dir}/new_app_version") ||
8035821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)       [[ -z "${update_version_app}" ]]; then
8045821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)      err "couldn't determine update_version_app"
8055821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)      exit 2
8065821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)    fi
8075821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)    note "update_version_app = ${update_version_app}"
8085821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)
8095821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)    if ! update_version_ks=$(<"${patch_dir}/new_ks_version") ||
8105821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)       [[ -z "${update_version_ks}" ]]; then
8115821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)      err "couldn't determine update_version_ks"
8125821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)      exit 2
8135821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)    fi
8145821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)    note "update_version_ks = ${update_version_ks}"
8155821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)
8165821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)    if ! product_id=$(<"${patch_dir}/ks_product") ||
8175821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)       [[ -z "${product_id}" ]]; then
8185821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)      err "couldn't determine product_id"
8195821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)      exit 2
8205821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)    fi
8215821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)    note "product_id = ${product_id}"
8225821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)
8235821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)    patch_app_dir="${patch_dir}/application.dirpatch"
8245821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)    if ! [[ -d "${patch_app_dir}" ]]; then
8255821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)      err "couldn't locate patch_app_dir"
8265821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)      exit 6
8275821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)    fi
8285821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)    note "patch_app_dir = ${patch_app_dir}"
8295821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)
8305821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)    patch_versioned_dir=\
8315821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)"${patch_dir}/version_${update_version_app_old}_${update_version_app}.dirpatch"
8325821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)    if ! [[ -d "${patch_versioned_dir}" ]]; then
8335821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)      err "couldn't locate patch_versioned_dir"
8345821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)      exit 6
8355821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)    fi
8365821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)    note "patch_versioned_dir = ${patch_versioned_dir}"
8375821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)  fi
8385821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)
8395821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)  # ksadmin is required. Keystone should have set a ${PATH} that includes it.
8405821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)  # Check that here, so that more useful feedback can be offered in the
8415821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)  # unlikely event that ksadmin is missing.
8425821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)  note "checking Keystone"
8435821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)
8445821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)  local ksadmin_path
8455821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)  if ! ksadmin_path="$(type -p ksadmin)" || [[ -z "${ksadmin_path}" ]]; then
8465821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)    err "couldn't locate ksadmin_path"
8475821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)    exit 3
8485821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)  fi
8495821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)  note "ksadmin_path = ${ksadmin_path}"
8505821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)
8515821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)  # Call ksadmin_version once to prime the global state.  This is needed
8525821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)  # because subsequent calls to ksadmin_version that occur in $(...)
8535821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)  # expansions will not affect the global state (although they can read from
8545821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)  # the already-initialized global state) and thus will cause a new ksadmin
8555821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)  # --ksadmin-version process to run for each check unless the globals have
8565821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)  # been properly initialized beforehand.
8575821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)  ksadmin_version >& /dev/null || true
8585821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)  local ksadmin_version_string
8595821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)  ksadmin_version_string="$(ksadmin_version 2> /dev/null || true)"
8605821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)  note "ksadmin_version_string = ${ksadmin_version_string}"
8615821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)
8625821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)  # Figure out where to install.
8635821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)  local installed_app
8645821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)  if ! installed_app="$(ksadmin -pP "${product_id}" | sed -Ene \
8655821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)      "s%^[[:space:]]+xc=<KSPathExistenceChecker:.* path=(/.+)>\$%\\1%p")" ||
8665821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)      [[ -z "${installed_app}" ]]; then
8675821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)    err "couldn't locate installed_app"
8685821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)    exit 3
8695821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)  fi
8705821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)  note "installed_app = ${installed_app}"
8715821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)
872f60fc993c7b081abf77ce2ffc7fcca1142c8cb01Torne (Richard Coles)  local want_full_installer_path="${installed_app}/.want_full_installer"
873f60fc993c7b081abf77ce2ffc7fcca1142c8cb01Torne (Richard Coles)  note "want_full_installer_path = ${want_full_installer_path}"
874f60fc993c7b081abf77ce2ffc7fcca1142c8cb01Torne (Richard Coles)
8755821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)  if [[ "${installed_app:0:1}" != "/" ]] ||
8765821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)     ! [[ -d "${installed_app}" ]]; then
8775821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)    err "installed_app must be an absolute path to a directory"
8785821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)    exit 3
8795821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)  fi
8805821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)
8815821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)  # If this script is running as root, it's being driven by a system ticket.
8825821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)  # Otherwise, it's being driven by a user ticket.
8835821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)  local system_ticket=
8845821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)  if [[ ${EUID} -eq 0 ]]; then
8855821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)    system_ticket="y"
8865821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)  fi
8875821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)  note "system_ticket = ${system_ticket}"
8885821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)
8895821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)  # If this script is being driven by a user ticket, but a system ticket is
8905821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)  # also present, there's a potential for the two to collide.  Both ticket
8915821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)  # types might be present if another user on the system promoted the ticket
8925821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)  # to system: the other user could not have removed this user's user ticket.
8935821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)  # Handle that case here by deleting the user ticket and exiting early with
8945821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)  # a discrete exit code.
8955821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)  #
8965821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)  # Current versions of ksadmin will exit 1 (false) when asked to print tickets
8975821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)  # and given a specific product ID to print.  Older versions of ksadmin would
8985821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)  # exit 0 (true), but those same versions did not support -S (meaning to check
8995821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)  # the system ticket store) and would exit 1 (false) with this invocation due
9005821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)  # to not understanding the question.  Therefore, the usage here will only
9015821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)  # delete the existing user ticket when running as non-root with access to a
9025821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)  # sufficiently recent ksadmin.  Older ksadmins are tolerated: the update will
9035821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)  # likely fail for another reason and the user ticket will hang around until
9045821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)  # something is eventually able to remove it.
9055821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)  if [[ -z "${system_ticket}" ]] &&
9065821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)     ksadmin -S --print-tickets --productid "${product_id}" >& /dev/null; then
9075821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)    ksadmin --delete --productid "${product_id}" || true
9085821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)    err "can't update on a user ticket when a system ticket is also present"
9095821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)    exit 4
9105821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)  fi
9115821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)
9125821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)  # Figure out what the existing installed application is using for its
9135821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)  # versioned directory.  This will be used later, to avoid removing the
9145821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)  # existing installed version's versioned directory in case anything is still
9155821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)  # using it.
9165821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)  note "reading install values"
9175821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)
9185821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)  local installed_app_plist="${installed_app}/${APP_PLIST}"
9195821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)  note "installed_app_plist = ${installed_app_plist}"
9205821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)  local installed_app_plist_path="${installed_app_plist}.plist"
9215821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)  note "installed_app_plist_path = ${installed_app_plist_path}"
9225821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)  local old_version_app
9235821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)  old_version_app="$(infoplist_read "${installed_app_plist}" \
9245821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)                                    "${APP_VERSION_KEY}" || true)"
9255821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)  note "old_version_app = ${old_version_app}"
9265821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)
9275821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)  # old_version_app is not required, because it won't be present in skeleton
9285821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)  # bootstrap installations, which just have an empty .app directory.  Only
9295821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)  # require it when doing a patch update, and use it to validate that the
9305821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)  # patch applies to the old installed version.  By definition, skeleton
9315821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)  # bootstraps can't be installed with patch updates.  They require the full
9325821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)  # application on the disk image.
9335821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)  if [[ -n "${is_patch}" ]]; then
9345821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)    if [[ -z "${old_version_app}" ]]; then
9355821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)      err "old_version_app required for patch"
9365821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)      exit 6
9375821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)    elif [[ "${old_version_app}" != "${update_version_app_old}" ]]; then
9385821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)      err "this patch does not apply to the installed version"
9395821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)      exit 6
9405821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)    fi
9415821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)  fi
9425821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)
9435821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)  local installed_versions_dir="${installed_app}/${VERSIONS_DIR}"
9445821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)  note "installed_versions_dir = ${installed_versions_dir}"
9455821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)
9465821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)  # If the installed application is incredibly old, old_versioned_dir may not
9475821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)  # exist.
9485821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)  local old_versioned_dir
9495821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)  if [[ -n "${old_version_app}" ]]; then
9505821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)    old_versioned_dir="${installed_versions_dir}/${old_version_app}"
9515821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)  fi
9525821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)  note "old_versioned_dir = ${old_versioned_dir}"
9535821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)
9545821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)  # Collect the installed application's brand code, it will be used later.  It
9555821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)  # is not an error for the installed application to not have a brand code.
9565821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)  local old_ks_plist="${installed_app_plist}"
9575821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)  note "old_ks_plist = ${old_ks_plist}"
9585821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)  local old_brand
9595821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)  old_brand="$(infoplist_read "${old_ks_plist}" \
9605821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)                              "${KS_BRAND_KEY}" 2> /dev/null ||
9615821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)               true)"
9625821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)  note "old_brand = ${old_brand}"
9635821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)
9645821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)  ensure_writable_symlinks_recursive "${installed_app}"
9655821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)
9665821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)  # By copying to ${installed_app}, the existing application name will be
9675821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)  # preserved, if the user has renamed the application on disk.  Respecting
9685821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)  # the user's changes is friendly.
9695821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)
9705821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)  # Make sure that ${installed_versions_dir} exists, so that it can receive
9715821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)  # the versioned directory.  It may not exist if updating from an older
9725821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)  # version that did not use the versioned layout on disk.  Later, during the
9735821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)  # rsync to copy the application directory, the mode bits and timestamp on
9745821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)  # ${installed_versions_dir} will be set to conform to whatever is present in
9755821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)  # the update.
9765821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)  #
9775821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)  # ${installed_app} is guaranteed to exist at this point, but
9785821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)  # ${installed_app}/${CONTENTS_DIR} may not if things are severely broken or
9795821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)  # if this update is actually an initial installation from a Keystone
9805821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)  # skeleton bootstrap.  The mkdir creates ${installed_app}/${CONTENTS_DIR} if
9815821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)  # it doesn't exist; its mode bits will be fixed up in a subsequent rsync.
9825821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)  note "creating installed_versions_dir"
9835821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)  if ! mkdir -p "${installed_versions_dir}"; then
9845821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)    err "mkdir of installed_versions_dir failed"
9855821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)    exit 5
9865821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)  fi
9875821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)
9885821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)  local new_versioned_dir
9895821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)  new_versioned_dir="${installed_versions_dir}/${update_version_app}"
9905821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)  note "new_versioned_dir = ${new_versioned_dir}"
9915821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)
9925821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)  # If there's an entry at ${new_versioned_dir} but it's not a directory
9935821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)  # (or it's a symbolic link, whether or not it points to a directory), rsync
9945821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)  # won't get rid of it.  It's never correct to have a non-directory in place
9955821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)  # of the versioned directory, so toss out whatever's there.  Don't treat
9965821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)  # this as a critical step: if removal fails, operation can still proceed to
9975821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)  # to the dirpatcher or rsync, which will likely fail.
9985821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)  if [[ -e "${new_versioned_dir}" ]] &&
9995821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)     ([[ -L "${new_versioned_dir}" ]] ||
10005821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)      ! [[ -d "${new_versioned_dir}" ]]); then
10015821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)    note "removing non-directory in place of versioned directory"
10025821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)    rm -f "${new_versioned_dir}" 2> /dev/null || true
10035821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)  fi
10045821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)
10055821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)  local update_versioned_dir
10065821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)  if [[ -z "${is_patch}" ]]; then
10075821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)    update_versioned_dir="${update_app}/${VERSIONS_DIR}/${update_version_app}"
10085821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)    note "update_versioned_dir = ${update_versioned_dir}"
10095821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)  else  # [[ -n "${is_patch}" ]]
10105821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)    # dirpatcher won't patch into a directory that already exists.  Doing so
10115821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)    # would be a bad idea, anyway.  If ${new_versioned_dir} already exists,
10125821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)    # it may be something left over from a previous failed or incomplete
10135821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)    # update attempt, or it may be the live versioned directory if this is a
10145821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)    # same-version update intended only to change channels.  Since there's no
10155821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)    # way to tell, this case is handled by having dirpatcher produce the new
10165821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)    # versioned directory in a temporary location and then having rsync copy
10175821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)    # it into place as an ${update_versioned_dir}, the same as in a non-patch
10185821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)    # update.  If ${new_versioned_dir} doesn't exist, dirpatcher can place the
10195821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)    # new versioned directory at that location directly.
10205821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)    local versioned_dir_target
10215821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)    if ! [[ -e "${new_versioned_dir}" ]]; then
10225821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)      versioned_dir_target="${new_versioned_dir}"
10235821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)      note "versioned_dir_target = ${versioned_dir_target}"
10245821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)    else
10255821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)      ensure_temp_dir
10265821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)      versioned_dir_target="${g_temp_dir}/${update_version_app}"
10275821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)      note "versioned_dir_target = ${versioned_dir_target}"
10285821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)      update_versioned_dir="${versioned_dir_target}"
10295821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)      note "update_versioned_dir = ${update_versioned_dir}"
10305821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)    fi
10315821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)
10325821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)    note "dirpatching versioned directory"
10335821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)    if ! "${dirpatcher}" "${old_versioned_dir}" \
10345821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)                         "${patch_versioned_dir}" \
10355821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)                         "${versioned_dir_target}"; then
10365821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)      err "dirpatcher of versioned directory failed, status ${PIPESTATUS[0]}"
1037f60fc993c7b081abf77ce2ffc7fcca1142c8cb01Torne (Richard Coles)      mark_failed_patch_update "${product_id}" \
1038f60fc993c7b081abf77ce2ffc7fcca1142c8cb01Torne (Richard Coles)                               "${want_full_installer_path}" \
1039f60fc993c7b081abf77ce2ffc7fcca1142c8cb01Torne (Richard Coles)                               "${old_ks_plist}" \
1040f60fc993c7b081abf77ce2ffc7fcca1142c8cb01Torne (Richard Coles)                               "${old_version_app}" \
1041f60fc993c7b081abf77ce2ffc7fcca1142c8cb01Torne (Richard Coles)                               "${system_ticket}"
10425821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)      exit 12
10435821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)    fi
10445821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)  fi
10455821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)
10465821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)  # Copy the versioned directory.  The new versioned directory should have a
10475821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)  # different name than any existing one, so this won't harm anything already
10485821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)  # present in ${installed_versions_dir}, including the versioned directory
10495821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)  # being used by any running processes.  If this step is interrupted, there
10505821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)  # will be an incomplete versioned directory left behind, but it won't
10515821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)  # won't interfere with anything, and it will be replaced or removed during a
10525821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)  # future update attempt.
10535821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)  #
10545821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)  # In certain cases, same-version updates are distributed to move users
10555821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)  # between channels; when this happens, the contents of the versioned
10565821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)  # directories are identical and rsync will not render the versioned
10575821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)  # directory unusable even for an instant.
10585821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)  #
10595821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)  # ${update_versioned_dir} may be empty during a patch update (${is_patch})
10605821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)  # if the dirpatcher above was able to write it into place directly.  In
10615821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)  # that event, dirpatcher guarantees that ${new_versioned_dir} is already in
10625821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)  # place.
10635821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)  if [[ -n "${update_versioned_dir}" ]]; then
10645821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)    note "rsyncing versioned directory"
10655821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)    if ! rsync ${RSYNC_FLAGS} --delete-before "${update_versioned_dir}/" \
10665821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)                                              "${new_versioned_dir}"; then
10675821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)      err "rsync of versioned directory failed, status ${PIPESTATUS[0]}"
10685821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)      exit 7
10695821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)    fi
10705821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)  fi
10715821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)
10725821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)  if [[ -n "${is_patch}" ]]; then
10735821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)    # If the versioned directory was prepared in a temporary directory and
10745821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)    # then rsynced into place, remove the temporary copy now that it's no
10755821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)    # longer needed.
10765821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)    if [[ -n "${update_versioned_dir}" ]]; then
10775821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)      rm -rf "${update_versioned_dir}" 2> /dev/null || true
10785821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)      update_versioned_dir=
10795821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)      note "update_versioned_dir = ${update_versioned_dir}"
10805821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)    fi
10815821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)
10825821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)    # Prepare ${update_app}.  This always needs to be done in a temporary
10835821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)    # location because dirpatcher won't write to a directory that already
10845821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)    # exists, and ${installed_app} needs to be used as input to dirpatcher
10855821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)    # in any event.  The new application will be rsynced into place once
10865821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)    # dirpatcher creates it.
10875821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)    ensure_temp_dir
10885821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)    update_app="${g_temp_dir}/${APP_DIR}"
10895821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)    note "update_app = ${update_app}"
10905821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)
10915821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)    note "dirpatching app directory"
10925821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)    if ! "${dirpatcher}" "${installed_app}" \
10935821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)                         "${patch_app_dir}" \
10945821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)                         "${update_app}"; then
10955821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)      err "dirpatcher of app directory failed, status ${PIPESTATUS[0]}"
1096f60fc993c7b081abf77ce2ffc7fcca1142c8cb01Torne (Richard Coles)      mark_failed_patch_update "${product_id}" \
1097f60fc993c7b081abf77ce2ffc7fcca1142c8cb01Torne (Richard Coles)                               "${want_full_installer_path}" \
1098f60fc993c7b081abf77ce2ffc7fcca1142c8cb01Torne (Richard Coles)                               "${old_ks_plist}" \
1099f60fc993c7b081abf77ce2ffc7fcca1142c8cb01Torne (Richard Coles)                               "${old_version_app}" \
1100f60fc993c7b081abf77ce2ffc7fcca1142c8cb01Torne (Richard Coles)                               "${system_ticket}"
11015821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)      exit 13
11025821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)    fi
11035821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)  fi
11045821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)
11055821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)  # See if the timestamp of what's currently on disk is newer than the
11065821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)  # update's outer .app's timestamp.  rsync will copy the update's timestamp
11075821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)  # over, but if that timestamp isn't as recent as what's already on disk, the
11085821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)  # .app will need to be touched.
11095821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)  local needs_touch=
11105821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)  if [[ "${installed_app}" -nt "${update_app}" ]]; then
11115821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)    needs_touch="y"
11125821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)  fi
11135821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)  note "needs_touch = ${needs_touch}"
11145821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)
11155821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)  # Copy the unversioned files into place, leaving everything in
11165821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)  # ${installed_versions_dir} alone.  If this step is interrupted, the
11175821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)  # application will at least remain in a usable state, although it may not
11185821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)  # pass signature validation.  Depending on when this step is interrupted,
11195821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)  # the application will either launch the old or the new version.  The
11205821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)  # critical point is when the main executable is replaced.  There isn't very
11215821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)  # much to copy in this step, because most of the application is in the
11225821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)  # versioned directory.  This step only accounts for around 50 files, most of
11235821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)  # which are small localized InfoPlist.strings files.  Note that
11245821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)  # ${VERSIONS_DIR} is included to copy its mode bits and timestamp, but its
11255821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)  # contents are excluded, having already been installed above.
11265821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)  note "rsyncing app directory"
11275821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)  if ! rsync ${RSYNC_FLAGS} --delete-after --exclude "/${VERSIONS_DIR}/*" \
11285821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)       "${update_app}/" "${installed_app}"; then
11295821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)    err "rsync of app directory failed, status ${PIPESTATUS[0]}"
11305821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)    exit 8
11315821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)  fi
11325821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)
11335821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)  note "rsyncs complete"
11345821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)
11355821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)  if [[ -n "${is_patch}" ]]; then
11365821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)    # update_app has been rsynced into place and is no longer needed.
11375821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)    rm -rf "${update_app}" 2> /dev/null || true
11385821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)    update_app=
11395821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)    note "update_app = ${update_app}"
11405821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)  fi
11415821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)
11425821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)  if [[ -n "${g_temp_dir}" ]]; then
11435821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)    # The temporary directory, if any, is no longer needed.
11445821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)    rm -rf "${g_temp_dir}" 2> /dev/null || true
11455821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)    g_temp_dir=
11465821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)    note "g_temp_dir = ${g_temp_dir}"
11475821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)  fi
11485821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)
1149f60fc993c7b081abf77ce2ffc7fcca1142c8cb01Torne (Richard Coles)  # Clean up any old .want_full_installer files from previous dirpatcher
1150f60fc993c7b081abf77ce2ffc7fcca1142c8cb01Torne (Richard Coles)  # failures. This is not considered a critical step, because this file
1151f60fc993c7b081abf77ce2ffc7fcca1142c8cb01Torne (Richard Coles)  # normally does not exist at all.
1152f60fc993c7b081abf77ce2ffc7fcca1142c8cb01Torne (Richard Coles)  rm -f "${want_full_installer_path}" || true
1153f60fc993c7b081abf77ce2ffc7fcca1142c8cb01Torne (Richard Coles)
11545821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)  # If necessary, touch the outermost .app so that it appears to the outside
11555821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)  # world that something was done to the bundle.  This will cause
11565821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)  # LaunchServices to invalidate the information it has cached about the
11575821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)  # bundle even if lsregister does not run.  This is not done if rsync already
11585821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)  # updated the timestamp to something newer than what had been on disk.  This
11595821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)  # is not considered a critical step, and if it fails, this script will not
11605821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)  # exit.
11615821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)  if [[ -n "${needs_touch}" ]]; then
11625821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)    touch -cf "${installed_app}" || true
11635821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)  fi
11645821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)
11655821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)  # Read the new values, such as the version.
11665821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)  note "reading new values"
11675821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)
11685821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)  local new_version_app
11695821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)  if ! new_version_app="$(infoplist_read "${installed_app_plist}" \
11705821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)                                         "${APP_VERSION_KEY}")" ||
11715821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)     [[ -z "${new_version_app}" ]]; then
11725821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)    err "couldn't determine new_version_app"
11735821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)    exit 9
11745821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)  fi
11755821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)  note "new_version_app = ${new_version_app}"
11765821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)
11775821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)  local new_versioned_dir="${installed_versions_dir}/${new_version_app}"
11785821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)  note "new_versioned_dir = ${new_versioned_dir}"
11795821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)
11805821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)  local new_ks_plist="${installed_app_plist}"
11815821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)  note "new_ks_plist = ${new_ks_plist}"
11825821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)
11835821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)  local new_version_ks
11845821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)  if ! new_version_ks="$(infoplist_read "${new_ks_plist}" \
11855821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)                                        "${KS_VERSION_KEY}")" ||
11865821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)     [[ -z "${new_version_ks}" ]]; then
11875821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)    err "couldn't determine new_version_ks"
11885821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)    exit 9
11895821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)  fi
11905821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)  note "new_version_ks = ${new_version_ks}"
11915821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)
11925821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)  local update_url
11935821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)  if ! update_url="$(infoplist_read "${new_ks_plist}" "${KS_URL_KEY}")" ||
11945821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)     [[ -z "${update_url}" ]]; then
11955821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)    err "couldn't determine update_url"
11965821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)    exit 9
11975821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)  fi
11985821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)  note "update_url = ${update_url}"
11995821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)
12005821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)  # The channel ID is optional.  Suppress stderr to prevent Keystone from
12015821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)  # seeing possible error output.
12025821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)  local channel
12035821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)  channel="$(infoplist_read "${new_ks_plist}" \
12045821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)                            "${KS_CHANNEL_KEY}" 2> /dev/null || true)"
12055821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)  note "channel = ${channel}"
12065821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)
1207f60fc993c7b081abf77ce2ffc7fcca1142c8cb01Torne (Richard Coles)  local tag="${channel}"
1208f60fc993c7b081abf77ce2ffc7fcca1142c8cb01Torne (Richard Coles)  local tag_key="${KS_CHANNEL_KEY}"
1209f60fc993c7b081abf77ce2ffc7fcca1142c8cb01Torne (Richard Coles)  if has_32_bit_only_cpu; then
1210f60fc993c7b081abf77ce2ffc7fcca1142c8cb01Torne (Richard Coles)    tag="${tag}-32bit"
1211f60fc993c7b081abf77ce2ffc7fcca1142c8cb01Torne (Richard Coles)    tag_key="${tag_key}-32bit"
1212f60fc993c7b081abf77ce2ffc7fcca1142c8cb01Torne (Richard Coles)  fi
1213f60fc993c7b081abf77ce2ffc7fcca1142c8cb01Torne (Richard Coles)  note "tag = ${tag}"
1214f60fc993c7b081abf77ce2ffc7fcca1142c8cb01Torne (Richard Coles)  note "tag_key = ${tag_key}"
1215f60fc993c7b081abf77ce2ffc7fcca1142c8cb01Torne (Richard Coles)
12165821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)  # Make sure that the update was successful by comparing the version found in
12175821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)  # the update with the version now on disk.
12185821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)  if [[ "${new_version_ks}" != "${update_version_ks}" ]]; then
12195821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)    err "new_version_ks and update_version_ks do not match"
12205821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)    exit 10
12215821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)  fi
12225821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)
12235821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)  # Notify LaunchServices.  This is not considered a critical step, and
12245821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)  # lsregister's exit codes shouldn't be confused with this script's own.
12255821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)  # Redirect stdout to /dev/null to suppress the useless "ThrottleProcessIO:
12265821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)  # throttling disk i/o" messages that lsregister might print.
12275821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)  note "notifying LaunchServices"
12285821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)  local coreservices="/System/Library/Frameworks/CoreServices.framework"
12295821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)  local launchservices="${coreservices}/Frameworks/LaunchServices.framework"
12305821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)  local lsregister="${launchservices}/Support/lsregister"
12315821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)  note "coreservices = ${coreservices}"
12325821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)  note "launchservices = ${launchservices}"
12335821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)  note "lsregister = ${lsregister}"
12345821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)  "${lsregister}" -f "${installed_app}" > /dev/null || true
12355821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)
12365821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)  # The brand information is stored differently depending on whether this is
12375821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)  # running for a system or user ticket.
12385821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)  note "handling brand code"
12395821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)
12405821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)  local set_brand_file_access=
12415821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)  local brand_plist
12425821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)  if [[ -n "${system_ticket}" ]]; then
12435821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)    # System ticket.
12445821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)    set_brand_file_access="y"
12455821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)    brand_plist="/${UNROOTED_BRAND_PLIST}"
12465821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)  else
12475821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)    # User ticket.
12485821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)    brand_plist=~/"${UNROOTED_BRAND_PLIST}"
12495821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)  fi
12505821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)  local brand_plist_path="${brand_plist}.plist"
12515821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)  note "set_brand_file_access = ${set_brand_file_access}"
12525821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)  note "brand_plist = ${brand_plist}"
12535821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)  note "brand_plist_path = ${brand_plist_path}"
12545821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)
12555821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)  local ksadmin_brand_plist_path
12565821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)  local ksadmin_brand_key
12575821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)
12585821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)  # Only the stable channel, identified by an empty channel string, has a
12595821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)  # brand code. On the beta and dev channels, remove the brand plist if
12605821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)  # present. Its presence means that the ticket used to manage a
12615821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)  # stable-channel Chrome but the user has since replaced it with a beta or
12625821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)  # dev channel version. Since the canary channel can run side-by-side with
12635821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)  # another Chrome installation, don't remove the brand plist on that channel,
12645821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)  # but skip the rest of the brand logic.
12655821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)  if [[ "${channel}" = "beta" ]] || [[ "${channel}" = "dev" ]]; then
12665821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)    note "defeating brand code on channel ${channel}"
12675821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)    rm -f "${brand_plist_path}" 2>/dev/null || true
12685821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)  elif [[ -n "${channel}" ]]; then
12695821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)    # Canary channel.
12705821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)    note "skipping brand code on channel ${channel}"
12715821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)  else
12725821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)    # Stable channel.
12735821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)    # If the user manually updated their copy of Chrome, there might be new
12745821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)    # brand information in the app bundle, and that needs to be copied out
12755821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)    # into the file Keystone looks at.
12765821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)    if [[ -n "${old_brand}" ]]; then
12775821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)      local brand_dir
12785821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)      brand_dir="$(dirname "${brand_plist_path}")"
12795821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)      note "brand_dir = ${brand_dir}"
12805821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)      if ! mkdir -p "${brand_dir}"; then
12815821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)        err "couldn't mkdir brand_dir, continuing"
12825821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)      else
12835821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)        if ! defaults write "${brand_plist}" "${KS_BRAND_KEY}" \
12845821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)                            -string "${old_brand}"; then
12855821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)          err "couldn't write brand_plist, continuing"
12865821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)        elif [[ -n "${set_brand_file_access}" ]]; then
12875821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)          if ! chown "root:wheel" "${brand_plist_path}"; then
12885821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)            err "couldn't chown brand_plist_path, continuing"
12895821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)          else
12905821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)            if ! chmod 644 "${brand_plist_path}"; then
12915821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)              err "couldn't chmod brand_plist_path, continuing"
12925821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)            fi
12935821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)          fi
12945821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)        fi
12955821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)      fi
12965821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)    fi
12975821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)
12985821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)    # Confirm that the brand file exists.  It's optional.
12995821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)    ksadmin_brand_plist_path="${brand_plist_path}"
13005821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)    ksadmin_brand_key="${KS_BRAND_KEY}"
13015821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)
13025821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)    if [[ ! -f "${ksadmin_brand_plist_path}" ]]; then
13035821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)      # Clear any branding information.
13045821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)      ksadmin_brand_plist_path=
13055821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)      ksadmin_brand_key=
13065821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)    fi
13075821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)  fi
13085821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)
13095821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)  note "ksadmin_brand_plist_path = ${ksadmin_brand_plist_path}"
13105821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)  note "ksadmin_brand_key = ${ksadmin_brand_key}"
13115821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)
13125821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)  note "notifying Keystone"
13135821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)
13145821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)  local ksadmin_args=(
13155821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)    --register
13165821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)    --productid "${product_id}"
13175821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)    --version "${new_version_ks}"
13185821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)    --xcpath "${installed_app}"
13195821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)    --url "${update_url}"
13205821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)  )
13215821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)
13225821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)  if ksadmin_supports_tag; then
13235821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)    ksadmin_args+=(
1324f60fc993c7b081abf77ce2ffc7fcca1142c8cb01Torne (Richard Coles)      --tag "${tag}"
13255821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)    )
13265821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)  fi
13275821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)
13285821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)  if ksadmin_supports_tagpath_tagkey; then
13295821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)    ksadmin_args+=(
13305821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)      --tag-path "${installed_app_plist_path}"
1331f60fc993c7b081abf77ce2ffc7fcca1142c8cb01Torne (Richard Coles)      --tag-key "${tag_key}"
13325821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)    )
13335821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)  fi
13345821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)
13355821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)  if ksadmin_supports_brandpath_brandkey; then
13365821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)    ksadmin_args+=(
13375821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)      --brand-path "${ksadmin_brand_plist_path}"
13385821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)      --brand-key "${ksadmin_brand_key}"
13395821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)    )
13405821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)  fi
13415821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)
13425821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)  if ksadmin_supports_versionpath_versionkey; then
13435821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)    ksadmin_args+=(
13445821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)      --version-path "${installed_app_plist_path}"
13455821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)      --version-key "${KS_VERSION_KEY}"
13465821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)    )
13475821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)  fi
13485821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)
13495821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)  note "ksadmin_args = ${ksadmin_args[*]}"
13505821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)
13515821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)  if ! ksadmin "${ksadmin_args[@]}"; then
13525821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)    err "ksadmin failed"
13535821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)    exit 11
13545821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)  fi
13555821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)
13565821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)  # The remaining steps are not considered critical.
13575821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)  set +e
13585821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)
13595821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)  # Try to clean up old versions that are not in use.  The strategy is to keep
13605821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)  # the versioned directory corresponding to the update just applied
13615821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)  # (obviously) and the version that was just replaced, and to use ps and lsof
13625821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)  # to see if it looks like any processes are currently using any other old
13635821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)  # directories.  Directories not in use are removed.  Old versioned
13645821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)  # directories that are in use are left alone so as to not interfere with
13655821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)  # running processes.  These directories can be cleaned up by this script on
13665821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)  # future updates.
13675821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)  #
13685821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)  # To determine which directories are in use, both ps and lsof are used.
13695821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)  # Each approach has limitations.
13705821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)  #
13715821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)  # The ps check looks for processes within the versioned directory.  Only
13725821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)  # helper processes, such as renderers, are within the versioned directory.
13735821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)  # Browser processes are not, so the ps check will not find them, and will
13745821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)  # assume that a versioned directory is not in use if a browser is open
13755821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)  # without any windows.  The ps mechanism can also only detect processes
13765821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)  # running on the system that is performing the update.  If network shares
13775821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)  # are involved, all bets are off.
13785821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)  #
13795821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)  # The lsof check looks to see what processes have the framework dylib open.
13805821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)  # Browser processes will have their versioned framework dylib open, so this
13815821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)  # check is able to catch browsers even if there are no associated helper
13825821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)  # processes.  Like the ps check, the lsof check is limited to processes on
13835821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)  # the system that is performing the update.  Finally, unless running as
13845821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)  # root, the lsof check can only find processes running as the effective user
13855821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)  # performing the update.
13865821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)  #
13875821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)  # These limitations are motivations to additionally preserve the versioned
13885821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)  # directory corresponding to the version that was just replaced.
13895821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)  note "cleaning up old versioned directories"
13905821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)
13915821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)  local versioned_dir
13925821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)  for versioned_dir in "${installed_versions_dir}/"*; do
13935821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)    note "versioned_dir = ${versioned_dir}"
13945821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)    if [[ "${versioned_dir}" = "${new_versioned_dir}" ]] || \
13955821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)       [[ "${versioned_dir}" = "${old_versioned_dir}" ]]; then
13965821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)      # This is the versioned directory corresponding to the update that was
13975821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)      # just applied or the version that was previously in use.  Leave it
13985821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)      # alone.
13995821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)      note "versioned_dir is new_versioned_dir or old_versioned_dir, skipping"
14005821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)      continue
14015821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)    fi
14025821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)
14035821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)    # Look for any processes whose executables are within this versioned
14045821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)    # directory.  They'll be helper processes, such as renderers.  Their
14055821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)    # existence indicates that this versioned directory is currently in use.
14065821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)    local ps_string="${versioned_dir}/"
14075821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)    note "ps_string = ${ps_string}"
14085821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)
14095821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)    # Look for any processes using the framework dylib.  This will catch
14105821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)    # browser processes where the ps check will not, but it is limited to
14115821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)    # processes running as the effective user.
14125821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)    local lsof_file="${versioned_dir}/${FRAMEWORK_DIR}/${FRAMEWORK_NAME}"
14135821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)    note "lsof_file = ${lsof_file}"
14145821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)
14155821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)    # ps -e displays all users' processes, -ww causes ps to not truncate
14165821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)    # lines, -o comm instructs it to only print the command name, and the =
14175821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)    # tells it to not print a header line.
14185821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)    # The cut invocation filters the ps output to only have at most the number
14195821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)    # of characters in ${ps_string}.  This is done so that grep can look for
14205821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)    # an exact match.
14215821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)    # grep -F tells grep to look for lines that are exact matches (not regular
14225821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)    # expressions), -q tells it to not print any output and just indicate
14235821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)    # matches by exit status, and -x tells it that the entire line must match
14245821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)    # ${ps_string} exactly, as opposed to matching a substring.  A match
14255821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)    # causes grep to exit zero (true).
14265821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)    #
14275821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)    # lsof will exit nonzero if ${lsof_file} does not exist or is open by any
14285821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)    # process.  If the file exists and is open, it will exit zero (true).
14295821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)    if (! ps -ewwo comm= | \
14305821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)          cut -c "1-${#ps_string}" | \
14315821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)          grep -Fqx "${ps_string}") &&
14325821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)       (! lsof "${lsof_file}" >& /dev/null); then
14335821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)      # It doesn't look like anything is using this versioned directory.  Get
14345821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)      # rid of it.
14355821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)      note "versioned_dir doesn't appear to be in use, removing"
14365821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)      rm -rf "${versioned_dir}"
14375821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)    else
14385821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)      note "versioned_dir is in use, skipping"
14395821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)    fi
14405821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)  done
14415821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)
14425821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)  # If this script is being driven by a user Keystone ticket, it is not
14435821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)  # running as root.  If the application is installed somewhere under
14445821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)  # /Applications, try to make it writable by all admin users.  This will
14455821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)  # allow other admin users to update the application from their own user
14465821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)  # Keystone instances.
14475821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)  #
14485821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)  # If the script is being driven by a user Keystone ticket (not running as
14495821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)  # root) and the application is not installed under /Applications, it might
14505821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)  # not be in a system-wide location, and it probably won't be something that
14515821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)  # other users on the system are running, so err on the side of safety and
14525821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)  # don't make it group-writable.
14535821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)  #
14545821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)  # If this script is being driven by a system ticket (running as root), it's
14555821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)  # future updates can be expected to be applied the same way, so admin-
14565821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)  # writability is not a concern.  Set the entire thing to be owned by root
14575821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)  # in that case, regardless of where it's installed, and drop any group and
14585821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)  # other write permission.
14595821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)  #
14605821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)  # If this script is running as a user that is not a member of the admin
14615821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)  # group, the chgrp operation will not succeed.  Tolerate that case, because
14625821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)  # it's better than the alternative, which is to make the application
14635821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)  # world-writable.
14645821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)  note "setting permissions"
14655821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)
14665821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)  local chmod_mode="a+rX,u+w,go-w"
14675821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)  if [[ -z "${system_ticket}" ]]; then
14685821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)    if [[ "${installed_app:0:14}" = "/Applications/" ]] &&
14695821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)       chgrp -Rh admin "${installed_app}" 2> /dev/null; then
14705821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)      chmod_mode="a+rX,ug+w,o-w"
14715821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)    fi
14725821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)  else
14735821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)    chown -Rh root:wheel "${installed_app}" 2> /dev/null
14745821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)  fi
14755821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)
14765821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)  note "chmod_mode = ${chmod_mode}"
14775821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)  chmod -R "${chmod_mode}" "${installed_app}" 2> /dev/null
14785821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)
14795821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)  # On the Mac, or at least on HFS+, symbolic link permissions are significant,
14805821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)  # but chmod -R and -h can't be used together.  Do another pass to fix the
14815821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)  # permissions on any symbolic links.
14825821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)  find "${installed_app}" -type l -exec chmod -h "${chmod_mode}" {} + \
14835821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)      2> /dev/null
14845821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)
14855821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)  # If an update is triggered from within the application itself, the update
14865821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)  # process inherits the quarantine bit (LSFileQuarantineEnabled).  Any files
14875821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)  # or directories created during the update will be quarantined in that case,
14885821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)  # which may cause Launch Services to display quarantine UI.  That's bad,
14895821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)  # especially if it happens when the outer .app launches a quarantined inner
14905821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)  # helper.  If the application is already on the system and is being updated,
14915821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)  # then it can be assumed that it should not be quarantined.  Use xattr to
14925821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)  # drop the quarantine attribute.
14935821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)  #
14945821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)  # TODO(mark): Instead of letting the quarantine attribute be set and then
14955821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)  # dropping it here, figure out a way to get the update process to run
14965821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)  # without LSFileQuarantineEnabled even when triggering an update from within
14975821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)  # the application.
14985821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)  note "lifting quarantine"
14995821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)
15005821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)  if os_xattr_supports_r; then
15015821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)    # On 10.6, xattr supports -r for recursive operation.
15025821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)    xattr -d -r "${QUARANTINE_ATTR}" "${installed_app}" 2> /dev/null
15035821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)  else
15045821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)    # On earlier systems, xattr doesn't support -r, so run xattr via find.
15055821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)    find "${installed_app}" -exec xattr -d "${QUARANTINE_ATTR}" {} + \
15065821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)        2> /dev/null
15075821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)  fi
15085821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)
15095821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)  # Do Keychain reauthorization. This involves running a stub executable on
15105821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)  # the dmg that loads the newly-updated framework and jumps to it to perform
15115821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)  # the reauthorization. The stub executable can be signed by the old
15125821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)  # certificate even after the rest of Chrome switches to the new certificate,
15135821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)  # so it still has access to the old Keychain items. The stub executable is
15145821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)  # an unbundled flat file executable whose name matches the real
15155821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)  # application's bundle identifier, so it's permitted access to the Keychain
15165821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)  # items. Doing a reauthorization step at update time reauthorizes Keychain
15175821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)  # items for users who never bother restarting Chrome, and provides a
15185821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)  # mechanism to continue doing reauthorizations even after the certificate
15195821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)  # changes. However, it only works for non-system ticket installations of
15205821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)  # Chrome, because the updater runs as root when on a system ticket, and root
15215821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)  # can't access individual user Keychains.
15225821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)  #
15235821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)  # Even if the reauthorization tool is launched, it doesn't necessarily try
15245821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)  # to do anything. It will only attempt to perform a reauthorization if one
15255821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)  # hasn't yet been done at update time.
15265821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)  note "maybe reauthorizing Keychain"
15275821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)
15285821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)  if [[ -z "${system_ticket}" ]]; then
15295821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)    local new_bundleid_app
15305821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)    new_bundleid_app="$(infoplist_read "${installed_app_plist}" \
15315821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)                                       "${APP_BUNDLEID_KEY}" || true)"
15325821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)    note "new_bundleid_app = ${new_bundleid_app}"
15335821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)
15345821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)    local keychain_reauthorize_dir="\
15355821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)${update_dmg_mount_point}/${KEYCHAIN_REAUTHORIZE_DIR}"
15365821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)    local keychain_reauthorize_path="\
15375821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)${keychain_reauthorize_dir}/${new_bundleid_app}"
15385821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)    note "keychain_reauthorize_path = ${keychain_reauthorize_path}"
15395821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)
15405821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)    if [[ -x "${keychain_reauthorize_path}" ]]; then
15415821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)      local framework_dir="${new_versioned_dir}/${FRAMEWORK_DIR}"
15425821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)      local framework_code_path="${framework_dir}/${FRAMEWORK_NAME}"
15435821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)      note "framework_code_path = ${framework_code_path}"
15445821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)
15455821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)      if [[ -f "${framework_code_path}" ]]; then
15465821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)        note "reauthorizing Keychain"
15475821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)        "${keychain_reauthorize_path}" "${framework_code_path}"
15485821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)      fi
15495821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)    fi
15505821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)  else
15515821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)    note "system ticket, not reauthorizing Keychain"
15525821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)  fi
15535821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)
15545821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)  # Great success!
15555821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)  note "done!"
15565821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)
15575821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)  trap - EXIT
15585821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)
15595821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)  return 0
15605821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)}
15615821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)
15625821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)# Check "less than" instead of "not equal to" in case Keystone ever changes to
15635821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)# pass more arguments.
15645821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)if [[ ${#} -lt 1 ]]; then
15655821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)  usage
15665821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)  exit 2
15675821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)fi
15685821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)
15695821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)main "${@}"
15705821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)exit ${?}
1571