def _find_loopback(self, use_loopback=True, var_name='loopback'): """Finds a free loopback device that can be used. The loopback is stored in :attr:`loopback`. If *use_loopback* is True, the loopback will also be used directly. :returns: the loopback address :raises NoLoopbackAvailableError: if no loopback could be found """ # noinspection PyBroadException try: loopback = _util.check_output_(['losetup', '-f']).strip() setattr(self, var_name, loopback) except Exception: logger.warning("No free loopback device found.", exc_info=True) raise NoLoopbackAvailableError() # noinspection PyBroadException if use_loopback: try: cmd = [ 'losetup', '-o', str(self.offset), '--sizelimit', str(self.size), loopback, self.get_raw_path() ] if not self.disk.read_write: cmd.insert(1, '-r') _util.check_call_(cmd, stdout=subprocess.PIPE) except Exception: logger.exception("Loopback device could not be mounted.") raise NoLoopbackAvailableError() return loopback
def _find_loopback(self, use_loopback=True, var_name='loopback'): """Finds a free loopback device that can be used. The loopback is stored in :attr:`loopback`. If *use_loopback* is True, the loopback will also be used directly. :return: boolean indicating whether a loopback device was found """ # noinspection PyBroadException try: loopback = _util.check_output_(['losetup', '-f']).strip() setattr(self, var_name, loopback) except Exception: logger.warning("No free loopback device found.", exc_info=True) return False # noinspection PyBroadException if use_loopback: try: cmd = ['losetup', '-o', str(self.offset), '--sizelimit', str(self.size), loopback, self.get_raw_base_path()] if not self.disk.read_write: cmd.insert(1, '-r') _util.check_call_(cmd, stdout=subprocess.PIPE) except Exception as e: logger.exception("Loopback device could not be mounted.") return False return True
def mount(self, volume): """Performs mount actions on a LVM. Scans for active volume groups from the loopback device, activates it and fills :attr:`volumes` with the logical volumes. :raises NoLoopbackAvailableError: when no loopback was available :raises IncorrectFilesystemError: when the volume is not a volume group """ os.environ['LVM_SUPPRESS_FD_WARNINGS'] = '1' # find free loopback device volume._find_loopback() time.sleep(0.2) try: # Scan for new lvm volumes result = _util.check_output_(["lvm", "pvscan"]) for l in result.splitlines(): if volume.loopback in l or (volume.offset == 0 and volume.get_raw_path() in l): for vg in re.findall(r'VG (\S+)', l): volume.info['volume_group'] = vg if not volume.info.get('volume_group'): logger.warning("Volume is not a volume group. (Searching for %s)", volume.loopback) raise IncorrectFilesystemError() # Enable lvm volumes _util.check_call_(["lvm", "vgchange", "-a", "y", volume.info['volume_group']], stdout=subprocess.PIPE) except Exception: volume._free_loopback() raise volume.volumes.vstype = 'lvm' # fills it up. for _ in volume.volumes.detect_volumes('lvm'): pass
def _open_lvm(self): """Performs mount actions on a LVM. Scans for active volume groups from the loopback device, activates it and fills :attr:`volumes` with the logical volumes. :raises NoLoopbackAvailableError: when no loopback was available :raises IncorrectFilesystemError: when the volume is not a volume group """ os.environ['LVM_SUPPRESS_FD_WARNINGS'] = '1' # find free loopback device self._find_loopback() time.sleep(0.2) # Scan for new lvm volumes result = _util.check_output_(["lvm", "pvscan"]) for l in result.splitlines(): if self.loopback in l or (self.offset == 0 and self.get_raw_path() in l): for vg in re.findall(r'VG (\S+)', l): self.info['volume_group'] = vg if not self.info.get('volume_group'): logger.warning("Volume is not a volume group. (Searching for %s)", self.loopback) raise IncorrectFilesystemError() # Enable lvm volumes _util.check_call_(["lvm", "vgchange", "-a", "y", self.info['volume_group']], stdout=subprocess.PIPE)
def unmount(self, allow_lazy=False): super().unmount(allow_lazy=allow_lazy) if self.loopback is not None: _util.check_call_(['losetup', '-d', self.loopback], wrap_error=True) self.loopback = None
def _free_loopback(self): if self.loopback is not None: try: _util.check_call_(['losetup', '-d', self.loopback], wrap_error=True) except Exception: pass # TODO
def unmount(self, allow_lazy=False): if self.luks_name is not None: _util.check_call_(['cryptsetup', 'luksClose', self.luks_name], wrap_error=True, stdout=subprocess.PIPE) self.luks_name = None super().unmount(allow_lazy=allow_lazy)
def mount(self, volume): volume._make_mountpoint() volume._find_loopback() try: _util.check_call_(['vmfs-fuse', volume.loopback, volume.mountpoint], stdout=subprocess.PIPE) except Exception: volume._free_loopback() volume._clear_mountpoint() raise
def mount(self): self._make_mountpoint() self._find_loopback() try: _util.check_call_(['vmfs-fuse', self.loopback, self.mountpoint], stdout=subprocess.PIPE) except Exception: self._free_loopback() self._clear_mountpoint() raise
def mount(self, volume): volume._make_mountpoint() volume._find_loopback() try: _util.check_call_( ['vmfs-fuse', volume.loopback, volume.mountpoint], stdout=subprocess.PIPE) except Exception: volume._free_loopback() volume._clear_mountpoint() raise
def mount(self, volume): """Mounts a BDE container. Uses key material provided by the :attr:`keys` attribute. The key material should be provided in the same format as to :cmd:`bdemount`, used as follows: k:full volume encryption and tweak key p:passphrase r:recovery password s:file to startup key (.bek) :return: the Volume contained in the BDE container :raises ArgumentError: if the keys argument is invalid :raises SubsystemError: when the underlying command fails """ volume._paths['bde'] = tempfile.mkdtemp(prefix='image_mounter_bde_') try: if volume.key: t, v = volume.key.split(':', 1) key = ['-' + t, v] else: logger.warning("No key material provided for %s", volume) key = [] except ValueError: logger.exception( "Invalid key material provided (%s) for %s. Expecting [arg]:[value]", volume.key, volume) raise ArgumentError() # noinspection PyBroadException try: cmd = [ "bdemount", volume.get_raw_path(), volume._paths['bde'], '-o', str(volume.offset) ] cmd.extend(key) _util.check_call_(cmd) except Exception as e: del volume._paths['bde'] logger.exception("Failed mounting BDE volume %s.", volume) raise SubsystemError(e) container = volume.volumes._make_single_subvolume(flag='alloc', offset=0, size=volume.size) container.info['fsdescription'] = 'BDE Volume' return container
def bindmount(self, mountpoint): """Bind mounts the volume to another mountpoint. Only works if the volume is already mounted. Note that only the last bindmountpoint is remembered and cleaned. :return: bool indicating whether the bindmount succeeded """ if not self.mountpoint: return False try: self.bindmountpoint = mountpoint _util.check_call_(['mount', '--bind', self.mountpoint, self.bindmountpoint], stdout=subprocess.PIPE) return True except Exception as e: self.bindmountpoint = "" logger.exception("Error bind mounting {0}.".format(self)) return False
def find_lvm_volumes(self, force=False): """Performs post-mount actions on a LVM. Scans for active volume groups from the loopback device, activates it and fills :attr:`volumes` with the logical volumes. If *force* is true, the LVM detection is ran even when the LVM is not mounted on a loopback device. """ if not self.loopback and not force: return [] # Scan for new lvm volumes result = _util.check_output_(["lvm", "pvscan"]) for l in result.splitlines(): if self.loopback in l or (self.offset == 0 and self.get_raw_base_path() in l): for vg in re.findall(r'VG (\S+)', l): self.volume_group = vg if not self.volume_group: logger.warning("Volume is not a volume group.") return [] # Enable lvm volumes _util.check_call_(["vgchange", "-a", "y", self.volume_group], stdout=subprocess.PIPE) # Gather information about lvolumes, gathering their label, size and raw path result = _util.check_output_(["lvdisplay", self.volume_group]) for l in result.splitlines(): if "--- Logical volume ---" in l: self.volumes.append(Volume(disk=self.disk, stats=self.stats, fsforce=self.fsforce, fsfallback=self.fsfallback, fstypes=self.fstypes, pretty=self.pretty, mountdir=self.mountdir)) self.volumes[-1].index = "{0}.{1}".format(self.index, len(self.volumes) - 1) self.volumes[-1].fsdescription = 'Logical Volume' self.volumes[-1].flag = 'alloc' self.volumes[-1].parent = self if "LV Name" in l: self.volumes[-1].label = l.replace("LV Name", "").strip() if "LV Size" in l: self.volumes[-1].size = l.replace("LV Size", "").strip() if "LV Path" in l: self.volumes[-1].lv_path = l.replace("LV Path", "").strip() self.volumes[-1].offset = 0 logger.info("{0} volumes found".format(len(self.volumes))) return self.volumes
def detect_volume_shadow_copies(self): """Method to call vshadowmount and mount NTFS volume shadow copies. :return: iterable with the :class:`Volume` objects of the VSS :raises CommandNotFoundError: if the underlying command does not exist :raises SubSystemError: if the underlying command fails :raises NoMountpointAvailableError: if there is no mountpoint available """ self._paths['vss'] = self._make_mountpoint(suffix="vss") try: _util.check_call_(["vshadowmount", "-o", str(self.offset), self.get_raw_path(), self._paths['vss']]) except Exception as e: logger.exception("Failed mounting the volume shadow copies.") raise SubsystemError(e) else: return self.volumes.detect_volumes(vstype='vss')
def _mount_avfs(self): """Mounts the AVFS filesystem.""" self._paths["avfs"] = tempfile.mkdtemp(prefix="image_mounter_avfs_") # start by calling the mountavfs command to initialize avfs _util.check_call_(["avfsd", self._paths["avfs"], "-o", "allow_other"], stdout=subprocess.PIPE) # no multifile support for avfs avfspath = self._paths["avfs"] + "/" + os.path.abspath(self.paths[0]) + "#" targetraw = os.path.join(self.mountpoint, "avfs") os.symlink(avfspath, targetraw) logger.debug("Symlinked {} with {}".format(avfspath, targetraw)) raw_path = self.get_raw_path() logger.debug("Raw path to avfs is {}".format(raw_path)) if raw_path is None: raise MountpointEmptyError()
def _mount_avfs(self): """Mounts the AVFS filesystem.""" self._paths['avfs'] = tempfile.mkdtemp(prefix='image_mounter_avfs_') # start by calling the mountavfs command to initialize avfs _util.check_call_(['avfsd', self._paths['avfs'], '-o', 'allow_other'], stdout=subprocess.PIPE) # no multifile support for avfs avfspath = self._paths['avfs'] + '/' + os.path.abspath(self.paths[0]) + '#' targetraw = os.path.join(self.mountpoint, 'avfs') os.symlink(avfspath, targetraw) logger.debug("Symlinked {} with {}".format(avfspath, targetraw)) raw_path = self.get_raw_path() logger.debug("Raw path to avfs is {}".format(raw_path)) if raw_path is None: raise MountpointEmptyError()
def detect_volume_shadow_copies(self): """Method to call vshadowmount and mount NTFS volume shadow copies. :return: iterable with the :class:`Volume` objects of the VSS :raises CommandNotFoundError: if the underlying command does not exist :raises SubSystemError: if the underlying command fails :raises NoMountpointAvailableError: if there is no mountpoint available """ self._make_mountpoint(var_name='vss', suffix="vss", in_paths=True) try: _util.check_call_(["vshadowmount", "-o", str(self.offset), self.get_raw_path(), self._paths['vss']]) except Exception as e: logger.exception("Failed mounting the volume shadow copies.") raise SubsystemError(e) else: return self.volumes.detect_volumes(vstype='vss')
def bindmount(self, mountpoint): """Bind mounts the volume to another mountpoint. Only works if the volume is already mounted. :raises NotMountedError: when the volume is not yet mounted :raises SubsystemError: when the underlying command failed """ if not self.mountpoint: raise NotMountedError(self) try: _util.check_call_(['mount', '--bind', self.mountpoint, mountpoint], stdout=subprocess.PIPE) if 'bindmounts' in self._paths: self._paths['bindmounts'].append(mountpoint) else: self._paths['bindmounts'] = [mountpoint] return True except Exception as e: logger.exception("Error bind mounting {0}.".format(self)) raise SubsystemError(e)
def bindmount(self, mountpoint): """Bind mounts the volume to another mountpoint. Only works if the volume is already mounted. :raises NotMountedError: when the volume is not yet mounted :raises SubsystemError: when the underlying command failed """ if not self.mountpoint: raise NotMountedError() try: _util.check_call_(['mount', '--bind', self.mountpoint, mountpoint], stdout=subprocess.PIPE) if 'bindmounts' in self._paths: self._paths['bindmounts'].append(mountpoint) else: self._paths['bindmounts'] = [mountpoint] return True except Exception as e: logger.exception("Error bind mounting {0}.".format(self)) raise SubsystemError(e)
def add_to_raid(self): """Adds the disk to a central RAID volume. This function will first test whether it is actually a RAID volume by using :func:`is_raid` and, if so, will add the disk to the array via a loopback device. :return: whether the addition succeeded""" if not self.is_raid(): return False # find free loopback device # noinspection PyBroadException try: self.loopback = _util.check_output_(['losetup', '-f']).strip() except Exception: logger.warning("No free loopback device found for RAID", exc_info=True) return False # mount image as loopback cmd = ['losetup', '-o', str(self.offset), self.loopback, self.get_raw_path()] if not self.read_write: cmd.insert(1, '-r') try: _util.check_call_(cmd, stdout=subprocess.PIPE) except Exception: logger.exception("Failed mounting image to loopback") return False try: # use mdadm to mount the loopback to a md device # incremental and run as soon as available output = _util.check_output_(['mdadm', '-IR', self.loopback], stderr=subprocess.STDOUT) match = re.findall(r"attached to ([^ ,]+)", output) if match: self.md_device = os.path.realpath(match[0]) logger.info("Mounted RAID to {0}".format(self.md_device)) except Exception as e: logger.exception("Failed mounting RAID.") return False return True
def mount(self, volume): """Mounts a BDE container. Uses key material provided by the :attr:`keys` attribute. The key material should be provided in the same format as to :cmd:`bdemount`, used as follows: k:full volume encryption and tweak key p:passphrase r:recovery password s:file to startup key (.bek) :return: the Volume contained in the BDE container :raises ArgumentError: if the keys argument is invalid :raises SubsystemError: when the underlying command fails """ volume._paths['bde'] = tempfile.mkdtemp(prefix='image_mounter_bde_') try: if volume.key: t, v = volume.key.split(':', 1) key = ['-' + t, v] else: logger.warning("No key material provided for %s", volume) key = [] except ValueError: logger.exception("Invalid key material provided (%s) for %s. Expecting [arg]:[value]", volume.key, volume) raise ArgumentError() # noinspection PyBroadException try: cmd = ["bdemount", volume.get_raw_path(), volume._paths['bde'], '-o', str(volume.offset)] cmd.extend(key) _util.check_call_(cmd) except Exception as e: del volume._paths['bde'] logger.exception("Failed mounting BDE volume %s.", volume) raise SubsystemError(e) container = volume.volumes._make_single_subvolume(flag='alloc', offset=0, size=volume.size) container.info['fsdescription'] = 'BDE Volume' return container
def vshadowmount(self): """Method to call vshadowmount and mount NTFS volume shadow copies. :return: string representing the path to the volume shadow copies :raises CommandNotFoundError: if the underlying command does not exist :raises SubSystemError: if the underlying command fails :raises NoMountpointAvailableError: if there is no mountpoint available """ if not _util.command_exists('vshadowmount'): logger.warning("vshadowmount is not installed, could not mount volume shadow copies") raise CommandNotFoundError('vshadowmount') self._make_mountpoint(var_name='vss', suffix="vss", in_paths=True) try: _util.check_call_(["vshadowmount", "-o", str(self.offset), self.get_raw_path(), self._paths['vss']]) return self._paths['vss'] except Exception as e: logger.exception("Failed mounting the volume shadow copies.") raise SubsystemError(e)
def unmount(self, remove_rw=False): """Removes all ties of this disk to the filesystem, so the image can be unmounted successfully. Warning: this method will destruct the entire RAID array in which this disk takes part. """ for m in list(sorted(self.volumes, key=lambda v: v.mountpoint or "", reverse=True)): if not m.unmount(): logger.warning("Error unmounting volume {0}".format(m.mountpoint)) # TODO: remove specific device from raid array if self.md_device: # Removes the RAID device first. Actually, we should be able to remove the devices from the array separately, # but whatever. try: _util.check_call_(['mdadm', '-S', self.md_device], stdout=subprocess.PIPE, stderr=subprocess.PIPE) self.md_device = None except Exception as e: logger.warning("Failed unmounting MD device {0}".format(self.md_device), exc_info=True) if self.loopback: # noinspection PyBroadException try: _util.check_call_(['losetup', '-d', self.loopback]) self.loopback = None except Exception: logger.warning("Failed deleting loopback device {0}".format(self.loopback), exc_info=True) if self.mountpoint and not _util.clean_unmount(['fusermount', '-u'], self.mountpoint): logger.error("Error unmounting base {0}".format(self.mountpoint)) return False if self.avfs_mountpoint and not _util.clean_unmount(['fusermount', '-u'], self.avfs_mountpoint): logger.error("Error unmounting AVFS mountpoint {0}".format(self.avfs_mountpoint)) return False if self.rw_active() and remove_rw: os.remove(self.rwpath) return True
def carve(self, freespace=True): """Call this method to carve the free space of the volume for (deleted) files. Note that photorec has its own interface that temporarily takes over the shell. :param freespace: indicates whether the entire volume should be carved (False) or only the free space (True) :type freespace: bool :return: string to the path where carved data is available :raises CommandNotFoundError: if the underlying command does not exist :raises SubsystemError: if the underlying command fails :raises NoMountpointAvailableError: if there is no mountpoint available :raises NoLoopbackAvailableError: if there is no loopback available (only when volume has no slot number) """ self._make_mountpoint(var_name='carve', suffix="carve", in_paths=True) # if no slot, we need to make a loopback that we can use to carve the volume loopback_was_created_for_carving = False if not self.slot: if not self.loopback: self._find_loopback() # Can't carve if volume has no slot number and can't be mounted on loopback. loopback_was_created_for_carving = True # noinspection PyBroadException try: _util.check_call_([ "photorec", "/d", self._paths['carve'] + os.sep, "/cmd", self.loopback, ("freespace," if freespace else "") + "search" ]) # clean out the loop device if we created it specifically for carving if loopback_was_created_for_carving: # noinspection PyBroadException try: _util.check_call_(['losetup', '-d', self.loopback]) except Exception: pass else: self.loopback = "" return self._paths['carve'] except Exception as e: logger.exception("Failed carving the volume.") raise SubsystemError(e) else: # noinspection PyBroadException try: _util.check_call_([ "photorec", "/d", self._paths['carve'] + os.sep, "/cmd", self.get_raw_path(), str(self.slot) + (",freespace" if freespace else "") + ",search" ]) return self._paths['carve'] except Exception as e: logger.exception("Failed carving the volume.") raise SubsystemError(e)
def carve(self, freespace=True): """Call this method to carve the free space of the volume for (deleted) files. Note that photorec has its own interface that temporarily takes over the shell. :param freespace: indicates whether the entire volume should be carved (False) or only the free space (True) :type freespace: bool :return: string to the path where carved data is available :raises CommandNotFoundError: if the underlying command does not exist :raises SubsystemError: if the underlying command fails :raises NoMountpointAvailableError: if there is no mountpoint available :raises NoLoopbackAvailableError: if there is no loopback available (only when volume has no slot number) """ if not _util.command_exists('photorec'): logger.warning("photorec is not installed, could not carve volume") raise CommandNotFoundError("photorec") self._make_mountpoint(var_name='carve', suffix="carve", in_paths=True) # if no slot, we need to make a loopback that we can use to carve the volume loopback_was_created_for_carving = False if not self.slot: if not self.loopback: self._find_loopback() #Can't carve if volume has no slot number and can't be mounted on loopback. loopback_was_created_for_carving = True # noinspection PyBroadException try: _util.check_call_(["photorec", "/d", self._paths['carve'] + os.sep, "/cmd", self.loopback, ("freespace," if freespace else "") + "search"]) # clean out the loop device if we created it specifically for carving if loopback_was_created_for_carving: # noinspection PyBroadException try: _util.check_call_(['losetup', '-d', self.loopback]) except Exception: pass else: self.loopback = "" return self._paths['carve'] except Exception as e: logger.exception("Failed carving the volume.") raise SubsystemError(e) else: # noinspection PyBroadException try: _util.check_call_(["photorec", "/d", self._paths['carve'] + os.sep, "/cmd", self.get_raw_path(), str(self.slot) + (",freespace" if freespace else "") + ",search"]) return self._paths['carve'] except Exception as e: logger.exception("Failed carving the volume.") raise SubsystemError(e)
def unmount(self): """Unounts the volume from the filesystem.""" for volume in self.volumes: volume.unmount() if self.loopback and self.volume_group: try: _util.check_call_(['vgchange', '-a', 'n', self.volume_group], stdout=subprocess.PIPE) except Exception: return False self.volume_group = "" if self.loopback and self.luks_path: try: _util.check_call_(['cryptsetup', 'luksClose', self.luks_path], stdout=subprocess.PIPE) except Exception: return False self.luks_path = "" if self.loopback: try: _util.check_call_(['losetup', '-d', self.loopback]) except Exception: return False self.loopback = "" if self.bindmountpoint: if not _util.clean_unmount(['umount'], self.bindmountpoint, rmdir=False): return False self.bindmountpoint = "" if self.mountpoint: if not _util.clean_unmount(['umount'], self.mountpoint): return False self.mountpoint = "" if self.carvepoint: try: shutil.rmtree(self.carvepoint) except OSError: return False else: self.carvepoint = "" return True
def carve(self, freespace=True): """Call this method to carve the free space of the volume for (deleted) files. Note that photorec has its own interface that temporarily takes over the shell. :param freespace: indicates whether the entire volume should be carved (False) or only the free space (True) :type freespace: bool :return: boolean indicating whether the command succeeded """ if not _util.command_exists('photorec'): logger.warning("photorec is not installed, could not carve volume") return False if not self._make_mountpoint(var_name='carvepoint', suffix="carve"): return False # if no slot, we need to make a loopback that we can use to carve the volume loopback_was_created_for_carving = False if not self.slot: if not self.loopback: if not self._find_loopback(): logger.error("Can't carve if volume has no slot number and can't be mounted on loopback.") return False loopback_was_created_for_carving = True try: _util.check_call_(["photorec", "/d", self.carvepoint + os.sep, "/cmd", self.loopback, ("freespace," if freespace else "") + "search"]) # clean out the loop device if we created it specifically for carving if loopback_was_created_for_carving: try: _util.check_call_(['losetup', '-d', self.loopback]) except Exception: pass else: self.loopback = "" return True except Exception: logger.exception("Failed carving the volume.") return False else: try: _util.check_call_(["photorec", "/d", self.carvepoint + os.sep, "/cmd", self.get_raw_base_path(), str(self.slot) + (",freespace" if freespace else "") + ",search"]) return True except Exception: logger.exception("Failed carving the volume.") return False
def open_jffs2(self): """Perform specific operations to mount a JFFS2 image. This kind of image is sometimes used for things like bios images. so external tools are required but given this method you don't have to memorize anything and it works fast and easy. Note that this module might not yet work while mounting multiple images at the same time. """ # we have to make a ram-device to store the image, we keep 20% overhead size_in_kb = int((self.size / 1024) * 1.2) _util.check_call_(['modprobe', '-v', 'mtd']) _util.check_call_(['modprobe', '-v', 'jffs2']) _util.check_call_(['modprobe', '-v', 'mtdram', 'total_size={}'.format(size_in_kb), 'erase_size=256']) _util.check_call_(['modprobe', '-v', 'mtdblock']) _util.check_call_(['dd', 'if=' + self.get_raw_base_path(), 'of=/dev/mtd0']) _util.check_call_(['mount', '-t', 'jffs2', '/dev/mtdblock0', self.mountpoint]) return True
def open_luks_container(self): """Command that is an alternative to the :func:`mount` command that opens a LUKS container. The opened volume is added to the subvolume set of this volume. Requires the user to enter the key manually. :return: the Volume contained in the LUKS container, or None on failure. """ # Open a loopback device if not self._find_loopback(): return None # Check if this is a LUKS device # noinspection PyBroadException try: _util.check_call_(["cryptsetup", "isLuks", self.loopback], stderr=subprocess.STDOUT) # ret = 0 if isLuks except Exception: logger.warning("Not a LUKS volume") # clean the loopback device, we want this method to be clean as possible # noinspection PyBroadException try: _util.check_call_(['losetup', '-d', self.loopback]) self.loopback = "" except Exception: pass return None # Open the LUKS container self.luks_path = 'image_mounter_' + str(random.randint(10000, 99999)) # noinspection PyBroadException try: cmd = ["cryptsetup", "luksOpen", self.loopback, self.luks_path] if not self.disk.read_write: cmd.insert(1, '-r') _util.check_call_(cmd) except Exception: self.luks_path = "" return None size = None # noinspection PyBroadException try: result = _util.check_output_(["cryptsetup", "status", self.luks_path]) for l in result.splitlines(): if "size:" in l and "key" not in l: size = int(l.replace("size:", "").replace("sectors", "").strip()) * self.disk.block_size except Exception: pass container = Volume(disk=self.disk, stats=self.stats, fsforce=self.fsforce, fsfallback=self.fsfallback, fstypes=self.fstypes, pretty=self.pretty, mountdir=self.mountdir) container.index = "{0}.0".format(self.index) container.fsdescription = 'LUKS container' container.flag = 'alloc' container.parent = self container.offset = 0 container.size = size self.volumes.append(container) return container
def mount(self): """Mounts the base image on a temporary location using the mount method stored in :attr:`method`. If mounting was successful, :attr:`mountpoint` is set to the temporary mountpoint. If :attr:`read_write` is enabled, a temporary read-write cache is also created and stored in :attr:`rwpath`. :return: whether the mounting was successful :rtype: bool """ if self.parser.casename: self.mountpoint = tempfile.mkdtemp(prefix='image_mounter_', suffix='_' + self.parser.casename) else: self.mountpoint = tempfile.mkdtemp(prefix='image_mounter_') if self.read_write: self.rwpath = tempfile.mkstemp(prefix="image_mounter_rw_cache_")[1] disk_type = self.get_disk_type() methods = self._get_mount_methods(disk_type) cmds = [] for method in methods: if method == 'avfs': # avfs does not participate in the fallback stuff, unfortunately self._mount_avfs() self.disk_mounter = method self.was_mounted = True self.is_mounted = True return elif method == 'dummy': os.rmdir(self.mountpoint) self.mountpoint = "" logger.debug("Raw path to dummy is {}".format(self.get_raw_path())) self.disk_mounter = method self.was_mounted = True self.is_mounted = True return elif method == 'xmount': cmds.append(['xmount', ]) if self.read_write: cmds[-1].extend(['--cache', self.rwpath]) cmds[-1].extend(['--in', 'ewf' if disk_type == 'encase' else 'dd']) cmds[-1].extend(self.paths) # specify all paths, xmount needs this :( cmds[-1].append(self.mountpoint) elif method == 'affuse': cmds.extend([['affuse', '-o', 'allow_other', self.paths[0], self.mountpoint], ['affuse', self.paths[0], self.mountpoint]]) elif method == 'ewfmount': cmds.extend([['ewfmount', '-X', 'allow_other', self.paths[0], self.mountpoint], ['ewfmount', self.paths[0], self.mountpoint]]) elif method == 'vmware-mount': cmds.append(['vmware-mount', '-r', '-f', self.paths[0], self.mountpoint]) elif method == 'qemu-nbd': _util.check_output_(['modprobe', 'nbd', 'max_part=63']) # Load nbd driver try: self._paths['nbd'] = _util.get_free_nbd_device() # Get free nbd device except NoNetworkBlockAvailableError: logger.warning("No free network block device found.", exc_info=True) raise cmds.extend([['qemu-nbd', '--read-only', '-c', self._paths['nbd'], self.paths[0]]]) else: raise ArgumentError("Unknown mount method {0}".format(self.disk_mounter)) for cmd in cmds: # noinspection PyBroadException try: _util.check_call_(cmd, stdout=subprocess.PIPE) # mounting does not seem to be instant, add a timer here time.sleep(.1) except Exception: logger.warning('Could not mount {0}, trying other method'.format(self.paths[0]), exc_info=True) continue else: raw_path = self.get_raw_path() logger.debug("Raw path to disk is {}".format(raw_path)) self.disk_mounter = cmd[0] if raw_path is None: raise MountpointEmptyError() self.was_mounted = True self.is_mounted = True return logger.error('Unable to mount {0}'.format(self.paths[0])) os.rmdir(self.mountpoint) self.mountpoint = "" raise MountError()
def mount(self): """Based on the file system type as determined by :func:`determine_fs_type`, the proper mount command is executed for this volume. The volume is mounted in a temporary path (or a pretty path if :attr:`pretty` is enabled) in the mountpoint as specified by :attr:`mountpoint`. If the file system type is a LUKS container, :func:`open_luks_container` is called only. If it is a LVM volume, :func:`find_lvm_volumes` is called after the LVM has been mounted. Both methods will add subvolumes to :attr:`volumes` :return: boolean indicating whether the mount succeeded """ raw_path = self.get_raw_base_path() self.determine_fs_type() # we need a mountpoint if it is not a lvm or luks volume if self.fstype not in ('luks', 'lvm') and self.fstype in FILE_SYSTEM_TYPES and not self._make_mountpoint(): return False # Prepare mount command try: if self.fstype == 'ext': # ext cmd = ['mount', raw_path, self.mountpoint, '-t', 'ext4', '-o', 'loop,noexec,noload,offset=' + str(self.offset)] if not self.disk.read_write: cmd[-1] += ',ro' _util.check_call_(cmd, stdout=subprocess.PIPE) elif self.fstype == 'ufs': # ufs # mount -t ufs -o ufstype=ufs2,loop,ro,offset=4294967296 /tmp/image/ewf1 /media/a cmd = ['mount', raw_path, self.mountpoint, '-t', 'ufs', '-o', 'ufstype=ufs2,loop,offset=' + str(self.offset)] if not self.disk.read_write: cmd[-1] += ',ro' _util.check_call_(cmd, stdout=subprocess.PIPE) elif self.fstype == 'ntfs': # NTFS cmd = ['mount', raw_path, self.mountpoint, '-t', 'ntfs', '-o', 'loop,show_sys_files,noexec,force,offset=' + str(self.offset)] if not self.disk.read_write: cmd[-1] += ',ro' _util.check_call_(cmd, stdout=subprocess.PIPE) elif self.fstype == 'xfs': # ext cmd = ['mount', raw_path, self.mountpoint, '-t', 'xfs', '-o', 'loop,norecovery,offset=' + str(self.offset)] if not self.disk.read_write: cmd[-1] += ',ro' _util.check_call_(cmd, stdout=subprocess.PIPE) elif self.fstype in ('iso', 'udf', 'squashfs', 'cramfs', 'minix', 'fat'): mnt_type = {'iso': 'iso9660', 'fat': 'vfat'}.get(self.fstype, self.fstype) cmd = ['mount', raw_path, self.mountpoint, '-t', mnt_type, '-o', 'loop,offset=' + str(self.offset)] # not always needed, only to make command generic if not self.disk.read_write: cmd[-1] += ',ro' _util.check_call_(cmd, stdout=subprocess.PIPE) elif self.fstype == 'vmfs': if not self._find_loopback(): return False _util.check_call_(['vmfs-fuse', self.loopback, self.mountpoint], stdout=subprocess.PIPE) elif self.fstype == 'unknown': # mounts without specifying the filesystem type cmd = ['mount', raw_path, self.mountpoint, '-o', 'loop,offset=' + str(self.offset)] if not self.disk.read_write: cmd[-1] += ',ro' _util.check_call_(cmd, stdout=subprocess.PIPE) elif self.fstype == 'jffs2': self.open_jffs2() elif self.fstype == 'luks': self.open_luks_container() elif self.fstype == 'lvm': # LVM os.environ['LVM_SUPPRESS_FD_WARNINGS'] = '1' # find free loopback device if not self._find_loopback(): return False self.find_lvm_volumes() elif self.fstype == 'dir': os.rmdir(self.mountpoint) os.symlink(raw_path, self.mountpoint) else: try: size = self.size / self.disk.block_size except TypeError: size = self.size logger.warning("Unsupported filesystem {0} (type: {1}, block offset: {2}, length: {3})" .format(self, self.fstype, self.offset / self.disk.block_size, size)) return False self.was_mounted = True return True except Exception as e: logger.exception("Execution failed due to {}".format(e), exc_info=True) self.exception = e try: if self.mountpoint: os.rmdir(self.mountpoint) self.mountpoint = "" if self.loopback: self.loopback = "" except Exception as e2: logger.exception("Clean-up failed", exc_info=True) return False
def mount(self, fstype=None): """Based on the file system type as determined by :func:`determine_fs_type`, the proper mount command is executed for this volume. The volume is mounted in a temporary path (or a pretty path if :attr:`pretty` is enabled) in the mountpoint as specified by :attr:`mountpoint`. If the file system type is a LUKS container or LVM, additional methods may be called, adding subvolumes to :attr:`volumes` :raises NotMountedError: if the parent volume/disk is not mounted :raises NoMountpointAvailableError: if no mountpoint was found :raises NoLoopbackAvailableError: if no loopback device was found :raises UnsupportedFilesystemError: if the fstype is not supported for mounting :raises SubsystemError: if one of the underlying commands failed """ if not self.parent.is_mounted: raise NotMountedError(self.parent) raw_path = self.get_raw_path() if fstype is None: fstype = self.determine_fs_type() self._load_fsstat_data() # we need a mountpoint if it is not a lvm or luks volume if fstype not in ('luks', 'lvm', 'bde', 'raid', 'volumesystem') and \ fstype in FILE_SYSTEM_TYPES: self._make_mountpoint() # Prepare mount command try: def call_mount(type, opts): cmd = ['mount', raw_path, self.mountpoint, '-t', type, '-o', opts] if not self.disk.read_write: cmd[-1] += ',ro' _util.check_output_(cmd, stderr=subprocess.STDOUT) if fstype == 'ext': call_mount('ext4', 'noexec,noload,loop,offset=' + str(self.offset)) elif fstype == 'ufs': call_mount('ufs', 'ufstype=ufs2,loop,offset=' + str(self.offset)) elif fstype == 'ntfs': call_mount('ntfs', 'show_sys_files,noexec,force,loop,offset=' + str(self.offset)) elif fstype == 'xfs': call_mount('xfs', 'norecovery,loop,offset=' + str(self.offset)) elif fstype == 'hfs+': call_mount('hfsplus', 'force,loop,offset=' + str(self.offset) + ',sizelimit=' + str(self.size)) elif fstype in ('iso', 'udf', 'squashfs', 'cramfs', 'minix', 'fat', 'hfs'): mnt_type = {'iso': 'iso9660', 'fat': 'vfat'}.get(fstype, fstype) call_mount(mnt_type, 'loop,offset=' + str(self.offset)) elif fstype == 'vmfs': self._find_loopback() _util.check_call_(['vmfs-fuse', self.loopback, self.mountpoint], stdout=subprocess.PIPE) elif fstype == 'unknown': # mounts without specifying the filesystem type cmd = ['mount', raw_path, self.mountpoint, '-o', 'loop,offset=' + str(self.offset)] if not self.disk.read_write: cmd[-1] += ',ro' _util.check_call_(cmd, stdout=subprocess.PIPE) elif fstype == 'jffs2': self._open_jffs2() elif fstype == 'luks': self._open_luks_container() elif fstype == 'bde': self._open_bde_container() elif fstype == 'lvm': self._open_lvm() self.volumes.vstype = 'lvm' for _ in self.volumes.detect_volumes('lvm'): pass elif fstype == 'raid': self._open_raid_volume() elif fstype == 'dir': os.rmdir(self.mountpoint) os.symlink(raw_path, self.mountpoint) elif fstype == 'volumesystem': for _ in self.volumes.detect_volumes(): pass else: try: size = self.size // self.disk.block_size except TypeError: size = self.size logger.warning("Unsupported filesystem {0} (type: {1}, block offset: {2}, length: {3})" .format(self, fstype, self.offset // self.disk.block_size, size)) raise UnsupportedFilesystemError(fstype) self.was_mounted = True self.is_mounted = True self.fstype = fstype except Exception as e: logger.exception("Execution failed due to {} {}".format(type(e), e), exc_info=True) try: if self.mountpoint: os.rmdir(self.mountpoint) self.mountpoint = "" if self.loopback: self.loopback = "" except Exception as e2: logger.exception("Clean-up failed", exc_info=True) if not isinstance(e, ImageMounterError): raise SubsystemError(e) else: raise
def mount(self): """Mounts the base image on a temporary location using the mount method stored in :attr:`method`. If mounting was successful, :attr:`mountpoint` is set to the temporary mountpoint. If :attr:`read_write` is enabled, a temporary read-write cache is also created and stored in :attr:`rwpath`. :return: whether the mounting was successful :rtype: bool """ if self.parser.casename: self.mountpoint = tempfile.mkdtemp(prefix='image_mounter_', suffix='_' + self.parser.casename) else: self.mountpoint = tempfile.mkdtemp(prefix='image_mounter_') if self.read_write: self.rwpath = tempfile.mkstemp(prefix="image_mounter_rw_cache_")[1] # Find the mount methods if self.method == 'auto': methods = [] def add_method_if_exists(method): if (method == 'avfs' and _util.command_exists('avfsd')) or _util.command_exists(method): methods.append(method) if self.read_write: add_method_if_exists('xmount') else: if self.type == 'encase': add_method_if_exists('ewfmount') elif self.type == 'vmdk': add_method_if_exists('vmware-mount') add_method_if_exists('affuse') elif self.type == 'dd': add_method_if_exists('affuse') elif self.type == 'compressed': add_method_if_exists('avfs') add_method_if_exists('xmount') else: methods = [self.method] cmds = [] for method in methods: if method == 'avfs': # avfs does not participate in the fallback stuff, unfortunately self.avfs_mountpoint = tempfile.mkdtemp(prefix='image_mounter_avfs_') # start by calling the mountavfs command to initialize avfs _util.check_call_(['avfsd', self.avfs_mountpoint, '-o', 'allow_other'], stdout=subprocess.PIPE) # no multifile support for avfs avfspath = self.avfs_mountpoint + '/' + os.path.abspath(self.paths[0]) + '#' targetraw = os.path.join(self.mountpoint, 'avfs') os.symlink(avfspath, targetraw) logger.debug("Symlinked {} with {}".format(avfspath, targetraw)) raw_path = self.get_raw_path() logger.debug("Raw path to avfs is {}".format(raw_path)) if self.method == 'auto': self.method = 'avfs' return raw_path is not None elif method == 'xmount': cmds.append(['xmount', '--in', 'ewf' if self.type == 'encase' else 'dd']) if self.read_write: cmds[-1].extend(['--rw', self.rwpath]) elif method == 'affuse': cmds.extend([['affuse', '-o', 'allow_other'], ['affuse']]) elif method == 'ewfmount': cmds.extend([['ewfmount', '-X', 'allow_other'], ['ewfmount']]) elif method == 'vmware-mount': cmds.append(['vmware-mount', '-r', '-f']) elif method == 'dummy': os.rmdir(self.mountpoint) self.mountpoint = "" logger.debug("Raw path to dummy is {}".format(self.get_raw_path())) return True else: raise Exception("Unknown mount method {0}".format(self.method)) # if multifile is enabled, add additional mount methods to the end of it for cmd in cmds[:]: if self.multifile: cmds.append(cmd[:]) cmds[-1].extend(self.paths) cmds[-1].append(self.mountpoint) cmd.append(self.paths[0]) cmd.append(self.mountpoint) for cmd in cmds: # noinspection PyBroadException try: _util.check_call_(cmd, stdout=subprocess.PIPE) # mounting does not seem to be instant, add a timer here time.sleep(.1) except Exception: logger.warning('Could not mount {0}, trying other method'.format(self.paths[0]), exc_info=True) continue else: raw_path = self.get_raw_path() logger.debug("Raw path to disk is {}".format(raw_path)) if self.method == 'auto': self.method = cmd[0] return raw_path is not None logger.error('Unable to mount {0}'.format(self.paths[0]), exc_info=True) os.rmdir(self.mountpoint) self.mountpoint = "" return False
def mount(self, volume): """Command that is an alternative to the :func:`mount` command that opens a LUKS container. The opened volume is added to the subvolume set of this volume. Requires the user to enter the key manually. TODO: add support for :attr:`keys` :return: the Volume contained in the LUKS container, or None on failure. :raises NoLoopbackAvailableError: when no free loopback could be found :raises IncorrectFilesystemError: when this is not a LUKS volume :raises SubsystemError: when the underlying command fails """ # Open a loopback device volume._find_loopback() # Check if this is a LUKS device # noinspection PyBroadException try: _util.check_call_(["cryptsetup", "isLuks", volume.loopback], stderr=subprocess.STDOUT) # ret = 0 if isLuks except Exception: logger.warning("Not a LUKS volume") # clean the loopback device, we want this method to be clean as possible # noinspection PyBroadException try: volume._free_loopback() except Exception: pass raise IncorrectFilesystemError() try: extra_args = [] key = None if volume.key: t, v = volume.key.split(':', 1) if t == 'p': # passphrase key = v elif t == 'f': # key-file extra_args = ['--key-file', v] elif t == 'm': # master-key-file extra_args = ['--master-key-file', v] else: logger.warning("No key material provided for %s", volume) except ValueError: logger.exception("Invalid key material provided (%s) for %s. Expecting [arg]:[value]", volume.key, volume) volume._free_loopback() raise ArgumentError() # Open the LUKS container volume._paths['luks'] = 'image_mounter_luks_' + str(random.randint(10000, 99999)) # noinspection PyBroadException try: cmd = ["cryptsetup", "luksOpen", volume.loopback, volume._paths['luks']] cmd.extend(extra_args) if not volume.disk.read_write: cmd.insert(1, '-r') if key is not None: logger.debug('$ {0}'.format(' '.join(cmd))) # for py 3.2+, we could have used input=, but that doesn't exist in py2.7. p = subprocess.Popen(cmd, stdin=subprocess.PIPE, stdout=subprocess.PIPE, stderr=subprocess.PIPE) p.communicate(key.encode("utf-8")) p.wait() retcode = p.poll() if retcode: raise KeyInvalidError() else: _util.check_call_(cmd) except ImageMounterError: del volume._paths['luks'] volume._free_loopback() raise except Exception as e: del volume._paths['luks'] volume._free_loopback() raise SubsystemError(e) size = None # noinspection PyBroadException try: result = _util.check_output_(["cryptsetup", "status", volume._paths['luks']]) for l in result.splitlines(): if "size:" in l and "key" not in l: size = int(l.replace("size:", "").replace("sectors", "").strip()) * volume.disk.block_size except Exception: pass container = volume.volumes._make_single_subvolume(flag='alloc', offset=0, size=size) container.info['fsdescription'] = 'LUKS Volume' return container
def unmount(self): """Unounts the volume from the filesystem. :raises SubsystemError: if one of the underlying processes fails :raises CleanupError: if the cleanup fails """ for volume in self.volumes: try: volume.unmount() except ImageMounterError: pass if self.is_mounted: logger.info("Unmounting volume %s", self) if self.loopback and self.info.get('volume_group'): _util.check_call_(["lvm", 'vgchange', '-a', 'n', self.info['volume_group']], wrap_error=True, stdout=subprocess.PIPE) self.info['volume_group'] = "" if self.loopback and self._paths.get('luks'): _util.check_call_(['cryptsetup', 'luksClose', self._paths['luks']], wrap_error=True, stdout=subprocess.PIPE) del self._paths['luks'] if self._paths.get('bde'): _util.clean_unmount(['fusermount', '-u'], self._paths['bde']) del self._paths['bde'] if self._paths.get('md'): md_path = self._paths['md'] del self._paths['md'] # removing it here to ensure we do not enter an infinite loop, will add it back later # MD arrays are a bit complicated, we also check all other volumes that are part of this array and # unmount them as well. logger.debug("All other volumes that use %s as well will also be unmounted", md_path) for v in self.disk.get_volumes(): if v != self and v._paths.get('md') == md_path: v.unmount() try: _util.check_output_(["mdadm", '--stop', md_path], stderr=subprocess.STDOUT) except Exception as e: self._paths['md'] = md_path raise SubsystemError(e) if self._paths.get('vss'): _util.clean_unmount(['fusermount', '-u'], self._paths['vss']) del self._paths['vss'] if self.loopback: _util.check_call_(['losetup', '-d', self.loopback], wrap_error=True) self.loopback = "" if self._paths.get('bindmounts'): for mp in self._paths['bindmounts']: _util.clean_unmount(['umount'], mp, rmdir=False) del self._paths['bindmounts'] if self.mountpoint: _util.clean_unmount(['umount'], self.mountpoint) self.mountpoint = "" if self._paths.get('carve'): try: shutil.rmtree(self._paths['carve']) except OSError as e: raise SubsystemError(e) else: del self._paths['carve'] self.is_mounted = False
def mount(self): """Mounts the base image on a temporary location using the mount method stored in :attr:`method`. If mounting was successful, :attr:`mountpoint` is set to the temporary mountpoint. If :attr:`read_write` is enabled, a temporary read-write cache is also created and stored in :attr:`rwpath`. :return: whether the mounting was successful :rtype: bool """ if self.parser.casename: self.mountpoint = tempfile.mkdtemp(prefix='image_mounter_', suffix='_' + self.parser.casename) else: self.mountpoint = tempfile.mkdtemp(prefix='image_mounter_') if self.read_write: self.rwpath = tempfile.mkstemp(prefix="image_mounter_rw_cache_")[1] disk_type = self.get_disk_type() methods = self._get_mount_methods(disk_type) cmds = [] for method in methods: if method == 'avfs': # avfs does not participate in the fallback stuff, unfortunately self._mount_avfs() self.disk_mounter = method self.was_mounted = True self.is_mounted = True return elif method == 'dummy': os.rmdir(self.mountpoint) self.mountpoint = "" logger.debug("Raw path to dummy is {}".format( self.get_raw_path())) self.disk_mounter = method self.was_mounted = True self.is_mounted = True return elif method == 'xmount': cmds.append([ 'xmount', '--in', 'ewf' if disk_type == 'encase' else 'dd' ]) if self.read_write: cmds[-1].extend(['--rw', self.rwpath]) cmds[-1].extend( self.paths) # specify all paths, xmount needs this :( cmds[-1].append(self.mountpoint) elif method == 'affuse': cmds.extend([[ 'affuse', '-o', 'allow_other', self.paths[0], self.mountpoint ], ['affuse', self.paths[0], self.mountpoint]]) elif method == 'ewfmount': cmds.extend([[ 'ewfmount', '-X', 'allow_other', self.paths[0], self.mountpoint ], ['ewfmount', self.paths[0], self.mountpoint]]) elif method == 'vmware-mount': cmds.append([ 'vmware-mount', '-r', '-f', self.paths[0], self.mountpoint ]) elif method == 'nbd': _util.check_output_(['modprobe', 'nbd', 'max_part=63']) # Load nbd driver try: self._paths['nbd'] = _util.get_free_nbd_device( ) # Get free nbd device except NoNetworkBlockAvailableError: logger.warning("No free network block device found.", exc_info=True) raise cmds.extend([[ 'qemu-nbd', '--read-only', '-c', self._paths['nbd'], self.paths[0] ]]) else: raise ArgumentError("Unknown mount method {0}".format( self.disk_mounter)) for cmd in cmds: # noinspection PyBroadException try: _util.check_call_(cmd, stdout=subprocess.PIPE) # mounting does not seem to be instant, add a timer here time.sleep(.1) except Exception: logger.warning( 'Could not mount {0}, trying other method'.format( self.paths[0]), exc_info=True) continue else: raw_path = self.get_raw_path() logger.debug("Raw path to disk is {}".format(raw_path)) self.disk_mounter = cmd[0] if raw_path is None: raise MountpointEmptyError() self.was_mounted = True self.is_mounted = True return logger.error('Unable to mount {0}'.format(self.paths[0])) os.rmdir(self.mountpoint) self.mountpoint = "" raise MountError()
def _free_loopback(self, var_name='loopback'): if getattr(self, var_name): _util.check_call_(['losetup', '-d', getattr(self, var_name)], wrap_error=True) setattr(self, var_name, "")
def unmount(self, allow_lazy=False): """Unounts the volume from the filesystem. :raises SubsystemError: if one of the underlying processes fails :raises CleanupError: if the cleanup fails """ for volume in self.volumes: try: volume.unmount(allow_lazy=allow_lazy) except ImageMounterError: pass if self.is_mounted: logger.info("Unmounting volume %s", self) if self.loopback and self.info.get('volume_group'): _util.check_call_( ["lvm", 'vgchange', '-a', 'n', self.info['volume_group']], wrap_error=True, stdout=subprocess.PIPE) self.info['volume_group'] = "" if self.loopback and self._paths.get('luks'): _util.check_call_(['cryptsetup', 'luksClose', self._paths['luks']], wrap_error=True, stdout=subprocess.PIPE) del self._paths['luks'] if self._paths.get('bde'): try: _util.clean_unmount(['fusermount', '-u'], self._paths['bde']) except SubsystemError: if not allow_lazy: raise _util.clean_unmount(['fusermount', '-uz'], self._paths['bde']) del self._paths['bde'] if self._paths.get('md'): md_path = self._paths['md'] del self._paths[ 'md'] # removing it here to ensure we do not enter an infinite loop, will add it back later # MD arrays are a bit complicated, we also check all other volumes that are part of this array and # unmount them as well. logger.debug( "All other volumes that use %s as well will also be unmounted", md_path) for v in self.disk.get_volumes(): if v != self and v._paths.get('md') == md_path: v.unmount(allow_lazy=allow_lazy) try: _util.check_output_(["mdadm", '--stop', md_path], stderr=subprocess.STDOUT) except Exception as e: self._paths['md'] = md_path raise SubsystemError(e) if self._paths.get('vss'): try: _util.clean_unmount(['fusermount', '-u'], self._paths['vss']) except SubsystemError: if not allow_lazy: raise _util.clean_unmount(['fusermount', '-uz'], self._paths['vss']) del self._paths['vss'] if self.loopback: _util.check_call_(['losetup', '-d', self.loopback], wrap_error=True) self.loopback = "" if self._paths.get('bindmounts'): for mp in self._paths['bindmounts']: _util.clean_unmount(['umount'], mp, rmdir=False) del self._paths['bindmounts'] if self.mountpoint: _util.clean_unmount(['umount'], self.mountpoint) self.mountpoint = "" if self._paths.get('carve'): try: shutil.rmtree(self._paths['carve']) except OSError as e: raise SubsystemError(e) else: del self._paths['carve'] self.is_mounted = False
def _free_loopback(self, var_name='loopback'): if getattr(self, var_name): _util.check_call_( ['losetup', '-d', getattr(self, var_name)], wrap_error=True) setattr(self, var_name, "")
def mount(self): """Based on the file system type as determined by :func:`determine_fs_type`, the proper mount command is executed for this volume. The volume is mounted in a temporary path (or a pretty path if :attr:`pretty` is enabled) in the mountpoint as specified by :attr:`mountpoint`. If the file system type is a LUKS container or LVM, additional methods may be called, adding subvolumes to :attr:`volumes` :raises NoMountpointAvailableError: if no mountpoint was found :raises NoLoopbackAvailableError: if no loopback device was found :raises UnsupportedFilesystemError: if the fstype is not supported for mounting :raises SubsystemError: if one of the underlying commands failed """ raw_path = self.get_raw_path() self.determine_fs_type() self._load_fsstat_data() # we need a mountpoint if it is not a lvm or luks volume if self.fstype not in ('luks', 'lvm', 'bde', 'raid', 'volumesystem') and \ self.fstype in FILE_SYSTEM_TYPES: self._make_mountpoint() # Prepare mount command try: def call_mount(type, opts): cmd = ['mount', raw_path, self.mountpoint, '-t', type, '-o', opts] if not self.disk.read_write: cmd[-1] += ',ro' _util.check_output_(cmd, stderr=subprocess.STDOUT) if self.fstype == 'ext': call_mount('ext4', 'noexec,noload,loop,offset=' + str(self.offset)) elif self.fstype == 'ufs': call_mount('ufs', 'ufstype=ufs2,loop,offset=' + str(self.offset)) elif self.fstype == 'ntfs': call_mount('ntfs', 'show_sys_files,noexec,force,loop,offset=' + str(self.offset)) elif self.fstype == 'xfs': call_mount('xfs', 'norecovery,loop,offset=' + str(self.offset)) elif self.fstype == 'hfs+': call_mount('hfsplus', 'force,loop,offset=' + str(self.offset) + ',sizelimit=' + str(self.size)) elif self.fstype in ('iso', 'udf', 'squashfs', 'cramfs', 'minix', 'fat', 'hfs'): mnt_type = {'iso': 'iso9660', 'fat': 'vfat'}.get(self.fstype, self.fstype) call_mount(mnt_type, 'loop,offset=' + str(self.offset)) elif self.fstype == 'vmfs': self._find_loopback() _util.check_call_(['vmfs-fuse', self.loopback, self.mountpoint], stdout=subprocess.PIPE) elif self.fstype == 'unknown': # mounts without specifying the filesystem type cmd = ['mount', raw_path, self.mountpoint, '-o', 'loop,offset=' + str(self.offset)] if not self.disk.read_write: cmd[-1] += ',ro' _util.check_call_(cmd, stdout=subprocess.PIPE) elif self.fstype == 'jffs2': self._open_jffs2() elif self.fstype == 'luks': self._open_luks_container() elif self.fstype == 'bde': self._open_bde_container() elif self.fstype == 'lvm': self._open_lvm() self.volumes.vstype = 'lvm' for _ in self.volumes.detect_volumes('lvm'): pass elif self.fstype == 'raid': self._open_raid_volume() elif self.fstype == 'dir': os.rmdir(self.mountpoint) os.symlink(raw_path, self.mountpoint) elif self.fstype == 'volumesystem': for _ in self.volumes.detect_volumes(): pass else: try: size = self.size / self.disk.block_size except TypeError: size = self.size logger.warning("Unsupported filesystem {0} (type: {1}, block offset: {2}, length: {3})" .format(self, self.fstype, self.offset / self.disk.block_size, size)) raise UnsupportedFilesystemError(self.fstype) self.was_mounted = True self.is_mounted = True except Exception as e: logger.exception("Execution failed due to {} {}".format(type(e), e), exc_info=True) try: if self.mountpoint: os.rmdir(self.mountpoint) self.mountpoint = "" if self.loopback: self.loopback = "" except Exception as e2: logger.exception("Clean-up failed", exc_info=True) if not isinstance(e, ImageMounterError): raise SubsystemError(e) else: raise
def unmount(self, allow_lazy=False): if self.vgname: _util.check_call_(["lvm", 'vgchange', '-a', 'n', self.vgname], wrap_error=True, stdout=subprocess.PIPE) self.vgname = None
def mount(self): """Mounts the base image on a temporary location using the mount method stored in :attr:`method`. If mounting was successful, :attr:`mountpoint` is set to the temporary mountpoint. If :attr:`read_write` is enabled, a temporary read-write cache is also created and stored in :attr:`rwpath`. :return: whether the mounting was successful :rtype: bool """ if self.parser.casename: self.mountpoint = tempfile.mkdtemp(prefix="image_mounter_", suffix="_" + self.parser.casename) else: self.mountpoint = tempfile.mkdtemp(prefix="image_mounter_") if self.read_write: self.rwpath = tempfile.mkstemp(prefix="image_mounter_rw_cache_")[1] disk_type = self.get_disk_type() methods = self._get_mount_methods(disk_type) cmds = [] for method in methods: if method == "avfs": # avfs does not participate in the fallback stuff, unfortunately self._mount_avfs() self.disk_mounter = method self.was_mounted = True self.is_mounted = True return elif method == "dummy": os.rmdir(self.mountpoint) self.mountpoint = "" logger.debug("Raw path to dummy is {}".format(self.get_raw_path())) self.disk_mounter = method self.was_mounted = True self.is_mounted = True return elif method == "xmount": cmds.append(["xmount", "--in", "ewf" if disk_type == "encase" else "dd"]) if self.read_write: cmds[-1].extend(["--rw", self.rwpath]) cmds[-1].extend(self.paths) # specify all paths, xmount needs this :( cmds[-1].append(self.mountpoint) elif method == "affuse": cmds.extend( [ ["affuse", "-o", "allow_other", self.paths[0], self.mountpoint], ["affuse", self.paths[0], self.mountpoint], ] ) elif method == "ewfmount": cmds.extend( [ ["ewfmount", "-X", "allow_other", self.paths[0], self.mountpoint], ["ewfmount", self.paths[0], self.mountpoint], ] ) elif method == "vmware-mount": cmds.append(["vmware-mount", "-r", "-f", self.paths[0], self.mountpoint]) else: raise ArgumentError("Unknown mount method {0}".format(self.disk_mounter)) for cmd in cmds: # noinspection PyBroadException try: _util.check_call_(cmd, stdout=subprocess.PIPE) # mounting does not seem to be instant, add a timer here time.sleep(0.1) except Exception: logger.warning("Could not mount {0}, trying other method".format(self.paths[0]), exc_info=True) continue else: raw_path = self.get_raw_path() logger.debug("Raw path to disk is {}".format(raw_path)) self.disk_mounter = cmd[0] if raw_path is None: raise MountpointEmptyError() self.was_mounted = True self.is_mounted = True return logger.error("Unable to mount {0}".format(self.paths[0])) os.rmdir(self.mountpoint) self.mountpoint = "" raise MountError()
def mount(self, volume): """Command that is an alternative to the :func:`mount` command that opens a LUKS container. The opened volume is added to the subvolume set of this volume. Requires the user to enter the key manually. TODO: add support for :attr:`keys` :return: the Volume contained in the LUKS container, or None on failure. :raises NoLoopbackAvailableError: when no free loopback could be found :raises IncorrectFilesystemError: when this is not a LUKS volume :raises SubsystemError: when the underlying command fails """ # Open a loopback device volume._find_loopback() # Check if this is a LUKS device # noinspection PyBroadException try: _util.check_call_(["cryptsetup", "isLuks", volume.loopback], stderr=subprocess.STDOUT) # ret = 0 if isLuks except Exception: logger.warning("Not a LUKS volume") # clean the loopback device, we want this method to be clean as possible # noinspection PyBroadException try: volume._free_loopback() except Exception: pass raise IncorrectFilesystemError() try: extra_args = [] key = None if volume.key: t, v = volume.key.split(':', 1) if t == 'p': # passphrase key = v elif t == 'f': # key-file extra_args = ['--key-file', v] elif t == 'm': # master-key-file extra_args = ['--master-key-file', v] else: logger.warning("No key material provided for %s", volume) except ValueError: logger.exception( "Invalid key material provided (%s) for %s. Expecting [arg]:[value]", volume.key, volume) volume._free_loopback() raise ArgumentError() # Open the LUKS container volume._paths['luks'] = 'image_mounter_luks_' + str( random.randint(10000, 99999)) # noinspection PyBroadException try: cmd = [ "cryptsetup", "luksOpen", volume.loopback, volume._paths['luks'] ] cmd.extend(extra_args) if not volume.disk.read_write: cmd.insert(1, '-r') if key is not None: logger.debug('$ {0}'.format(' '.join(cmd))) # for py 3.2+, we could have used input=, but that doesn't exist in py2.7. p = subprocess.Popen(cmd, stdin=subprocess.PIPE, stdout=subprocess.PIPE, stderr=subprocess.PIPE) p.communicate(key.encode("utf-8")) p.wait() retcode = p.poll() if retcode: raise KeyInvalidError() else: _util.check_call_(cmd) except ImageMounterError: del volume._paths['luks'] volume._free_loopback() raise except Exception as e: del volume._paths['luks'] volume._free_loopback() raise SubsystemError(e) size = None # noinspection PyBroadException try: result = _util.check_output_( ["cryptsetup", "status", volume._paths['luks']]) for l in result.splitlines(): if "size:" in l and "key" not in l: size = int( l.replace("size:", "").replace( "sectors", "").strip()) * volume.disk.block_size except Exception: pass container = volume.volumes._make_single_subvolume(flag='alloc', offset=0, size=size) container.info['fsdescription'] = 'LUKS Volume' return container
def mount(self, volume): """Perform specific operations to mount a JFFS2 image. This kind of image is sometimes used for things like bios images. so external tools are required but given this method you don't have to memorize anything and it works fast and easy. Note that this module might not yet work while mounting multiple images at the same time. """ # we have to make a ram-device to store the image, we keep 20% overhead size_in_kb = int((volume.size / 1024) * 1.2) _util.check_call_(['modprobe', '-v', 'mtd']) _util.check_call_(['modprobe', '-v', 'jffs2']) _util.check_call_([ 'modprobe', '-v', 'mtdram', 'total_size={}'.format(size_in_kb), 'erase_size=256' ]) _util.check_call_(['modprobe', '-v', 'mtdblock']) _util.check_call_( ['dd', 'if=' + volume.get_raw_path(), 'of=/dev/mtd0']) _util.check_call_( ['mount', '-t', 'jffs2', '/dev/mtdblock0', volume.mountpoint])