diff --git a/code/client/munkilib/installer.py b/code/client/munkilib/installer.py index 94b98f22..0e79ea64 100644 --- a/code/client/munkilib/installer.py +++ b/code/client/munkilib/installer.py @@ -657,6 +657,60 @@ def installWithInfo(dirpath, installlist, only_forced=False): return (restartflag, skipped_installs) +def writefile(stringdata, path): + '''Writes string data to path. + Returns the path on success, empty string on failure.''' + try: + fileobject = open(path, mode='w', buffering=1) + print >> fileobject, stringdata.encode('UTF-8') + fileobject.close() + return path + except (OSError, IOError): + munkicommon.display_error("Couldn't write %s" % stringdata) + return "" + + +def runUninstallScript(name, path): + if munkicommon.munkistatusoutput: + munkistatus.message("Running uninstall script " + "for %s..." % name) + munkistatus.detail("") + # set indeterminate progress bar + munkistatus.percent(-1) + + uninstalleroutput = [] + proc = subprocess.Popen(path, shell=False, bufsize=1, + stdin=subprocess.PIPE, + stdout=subprocess.PIPE, + stderr=subprocess.PIPE) + + while (proc.poll() == None): + msg = proc.stdout.readline().decode('UTF-8') + # save all uninstaller output in case there is + # an error so we can dump it to the log + uninstalleroutput.append(msg) + msg = msg.rstrip("\n") + munkicommon.display_info(msg) + + retcode = proc.poll() + if retcode: + munkicommon.display_error( + "Uninstall of %s failed." % name) + munkicommon.display_error("-"*78) + for line in uninstalleroutput: + munkicommon.display_error("\t%s" % line.rstrip("\n")) + munkicommon.display_error("-"*78) + else: + munkicommon.log("Uninstall of %s was " + "successful." % name) + + if munkicommon.munkistatusoutput: + # clear indeterminate progress bar + munkistatus.percent(0) + + return retcode + + def processRemovals(removallist, only_forced=False): '''processes removals from the removal list''' restartFlag = False @@ -735,50 +789,38 @@ def processRemovals(removallist, only_forced=False): "info missing from %s" % name) + elif uninstallmethod[0] == "uninstall_script": + uninstall_script = item.get('uninstall_script') + if uninstall_script: + scriptpath = os.path.join(munkicommon.tmpdir, + "uninstallscript") + if writefile(uninstall_script, scriptpath): + cmd = ['/bin/chmod', '-R', 'o+x', scriptpath] + retcode = subprocess.call(cmd) + if retcode: + munkicommon.display_error("Error setting mode " + "for %s" % scriptpath) + else: + retcode = runUninstallScript(name, scriptpath) + if (retcode == 0 and item.get( + 'RestartAction') == "RequireRestart"): + restartFlag = True + os.unlink(scriptpath) + else: + munkicommon.display_error("Cannot write uninstall " + "script for %s" % name) + else: + munkicommon.display_error("Uninstall script missing " + "from %s" % name) + elif os.path.exists(uninstallmethod[0]) and \ os.access(uninstallmethod[0], os.X_OK): # it's a script or program to uninstall - if munkicommon.munkistatusoutput: - munkistatus.message("Running uninstall script " - "for %s..." % name) - munkistatus.detail("") - # set indeterminate progress bar - munkistatus.percent(-1) - - if item.get('RestartAction') == "RequireRestart": + retcode = runUninstallScript(name, uninstallmethod[0]) + if (retcode == 0 and + item.get('RestartAction') == "RequireRestart"): restartFlag = True - cmd = uninstallmethod - uninstalleroutput = [] - proc = subprocess.Popen(cmd, shell=False, bufsize=1, - stdin=subprocess.PIPE, - stdout=subprocess.PIPE, - stderr=subprocess.STDOUT) - - while (proc.poll() == None): - msg = proc.stdout.readline().decode('UTF-8') - # save all uninstaller output in case there is - # an error so we can dump it to the log - uninstalleroutput.append(msg) - msg = msg.rstrip("\n") - munkicommon.display_info(msg) - - retcode = proc.poll() - if retcode: - munkicommon.display_error( - "Uninstall of %s failed." % name) - munkicommon.display_error("-"*78) - for line in uninstalleroutput: - munkicommon.display_error("\t%s" % line.rstrip("\n")) - munkicommon.display_error("-"*78) - else: - munkicommon.log("Uninstall of %s was " - "successful." % name) - - if munkicommon.munkistatusoutput: - # clear indeterminate progress bar - munkistatus.percent(0) - else: munkicommon.log("Uninstall of %s failed because " "there was no valid uninstall " diff --git a/code/client/munkilib/updatecheck.py b/code/client/munkilib/updatecheck.py index f8bfa7ce..010c874e 100644 --- a/code/client/munkilib/updatecheck.py +++ b/code/client/munkilib/updatecheck.py @@ -758,7 +758,6 @@ def getAllItemsWithName(name, cataloglist): if itemlist: # sort so latest version is first itemlist.sort(compare_item_versions) - return itemlist @@ -1070,16 +1069,8 @@ def evidenceThisIsInstalled(item_pl): This is used when determining if we can remove the item, thus the attention given to the uninstall method. """ - if item_pl.get('uninstall_method') == 'removepackages': - # we're supposed to use receipt info to remove - # this, so we should check for relevent receipts - if item_pl.get('receipts'): - if PKGDATA == {}: - # build our database of installed packages - analyzeInstalledPkgs() - if item_pl['name'] in PKGDATA['installed_names']: - return True - elif 'installs' in item_pl: + if ('installs' in item_pl and + item_pl.get('uninstall_method') != 'removepackages'): installitems = item_pl['installs'] foundallinstallitems = True for item in installitems: @@ -1092,6 +1083,12 @@ def evidenceThisIsInstalled(item_pl): foundallinstallitems = False if foundallinstallitems: return True + if item_pl.get('receipts'): + if PKGDATA == {}: + # build our database of installed packages + analyzeInstalledPkgs() + if item_pl['name'] in PKGDATA['installed_names']: + return True # if we got this far, we failed all the tests, so the item # must not be installed (or we dont't have the right info...) @@ -1643,9 +1640,14 @@ def processRemoval(manifestitem, cataloglist, installinfo): installEvidence = False for item in infoitems: + munkicommon.display_debug2('Considering item %s-%s for removal info' + % (item['name'], item['version'])) if evidenceThisIsInstalled(item): installEvidence = True break + else: + munkicommon.display_debug2('%s-%s not installed.' + % (item['name'], item['version'])) if not installEvidence: munkicommon.display_detail('%s doesn\'t appear to be installed.' % @@ -1673,9 +1675,9 @@ def processRemoval(manifestitem, cataloglist, installinfo): elif uninstallmethod.startswith('Adobe'): # Adobe CS3/CS4/CS5 product uninstall_item = item - elif uninstallmethod == 'remove_copied_items': - uninstall_item = item - elif uninstallmethod == 'remove_app': + elif uninstallmethod in ['remove_copied_items', + 'remove_app', + 'uninstall_script']: uninstall_item = item else: # uninstall_method is a local script. @@ -1824,6 +1826,8 @@ def processRemoval(manifestitem, cataloglist, installinfo): elif uninstallmethod == 'remove_app': if uninstall_item.get('installs', None): iteminfo['remove_app_info'] = uninstall_item['installs'][0] + elif uninstallmethod == 'uninstall_script': + iteminfo['uninstall_script'] = item.get('uninstall_script','') # before we add this removal to the list, # check for installed updates and add them to the