def countPayloads(dirpath): '''Attempts to count the payloads in the Adobe installation item''' for item in munkicommon.listdir(dirpath): itempath = os.path.join(dirpath, item) if os.path.isdir(itempath): if item == "payloads": count = 0 for subitem in munkicommon.listdir(itempath): subitempath = os.path.join(itempath, subitem) if os.path.isdir(subitempath): count = count + 1 return count else: payloadcount = countPayloads(itempath) if payloadcount: return payloadcount return 0
def findBundleReceiptFromID(pkgid): '''Finds a bundle receipt in /Library/Receipts based on packageid. Some packages write bundle receipts under /Library/Receipts even on Snow Leopard; we need to be able to find them so we can remove them. Returns a path.''' if not pkgid: return '' receiptsdir = "/Library/Receipts" for item in munkicommon.listdir(receiptsdir): itempath = os.path.join(receiptsdir, item) if item.endswith('.pkg') and os.path.isdir(itempath): info = munkicommon.getOnePackageInfo(itempath) if info.get('packageid') == pkgid: return itempath #if we get here, not found return ''
def getPayloadInfo(dirpath): '''Parses Adobe payloads, pulling out info useful to munki''' payloadinfo = {} # look for .proxy.xml file dir if os.path.isdir(dirpath): for item in munkicommon.listdir(dirpath): if item.endswith('.proxy.xml'): xmlpath = os.path.join(dirpath, item) dom = minidom.parse(xmlpath) payload_info = dom.getElementsByTagName("PayloadInfo") if payload_info: installer_properties = \ payload_info[0].getElementsByTagName( "InstallerProperties") if installer_properties: properties = \ installer_properties[0].getElementsByTagName( "Property") for prop in properties: if 'name' in prop.attributes.keys(): propname = \ prop.attributes['name'].value.encode('UTF-8') propvalue = '' for node in prop.childNodes: propvalue += node.nodeValue if propname == 'AdobeCode': payloadinfo['AdobeCode'] = propvalue if propname == 'ProductName': payloadinfo['display_name'] = propvalue if propname == 'ProductVersion': payloadinfo['version'] = propvalue installmetadata = \ payload_info[0].getElementsByTagName( "InstallDestinationMetadata") if installmetadata: totalsizes = \ installmetadata[0].getElementsByTagName("TotalSize") if totalsizes: installsize = '' for node in totalsizes[0].childNodes: installsize += node.nodeValue payloadinfo['installed_size'] = \ int(installsize)/1024 return payloadinfo
def shouldRebuildDB(pkgdbpath): """ Checks to see if our internal package DB should be rebuilt. If anything in /Library/Receipts, /Library/Receipts/boms, or /Library/Receipts/db/a.receiptdb has a newer modtime than our database, we should rebuild. """ receiptsdir = "/Library/Receipts" bomsdir = "/Library/Receipts/boms" sl_receiptsdir = "/private/var/db/receipts" installhistory = "/Library/Receipts/InstallHistory.plist" applepkgdb = "/Library/Receipts/db/a.receiptdb" if not os.path.exists(pkgdbpath): return True packagedb_modtime = os.stat(pkgdbpath).st_mtime if os.path.exists(receiptsdir): receiptsdir_modtime = os.stat(receiptsdir).st_mtime if packagedb_modtime < receiptsdir_modtime: return True receiptlist = munkicommon.listdir(receiptsdir) for item in receiptlist: if item.endswith(".pkg"): pkgpath = os.path.join(receiptsdir, item) pkg_modtime = os.stat(pkgpath).st_mtime if packagedb_modtime < pkg_modtime: return True if os.path.exists(bomsdir): bomsdir_modtime = os.stat(bomsdir).st_mtime if packagedb_modtime < bomsdir_modtime: return True bomlist = munkicommon.listdir(bomsdir) for item in bomlist: if item.endswith(".bom"): bompath = os.path.join(bomsdir, item) bom_modtime = os.stat(bompath).st_mtime if packagedb_modtime < bom_modtime: return True if os.path.exists(sl_receiptsdir): receiptsdir_modtime = os.stat(sl_receiptsdir).st_mtime if packagedb_modtime < receiptsdir_modtime: return True receiptlist = munkicommon.listdir(sl_receiptsdir) for item in receiptlist: if item.endswith(".bom") or item.endswith(".plist"): pkgpath = os.path.join(sl_receiptsdir, item) pkg_modtime = os.stat(pkgpath).st_mtime if packagedb_modtime < pkg_modtime: return True if os.path.exists(installhistory): installhistory_modtime = os.stat(installhistory).st_mtime if packagedb_modtime < installhistory_modtime: return True if os.path.exists(applepkgdb): applepkgdb_modtime = os.stat(applepkgdb).st_mtime if packagedb_modtime < applepkgdb_modtime: return True # if we got this far, we don't need to update the db return False
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 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 listdir(self, path): '''Lists the contents of a repo directory.''' return listdir(os.path.join(self.path, path))
def shouldRebuildDB(pkgdbpath): """ Checks to see if our internal package DB should be rebuilt. If anything in /Library/Receipts, /Library/Receipts/boms, or /Library/Receipts/db/a.receiptdb has a newer modtime than our database, we should rebuild. """ receiptsdir = "/Library/Receipts" bomsdir = "/Library/Receipts/boms" sl_receiptsdir = "/private/var/db/receipts" installhistory = "/Library/Receipts/InstallHistory.plist" applepkgdb = "/Library/Receipts/db/a.receiptdb" if not os.path.exists(pkgdbpath): return True packagedb_modtime = os.stat(pkgdbpath).st_mtime if os.path.exists(receiptsdir): receiptsdir_modtime = os.stat(receiptsdir).st_mtime if packagedb_modtime < receiptsdir_modtime: return True receiptlist = munkicommon.listdir(receiptsdir) for item in receiptlist: if item.endswith(".pkg"): pkgpath = os.path.join(receiptsdir, item) pkg_modtime = os.stat(pkgpath).st_mtime if (packagedb_modtime < pkg_modtime): return True if os.path.exists(bomsdir): bomsdir_modtime = os.stat(bomsdir).st_mtime if packagedb_modtime < bomsdir_modtime: return True bomlist = munkicommon.listdir(bomsdir) for item in bomlist: if item.endswith(".bom"): bompath = os.path.join(bomsdir, item) bom_modtime = os.stat(bompath).st_mtime if (packagedb_modtime < bom_modtime): return True if os.path.exists(sl_receiptsdir): receiptsdir_modtime = os.stat(sl_receiptsdir).st_mtime if packagedb_modtime < receiptsdir_modtime: return True receiptlist = munkicommon.listdir(sl_receiptsdir) for item in receiptlist: if item.endswith(".bom") or item.endswith(".plist"): pkgpath = os.path.join(sl_receiptsdir, item) pkg_modtime = os.stat(pkgpath).st_mtime if (packagedb_modtime < pkg_modtime): return True if os.path.exists(installhistory): installhistory_modtime = os.stat(installhistory).st_mtime if packagedb_modtime < installhistory_modtime: return True if os.path.exists(applepkgdb): applepkgdb_modtime = os.stat(applepkgdb).st_mtime if packagedb_modtime < applepkgdb_modtime: return True # if we got this far, we don't need to update the db return False
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 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
def getAdobeSetupInfo(installroot): '''Given the root of mounted Adobe DMG, look for info about the installer or updater''' info = {} payloads = [] # look for a payloads folder for (path, unused_dirs, unused_files) in os.walk(installroot): if path.endswith("/payloads"): driverfolder = '' mediaSignature = '' setupxml = os.path.join(path, "setup.xml") if os.path.exists(setupxml): dom = minidom.parse(setupxml) drivers = dom.getElementsByTagName("Driver") if drivers: driver = drivers[0] if 'folder' in driver.attributes.keys(): driverfolder = \ driver.attributes['folder'].value.encode('UTF-8') if driverfolder == '': # look for mediaSignature (CS5 AAMEE install) setupElements = dom.getElementsByTagName("Setup") if setupElements: mediaSignatureElements = \ setupElements[0].getElementsByTagName( "mediaSignature") if mediaSignatureElements: element = mediaSignatureElements[0] for node in element.childNodes: mediaSignature += node.nodeValue for item in munkicommon.listdir(path): payloadpath = os.path.join(path, item) payloadinfo = getPayloadInfo(payloadpath) if payloadinfo: payloads.append(payloadinfo) if (driverfolder and item == driverfolder) or \ (mediaSignature and payloadinfo['AdobeCode'] == mediaSignature): info['display_name'] = payloadinfo['display_name'] info['version'] = payloadinfo['version'] info['AdobeSetupType'] = "ProductInstall" # we found a payloads directory, # so no need to keep walking the installroot break if not payloads: # look for an extensions folder; almost certainly this is an Updater for (path, unused_dirs, unused_files) in os.walk(installroot): if path.endswith("/extensions"): for item in munkicommon.listdir(path): #skip LanguagePacks if item.find("LanguagePack") == -1: itempath = os.path.join(path, item) payloadinfo = getPayloadInfo(itempath) if payloadinfo: payloads.append(payloadinfo) # we found an extensions dir, # so no need to keep walking the install root break if payloads: if len(payloads) == 1: info['display_name'] = payloads[0]['display_name'] info['version'] = payloads[0]['version'] else: if not 'display_name' in info: info['display_name'] = "ADMIN: choose from payloads" if not 'version' in info: info['version'] = "ADMIN please set me" info['payloads'] = payloads installed_size = 0 for payload in payloads: installed_size = installed_size + \ payload.get('installed_size',0) info['installed_size'] = installed_size return info