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 getpathstoremove(pkgkeylist): """ Queries our database for paths to remove. """ pkgkeys = tuple(pkgkeylist) # open connection and cursor to our database conn = sqlite3.connect(packagedb) curs = conn.cursor() # set up some subqueries: # all the paths that are referred to by the selected packages: if len(pkgkeys) > 1: in_selected_packages = \ "select distinct path_key from pkgs_paths where pkg_key in %s" % \ str(pkgkeys) else: in_selected_packages = \ "select distinct path_key from pkgs_paths where pkg_key = %s" % \ str(pkgkeys[0]) # all the paths that are referred to by every package # except the selected packages: if len(pkgkeys) > 1: not_in_other_packages = \ "select distinct path_key from pkgs_paths where pkg_key not in %s" % \ str(pkgkeys) else: not_in_other_packages = \ "select distinct path_key from pkgs_paths where pkg_key != %s" % \ str(pkgkeys[0]) # every path that is used by the selected packages and no other packages: combined_query = \ "select path from paths where " + \ "(path_key in (%s) and path_key not in (%s))" % \ (in_selected_packages, not_in_other_packages) munkicommon.display_status('Determining which filesystem items to remove') if munkicommon.munkistatusoutput: munkistatus.percent(-1) curs.execute(combined_query) results = curs.fetchall() curs.close() conn.close() removalpaths = [] for item in results: removalpaths.append(item[0]) return removalpaths
def removepackages(pkgnames, forcedeletebundles=False, listfiles=False, rebuildpkgdb=False, noremovereceipts=False, noupdateapplepkgdb=False): """ Our main function, called by installer.py to remove items based on receipt info. """ if pkgnames == []: munkicommon.display_error( "You must specify at least one package to remove!") return -2 if not initDatabase(forcerebuild=rebuildpkgdb): munkicommon.display_error("Could not initialize receipt database.") return -3 pkgkeyslist = getpkgkeys(pkgnames) if len(pkgkeyslist) == 0: return -4 if munkicommon.stopRequested(): return -128 removalpaths = getpathstoremove(pkgkeyslist) if munkicommon.stopRequested(): return -128 if removalpaths: if listfiles: removalpaths.sort() for item in removalpaths: print "/" + item.encode('UTF-8') else: if munkicommon.munkistatusoutput: munkistatus.disableStopButton() removeFilesystemItems(removalpaths, forcedeletebundles) else: munkicommon.display_status('Nothing to remove.') if munkicommon.munkistatusoutput: time.sleep(2) if not listfiles: if not noremovereceipts: removeReceipts(pkgkeyslist, noupdateapplepkgdb) if munkicommon.munkistatusoutput: munkistatus.enableStopButton() munkicommon.display_status('Package removal complete.') time.sleep(2) return 0
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 cacheSwupdMetadata(): '''Copies ServerMetadata (.smd), Metadata (.pkm), and Distribution (.dist) files for the available updates to the local machine and writes a new sucatalog that refers to the local copies of these files.''' filtered_catalogpath = os.path.join( swupdCacheDir(), 'content/catalogs/filtered_index.sucatalog') catalog = FoundationPlist.readPlist(filtered_catalogpath) if 'Products' in catalog: product_keys = list(catalog['Products'].keys()) for product_key in product_keys: munkicommon.display_status('Caching metadata for product ID %s', product_key) product = catalog['Products'][product_key] if 'ServerMetadataURL' in product: unused_path = replicateURLtoFilesystem( product['ServerMetadataURL'], copy_only_if_missing=True) for package in product.get('Packages', []): ### not replicating the packages themselves ### #if 'URL' in package: # unused_path = replicateURLtoFilesystem( # package['URL'], # copy_only_if_missing=fast_scan) if 'MetadataURL' in package: munkicommon.display_status( 'Caching package metadata for product ID %s', product_key) unused_path = replicateURLtoFilesystem( package['MetadataURL'], copy_only_if_missing=True) distributions = product['Distributions'] for dist_lang in distributions.keys(): munkicommon.display_status( 'Caching %s distribution for product ID %s', dist_lang, product_key) dist_url = distributions[dist_lang] unused_path = replicateURLtoFilesystem( dist_url, copy_only_if_missing=True) # rewrite URLs to point to local resources rewriteURLs(catalog, rewrite_pkg_urls=False) # write out the rewritten catalog localcatalogpath = os.path.join(swupdCacheDir(), 'content', 'catalogs') if not os.path.exists(localcatalogpath): try: os.makedirs(localcatalogpath) except OSError, oserr: raise ReplicationError(oserr) localcatalogpathname = os.path.join(localcatalogpath, 'local_download.sucatalog') FoundationPlist.writePlist(catalog, localcatalogpathname) rewriteURLs(catalog, rewrite_pkg_urls=True) localcatalogpathname = os.path.join(localcatalogpath, 'local_install.sucatalog') FoundationPlist.writePlist(catalog, localcatalogpathname)
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) results['installed'].append(output[5:]) elif output.startswith('Downloading ') and mode == 'install':
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
def cacheSwupdMetadata(): '''Copies ServerMetadata (.smd), Metadata (.pkm), and Distribution (.dist) files for the available updates to the local machine and writes a new sucatalog that refers to the local copies of these files.''' filtered_catalogpath = os.path.join(swupdCacheDir(), 'content/catalogs/filtered_index.sucatalog') catalog = FoundationPlist.readPlist(filtered_catalogpath) if 'Products' in catalog: product_keys = list(catalog['Products'].keys()) for product_key in product_keys: munkicommon.display_status( 'Caching metadata for product ID %s', product_key) product = catalog['Products'][product_key] if 'ServerMetadataURL' in product: unused_path = replicateURLtoFilesystem( product['ServerMetadataURL'], copy_only_if_missing=True) for package in product.get('Packages', []): ### not replicating the packages themselves ### #if 'URL' in package: # unused_path = replicateURLtoFilesystem( # package['URL'], # copy_only_if_missing=fast_scan) if 'MetadataURL' in package: munkicommon.display_status( 'Caching package metadata for product ID %s', product_key) unused_path = replicateURLtoFilesystem( package['MetadataURL'], copy_only_if_missing=True) distributions = product['Distributions'] for dist_lang in distributions.keys(): munkicommon.display_status( 'Caching %s distribution for product ID %s', dist_lang, product_key) dist_url = distributions[dist_lang] unused_path = replicateURLtoFilesystem( dist_url, copy_only_if_missing=True) # rewrite URLs to point to local resources rewriteURLs(catalog, rewrite_pkg_urls=False) # write out the rewritten catalog localcatalogpath = os.path.join(swupdCacheDir(), 'content', 'catalogs') if not os.path.exists(localcatalogpath): try: os.makedirs(localcatalogpath) except OSError, oserr: raise ReplicationError(oserr) localcatalogpathname = os.path.join(localcatalogpath, 'local_download.sucatalog') FoundationPlist.writePlist(catalog, localcatalogpathname) rewriteURLs(catalog, rewrite_pkg_urls=True) localcatalogpathname = os.path.join(localcatalogpath, 'local_install.sucatalog') FoundationPlist.writePlist(catalog, localcatalogpathname)
def removeReceipts(pkgkeylist, noupdateapplepkgdb): """ Removes receipt data from /Library/Receipts, /Library/Receipts/boms, our internal package database, and optionally Apple's package database. """ munkicommon.display_status('Removing receipt info') local_display_percent_done(0, 4) conn = sqlite3.connect(packagedb) curs = conn.cursor() osvers = int(os.uname()[2].split('.')[0]) applepkgdb = '/Library/Receipts/db/a.receiptdb' if not noupdateapplepkgdb and osvers < 10: aconn = sqlite3.connect(applepkgdb) acurs = aconn.cursor() local_display_percent_done(1, 4) for pkgkey in pkgkeylist: pkgid = '' pkgkey_t = (pkgkey, ) row = curs.execute( 'SELECT pkgname, pkgid from pkgs where pkg_key = ?', pkgkey_t).fetchone() if row: pkgname = row[0] pkgid = row[1] receiptpath = None if osvers < 10: if pkgname.endswith('.pkg'): receiptpath = os.path.join('/Library/Receipts', pkgname) if pkgname.endswith('.bom'): receiptpath = os.path.join('/Library/Receipts/boms', pkgname) else: # clean up /Library/Receipts in case there's stuff left there receiptpath = findBundleReceiptFromID(pkgid) if receiptpath and os.path.exists(receiptpath): munkicommon.display_detail("Removing %s..." % receiptpath) unused_retcode = subprocess.call( ["/bin/rm", "-rf", receiptpath]) # remove pkg info from our database munkicommon.display_detail( "Removing package data from internal database...") curs.execute('DELETE FROM pkgs_paths where pkg_key = ?', pkgkey_t) curs.execute('DELETE FROM pkgs where pkg_key = ?', pkgkey_t) # then remove pkg info from Apple's database unless option is passed if not noupdateapplepkgdb and pkgid: if osvers < 10: # Leopard pkgid_t = (pkgid, ) row = acurs.execute( 'SELECT pkg_key FROM pkgs where pkgid = ?', pkgid_t).fetchone() if row: munkicommon.display_detail( "Removing package data from Apple package "+ "database...") apple_pkg_key = row[0] pkgkey_t = (apple_pkg_key, ) acurs.execute( 'DELETE FROM pkgs where pkg_key = ?', pkgkey_t) acurs.execute( 'DELETE FROM pkgs_paths where pkg_key = ?', pkgkey_t) acurs.execute( 'DELETE FROM pkgs_groups where pkg_key = ?', pkgkey_t) acurs.execute( 'DELETE FROM acls where pkg_key = ?', pkgkey_t) acurs.execute( 'DELETE FROM taints where pkg_key = ?', pkgkey_t) acurs.execute( 'DELETE FROM sha1s where pkg_key = ?', pkgkey_t) acurs.execute( 'DELETE FROM oldpkgs where pkg_key = ?', pkgkey_t) else: # Snow Leopard or higher, must use pkgutil cmd = ['/usr/sbin/pkgutil', '--forget', pkgid] proc = subprocess.Popen(cmd, bufsize=1, stdout=subprocess.PIPE, stderr=subprocess.PIPE) (output, unused_err) = proc.communicate() if output: munkicommon.display_detail( str(output).decode('UTF-8').rstrip('\n')) local_display_percent_done(2, 4) # now remove orphaned paths from paths table # first, Apple's database if option is passed if not noupdateapplepkgdb: if osvers < 10: munkicommon.display_detail( "Removing unused paths from Apple package database...") acurs.execute( '''DELETE FROM paths where path_key not in (select distinct path_key from pkgs_paths)''') aconn.commit() acurs.close() aconn.close() local_display_percent_done(3, 4) # we do our database last so its modtime is later than the modtime for the # Apple DB... munkicommon.display_detail("Removing unused paths from internal package " "database...") curs.execute( '''DELETE FROM paths where path_key not in (select distinct path_key from pkgs_paths)''') conn.commit() curs.close() conn.close() local_display_percent_done(4, 4)
def initDatabase(forcerebuild=False): """ Builds or rebuilds our internal package database. """ if not shouldRebuildDB(packagedb) and not forcerebuild: return True munkicommon.display_status('Gathering information on installed packages') if os.path.exists(packagedb): try: os.remove(packagedb) except (OSError, IOError): munkicommon.display_error( "Could not remove out-of-date receipt database.") return False osvers = int(os.uname()[2].split('.')[0]) pkgcount = 0 receiptsdir = "/Library/Receipts" bomsdir = "/Library/Receipts/boms" if os.path.exists(receiptsdir): receiptlist = munkicommon.listdir(receiptsdir) for item in receiptlist: if item.endswith(".pkg"): pkgcount += 1 if os.path.exists(bomsdir): bomslist = munkicommon.listdir(bomsdir) for item in bomslist: if item.endswith(".bom"): pkgcount += 1 if osvers > 9: # Snow Leopard or later pkglist = [] cmd = ['/usr/sbin/pkgutil', '--pkgs'] proc = subprocess.Popen(cmd, shell=False, bufsize=1, stdin=subprocess.PIPE, stdout=subprocess.PIPE, stderr=subprocess.PIPE) while True: line = proc.stdout.readline() if not line and (proc.poll() != None): break pkglist.append(line.rstrip('\n')) pkgcount += 1 conn = sqlite3.connect(packagedb) conn.text_factory = str curs = conn.cursor() CreateTables(curs) currentpkgindex = 0 local_display_percent_done(0, pkgcount) if os.path.exists(receiptsdir): receiptlist = munkicommon.listdir(receiptsdir) for item in receiptlist: if munkicommon.stopRequested(): curs.close() conn.close() #our package db isn't valid, so we should delete it os.remove(packagedb) return False if item.endswith(".pkg"): receiptpath = os.path.join(receiptsdir, item) munkicommon.display_detail("Importing %s..." % receiptpath) ImportPackage(receiptpath, curs) currentpkgindex += 1 local_display_percent_done(currentpkgindex, pkgcount) if os.path.exists(bomsdir): bomslist = munkicommon.listdir(bomsdir) for item in bomslist: if munkicommon.stopRequested(): curs.close() conn.close() #our package db isn't valid, so we should delete it os.remove(packagedb) return False if item.endswith(".bom"): bompath = os.path.join(bomsdir, item) munkicommon.display_detail("Importing %s..." % bompath) ImportBom(bompath, curs) currentpkgindex += 1 local_display_percent_done(currentpkgindex, pkgcount) if osvers > 9: # Snow Leopard or later for pkg in pkglist: if munkicommon.stopRequested(): curs.close() conn.close() #our package db isn't valid, so we should delete it os.remove(packagedb) return False munkicommon.display_detail("Importing %s..." % pkg) ImportFromPkgutil(pkg, curs) currentpkgindex += 1 local_display_percent_done(currentpkgindex, pkgcount) # in case we didn't quite get to 100% for some reason if currentpkgindex < pkgcount: local_display_percent_done(pkgcount, pkgcount) # commit and close the db when we're done. conn.commit() curs.close() conn.close() return True
def removeFilesystemItems(removalpaths, forcedeletebundles): """ Attempts to remove all the paths in the array removalpaths """ # we sort in reverse because we can delete from the bottom up, # clearing a directory before we try to remove the directory itself removalpaths.sort(reverse=True) removalerrors = "" removalcount = len(removalpaths) munkicommon.display_status("Removing %s filesystem items" % removalcount) itemcount = len(removalpaths) itemindex = 0 local_display_percent_done(itemindex, itemcount) for item in removalpaths: itemindex += 1 pathtoremove = "/" + item # use os.path.lexists so broken links return true # so we can remove them if os.path.lexists(pathtoremove): munkicommon.display_detail("Removing: " + pathtoremove) if (os.path.isdir(pathtoremove) and \ not os.path.islink(pathtoremove)): diritems = munkicommon.listdir(pathtoremove) if diritems == ['.DS_Store']: # If there's only a .DS_Store file # we'll consider it empty ds_storepath = pathtoremove + "/.DS_Store" try: os.remove(ds_storepath) except (OSError, IOError): pass diritems = munkicommon.listdir(pathtoremove) if diritems == []: # directory is empty try: os.rmdir(pathtoremove) except (OSError, IOError), err: msg = "Couldn't remove directory %s - %s" % \ (pathtoremove, err) munkicommon.display_error(msg) removalerrors = removalerrors + "\n" + msg else: # the directory is marked for deletion but isn't empty. # if so directed, if it's a bundle (like .app), we should # remove it anyway - no use having a broken bundle hanging # around if (forcedeletebundles and isBundle(pathtoremove)): munkicommon.display_warning( "Removing non-empty bundle: %s" % pathtoremove) retcode = subprocess.call( ['/bin/rm', '-r', pathtoremove]) if retcode: msg = "Couldn't remove bundle %s" % pathtoremove munkicommon.display_error(msg) removalerrors = removalerrors + "\n" + msg else: # if this path is inside a bundle, and we've been # directed to force remove bundles, # we don't need to warn because it's going to be # removed with the bundle. # Otherwise, we should warn about non-empty # directories. if not insideBundle(pathtoremove) or \ not forcedeletebundles: msg = \ "Did not remove %s because it is not empty." % \ pathtoremove munkicommon.display_error(msg) removalerrors = removalerrors + "\n" + msg else: # not a directory, just unlink it # I was using rm instead of Python because I don't trust # handling of resource forks with Python #retcode = subprocess.call(['/bin/rm', pathtoremove]) # but man that's slow. # I think there's a lot of overhead with the # subprocess call. I'm going to use os.remove. # I hope I don't regret it. retcode = '' try: os.remove(pathtoremove) except (OSError, IOError), err: msg = "Couldn't remove item %s: %s" % (pathtoremove, err) munkicommon.display_error(msg) removalerrors = removalerrors + "\n" + msg
def removeReceipts(pkgkeylist, noupdateapplepkgdb): """ Removes receipt data from /Library/Receipts, /Library/Receipts/boms, our internal package database, and optionally Apple's package database. """ munkicommon.display_status('Removing receipt info') local_display_percent_done(0, 4) conn = sqlite3.connect(packagedb) curs = conn.cursor() osvers = int(os.uname()[2].split('.')[0]) applepkgdb = '/Library/Receipts/db/a.receiptdb' if not noupdateapplepkgdb and osvers < 10: aconn = sqlite3.connect(applepkgdb) acurs = aconn.cursor() local_display_percent_done(1, 4) for pkgkey in pkgkeylist: pkgid = '' pkgkey_t = (pkgkey, ) row = curs.execute('SELECT pkgname, pkgid from pkgs where pkg_key = ?', pkgkey_t).fetchone() if row: pkgname = row[0] pkgid = row[1] receiptpath = None if osvers < 10: if pkgname.endswith('.pkg'): receiptpath = os.path.join('/Library/Receipts', pkgname) if pkgname.endswith('.bom'): receiptpath = os.path.join('/Library/Receipts/boms', pkgname) else: # clean up /Library/Receipts in case there's stuff left there receiptpath = findBundleReceiptFromID(pkgid) if receiptpath and os.path.exists(receiptpath): munkicommon.display_detail("Removing %s..." % receiptpath) unused_retcode = subprocess.call( ["/bin/rm", "-rf", receiptpath]) # remove pkg info from our database munkicommon.display_detail( "Removing package data from internal database...") curs.execute('DELETE FROM pkgs_paths where pkg_key = ?', pkgkey_t) curs.execute('DELETE FROM pkgs where pkg_key = ?', pkgkey_t) # then remove pkg info from Apple's database unless option is passed if not noupdateapplepkgdb and pkgid: if osvers < 10: # Leopard pkgid_t = (pkgid, ) row = acurs.execute('SELECT pkg_key FROM pkgs where pkgid = ?', pkgid_t).fetchone() if row: munkicommon.display_detail( "Removing package data from Apple package " + "database...") apple_pkg_key = row[0] pkgkey_t = (apple_pkg_key, ) acurs.execute('DELETE FROM pkgs where pkg_key = ?', pkgkey_t) acurs.execute('DELETE FROM pkgs_paths where pkg_key = ?', pkgkey_t) acurs.execute('DELETE FROM pkgs_groups where pkg_key = ?', pkgkey_t) acurs.execute('DELETE FROM acls where pkg_key = ?', pkgkey_t) acurs.execute('DELETE FROM taints where pkg_key = ?', pkgkey_t) acurs.execute('DELETE FROM sha1s where pkg_key = ?', pkgkey_t) acurs.execute('DELETE FROM oldpkgs where pkg_key = ?', pkgkey_t) else: # Snow Leopard or higher, must use pkgutil cmd = ['/usr/sbin/pkgutil', '--forget', pkgid] proc = subprocess.Popen(cmd, bufsize=1, stdout=subprocess.PIPE, stderr=subprocess.PIPE) (output, unused_err) = proc.communicate() if output: munkicommon.display_detail( str(output).decode('UTF-8').rstrip('\n')) local_display_percent_done(2, 4) # now remove orphaned paths from paths table # first, Apple's database if option is passed if not noupdateapplepkgdb: if osvers < 10: munkicommon.display_detail( "Removing unused paths from Apple package database...") acurs.execute('''DELETE FROM paths where path_key not in (select distinct path_key from pkgs_paths)''') aconn.commit() acurs.close() aconn.close() local_display_percent_done(3, 4) # we do our database last so its modtime is later than the modtime for the # Apple DB... munkicommon.display_detail("Removing unused paths from internal package " "database...") curs.execute('''DELETE FROM paths where path_key not in (select distinct path_key from pkgs_paths)''') conn.commit() curs.close() conn.close() local_display_percent_done(4, 4)
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
def removeFilesystemItems(removalpaths, forcedeletebundles): """ Attempts to remove all the paths in the array removalpaths """ # we sort in reverse because we can delete from the bottom up, # clearing a directory before we try to remove the directory itself removalpaths.sort(reverse=True) removalerrors = "" removalcount = len(removalpaths) munkicommon.display_status("Removing %s filesystem items" % removalcount) itemcount = len(removalpaths) itemindex = 0 local_display_percent_done(itemindex, itemcount) for item in removalpaths: itemindex += 1 pathtoremove = "/" + item # use os.path.lexists so broken links return true # so we can remove them if os.path.lexists(pathtoremove): munkicommon.display_detail("Removing: " + pathtoremove) if (os.path.isdir(pathtoremove) and \ not os.path.islink(pathtoremove)): diritems = munkicommon.listdir(pathtoremove) if diritems == ['.DS_Store']: # If there's only a .DS_Store file # we'll consider it empty ds_storepath = pathtoremove + "/.DS_Store" try: os.remove(ds_storepath) except (OSError, IOError): pass diritems = munkicommon.listdir(pathtoremove) if diritems == []: # directory is empty try: os.rmdir(pathtoremove) except (OSError, IOError), err: msg = "Couldn't remove directory %s - %s" % \ (pathtoremove, err) munkicommon.display_error(msg) removalerrors = removalerrors + "\n" + msg else: # the directory is marked for deletion but isn't empty. # if so directed, if it's a bundle (like .app), we should # remove it anyway - no use having a broken bundle hanging # around if (forcedeletebundles and isBundle(pathtoremove)): munkicommon.display_warning( "Removing non-empty bundle: %s" % pathtoremove) retcode = subprocess.call(['/bin/rm', '-r', pathtoremove]) if retcode: msg = "Couldn't remove bundle %s" % pathtoremove munkicommon.display_error(msg) removalerrors = removalerrors + "\n" + msg else: # if this path is inside a bundle, and we've been # directed to force remove bundles, # we don't need to warn because it's going to be # removed with the bundle. # Otherwise, we should warn about non-empty # directories. if not insideBundle(pathtoremove) or \ not forcedeletebundles: msg = \ "Did not remove %s because it is not empty." % \ pathtoremove munkicommon.display_error(msg) removalerrors = removalerrors + "\n" + msg else: # not a directory, just unlink it # I was using rm instead of Python because I don't trust # handling of resource forks with Python #retcode = subprocess.call(['/bin/rm', pathtoremove]) # but man that's slow. # I think there's a lot of overhead with the # subprocess call. I'm going to use os.remove. # I hope I don't regret it. retcode = '' try: os.remove(pathtoremove) except (OSError, IOError), err: msg = "Couldn't remove item %s: %s" % (pathtoremove, err) munkicommon.display_error(msg) removalerrors = removalerrors + "\n" + msg