Пример #1
0
    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
Пример #2
0
    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
Пример #3
0
    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
Пример #4
0
    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)
Пример #5
0
    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
Пример #6
0
 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
Пример #7
0
 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)
Пример #8
0
 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
Пример #9
0
 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
Пример #10
0
 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
Пример #11
0
    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
Пример #12
0
    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
Пример #13
0
    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
Пример #14
0
    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')
Пример #15
0
    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()
Пример #16
0
    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()
Пример #17
0
    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')
Пример #18
0
    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)
Пример #19
0
    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)
Пример #20
0
    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
Пример #21
0
    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
Пример #22
0
    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)
Пример #23
0
    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
Пример #24
0
    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)
Пример #25
0
    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)
Пример #26
0
    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
Пример #27
0
    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
Пример #28
0
    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
Пример #29
0
    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
Пример #30
0
    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()
Пример #31
0
    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
Пример #32
0
    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
Пример #33
0
    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
Пример #34
0
    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
Пример #35
0
    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
Пример #36
0
    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()
Пример #37
0
 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, "")
Пример #38
0
    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
Пример #39
0
 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, "")
Пример #40
0
    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
Пример #41
0
 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
Пример #42
0
    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()
Пример #43
0
    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
Пример #44
0
    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])