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_minor( '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_minor("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_minor("Package removal complete.") time.sleep(2) return 0
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_minor('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_minor('Package removal complete.') time.sleep(2) return 0
def runAdobeSetup(dmgpath, uninstalling=False): '''Runs the Adobe setup tool in silent mode from an Adobe update DMG or an Adobe CS3 install DMG''' munkicommon.display_status_minor( 'Mounting disk image %s' % os.path.basename(dmgpath)) mountpoints = mountAdobeDmg(dmgpath) if mountpoints: setup_path = findSetupApp(mountpoints[0]) if setup_path: # look for install.xml or uninstall.xml at root deploymentfile = None installxml = os.path.join(mountpoints[0], "install.xml") uninstallxml = os.path.join(mountpoints[0], "uninstall.xml") if uninstalling: if os.path.exists(uninstallxml): deploymentfile = uninstallxml else: # we've been asked to uninstall, # but found no uninstall.xml # so we need to bail munkicommon.unmountdmg(mountpoints[0]) munkicommon.display_error( "%s doesn't appear to contain uninstall info." % os.path.basename(dmgpath)) return -1 else: if os.path.exists(installxml): deploymentfile = installxml # try to find and count the number of payloads # so we can give a rough progress indicator number_of_payloads = countPayloads(mountpoints[0]) munkicommon.display_status_minor('Running Adobe Setup') adobe_setup = [ setup_path, '--mode=silent', '--skipProcessCheck=1' ] if deploymentfile: adobe_setup.append('--deploymentFile=%s' % deploymentfile) retcode = runAdobeInstallTool(adobe_setup, number_of_payloads) else: munkicommon.display_error( '%s doesn\'t appear to contain Adobe Setup.' % os.path.basename(dmgpath)) retcode = -1 munkicommon.unmountdmg(mountpoints[0]) return retcode else: munkicommon.display_error('No mountable filesystems on %s' % dmgpath) return -1
def runAdobeCS5PatchInstaller(dmgpath, copylocal=False): '''Runs the AdobePatchInstaller for CS5. Optionally can copy the DMG contents to the local disk to work around issues with the patcher.''' munkicommon.display_status_minor( 'Mounting disk image %s' % os.path.basename(dmgpath)) mountpoints = mountAdobeDmg(dmgpath) if mountpoints: if copylocal: # copy the update to the local disk before installing updatedir = tempfile.mkdtemp() retcode = subprocess.call(["/bin/cp", "-r", mountpoints[0], updatedir]) # unmount diskimage munkicommon.unmountdmg(mountpoints[0]) if retcode: munkicommon.display_error( "Error copying items from %s" % dmgpath) return -1 # remove the dmg file to free up space, since we don't need it # any longer unused_result = subprocess.call(["/bin/rm", dmgpath]) else: updatedir = mountpoints[0] patchinstaller = findAdobePatchInstallerApp(updatedir) if patchinstaller: # try to find and count the number of payloads # so we can give a rough progress indicator number_of_payloads = countPayloads(updatedir) munkicommon.display_status_minor('Running Adobe Patch Installer') install_cmd = [ patchinstaller, '--mode=silent', '--skipProcessCheck=1' ] retcode = runAdobeInstallTool(install_cmd, number_of_payloads) else: munkicommon.display_error( "%s doesn't appear to contain AdobePatchInstaller.app." % os.path.basename(dmgpath)) retcode = -1 if copylocal: # clean up our mess unused_result = subprocess.call(["/bin/rm", "-rf", updatedir]) else: munkicommon.unmountdmg(mountpoints[0]) return retcode else: munkicommon.display_error("No mountable filesystems on %s" % dmgpath) return -1
def runAdobeUberTool(dmgpath, pkgname='', uninstalling=False): '''Runs either AdobeUberInstaller or AdobeUberUninstaller from a disk image and provides progress feedback. pkgname is the name of a directory at the top level of the dmg containing the AdobeUber tools and their XML files.''' munkicommon.display_status_minor( 'Mounting disk image %s' % os.path.basename(dmgpath)) mountpoints = mountAdobeDmg(dmgpath) if mountpoints: installroot = mountpoints[0] if uninstalling: ubertool = os.path.join(installroot, pkgname, "AdobeUberUninstaller") else: ubertool = os.path.join(installroot, pkgname, "AdobeUberInstaller") if os.path.exists(ubertool): info = getAdobePackageInfo(installroot) packagename = info['display_name'] action = "Installing" if uninstalling: action = "Uninstalling" munkicommon.display_status_major('%s %s' % (action, packagename)) if munkicommon.munkistatusoutput: munkistatus.detail('Starting %s' % os.path.basename(ubertool)) # try to find and count the number of payloads # so we can give a rough progress indicator number_of_payloads = countPayloads(installroot) retcode = runAdobeInstallTool( [ubertool], number_of_payloads, killAdobeAIR=True) else: munkicommon.display_error("No %s found" % ubertool) retcode = -1 munkicommon.unmountdmg(installroot) return retcode else: munkicommon.display_error("No mountable filesystems on %s" % dmgpath) return -1
def doAdobeCS5Uninstall(adobeInstallInfo): '''Runs the locally-installed Adobe CS5 tools to remove CS5 products. We need the uninstallxml and the CS5 Setup.app.''' uninstallxml = adobeInstallInfo.get('uninstallxml') if not uninstallxml: munkicommon.display_error("No uninstall.xml in adobe_install_info") return -1 payloadcount = adobeInstallInfo.get('payload_count', 0) path = os.path.join(munkicommon.tmpdir, "uninstall.xml") deploymentFile = writefile(uninstallxml, path) if not deploymentFile: return -1 setupapp = "/Library/Application Support/Adobe/OOBE/PDApp/DWA/Setup.app" setup = os.path.join(setupapp, "Contents/MacOS/Setup") if not os.path.exists(setup): munkicommon.display_error("%s is not installed." % setupapp) return -1 uninstall_cmd = [ setup, '--mode=silent', '--action=uninstall', '--skipProcessCheck=1', '--deploymentFile=%s' % deploymentFile ] munkicommon.display_status_minor('Running Adobe Uninstall') return runAdobeInstallTool(uninstall_cmd, payloadcount)
def initDatabase(forcerebuild=False): """ Builds or rebuilds our internal package database. """ if not shouldRebuildDB(packagedb) and not forcerebuild: return True munkicommon.display_status_minor( '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 os_version = munkicommon.getOsVersion(as_tuple=True) 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 os_version >= (10, 6): # 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 munkicommon.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 munkicommon.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 munkicommon.display_percent_done(currentpkgindex, pkgcount) if os_version >= (10, 6): # 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 munkicommon.display_percent_done(currentpkgindex, pkgcount) # in case we didn't quite get to 100% for some reason if currentpkgindex < pkgcount: munkicommon.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_minor( 'Removing %s filesystem items' % removalcount) itemcount = len(removalpaths) itemindex = 0 munkicommon.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_minor('Removing receipt info') munkicommon.display_percent_done(0, 4) conn = sqlite3.connect(packagedb) curs = conn.cursor() os_version = munkicommon.getOsVersion(as_tuple=True) applepkgdb = '/Library/Receipts/db/a.receiptdb' if not noupdateapplepkgdb and os_version <= (10, 5): aconn = sqlite3.connect(applepkgdb) acurs = aconn.cursor() munkicommon.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 os_version <= (10, 5): 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 os_version <= (10, 5): # 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')) munkicommon.display_percent_done(2, 4) # now remove orphaned paths from paths table # first, Apple's database if option is passed if not noupdateapplepkgdb: if os_version <= (10, 5): 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() munkicommon.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() munkicommon.display_percent_done(4, 4)
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_minor('Removing %s filesystem items' % removalcount) itemcount = len(removalpaths) itemindex = 0 munkicommon.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 runAdobeInstallTool(cmd, number_of_payloads=0, killAdobeAIR=False): '''An abstraction of the tasks for running Adobe Setup, AdobeUberInstaller, AdobeUberUninstaller, AdobeDeploymentManager, etc''' if munkicommon.munkistatusoutput and not number_of_payloads: # indeterminate progress bar munkistatus.percent(-1) proc = subprocess.Popen(cmd, shell=False, bufsize=1, stdin=subprocess.PIPE, stdout=subprocess.PIPE, stderr=subprocess.STDOUT) old_payload_completed_count = 0 payloadname = "" while (proc.poll() == None): time.sleep(1) (payload_completed_count, payloadname) = \ getAdobeInstallProgressInfo(old_payload_completed_count, payloadname) if payload_completed_count > old_payload_completed_count: old_payload_completed_count = payload_completed_count if payloadname: payloadinfo = " - " + payloadname else: payloadinfo = "" if number_of_payloads: munkicommon.display_status_minor( 'Completed payload %s of %s%s' % (payload_completed_count, number_of_payloads, payloadinfo)) else: munkicommon.display_status_minor('Completed payload %s%s' % (payload_completed_count, payloadinfo)) if munkicommon.munkistatusoutput: munkistatus.percent(getPercent(payload_completed_count, number_of_payloads)) # Adobe AIR Installer workaround/hack # CSx installs at the loginwindow hang when Adobe AIR is installed. # So we check for this and kill the process. Ugly. # Hopefully we can disable this in the future. if killAdobeAIR: if (not munkicommon.getconsoleuser() or munkicommon.getconsoleuser() == u"loginwindow"): # we're at the loginwindow. killStupidProcesses() # run of tool completed retcode = proc.poll() #check output for errors output = proc.stdout.readlines() for line in output: line = line.rstrip("\n") if line.startswith("Error"): munkicommon.display_error(line) if line.startswith("Exit Code:"): if retcode == 0: try: retcode = int(line[11:]) except (ValueError, TypeError): retcode = -1 if retcode != 0 and retcode != 8: munkicommon.display_error("Adobe Setup error: %s: %s" % (retcode, adobeSetupError(retcode))) else: if munkicommon.munkistatusoutput: munkistatus.percent(100) munkicommon.display_status_minor('Done.') return retcode
# partial content because we're resuming munkicommon.display_detail( 'Resuming partial download for %s' % os.path.basename(destinationpath)) contentrange = header.get('content-range') if contentrange.startswith('bytes'): try: targetsize = int(contentrange.split('/')[1]) except (ValueError, TypeError): targetsize = 0 if message and header.get('http_result_code') != '304': if message: # log always, display if verbose is 1 or more # also display in MunkiStatus detail field munkicommon.display_status_minor(message) elif targetsize and header.get('http_result_code').startswith('2'): # display progress if we get a 2xx result code if os.path.exists(tempdownloadpath): downloadedsize = os.path.getsize(tempdownloadpath) percent = int(float(downloadedsize) /float(targetsize)*100) if percent != downloadedpercent: # percent changed; update display downloadedpercent = percent munkicommon.display_percent_done(downloadedpercent, 100) time.sleep(0.1) else: # Headers have finished, but not targetsize or HTTP2xx. # It's possible that Content-Length was not in the headers.
def get_url(url, destinationpath, custom_headers=None, message=None, onlyifnewer=False, resume=False, follow_redirects=False): """Gets an HTTP or HTTPS URL and stores it in destination path. Returns a dictionary of headers, which includes http_result_code and http_result_description. Will raise CurlError if Gurl returns an error. Will raise HTTPError if HTTP Result code is not 2xx or 304. If destinationpath already exists, you can set 'onlyifnewer' to true to indicate you only want to download the file only if it's newer on the server. If you set resume to True, Gurl will attempt to resume an interrupted download.""" tempdownloadpath = destinationpath + '.download' if os.path.exists(tempdownloadpath) and not resume: if resume and not os.path.exists(destinationpath): os.remove(tempdownloadpath) cache_data = None if onlyifnewer and os.path.exists(destinationpath): # create a temporary Gurl object so we can extract the # stored caching data so we can download only if the # file has changed on the server gurl_obj = Gurl.alloc().initWithOptions_({'file': destinationpath}) cache_data = gurl_obj.get_stored_headers() del gurl_obj options = {'url': url, 'file': tempdownloadpath, 'follow_redirects': follow_redirects, 'can_resume': resume, 'additional_headers': header_dict_from_list(custom_headers), 'download_only_if_changed': onlyifnewer, 'cache_data': cache_data, 'logging_function': munkicommon.display_debug2} munkicommon.display_debug2('Options: %s' % options) connection = Gurl.alloc().initWithOptions_(options) stored_percent_complete = -1 stored_bytes_received = 0 connection.start() try: while True: # if we did `while not connection.isDone()` we'd miss printing # messages and displaying percentages if we exit the loop first connection_done = connection.isDone() if message and connection.status and connection.status != 304: # log always, display if verbose is 1 or more # also display in MunkiStatus detail field munkicommon.display_status_minor(message) # now clear message so we don't display it again message = None if (str(connection.status).startswith('2') and connection.percentComplete != -1): if connection.percentComplete != stored_percent_complete: # display percent done if it has changed stored_percent_complete = connection.percentComplete munkicommon.display_percent_done( stored_percent_complete, 100) elif connection.bytesReceived != stored_bytes_received: # if we don't have percent done info, log bytes received stored_bytes_received = connection.bytesReceived munkicommon.display_detail( 'Bytes received: %s', stored_bytes_received) if connection_done: break except (KeyboardInterrupt, SystemExit): # safely kill the connection then re-raise connection.cancel() raise except Exception, err: # too general, I know # Let us out! ... Safely! Unexpectedly quit dialogs are annoying... connection.cancel() # Re-raise the error as a GurlError raise GurlError(-1, str(err))
def updateAcrobatPro(dmgpath): """Uses the scripts and Resources inside the Acrobat Patch application bundle to silently update Acrobat Pro and related apps Why oh why does this use a different mechanism than the other Adobe apps?""" if munkicommon.munkistatusoutput: munkistatus.percent(-1) #first mount the dmg munkicommon.display_status_minor( 'Mounting disk image %s' % os.path.basename(dmgpath)) mountpoints = mountAdobeDmg(dmgpath) if mountpoints: installroot = mountpoints[0] pathToAcrobatPatchApp = findAcrobatPatchApp(installroot) else: munkicommon.display_error("No mountable filesystems on %s" % dmgpath) return -1 if not pathToAcrobatPatchApp: munkicommon.display_error("No Acrobat Patch app at %s" % pathToAcrobatPatchApp) munkicommon.unmountdmg(installroot) return -1 # some values needed by the patching script resourcesDir = os.path.join(pathToAcrobatPatchApp, "Contents", "Resources") ApplyOperation = os.path.join(resourcesDir, "ApplyOperation.py") callingScriptPath = os.path.join(resourcesDir, "InstallUpdates.sh") appList = [] appListFile = os.path.join(resourcesDir, "app_list.txt") if os.path.exists(appListFile): fileobj = open(appListFile, mode='r', buffering=1) if fileobj: for line in fileobj.readlines(): appList.append(line) fileobj.close() if not appList: munkicommon.display_error("Did not find a list of apps to update.") munkicommon.unmountdmg(installroot) return -1 payloadNum = -1 for line in appList: payloadNum = payloadNum + 1 if munkicommon.munkistatusoutput: munkistatus.percent(getPercent(payloadNum + 1, len(appList) + 1)) (appname, status) = line.split("\t") munkicommon.display_status_minor('Searching for %s' % appname) # first look in the obvious place pathname = os.path.join("/Applications/Adobe Acrobat 9 Pro", appname) if os.path.exists(pathname): item = {} item['path'] = pathname candidates = [item] else: # use system_profiler to search for the app candidates = [item for item in munkicommon.getAppData() if item['path'].endswith('/' + appname)] # hope there's only one! if len(candidates) == 0: if status == "optional": continue else: munkicommon.display_error("Cannot patch %s because it " "was not found on the startup " "disk." % appname) munkicommon.unmountdmg(installroot) return -1 if len(candidates) > 1: munkicommon.display_error("Cannot patch %s because we found " "more than one copy on the " "startup disk." % appname) munkicommon.unmountdmg(installroot) return -1 munkicommon.display_status_minor('Updating %s' % appname) apppath = os.path.dirname(candidates[0]["path"]) cmd = [ApplyOperation, apppath, appname, resourcesDir, callingScriptPath, str(payloadNum)] # figure out the log file path #patchappname = os.path.basename(pathToAcrobatPatchApp) #logfile_name = patchappname.split('.')[0] + str(payloadNum) + '.log' #homePath = os.path.expanduser("~") #logfile_dir = os.path.join(homePath, "Library", "Logs", # "Adobe", "Acrobat") #logfile_path = os.path.join(logfile_dir, logfile_name) proc = subprocess.Popen(cmd, shell=False, bufsize=1, stdin=subprocess.PIPE, stdout=subprocess.PIPE, stderr=subprocess.STDOUT) while (proc.poll() == None): time.sleep(1) #loginfo = getAcrobatPatchLogInfo(logfile_path) #if loginfo: # print loginfo # run of patch tool completed retcode = proc.poll() if retcode != 0: munkicommon.display_error("Error patching %s: %s" % (appname, retcode)) break else: munkicommon.display_status_minor( 'Patching %s complete.' % appname) munkicommon.display_status_minor('Done.') if munkicommon.munkistatusoutput: munkistatus.percent(100) munkicommon.unmountdmg(installroot) return retcode
def get_url(url, destinationpath, custom_headers=None, message=None, onlyifnewer=False, resume=False, follow_redirects=False): """Gets an HTTP or HTTPS URL and stores it in destination path. Returns a dictionary of headers, which includes http_result_code and http_result_description. Will raise CurlError if Gurl returns an error. Will raise HTTPError if HTTP Result code is not 2xx or 304. If destinationpath already exists, you can set 'onlyifnewer' to true to indicate you only want to download the file only if it's newer on the server. If you set resume to True, Gurl will attempt to resume an interrupted download.""" tempdownloadpath = destinationpath + '.download' if os.path.exists(tempdownloadpath) and not resume: if resume and not os.path.exists(destinationpath): os.remove(tempdownloadpath) cache_data = None if onlyifnewer and os.path.exists(destinationpath): # create a temporary Gurl object so we can extract the # stored caching data so we can download only if the # file has changed on the server gurl_obj = Gurl.alloc().initWithOptions_({'file': destinationpath}) cache_data = gurl_obj.get_stored_headers() del gurl_obj options = { 'url': url, 'file': tempdownloadpath, 'follow_redirects': follow_redirects, 'can_resume': resume, 'additional_headers': header_dict_from_list(custom_headers), 'download_only_if_changed': onlyifnewer, 'cache_data': cache_data, 'logging_function': munkicommon.display_debug2 } munkicommon.display_debug2('Options: %s' % options) connection = Gurl.alloc().initWithOptions_(options) stored_percent_complete = -1 stored_bytes_received = 0 connection.start() try: while True: # if we did `while not connection.isDone()` we'd miss printing # messages and displaying percentages if we exit the loop first connection_done = connection.isDone() if message and connection.status and connection.status != 304: # log always, display if verbose is 1 or more # also display in MunkiStatus detail field munkicommon.display_status_minor(message) # now clear message so we don't display it again message = None if (str(connection.status).startswith('2') and connection.percentComplete != -1): if connection.percentComplete != stored_percent_complete: # display percent done if it has changed stored_percent_complete = connection.percentComplete munkicommon.display_percent_done(stored_percent_complete, 100) elif connection.bytesReceived != stored_bytes_received: # if we don't have percent done info, log bytes received stored_bytes_received = connection.bytesReceived munkicommon.display_detail('Bytes received: %s', stored_bytes_received) if connection_done: break except (KeyboardInterrupt, SystemExit): # safely kill the connection then re-raise connection.cancel() raise except Exception, err: # too general, I know # Let us out! ... Safely! Unexpectedly quit dialogs are annoying... connection.cancel() # Re-raise the error as a GurlError raise GurlError(-1, str(err))
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_minor('Removing receipt info') munkicommon.display_percent_done(0, 4) conn = sqlite3.connect(packagedb) curs = conn.cursor() os_version = munkicommon.getOsVersion(as_tuple=True) applepkgdb = '/Library/Receipts/db/a.receiptdb' if not noupdateapplepkgdb and os_version <= (10, 5): aconn = sqlite3.connect(applepkgdb) acurs = aconn.cursor() munkicommon.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 os_version <= (10, 5): 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) dummy_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 os_version <= (10, 5): # 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, dummy_err) = proc.communicate() if output: munkicommon.display_detail( str(output).decode('UTF-8').rstrip('\n')) munkicommon.display_percent_done(2, 4) # now remove orphaned paths from paths table # first, Apple's database if option is passed if not noupdateapplepkgdb: if os_version <= (10, 5): 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() munkicommon.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() munkicommon.display_percent_done(4, 4)
def verifySoftwarePackageIntegrity(file_path, item_hash, always_hash=False): """Verifies the integrity of the given software package. The feature is controlled through the PackageVerificationMode key in the ManagedInstalls.plist. Following modes currently exist: none: No integrity check is performed. hash: Integrity check is performed by calcualting a SHA-256 hash of the given file and comparing it against the reference value in catalog. Only applies for package plists that contain the item_key; for packages without the item_key, verifcation always returns True. hash_strict: Same as hash, but returns False for package plists that do not contain the item_key. Args: file_path: The file to check integrity on. item_hash: the sha256 hash expected. always_hash: True/False always check (& return) the hash even if not necessary for this function. Returns: (True/False, sha256-hash) True if the package integrity could be validated. Otherwise, False. """ mode = munkicommon.pref('PackageVerificationMode') chash = None item_name = getURLitemBasename(file_path) if always_hash: chash = munkicommon.getsha256hash(file_path) if not mode: return (True, chash) elif mode.lower() == 'none': munkicommon.display_warning('Package integrity checking is disabled.') return (True, chash) elif mode.lower() == 'hash' or mode.lower() == 'hash_strict': if item_hash: munkicommon.display_status_minor('Verifying package integrity...') if not chash: chash = munkicommon.getsha256hash(file_path) if item_hash == chash: return (True, chash) else: munkicommon.display_error( 'Hash value integrity check for %s failed.' % item_name) return (False, chash) else: if mode.lower() == 'hash_strict': munkicommon.display_error( 'Reference hash value for %s is missing in catalog.' % item_name) return (False, chash) else: munkicommon.display_warning( 'Reference hash value missing for %s -- package ' 'integrity verification skipped.' % item_name) return (True, chash) else: munkicommon.display_error( 'The PackageVerificationMode in the ManagedInstalls.plist has an ' 'illegal value: %s' % munkicommon.pref('PackageVerificationMode')) return (False, chash)
def runAdobeCS5AAMEEInstall(dmgpath): '''Installs a CS5 product using an AAMEE-generated package on a disk image.''' munkicommon.display_status_minor( 'Mounting disk image %s' % os.path.basename(dmgpath)) mountpoints = mountAdobeDmg(dmgpath) if not mountpoints: munkicommon.display_error("No mountable filesystems on %s" % dmgpath) return -1 deploymentmanager = findAdobeDeploymentManager(mountpoints[0]) if deploymentmanager: # big hack to convince the Adobe tools to install off a mounted # disk image. # For some reason, the Adobe install tools refuse to install when # the payloads are on a "removable" disk, which includes mounted disk # images. # we create a temporary directory on the local disk and then symlink # some resources from the mounted disk image to the temporary # directory. When we pass this temporary directory to the Adobe # installation tools, they are now happy. basepath = os.path.dirname(deploymentmanager) number_of_payloads = countPayloads(basepath) tmpdir = tempfile.mkdtemp() # make our symlinks os.symlink(os.path.join(basepath,"ASU"), os.path.join(tmpdir, "ASU")) os.symlink(os.path.join(basepath,"ProvisioningTool"), os.path.join(tmpdir, "ProvisioningTool")) realsetupdir = os.path.join(basepath,"Setup") tmpsetupdir = os.path.join(tmpdir, "Setup") os.mkdir(tmpsetupdir) for item in munkicommon.listdir(realsetupdir): os.symlink(os.path.join(realsetupdir, item), os.path.join(tmpsetupdir, item)) optionXMLfile = os.path.join(basepath, "optionXML.xml") if (not munkicommon.getconsoleuser() or munkicommon.getconsoleuser() == u"loginwindow"): # we're at the loginwindow, so we need to run the deployment # manager in the loginwindow context using launchctl bsexec loginwindowPID = utils.getPIDforProcessName("loginwindow") cmd = ['/bin/launchctl', 'bsexec', loginwindowPID] else: cmd = [] cmd.extend([deploymentmanager, '--optXMLPath=%s' % optionXMLfile, '--setupBasePath=%s' % tmpdir, '--installDirPath=/', '--mode=install']) munkicommon.display_status_minor('Starting Adobe CS5 installer...') retcode = runAdobeInstallTool(cmd, number_of_payloads, killAdobeAIR=True) # now clean up our symlink hackfest unused_result = subprocess.call(["/bin/rm", "-rf", tmpdir]) else: munkicommon.display_error( "%s doesn't appear to contain AdobeDeploymentManager" % os.path.basename(dmgpath)) retcode = -1 munkicommon.unmountdmg(mountpoints[0]) return retcode