def verifyIsDMG(myClass, identifier, checksumDMG=False): '''Confirm with hdiutil that the object identified is a dmg, optionally checksumming it''' if not hasattr(identifier, 'capitalize'): raise ValueError( 'verifyIsDMG requires a path, bsd name, or a dev path. Got: ' + str(identifier)) if not checksumDMG in [True, False]: raise ValueError( 'The option checksumDMG given to verifyIsDMG must be either True or False. Got: ' + str(checksumDMG)) command = ['/usr/bin/hdiutil', 'imageinfo', str(identifier)] try: process = managedSubprocess(command) except RuntimeError: return False if checksumDMG is True: command = ['/usr/bin/hdiutil', 'verify', str(identifier)] try: process = managedSubprocess(command) except RuntimeError: return False return True
def test_failingCommand(self): '''Calling a command that results in a failing return code should throw a RuntimeError''' command = ['/bin/ls', '/this-should-not-exist'] self.assertRaises(RuntimeError, managedSubprocess, command) # again, but this time catch the output try: managedSubprocess(command) except RuntimeError, e: expectedString = 'The process "/bin/ls /this-should-not-exist" failed with error: 1\nStderr: ls: /this-should-not-exist: No such file or directory' self.assertEqual(str(e), expectedString, 'Calling a failing command (%s) and expected:\n%s\nBut got:\n%s' % (" ".join(command), expectedString, str(e)))
def unmountVolume(targetPath): '''Unmount a volume or dmg mounted at the path given''' if not os.path.ismount(targetPath): raise ValueError('unmountVolume valled on a path that was not a mount point: ' + targetPath) targetPath = pathHelpers.normalizePath(targetPath) # check to see if this is a disk image isMountedDMG = False command = ['/usr/bin/hdiutil', 'info', '-plist'] process = managedSubprocess(command, processAsPlist=True) plistData = process.getPlistObject() if not hasattr(plistData, 'has_key') or not plistData.has_key('images') or not hasattr(plistData['images'], '__iter__'): raise RuntimeError('The "%s" output does not have an "images" array as expected. Output was:\n%s' % (' '.join(command), output)) for thisImage in plistData['images']: if not hasattr(thisImage, '__iter__') or not thisImage.has_key('system-entities') or not hasattr(thisImage['system-entities'], '__iter__'): raise RuntimeError('The "%s" output had an image entry that was not formed as expected. Output was:\n%s' % (' '.join(command), output)) for thisEntry in thisImage['system-entities']: if thisEntry.has_key('mount-point') and os.path.samefile(thisEntry['mount-point'], targetPath): isMountedDMG = True break if isMountedDMG is True: break if isMountedDMG is True: # ToDo: log this command = ['/usr/bin/hdiutil', 'eject', targetPath] process = subprocess.Popen(command, stdout=subprocess.PIPE, stderr=subprocess.PIPE) if process.wait() != 0 and os.path.ismount(targetPath): # try again with a bit more force # ToDo: log this command = ['/usr/bin/hdiutil', 'eject', '-force', targetPath] managedSubprocess(command) else: # a non dmg mount point # ToDo: log this command = ['/usr/sbin/diskutil', 'unmount', targetPath] process = subprocess.Popen(command, stdout=subprocess.PIPE, stderr=subprocess.PIPE) if process.wait() != 0 and os.path.ismount(targetPath): # try again with a bit more force # ToDo: log this command = ['/usr/sbin/diskutil', 'unmount', 'force', targetPath] managedSubprocess(command)
def test_failingCommand(self): '''Calling a command that results in a failing return code should throw a RuntimeError''' command = ['/bin/ls', '/this-should-not-exist'] self.assertRaises(RuntimeError, managedSubprocess, command) # again, but this time catch the output try: managedSubprocess(command) except RuntimeError, e: expectedString = 'The process "/bin/ls /this-should-not-exist" failed with error: 1\nStderr: ls: /this-should-not-exist: No such file or directory' self.assertEqual( str(e), expectedString, 'Calling a failing command (%s) and expected:\n%s\nBut got:\n%s' % (" ".join(command), expectedString, str(e)))
def getMountedVolumes(excludeRoot=True): diskutilArguments = ['/usr/sbin/diskutil', 'list', '-plist'] diskutilProcess = managedSubprocess(diskutilArguments, processAsPlist=True) diskutilOutput = diskutilProcess.getPlistObject() if not "AllDisks" in diskutilOutput or not hasattr( diskutilOutput["AllDisks"], '__iter__'): raise RuntimeError( 'Error: The output from diksutil list does not look right:\n%s\n' % str(diskutilOutput)) mountedVolumes = [] for thisVolume in diskutilOutput["AllDisks"]: # get the mount thisVolumeInfo = getDiskutilInfo(str(thisVolume)) # exclude whole disks if thisVolumeInfo['bsdName'] == thisVolumeInfo['diskBsdName']: continue # exclude unmounted disks if not 'mountPath' in thisVolumeInfo or thisVolumeInfo[ 'mountPath'] in [None, '']: continue # exclude the root mount if it is not requested elif thisVolumeInfo['mountPath'] == '/' and excludeRoot == True: continue mountedVolumes.append(str(thisVolumeInfo['mountPath'])) return mountedVolumes
def test_stderr(self): '''Make sure that stderr is being properly returned''' # setup a folder to then test on (so things are predictable) outerFolder = tempFolderManager.getNewTempFolder() testFile = open(os.path.join(outerFolder, "testItem"), "w") testFile.close() command = ["/bin/ls " + outerFolder + " 1>&2"] process = managedSubprocess(command, shell=True) self.assertTrue( hasattr(process, 'stderr') and process.stderr is not None, 'managedSubprocess did not have a stderr item when it should have') result = process.stderr.read() expectedResult = "testItem\n" self.assertTrue( isinstance(process.stderrLen, int), 'managedSubprocess should have had an integer value for stderrLen, rather it had: ' + str(process.stderrLen)) self.assertEqual( len(expectedResult), process.stderrLen, 'managedSubprocess should have had a length of %i for stdoutLen, rather it had a length of %i: %s' % (len(expectedResult), process.stderrLen, result)) self.assertEqual( result, expectedResult, 'managedSubprocess did not return the correct stderr for process "%s". Got "%s" rather than "%s"' % (" ".join(command), result, expectedResult))
def getDMGMountPoints(myClass, dmgFilePath): if not os.path.exists(dmgFilePath): raise ValueError( 'getDMGMountPoint called with a dmgFilePath that does not exist: ' + dmgFilePath) hdiutilArguments = ['/usr/bin/hdiutil', 'info', '-plist'] hdiutilProcess = managedSubprocess(hdiutilArguments, processAsPlist=True) hdiutilOutput = hdiutilProcess.getPlistObject() if 'images' in hdiutilOutput: for thisDMG in hdiutilOutput['images']: if os.path.samefile(thisDMG['image-path'], dmgFilePath): mountPoints = [] for thisEntry in thisDMG['system-entities']: if 'mount-point' in thisEntry: mountPoints.append(str(thisEntry['mount-point'])) if len(mountPoints) > 0: return mountPoints break return None
def getMountedVolumes(excludeRoot=True): diskutilArguments = ['/usr/sbin/diskutil', 'list', '-plist'] diskutilProcess = managedSubprocess(diskutilArguments, processAsPlist=True) diskutilOutput = diskutilProcess.getPlistObject() if not "AllDisks" in diskutilOutput or not hasattr(diskutilOutput["AllDisks"], '__iter__'): raise RuntimeError('Error: The output from diksutil list does not look right:\n%s\n' % str(diskutilOutput)) mountedVolumes = [] for thisVolume in diskutilOutput["AllDisks"]: # get the mount thisVolumeInfo = getDiskutilInfo(str(thisVolume)) # exclude whole disks if thisVolumeInfo['bsdName'] == thisVolumeInfo['diskBsdName']: continue # exclude unmounted disks if not 'mountPath' in thisVolumeInfo or thisVolumeInfo['mountPath'] in [None, '']: continue # exclude the root mount if it is not requested elif thisVolumeInfo['mountPath'] == '/' and excludeRoot == True: continue mountedVolumes.append(str(thisVolumeInfo['mountPath'])) return mountedVolumes
def test_processAsPlist(self): '''Make sure that correct plists get returned properly''' command = ['/usr/bin/hdiutil', 'info', '-plist'] process = managedSubprocess(command, processAsPlist=True) self.assertTrue(not hasattr(process, 'stdout') or process.stdout is None, 'When called with processAsPlist=True managedSubprocess should not have a useable stdout attribute') self.assertTrue(not hasattr(process, 'stderr') or process.stderr is None, 'When called with processAsPlist=True managedSubprocess should not have a useable stderr attribute') plistData = process.getPlistObject() self.assertTrue(hasattr(plistData, 'has_key') and plistData.has_key('images'), 'The plist data returned form the command "%s" was not a hash with a "images" key as expected' % " ".join(command))
def getDiskutilInfo(identifier): '''Return the following information about the mount point, bsd name, or dev path provided: mountPath, volumeName, bsdPath, volumeFormat, diskType, bsdName, diskBsdName, volumeSizeInBytes, volumeUuid''' if not hasattr(identifier, 'capitalize'): raise ValueError('getVolumeInfo requires a path, bsd name, or a dev path. Got: ' + str(identifier)) command = ['/usr/sbin/diskutil', 'info', '-plist', str(identifier)] try: process = managedSubprocess(command, processAsPlist=True) except RuntimeError, error: raise ValueError('The input to getVolumeInfo does not look like it was valid: ' + str(identifier) + "\nError:\n" + str(error))
def test_commandLine(self): pathToCommandLine = os.path.join(os.path.dirname(__file__), "findInstallerDisc.py") buildsInfo = self.getAllowedBuildsFromVanilla("10.6") process = managedSubprocess.managedSubprocess( [pathToCommandLine, "--allowed-builds", ", ".join(buildsInfo), "--supress-return"] ) results = process.stdout.read().split("\n") self.assertTrue( os.path.exists(results[0]), "The first line returned by the command-line version is not a path to a valid item: " + str(results[0]), )
def verifyIsDMG(myClass, identifier, checksumDMG=False): '''Confirm with hdiutil that the object identified is a dmg, optionally checksumming it''' if not hasattr(identifier, 'capitalize'): raise ValueError('verifyIsDMG requires a path, bsd name, or a dev path. Got: ' + str(identifier)) if not checksumDMG in [True, False]: raise ValueError('The option checksumDMG given to verifyIsDMG must be either True or False. Got: ' + str(checksumDMG)) command = ['/usr/bin/hdiutil', 'imageinfo', str(identifier)] try: process = managedSubprocess(command) except RuntimeError: return False if checksumDMG is True: command = ['/usr/bin/hdiutil', 'verify', str(identifier)] try: process = managedSubprocess(command) except RuntimeError: return False return True
def getDiskutilInfo(identifier): '''Return the following information about the mount point, bsd name, or dev path provided: mountPath, volumeName, bsdPath, volumeFormat, diskType, bsdName, diskBsdName, volumeSizeInBytes, volumeUuid''' if not hasattr(identifier, 'capitalize'): raise ValueError( 'getVolumeInfo requires a path, bsd name, or a dev path. Got: ' + str(identifier)) command = ['/usr/sbin/diskutil', 'info', '-plist', str(identifier)] try: process = managedSubprocess(command, processAsPlist=True) except RuntimeError, error: raise ValueError( 'The input to getVolumeInfo does not look like it was valid: ' + str(identifier) + "\nError:\n" + str(error))
def test_commandLine(self): pathToCommandLine = os.path.join(os.path.dirname(__file__), 'findInstallerDisc.py') buildsInfo = self.getAllowedBuildsFromVanilla('10.6') process = managedSubprocess.managedSubprocess([ pathToCommandLine, '--allowed-builds', ", ".join(buildsInfo), '--supress-return' ]) results = process.stdout.read().split('\n') self.assertTrue( os.path.exists(results[0]), 'The first line returned by the command-line version is not a path to a valid item: ' + str(results[0]))
def test_stderr(self): '''Make sure that stderr is being properly returned''' # setup a folder to then test on (so things are predictable) outerFolder = tempFolderManager.getNewTempFolder() testFile = open(os.path.join(outerFolder, "testItem"), "w") testFile.close() command = ["/bin/ls " + outerFolder + " 1>&2"] process = managedSubprocess(command, shell=True) self.assertTrue(hasattr(process, 'stderr') and process.stderr is not None, 'managedSubprocess did not have a stderr item when it should have') result = process.stderr.read() expectedResult = "testItem\n" self.assertTrue(isinstance(process.stderrLen, int), 'managedSubprocess should have had an integer value for stderrLen, rather it had: ' + str(process.stderrLen)) self.assertEqual(len(expectedResult), process.stderrLen, 'managedSubprocess should have had a length of %i for stdoutLen, rather it had a length of %i: %s' % (len(expectedResult), process.stderrLen, result)) self.assertEqual(result, expectedResult, 'managedSubprocess did not return the correct stderr for process "%s". Got "%s" rather than "%s"' % (" ".join(command), result, expectedResult))
def test_processAsPlist(self): '''Make sure that correct plists get returned properly''' command = ['/usr/bin/hdiutil', 'info', '-plist'] process = managedSubprocess(command, processAsPlist=True) self.assertTrue( not hasattr(process, 'stdout') or process.stdout is None, 'When called with processAsPlist=True managedSubprocess should not have a useable stdout attribute' ) self.assertTrue( not hasattr(process, 'stderr') or process.stderr is None, 'When called with processAsPlist=True managedSubprocess should not have a useable stderr attribute' ) plistData = process.getPlistObject() self.assertTrue( hasattr(plistData, 'has_key') and plistData.has_key('images'), 'The plist data returned form the command "%s" was not a hash with a "images" key as expected' % " ".join(command))
def getDMGMountPoints(myClass, dmgFilePath): if not os.path.exists(dmgFilePath): raise ValueError('getDMGMountPoint called with a dmgFilePath that does not exist: ' + dmgFilePath) hdiutilArguments = ['/usr/bin/hdiutil', 'info', '-plist'] hdiutilProcess = managedSubprocess(hdiutilArguments, processAsPlist=True) hdiutilOutput = hdiutilProcess.getPlistObject() if 'images' in hdiutilOutput: for thisDMG in hdiutilOutput['images']: if os.path.samefile(thisDMG['image-path'], dmgFilePath): mountPoints = [] for thisEntry in thisDMG['system-entities']: if 'mount-point' in thisEntry: mountPoints.append(str(thisEntry['mount-point'])) if len(mountPoints) > 0: return mountPoints break return None
def test_getPlistObjectWithoutAPlist(self): '''Confirm that a RuntimeError is called if getPlistObject is called on a non-plist object''' command = ['/bin/ls'] process = managedSubprocess(command) self.assertRaises(RuntimeError, process.getPlistObject)
def getVolumeInfo(myClass, identifier): '''Get the following information for a volume (if avalible) and return it as a hash: filePath, dmgFormat, writeable, dmg-checksum-type, dmg-checksum-value Additionally, provide information from the superclass if it is mounted: volumeName, mount-points, bsd-label ''' if not hasattr(identifier, 'capitalize'): raise ValueError( 'getVolumeInfo requires a path, bsd name, or a dev path. Got: ' + str(identifier)) result = None # look to see if this is a mount path, bsd name, or a dev path that diskutil can work with try: result = super(self.__class__, self).getVolumeInfo(identifier) if result['diskType'] is not 'Disk Image': raise ValueError( "%s's getVolumeInfo method requires a disk image as the argument. Got a %s as the argument: %s" % (myClass.__name__, result['diskType'], identifier)) # if we are here, then the identifier must be the mounted path, or something in it identifier = result['mountPath'] except ValueError: # this might be a pointer to the dmg file result = {} # try for information on this as a dmg command = ['/usr/bin/hdiutil', 'imageinfo', '-plist', str(identifier)] try: process = managedSubprocess(command, processAsPlist=True) except RuntimeError: raise ValueError('The item given does not seem to be a DMG: ' + str(identifier)) dmgProperties = process.getPlistObject() # filePath result['filePath'] = dmgProperties['Backing Store Information']['URL'] if result['filePath'].startswith('file://localhost'): result['filePath'] = result['filePath'][len('file://localhost'):] elif result['filePath'].startswith('file://'): result['filePath'] = result['filePath'][len('file://'):] # dmgFormat result['dmgFormat'] = dmgProperties['Format'] # writable if dmgProperties['Format'] in ['UDRW', 'UDSP', 'UDSB', 'RdWr']: result['writeable'] = True else: result['writeable'] = False # dmg-checksum-type if 'Checksum Type' in dmgProperties: result['dmgChecksumType'] = dmgProperties['Checksum Type'] else: result['dmgChecksumType'] = None # just in case we ever re-do this # dmg-checksum-value if 'Checksum Value' in dmgProperties: result['dmgChecksumValue'] = dmgProperties['Checksum Value'] else: result[ 'dmgChecksumValue'] = None # just in case we ever re-do this return result
def mountImage(myClass, dmgFile, mountPoint=None, mountInFolder=None, mountReadWrite=None, shadowFile=None, paranoidMode=False): '''Mount an image''' # -- validate input # dmgFile if dmgFile is None or not os.path.exists(dmgFile) or os.path.ismount( dmgFile): raise ValueError( 'mountImage requires dmgFile be a path to a file, got: ' + dmgFile) dmgFile = pathHelpers.normalizePath(dmgFile, followSymlink=True) # mountPoint/mountInFolder if mountPoint is not None and mountInFolder is not None: raise ValueError( 'mountImage can only be called with mountPoint or mountInFolder, not both' ) elif mountPoint is not None and os.path.ismount(mountPoint): raise ValueError( 'mountImage called with a mountPoint that is already a mount point: ' + mountPoint) elif mountPoint is not None and os.path.isdir(mountPoint): if len(os.listdir(mountPoint)) != 0: raise ValueError( 'mountImage called with a mountPoint that already has contents: %s (%s)' % (mountPoint, str(os.listdir(mountPoint)))) elif mountPoint is not None and os.path.exists(mountPoint): raise ValueError( 'mountImage called with a mountPoint that already exists and is not a folder: ' + mountPoint) elif mountPoint is not None: tempFolderManager.add elif mountInFolder is not None and not os.path.isdir(mountInFolder): raise ValueError( 'mountImage called with a mountInFolder path that is not a folder: ' + mountInFolder) elif mountInFolder is not None: mountPoint = tempFolderManager.getNewMountPoint() else: # create a default mount point mountPoint = tempFolderManager.getNewMountPoint() mountPoint = pathHelpers.normalizePath(mountPoint, followSymlink=True) # mountReadWrite if mountReadWrite not in [None, True, False]: raise ValueError( 'The only valid options for mountReadWrite are True or False') # shadowFile if shadowFile is True: # generate a temporary one shadowFile = tempFolderManager.getNewTempFile(suffix='.shadow') elif shadowFile is not None: shadowFile = pathHelpers.normalizePath(shadowFile, followSymlink=True) if os.path.isfile(shadowFile): # work here pass # just use the file elif os.path.isdir(shadowFile): # a directory to put the shadow file in shadowFile = tempFolderManager.getNewTempFile( parentFolder=shadowFile, suffix='.shadow') elif os.path.isdir(os.path.dirname(shadowFile)): # the path does not exist, but the directory it is in looks good pass else: # not valid raise ValueError( 'The path given for the shadow file does not look valid: ' + str(shadowFile)) # paranoidMode if paranoidMode not in [True, False]: raise ValueError( 'checksumImage must be either True, or False. Got: ' + str(paranoidMode)) # -- check to see if it is already mounted existingMountPoints = myClass.getDMGMountPoints(dmgFile) if existingMountPoints is not None: raise RuntimeError('The image (%s) was already mounted: %s' % (dmgFile, ", ".join(existingMountPoints))) # -- construct the command command = [ '/usr/bin/hdiutil', 'attach', dmgFile, '-plist', '-mountpoint', mountPoint, '-nobrowse', '-owners', 'on' ] if mountReadWrite is True and shadowFile is None and self.writeable is False: shadowFile = tempFolderManager.getNewTempFile(suffix='.shadow') elif mountReadWrite is False and shadowFile is None: command += ['-readonly'] if paranoidMode is False: command += ['-noverify', '-noautofsck'] else: command += ['-verify', '-autofsck'] if shadowFile is not None: command += ['-shadow', shadowFile] # -- run the command process = managedSubprocess(command, processAsPlist=True) mountInfo = process.getPlistObject() actualMountedPath = None for thisEntry in mountInfo['system-entities']: if 'mount-point' in thisEntry: if actualMountedPath != None and os.path.ismount( actualMountedPath): # ToDo: think all of this through, prefereabley before it is needed raise Exception( 'This item (%s) seems to be mounted at two places , this is possible, but now that it is happening larkost needs to work on this' % self.filePath) actualMountedPath = thisEntry[ 'mount-point'] # ToDo: make sure that this is the mount point we requested break # assume that there is only one mountable item... otherwise we are hosed already if actualMountedPath is None: raise RuntimeError('Error: image could not be mounted') return actualMountedPath
def unmountVolume(targetPath): '''Unmount a volume or dmg mounted at the path given''' if not os.path.ismount(targetPath): raise ValueError( 'unmountVolume valled on a path that was not a mount point: ' + targetPath) targetPath = pathHelpers.normalizePath(targetPath) # check to see if this is a disk image isMountedDMG = False command = ['/usr/bin/hdiutil', 'info', '-plist'] process = managedSubprocess(command, processAsPlist=True) plistData = process.getPlistObject() if not hasattr(plistData, 'has_key') or not plistData.has_key( 'images') or not hasattr(plistData['images'], '__iter__'): raise RuntimeError( 'The "%s" output does not have an "images" array as expected. Output was:\n%s' % (' '.join(command), output)) for thisImage in plistData['images']: if not hasattr(thisImage, '__iter__') or not thisImage.has_key( 'system-entities') or not hasattr(thisImage['system-entities'], '__iter__'): raise RuntimeError( 'The "%s" output had an image entry that was not formed as expected. Output was:\n%s' % (' '.join(command), output)) for thisEntry in thisImage['system-entities']: if thisEntry.has_key('mount-point') and os.path.samefile( thisEntry['mount-point'], targetPath): isMountedDMG = True break if isMountedDMG is True: break if isMountedDMG is True: # ToDo: log this command = ['/usr/bin/hdiutil', 'eject', targetPath] process = subprocess.Popen(command, stdout=subprocess.PIPE, stderr=subprocess.PIPE) if process.wait() != 0 and os.path.ismount(targetPath): # try again with a bit more force # ToDo: log this command = ['/usr/bin/hdiutil', 'eject', '-force', targetPath] managedSubprocess(command) else: # a non dmg mount point # ToDo: log this command = ['/usr/sbin/diskutil', 'unmount', targetPath] process = subprocess.Popen(command, stdout=subprocess.PIPE, stderr=subprocess.PIPE) if process.wait() != 0 and os.path.ismount(targetPath): # try again with a bit more force # ToDo: log this command = ['/usr/sbin/diskutil', 'unmount', 'force', targetPath] managedSubprocess(command)
def mountImage(myClass, dmgFile, mountPoint=None, mountInFolder=None, mountReadWrite=None, shadowFile=None, paranoidMode=False): '''Mount an image''' # -- validate input # dmgFile if dmgFile is None or not os.path.exists(dmgFile) or os.path.ismount(dmgFile): raise ValueError('mountImage requires dmgFile be a path to a file, got: ' + dmgFile) dmgFile = pathHelpers.normalizePath(dmgFile, followSymlink=True) # mountPoint/mountInFolder if mountPoint is not None and mountInFolder is not None: raise ValueError('mountImage can only be called with mountPoint or mountInFolder, not both') elif mountPoint is not None and os.path.ismount(mountPoint): raise ValueError('mountImage called with a mountPoint that is already a mount point: ' + mountPoint) elif mountPoint is not None and os.path.isdir(mountPoint): if len(os.listdir(mountPoint)) != 0: raise ValueError('mountImage called with a mountPoint that already has contents: %s (%s)' % (mountPoint, str(os.listdir(mountPoint)))) elif mountPoint is not None and os.path.exists(mountPoint): raise ValueError('mountImage called with a mountPoint that already exists and is not a folder: ' + mountPoint) elif mountPoint is not None: tempFolderManager.add elif mountInFolder is not None and not os.path.isdir(mountInFolder): raise ValueError('mountImage called with a mountInFolder path that is not a folder: ' + mountInFolder) elif mountInFolder is not None: mountPoint = tempFolderManager.getNewMountPoint() else: # create a default mount point mountPoint = tempFolderManager.getNewMountPoint() mountPoint = pathHelpers.normalizePath(mountPoint, followSymlink=True) # mountReadWrite if mountReadWrite not in [None, True, False]: raise ValueError('The only valid options for mountReadWrite are True or False') # shadowFile if shadowFile is True: # generate a temporary one shadowFile = tempFolderManager.getNewTempFile(suffix='.shadow') elif shadowFile is not None: shadowFile = pathHelpers.normalizePath(shadowFile, followSymlink=True) if os.path.isfile(shadowFile): # work here pass # just use the file elif os.path.isdir(shadowFile): # a directory to put the shadow file in shadowFile = tempFolderManager.getNewTempFile(parentFolder=shadowFile, suffix='.shadow') elif os.path.isdir(os.path.dirname(shadowFile)): # the path does not exist, but the directory it is in looks good pass else: # not valid raise ValueError('The path given for the shadow file does not look valid: ' + str(shadowFile)) # paranoidMode if paranoidMode not in [True, False]: raise ValueError('checksumImage must be either True, or False. Got: ' + str(paranoidMode)) # -- check to see if it is already mounted existingMountPoints = myClass.getDMGMountPoints(dmgFile) if existingMountPoints is not None: raise RuntimeError('The image (%s) was already mounted: %s' % (dmgFile, ", ".join(existingMountPoints))) # -- construct the command command = ['/usr/bin/hdiutil', 'attach', dmgFile, '-plist', '-mountpoint', mountPoint, '-nobrowse', '-owners', 'on'] if mountReadWrite is True and shadowFile is None and self.writeable is False: shadowFile = tempFolderManager.getNewTempFile(suffix='.shadow') elif mountReadWrite is False and shadowFile is None: command += ['-readonly'] if paranoidMode is False: command += ['-noverify', '-noautofsck'] else: command += ['-verify', '-autofsck'] if shadowFile is not None: command += ['-shadow', shadowFile] # -- run the command process = managedSubprocess(command, processAsPlist=True) mountInfo = process.getPlistObject() actualMountedPath = None for thisEntry in mountInfo['system-entities']: if 'mount-point' in thisEntry: if actualMountedPath != None and os.path.ismount(actualMountedPath): # ToDo: think all of this through, prefereabley before it is needed raise Exception('This item (%s) seems to be mounted at two places , this is possible, but now that it is happening larkost needs to work on this' % self.filePath) actualMountedPath = thisEntry['mount-point'] # ToDo: make sure that this is the mount point we requested break # assume that there is only one mountable item... otherwise we are hosed already if actualMountedPath is None: raise RuntimeError('Error: image could not be mounted') return actualMountedPath
def getVolumeInfo(myClass, identifier): '''Get the following information for a volume (if avalible) and return it as a hash: filePath, dmgFormat, writeable, dmg-checksum-type, dmg-checksum-value Additionally, provide information from the superclass if it is mounted: volumeName, mount-points, bsd-label ''' if not hasattr(identifier, 'capitalize'): raise ValueError('getVolumeInfo requires a path, bsd name, or a dev path. Got: ' + str(identifier)) result = None # look to see if this is a mount path, bsd name, or a dev path that diskutil can work with try: result = super(self.__class__, self).getVolumeInfo(identifier) if result['diskType'] is not 'Disk Image': raise ValueError("%s's getVolumeInfo method requires a disk image as the argument. Got a %s as the argument: %s" % (myClass.__name__, result['diskType'], identifier)) # if we are here, then the identifier must be the mounted path, or something in it identifier = result['mountPath'] except ValueError: # this might be a pointer to the dmg file result = {} # try for information on this as a dmg command = ['/usr/bin/hdiutil', 'imageinfo', '-plist', str(identifier)] try: process = managedSubprocess(command, processAsPlist=True) except RuntimeError: raise ValueError('The item given does not seem to be a DMG: ' + str(identifier)) dmgProperties = process.getPlistObject() # filePath result['filePath'] = dmgProperties['Backing Store Information']['URL'] if result['filePath'].startswith('file://localhost'): result['filePath'] = result['filePath'][len('file://localhost'):] elif result['filePath'].startswith('file://'): result['filePath'] = result['filePath'][len('file://'):] # dmgFormat result['dmgFormat'] = dmgProperties['Format'] # writable if dmgProperties['Format'] in ['UDRW', 'UDSP', 'UDSB', 'RdWr']: result['writeable'] = True else: result['writeable'] = False # dmg-checksum-type if 'Checksum Type' in dmgProperties: result['dmgChecksumType'] = dmgProperties['Checksum Type'] else: result['dmgChecksumType'] = None # just in case we ever re-do this # dmg-checksum-value if 'Checksum Value' in dmgProperties: result['dmgChecksumValue'] = dmgProperties['Checksum Value'] else: result['dmgChecksumValue'] = None # just in case we ever re-do this return result
def isValidInstaller(myClass, pkgPath, chrootPath=None, installerChoicesFilePath=None): '''Use installer to check if a target looks like a valid pkg/mpkg''' pathToInstaller = '/usr/sbin/installer' # ---- validate and normalize input # pkg path if pkgPath is None: raise ValueError('The pkgPath given to isValidInstaller can not be none') elif not os.path.isdir(pkgPath) and not os.path.isfile(pkgPath): raise ValueError('The pkgPath given to isValidInstaller does not look correct: ' + str(pkgPath)) pkgPath = pathHelpers.normalizePath(pkgPath, followSymlink=True) # chroot path if chrootPath is not None and not os.path.ismount(chrootPath): raise ValueError('The chrootPath given to isValidInstaller must be a mount point, this was not: ' + str(chrootPath)) if chrootPath is not None: chrootPath = pathHelpers.normalizePath(chrootPath, followSymlink=True) # installer choices if installerChoicesFilePath is not None and not os.path.isfile(installerChoicesFilePath): raise ValueError('The installerChoicesFilePath given to isValidInstaller must be a file, this was not: ' + str(installerChoicesFilePath)) installerChoicesFilePath = pathHelpers.normalizePath(installerChoicesFilePath, followSymlink=True) # ---- validate that the installer command is avalible if chrootPath is None and not os.access(pathToInstaller, os.F_OK | os.X_OK): raise RuntimeError('The installer command was not avalible where it was expected to be, or was not useable: ' + pathToInstaller) elif chrootPath is not None and not os.access(os.path.join(chrootPath, pathToInstaller[1:]), os.F_OK | os.X_OK): raise RuntimeError('The installer command was not avalible where it was expected to be, or was not useable: ' + os.path.join(chrootPath, pathToInstaller[1:])) # ---- build the command command = [] if chrootPath is not None: command += ['/usr/sbin/chroot', chrootPath] command += [pathToInstaller, '-pkginfo'] if installerChoicesFilePath is not None: command += ['-applyChoiceChangesXML', installerChoicesFilePath] command += ['-pkg', pkgPath, '-target', '.'] # ---- run the command process = None try: if chrootPath is None: process = managedSubprocess(command) else: process = managedSubprocess(command, cwd=chrootPath) except: return False # if installer did not have a problem, then it is very probably good return True