def killStupidProcesses(): '''A nasty bit of hackery to get Adobe CS5 AAMEE packages to install when at the loginwindow.''' stupid_processes = ["Adobe AIR Installer", "Adobe AIR Application Installer", "InstallAdobeHelp", "open -a /Library/Application Support/Adobe/SwitchBoard/SwitchBoard.app"] for procname in stupid_processes: pid = utils.getPIDforProcessName(procname) if pid: if not pid in secondsToLive: secondsToLive[pid] = 30 else: secondsToLive[pid] = secondsToLive[pid] - 1 if secondsToLive[pid] == 0: # it's been running too long; kill it munkicommon.log("Killing PID %s: %s" % (pid, procname)) try: os.kill(int(pid), 9) except OSError: pass # remove this PID from our list del secondsToLive[pid] # only kill one process per invocation return
def availableUpdatesAreDownloaded(): '''Verifies that applicable/available Apple updates have been downloaded. Returns False if one or more product directories are missing, True otherwise (including when there are no available updates).''' appleUpdates = getSoftwareUpdateInfo() if not appleUpdates: return True try: downloadIndex = FoundationPlist.readPlist( '/Library/Updates/index.plist') downloaded = downloadIndex.get('ProductPaths', []) except FoundationPlist.FoundationPlistException: munkicommon.log('Apple downloaded update index is invalid. ' '/Library/Updates/index.plist') return False for update in appleUpdates: productKey = update.get('productKey') if productKey: if (productKey not in downloaded or not os.path.isdir( os.path.join('/Library/Updates', downloaded[productKey]))): munkicommon.log('Apple Update product directory for %s is ' 'missing.' % update['name']) return False return True
def downloadAvailableUpdates(): '''Downloads the available Apple updates using our local filtered sucatalog. Returns True if successful, False otherwise.''' msg = "Downloading available Apple Software Updates..." if munkicommon.munkistatusoutput: munkistatus.message(msg) munkistatus.detail("") munkistatus.percent(-1) munkicommon.log(msg) else: munkicommon.display_status(msg) # use our filtered local catalog catalogpath = os.path.join(swupdCacheDir(), 'content/catalogs/local_download.sucatalog') if not os.path.exists(catalogpath): munkicommon.display_error( 'Missing local Software Update catalog at %s', catalogpath) return False catalogURL = 'file://localhost' + urllib2.quote(catalogpath) # get the OS version osvers = int(os.uname()[2].split('.')[0]) if osvers == 9: retcode = leopardDownloadAvailableUpdates(catalogURL) else: retcode = run_softwareupdate(['--CatalogURL', catalogURL, '-d', '-a']) if retcode: # there was an error munkicommon.display_error("softwareupdate error: %s" % retcode) return False return True
def getAvailableUpdates(): '''Returns a list of product IDs of available Apple updates''' msg = "Checking for available Apple Software Updates..." if munkicommon.munkistatusoutput: munkistatus.message(msg) munkistatus.detail("") munkistatus.percent(-1) munkicommon.log(msg) else: munkicommon.display_status(msg) applicable_updates = os.path.join(swupdCacheDir(), 'ApplicableUpdates.plist') if os.path.exists(applicable_updates): # remove any old item try: os.unlink(applicable_updates) except (OSError, IOError): pass # use our locally-cached Apple catalog catalogpath = os.path.join(swupdCacheDir(), 'content/catalogs/apple_index.sucatalog') catalogURL = 'file://localhost' + urllib2.quote(catalogpath) su_options = ['--CatalogURL', catalogURL, '-l', '-f', applicable_updates] retcode = run_softwareupdate(su_options) if retcode: # there was an error osvers = int(os.uname()[2].split('.')[0]) if osvers == 9: # always a non-zero retcode on Leopard pass else: munkicommon.display_error("softwareupdate error: %s" % retcode) return [] if os.path.exists(applicable_updates): try: updatelist = FoundationPlist.readPlist(applicable_updates) if updatelist: results_array = updatelist.get('phaseResultsArray', []) return [ item['productKey'] for item in results_array if 'productKey' in item ] except FoundationPlist.NSPropertyListSerializationException: return [] return []
def getAvailableUpdates(): '''Returns a list of product IDs of available Apple updates''' msg = "Checking for available Apple Software Updates..." if munkicommon.munkistatusoutput: munkistatus.message(msg) munkistatus.detail("") munkistatus.percent(-1) munkicommon.log(msg) else: munkicommon.display_status(msg) applicable_updates = os.path.join(swupdCacheDir(), 'ApplicableUpdates.plist') if os.path.exists(applicable_updates): # remove any old item try: os.unlink(applicable_updates) except (OSError, IOError): pass # use our locally-cached Apple catalog catalogpath = os.path.join(swupdCacheDir(), 'content/catalogs/apple_index.sucatalog') catalogURL = 'file://localhost' + urllib2.quote(catalogpath) su_options = ['--CatalogURL', catalogURL, '-l', '-f', applicable_updates] retcode = run_softwareupdate(su_options) if retcode: # there was an error osvers = int(os.uname()[2].split('.')[0]) if osvers == 9: # always a non-zero retcode on Leopard pass else: munkicommon.display_error("softwareupdate error: %s" % retcode) return [] if os.path.exists(applicable_updates): try: updatelist = FoundationPlist.readPlist(applicable_updates) if updatelist: results_array = updatelist.get('phaseResultsArray', []) return [item['productKey'] for item in results_array if 'productKey' in item] except FoundationPlist.NSPropertyListSerializationException: return [] return []
def getResourceIfChangedAtomically(url, destinationpath, custom_headers=None, expected_hash=None, message=None, resume=False, verify=False, follow_redirects=False): """Gets file from a URL. Checks first if there is already a file with the necessary checksum. Then checks if the file has changed on the server, resuming or re-downloading as necessary. If the file has changed verify the pkg hash if so configured. Supported schemes are http, https, file. Returns True if a new download was required; False if the item is already in the local cache. Raises a MunkiDownloadError derived class if there is an error.""" changed = False # If we already have a downloaded file & its (cached) hash matches what # we need, do nothing, return unchanged. if resume and expected_hash and os.path.isfile(destinationpath): xattr_hash = getxattr(destinationpath, XATTR_SHA) if not xattr_hash: xattr_hash = writeCachedChecksum(destinationpath) if xattr_hash == expected_hash: #File is already current, no change. return False elif munkicommon.pref('PackageVerificationMode').lower() in [ 'hash_strict', 'hash' ]: try: os.unlink(destinationpath) except OSError: pass munkicommon.log('Cached payload does not match hash in catalog, ' 'will check if changed and redownload: %s' % destinationpath) #continue with normal if-modified-since/etag update methods. if follow_redirects != True: # If we haven't explicitly said to follow redirect, the preference decides follow_redirects = munkicommon.pref('FollowHTTPRedirects') url_parse = urlparse.urlparse(url) if url_parse.scheme in ['http', 'https']: changed = getHTTPfileIfChangedAtomically( url, destinationpath, custom_headers=custom_headers, message=message, resume=resume, follow_redirects=follow_redirects) elif url_parse.scheme == 'file': changed = getFileIfChangedAtomically(url_parse.path, destinationpath) else: raise MunkiDownloadError('Unsupported scheme for %s: %s' % (url, url_parse.scheme)) if changed and verify: (verify_ok, fhash) = verifySoftwarePackageIntegrity(destinationpath, expected_hash, always_hash=True) if not verify_ok: try: os.unlink(destinationpath) except OSError: pass raise PackageVerificationError() if fhash: writeCachedChecksum(destinationpath, fhash=fhash) return changed
def getResourceIfChangedAtomically(url, destinationpath, cert_info=None, custom_headers=None, expected_hash=None, message=None, resume=False, verify=False, follow_redirects=False): """Gets file from a URL. Checks first if there is already a file with the necessary checksum. Then checks if the file has changed on the server, resuming or re-downloading as necessary. If the file has changed verify the pkg hash if so configured. Supported schemes are http, https, file. Returns True if a new download was required; False if the item is already in the local cache. Raises a MunkiDownloadError derived class if there is an error.""" changed = False # If we already have a downloaded file & its (cached) hash matches what # we need, do nothing, return unchanged. if resume and expected_hash and os.path.isfile(destinationpath): xattr_hash = getxattr(destinationpath, XATTR_SHA) if not xattr_hash: xattr_hash = writeCachedChecksum(destinationpath) if xattr_hash == expected_hash: #File is already current, no change. return False elif munkicommon.pref('PackageVerificationMode').lower() in \ ['hash_strict', 'hash']: try: os.unlink(destinationpath) except OSError: pass munkicommon.log('Cached payload does not match hash in catalog, ' 'will check if changed and redownload: %s' % destinationpath) #continue with normal if-modified-since/etag update methods. url_parse = urlparse.urlparse(url) if url_parse.scheme in ['http', 'https']: changed = getHTTPfileIfChangedAtomically( url, destinationpath, cert_info=cert_info, custom_headers=custom_headers, message=message, resume=resume, follow_redirects=follow_redirects) elif url_parse.scheme == 'file': changed = getFileIfChangedAtomically(url_parse.path, destinationpath) else: raise MunkiDownloadError( 'Unsupported scheme for %s: %s' % (url, url_parse.scheme)) if changed and verify: (verify_ok, fhash) = verifySoftwarePackageIntegrity(destinationpath, expected_hash, always_hash=True) if not verify_ok: try: os.unlink(destinationpath) except OSError: pass raise PackageVerificationError() if fhash: writeCachedChecksum(destinationpath, fhash=fhash) return changed
def installAppleUpdates(): '''Uses /usr/sbin/softwareupdate to install previously downloaded updates. Returns True if a restart is needed after install, False otherwise.''' msg = "Installing available Apple Software Updates..." if munkicommon.munkistatusoutput: munkistatus.message(msg) munkistatus.detail("") munkistatus.percent(-1) munkicommon.log(msg) else: munkicommon.display_status(msg) restartneeded = restartNeeded() # use our filtered local catalog catalogpath = os.path.join(swupdCacheDir(), 'content/catalogs/local_install.sucatalog') if not os.path.exists(catalogpath): munkicommon.display_error( 'Missing local Software Update catalog at %s', catalogpath) # didn't do anything, so no restart needed return False installlist = getSoftwareUpdateInfo() installresults = {'installed': [], 'download': []} catalogURL = 'file://localhost' + urllib2.quote(catalogpath) retcode = run_softwareupdate(['--CatalogURL', catalogURL, '-i', '-a'], mode='install', results=installresults) if not 'InstallResults' in munkicommon.report: munkicommon.report['InstallResults'] = [] for item in installlist: rep = {} rep['name'] = item.get('display_name') rep['version'] = item.get('version_to_install', '') rep['applesus'] = True rep['productKey'] = item.get('productKey', '') message = "Apple Software Update install of %s-%s: %s" if rep['name'] in installresults['installed']: rep['status'] = 0 install_status = 'SUCCESSFUL' elif rep['name'] in installresults['download']: rep['status'] = -1 install_status = 'FAILED due to missing package.' munkicommon.display_warning( 'Apple update %s, %s failed. A sub-package was missing ' 'on disk at time of install.' % (rep['name'], rep['productKey'])) else: rep['status'] = -2 install_status = 'FAILED for unknown reason' munkicommon.display_warning( 'Apple update %s, %s failed to install. No record of ' 'success or failure.' % (rep['name'], rep['productKey'])) munkicommon.report['InstallResults'].append(rep) log_msg = message % (rep['name'], rep['version'], install_status) munkicommon.log(log_msg, "Install.log") if retcode: # there was an error munkicommon.display_error("softwareupdate error: %s" % retcode) # clean up our now stale local cache cachedir = os.path.join(swupdCacheDir()) if os.path.exists(cachedir): unused_retcode = subprocess.call(['/bin/rm', '-rf', cachedir]) # remove the now invalid appleUpdatesFile try: os.unlink(appleUpdatesFile()) except OSError: pass # Also clear our pref value for last check date. We may have # just installed an update which is a pre-req for some other update. # Let's check again soon. munkicommon.set_pref('LastAppleSoftwareUpdateCheck', None) return restartneeded
percent = -1 munkicommon.display_percent_done(percent, 100) elif output.startswith('Software Update Tool'): # don't display this pass elif output.startswith('Copyright 2'): # don't display this pass elif output.startswith('Installing ') and mode == 'install': item = output[11:] if item: if munkicommon.munkistatusoutput: munkistatus.message(output) munkistatus.detail("") munkistatus.percent(-1) munkicommon.log(output) else: munkicommon.display_status(output) elif output.startswith('Installed '): # 10.6 / 10.7. Successful install of package name. if mode == 'install': munkicommon.display_status(output) results['installed'].append(output[10:]) else: pass # don't display. # softwareupdate logging "Installed" at the end of a # successful download-only session is odd. elif output.startswith('Done '): # 10.5. Successful install of package name. munkicommon.display_status(output)
def installAppleUpdates(): '''Uses /usr/sbin/softwareupdate to install previously downloaded updates. Returns True if a restart is needed after install, False otherwise.''' msg = "Installing available Apple Software Updates..." if munkicommon.munkistatusoutput: munkistatus.message(msg) munkistatus.detail("") munkistatus.percent(-1) munkicommon.log(msg) else: munkicommon.display_status(msg) restartneeded = restartNeeded() # use our filtered local catalog catalogpath = os.path.join(swupdCacheDir(), 'content/catalogs/local_install.sucatalog') if not os.path.exists(catalogpath): munkicommon.display_error( 'Missing local Software Update catalog at %s', catalogpath) # didn't do anything, so no restart needed return False installlist = getSoftwareUpdateInfo() installresults = {'installed':[], 'download':[]} catalogURL = 'file://localhost' + urllib2.quote(catalogpath) retcode = run_softwareupdate(['--CatalogURL', catalogURL, '-i', '-a'], mode='install', results=installresults) if not 'InstallResults' in munkicommon.report: munkicommon.report['InstallResults'] = [] for item in installlist: rep = {} rep['name'] = item.get('display_name') rep['version'] = item.get('version_to_install', '') rep['applesus'] = True rep['productKey'] = item.get('productKey', '') message = "Apple Software Update install of %s-%s: %s" if rep['name'] in installresults['installed']: rep['status'] = 0 install_status = 'SUCCESSFUL' elif rep['name'] in installresults['download']: rep['status'] = -1 install_status = 'FAILED due to missing package.' munkicommon.display_warning( 'Apple update %s, %s failed. A sub-package was missing ' 'on disk at time of install.' % (rep['name'], rep['productKey'])) else: rep['status'] = -2 install_status = 'FAILED for unknown reason' munkicommon.display_warning( 'Apple update %s, %s failed to install. No record of ' 'success or failure.' % (rep['name'],rep['productKey'])) munkicommon.report['InstallResults'].append(rep) log_msg = message % (rep['name'], rep['version'], install_status) munkicommon.log(log_msg, "Install.log") if retcode: # there was an error munkicommon.display_error("softwareupdate error: %s" % retcode) # clean up our now stale local cache cachedir = os.path.join(swupdCacheDir()) if os.path.exists(cachedir): unused_retcode = subprocess.call(['/bin/rm', '-rf', cachedir]) # remove the now invalid appleUpdatesFile try: os.unlink(appleUpdatesFile()) except OSError: pass # Also clear our pref value for last check date. We may have # just installed an update which is a pre-req for some other update. # Let's check again soon. munkicommon.set_pref('LastAppleSoftwareUpdateCheck', None) return restartneeded