Index: chrome_mac/Google Chrome.app/Contents/Versions/34.0.1800.0/Google Chrome Framework.framework/Frameworks/KeystoneRegistration.framework/Resources/install.py |
=================================================================== |
--- chrome_mac/Google Chrome.app/Contents/Versions/34.0.1800.0/Google Chrome Framework.framework/Frameworks/KeystoneRegistration.framework/Resources/install.py (revision 273864) |
+++ chrome_mac/Google Chrome.app/Contents/Versions/34.0.1800.0/Google Chrome Framework.framework/Frameworks/KeystoneRegistration.framework/Resources/install.py (working copy) |
@@ -1,1446 +0,0 @@ |
-#!/usr/bin/python |
-# Copyright 2008 Google Inc. All rights reserved. |
- |
-"""This script will install Keystone in the correct context |
-(system-wide or per-user). It can also uninstall Keystone. is run by |
-KeystoneRegistration.framework. |
- |
-Example command lines for testing: |
-Install: install.py --install=/tmp/Keystone.tbz --root=/Users/fred |
-Uninstall: install.py --nuke --root=/Users/fred |
- |
-Example real command lines, for user and root install and uninstall: |
- install.py --install Keystone.tbz |
- install.py --nuke |
- sudo install.py --install Keystone.tbz |
- sudo install.py --nuke |
- |
-For a system-wide Keystone, the install root is "/". Run with --help |
-for a list of options. Use --no-launchdjobs to NOT start background |
-processes. |
- |
-Errors can happen if: |
- - we don't have write permission to install in the given root |
- - pieces of our install are missing |
- |
-On error, we print a simple message on stdout and our exit status is |
-non-zero. On success, we print nothing and exit with a status of 0. |
-""" |
- |
-import os |
-import re |
-import sys |
-import pwd |
-import stat |
-import glob |
-import getopt |
-import shutil |
-import platform |
-import fcntl |
-import traceback |
- |
- |
-# Allow us to force the installer to think we're on Tiger (10.4) |
-FORCE_TIGER = False |
- |
-# Allow us to adjust the agent launch interval (for testing). |
-# In seconds. Time is 1 hour minus a jitter factor. |
-AGENT_START_INTERVAL = 3523 |
- |
-# Name of our "lockdown" ticket. If you change this name be sure to |
-# change it in other places in the code (grep is your friend) |
-LOCKDOWN_TICKET = 'com.google.Keystone.Lockdown' |
- |
-# Process that we consider a marker of a running user login session |
-USER_SESSION_PROCESSNAME = \ |
- ' /System/Library/CoreServices/Finder.app/Contents/MacOS/Finder' |
- |
-# URL for our Omaha server |
-OMAHA_SERVER_URL = 'https://tools.google.com/service/update2' |
- |
- |
-# Error codes. These need to be changed in sync with KSInstallErrors.h |
-# and (potentially) KSAdministration/KSInstallation |
-UNKNOWN_ERROR_CODE = -1 |
-MASTER_DISABLE_ERROR_CODE = -2 |
-# Error 1 reserved for generic errors from this script. |
-GENERIC_ERROR_CODE = 1 |
-PACKAGE_ERROR_CODE = 2 |
-UNSUPPORTED_OS_ERROR_CODE = 3 |
-# These are internal and reported with a generic error message and their value |
-# in a nested error. |
-USAGE_ERROR_CODE = 64 |
-BAD_ROOT_ERROR_CODE = 65 |
-INSTALLED_VERSION_CHECK_ERROR_CODE = 66 |
-PACKAGE_VERSION_CHECK_ERROR_CODE = 67 |
-FILE_INSTALLATION_ERROR_CODE = 68 |
-DAEMON_CONTROL_ERROR_CODE = 69 |
-AGENT_CONTROL_ERROR_CODE = 70 |
-AGENT_INSTALL_ERROR_CODE = 71 |
-KSADMIN_MISSING_ERROR_CODE = 72 |
-KEYSTONE_TICKET_ERROR_CODE = 73 |
- |
- |
-class Error(Exception): |
- """Exception for Keystone install failure. Has methods for pretty printing |
- in a manner readable by our Obj-C wrapper code. |
- """ |
- |
- def __init__(self, package, root, code, msg): |
- self.package = package |
- self.root = root |
- self.code = code |
- self.msg = msg |
- |
- def __str__(self): |
- return '%s (Package: %s Root: %s)' % (self.msg, self.package, self.root) |
- |
- def errorcode(self): |
- if self.code: |
- return self.code |
- else: |
- return UNKNOWN_ERROR_CODE |
- |
- def message(self): |
- return self.msg |
- |
- |
-def CheckOnePath(file, statmode, errorcode=UNKNOWN_ERROR_CODE): |
- """Sanity check a file or directory as requested. On failure throw |
- an exception.""" |
- if os.path.exists(file): |
- st = os.stat(file) |
- if (st.st_mode & statmode) != 0: |
- return True |
- return False |
- |
-# ------------------------------------------------------------------------- |
- |
-class KeystoneInstall(object): |
- """Worker object which does the heavy lifting of install or uninstall. |
- By default it assumes 10.5 (Leopard). |
- |
- Args: |
- package: The package to install (i.e. Keystone.tbz) |
- is_system: True if this is a system Keystone install |
- agent_job_uid: uid to start agent jobs as or None to use current euid |
- root: root directory for install. On System this would be "/"; |
- else would be a user home directory (unless testing, in which case |
- the root can be anywhere). |
- launchd_setup: True if the installation should setup launchd job description |
- plists (and Tiger equivalents) |
- launchd_jobs: True if the installation should start/stop related jobs |
- self_destruct: True if uninstall is being triggered by a process the |
- uninstall is expected to kill |
- |
- Conventions: |
- All functions which return directory paths end in '/' |
- """ |
- |
- def __init__(self, package, is_system, agent_job_uid, root, |
- launchd_setup, launchd_jobs, self_destruct): |
- self.package = package |
- self.is_system = is_system |
- self.agent_job_uid = agent_job_uid |
- if is_system: |
- assert agent_job_uid is not None, 'System install needs agent job uid' |
- self.root = root |
- if not self.root.endswith('/'): |
- self.root = self.root + '/' |
- self.launchd_setup = launchd_setup |
- self.launchd_jobs = launchd_jobs |
- self.self_destruct = self_destruct |
- self.cached_package_version = None |
- # Save/restore permissions |
- self.old_euid = None |
- self.old_egid = None |
- self.old_umask = None |
- |
- def RunCommand(self, cmd): |
- """Runs a command, returning return code and output. |
- |
- Returns: |
- Tuple of return value, stdout and stderr. |
- """ |
- # We need to work in python 2.3 (OSX 10.4), 2.5 (10.5), and 2.6 (10.6) |
- if (sys.version_info[0] == 2) and (sys.version_info[1] <= 5): |
- # subprocess.communicate implemented the hard way |
- import errno |
- import popen2 |
- import select |
- p = popen2.Popen3(cmd, True) |
- stdout = [] |
- stderr = [] |
- readable = [ p.fromchild, p.childerr ] |
- while not p.fromchild.closed or not p.childerr.closed: |
- try: |
- try_to_read = [] |
- if not p.fromchild.closed: |
- try_to_read.append(p.fromchild) |
- if not p.childerr.closed: |
- try_to_read.append(p.childerr) |
- readable, ignored_w, ignored_x = select.select(try_to_read, [], []) |
- except select.error, e: |
- if e.args[0] == errno.EINTR: |
- continue |
- raise |
- if p.fromchild in readable: |
- out = os.read(p.fromchild.fileno(), 1024) |
- stdout.append(out) |
- if out == '': |
- p.fromchild.close() |
- if p.childerr in readable: |
- errout = os.read(p.childerr.fileno(), 1024) |
- stderr.append(errout) |
- if errout == '': |
- p.childerr.close() |
- result = p.wait() |
- return (os.WEXITSTATUS(result), ''.join(stdout), ''.join(stderr)) |
- else: |
- # Just use subprocess, so much simpler |
- import subprocess |
- p = subprocess.Popen(cmd, shell=False, stdout=subprocess.PIPE, |
- stderr=subprocess.PIPE, close_fds=True) |
- (stdout, stderr) = p.communicate() |
- return (p.returncode, stdout, stderr) |
- |
- def _AgentProcessName(self): |
- """Return the process name of the agent.""" |
- return 'GoogleSoftwareUpdateAgent' |
- |
- def _LibraryCachesDirPath(self): |
- """Return the Library/Caches subdirectory""" |
- return os.path.join(self.root, 'Library/Caches/') |
- |
- def _LibraryGoogleDirPath(self): |
- """Return the Library subdirectory that parents all our dirs""" |
- return os.path.join(self.root, 'Library/Google/') |
- |
- def _KeystoneDirPath(self): |
- """Return the subdirectory where Keystone.bundle is or will be. |
- Does not sanity check the directory.""" |
- return os.path.join(self._LibraryGoogleDirPath(), 'GoogleSoftwareUpdate/') |
- |
- def _KeystoneBundlePath(self): |
- """Return the location of Keystone.bundle.""" |
- return os.path.join(self._KeystoneDirPath(), 'GoogleSoftwareUpdate.bundle/') |
- |
- def _KeystoneTicketStorePath(self): |
- """Returns directory path of the Keystone ticket store.""" |
- return os.path.join(self._KeystoneDirPath(), 'TicketStore') |
- |
- def _KsadminPath(self): |
- """Return a path to ksadmin which will exist only AFTER Keystone is |
- installed. Return None if it doesn't exist.""" |
- ksadmin = os.path.join(self._KeystoneBundlePath(), 'Contents/MacOS/ksadmin') |
- if not os.path.exists(ksadmin): |
- return None |
- return ksadmin |
- |
- def _KeystoneResourcePath(self): |
- """Return the subdirectory where Keystone.bundle's resources should be.""" |
- return os.path.join(self._KeystoneBundlePath(), 'Contents/Resources/') |
- |
- def _KeystoneAgentPath(self): |
- """Returns a path to installed KeystoneAgent.app.""" |
- return os.path.join(self._KeystoneResourcePath(), |
- 'GoogleSoftwareUpdateAgent.app/') |
- |
- def _LaunchAgentConfigDir(self): |
- """Return the destination directory where launch agents should go.""" |
- return os.path.join(self.root, 'Library/LaunchAgents/') |
- |
- def _LaunchDaemonConfigDir(self): |
- """Return the destination directory where launch daemons should go.""" |
- return os.path.join(self.root, 'Library/LaunchDaemons/') |
- |
- def _AgentPlistFileName(self): |
- """Return the filename of the Keystone agent launchd plist or None.""" |
- return 'com.google.keystone.agent.plist' |
- |
- def _DaemonPlistSourceFileName(self): |
- """Return the filename of the Keystone daemon launchd plist to install.""" |
- return 'com.google.keystone.daemon.plist' |
- |
- def _DaemonPlistFileName(self): |
- """Return the filename of the post-install Keystone daemon launchd plist.""" |
- return 'com.google.keystone.daemon.plist' |
- |
- def _IsMasterDisabled(self): |
- """Check for master disable MCX preference.""" |
- # 'defaults' does not honor MCX, so read the file directly |
- if os.path.exists('/Library/Managed Preferences/com.google.Keystone.plist'): |
- cmd = ['/usr/bin/defaults', 'read', |
- '/Library/Managed Preferences/com.google.Keystone', |
- 'masterDisable'] |
- (result, out, errout) = self.RunCommand(cmd) |
- if result == 0 and out.strip() == '1': |
- return True |
- # Honor admin preferences too |
- if os.path.exists('/Library/Preferences/com.google.Keystone.plist'): |
- cmd = ['/usr/bin/defaults', 'read', |
- '/Library/Preferences/com.google.Keystone', |
- 'masterDisable'] |
- (result, out, errout) = self.RunCommand(cmd) |
- if result == 0 and out.strip() == '1': |
- return True |
- return False |
- |
- def _CFBundleVersionFromInfo(self, info): |
- """Given the content of an Info.plist return its CFBundleVersion.""" |
- linelist = info.splitlines() |
- for i in range(len(linelist)): |
- if linelist[i].find('<key>CFBundleVersion</key>') != -1: |
- version = linelist[i+1].strip() |
- version = version.strip('<string>').strip('</string>') |
- if version: |
- return version |
- return None |
- |
- def InstalledKeystoneTicketVersion(self): |
- """Return the version the current Keystone ticket, or None if not installed. |
- |
- Invariant: we use the same a 4-digit version for the ticket as we use |
- for CFBundleVersion. |
- """ |
- ksadmin_path = self._KsadminPath() |
- if not ksadmin_path or not os.path.exists(ksadmin_path): |
- return None |
- cmd = [ksadmin_path, |
- # store is specified explicitly so unit tests work |
- '--store', os.path.join(self._KeystoneTicketStorePath(), |
- 'Keystone.ticketstore'), |
- '--productid', 'com.google.Keystone', |
- '--print-tickets'] |
- (result, out, errout) = self.RunCommand(cmd) |
- if result: |
- return None |
- for line in out.splitlines(): |
- if line.find('version=') != -1: |
- version = line.strip() |
- version = version.strip('version=') |
- if version: |
- return version |
- return None |
- |
- def InstalledKeystoneBundleVersion(self): |
- """Return the version of an installed Keystone bundle, or None if |
- not installed. Specifically, it returns the CFBundleVersion as a |
- string (e.g. "0.1.0.0"). |
- |
- Invariant: we require a 4-digit version when building Keystone.bundle. |
- """ |
- plist_path = os.path.join(self._KeystoneBundlePath(), 'Contents/Info.plist') |
- if not os.path.exists(plist_path): |
- return None |
- p = open(plist_path, 'r') |
- info = p.read() |
- p.close() |
- return self._CFBundleVersionFromInfo(info) |
- |
- def MyKeystoneBundleVersion(self): |
- """Return the version of our Keystone bundle which we might want to install. |
- Specifically, it returns the CFBundleVersion as a string (e.g. "0.1.0.0"). |
- |
- Invariant: we require a 4-digit version when building Keystone.bundle. |
- """ |
- if self.cached_package_version is None: |
- cmd = ['/usr/bin/tar', '-Oxjf', |
- self.package, |
- 'GoogleSoftwareUpdate.bundle/Contents/Info.plist'] |
- (result, out, errout) = self.RunCommand(cmd) |
- if result != 0: |
- raise Error(self.package, self.root, PACKAGE_VERSION_CHECK_ERROR_CODE, |
- 'Google Software Update installer unable to read package ' |
- 'Info.plist: "%s"' % errout) |
- self.cached_package_version = self._CFBundleVersionFromInfo(out) |
- return self.cached_package_version |
- |
- def IsVersionGreaterThanVersion(self, a_version, b_version): |
- """Return True if a_version is greater than b_version. |
- |
- Invariant: we require a 4-digit version when building Keystone.bundle. |
- """ |
- if a_version is None or b_version is None: |
- return True |
- else: |
- a_version = a_version.split('.') |
- b_version = b_version.split('.') |
- # Only correct for 4-digit versions, see invariants. |
- if len(a_version) != len(b_version): |
- return True |
- for a, b in zip(a_version, b_version): |
- if int(a) > int(b): |
- return True |
- elif int(a) < int(b): |
- return False |
- # If we get here, it's a complete match, so no. |
- return False |
- |
- def IsMyVersionGreaterThanInstalledVersion(self): |
- """Returns True if package Keystone version is greater than current install. |
- |
- Invariant: we require a 4-digit version when building Keystone.bundle. |
- """ |
- my_version = self.MyKeystoneBundleVersion() |
- bundle_version = self.InstalledKeystoneBundleVersion() |
- if self.IsVersionGreaterThanVersion(my_version, bundle_version): |
- return True |
- ticket_version = self.InstalledKeystoneTicketVersion() |
- if self.IsVersionGreaterThanVersion(my_version, ticket_version): |
- return True |
- return False |
- |
- def _SetSystemInstallPermissions(self): |
- """Set permissions for system install, must pair with |
- _ClearSystemInstallPermissions(). Call before any filesystem access.""" |
- assert (self.old_euid is None and self.old_egid is None and |
- self.old_umask is None), 'System permissions used reentrant' |
- self.old_euid = os.geteuid() |
- os.seteuid(0) |
- self.old_egid = os.getegid() |
- os.setegid(0) |
- self.old_umask = os.umask(022) |
- |
- def _ClearSystemInstallPermissions(self): |
- """Restore prior permissions after _SetSystemInstallPermissions().""" |
- assert (self.old_euid is not None and self.old_egid is not None and |
- self.old_umask is not None), 'System permissions cleared before set' |
- os.seteuid(self.old_euid) |
- self.old_euid = None |
- os.setegid(self.old_egid) |
- self.old_egid = None |
- os.umask(self.old_umask) |
- self.old_umask = None |
- |
- def _InstallPlist(self, source, dest_name, dest_dir): |
- """Install a copy of the plist from Resources to the dest_dir path using |
- dest_name. For system install, assumes you have already called |
- _SetSystemInstallPermissions(). |
- """ |
- try: |
- pf = open(os.path.join(self._KeystoneResourcePath(), source), 'r') |
- content = pf.read() |
- pf.close() |
- except IOError, e: |
- raise Error(self.package, self.root, PACKAGE_ERROR_CODE, |
- 'Google Software Update installer failed to read resource ' |
- 'launchd plist "%s": %s' % (source, str(e))) |
- # This line is key. We can't have a tilde in a launchd script; |
- # we need an absolute path. So we replace a known token, like this: |
- # cat src.plist | 's/INSTALL_ROOT/self.root/g' > dest.plist |
- content = content.replace('${INSTALL_ROOT}', self.root) |
- content = content.replace(self.root + '/', self.root) # doubleslash remove |
- # Make sure launchd can distinguish between user and system Agents. |
- # This is a no-op for the daemon. |
- if self.is_system: |
- content = content.replace('${INSTALL_TYPE}', 'system') |
- else: |
- content = content.replace('${INSTALL_TYPE}', 'user') |
- # Allow start interval to be configured. |
- content = content.replace('${START_INTERVAL}', str(AGENT_START_INTERVAL)) |
- try: |
- # Write to temp file then move in place (safe save) |
- target_file = os.path.join(dest_dir, dest_name) |
- target_tmp_file = target_file + '.tmp' |
- pf = open(target_tmp_file, 'w') |
- pf.write(content) |
- pf.close() |
- os.rename(target_tmp_file, target_file) |
- except IOError, e: |
- raise Error(self.package, self.root, FILE_INSTALLATION_ERROR_CODE, |
- 'Google Software Update installer failed to install launchd ' |
- 'plist "%s": %s' % (os.path.join(dest_dir, dest_name), |
- str(e))) |
- |
- def _RemoveOldDaemonPlists(self): |
- """Remove older daemon plists installed using older names. |
- Assumes _SetSystemInstallPermissions() has been called.""" |
- # In general we have nothing to do |
- pass |
- |
- def _InstallAgentLoginItem(self): |
- """Setup the agent login item (vs. launchd job). |
- Assumes _SetSystemInstallPermissions() has been called.""" |
- pass |
- |
- def _RemoveAgentLoginItem(self): |
- """Remove the agent login item (vs. launchd job). |
- Assumes _SetSystemInstallPermissions() has been called. |
- |
- Note: We use this code on both Tiger and Leopard to handle the OS upgrade |
- case. |
- """ |
- if self.is_system: |
- domain = '/Library/Preferences/loginwindow' |
- else: |
- domain = 'loginwindow' |
- (result, alaout, errout) = self.RunCommand(['/usr/bin/defaults', 'read', |
- domain, 'AutoLaunchedApplicationDictionary']) |
- # Ignoring result |
- if len(alaout.strip()) == 0: |
- alaout = '()' |
- # One line per loginitem to help us match |
- alaout = re.compile('[\n]+').sub('', alaout) |
- # handles case where we are the only item |
- alaout = alaout.replace('(', '(\n') |
- alaout = alaout.replace('}', '}\n') |
- needed_removal = False |
- for line in alaout.splitlines(): |
- if line.find('/Library/Google/GoogleSoftwareUpdate/' |
- 'GoogleSoftwareUpdate.bundle/Contents/' |
- 'Resources/GoogleSoftwareUpdateAgent.app') != -1: |
- alaout = alaout.replace(line, '') |
- needed_removal = True |
- alaout = alaout.replace('\n', '') |
- # make sure it's a well-formed list |
- alaout = alaout.replace('(,', '(') |
- if needed_removal: |
- (result, out, errout) = self.RunCommand(['/usr/bin/defaults', 'write', |
- domain, 'AutoLaunchedApplicationDictionary', alaout]) |
- # Ignore result, if we messed up the parse just move on. |
- |
- def _ChangeDaemonRunStatus(self, start, ignore_failure): |
- """Start or stop the daemon using launchd.""" |
- assert self.is_system, 'Daemon start on non-system install' |
- self._SetSystemInstallPermissions() |
- try: |
- if start: |
- action = 'load' |
- else: |
- action = 'unload' |
- job_path = os.path.join(self._LaunchDaemonConfigDir(), |
- self._DaemonPlistFileName()) |
- # Workaround for incorrect Tiger installation |
- if os.path.exists(os.path.join(self._LaunchDaemonConfigDir(), |
- self._DaemonPlistSourceFileName())): |
- job_path = os.path.join(self._LaunchDaemonConfigDir(), |
- self._DaemonPlistSourceFileName()) |
- (result, out, errout) = self.RunCommand(['/bin/launchctl', action, |
- job_path]) |
- if not ignore_failure and result != 0: |
- raise Error(self.package, self.root, DAEMON_CONTROL_ERROR_CODE, |
- 'Google Software Update installer failed to %s daemon ' |
- '(%d): %s' % (action, result, errout)) |
- finally: |
- self._ClearSystemInstallPermissions() |
- |
- def _ChangeAgentRunStatus(self, start, ignore_failure): |
- """Start or stop the agent using launchd.""" |
- if self._AgentPlistFileName() is None: |
- return |
- if start: |
- action = 'load' |
- search_process_name = USER_SESSION_PROCESSNAME |
- else: |
- action = 'unload' |
- search_process_name = self._AgentProcessName() |
- if self.is_system: |
- self._SetSystemInstallPermissions() |
- try: |
- # System installation needs to use bsexec to hit all the running agents |
- (result, psout, pserr) = self.RunCommand(['/bin/ps', 'auxwww']) |
- if result != 0: # Internal problem so don't use ignore_failure |
- raise Error(self.package, self.root, UNKNOWN_ERROR_CODE, |
- 'Google Software Update installer could not run ' |
- '/bin/ps: %s' % pserr) |
- for psline in psout.splitlines(): |
- if psline.find(search_process_name) != -1: |
- username = psline.split()[0] |
- uid = pwd.getpwnam(username)[2] |
- # Must be root to bsexec. |
- # Must bsexec to (pid) to get in local user's context. |
- # Must become local user to have right process owner. |
- # Must unset SUDO_COMMAND to keep launchctl happy. |
- # Order is important. |
- agent_plist_path = os.path.join(self._LaunchAgentConfigDir(), |
- self._AgentPlistFileName()) |
- (result, out, errout) = self.RunCommand([ |
- '/bin/launchctl', 'bsexec', psline.split()[1], |
- '/usr/bin/sudo', '-u', username, '/bin/bash', '-c', |
- 'unset SUDO_COMMAND ; /bin/launchctl %s -S Aqua "%s"' % ( |
- action, |
- os.path.join(self._LaunchAgentConfigDir(), |
- self._AgentPlistFileName()))]) |
- # Although we're running for every user, only treat the requested |
- # user as an error |
- if not ignore_failure and result != 0 and uid == self.agent_job_uid: |
- raise Error(self.package, self.root, AGENT_CONTROL_ERROR_CODE, |
- 'Google Software Update installer failed to %s agent for ' |
- 'uid %d from plist "%s" (%d): %s' % |
- (action, self.agent_job_uid, agent_plist_path, result, |
- errout)) |
- finally: |
- self._ClearSystemInstallPermissions() |
- else: |
- # Non-system variant requires basic launchctl commands |
- agent_plist_path = os.path.join(self._LaunchAgentConfigDir(), |
- self._AgentPlistFileName()) |
- (result, out, errout) = self.RunCommand(['/bin/launchctl', action, |
- '-S', 'Aqua', agent_plist_path]) |
- if not ignore_failure and result != 0: |
- raise Error(self.package, self.root, AGENT_CONTROL_ERROR_CODE, |
- 'Google Software Update installer failed to %s agent from ' |
- 'plist "%s" (%d): %s' % |
- (action, agent_plist_path, result, errout)) |
- |
- def _ClearQuarantine(self, path): |
- """Remove LaunchServices quarantine attributes from a file hierarchy.""" |
- # /usr/bin/xattr* are implemented in Python, and there's much magic |
- # around which of /usr/bin/xattr and the multiple /usr/bin/xattr-2.? |
- # actually execute. I suspect at least some users have /usr/bin/python |
- # linked to a "real" copy or otherwise replaced, so we're going to |
- # try a bunch of different options. |
- # Implement it ourself |
- try: |
- import xattr |
- for (root, dirs, files) in os.walk(path): |
- for name in files: |
- attrs = xattr.xattr(os.path.join(path, name)) |
- try: |
- del attrs['com.apple.quarantine'] |
- except KeyError: |
- pass |
- return # Success |
- except: |
- pass |
- # Use specific version by name in case /usr/bin/python isn't the Apple magic |
- # that selects the right copy of xattr. xattr-2.6 present on 10.6 and 10.7. |
- if os.path.exists('/usr/bin/xattr-2.6'): |
- (result, out, errout) = self.RunCommand(['/usr/bin/xattr-2.6', '-dr', |
- 'com.apple.quarantine', path]) |
- if result == 0: |
- return |
- # Fall back to /usr/bin/xattr. On Leopard it doesn't support '-r' so |
- # recurse using find. Ignore the result, this is our last attempt. |
- self.RunCommand(['/usr/bin/find', '-x', path, '-exec', '/usr/bin/xattr', |
- '-d', 'com.apple.quarantine', '{}']) |
- |
- def Install(self): |
- """Perform a complete install operation, including safe upgrade""" |
- # Unload any current processes but ignore failures |
- if self.launchd_setup and self.launchd_jobs: |
- self._ChangeAgentRunStatus(False, True) |
- if self.is_system: |
- self._ChangeDaemonRunStatus(False, True) |
- # Install new files |
- if self.is_system: |
- self._SetSystemInstallPermissions() |
- try: |
- # Make and protect base directories (always safe during upgrade) |
- if not os.path.isdir(self._KeystoneDirPath()): |
- os.makedirs(self._KeystoneDirPath()) |
- if self.is_system: |
- os.chown(self._KeystoneDirPath(), 0, 0) |
- os.chmod(self._KeystoneDirPath(), 0755) |
- os.chown(self._LibraryGoogleDirPath(), 0, 0) |
- os.chmod(self._LibraryGoogleDirPath(), 0755) |
- # Unpack Keystone bundle. In an upgrade we want to try to restore |
- # to the old binary if install encounters a problem. Options flag names |
- # chosen to be compatible with both 10.4 and 10.6 (both BSD tar, but |
- # very different versions). |
- saved_bundle_path = self._KeystoneBundlePath().rstrip('/') + '.old' |
- if os.path.exists(self._KeystoneBundlePath()): |
- if os.path.isdir(saved_bundle_path): |
- shutil.rmtree(saved_bundle_path) |
- elif os.path.exists(saved_bundle_path): |
- os.unlink(saved_bundle_path) |
- os.rename(self._KeystoneBundlePath(), saved_bundle_path) |
- cmd = ['/usr/bin/tar', 'xjf', self.package, '--no-same-owner', |
- '-C', self._KeystoneDirPath()] |
- (result, out, errout) = self.RunCommand(cmd) |
- if result != 0: |
- try: |
- if os.path.exists(saved_bundle_path): |
- os.rename(saved_bundle_path, self._KeystoneBundlePath()) |
- finally: |
- raise Error(self.package, self.root, FILE_INSTALLATION_ERROR_CODE, |
- 'Google Software Update installer unable to unpack ' |
- 'package: "%s"' % errout) |
- if os.path.exists(saved_bundle_path): |
- shutil.rmtree(saved_bundle_path) |
- # Clear quarantine on the new bundle. Failure is ignored, user will |
- # be prompted if quarantine is not cleared, but we will still operate |
- # correctly. |
- self._ClearQuarantine(self._KeystoneBundlePath()) |
- # Create Keystone ticket store. On a system install start by checking |
- # ticket store permissions. Bad permissions on the store could be the |
- # result of a prior install or an attempt to poison the store. |
- if self.is_system and os.path.exists(self._KeystoneTicketStorePath()): |
- s = os.lstat(self._KeystoneTicketStorePath()) |
- if (s[stat.ST_UID] == 0 and |
- (s[stat.ST_GID] == 0 or s[stat.ST_GID] == 80)): |
- pass |
- else: |
- if os.path.isdir(self._KeystoneTicketStorePath()): |
- shutil.rmtree(self._KeystoneTicketStorePath()) |
- else: |
- os.unlink(self._KeystoneTicketStorePath()) |
- # Now create and protect ticket store |
- if not os.path.isdir(self._KeystoneTicketStorePath()): |
- os.makedirs(self._KeystoneTicketStorePath()) |
- if self.is_system: |
- os.chown(self._KeystoneTicketStorePath(), 0, 0) |
- os.chmod(self._KeystoneTicketStorePath(), 0755) |
- # Create/update Keystone ticket |
- ksadmin_path = self._KsadminPath() |
- if not ksadmin_path or not os.path.exists(ksadmin_path): |
- raise Error(self.package, self.root, KSADMIN_MISSING_ERROR_CODE, |
- 'Google Software Update installer ksadmin not available') |
- cmd = [ksadmin_path, |
- # store is specified explicitly so unit tests work |
- '--store', os.path.join(self._KeystoneTicketStorePath(), |
- 'Keystone.ticketstore'), |
- '--register', |
- '--productid', 'com.google.Keystone', |
- '--version', self.MyKeystoneBundleVersion(), |
- '--xcpath', self._KeystoneBundlePath(), |
- '--url', OMAHA_SERVER_URL, |
- '--preserve-tttoken'] |
- (result, out, errout) = self.RunCommand(cmd) |
- if result != 0: |
- raise Error(self.package, self.root, KEYSTONE_TICKET_ERROR_CODE, |
- 'Google Software Update installer Keystone ticket install failed ' |
- '(%d): %s' % (result, errout)) |
- # launchd config if requested |
- if self.launchd_setup: |
- # Daemon first (safer if upgrade fails) |
- if self.is_system: |
- if not os.path.isdir(self._LaunchDaemonConfigDir()): |
- os.makedirs(self._LaunchDaemonConfigDir()) |
- # Again set permissions only if we created it, but if we did use |
- # standard permission from a default OS install. |
- os.chown(self._LaunchDaemonConfigDir(), 0, 0) |
- os.chmod(self._LaunchDaemonConfigDir(), 0755) |
- self._InstallPlist(self._DaemonPlistSourceFileName(), |
- self._DaemonPlistFileName(), |
- self._LaunchDaemonConfigDir()) |
- # Remove daemon under older names |
- self._RemoveOldDaemonPlists() |
- # Agent launchd |
- if self._AgentPlistFileName() is not None: |
- if not os.path.isdir(self._LaunchAgentConfigDir()): |
- os.makedirs(self._LaunchAgentConfigDir()) |
- # /Library/LaunchAgents is a OS directory, use permissions from |
- # default OS install, but only if we created it. |
- if self.is_system: |
- os.chown(self._LaunchAgentConfigDir(), 0, 0) |
- os.chmod(self._LaunchAgentConfigDir(), 0755) |
- self._InstallPlist(self._AgentPlistFileName(), |
- self._AgentPlistFileName(), |
- self._LaunchAgentConfigDir()) |
- # Agent login item remove/restore. Removal prior to add |
- # so that removal happens in Tiger -> Leopard upgrade case and |
- # we do not duplicate entries. |
- self._RemoveAgentLoginItem() |
- self._InstallAgentLoginItem() |
- finally: |
- if self.is_system: |
- self._ClearSystemInstallPermissions() |
- # If requested, start our jobs, failures treated as errors. |
- if self.launchd_setup and self.launchd_jobs: |
- if self.is_system: |
- self._ChangeDaemonRunStatus(True, False) |
- self._ChangeAgentRunStatus(True, False) |
- |
- def LockdownKeystone(self): |
- """Prevent Keystone from ever self-uninstalling. |
- |
- This is necessary for a System Keystone used for Trusted Tester support. |
- We do this by installing (and never uninstalling) a system ticket. |
- """ |
- if self.is_system: |
- self._SetSystemInstallPermissions() |
- try: |
- ksadmin_path = self._KsadminPath() |
- if not ksadmin_path: |
- raise Error(self.package, self.root, KSADMIN_MISSING_ERROR_CODE, |
- 'Google Software Update installer ksadmin not available') |
- cmd = [ksadmin_path, |
- # store is specified explicitly so unit tests work |
- '--store', os.path.join(self._KeystoneTicketStorePath(), |
- 'Keystone.ticketstore'), |
- '--register', |
- '--productid', LOCKDOWN_TICKET, |
- '--version', '1.0', |
- '--xcpath', '/', |
- '--url', OMAHA_SERVER_URL] |
- (result, out, errout) = self.RunCommand(cmd) |
- if result != 0: |
- raise Error(self.package, self.root, KEYSTONE_TICKET_ERROR_CODE, |
- 'Google Software Update installer Keystone ticket install ' |
- 'failed (%d): %s' % (result, errout)) |
- finally: |
- if self.is_system: |
- self._ClearSystemInstallPermissions() |
- |
- def Uninstall(self): |
- """Perform a complete uninstall (uninstall leaves tickets in place)""" |
- # On uninstall if we are not in self-destruct stop all processes but |
- # ignore failure (may not be running). On a non-self destruct case we do |
- # this first since it avoids race conditions on caches and pref writes |
- if not self.self_destruct and self.launchd_setup and self.launchd_jobs: |
- self._ChangeAgentRunStatus(False, True) |
- if self.is_system: |
- self._ChangeDaemonRunStatus(False, True) |
- # Perform file removals. In self-destruct case the processes may still |
- # be running. |
- if self.is_system: |
- self._SetSystemInstallPermissions() |
- try: |
- # Remove plist files unless blocked |
- if self.launchd_setup: |
- # In self-destruct mode we still need these plists for launchctl |
- if not self.self_destruct: |
- if self._AgentPlistFileName() is not None: |
- agent_plist = os.path.join(self._LaunchAgentConfigDir(), |
- self._AgentPlistFileName()) |
- if os.path.exists(agent_plist): |
- os.unlink(agent_plist) |
- daemon_plist = os.path.join(self._LaunchDaemonConfigDir(), |
- self._DaemonPlistFileName()) |
- if os.path.exists(daemon_plist): |
- os.unlink(daemon_plist) |
- # Remove daemon under older names as well |
- self._RemoveOldDaemonPlists() |
- # Self-destruct or not, we can remove login item (Tiger) |
- self._RemoveAgentLoginItem() |
- # Unregister Keystone ticket (if installed at all). |
- if os.path.exists(self._KeystoneBundlePath()): |
- ksadmin_path = self._KsadminPath() |
- if not ksadmin_path or not os.path.exists(ksadmin_path): |
- raise Error(self.package, self.root, KSADMIN_MISSING_ERROR_CODE, |
- 'Google Software Update installer ksadmin not available') |
- cmd = [ksadmin_path, |
- # store is specified explicitly so unit tests work |
- '--store', os.path.join(self._KeystoneTicketStorePath(), |
- 'Keystone.ticketstore'), |
- '--delete', '--productid', 'com.google.Keystone'] |
- (result, out, errout) = self.RunCommand(cmd) |
- if result != 0 and errout.find('No ticket to delete') == -1: |
- raise Error(self.package, self.root, KEYSTONE_TICKET_ERROR_CODE, |
- 'Google Software Update installer Keystone ticket uninstall ' |
- 'failed (%d): %s' % (result, errout)) |
- # Remove the Keystone bundle |
- if os.path.exists(self._KeystoneBundlePath()): |
- shutil.rmtree(self._KeystoneBundlePath()) |
- # Clean up caches. Race condition here if self-destructing, but unlikely |
- # and we'll just leak a cache dir. |
- if os.path.exists(self._LibraryCachesDirPath()): |
- caches = glob.glob(os.path.join(self._LibraryCachesDirPath(), |
- 'com.google.Keystone.*')) |
- caches.extend(glob.glob(os.path.join(self._LibraryCachesDirPath(), |
- 'com.google.UpdateEngine.*'))) |
- caches.extend(glob.glob(os.path.join(self._LibraryCachesDirPath(), |
- 'UpdateEngine-Temp'))) |
- for cache_item in caches: |
- if os.path.isdir(cache_item): |
- shutil.rmtree(cache_item, True) # Ignore cache deletion errors |
- else: |
- try: |
- os.unlink(cache_item) |
- except OSError: |
- pass |
- # Clean up preferences, this prevents old installations from propagating |
- # dates (like uninstall embargo time) forward in a complete uninstall/ |
- # reinstall scenario. Again, race condition here for self-destruct case |
- # but the risk is minor and only leaks a pref file. |
- if self.is_system: |
- agent_pref_path = os.path.join(pwd.getpwuid(self.agent_job_uid)[5], |
- 'Library/Preferences/' |
- 'com.google.Keystone.Agent.plist') |
- else: |
- agent_pref_path = os.path.expanduser('~/Library/Preferences/' |
- 'com.google.Keystone.Agent.plist') |
- if os.path.exists(agent_pref_path): |
- os.unlink(agent_pref_path) |
- finally: |
- if self.is_system: |
- self._ClearSystemInstallPermissions() |
- # Remove receipts |
- self.RemoveReceipts() |
- # With all other files removed, cleanup processes and job control files in |
- # the self-destruct case. This will presumably kill our parent, so after |
- # this no one is listening for our errors. We do it as late as possible. |
- if self.self_destruct: |
- if self.launchd_setup and self.launchd_jobs: |
- self._ChangeAgentRunStatus(False, True) |
- if self.is_system: |
- self._ChangeDaemonRunStatus(False, True) |
- if self.is_system: |
- self._SetSystemInstallPermissions() |
- try: |
- # We needed these plists to stop the agent and daemon. No one is |
- # listening to errors, but failure only leaves a stale launchctl file |
- # (actual program files removed above) |
- if self._AgentPlistFileName() is not None: |
- agent_plist = os.path.join(self._LaunchAgentConfigDir(), |
- self._AgentPlistFileName()) |
- if os.path.exists(agent_plist): |
- os.unlink(agent_plist) |
- daemon_plist = os.path.join(self._LaunchDaemonConfigDir(), |
- self._DaemonPlistFileName()) |
- if os.path.exists(daemon_plist): |
- os.unlink(daemon_plist) |
- # Remove daemon under older names as well |
- self._RemoveOldDaemonPlists() |
- finally: |
- if self.is_system: |
- self._ClearSystemInstallPermissions() |
- |
- def Nuke(self): |
- """Perform an uninstall and remove all files (including tickets)""" |
- # Uninstall |
- self.Uninstall() |
- # Nuke what's left |
- if self.is_system: |
- self._SetSystemInstallPermissions() |
- try: |
- # Remove whole Keystone tree |
- if os.path.exists(self._KeystoneDirPath()): |
- shutil.rmtree(self._KeystoneDirPath()) |
- finally: |
- if self.is_system: |
- self._ClearSystemInstallPermissions() |
- |
- def RemoveReceipts(self): |
- """Remove receipts from Apple's package database, allowing downgrade or |
- reinstall.""" |
- # Only works on system installs |
- if self.is_system: |
- self._SetSystemInstallPermissions() |
- try: |
- # In theory we should only handle old-style receipts on older OS |
- # versions. However, we don't know the upgrade history of the machine. |
- # So we try all variants. |
- if os.path.isdir('/Library/Receipts/Keystone.pkg'): |
- shutil.rmtree('/Library/Receipts/Keystone.pkg', True) |
- if os.path.exists('/Library/Receipts/Keystone.pkg'): |
- try: |
- os.unlink('/Library/Receipts/Keystone.pkg') |
- except OSError: |
- pass |
- if os.path.isdir('/Library/Receipts/UninstallKeystone.pkg'): |
- shutil.rmtree('/Library/Receipts/UninstallKeystone.pkg', True) |
- if os.path.exists('/Library/Receipts/UninstallKeystone.pkg'): |
- try: |
- os.unlink('/Library/Receipts/UninstallKeystone.pkg') |
- except OSError: |
- pass |
- if os.path.isdir('/Library/Receipts/NukeKeystone.pkg'): |
- shutil.rmtree('/Library/Receipts/NukeKeystone.pkg', True) |
- if os.path.exists('/Library/Receipts/NukeKeystone.pkg'): |
- try: |
- os.unlink('/Library/Receipts/NukeKeystone.pkg') |
- except OSError: |
- pass |
- # pkgutil where appropriate (ignoring results) |
- if os.path.exists('/usr/sbin/pkgutil'): |
- self.RunCommand(['/usr/sbin/pkgutil', '--forget', |
- 'com.google.pkg.Keystone']) |
- self.RunCommand(['/usr/sbin/pkgutil', '--forget', |
- 'com.google.pkg.UninstallKeystone']) |
- self.RunCommand(['/usr/sbin/pkgutil', '--forget', |
- 'com.google.pkg.NukeKeystone']) |
- finally: |
- self._ClearSystemInstallPermissions() |
- |
- def FixupProducts(self): |
- """Attempt to repair any products might be broken.""" |
- if self.is_system: |
- self._SetSystemInstallPermissions() |
- try: |
- # Remove the (original) Google Updater manifest files. Stale manifest |
- # caches prevent Updater from checking for updates and downloading its |
- # auto-uninstall package. Stomp those files everywhere we can. |
- try: |
- os.unlink(os.path.expanduser('~/Library/Application Support/' |
- 'Google/SoftwareUpdates/manifest.xml')) |
- except OSError: |
- pass |
- if self.agent_job_uid is not None: |
- try: |
- os.unlink(os.path.join(pwd.getpwuid(self.agent_job_uid)[5], |
- 'Library/Application Support/' |
- 'Google/SoftwareUpdates/manifest.xml')) |
- except OSError: |
- pass |
- try: |
- os.unlink(os.path.join(self.root, 'Library/Application Support/' |
- 'Google/SoftwareUpdates/manifest.xml')) |
- except OSError: |
- pass |
- try: |
- os.unlink('/Library/Caches/Google/SoftwareUpdates/manifest.xml') |
- except OSError: |
- pass |
- |
- # Other repairs require ksadmin |
- ksadmin_path = self._KsadminPath() |
- if ksadmin_path and os.path.exists(ksadmin_path): |
- |
- # Fix various Talk plugin problems |
- if self.is_system: |
- (result, out, errout) = self.RunCommand([ksadmin_path, '--productid', |
- 'com.google.talkplugin', '-p']) |
- |
- # Google Talk Plugin 1.0.15.1351 can have its existence checker |
- # pointing to a deleted directory. Fix up the xc so it'll update |
- # next time. |
- if out.find('1.0.15.1351') != -1: |
- # Fix the ticket by reregistering it. |
- # We can only get here if 1.0.15.1351 is the current version, so |
- # it's safe to use that version. |
- (result, out, errout) = self.RunCommand([ksadmin_path, '--register', |
- '--productid', 'com.google.talkplugin', |
- '--xcpath', |
- '/Library/Internet Plug-Ins/googletalkbrowserplugin.plugin', |
- '--version', '1.0.15.1351', |
- '--url', OMAHA_SERVER_URL]) |
- |
- # Repair tickets presumed lost in the 1.x to 2.x Talk upgrade. |
- if ((out.find('productID=com.google.talkplugin') == -1) and |
- os.path.exists( |
- '/Library/Internet Plug-Ins/googletalkbrowserplugin.plugin')): |
- # Register Talk again, using a unique version number that will be |
- # updated. |
- (result, out, errout) = self.RunCommand([ksadmin_path, '--register', |
- '--productid', 'com.google.talkplugin', |
- '--xcpath', |
- '/Library/Internet Plug-Ins/googletalkbrowserplugin.plugin', |
- '--version', '0.1.0.1234', |
- '--url', OMAHA_SERVER_URL]) |
- |
- finally: |
- if self.is_system: |
- self._ClearSystemInstallPermissions() |
- |
-# ------------------------------------------------------------------------- |
- |
-class KeystoneInstallTiger(KeystoneInstall): |
- |
- """Like KeystoneInstall, but overrides a few methods to support 10.4""" |
- |
- def _AgentPlistFileName(self): |
- return None |
- |
- def _DaemonPlistSourceFileName(self): |
- return 'com.google.keystone.daemon4.plist' |
- |
- def _RemoveOldDaemonPlists(self): |
- # Older installers installed this under the wrong name |
- daemon_plist = os.path.join(self._LaunchDaemonConfigDir(), |
- self._DaemonPlistSourceFileName()) |
- if os.path.exists(daemon_plist): |
- os.unlink(daemon_plist) |
- |
- def _InstallAgentLoginItem(self): |
- # This will write to the Library domain as root/wheel, which is OK because |
- # permissions on /Library/Preferences still allow admin group to modify |
- if self.is_system: |
- domain = '/Library/Preferences/loginwindow' |
- else: |
- domain = 'loginwindow' |
- (result, out, errout) = self.RunCommand( |
- ['/usr/bin/defaults', 'write', domain, |
- 'AutoLaunchedApplicationDictionary', '-array-add', |
- '{Hide = 1; Path = "%s"; }' % self._KeystoneAgentPath()]) |
- if result == 0: |
- return |
- # An empty AutoLaunchedApplicationDictionary is an empty string, |
- # not an empty array, in which case -array-add chokes. There is |
- # no easy way to do a typeof(AutoLaunchedApplicationDictionary) |
- # for a plist. Our solution is to catch the error and try a |
- # different way. |
- (result, out, errout) = self.RunCommand( |
- ['/usr/bin/defaults', 'write', domain, |
- 'AutoLaunchedApplicationDictionary', '-array', |
- '{Hide = 1; Path = "%s"; }' % self._KeystoneAgentPath()]) |
- if result != 0: |
- raise Error(self.package, self.root, AGENT_INSTALL_ERROR_CODE, |
- 'Google Software Update installer Keystone agent login item ' |
- 'in domain "%s" failed (%d): %s' % (domain, result, errout)) |
- |
- def _ChangeAgentRunStatus(self, start, ignore_failure): |
- """Start the agent as a normal (non-launchd) process on Tiger.""" |
- if self.is_system: |
- self._SetSystemInstallPermissions() |
- try: |
- # Start |
- if start: |
- if self.is_system: |
- # Tiger 'sudo' has problems with numeric uid so use username (man |
- # page wrong) |
- username = pwd.getpwuid(self.agent_job_uid)[0] |
- (result, out, errout) = self.RunCommand(['/usr/bin/sudo', |
- '-u', username, |
- '/usr/bin/open', |
- self._KeystoneAgentPath()]) |
- if not ignore_failure and result != 0: |
- raise Error(self.package, self.root, AGENT_CONTROL_ERROR_CODE, |
- 'Google Software Update installer failed to start ' |
- 'system agent for uid %d (%d): %s' % |
- (self.agent_job_uid, result, errout)) |
- else: |
- (result, out, errout) = self.RunCommand(['/usr/bin/open', |
- self._KeystoneAgentPath()]) |
- if not ignore_failure and result != 0: |
- raise Error(self.package, self.root, AGENT_CONTROL_ERROR_CODE, |
- 'Google Software Update installer failed to start ' |
- 'user agent (%d): %s' % (result, errout)) |
- # Stop |
- else: |
- if self.is_system: |
- cmd = ['/usr/bin/killall', '-u', str(self.agent_job_uid), |
- self._AgentProcessName()] |
- else: |
- cmd = ['/usr/bin/killall', self._AgentProcessName()] |
- (result, out, errout) = self.RunCommand(cmd) |
- if (not ignore_failure and result != 0 and |
- out.find('No matching processes') == -1): |
- raise Error(self.package, self.root, AGENT_CONTROL_ERROR_CODE, |
- 'Google Software Update installer failed to kill ' |
- 'agent (%d): %s' % (result, errout)) |
- finally: |
- if self.is_system: |
- self._ClearSystemInstallPermissions() |
- |
- def _ClearQuarantine(self, path): |
- """Remove LaunchServices quarantine attributes from a file hierarchy.""" |
- # Tiger does not implement quarantine (http://support.apple.com/kb/HT3662) |
- return |
- |
- |
-# ------------------------------------------------------------------------- |
- |
-class Keystone(object): |
- |
- """Top-level interface for Keystone install and uninstall. |
- |
- Attributes: |
- install_class: KeystoneInstall subclass to use for installation |
- installer: KeystoneInstall instance (system or user) |
- """ |
- |
- def __init__(self, package, root, launchd_setup, start_jobs, self_destruct): |
- # Sanity |
- if package: |
- package = os.path.abspath(os.path.expanduser(package)) |
- if not CheckOnePath(package, stat.S_IRUSR): |
- raise Error(package, root, PACKAGE_ERROR_CODE, |
- 'Google Software Update installer missing or unreadable ' |
- 'installation package.') |
- if root: |
- expanded_root = os.path.abspath(os.path.expanduser(root)) |
- assert (expanded_root and |
- len(expanded_root) > 0), 'Root is empty after expansion.' |
- # Force user-supplied root to pre-exist, this was a side effect of |
- # prior versions of the code and the tests assume its part of the contract |
- if not CheckOnePath(expanded_root, stat.S_IWUSR): |
- raise Error(package, root, BAD_ROOT_ERROR_CODE, |
- 'Google Software Update installer installation location ' |
- 'missing or unwritable.') |
- root = expanded_root |
- |
- # Setup installer instances |
- self.install_class = KeystoneInstall |
- if self._IsTiger(): |
- self.install_class = KeystoneInstallTiger |
- if self._IsPrivilegedInstall(): |
- # Install using privileges on behalf of other user (for agent start) |
- install_uid = self._LocalUserUID() |
- if root is not None: |
- self.installer = self.install_class(package, True, install_uid, root, |
- launchd_setup, start_jobs, |
- self_destruct) |
- else: |
- self.installer = self.install_class(package, True, install_uid, |
- self._DefaultRootForUID(0), |
- launchd_setup, start_jobs, |
- self_destruct) |
- else: |
- # Non-system install, no attempt at privilege changes |
- if root is not None: |
- self.installer = self.install_class(package, False, None, root, |
- launchd_setup, start_jobs, |
- self_destruct) |
- else: |
- self.installer = self.install_class(package, False, None, |
- self._DefaultRootForUID( |
- self._LocalUserUID()), |
- launchd_setup, start_jobs, |
- self_destruct) |
- |
- def _LocalUserUID(self): |
- """Return the UID of the local (non-root) user who initiated this |
- install/uninstall. If we can't figure it out, default to the user |
- on conosle. We don't want to default to console user in case a |
- FUS happens in the middle of install or uninstall.""" |
- uid = os.geteuid() |
- if uid != 0: |
- return uid |
- else: |
- return os.stat('/dev/console')[stat.ST_UID] |
- |
- def _IsLeopardOrLater(self): |
- """Return True if we're on 10.5 or later; else return False.""" |
- global FORCE_TIGER |
- if FORCE_TIGER: |
- return False |
- # Ouch! platform.mac_ver() returns strange results. |
- # ('10.7', ('', '', ''), 'i386') - 10.7, python2.7 |
- # ('10.7.0', ('', '', ''), 'i386') - 10.7, python2.5 or python2.6 |
- # ('10.6.7', ('', '', ''), 'i386') - 10.6, python2.5 or python2.6 |
- # ('10.5.1', ('', '', ''), 'i386') - 10.5, python2.4 or python2.5 |
- # ('', ('', '', ''), '') - 10.4, python2.3 (also 2.4) |
- (vers, ignored1, ignored2) = platform.mac_ver() |
- splits = vers.split('.') |
- # Try to break down a proper version number |
- if ((len(splits) == 2) or (len(splits) == 3)) and (splits[1] >= '5'): |
- return True |
- # Tiger is rare these days, so unless we're on 2.3 build of Python |
- # assume we must be newer. |
- if (((sys.version_info[0] == 2) and (sys.version_info[1] == 3)) or |
- ((sys.version_info[0] == 2) and (sys.version_info[1] == 4) and |
- (vers == ''))): |
- return False |
- else: |
- return True |
- |
- def _IsTiger(self): |
- """Return the boolean opposite of IsLeopardOrLater().""" |
- if self._IsLeopardOrLater(): |
- return False |
- else: |
- return True |
- |
- def _IsPrivilegedInstall(self): |
- """Return True if this is a privileged (root) install.""" |
- if os.geteuid() == 0: |
- return True |
- else: |
- return False |
- |
- def _DefaultRootForUID(self, uid): |
- """For the given UID, return the default install root for Keystone (where |
- is is, or where it should be, installed).""" |
- if uid == 0: |
- return '/' |
- else: |
- return pwd.getpwuid(uid)[5] |
- |
- def _ShouldInstall(self): |
- """Return True if we should on install. |
- |
- Possible reasons for punting (returning False): |
- 1) This is a System Keystone install and the installed System |
- Keystone has a smaller version. |
- 2) This is a User Keystone and there is a System Keystone |
- installed (of any version). |
- 3) This is a User Keystone and the installed User Keystone has a |
- smaller version. |
- """ |
- if self._IsPrivilegedInstall(): |
- if self.installer.IsMyVersionGreaterThanInstalledVersion(): |
- return True |
- else: |
- return False |
- else: |
- # User install, need to check if system install exists |
- system_checker = self.install_class(None, False, None, |
- self._DefaultRootForUID(0), |
- False, False, False) |
- if system_checker.InstalledKeystoneBundleVersion() != None: |
- return False |
- # Check just user version |
- if self.installer.IsMyVersionGreaterThanInstalledVersion(): |
- return True |
- else: |
- return False |
- |
- def Install(self, force, lockdown): |
- """Public install interface. |
- |
- force: If True, no version check is performed. |
- lockdown: if True, install a special ticket to lock down Keystone |
- and prevent uninstall. This will happen even if an install |
- of Keystone itself is not needed. |
- """ |
- if self.installer._IsMasterDisabled(): |
- raise Error(None, None, MASTER_DISABLE_ERROR_CODE, |
- 'Google Software Update installer failed. An administrator has ' |
- 'disabled Google Software Update.') |
- if force or self._ShouldInstall(): |
- self.installer.Install() |
- # possibly lockdown even if we don't need to install |
- if lockdown: |
- self.installer.LockdownKeystone() |
- |
- def Uninstall(self): |
- """Uninstall, which has the effect of preparing this machine for a new |
- install. Although similar, it is NOT as comprehensive as a nuke. |
- """ |
- self.installer.Uninstall() |
- |
- def Nuke(self): |
- """Public nuke interface. Typically only used for testing.""" |
- self.installer.Nuke() |
- |
- def RemoveReceipts(self): |
- """Public receipt removal interface. Used by uninstall, and to allow |
- downgraades of system installations.""" |
- self.installer.RemoveReceipts() |
- |
- def FixupProducts(self): |
- """Attempt to repair any products might have broken tickets.""" |
- self.installer.FixupProducts() |
- |
-# ------------------------------------------------------------------------- |
- |
-def PrintUse(): |
- print 'Use: ' |
- print ' [--install PKG] Install keystone using PKG as the source.' |
- print ' [--root ROOT] Use ROOT as the dest for an install. Optional.' |
- print ' [--uninstall] Remove Keystone program files but do NOT delete ' |
- print ' the ticket store.' |
- print ' [--nuke] Remove Keystone and all tickets.' |
- print ' [--remove-receipts] Remove Keystone package receipts, allowing for ' |
- print ' downgrade (system install only)' |
- print ' [--no-launchd] Do NOT touch Keystone launchd plists or jobs,' |
- print ' for both install and uninstall. For test.' |
- print ' [--no-launchdjobs] Do NOT start/stop jobs, but do change launchd' |
- print ' plist files,for both install and uninstall.' |
- print ' For test.' |
- print ' [--self-destruct] Use if uninstall is triggered by process that ' |
- print ' will be killed by uninstall.' |
- print ' [--force] Force an install no matter what. For test.' |
- print ' [--forcetiger] Pretend we are on Tiger (MacOSX 10.4). For test.' |
- print ' [--failcode] Fake an error with that code occurred. For test.' |
- print ' [--lockdown] Prevent Keystone from ever uninstalling itself.' |
- print ' [--interval N] Change agent plist to wake up every N seconds.' |
- print ' [--help] This message.' |
- |
- |
-def main(): |
- os.environ.clear() |
- os.environ['PATH'] = '/bin:/sbin:/usr/bin:/usr/sbin:/usr/libexec' |
- |
- # Make sure AuthorizationExecuteWithPrivileges() is happy |
- if os.getuid() and os.geteuid() == 0: |
- os.setuid(os.geteuid()) |
- |
- try: |
- opts, args = getopt.getopt(sys.argv[1:], 'i:r:XunNfI:h', |
- ['install=', 'root=', 'nuke', 'uninstall', |
- 'no-launchd', 'no-launchdjobs', 'force', |
- 'interval=', 'help', |
- # Long-only |
- 'remove-receipts', 'self-destruct', |
- 'forcetiger', 'failcode=', 'lockdown']) |
- except getopt.GetoptError: |
- print 'Bad options.' |
- PrintUse() |
- sys.exit(USAGE_ERROR_CODE) |
- |
- root = None |
- package = None |
- nuke = False |
- uninstall = False |
- remove_receipts = False |
- launchd_setup = True |
- fail_code = 0 |
- start_jobs = True |
- self_destruct = False |
- force = False |
- lockdown = False # If true, prevent uninstall by adding a "lockdown" ticket |
- |
- for opt, val in opts: |
- if opt in ('-h', '--help'): |
- PrintUse() |
- sys.exit(USAGE_ERROR_CODE) |
- |
- if opt in ['-i', '--install']: |
- package = val |
- if opt in ['-r', '--root']: |
- root = val |
- if opt in ['-X', '--nuke']: |
- nuke = True |
- if opt in ['-u', '--uninstall']: |
- uninstall = True |
- if opt in ['-n', '--no-launchd']: |
- launchd_setup = False |
- if opt in ['-N', '--no-launchdjobs']: |
- start_jobs = False |
- if opt in ['-f', '--force']: |
- force = True |
- if opt in ['-I', '--interval']: |
- global AGENT_START_INTERVAL |
- AGENT_START_INTERVAL = int(val) |
- if opt == '--remove-receipts': |
- remove_receipts = True |
- if opt == '--self-destruct': |
- self_destruct = True |
- if opt == '--forcetiger': |
- global FORCE_TIGER |
- FORCE_TIGER = True |
- if opt == '--failcode': |
- fail_code = int(val) |
- if opt == '--lockdown': |
- lockdown = True |
- |
- if package is None and not nuke and not uninstall and not remove_receipts: |
- print 'Must specify package path, uninstall, nuke, or remove-receipts.' |
- PrintUse() |
- sys.exit(USAGE_ERROR_CODE) |
- try: |
- (vers, ignored1, ignored2) = platform.mac_ver() |
- splits = vers.split('.') |
- if (len(splits) == 3) and (int(splits[1]) < 4): |
- print 'Requires Mac OS 10.4 or later.' |
- sys.exit(UNSUPPORTED_OS_ERROR_CODE) |
- except: |
- # 10.3 throws an exception for platform.mac_ver() |
- print 'Requires Mac OS 10.4 or later.' |
- sys.exit(UNSUPPORTED_OS_ERROR_CODE) |
- |
- # Lock file to make sure only one Keystone install at once. We want to |
- # share this lock amongst all users on the machine. |
- lockfilename = '/tmp/.keystone_install_lock' |
- oldmask = os.umask(0000) |
- lockfile = os.open(lockfilename, os.O_CREAT | os.O_RDONLY | os.O_NOFOLLOW, |
- 0444) |
- os.umask(oldmask) |
- # Lock, callers that cannot wait are expected to kill us. |
- fcntl.flock(lockfile, fcntl.LOCK_EX) |
- |
- try: |
- try: |
- # Simulate a failure |
- if fail_code != 0: |
- raise Error(None, None, fail_code, |
- 'Google Software Update installer simulated failure %d' % fail_code) |
- # Do the install |
- k = Keystone(package, root, launchd_setup, start_jobs, self_destruct) |
- # Ordered by level of cleanup applied |
- if nuke: |
- k.Nuke() |
- elif uninstall: |
- k.Uninstall() |
- elif remove_receipts: |
- k.RemoveReceipts() |
- else: |
- k.Install(force, lockdown) |
- k.FixupProducts() |
- except Error, e: |
- # To conform to previous contract on this tool (see headerdoc) |
- print e.message() |
- # We want the backtrace on stderr, but we need to control the exit code |
- # so dump manually |
- traceback.print_exc(file=sys.stderr) |
- # exit with the right error code |
- sys.exit(e.errorcode()) |
- finally: |
- os.close(lockfile) # Lock file left around on purpose |
- |
-if __name__ == '__main__': |
- main() |